diff --git a/docs/html-intl/intl/in/guide/components/activities.jd b/docs/html-intl/intl/in/guide/components/activities.jd new file mode 100644 index 0000000000000000000000000000000000000000..6cac69638f49a6cc37da974ee4debd85ab9d26ba --- /dev/null +++ b/docs/html-intl/intl/in/guide/components/activities.jd @@ -0,0 +1,756 @@ +page.title=Aktivitas +page.tags=aktivitas,intent +@jd:body + +
+
+

Dalam dokumen ini

+
    +
  1. Membuat Aktivitas +
      +
    1. Mengimplementasikan antarmuka pengguna
    2. +
    3. Mendeklarasikan aktivitas dalam manifes
    4. +
    +
  2. +
  3. Memulai Aktivitas +
      +
    1. Memulai aktivitas agar berhasil
    2. +
    +
  4. +
  5. Mematikan Aktivitas
  6. +
  7. Mengelola Daur Hidup Aktivitas +
      +
    1. Mengimplementasikan callback daur hidup
    2. +
    3. Menyimpan status aktivitas
    4. +
    5. Menangani perubahan konfigurasi
    6. +
    7. Mengoordinasikan aktivitas
    8. +
    +
  8. +
+ +

Kelas-kelas utama

+
    +
  1. {@link android.app.Activity}
  2. +
+ +

Lihat juga

+
    +
  1. Tugas dan +Back-Stack
  2. +
+ +
+
+ + + +

{@link android.app.Activity} adalah sebuah komponen aplikasi yang menyediakan layar yang digunakan +pengguna untuk berinteraksi guna melakukan sesuatu, misalnya memilih nomor telepon, mengambil foto, mengirim email, atau +menampilkan peta. Tiap aktivitas diberi sebuah jendela untuk menggambar antarmuka penggunanya. Jendela ini +biasanya mengisi layar, namun mungkin lebih kecil daripada layar dan mengambang di atas +jendela lain.

+ +

Sebuah aplikasi biasanya terdiri atas beberapa aktivitas yang terikat secara longgar +satu sama lain. Biasanya, satu aktivitas dalam aplikasi ditetapkan sebagai aktivitas "utama", yang +ditampilkan kepada pengguna saat membuka aplikasi untuk pertama kali. Tiap +aktivitas kemudian bisa memulai aktivitas lain untuk melakukan berbagai tindakan. Tiap kali +aktivitas baru dimulai, aktivitas sebelumnya akan dihentikan, namun sistem mempertahankan aktivitas +dalam sebuah tumpukan ("back-stack"). Bila sebuah aktivitas baru dimulai, aktivitas itu akan didorong ke atas back-stack dan +mengambil fokus pengguna. Back-stack mematuhi mekanisme dasar tumpukan "masuk terakhir, keluar pertama", +jadi, bila pengguna selesai dengan aktivitas saat ini dan menekan tombol Back, aktivitas +akan dikeluarkan dari tumpukan (dan dimusnahkan) dan aktivitas sebelumnya akan dilanjutkan. (Back-stack +dibahas selengkapnya dalam dokumen Tugas +dan Back-Stack.)

+ +

Bila aktivitas dihentikan karena ada aktivitas baru yang dimulai, aktivitas lama akan diberi tahu tentang perubahan status ini +melalui metode callback daur hidupnya. +Ada beberapa metode callback yang mungkin diterima aktivitas, karena sebuah perubahan dalam +statusnya—apakah sistem sedang membuatnya, menghentikannya, melanjutkannya, atau menghapuskannya—dan +masing-masing callback memberi Anda kesempatan melakukan pekerjaan tertentu yang +sesuai untuk perubahan status itu. Misalnya, bila dihentikan, aktivitas Anda harus melepas +objek besar, seperti koneksi jaringan atau database. Bila aktivitas dilanjutkan, Anda bisa +memperoleh kembali sumber daya yang diperlukan dan melanjutkan tindakan yang terputus. Transisi status ini +semuanya bagian dari daur hidup aktivitas.

+ +

Bagian selebihnya dari dokumen ini membahas dasar-dasar cara membuat dan menggunakan aktivitas, +yang meliputi satu pembahasan lengkap tentang cara kerja daur hidup aktivitas, sehingga Anda bisa dengan benar mengelola +transisi di antara berbagai status aktivitas.

+ + + +

Membuat Aktivitas

+ +

Untuk membuat sebuah aktivitas, Anda harus membuat subkelas {@link android.app.Activity} (atau +subkelasnya yang ada). Dalam subkelas itu, Anda perlu mengimplementasikan metode-metode callback yang +dipanggil sistem saat aktivitas bertransisi di antara berbagai status daur hidupnya, misalnya saat +aktivitas sedang dibuat, dihentikan, dilanjutkan, atau dimusnahkan. Dua metode callback +terpenting adalah:

+ +
+
{@link android.app.Activity#onCreate onCreate()}
+
Anda harus mengimplementasikan metode ini. Sistem memanggilnya saat membuat + aktivitas Anda. Dalam implementasi, Anda harus menginisialisasi komponen-komponen esensial +aktivitas. + Yang terpenting, inilah tempat Anda harus memanggil {@link android.app.Activity#setContentView + setContentView()} untuk mendefinisikan layout untuk antarmuka pengguna aktivitas.
+
{@link android.app.Activity#onPause onPause()}
+
Sistem memanggil metode ini sebagai pertanda pertama bahwa pengguna sedang meninggalkan +aktivitas Anda (walau itu tidak selalu berarti aktivitas sedang dimusnahkan). Inilah biasanya tempat Anda +harus mengikat setiap perubahan yang harus dipertahankan selepas sesi pengguna saat ini (karena +pengguna mungkin tidak kembali).
+
+ +

Ada beberapa metode callback daur hidup lainnya yang harus Anda gunakan untuk memberikan +pengalaman pengguna yang mengalir di antara aktivitas dan menangani interupsi tidak terduga yang menyebabkan aktivitas Anda +dihentikan dan bahkan dimusnahkan. Semua metode callback daur hidup akan dibahas nanti, di +bagian tentang Mengelola Daur Hidup Aktivitas.

+ + + +

Mengimplementasikan antarmuka pengguna

+ +

Antarmuka pengguna aktivitas disediakan oleh hierarki objek—tampilan yang diturunkan +dari kelas {@link android.view.View}. Tiap tampilan mengontrol sebuah ruang persegi panjang tertentu +dalam jendela aktivitas dan bisa merespons interaksi pengguna. Misalnya, sebuah tampilan mungkin berupa sebuah +tombol yang mengawali suatu tindakan bila pengguna menyentuhnya.

+ +

Android menyediakan sejumlah tampilan siap-dibuat yang bisa Anda gunakan untuk mendesain dan mengatur +layout. "Widget" adalah tampilan yang menyediakan elemen-elemen visual (dan interaktif) untuk layar, +misalnya tombol, bidang teks, kotak cek, atau sekadar sebuah gambar. "Layout" adalah tampilan yang diturunkan dari {@link +android.view.ViewGroup} yang memberikan sebuah model layout unik untuk tampilan anaknya, misalnya +layout linier, layout grid, atau layout relatif. Anda juga bisa mensubkelaskan kelas-kelas {@link android.view.View} dan +{@link android.view.ViewGroup} (atau subkelas yang ada) untuk membuat widget dan +layout Anda sendiri dan menerapkannya ke layout aktivitas Anda.

+ +

Cara paling umum untuk mendefinisikan layout dengan menggunakan tampilan adalah dengan file layout XML yang disimpan dalam +sumber daya aplikasi Anda. Dengan cara ini, Anda bisa memelihara desain antarmuka pengguna Anda secara terpisah dari +kode yang mendefinisikan perilaku aktivitas. Anda bisa mengatur layout sebagai UI +aktivitas Anda dengan {@link android.app.Activity#setContentView(int) setContentView()}, dengan meneruskan +ID sumber daya untuk layout itu. Akan tetapi, Anda juga bisa membuat {@link android.view.View} baru dalam +kode aktivitas dan membuat hierarki tampilan dengan menyisipkan {@link +android.view.View} baru ke dalam {@link android.view.ViewGroup}, kemudian menggunakan layout itu dengan meneruskan akar +{@link android.view.ViewGroup} ke {@link android.app.Activity#setContentView(View) +setContentView()}.

+ +

Untuk informasi tentang cara membuat antarmuka pengguna, lihat dokumentasi Antarmuka Pengguna.

+ + + +

Mendeklarasikan aktivitas dalam manifes

+ +

Anda harus mendeklarasikan aktivitas dalam file manifes agar file itu +bisa diakses oleh sistem. Untuk mendeklarasikan aktivitas, bukalah file manifes Anda dan tambahkan sebuah elemen {@code <activity>} +sebagai anak elemen {@code <application>} +. Misalnya:

+ +
+<manifest ... >
+  <application ... >
+      <activity android:name=".ExampleActivity" />
+      ...
+  </application ... >
+  ...
+</manifest >
+
+ +

Ada beberapa atribut lain yang bisa Anda sertakan dalam elemen ini, untuk mendefinisikan properti +misalnya label untuk aktivitas, ikon untuk aktivitas, atau tema untuk memberi gaya ke +UI aktivitas. Atribut {@code android:name} + adalah satu-satunya atribut yang diperlukan—atribut ini menetapkan nama kelas aktivitas. Setelah +Anda mempublikasikan aplikasi, Anda tidak boleh mengubah nama ini, karena jika melakukannya, Anda bisa merusak +sebagian fungsionalitas, misalnya pintasan aplikasi (bacalah posting blog berjudul Things +That Cannot Change).

+ +

Lihat acuan elemen {@code <activity>} +untuk informasi selengkapnya tentang cara mendeklarasikan aktivitas Anda dalam manifes.

+ + +

Menggunakan filter intent

+ +

Elemen {@code +<activity>} juga bisa menetapkan berbagai filter intent—dengan menggunakan elemen {@code +<intent-filter>} —untuk mendeklarasikan cara komponen aplikasi lain +mengaktifkannya.

+ +

Bila Anda membuat aplikasi baru dengan Android SDK Tools, aktivitas stub +yang dibuat untuk Anda secara otomatis menyertakan filter intent yang mendeklarasikan respons +aktivitas pada tindakan "main" (utama) dan harus diletakkan dalam kategori "launcher"). Filter intent +terlihat seperti ini:

+ +
+<activity android:name=".ExampleActivity" android:icon="@drawable/app_icon">
+    <intent-filter>
+        <action android:name="android.intent.action.MAIN" />
+        <category android:name="android.intent.category.LAUNCHER" />
+    </intent-filter>
+</activity>
+
+ +

Elemen {@code +<action>} menetapkan bahwa ini adalah titik masuk "main" ke aplikasi. Elemen {@code +<category>} menetapkan bahwa aktivitas ini harus tercantum dalam launcher aplikasi +sistem (untuk memungkinkan pengguna meluncurkan aktivitas ini).

+ +

Jika Anda bermaksud agar aplikasi dimuat dengan sendirinya dan tidak memperbolehkan aplikasi lain +mengaktifkan aktivitasnya, maka Anda tidak memerlukan filter intent lain. Hanya satu aktivitas yang boleh +memiliki tindakan "main" dan kategori "launcher", seperti dalam contoh sebelumnya. Aktivitas yang +tidak ingin Anda sediakan untuk aplikasi lain tidak boleh memiliki filter intent dan Anda bisa +memulai sendiri aktivitas dengan menggunakan intent secara eksplisit (seperti dibahas di bagian berikut).

+ +

Akan tetapi, jika ingin aktivitas Anda merespons intent implisit yang dikirim dari +aplikasi lain (dan aplikasi Anda sendiri), maka Anda harus mendefinisikan filter intent tambahan untuk +aktivitas. Untuk masing-masing tipe intent yang ingin direspons, Anda harus menyertakan sebuah {@code +<intent-filter>} yang menyertakan elemen +{@code +<action>} dan, opsional, sebuah elemen {@code +<category>} dan/atau elemen {@code +<data>}. Elemen-elemen ini menetapkan tipe intent yang bisa +direspons oleh aktivitas Anda.

+ +

Untuk informasi selengkapnya tentang cara aktivitas Anda merespons intent, lihat dokumen Intent dan Filter Intent. +

+ + + +

Memulai Aktivitas

+ +

Anda bisa memulai aktivitas lain dengan memanggil {@link android.app.Activity#startActivity + startActivity()}, dengan meneruskan sebuah {@link android.content.Intent} yang menjelaskan aktivitas + yang ingin Anda mulai. Intent menetapkan aktivitas persis yang ingin Anda mulai atau menjelaskan + tipe tindakan yang ingin Anda lakukan (dan sistem akan memilih aktivitas yang sesuai untuk Anda, +yang bahkan + bisa berasal dari aplikasi berbeda). Intent juga bisa membawa sejumlah kecil data untuk + digunakan oleh aktivitas yang dimulai.

+ +

Saat bekerja dalam aplikasi sendiri, Anda nanti akan sering meluncurkan aktivitas yang dikenal saja. + Anda bisa melakukannya dengan membuat intent yang mendefinisikan secara eksplisit aktivitas yang ingin Anda mulai, +dengan menggunakan nama kelas. Misalnya, beginilah cara satu aktivitas memulai aktivitas lain bernama {@code +SignInActivity}:

+ +
+Intent intent = new Intent(this, SignInActivity.class);
+startActivity(intent);
+
+ +

Akan tetapi, aplikasi Anda mungkin juga perlu melakukan beberapa tindakan, misalnya mengirim email, + pesan teks, atau pembaruan status, dengan menggunakan data dari aktivitas Anda. Dalam hal ini, aplikasi Anda mungkin + tidak memiliki aktivitasnya sendiri untuk melakukan tindakan tersebut, sehingga Anda bisa memanfaatkan aktivitas + yang disediakan oleh aplikasi lain pada perangkat, yang bisa melakukan tindakan itu untuk Anda. Inilah saatnya +intent benar-benar berharga—Anda bisa membuat intent yang menjelaskan tindakan yang ingin +dilakukan dan sistem + akan meluncurkan aktivitas yang tepat dari aplikasi lain. Jika ada + beberapa aktivitas yang bisa menangani intent itu, pengguna bisa memilih aktivitas yang akan digunakan. Misalnya, + jika Anda ingin memperbolehkan pengguna mengirim pesan email, Anda bisa membuat + intent berikut:

+ +
+Intent intent = new Intent(Intent.ACTION_SEND);
+intent.putExtra(Intent.EXTRA_EMAIL, recipientArray);
+startActivity(intent);
+
+ +

Ekstra {@link android.content.Intent#EXTRA_EMAIL} yang ditambahkan ke intent adalah sebuah larik string + alamat email yang menjadi tujuan pengiriman email. Bila aplikasi email merespons intent ini, + aplikasi itu akan membaca larik string yang disediakan dalam ekstra dan meletakkannya dalam bidang "to" + pada formulir penulisan email. Dalam situasi ini, aktivitas aplikasi email dimulai dan bila + pengguna selesai, aktivitas Anda akan dilanjutkan.

+ + + + +

Memulai aktivitas agar berhasil

+ +

Kadang-kadang, Anda mungkin ingin menerima hasil dari aktivitas yang Anda mulai. Dalam hal itu, + mulailah aktivitas dengan memanggil {@link android.app.Activity#startActivityForResult + startActivityForResult()} (sebagai ganti {@link android.app.Activity#startActivity + startActivity()}). Untuk menerima hasil dari +aktivitas selanjutnya nanti, implementasikan metode callback {@link android.app.Activity#onActivityResult onActivityResult()} +. Bila aktivitas selanjutnya selesai, aktivitas akan mengembalikan hasil dalam {@link +android.content.Intent} kepada metode {@link android.app.Activity#onActivityResult onActivityResult()} +Anda.

+ +

Misalnya, mungkin Anda ingin pengguna mengambil salah satu kontaknya, sehingga aktivitas Anda bisa +melakukan sesuatu dengan informasi dalam kontak itu. Begini caranya membuat intent tersebut dan +menangani hasilnya:

+ +
+private void pickContact() {
+    // Create an intent to "pick" a contact, as defined by the content provider URI
+    Intent intent = new Intent(Intent.ACTION_PICK, Contacts.CONTENT_URI);
+    startActivityForResult(intent, PICK_CONTACT_REQUEST);
+}
+
+@Override
+protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+    // If the request went well (OK) and the request was PICK_CONTACT_REQUEST
+    if (resultCode == Activity.RESULT_OK && requestCode == PICK_CONTACT_REQUEST) {
+        // Perform a query to the contact's content provider for the contact's name
+        Cursor cursor = getContentResolver().query(data.getData(),
+        new String[] {Contacts.DISPLAY_NAME}, null, null, null);
+        if (cursor.moveToFirst()) { // True if the cursor is not empty
+            int columnIndex = cursor.getColumnIndex(Contacts.DISPLAY_NAME);
+            String name = cursor.getString(columnIndex);
+            // Do something with the selected contact's name...
+        }
+    }
+}
+
+ +

Contoh ini menunjukkan logika dasar yang harus Anda gunakan dalam metode {@link +android.app.Activity#onActivityResult onActivityResult()} Anda untuk menangani +hasil aktivitas. Syarat pertama memeriksa apakah permintaan berhasil—jika ya, maka + {@code resultCode} akan berupa {@link android.app.Activity#RESULT_OK}—dan apakah permintaan +yang direspons hasil ini dikenal—dalam hal ini, {@code requestCode} cocok dengan +parameter kedua yang dikirim dengan {@link android.app.Activity#startActivityForResult +startActivityForResult()}. Dari sana, kode akan menangani hasil aktivitas dengan membuat query +data yang dihasilkan dalam{@link android.content.Intent} (parameter {@code data}).

+ +

Yang terjadi adalah {@link +android.content.ContentResolver} melakukan query terhadap penyedia konten, yang menghasilkan +{@link android.database.Cursor} yang memperbolehkan data query dibaca. Untuk informasi selengkapnya, lihat dokumen +Penyedia Konten.

+ +

Untuk informasi selengkapnya tentang menggunakan intent, lihat dokumen Intent dan Filter +Intent.

+ + +

Mematikan Aktivitas

+ +

Anda bisa mematikan aktivitas dengan memanggil metode {@link android.app.Activity#finish +finish()}-nya. Anda juga bisa mematikan aktivitas terpisah yang sebelumnya Anda mulai dengan memanggil +{@link android.app.Activity#finishActivity finishActivity()}.

+ +

Catatan: Pada umumnya, Anda tidak boleh secara eksplisit mengakhiri aktivitas +dengan menggunakan metode-metode ini. Seperti yang dibahas di bagian berikut tentang daur hidup aktivitas, +sistem Android mengelola hidup aktivitas untuk Anda, sehingga Anda tidak perlu menyelesaikan sendiri +aktivitas tersebut. Memanggil metode-metode ini bisa berpengaruh negatif pada pengalaman +pengguna yang diharapkan dan hanya boleh digunakan bila Anda benar-benar tidak ingin pengguna kembali ke +instance aktivitas ini.

+ + +

Mengelola Daur Hidup Aktivitas

+ +

Mengelola daur hidup aktivitas dengan mengimplementasikan metode-metode callback sangat +penting untuk mengembangkan +aplikasi yang kuat dan fleksibel. Daur hidup aktivitas dipengaruhi langsung oleh kaitannya dengan +aktivitas lain, tugasnya, serta back-stack.

+ +

Pada dasarnya, sebuah aktivitas bisa berada dalam tiga status:

+ +
+
Dilanjutkan
+
Aktivitas berada di latar depan layar dan mendapatkan fokus pengguna. (Status ini +kadang-kadang disebut juga dengan "running" (berjalan).)
+ +
Dihentikan sementara
+
Aktivitas lain berada di latar depan dan mendapat fokus, namun aktivitas ini masih terlihat. Yakni, +aktivitas lain terlihat di atas aplikasi ini dan aktivitas itu setengah transparan atau tidak +menuutpi seluruh layar. Aktivitas yang dihentikan sementara adalah benar-benar hidup (objek {@link android.app.Activity} +dipertahankan dalam memori, objek itu memelihara semua informasi status dan anggota, dan tetap dikaitkan dengan +window manager), namun bisa dimatikan oleh sistem dalam situasi memori sangat rendah.
+ +
Dihentikan
+
Aktivitas ditutupi sepenuhnya oleh aktivitas lain (aktivitas sekarang berada di +"latar belakang"). Aktivitas yang dihentikan juga masih hidup (objek {@link android.app.Activity} +dipertahankan dalam memori, objek itu menjaga semua informasi status dan anggota, namun tidak +dikaitkan dengan window manager). Akan tetapi, aktivitas tidak lagi terlihat bagi pengguna dan +bisa dimatikan oleh sistem bila memori diperlukan di lain.
+
+ +

Jika aktivitas dihentikan sementara atau dihentikan, sistem bisa mengeluarkannya dari memori baik dengan memintanya agar +diakhiri (memanggil metode {@link android.app.Activity#finish finish()}-nya), atau sekadar mematikan +prosesnya. Bila dibuka lagi (setelah diakhiri atau dimatikan), aktivitas harus dibuat dari +awal.

+ + + +

Mengimplementasikan callback daur hidup

+ +

Saat bertransisi ke dalam dan ke luar berbagai status yang dijelaskan di atas, aktivitas diberi tahu +melalui berbagai metode callback. Semua metode callback adalah sangkutan yang +bisa Anda kesampingkan untuk melakukan pekerjaan yang sesuai saat status aktivitas Anda berubah. Aktivitas skeleton +berikut menyertakan setiap metode daur hidup mendasar:

+ + +
+public class ExampleActivity extends Activity {
+    @Override
+    public void {@link android.app.Activity#onCreate onCreate}(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        // The activity is being created.
+    }
+    @Override
+    protected void {@link android.app.Activity#onStart onStart()} {
+        super.onStart();
+        // The activity is about to become visible.
+    }
+    @Override
+    protected void {@link android.app.Activity#onResume onResume()} {
+        super.onResume();
+        // The activity has become visible (it is now "resumed").
+    }
+    @Override
+    protected void {@link android.app.Activity#onPause onPause()} {
+        super.onPause();
+        // Another activity is taking focus (this activity is about to be "paused").
+    }
+    @Override
+    protected void {@link android.app.Activity#onStop onStop()} {
+        super.onStop();
+        // The activity is no longer visible (it is now "stopped")
+    }
+    @Override
+    protected void {@link android.app.Activity#onDestroy onDestroy()} {
+        super.onDestroy();
+        // The activity is about to be destroyed.
+    }
+}
+
+ +

Catatan: Implementasi Anda terhadap metode-metode daur hidup ini harus +selalu memanggil implementasi superkelas sebelum melakukan pekerjaan apa pun, seperti yang ditampilkan dalam contoh-contoh di atas.

+ +

Bersama-sama, semua metode ini mendefinisikan seluruh daur hidup sebuah aktivitas. Dengan mengimplementasikan +metode-metode ini, Anda bisa memantau tiga loop tersarang (nested loop) dalam daur hidup aktivitas:

+ + + +

Gambar 1 mengilustrasikan loop dan path yang mungkin diambil sebuah aktivitas di antara status-status. +Persegi panjang mewakili metode callback yang bisa Anda implementasikan untuk melakukan operasi saat +aktivitas bertransisi di antara status.

+ + +

Gambar 1. Daur hidup aktivitas.

+ +

Metode-metode callback daur hidup yang sama tercantum dalam tabel 1, yang menjelaskan setiap metode callback +secara lebih detail dan menentukan lokasinya masing-masing dalam +daur hidup aktivitas keseluruhan, termasuk apakah sistem bisa mematikan aktivitas setelah +metode callback selesai.

+ +

Tabel 1. Rangkuman metode callback +daur hidup aktivitas.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Metode Keterangan Bisa dimatikan setelahnya? Berikutnya
{@link android.app.Activity#onCreate onCreate()}Dipanggil saat aktivitas pertama kali dibuat. + Di sinilah Anda harus melakukan semua persiapan statis normal — + membuat tampilan, mengikat data ke daftar, dan sebagainya. Metode ini diberi + sebuah objek Bundle yang berisi status aktivitas sebelumnya, jika + status itu tertangkap (lihat Menyimpan Status Aktivitas, + nanti). +

Selalu diikuti oleh {@code onStart()}.

Tidak{@code onStart()}
    {@link android.app.Activity#onRestart +onRestart()}Dipanggil setelah aktivitas dihentikan, tepat sebelum + dimulai lagi. +

Selalu diikuti oleh {@code onStart()}

Tidak{@code onStart()}
{@link android.app.Activity#onStart onStart()}Dipanggil tepat sebelum aktivitas menjadi terlihat bagi pengguna. +

Diikuti oleh {@code onResume()} jika aktivitas maju + ke latar depan, atau {@code onStop()} jika menjadi tersembunyi.

Tidak{@code onResume()}
atau
{@code onStop()}
    {@link android.app.Activity#onResume onResume()}Dipanggil tepat sebelum aktivitas mulai + berinteraksi dengan pengguna. Pada titik ini, aktivitas berada di + puncak tumpukan aktivitas, dengan input pengguna menuju kepadanya. +

Selalu diikuti oleh {@code onPause()}.

Tidak{@code onPause()}
{@link android.app.Activity#onPause onPause()}Dipanggil bila sistem akan memulai pelanjutan + aktivitas lain. Metode ini biasanya digunakan untuk menerapkan (commit) perubahan yang tidak tersimpan pada + data persisten, menghentikan animasi dan hal-hal lain yang mungkin menghabiskan + CPU, dan sebagainya. Metode ini harus melakukan apa saja yang dilakukannya dengan sangat cepat, karena + aktivitas berikutnya tidak akan dilanjutkan hingga aktivitas ini kembali. +

Diikuti oleh {@code onResume()} jika aktivitas + kembali ke depan, atau oleh {@code onStop()} jika menjadi + tidak terlihat bagi pengguna.

Ya{@code onResume()}
atau
{@code onStop()}
{@link android.app.Activity#onStop onStop()}Dipanggil bila aktivitas tidak lagi terlihat bagi pengguna. Hal ini + bisa terjadi karena aktivitas sedang dimusnahkan, atau karena aktivitas lain + (aktivitas yang ada atau yang baru) telah dilanjutkan dan sedang menutupinya. +

Diikuti oleh {@code onRestart()} jika + aktivitas kembali untuk berinteraksi dengan pengguna, atau oleh + {@code onDestroy()} jika aktivitas ini akan menghilang.

Ya{@code onRestart()}
atau
{@code onDestroy()}
{@link android.app.Activity#onDestroy +onDestroy()}Dipanggil sebelum aktivitas dimusnahkan. Inilah panggilan terakhir + yang akan diterima aktivitas. Metode ini bisa dipanggil karena + aktivitas selesai (seseorang memanggil {@link android.app.Activity#finish + finish()} padanya), atau karena sistem memusnahkan sementara + instance aktivitas ini untuk menghemat tempat. Anda bisa membedakan + kedua skenario ini dengan metode {@link + android.app.Activity#isFinishing isFinishing()}.Yatidak ada
+ +

Kolom berlabel "Bisa dimatikan setelahnya?" menunjukkan apakah sistem bisa +atau tidak mematikan proses yang menjadi host aktivitas kapan saja setelah metode kembali, tanpa +menjalankan baris lain pada kode aktivitas. Tiga metode ini ditandai "ya": ({@link +android.app.Activity#onPause +onPause()}, {@link android.app.Activity#onStop onStop()}, dan {@link android.app.Activity#onDestroy +onDestroy()}). Karena {@link android.app.Activity#onPause onPause()} adalah yang pertama +dari tiga, begitu aktivitas dibuat, {@link android.app.Activity#onPause onPause()} adalah +metode terakhir yang dipastikan akan dipanggil sebelum proses bisa dimatikan—jika +sistem harus memulihkan memori dalam keadaan darurat, maka {@link +android.app.Activity#onStop onStop()} dan {@link android.app.Activity#onDestroy onDestroy()} mungkin +tidak dipanggil. Karena itu, Anda harus menggunakan {@link android.app.Activity#onPause onPause()} untuk menulis +data persisten yang penting (misalnya hasil edit pengguna) ke penyimpanan. Akan tetapi, Anda harus selektif dalam hal +informasi yang harus dipertahankan selama {@link android.app.Activity#onPause onPause()}, karena setiap +prosedur pemblokiran dalam metode ini akan memblokir transisi ke aktivitas berikutnya dan memperlambat +pengalaman pengguna.

+ +

Metode-metode yang ditandai "Tidak" dalam kolom Bisa dimatikan melindungi proses yang menjadi host +aktivitas dari dimatikan sejak saat metode dipanggil. Jadi, aktivitas bisa dimatikan +sejak {@link android.app.Activity#onPause onPause()} kembali hingga waktu +{@link android.app.Activity#onResume onResume()} dipanggil. Aktivitas tidak akan lagi bisa dimatikan hingga +{@link android.app.Activity#onPause onPause()} dipanggil lagi dan kembali.

+ +

Catatan: Aktivitas yang tidak "bisa dimatikan" secara teknis oleh +definisi dalam tabel 1 masih bisa dimatikan oleh sistem—namun itu hany terjadi dalam +situasi ekstrem bila tidak ada jalan lain. Kapan aktivitas bisa dimatikan +akan dibahas selengkapnya dalam dokumen Proses dan +Threading.

+ + +

Menyimpan status aktivitas

+ +

Pengantar untuk Mengelola Daur Hidup Aktivitas secara ringkas menyebutkan +bahwa +bila aktivitas dihentikan sementara atau dihentikan, status aktivitas akan dipertahankan. Hal itu terjadi karena +objek {@link android.app.Activity} masih ditahan dalam memori saat aktivitas dihentikan sementara atau +dihentikan—semua informasi tentang anggota dan statusnya saat ini masih hidup. Jadi, setiap perubahan +yang dibuat pengguna dalam aktivitas akan dipertahankan sehingga bila aktivitas kembali ke +latar depan (bila "dilanjutkan"), perubahan itu masih ada.

+ +

Akan tetapi, bila sistem memusnahkan aktivitas untuk memulihkan memori, objek {@link +android.app.Activity} akan dimusnahkan, sehingga sistem tidak bisa sekadar melanjutkan aktivitas dengan status +tidak berubah. Sebagai gantinya, sistem harus membuat ulang objek {@link android.app.Activity} jika pengguna +menyusuri kembali ke aktivitas tersebut. Namun, pengguna tidak menyadari +bahwa sistem memusnahkan aktivitas dan membuatnya kembali dan, karena itu, mungkin +mengharapkan aktivitas untuk sama persis dengan sebelumnya. Dalam situasi ini, Anda bisa memastikan bahwa +informasi penting tentang status aktivitas tetap terjaga dengan mengimplementasikan +metode callback tambahan yang memungkinkan Anda menyimpan informasi tentang status aktivitas: {@link +android.app.Activity#onSaveInstanceState onSaveInstanceState()}.

+ +

Sistem memanggil {@link android.app.Activity#onSaveInstanceState onSaveInstanceState()} +sebelum membuat aktivitas rawan terhadap pemusnahan. Sistem meneruskan ke metode ini +sebuah {@link android.os.Bundle} tempat Anda bisa menyimpan +informasi status tentang aktivitas sebagai pasangan nama-nilai, dengan menggunakan metode-metode misalnya {@link +android.os.Bundle#putString putString()} dan {@link +android.os.Bundle#putInt putInt()}. Kemudian, jika sistem mematikan proses aplikasi Anda +dan pengguna menyusuri kembali ke aktivitas tersebut, sistem akan membuat kembali aktivitas dan meneruskan +{@link android.os.Bundle} ke {@link android.app.Activity#onCreate onCreate()} maupun {@link +android.app.Activity#onRestoreInstanceState onRestoreInstanceState()}. Dengan menggunakan salah satu +metode ini, Anda bisa mengekstrak status tersimpan dari {@link android.os.Bundle} dan memulihkan +status aktivitas. Jika tidak ada informasi status untuk dipulihkan, maka {@link +android.os.Bundle} yang diteruskan kepada adalah Anda null (yang akan terjadi bila aktivitas dibuat untuk +pertama kali).

+ + +

Gambar 2. Ada dua cara yang bisa digunakan aktivitas untuk kembali ke fokus pengguna +dengan status tetap: aktivitas dimusnahkan, kemudian dibuat kembali, dan aktivitas harus memulihkan +status yang disimpan sebelumnya, atau aktivitas dihentikan, kemudian dilanjutkan dengan status aktivitas +tetap.

+ +

Catatan: Tidak ada jaminan bahwa {@link +android.app.Activity#onSaveInstanceState onSaveInstanceState()} akan dipanggil sebelum +aktivitas Anda dimusnahkan, karena bisa saja terjadi aktivitas tidak perlu menyimpan status +(misalnya saat pengguna meninggalkan aktivitas Anda dengan menggunakan tombol Back, karena pengguna menutup aktivitas +secara eksplisit +). Jika sistem memanggil {@link android.app.Activity#onSaveInstanceState +onSaveInstanceState()}, ini akan dilakukan sebelum {@link +android.app.Activity#onStop onStop()} dan mungkin sebelum {@link android.app.Activity#onPause +onPause()}.

+ +

Akan tetapi, sekalipun Anda tidak melakukan apa-apa dan tidak mengimplementasikan {@link +android.app.Activity#onSaveInstanceState onSaveInstanceState()}, beberapa status aktivitas +akan dipulihkan oleh implementasi default {@link +android.app.Activity#onSaveInstanceState onSaveInstanceState()} dalam kelas {@link android.app.Activity}. Khususnya, +implementasi default akan memanggil metode {@link +android.view.View#onSaveInstanceState onSaveInstanceState()} yang sesuai untuk setiap {@link +android.view.View} dalam layout, yang memungkinkan setiap tampilan untuk memberi informasi tentang dirinya +yang harus disimpan. Hampir setiap widget dalam kerangka kerja Android mengimplementasikan metode ini +sebagaimana mestinya, sehingga setiap perubahan yang terlihat pada UI akan disimpan dan dipulihkan secara otomatis bila +aktivitas Anda dibuat kembali. Misalnya, widget {@link android.widget.EditText} menyimpan teks apa saja +yang dimasukkan oleh pengguna dan widget {@link android.widget.CheckBox} menyimpan baik teks itu diperiksa maupun +tidak. Satu-satunya pekerjaan yang Anda perlukan adalah memberikan ID unik (dengan atribut {@code android:id} +) untuk masing-masing widget yang ingin disimpan statusnya. Jika widget tidak memiliki ID, maka sistem +tidak bisa menyimpan statusnya.

+ + + +

Walaupun implementasi default {@link +android.app.Activity#onSaveInstanceState onSaveInstanceState()} menyimpan informasi yang berguna tentang +UI aktivitas, Anda mungkin masih perlu mengesampingkannya untuk menyimpan informasi tambahan. +Misalnya, Anda mungkin perlu menyimpan nilai-nilai anggota yang berubah selama masa pakai aktivitas (yang +mungkin berkorelasi dengan nilai-nilai yang dipulihkan dalam UI, namun anggota-anggota yang menyimpan nilai-nilai UI itu tidak +dipulihkan, secara default).

+ +

Karena implementasi default {@link +android.app.Activity#onSaveInstanceState onSaveInstanceState()} membantu menyimpan status UI, jika +Anda mengesampingkan metode ini untuk menyimpan informasi tambahan status, Anda harus selalu memanggil +implementasi superkelas {@link android.app.Activity#onSaveInstanceState onSaveInstanceState()} +sebelum melakukan pekerjaan apa pun. Demikian pula, Anda juga harus memanggil implementasi superkelas {@link +android.app.Activity#onRestoreInstanceState onRestoreInstanceState()} jika Anda mengesampingkannya, sehingga +implementasi default bisa memulihkan status tampilan.

+ +

Catatan: Karena {@link android.app.Activity#onSaveInstanceState +onSaveInstanceState()} tidak dijamin +akan dipanggil, Anda harus menggunakannya hanya untuk mencatat status aktivitas sementara (transient) (status +UI)—Anda tidak boleh menggunakannya untuk menyimpan data persisten. Sebagai gantinya, Anda harus menggunakan {@link +android.app.Activity#onPause onPause()} untuk menyimpan data persisten (misalnya data yang harus disimpan +ke database) saat pengguna meninggalkan aktivitas.

+ +

Salah satu cara yang baik untuk menguji kemampuan aplikasi dalam memulihkan statusnya adalah cukup dengan memutar +perangkat sehingga orientasi layarnya berubah. Bila orientasi layar berubah, sistem +akan memusnahkan dan membuat kembali aktivitas untuk menerapkan sumber daya alternatif yang mungkin tersedia +untuk konfigurasi layar baru. Karena alasan ini saja, sangat penting bahwa aktivitas Anda +memulihkan statusnya secara lengkap saat dibuat kembali, karena pengguna memutar layar secara rutin saat +menggunakan aplikasi.

+ + +

Menangani perubahan konfigurasi

+ +

Sebagian konfigurasi perangkat bisa berubah saat runtime (misalnya orientasi layar, ketersediaan keyboard +, dan bahasa). Bila terjadi perubahan demikian, Android akan membuat kembali aktivitas yang berjalan +(sistem akan memanggil {@link android.app.Activity#onDestroy}, kemudian segera memanggil {@link +android.app.Activity#onCreate onCreate()}). Perilaku ini +didesain untuk membantu aplikasi Anda menyesuaikan diri dengan konfigurasi baru dengan cara memuat ulang +aplikasi Anda secara otomatis dengan sumber daya alternatif yang telah Anda sediakan (misalnya layout yang berbeda untuk +layar orientasi dan ukuran yang berbeda).

+ +

Jika Anda mendesain aktivitas dengan benar untuk menangani restart karena perubahan orientasi layar dan +memulihkan status aktivitas seperti yang dijelaskan di atas, aplikasi Anda akan lebih tahan terhadap +kejadian tidak terduga lainnya dalam daur hidup aktivitas.

+ +

Cara terbaik menangani restart tersebut adalah + menyimpan dan memulihkan status aktivitas Anda dengan menggunakan {@link + android.app.Activity#onSaveInstanceState onSaveInstanceState()} dan {@link +android.app.Activity#onRestoreInstanceState onRestoreInstanceState()} (atau {@link +android.app.Activity#onCreate onCreate()}), seperti yang dibahas di bagian sebelumnya.

+ +

Untuk informasi selengkapnya tentang konfigurasi perubahan yang terjadi saat program berjalan dan cara menanganinya +, bacalah panduan untuk Menangani +Perubahan Runtime.

+ + + +

Mengoordinasikan aktivitas

+ +

Bila suatu aktivitas memulai aktivitas lain, keduanya akan mengalami transisi daur hidup. Aktivitas pertama +akan berhenti sementara dan berhenti sama sekali (walau tidak akan berhenti jika masih terlihat di latar belakang), saat +aktivitas lain dibuat. Jika aktivitas-aktivitas ini berbagi data yang disimpan ke disk atau di tempat lain, Anda perlu +memahami bahwa aktivitas pertama tidak dihentikan sepenuhnya sebelum aktivitas kedua dibuat. +Sebagai gantinya, proses akan memulai aktivitas kedua secara tumpang tindih dengan proses penghentian +aktivitas pertama.

+ +

Urutan callback daur hidup didefinisikan dengan baik, khususnya bila kedua aktivitas berada dalam +proses yang sama dan salah satunya memulai yang lain. Berikut ini adalah urutan operasi yang terjadi bila Aktivitas +A memulai Aktivitas B:

+ +
    +
  1. Metode {@link android.app.Activity#onPause onPause()} Aktivitas A berjalan.
  2. + +
  3. Metode-metode {@link android.app.Activity#onCreate onCreate()}, {@link +android.app.Activity#onStart onStart()}, dan {@link android.app.Activity#onResume onResume()} +Aktivitas B berjalan secara berurutan. (Aktivitas B sekarang mendapatkan fokus pengguna.)
  4. + +
  5. Kemudian, jika Aktivitas A tidak lagi terlihat di layar, metode {@link +android.app.Activity#onStop onStop()}-nya akan dijalankan.
  6. +
+ +

Urutan callback daur hidup yang bisa diramalkan ini memungkinkan Anda mengelola transisi +informasi dari satu aktivitas ke aktivitas lainnya. Misalnya, jika Anda harus menulis ke database saat +aktivitas pertama berhenti agar aktivitas berikutnya bisa membacanya, maka Anda harus menulis ke +database selama {@link android.app.Activity#onPause onPause()} sebagai ganti selama {@link +android.app.Activity#onStop onStop()}.

+ + diff --git a/docs/html-intl/intl/in/guide/components/bound-services.jd b/docs/html-intl/intl/in/guide/components/bound-services.jd new file mode 100644 index 0000000000000000000000000000000000000000..5d1f65ed7c8edfe0e61bf16a9a804672319add74 --- /dev/null +++ b/docs/html-intl/intl/in/guide/components/bound-services.jd @@ -0,0 +1,658 @@ +page.title=Layanan Terikat +parent.title=Layanan +parent.link=services.html +@jd:body + + +
+
    +

    Dalam dokumen ini

    +
      +
    1. Dasar-Dasar
    2. +
    3. Membuat Layanan Terikat +
        +
      1. Memperluas kelas Binder
      2. +
      3. Menggunakan Messenger
      4. +
      +
    4. +
    5. Mengikat ke Layanan
    6. +
    7. Mengelola Daur Hidup Layanan Terikat
    8. +
    + +

    Kelas-kelas utama

    +
      +
    1. {@link android.app.Service}
    2. +
    3. {@link android.content.ServiceConnection}
    4. +
    5. {@link android.os.IBinder}
    6. +
    + +

    Contoh

    +
      +
    1. {@code + RemoteService}
    2. +
    3. {@code + LocalService}
    4. +
    + +

    Lihat juga

    +
      +
    1. Layanan
    2. +
    +
+ + +

Layanan terikat adalah server di antarmuka klien-server. Layanan terikat memungkinkan komponen-komponen +(seperti aktivitas) untuk diikat ke layanan, mengirim permintaan, menerima respons, dan bahkan melakukan +komunikasi antarproses (IPC). Layanan terikat biasanya hidup hanya saat melayani +komponen aplikasi lain dan tidak berjalan di latar belakang terus-menerus.

+ +

Dokumen ini menampilkan cara membuat layanan terikat, termasuk cara mengikat +ke layanan dari komponen aplikasi lain. Akan tetapi, Anda juga harus mengacu dokumen Layanan untuk +informasi tambahan tentang layanan secara umum, seperti cara menyampaikan pemberitahuan dari layanan, mengatur +layanan agar berjalan di latar depan, dan lain-lain.

+ + +

Dasar-Dasar

+ +

Layanan terikat adalah implementasi kelas {@link android.app.Service} yang memungkinkan +aplikasi lain diikat padanya dan berinteraksi dengannya. Untuk menyediakan pengikatan bagi sebuah +layanan, Anda harus mengimplementasikan metode callback {@link android.app.Service#onBind onBind()}. Metode ini +menghasilkan objek {@link android.os.IBinder} yang mendefinisikan antarmuka pemprograman yang +bisa digunakan klien untuk berinteraksi dengan layanan.

+ + + +

Klien bisa mengikat ke layanan dengan memanggil {@link android.content.Context#bindService +bindService()}. Bila itu dilakukan, klien harus menyediakan implementasi {@link +android.content.ServiceConnection}, yang memantau koneksi dengan layanan. Metode {@link +android.content.Context#bindService bindService()} kembali dengan serta-merta tanpa sebuah nilai, namun +bila sistem Android membuat koneksi antara klien +dan layanan, sistem akan memanggil {@link +android.content.ServiceConnection#onServiceConnected onServiceConnected()} pada {@link +android.content.ServiceConnection} untuk mengirim {@link android.os.IBinder} yang +bisa digunakan klien untuk berkomunikasi dengan layanan.

+ +

Beberapa klien bisa terhubung ke layanan dengan serentak. Akan tetapi, sistem akan memanggil metode +{@link android.app.Service#onBind onBind()} layanan Anda untuk mengambil {@link android.os.IBinder} hanya +bila klien pertama mengikat. Sistem lalu memberikan {@link android.os.IBinder} yang sama ke setiap +klien tambahan yang mengikat, tanpa memanggil {@link android.app.Service#onBind onBind()} lagi.

+ +

Bila klien terakhir melepas ikatan dari layanan, sistem akan menghapus layanan (kecuali jika +layanan juga dimulai oleh {@link android.content.Context#startService startService()}).

+ +

Bila Anda mengimplementasikan layanan terikat, yang terpenting adalah mendefinisikan antarmuka +yang dihasilkan metode callback {@link android.app.Service#onBind onBind()} Anda. Ada sedikit +cara mendefinisikan antarmuka {@link android.os.IBinder} layanan Anda dan bagian berikut +akan membahas masing-masing teknik.

+ + + +

Membuat Layanan Terikat

+ +

Saat membuat layanan yang menyediakan pengikatan, Anda harus menyediakan {@link android.os.IBinder} +yang menyediakan antarmuka pemrograman yang bisa digunakan klien untuk berinteraksi dengan layanan. Ada +tiga cara untuk mendefinisikan antarmuka:

+ +
+
Memperluas kelas Binder
+
Jika layanan Anda bersifat privat untuk aplikasi Anda sendiri dan berjalan dalam proses yang sama dengan klien +(biasanya), Anda harus membuat antarmuka dengan memperluas kelas {@link android.os.Binder} +dan menghasilkan instance dari +{@link android.app.Service#onBind onBind()}. Klien akan menerima {@link android.os.Binder} dan +bisa menggunakannya untuk mengakses langsung metode publik yang tersedia dalam implementasi {@link android.os.Binder} +atau bahkan {@link android.app.Service}. +

Inilah teknik yang lebih disukai bila layanan Anda sekadar pekerja latar belakang untuk aplikasi Anda +sendiri. Satu-satunya alasan tidak membuat antarmuka dengan cara ini adalah karena +layanan Anda akan digunakan oleh aplikasi lain atau pada proses-proses terpisah.

+ +
Menggunakan Messenger
+
Jika antarmuka Anda perlu bekerja lintas proses, Anda bisa membuat +antarmuka untuk layanan dengan {@link android.os.Messenger}. Dengan cara ini, layanan +mendefinisikan {@link android.os.Handler} yang akan merespons aneka tipe objek {@link +android.os.Message}. {@link android.os.Handler} +ini adalah dasar bagi {@link android.os.Messenger} yang nanti bisa berbagi {@link android.os.IBinder} +dengan klien, sehingga memungkinkan klien mengirim perintah ke layanan dengan menggunakan objek {@link +android.os.Message}. Selain itu, klien bisa mendefinisikan sendiri {@link android.os.Messenger} +sehingga layanan bisa mengirim balik pesan. +

Inilah cara termudah melakukan komunikasi antarproses (IPC), karena {@link +android.os.Messenger} akan mengantre semua permintaan ke dalam satu thread sehingga Anda tidak perlu mendesain +layanan agar thread-safe.

+
+ +
Menggunakan AIDL
+
AIDL (Android Interface Definition Language) melakukan semua pekerjaan untuk mengurai objek menjadi +primitif yang bisa dipahami dan diarahkan oleh sistem operasi ke berbagai proses untuk melakukan +IPC. Teknik sebelumnya, dengan menggunakan {@link android.os.Messenger}, sebenarnya berdasarkan AIDL sebagai +struktur yang mendasarinya. Seperti disebutkan di atas, {@link android.os.Messenger} membuat antrean +semua permintaan klien dalam satu thread, sehingga layanan akan menerima permintaan satu per satu. Akan tetapi, +jika ingin layanan Anda menangani beberapa permintaan sekaligus, Anda bisa menggunakan AIDL +secara langsung. Dalam hal ini, layanan Anda harus mampu multi-thread dan dibuat thread-safe. +

Untuk menggunakan AIDL secara langsung, Anda harus +membuat file {@code .aidl} yang mendefinisikan antarmuka pemrograman. Alat Android SDK menggunakan +file ini untuk menghasilkan kelas abstrak yang mengimplementasikan antarmuka dan menangani IPC, yang nanti +bisa Anda perluas dalam layanan.

+
+
+ +

Catatan: Umumnya aplikasi tidak boleh menggunakan AIDL untuk +membuat layanan terikat, karena hal itu mungkin memerlukan kemampuan multi-thread dan +bisa mengakibatkan implementasi yang lebih rumit. Dengan demikian, AIDL tidak cocok untuk sebagian besar aplikasi +dan dokumen ini tidak membahas cara menggunakannya untuk layanan Anda. Jika Anda yakin perlu +menggunakan AIDL secara langsung, lihat dokumen AIDL +.

+ + + + +

Memperluas kelas Binder

+ +

Jika layanan Anda hanya digunakan oleh aplikasi lokal dan tidak perlu bekerja lintas proses, +maka Anda bisa mengimplementasikan kelas {@link android.os.Binder} Anda sendiri yang memberi klien Anda +akses langsung ke metode publik dalam layanan.

+ +

Catatan: Hal ini hanya berhasil jika klien dan layanan berada dalam +aplikasi dan proses yang sama, suatu kondisi yang paling umum. Misalnya, cara ini sangat cocok untuk sebuah aplikasi musik +yang perlu mengikat aktivitas ke layanannya sendiri, yakni memutar musik di +latar belakang.

+ +

Berikut cara menyiapkannya:

+
    +
  1. Dalam layanan Anda, buat sebuah instance {@link android.os.Binder} yang: + +
  2. Hasilkan instance {@link android.os.Binder} ini dari metode callback {@link +android.app.Service#onBind onBind()}.
  3. +
  4. Di klien, terima {@link android.os.Binder} dari metode callback {@link +android.content.ServiceConnection#onServiceConnected onServiceConnected()} dan +buat panggilan ke layanan terikat dengan menggunakan metode yang disediakan.
  5. +
+ +

Catatan: Alasan layanan dan klien harus berada dalam aplikasi yang sama +adalah agar klien bisa mengkonversi objek yang dihasilkan dan memanggil API-nya dengan benar. Layanan +dan klien juga harus berada dalam proses yang sama, karena teknik ini tidak melakukan +pengarahan (marshalling) apa pun untuk lintas proses.

+ +

Misalnya, berikut ini adalah layanan yang memberi klien akses ke metode-metode dalam layanan melalui +implementasi {@link android.os.Binder}:

+ +
+public class LocalService extends Service {
+    // Binder given to clients
+    private final IBinder mBinder = new LocalBinder();
+    // Random number generator
+    private final Random mGenerator = new Random();
+
+    /**
+     * Class used for the client Binder.  Because we know this service always
+     * runs in the same process as its clients, we don't need to deal with IPC.
+     */
+    public class LocalBinder extends Binder {
+        LocalService getService() {
+            // Return this instance of LocalService so clients can call public methods
+            return LocalService.this;
+        }
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return mBinder;
+    }
+
+    /** method for clients */
+    public int getRandomNumber() {
+      return mGenerator.nextInt(100);
+    }
+}
+
+ +

{@code LocalBinder} menyediakan {@code getService()} metode bagi klien untuk mengambil +instance {@code LocalService} saat ini. Cara ini memungkinkan klien memanggil metode publik dalam +layanan. Misalnya, klien bisa memanggil {@code getRandomNumber()} dari layanan.

+ +

Berikut ini adalah aktivitas yang mengikat ke {@code LocalService} dan memanggil {@code getRandomNumber()} +bila tombol diklik:

+ +
+public class BindingActivity extends Activity {
+    LocalService mService;
+    boolean mBound = false;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.main);
+    }
+
+    @Override
+    protected void onStart() {
+        super.onStart();
+        // Bind to LocalService
+        Intent intent = new Intent(this, LocalService.class);
+        bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
+    }
+
+    @Override
+    protected void onStop() {
+        super.onStop();
+        // Unbind from the service
+        if (mBound) {
+            unbindService(mConnection);
+            mBound = false;
+        }
+    }
+
+    /** Called when a button is clicked (the button in the layout file attaches to
+      * this method with the android:onClick attribute) */
+    public void onButtonClick(View v) {
+        if (mBound) {
+            // Call a method from the LocalService.
+            // However, if this call were something that might hang, then this request should
+            // occur in a separate thread to avoid slowing down the activity performance.
+            int num = mService.getRandomNumber();
+            Toast.makeText(this, "number: " + num, Toast.LENGTH_SHORT).show();
+        }
+    }
+
+    /** Defines callbacks for service binding, passed to bindService() */
+    private ServiceConnection mConnection = new ServiceConnection() {
+
+        @Override
+        public void onServiceConnected(ComponentName className,
+                IBinder service) {
+            // We've bound to LocalService, cast the IBinder and get LocalService instance
+            LocalBinder binder = (LocalBinder) service;
+            mService = binder.getService();
+            mBound = true;
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName arg0) {
+            mBound = false;
+        }
+    };
+}
+
+ +

Contoh di atas menampilkan cara klien mengikat ke layanan dengan menggunakan implementasi +{@link android.content.ServiceConnection} dan callback {@link +android.content.ServiceConnection#onServiceConnected onServiceConnected()}. Bagian +berikut menyediakan informasi selengkapnya tentang proses pengikatan ke layanan.

+ +

Catatan: Contoh di atas tidak secara eksplisit melepas ikatan dari layanan, +namun semua klien harus melepas ikatan pada waktu yang tepat (seperti saat aktivitas sedang jeda).

+ +

Untuk contoh kode selengkapnya, lihat kelas {@code +LocalService.java} dan kelas {@code +LocalServiceActivities.java} dalam ApiDemos.

+ + + + + +

Menggunakan Messenger

+ + + +

Jika layanan perlu berkomunikasi dengan proses jauh, Anda bisa menggunakan +{@link android.os.Messenger} untuk menyediakan antarmuka bagi layanan Anda. Teknik ini memungkinkan +Anda melakukan komunikasi antarproses (IPC) tanpa harus menggunakan AIDL.

+ +

Berikut ini rangkuman cara menggunakan {@link android.os.Messenger}:

+ + + + +

Dengan cara ini, tidak ada "metode" untuk dipanggil klien pada layanan. Sebagai gantinya, +klien mengirim "pesan" (objek-objek {@link android.os.Message}) yang diterima layanan dalam +{@link android.os.Handler}-nya.

+ +

Berikut ini contoh layanan sederhana yang menggunakan antarmuka {@link android.os.Messenger}:

+ +
+public class MessengerService extends Service {
+    /** Command to the service to display a message */
+    static final int MSG_SAY_HELLO = 1;
+
+    /**
+     * Handler of incoming messages from clients.
+     */
+    class IncomingHandler extends Handler {
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_SAY_HELLO:
+                    Toast.makeText(getApplicationContext(), "hello!", Toast.LENGTH_SHORT).show();
+                    break;
+                default:
+                    super.handleMessage(msg);
+            }
+        }
+    }
+
+    /**
+     * Target we publish for clients to send messages to IncomingHandler.
+     */
+    final Messenger mMessenger = new Messenger(new IncomingHandler());
+
+    /**
+     * When binding to the service, we return an interface to our messenger
+     * for sending messages to the service.
+     */
+    @Override
+    public IBinder onBind(Intent intent) {
+        Toast.makeText(getApplicationContext(), "binding", Toast.LENGTH_SHORT).show();
+        return mMessenger.getBinder();
+    }
+}
+
+ +

Perhatikan bahwa metode {@link android.os.Handler#handleMessage handleMessage()} dalam +{@link android.os.Handler} adalah tempat layanan menerima {@link android.os.Message} +yang masuk dan memutuskan aksi yang harus dilakukan, berdasarkan anggota {@link android.os.Message#what}.

+ +

Klien tinggal membuat {@link android.os.Messenger} berdasarkan {@link +android.os.IBinder} yang dihasilkan layanan dan mengirim pesan menggunakan {@link +android.os.Messenger#send send()}. Misalnya, berikut ini adalah aktivitas sederhana yang mengikat ke +layanan dan mengirim pesan {@code MSG_SAY_HELLO} ke layanan:

+ +
+public class ActivityMessenger extends Activity {
+    /** Messenger for communicating with the service. */
+    Messenger mService = null;
+
+    /** Flag indicating whether we have called bind on the service. */
+    boolean mBound;
+
+    /**
+     * Class for interacting with the main interface of the service.
+     */
+    private ServiceConnection mConnection = new ServiceConnection() {
+        public void onServiceConnected(ComponentName className, IBinder service) {
+            // This is called when the connection with the service has been
+            // established, giving us the object we can use to
+            // interact with the service.  We are communicating with the
+            // service using a Messenger, so here we get a client-side
+            // representation of that from the raw IBinder object.
+            mService = new Messenger(service);
+            mBound = true;
+        }
+
+        public void onServiceDisconnected(ComponentName className) {
+            // This is called when the connection with the service has been
+            // unexpectedly disconnected -- that is, its process crashed.
+            mService = null;
+            mBound = false;
+        }
+    };
+
+    public void sayHello(View v) {
+        if (!mBound) return;
+        // Create and send a message to the service, using a supported 'what' value
+        Message msg = Message.obtain(null, MessengerService.MSG_SAY_HELLO, 0, 0);
+        try {
+            mService.send(msg);
+        } catch (RemoteException e) {
+            e.printStackTrace();
+        }
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.main);
+    }
+
+    @Override
+    protected void onStart() {
+        super.onStart();
+        // Bind to the service
+        bindService(new Intent(this, MessengerService.class), mConnection,
+            Context.BIND_AUTO_CREATE);
+    }
+
+    @Override
+    protected void onStop() {
+        super.onStop();
+        // Unbind from the service
+        if (mBound) {
+            unbindService(mConnection);
+            mBound = false;
+        }
+    }
+}
+
+ +

Perhatikan bahwa contoh ini tidak menampilkan cara layanan merespons klien. Jika ingin +layanan merespons, Anda juga perlu membuat {@link android.os.Messenger} di klien. Lalu +saat menerima callback {@link android.content.ServiceConnection#onServiceConnected +onServiceConnected()}, klien akan mengirim {@link android.os.Message} ke layanan yang berisi +{@link android.os.Messenger} klien dalam parameter {@link android.os.Message#replyTo} +metode {@link android.os.Messenger#send send()}.

+ +

Anda bisa melihat contoh cara menyediakan pertukaran pesan dua arah dalam contoh {@code +MessengerService.java} (layanan) dan {@code +MessengerServiceActivities.java} (klien).

+ + + + + +

Mengikat ke Layanan

+ +

Komponen-komponen aplikasi (klien) bisa mengikat ke layanan dengan memanggil +{@link android.content.Context#bindService bindService()}. Sistem Android +lalu memanggil metode {@link android.app.Service#onBind +onBind()} layanan, yang menghasilkan {@link android.os.IBinder} untuk berinteraksi dengan layanan.

+ +

Pengikatan ini bersifat asinkron. {@link android.content.Context#bindService +bindService()} segera kembali dan tidak mengembalikan {@link android.os.IBinder} ke +klien. Untuk menerima {@link android.os.IBinder}, klien harus membuat instance {@link +android.content.ServiceConnection} dan meneruskannya ke {@link android.content.Context#bindService +bindService()}. {@link android.content.ServiceConnection} berisi metode callback yang +dipanggil sistem untuk mengirim {@link android.os.IBinder}.

+ +

Catatan: Hanya aktivitas, layanan, dan penyedia konten yang bisa mengikat +ke layanan yang—Anda tidak bisa ikat ke layanan dari penerima siaran.

+ +

Jadi, untuk mengikat ke layanan dari klien, Anda harus:

+
    +
  1. Mengimplementasikan {@link android.content.ServiceConnection}. +

    Implementasi Anda harus mengesampingkan dua metode callback:

    +
    +
    {@link android.content.ServiceConnection#onServiceConnected onServiceConnected()}
    +
    Sistem memanggil ini untuk mengirim {@link android.os.IBinder} yang dihasilkan oleh +metode {@link android.app.Service#onBind onBind()} layanan.
    +
    {@link android.content.ServiceConnection#onServiceDisconnected +onServiceDisconnected()}
    +
    Sistem Android memanggil ini bila koneksi ke layanan putus +tanpa terduga, seperti ketika layanan mengalami crash atau dimatikan. Ini tidak dipanggil ketika +klien melepas ikatan.
    +
    +
  2. +
  3. Panggil {@link +android.content.Context#bindService bindService()}, dengan meneruskan implementasi {@link +android.content.ServiceConnection}.
  4. +
  5. Bila sistem memanggil metode callback {@link android.content.ServiceConnection#onServiceConnected +onServiceConnected()}, Anda bisa mulai membuat panggilan ke layanan, dengan menggunakan +metode yang didefinisikan oleh antarmuka.
  6. +
  7. Untuk memutus koneksi dari layanan, panggil {@link +android.content.Context#unbindService unbindService()}. +

    Bila telah dimusnahkan (destroyed), klien Anda akan melepas ikatan dari layanan, namun Anda harus selalu melepas ikatan +bila sudah selesai berinteraksi dengan layanan atau bila aktivitas Anda sedang jeda sehingga layanan bisa +dimatikan saat tidak sedang digunakan. (Waktu yang tepat untuk mengikat dan melepas ikatan dibahas +selengkapnya di bawah ini.)

    +
  8. +
+ +

Misalnya, cuplikan berikut menghubungkan klien ke layanan yang dibuat di atas dengan +memperluas kelas Binder, sehingga tinggal mengkonversi +{@link android.os.IBinder} yang dihasilkan ke kelas {@code LocalService} dan meminta instance {@code +LocalService}:

+ +
+LocalService mService;
+private ServiceConnection mConnection = new ServiceConnection() {
+    // Called when the connection with the service is established
+    public void onServiceConnected(ComponentName className, IBinder service) {
+        // Because we have bound to an explicit
+        // service that is running in our own process, we can
+        // cast its IBinder to a concrete class and directly access it.
+        LocalBinder binder = (LocalBinder) service;
+        mService = binder.getService();
+        mBound = true;
+    }
+
+    // Called when the connection with the service disconnects unexpectedly
+    public void onServiceDisconnected(ComponentName className) {
+        Log.e(TAG, "onServiceDisconnected");
+        mBound = false;
+    }
+};
+
+ +

Dengan {@link android.content.ServiceConnection} ini, klien bisa mengikat ke layanan dengan meneruskannya +ke {@link android.content.Context#bindService bindService()}. Misalnya:

+ +
+Intent intent = new Intent(this, LocalService.class);
+bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
+
+ + + + +

Catatan tambahan

+ +

Berikut ini beberapa catatan penting tentang mengikat ke layanan:

+ + +

Untuk contoh kode selengkapnya, yang menampilkan cara mengikat ke layanan, lihat kelas {@code +RemoteService.java} dalam ApiDemos.

+ + + + + +

Mengelola Daur Hidup Layanan Terikat

+ +

Bila layanan dilepas ikatannya dari semua klien, sistem Android akan menghapusnya (kecuali jika layanan juga +dimulai dengan {@link android.app.Service#onStartCommand onStartCommand()}). Dengan demikian, Anda tidak harus +mengelola daur hidup layanan jika layanan itu murni sebuah layanan +terikat—yang dikelola sistem Android untuk Anda berdasarkan apakah layanan terikat ke klien atau tidak.

+ +

Akan tetapi, Jika Anda memilih untuk mengimplementasikan metode callback {@link android.app.Service#onStartCommand +onStartCommand()}, maka Anda harus menghentikan layanan secara eksplisit, karena layanan +sekarang dianggap telah dimulai. Dalam hal ini, layanan akan berjalan hingga layanan +menghentikan dirinya sendiri dengan {@link android.app.Service#stopSelf()} atau panggilan komponen lain {@link +android.content.Context#stopService stopService()}, terlepas dari apakah layanan terikat ke +klien atau tidak.

+ +

Selain itu, jika layanan Anda telah dimulai dan menerima pengikatan, maka saat sistem memanggil +metode {@link android.app.Service#onUnbind onUnbind()}, Anda bisa memilih untuk mengembalikan +{@code true} jika ingin menerima panggilan ke {@link android.app.Service#onRebind +onRebind()} bila nanti klien mengikat ke layanan (sebagai ganti menerima panggilan ke {@link +android.app.Service#onBind onBind()}). {@link android.app.Service#onRebind +onRebind()} akan menghasilkan void, namun klien tetap menerima {@link android.os.IBinder} dalam callback +{@link android.content.ServiceConnection#onServiceConnected onServiceConnected()}. +Di bawah ini adalah gambar 1 yang mengilustrasikan logika untuk jenis daur hidup ini.

+ + + +

Gambar 1. Daur hidup untuk layanan yang dimulai +dan juga memungkinkan pengikatan.

+ + +

Untuk informasi selengkapnya tentang daur hidup layanan yang telah dimulai, lihat dokumen Layanan.

+ + + + diff --git a/docs/html-intl/intl/in/guide/components/fragments.jd b/docs/html-intl/intl/in/guide/components/fragments.jd new file mode 100644 index 0000000000000000000000000000000000000000..14d4ef3eccbfc404c7ecf4b1ca153f5b589a2a3a --- /dev/null +++ b/docs/html-intl/intl/in/guide/components/fragments.jd @@ -0,0 +1,812 @@ +page.title=Fragmen +parent.title=Aktivitas +parent.link=activities.html +@jd:body + +
+
+

Dalam dokumen ini

+
    +
  1. Filosofi Desain
  2. +
  3. Membuat Fragmen +
      +
    1. Menambahkan antarmuka pengguna
    2. +
    3. Menambahkan fragmen ke aktivitas
    4. +
    +
  4. +
  5. Mengelola Fragmen
  6. +
  7. Melakukan Transaksi Fragmen
  8. +
  9. Berkomunikasi dengan Aktivitas +
      +
    1. Membuat callback kejadian pada aktivitas
    2. +
    3. Menambahkan item ke Action-Bar
    4. +
    +
  10. +
  11. Menangani Daur Hidup Fragmen +
      +
    1. Mengoordinasi dengan daur hidup aktivitas
    2. +
    +
  12. +
  13. Contoh
  14. +
+ +

Kelas-kelas utama

+
    +
  1. {@link android.app.Fragment}
  2. +
  3. {@link android.app.FragmentManager}
  4. +
  5. {@link android.app.FragmentTransaction}
  6. +
+ +

Lihat juga

+
    +
  1. Membangun UI Dinamis dengan Fragmen
  2. +
  3. Mendukung Tablet +dan Handset
  4. +
+
+
+ +

{@link android.app.Fragment} mewakili perilaku atau bagian dari antarmuka pengguna dalam +{@link android.app.Activity}. Anda bisa mengombinasikan beberapa fragmen dalam satu aktivitas untuk membangun UI +multipanel dan menggunakan kembali sebuah fragmen dalam beberapa aktivitas. Anda bisa menganggap fragmen sebagai bagian +modular dari aktivitas, yang memiliki daur hidup sendiri, menerima kejadian input sendiri, dan +yang bisa Anda tambahkan atau hapus saat aktivitas berjalan (semacam "sub aktivitas" yang +bisa digunakan kembali dalam aktivitas berbeda).

+ +

Fragmen harus selalu tertanam dalam aktivitas dan daur hidup fragmen secara langsung +dipengaruhi oleh daur hidup aktivitas host-nya. Misalnya, saat aktivitas dihentikan sementara, +semua fragmen di dalamnya juga dihentikan sementara, dan bila aktivitas dimusnahkan, semua fragmen juga demikian. Akan tetapi, saat +aktivitas berjalan (dalam status daur hidup dilanjutkan, Anda bisa +memanipulasi setiap fragmen secara terpisah, seperti menambah atau menghapusnya. Saat melakukan transaksi +fragmen, Anda juga bisa menambahkannya ke back-stack yang dikelola oleh aktivitas +—setiap entri back-stack merupakan record transaksi fragmen yang +terjadi. Dengan back-stack pengguna dapat membalikkan transaksi fragmen (mengarah mundur), +dengan menekan tombol Back.

+ +

Bila Anda menambahkan fragmen sebagai bagian dari layout aktivitas, fragmen itu berada dalam {@link +android.view.ViewGroup} di hierarki tampilan aktivitas tersebut dan fragmen mendefinisikan +layout +tampilannya sendiri. Anda bisa menyisipkan fragmen ke dalam layout aktivitas dengan mendeklarasikan fragmen dalam file layout aktivitas +, sebagai elemen {@code <fragment>}, atau dari kode aplikasi dengan menambahkannya ke + {@link android.view.ViewGroup} yang ada. Akan tetapi, fragmen tidak harus menjadi bagian dari +layout aktivitas; Anda juga bisa menggunakan fragmen tanpa UI-nya sendiri sebagai pekerja tak terlihat untuk +aktivitas tersebut.

+ +

Dokumen ini menjelaskan cara membangun aplikasi menggunakan fragmen, termasuk +cara fragmen mempertahankan statusnya bila ditambahkan ke back-stack aktivitas, berbagi +kejadian dengan aktivitas, dan fragmen lain dalam aktivitas, berkontribusi pada action-bar +aktivitas, dan lainnya.

+ + +

Filosofi Desain

+ +

Android memperkenalkan fragmen di Android 3.0 (API level 11), terutama untuk mendukung desain UI yang lebih +dinamis dan fleksibel pada layar besar, seperti tablet. Karena +layar tablet jauh lebih besar daripada layar handset, maka lebih banyak ruang untuk mengombinasikan dan +bertukar komponen UI. Fragmen memungkinkan desain seperti itu tanpa perlu mengelola perubahan +kompleks pada hierarki tampilan. Dengan membagi layout aktivitas menjadi beberapa fragmen, Anda bisa +mengubah penampilan aktivitas saat runtime dan mempertahankan perubahan itu di back-stack +yang dikelola oleh aktivitas.

+ +

Misalnya, aplikasi berita bisa menggunakan satu fragmen untuk menampilkan daftar artikel di +sebelah kiri dan fragmen lainnya untuk menampilkan artikel di sebelah kanan—kedua fragmen ini muncul di satu +aktivitas, berdampingan, dan masing-masing fragmen memiliki serangkaian metode callback daur hidup dan menangani kejadian input +penggunanya sendiri. Sehingga, sebagai ganti menggunakan satu aktivitas untuk memilih +artikel dan aktivitas lainnya untuk membaca artikel, pengguna bisa memilih artikel dan membaca semuanya dalam +aktivitas yang sama, sebagaimana diilustrasikan dalam layout tablet pada gambar 1.

+ +

Anda harus mendesain masing-masing fragmen sebagai komponen aktivitas modular dan bisa digunakan kembali. Yakni, karena +setiap fragmen mendefinisikan layoutnya dan perilakunya dengan callback daur hidupnya sendiri, Anda bisa memasukkan +satu fragmen dalam banyak aktivitas, sehingga Anda harus mendesainnya untuk digunakan kembali dan mencegah +memanipulasi satu fragmen dari fragmen lain secara langsung. Ini terutama penting karena dengan +fragmen modular Anda bisa mengubah kombinasi fragmen untuk ukuran layar berbeda. Saat mendesain aplikasi +untuk mendukung tablet maupun handset, Anda bisa menggunakan kembali fragmen dalam +konfigurasi layout berbeda untuk mengoptimalkan pengalaman pengguna berdasarkan ruang layar yang tersedia. Misalnya +, pada handset, fragmen mungkin perlu dipisahkan untuk menyediakan UI panel tunggal +bila lebih dari satu yang tidak cocok dalam aktivitas yang sama.

+ + +

Gambar 1. Contoh cara dua modul UI yang didefinisikan oleh + fragmen bisa digabungkan ke dalam satu aktivitas untuk desain tablet, namun dipisahkan untuk +desain handset.

+ +

Misalnya—untuk melanjutkan contoh aplikasi berita— aplikasi bisa menanamkan +dua fragmen dalam Aktivitas A, saat berjalan pada perangkat berukuran tablet. Akan tetapi, pada +layar berukuran handset, ruang untuk kedua fragmen tidak cukup, sehingga Aktivitas A hanya +menyertakan fragmen untuk daftar artikel, dan saat pengguna memilih artikel, +Aktivitas B akan dimulai, termasuk fragmen kedua untuk membaca artikel. Sehingga, aplikasi mendukung +tablet dan handset dengan menggunakan kembali fragmen dalam kombinasi berbeda, seperti diilustrasikan dalam +gambar 1.

+ +

Untuk informasi selengkapnya tentang mendesain aplikasi menggunakan kombinasi fragmen berbeda +untuk konfigurasi layar berbeda, lihat panduan untuk Mendukung Tablet dan Handset.

+ + + +

Membuat Fragmen

+ +
+ +

Gambar 2. Daur hidup fragmen (saat + aktivitasnya berjalan).

+
+ +

Untuk membuat fragmen, Anda harus membuat subkelas {@link android.app.Fragment} (atau +subkelasnya yang ada). Kelas {@link android.app.Fragment} memiliki kode yang mirip seperti +{@link android.app.Activity}. Kelas ini memiliki metode callback yang serupa dengan aktivitas, seperti + {@link android.app.Fragment#onCreate onCreate()}, {@link android.app.Fragment#onStart onStart()}, +{@link android.app.Fragment#onPause onPause()}, dan {@link android.app.Fragment#onStop onStop()}. Sebenarnya +, jika Anda mengkonversi aplikasi Android saat ini untuk menggunakan fragmen, Anda mungkin cukup memindahkan +kode dari metode callback aktivitas ke masing-masing metode callback +fragmen.

+ +

Biasanya, Anda harus mengimplementasikan setidaknya metode daur hidup berikut ini:

+ +
+
{@link android.app.Fragment#onCreate onCreate()}
+
Sistem akan memanggilnya saat membuat fragmen. Dalam implementasi, Anda harus +menginisialisasi komponen penting dari fragmen yang ingin dipertahankan saat fragmen +dihentikan sementara atau dihentikan, kemudian dilanjutkan.
+
{@link android.app.Fragment#onCreateView onCreateView()}
+
Sistem akan memanggilnya saat fragmen menggambar antarmuka penggunanya +untuk yang pertama kali. Untuk menggambar UI fragmen, Anda harus mengembalikan {@link android.view.View} dari metode +ini yang menjadi akar layout fragmen. Hasil yang dikembalikan bisa berupa null jika +fragmen tidak menyediakan UI.
+
{@link android.app.Activity#onPause onPause()}
+
Sistem akan memanggil metode ini sebagai indikasi pertama bahwa pengguna sedang meninggalkan +fragmen Anda (walau itu tidak selalu berarti fragmen sedang dimusnahkan). Inilah biasanya tempat Anda +harus mengikat setiap perubahan yang harus dipertahankan selepas sesi pengguna saat ini (karena +pengguna mungkin tidak kembali).
+
+ +

Kebanyakan aplikasi harus mengimplementasikan setidaknya tiga metode ini untuk setiap fragmen, namun ada +beberapa metode callback lain yang juga harus Anda gunakan untuk menangani berbagai tahap +daur hidup fragmen. Semua metode callback daur hidup akan dibahas secara lebih detail, di bagian +tentang Menangani Daur Hidup Fragmen.

+ + +

Ada juga beberapa subkelas yang mungkin ingin diperpanjang, sebagai ganti kelas basis {@link +android.app.Fragment}:

+ +
+
{@link android.app.DialogFragment}
+
Menampilkan dialog mengambang. Penggunaan kelas ini untuk membuat dialog merupakan alternatif yang baik dari +penggunaan metode helper dialog di kelas {@link android.app.Activity}, karena Anda bisa +menyatukan dialog fragmen ke dalam back-stack fragmen yang dikelola oleh aktivitas, +sehingga pengguna bisa kembali ke fragmen yang ditinggalkan.
+ +
{@link android.app.ListFragment}
+
Menampilkan daftar item yang dikelola oleh adaptor (seperti {@link +android.widget.SimpleCursorAdapter}), serupa dengan {@link android.app.ListActivity}. Menampilkan +beberapa metode pengelolaan daftar tampilan seperti callback {@link +android.app.ListFragment#onListItemClick(ListView,View,int,long) onListItemClick()} untuk +menangani kejadian klik.
+ +
{@link android.preference.PreferenceFragment}
+
Menampilkan hierarki objek {@link android.preference.Preference} sebagai daftar, serupa dengan +{@link android.preference.PreferenceActivity}. Hal ini berguna saat membuat aktivitas +"pengaturan" untuk aplikasi Anda.
+
+ + +

Menambahkan antarmuka pengguna

+ +

Fragmen biasanya digunakan sebagai bagian dari antarmuka pengguna aktivitas dan menyumbangkan +layoutnya sendiri ke aktivitas.

+ +

Untuk menyediakan layout fragmen, Anda harus mengimplementasikan metode callback {@link +android.app.Fragment#onCreateView onCreateView()}, yang dipanggil sistem Android +bila tiba saatnya fragmen menggambar layoutnya. Implementasi Anda atas metode ini harus mengembalikan +{@link android.view.View} yang menjadi akar layout fragmen.

+ +

Catatan: Jika fragmen adalah subkelas {@link +android.app.ListFragment}, implementasi default akan mengembalikan {@link android.widget.ListView} dari +{@link android.app.Fragment#onCreateView onCreateView()}, sehingga Anda tidak perlu mengimplementasikannya.

+ +

Untuk mengembalikan layout dari {@link +android.app.Fragment#onCreateView onCreateView()}, Anda bisa memekarkannya dari sumber daya layout yang didefinisikan di XML. Untuk +membantu melakukannya, {@link android.app.Fragment#onCreateView onCreateView()} menyediakan objek +{@link android.view.LayoutInflater}.

+ +

Misalnya, ini adalah subkelas {@link android.app.Fragment} yang memuat layout dari file +{@code example_fragment.xml}:

+ +
+public static class ExampleFragment extends Fragment {
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+                             Bundle savedInstanceState) {
+        // Inflate the layout for this fragment
+        return inflater.inflate(R.layout.example_fragment, container, false);
+    }
+}
+
+ + + +

Parameter {@code container} yang diteruskan ke {@link android.app.Fragment#onCreateView +onCreateView()} adalah induk {@link android.view.ViewGroup} (dari layout aktivitas) tempat +layout fragmen +akan disisipkan. Parameter {@code savedInstanceState} adalah {@link android.os.Bundle} yang +menyediakan data tentang instance fragmen sebelumnya, jika fragmen dilanjutkan +(status pemulihan dibahas selengkapnya di bagian tentang Menangani +Daur Hidup Fragmen).

+ +

Metode {@link android.view.LayoutInflater#inflate(int,ViewGroup,boolean) inflate()} membutuhkan +tiga argumen:

+ + +

Anda kini telah melihat cara membuat fragmen yang menyediakan layout. Berikutnya, Anda perlu menambahkan +fragmen ke aktivitas.

+ + + +

Menambahkan fragmen ke aktivitas

+ +

Biasanya, fragmen berkontribusi pada sebagian UI ke aktivitas host, yang ditanamkan sebagai +bagian dari hierarki tampilan keseluruhan aktivitas. Ada dua cara untuk menambahkan fragmen ke layout +aktivitas:

+ + + + +

Menambahkan fragmen tanpa UI

+ +

Contoh di atas menampilkan cara menambahkan fragmen ke aktivitas untuk menyediakan UI. Akan tetapi, +Anda juga bisa menggunakan fragmen untuk menyediakan perilaku latar belakang bagi aktivitas tanpa menampilkan UI +tambahan.

+ +

Untuk menambahkan fragmen tanpa UI, tambahkan fragmen dari aktivitas menggunakan {@link +android.app.FragmentTransaction#add(Fragment,String)} (dengan menyediakan string unik "tag" untuk fragmen +, bukan ID tampilan). Ini akan menambahkan fragmen, namun, karena tidak dikaitkan dengan tampilan +dalam layout aktivitas, ini tidak akan menerima panggilan ke {@link +android.app.Fragment#onCreateView onCreateView()}. Jadi Anda tidak perlu mengimplementasikan metode itu.

+ +

Menyediakan tag string untuk fragmen tidak hanya untuk fragmen non-UI—Anda juga bisa +menyediakan tag string untuk fragmen yang memiliki UI—namun jika fragmen tidak memiliki UI +, maka tag string adalah satu-satunya cara untuk mengidentifikasinya. Jika Anda ingin mendapatkan fragmen dari +aktivitas nantinya, Anda perlu menggunakan {@link android.app.FragmentManager#findFragmentByTag +findFragmentByTag()}.

+ +

Untuk contoh aktivitas yang menggunakan fragmen sebagai pekerja latar belakang, tanpa UI, lihat sampel {@code +FragmentRetainInstance.java}, yang disertakan dalam sampel SDK (tersedia melalui +Android SDK Manager) dan terletak di sistem Anda sebagai +<sdk_root>/APIDemos/app/src/main/java/com/example/android/apis/app/FragmentRetainInstance.java.

+ + + +

Mengelola Fragmen

+ +

Untuk mengelola fragmen dalam aktivitas, Anda perlu menggunakan {@link android.app.FragmentManager}. Untuk +mendapatkannya, panggil {@link android.app.Activity#getFragmentManager()} dari aktivitas Anda.

+ +

Beberapa hal yang dapat Anda lakukan dengan {@link android.app.FragmentManager} antara lain:

+ + + +

Untuk informasi selengkapnya tentang metode ini dan hal lainnya, lihat dokumentasi kelas {@link +android.app.FragmentManager}.

+ +

Seperti yang ditunjukkan di bagian sebelumnya, Anda juga bisa menggunakan {@link android.app.FragmentManager} +untuk membuka {@link android.app.FragmentTransaction}, sehingga Anda bisa melakukan transaksi, seperti +menambah dan menghapus fragmen.

+ + +

Melakukan Transaksi Fragmen

+ +

Fitur menarik terkait penggunaan fragmen di aktivitas adalah kemampuan menambah, menghapus, mengganti, +dan melakukan tindakan lain dengannya, sebagai respons atas interaksi pengguna. Setiap set perubahan +yang Anda lakukan untuk aktivitas disebut transaksi dan Anda bisa melakukan transaksi menggunakan API di {@link +android.app.FragmentTransaction}. Anda juga bisa menyimpan setiap transaksi ke back-stack yang dikelola +aktivitas, sehingga pengguna bisa mengarah mundur melalui perubahan fragmen (mirip mengarah +mundur melalui aktivitas).

+ +

Anda bisa mengambil instance {@link android.app.FragmentTransaction} dari {@link +android.app.FragmentManager} seperti ini:

+ +
+FragmentManager fragmentManager = {@link android.app.Activity#getFragmentManager()};
+FragmentTransaction fragmentTransaction = fragmentManager.{@link android.app.FragmentManager#beginTransaction()};
+
+ +

Setiap transaksi merupakan serangkaian perubahan yang ingin dilakukan pada waktu yang sama. Anda bisa +mengatur semua perubahan yang ingin dilakukan untuk transaksi mana saja menggunakan metode seperti {@link +android.app.FragmentTransaction#add add()}, {@link android.app.FragmentTransaction#remove remove()}, +dan {@link android.app.FragmentTransaction#replace replace()}. Kemudian, untuk menerapkan transaksi +pada aktivitas, Anda harus memanggil {@link android.app.FragmentTransaction#commit()}.

+ + +

Akan tetapi, sebelum memanggil {@link +android.app.FragmentTransaction#commit()}, Anda mungkin perlu memanggil {@link +android.app.FragmentTransaction#addToBackStack addToBackStack()}, untuk menambahkan transaksi +ke back-stack dari transaksi fragmen. Back-stack ini dikelola oleh aktivitas dan memungkinkan +pengguna kembali ke status fragmen sebelumnya, dengan menekan tombol Back.

+ +

Misalnya, berikut ini cara mengganti satu fragmen dengan yang fragmen yang lain, dan mempertahankan +status sebelumnya di back-stack:

+ +
+// Create new fragment and transaction
+Fragment newFragment = new ExampleFragment();
+FragmentTransaction transaction = getFragmentManager().beginTransaction();
+
+// Replace whatever is in the fragment_container view with this fragment,
+// and add the transaction to the back stack
+transaction.replace(R.id.fragment_container, newFragment);
+transaction.addToBackStack(null);
+
+// Commit the transaction
+transaction.commit();
+
+ +

Dalam contoh ini, {@code newFragment} menggantikan fragmen apa saja (jika ada) yang saat ini berada dalam +kontainer layout yang diidentifikasi oleh ID {@code R.id.fragment_container}. Dengan memanggil @link +android.app.FragmentTransaction#addToBackStack addToBackStack()}, transaksi yang diganti +disimpan ke back-stack sehingga pengguna bisa membalikkan transaksi dan mengembalikan fragmen +sebelumnya dengan menekan tombol Back.

+ +

Jika Anda menambahkan beberapa perubahan pada transaksi (seperti {@link +android.app.FragmentTransaction#add add()} atau {@link android.app.FragmentTransaction#remove +remove()}) dan panggil {@link +android.app.FragmentTransaction#addToBackStack addToBackStack()}, maka semua perubahan akan diterapkan +sebelum Anda memanggil {@link android.app.FragmentTransaction#commit commit()} akan ditambahkan ke +back-stack sebagai satu transaksi dan tombol Back akan membalikannya semua.

+ +

Urutan menambahkan perubahan pada {@link android.app.FragmentTransaction} tidak berpengaruh, +kecuali:

+ + +

Jika Anda tidak memanggil {@link android.app.FragmentTransaction#addToBackStack(String) +addToBackStack()} saat melakukan transaksi yang menghapus fragmen, maka fragmen itu +akan dimusnahkan bila transaksi diikat dan pengguna tidak bisa mengarah kembali ke sana. Sedangkan, jika +Anda memanggil {@link android.app.FragmentTransaction#addToBackStack(String) addToBackStack()} saat +menghapus fragmen, maka fragmen itu akan dihentikan dan akan dilanjutkan jika pengguna mengarah +kembali.

+ +

Tip: Untuk setiap transaksi fragmen, Anda bisa menerapkan animasi +transisi, dengan memanggil {@link android.app.FragmentTransaction#setTransition setTransition()} sebelum +mengikatnya.

+ +

Memanggil {@link android.app.FragmentTransaction#commit()} tidak akan langsung menjalankan +transaksi. Namun sebuah jadwal akan dibuat untuk dijalankan pada thread UI aktivitas (thread "utama") +begitu thread bisa melakukannya. Akan tetapi, jika perlu Anda bisa memanggil {@link +android.app.FragmentManager#executePendingTransactions()} dari thread UI untuk segera +mengeksekusi transaksi yang diserahkan oleh {@link android.app.FragmentTransaction#commit()}. Hal itu +biasanya tidak perlu kecuali jika transaksi merupakan dependensi bagi pekerjaan dalam thread lain.

+ +

Perhatian: Anda bisa mengikat transaksi menggunakan {@link +android.app.FragmentTransaction#commit commit()} hanya sebelum aktivitas menyimpan +statusnya (saat pengguna meninggalkan aktivitas). Jika Anda mencoba mengikatnya setelah itu, +eksepsi akan dilontarkan. Ini karena status setelah pengikatan bisa hilang jika aktivitas +perlu dipulihkan. Untuk situasi yang memperbolehkan Anda meniadakan pengikatan (commit), gunakan {@link +android.app.FragmentTransaction#commitAllowingStateLoss()}.

+ + + + +

Berkomunikasi dengan Aktivitas

+ +

Meskipun {@link android.app.Fragment} diimplementasikan sebagai objek yang tidak bergantung pada +{@link android.app.Activity} dan bisa digunakan dalam banyak aktivitas, instance tertentu +dari fragmen secara langsung terkait dengan aktivitas yang dimuatnya.

+ +

Khususnya, fragmen bisa mengakses instance {@link android.app.Activity} dengan {@link +android.app.Fragment#getActivity()} dan dengan mudah melakukan tugas-tugas seperti mencari tampilan dalam + layout aktivitas:

+ +
+View listView = {@link android.app.Fragment#getActivity()}.{@link android.app.Activity#findViewById findViewById}(R.id.list);
+
+ +

Demikian pula, aktivitas Anda bisa memanggil metode di fragmen dengan meminta acuan ke +{@link android.app.Fragment} dari {@link android.app.FragmentManager}, menggunakan {@link +android.app.FragmentManager#findFragmentById findFragmentById()} atau {@link +android.app.FragmentManager#findFragmentByTag findFragmentByTag()}. Misalnya:

+ +
+ExampleFragment fragment = (ExampleFragment) getFragmentManager().findFragmentById(R.id.example_fragment);
+
+ + +

Membuat callback kejadian pada aktivitas

+ +

Dalam beberapa kasus, Anda mungkin perlu fragmen untuk berbagi kejadian dengan aktivitas. Cara yang baik untuk melakukannya +adalah mendefinisikan antarmuka callback di dalam fragmen dan mengharuskan aktivitas host +mengimplementasikannya. Saat aktivitas menerima callback melalui antarmuka, aktivitas akan bisa berbagi informasi itu +dengan fragmen lain dalam layout jika perlu.

+ +

Misalnya, jika sebuah aplikasi berita memiliki dua fragmen dalam aktivitas—satu untuk menampilkan daftar +artikel (fragmen A) dan satu lagi untuk menampilkan artikel (fragmen B)—maka fragmen A harus +memberi tahu aktivitas bila item daftar dipilih sehingga aktivitas bisa memberi tahu fragmen B untuk menampilkan artikel. Dalam +hal ini, antarmuka {@code OnArticleSelectedListener} dideklarasikan di dalam fragmen A:

+ +
+public static class FragmentA extends ListFragment {
+    ...
+    // Container Activity must implement this interface
+    public interface OnArticleSelectedListener {
+        public void onArticleSelected(Uri articleUri);
+    }
+    ...
+}
+
+ +

Selanjutnya aktivitas yang menjadi host fragmen akan mengimplementasikan antarmuka {@code OnArticleSelectedListener} + dan +mengesampingkan {@code onArticleSelected()} untuk memberi tahu fragmen B mengenai kejadian dari fragmen A. Untuk memastikan +bahwa aktivitas host mengimplementasikan antarmuka ini, metode callback fragmen A {@link +android.app.Fragment#onAttach onAttach()} (yang dipanggil sistem saat menambahkan +fragmen ke aktivitas) membuat instance {@code OnArticleSelectedListener} dengan +membuat {@link android.app.Activity} yang diteruskan ke {@link android.app.Fragment#onAttach +onAttach()}:

+ +
+public static class FragmentA extends ListFragment {
+    OnArticleSelectedListener mListener;
+    ...
+    @Override
+    public void onAttach(Activity activity) {
+        super.onAttach(activity);
+        try {
+            mListener = (OnArticleSelectedListener) activity;
+        } catch (ClassCastException e) {
+            throw new ClassCastException(activity.toString() + " must implement OnArticleSelectedListener");
+        }
+    }
+    ...
+}
+
+ +

Jika aktivitas belum mengimplementasikan antarmuka, maka fragmen akan melontarkan +{@link java.lang.ClassCastException}. +Jika berhasil, anggota {@code mListener} yang menyimpan acuan ke implementasi aktivitas +{@code OnArticleSelectedListener}, sehingga fragmen A bisa berbagi kejadian dengan aktivitas, dengan memanggil metode +yang didefinisikan oleh antarmuka {@code OnArticleSelectedListener}. Misalnya, jika fragmen A adalah +ekstensi dari {@link android.app.ListFragment}, maka setiap kali +pengguna mengklik item daftar, sistem akan memanggil {@link android.app.ListFragment#onListItemClick +onListItemClick()} di fragmen, yang selanjutnya memanggil {@code onArticleSelected()} untuk berbagi +kejadian dengan aktivitas:

+ +
+public static class FragmentA extends ListFragment {
+    OnArticleSelectedListener mListener;
+    ...
+    @Override
+    public void onListItemClick(ListView l, View v, int position, long id) {
+        // Append the clicked item's row ID with the content provider Uri
+        Uri noteUri = ContentUris.{@link android.content.ContentUris#withAppendedId withAppendedId}(ArticleColumns.CONTENT_URI, id);
+        // Send the event and Uri to the host activity
+        mListener.onArticleSelected(noteUri);
+    }
+    ...
+}
+
+ +

Parameter {@code id} yang diteruskan ke {@link +android.app.ListFragment#onListItemClick onListItemClick()} merupakan ID baris dari item yang diklik, +yang digunakan aktivitas (atau fragmen lain) untuk mengambil artikel dari {@link +android.content.ContentProvider} aplikasi.

+ +

Informasi selengkapnya tentang +menggunakan penyedia konten tersedia dalam dokumen Penyedia Konten.

+ + + +

Menambahkan item ke Action-Bar

+ +

Fragmen Anda bisa menyumbangkan item menu ke Menu Opsi aktivitas (dan, konsekuensinya, Action-Bar) dengan mengimplementasikan +{@link android.app.Fragment#onCreateOptionsMenu(Menu,MenuInflater) onCreateOptionsMenu()}. Agar +metode ini bisa menerima panggilan, Anda harus memanggil {@link +android.app.Fragment#setHasOptionsMenu(boolean) setHasOptionsMenu()} selama {@link +android.app.Fragment#onCreate(Bundle) onCreate()}, untuk menunjukkan bahwa fragmen +ingin menambahkan item ke Menu Opsi (jika tidak, fragmen tidak akan menerima panggilan ke +{@link android.app.Fragment#onCreateOptionsMenu onCreateOptionsMenu()}).

+ +

Setiap item yang selanjutnya Anda tambahkan ke Menu Opsi dari fragmen akan ditambahkan ke item menu +yang ada. Fragmen juga menerima callback ke {@link +android.app.Fragment#onOptionsItemSelected(MenuItem) onOptionsItemSelected()} bila item menu +dipilih.

+ +

Anda juga bisa mendaftarkan tampilan dalam layout fragmen untuk menyediakan menu konteks dengan memanggil {@link +android.app.Fragment#registerForContextMenu(View) registerForContextMenu()}. Bila pengguna +membuka menu konteks, fragmen akan menerima panggilan ke {@link +android.app.Fragment#onCreateContextMenu(ContextMenu,View,ContextMenu.ContextMenuInfo) +onCreateContextMenu()}. Bila pengguna memilih item, fragmen akan menerima panggilan ke @link +android.app.Fragment#onContextItemSelected(MenuItem) onContextItemSelected()}.

+ +

Catatan: Walaupun fragmen menerima callback pada item yang dipilih +untuk setiap item menu yang ditambahkannya, aktivitaslah yang pertama kali menerima masing-masing callback saat pengguna +memilih item menu. Jika implementasi aktivitas dari callback bila-item-dipilih, +tidak menangani item yang dipilih, maka kejadian akan diteruskan ke callback fragmen. Ini berlaku +untuk Menu Opsi dan menu konteks.

+ +

Untuk informasi selengkapnya tentang menu, lihat panduan pengembang Menu dan Action-Bar.

+ + + + +

Menangani Daur Hidup Fragmen

+ +
+ +

Gambar 3. Efek daur hidup aktivitas pada daur hidup +fragmen.

+
+ +

Mengelola daur hidup fragmen mirip sekali dengan mengelola daur hidup aktivitas. Seperti +aktivitas, fragmen bisa berada dalam tiga status:

+ +
+
Dilanjutkan
+
Fragmen terlihat dalam aktivitas yang berjalan.
+ +
Dihentikan sementara
+
Aktivitas lain berada di latar depan dan memiliki fokus, namun aktivitas tempat fragmen berada +masih terlihat (aktivitas latar depan sebagian terlihat atau tidak menutupi +seluruh layar).
+ +
Dihentikan
+
Fragmen tidak terlihat. Aktivitas host telah dihentikan atau +fragmen telah dihapus dari aktivitas namun ditambahkan ke back-stack. Fragmen yang dihentikan +masih hidup (semua status dan informasi anggota masih disimpan oleh sistem). Akan tetapi, fragmen +tidak terlihat lagi oleh pengguna dan akan dimatikan jika aktivitas dimatikan.
+
+ +

Seperti halnya aktivitas, Anda bisa mempertahankan status fragmen menggunakan {@link +android.os.Bundle}, jika proses aktivitas dimatikan dan Anda harus memulihkan status +fragmen bila aktivitas dibuat kembali. Anda bisa menyimpan status selama callback {@link +android.app.Fragment#onSaveInstanceState onSaveInstanceState()} fragmen dan memulihkannya selama +{@link android.app.Fragment#onCreate onCreate()}, {@link +android.app.Fragment#onCreateView onCreateView()}, atau {@link +android.app.Fragment#onActivityCreated onActivityCreated()}. Untuk informasi selengkapnya tentang menyimpan +status, lihat dokumen Aktivitas +.

+ +

Perbedaan paling signifikan dalam daur hidup antara aktivitas dan fragmen ada +pada cara penyimpanannya dalam back-stack masing-masing. Aktivitas ditempatkan ke back-stack aktivitas +yang dikelola oleh sistem saat dihentikan, secara default (sehingga pengguna bisa mengarah kembali +ke aktivitas dengan tombol Back, seperti yang dibahas dalam Tugas dan Back-Stack). +Akan tetapi, fragmen yang ditempatkan ke back-stack dikelola oleh aktivitas host hanya saat +Anda secara eksplisit meminta agar instance disimpan dengan memanggil {@link +android.app.FragmentTransaction#addToBackStack(String) addToBackStack()} selama transaksi yang +menghapus fragmen.

+ +

Jika tidak, pengelolaan daur hidup fragmen mirip sekali dengan mengelola daur hidup +aktivitas. Jadi, praktik yang sama untuk mengelola daur hidup +aktivitas juga berlaku untuk fragmen. Namun yang perlu juga Anda pahami adalah bagaimana hidup +aktivitas memengaruhi hidup fragmen.

+ +

Perhatian: Jika Anda memerlukan objek {@link android.content.Context} + dalam {@link android.app.Fragment}, Anda bisa memanggil {@link android.app.Fragment#getActivity()}. +Akan tetapi, berhati-hatilah memanggil {@link android.app.Fragment#getActivity()} hanya bila fragmen +terkait dengan aktivitas. Bila fragmen belum terkait, atau terlepas selama akhir daur +hidupnya, {@link android.app.Fragment#getActivity()} akan kembali nol.

+ + +

Mengoordinasi dengan daur hidup aktivitas

+ +

Daur hidup aktivitas tempat fragmen berada akan memengaruhi langsung siklus hidup +fragmen sedemikian rupa sehingga setiap callback daur hidup aktivitas menghasilkan callback yang sama untuk masing-masing +fragmen. Misalnya, bila aktivitas menerima {@link android.app.Activity#onPause}, masing-masing +fragmen dalam aktivitas akan menerima {@link android.app.Fragment#onPause}.

+ +

Namun fragmen memiliki beberapa callback daur hidup ekstra, yang menangani interaksi +unik dengan aktivitas untuk melakukan tindakan seperti membangun dan memusnahkan UI fragmen. Metode callback +tambahan ini adalah:

+ +
+
{@link android.app.Fragment#onAttach onAttach()}
+
Dipanggil bila fragmen telah dikaitkan dengan aktivitas ({@link +android.app.Activity} diteruskan di sini).
+
{@link android.app.Fragment#onCreateView onCreateView()}
+
Dipanggil untuk membuat hierarki tampilan yang dikaitkan dengan fragmen.
+
{@link android.app.Fragment#onActivityCreated onActivityCreated()}
+
Dipanggil bila metode {@link android.app.Activity#onCreate +onCreate()} aktivitas telah dikembalikan.
+
{@link android.app.Fragment#onDestroyView onDestroyView()}
+
Dipanggil bila hierarki tampilan yang terkait dengan fragmen dihapus.
+
{@link android.app.Fragment#onDetach onDetach()}
+
Dipanggil bila fragmen diputuskan dari aktivitas.
+
+ +

Aliran daur hidup fragmen, karena dipengaruhi oleh aktivitas host-nya, diilustrasikan oleh +gambar 3. Dalam gambar ini, Anda bisa melihat bagaimana setiap status aktivitas menentukan +metode callback mana yang mungkin diterima fragmen. Misalnya, saat aktivitas menerima call back {@link +android.app.Activity#onCreate onCreate()}, fragmen dalam aktivitas akan menerima tidak lebih +dari callback {@link android.app.Fragment#onActivityCreated onActivityCreated()}.

+ +

Setelah status aktivitas diteruskan kembali, Anda bisa bebas menambah dan menghapus fragmen untuk +aktivitas tersebut. Sehingga, hanya saat aktivitas berada dalam status dilanjutkan, daur hidup fragmen bisa +berubah secara independen.

+ +

Akan tetapi, saat aktivitas meninggalkan status dilanjutkan, fragmen akan kembali didorong +melalui daur hidupnya oleh aktivitas.

+ + + + +

Contoh

+ +

Untuk merangkum semua yang telah dibahas dalam dokumen ini, berikut ini contoh aktivitas +yang menggunakan dua fragmen untuk membuat layout dua panel. Aktivitas di bawah ini menyertakan satu fragmen untuk +menampilkan daftar putar Shakespeare dan fragmen lainnya menampilkan rangkuman pemutaran bila dipilih dari +daftar. Aktivitas ini juga menunjukkan cara menyediakan konfigurasi fragmen berbeda, +berdasarkan konfigurasi layar.

+ +

Catatan: Kode sumber lengkap untuk aktivitas ini tersedia di +{@code +FragmentLayout.java}.

+ +

Aktivitas utama akan menerapkan layout seperti biasa, selama {@link +android.app.Activity#onCreate onCreate()}:

+ +{@sample development/samples/ApiDemos/src/com/example/android/apis/app/FragmentLayout.java main} + +

Layout yang diterapkan adalah {@code fragment_layout.xml}:

+ +{@sample development/samples/ApiDemos/res/layout-land/fragment_layout.xml layout} + +

Dengan layout ini, sistem akan membuat instance {@code TitlesFragment} (yang mencantumkan +judul) segera setelah aktivitas memuat layout, sementara {@link android.widget.FrameLayout} + (lokasi penempatan fragmen untuk menampilkan rangkuman pemutaran) menempati ruang di sisi kanan +layar, namun pada awalnya masih kosong. Seperti yang akan Anda lihat di bawah ini, sampai pengguna memilih item +dari daftar maka fragmen baru akan ditempatkan ke dalam {@link android.widget.FrameLayout}.

+ +

Akan tetapi, tidak semua konfigurasi layar cukup lebar untuk menampilkan +daftar putar dan rangkuman secara berdampingan. Sehingga, layout di atas hanya digunakan untuk konfigurasi +layar mendatar, dengan menyimpannya di {@code res/layout-land/fragment_layout.xml}.

+ +

Sehingga, bila layar berada dalam orientasi tegak, sistem akan menerapkan layout berikut, yang +tersimpan di {@code res/layout/fragment_layout.xml}:

+ +{@sample development/samples/ApiDemos/res/layout/fragment_layout.xml layout} + +

Layout ini hanya menyertakan {@code TitlesFragment}. Ini artinya saat perangkat berada dalam +orientasi tegak, hanya judul daftar putar yang terlihat. Jadi, saat pengguna mengklik item +daftar dalam konfigurasi ini, aplikasi akan memulai aktivitas baru untuk menampilkan rangkuman, +sebagai ganti pemuatan fragmen kedua.

+ +

Berikutnya, Anda bisa melihat bagaimana hal ini dilakukan dalam kelas fragmen. Pertama adalah {@code +TitlesFragment}, yang menampilkan judul daftar putar Shakespeare. Fragmen ini membuat ekstensi {@link +android.app.ListFragment} dan mengandalkannya itu untuk menangani sebagian besar pekerjaan tampilan daftar.

+ +

Saat Anda memeriksa kode ini, perhatikan bahwa ada dua kemungkinan perilaku saat pengguna mengklik +item daftar: bergantung pada layout mana yang aktif, bisa membuat dan menampilkan fragmen +baru untuk menampilkan detail dalam aktivitas yang sama (menambahkan fragmen ke {@link +android.widget.FrameLayout}), atau memulai aktivitas baru (tempat fragmen ditampilkan).

+ +{@sample development/samples/ApiDemos/src/com/example/android/apis/app/FragmentLayout.java titles} + +

Fragmen kedua, {@code DetailsFragment} menampilkan rangkuman pemutaran untuk item yang dipilih dari +daftar dari {@code TitlesFragment}:

+ +{@sample development/samples/ApiDemos/src/com/example/android/apis/app/FragmentLayout.java details} + +

Ingatlah dari kelas {@code TitlesFragment}, bahwa, jika pengguna mengklik item daftar dan +layout saat ini tidak menyertakan tampilan {@code R.id.details} (yaitu tempat +{@code DetailsFragment} berada), maka aplikasi memulai aktivitas {@code DetailsActivity} +untuk menampilkan konten item.

+ +

Berikut ini adalah {@code DetailsActivity}, yang hanya menanamkan {@code DetailsFragment} untuk menampilkan rangkuman pemutaran +yang dipilih saat layar dalam orientasi tegak:

+ +{@sample development/samples/ApiDemos/src/com/example/android/apis/app/FragmentLayout.java +details_activity} + +

Perhatikan bahwa aktivitas ini selesai sendiri jika konfigurasi mendatar, sehingga aktivitas utama +bisa mengambil alih dan menampilkan {@code DetailsFragment} bersama {@code TitlesFragment}. +Ini bisa terjadi jika pengguna memulai {@code DetailsActivity} saat dalam orientasi tegak, namun kemudian +memutarnya menjadi mendatar (yang akan memulai lagi aktivitas saat ini).

+ + +

Untuk contoh lainnya mengenai penggunaan fragmen (dan file sumber lengkap untuk contoh ini), +lihat aplikasi contoh Demo API yang tersedia di +ApiDemos (bisa diunduh dari Komponen contoh SDK).

+ + diff --git a/docs/html-intl/intl/in/guide/components/fundamentals.jd b/docs/html-intl/intl/in/guide/components/fundamentals.jd new file mode 100644 index 0000000000000000000000000000000000000000..bd9a500f77a5c608557ab62134bc7400a24f241c --- /dev/null +++ b/docs/html-intl/intl/in/guide/components/fundamentals.jd @@ -0,0 +1,480 @@ +page.title=Dasar-Dasar Aplikasi +@jd:body + +
+
+ +

Dalam dokumen ini

+
    +
  1. Komponen Aplikasi +
      +
    1. Mengaktifkan komponen
    2. +
    +
  2. +
  3. File Manifes +
      +
    1. Mendeklarasikan komponen
    2. +
    3. Mendeklarasikan kebutuhan aplikasi
    4. +
    +
  4. +
  5. Sumber Daya Aplikasi
  6. +
+
+
+ +

Aplikasi Android ditulis dalam bahasa pemrograman Java. Android SDK Tools mengkompilasi +kode Anda—bersama data dan file sumber daya —ke dalam APK: Paket Android, +yaitu file arsip berekstensi {@code .apk}. Satu file APK berisi semua konten +aplikasi Android dan merupakan file yang digunakan perangkat berbasis Android untuk menginstal aplikasi.

+ +

Setelah diinstal di perangkat, setiap aplikasi Android tinggal di sandbox keamanannya sendiri:

+ + + +

Dengan cara ini, sistem Android mengimplementasikan prinsip privilese minim. Ini berarti, +secara default aplikasi hanya memiliki akses ke komponen yang diperlukannya untuk melakukan pekerjaannya dan +tidak lebih dari itu. Hal ini menghasilkan lingkungan yang sangat aman sehingga aplikasi tidak bisa mengakses bagian +sistem bila tidak diberi izin.

+ +

Akan tetapi, ada beberapa cara bagi aplikasi untuk berbagi data dengan aplikasi lain dan bagi aplikasi +untuk mengakses layanan sistem:

+ + + +

Hal tersebut mencakup dasar-dasar tentang cara aplikasi Android berada di dalam sistem. Bagian dokumen +selanjutnya memperkenalkan Anda pada:

+ + + + +

Komponen Aplikasi

+ +

Komponen aplikasi adalah blok pembangun penting dari aplikasi Android. +Setiap komponen merupakan titik berbeda yang digunakan sistem untuk memasuki aplikasi. Tidak semua komponen +merupakan titik masuk sebenarnya bagi pengguna dan sebagian saling bergantung, namun masing-masing komponen tersedia +sebagai kesatuan sendiri dan memainkan peran tertentu—masing-masing merupakan +blok pembangun unik yang mendefinisikan perilaku aplikasi secara keseluruhan.

+ +

Ada empat macam tipe komponen aplikasi. Setiap tipe memiliki kegunaan tersendiri +dan daur hidupnya sendiri yang mendefinisikan cara komponen dibuat dan dimusnahkan.

+ +

Berikut ini empat tipe komponen aplikasi:

+ +
+ +
Aktivitas
+ +
Sebuah aktivitas mewakili satu layar dengan antarmuka pengguna. Misalnya, +aplikasi email mungkin memiliki satu aktivitas yang menampilkan daftar email +baru, aktivitas lain untuk menulis email, dan aktivitas satunya lagi untuk membaca email. Walaupun +semua aktivitas bekerja sama untuk membentuk pengalaman pengguna yang kohesif dalam aplikasi email, +masing-masing tidak saling bergantung. Karenanya, aplikasi berbeda bisa memulai +salah satu aktivitas ini (jika aplikasi email mengizinkannya). Misalnya, aplikasi kamera bisa memulai +aktivitas dalam aplikasi email yang membuat email baru agar pengguna bisa berbagi gambar. + +

Aktivitas diimplementasikan sebagai subkelas {@link android.app.Activity} dan Anda bisa mengetahui selengkapnya +tentang hal ini dalam panduan pengembang Aktivitas +.

+
+ + +
Layanan
+ +
Sebuah layanan adalah komponen yang berjalan di latar belakang untuk melakukan +operasi yang berjalan lama atau untuk melakukan pekerjaan bagi proses jarak jauh. Layanan +tidak menyediakan antarmuka pengguna. Misalnya, sebuah layanan bisa memutar musik di latar belakang sementara +pengguna berada dalam aplikasi lain, atau layanan bisa menarik data lewat jaringan tanpa +memblokir interaksi pengguna dengan aktivitas. Komponen lain, seperti aktivitas, bisa memulai +layanan dan membiarkannya berjalan atau mengikat layanan untuk berinteraksi dengannya. + +

Layanan diimplementasikan sebagai subkelas {@link android.app.Service} dan Anda bisa mengetahui selengkapnya +tentang hal ini dalam panduan +pengembang Layanan.

+
+ + +
Penyedia konten
+ +
Sebuah penyedia konten mengelola seperangkat data-bersama aplikasi. Anda bisa menyimpan data +dalam sistem file, database SQLite, di web, atau lokasi penyimpanan permanen lainnya +yang bisa diakses aplikasi. Melalui penyedia konten, aplikasi lain bisa melakukan query atau bahkan +memodifikasi data (jika penyedia konten mengizinkannya). Misalnya, sistem Android menyediakan penyedia +konten yang mengelola informasi kontak pengguna. Karenanya, setiap aplikasi +dengan izin yang sesuai bisa melakukan query mengenai bagian dari penyedia konten (seperti {@link +android.provider.ContactsContract.Data}) untuk membaca dan menulis informasi tentang orang tertentu. + +

Penyedia konten juga berguna untuk membaca dan menulis data privat ke aplikasi Anda +dan tidak dibagikan. Misalnya, aplikasi contoh Note Pad menggunakan +penyedia konten untuk menyimpan catatan.

+ +

Penyedia konten diimplementasikan sebagai subkelas {@link android.content.ContentProvider} +dan harus mengimplementasikan seperangkat standar API yang memungkinkan aplikasi +lain melakukan transaksi. Untuk informasi selengkapnya, lihat panduan pengembang +Penyedia Konten.

+
+ + +
Penerima siaran
+ +
Sebuah penerima siaran adalah komponen yang merespons pengumuman siaran dalam lingkup +sistem. Banyak siaran yang berasal dari sistem—misalnya, siaran yang mengumumkan bahwa +layar telah dimatikan, baterai lemah, atau gambar telah direkam. +Aplikasi juga bisa memulai siaran—misalnya untuk menginformasikan ke +aplikasi lain bahwa sebagian data telah diunduh ke perangkat dan bisa digunakan aplikasi lain tersebut. Walaupun penerima +siaran tidak menampilkan antarmuka pengguna, penerima bisa membuat pemberitahuan baris status +untuk memberi tahu pengguna kapan kejadian siaran dilakukan. Meskipun penerima siaran umumnya cuma menjadi +"gerbang" untuk komponen lain dan dimaksudkan untuk melakukan pekerjaan dalam jumlah sangat minim. Misalnya +, penerima siaran bisa menjalankan layanan untuk melakukan beberapa pekerjaan berdasarkan kejadian. + +

Penerima siaran diimplementasikan sebagai subkelas {@link android.content.BroadcastReceiver} +dan setiap siaran dikirim sebagai objek {@link android.content.Intent}. Untuk informasi selengkapnya, +lihat kelas {@link android.content.BroadcastReceiver}.

+
+ +
+ + + +

Aspek unik dari desain sistem Android adalah aplikasi mana pun bisa memulai +komponen aplikasi lain. Misalnya, jika Anda menginginkan pengguna mengambil +foto dengan kamera perangkat, bisa saja aplikasi lain yang melakukannya dan aplikasi +Anda bisa menggunakannya, sebagai ganti mengembangkan aktivitas sendiri untuk mengambil foto. Anda tidak +harus menyatukan atau bahkan menautkan ke kode dari aplikasi kamera. +Sebagai gantinya, Anda tinggal memulai aktivitas di aplikasi kamera yang akan mengambil +foto. Bila selesai, foto akan dikembalikan ke aplikasi sehingga Anda bisa menggunakannya. Bagi pengguna, +kamera seakan menjadi bagian dari aplikasi Anda.

+ +

Saat sistem memulai komponen, sistem akan memulai proses untuk aplikasi itu (jika +belum berjalan) dan membuat instance kelas yang diperlukan untuk komponen. Misalnya, jika aplikasi Anda +memulai aktivitas dalam aplikasi kamera yang mengambil foto, aktivitas itu akan +berjalan dalam proses yang dimiliki oleh aplikasi kamera, bukan dalam proses aplikasi Anda. +Karenanya, tidak seperti aplikasi di sebagian besar sistem lain, aplikasi Android tidak memiliki titik +masuk tunggal (misalnya tidak ada fungsi {@code main()}).

+ +

Karena sistem menjalankan setiap aplikasi dalam proses terpisah dengan izin file yang +membatasi akses ke aplikasi lain, aplikasi Anda tidak bisa langsung mengaktifkan komponen dari aplikasi lain. Akan tetapi, sistem +Android bisa melakukannya. Jadi, untuk mengaktifkan +komponen dalam aplikasi lain, Anda harus mengirim pesan ke sistem yang menetapkan intent Anda untuk memulai +komponen tertentu. Selanjutnya sistem akan mengaktifkan komponen untuk Anda.

+ + +

Mengaktifkan Komponen

+ +

Tiga dari empat tipe komponen—aktivitas, layanan, dan +penerima siaran—diaktifkan oleh pesan asinkron yang disebut intent. +Intent saling mengikat setiap komponen saat runtime (Anda bisa menganggapnya +sebagai pembawa pesan yang meminta tindakan dari komponen lain), baik komponen itu milik aplikasi Anda +atau milik aplikasi lain.

+ +

Intent dibuat dengan objek {@link android.content.Intent}, yang mendefinisikan pesan untuk +mengaktifkan komponen tertentu atau komponen tipe komponen tertentu—masing-masing intent +bisa eksplisit atau implisit.

+ +

Untuk aktivitas dan layanan, intent mendefinisikan tindakan yang akan dilakukan (misalnya, untuk "melihat" atau +"mengirim" sesuatu) dan mungkin menetapkan URI data untuk ditindaklanjuti (salah satu hal yang mungkin perlu diketahui +oleh komponen yang akan dimulai). Misalnya, intent mungkin menyampaikan permintaan suatu +aktivitas untuk menampilkan gambar atau membuka halaman web. Dalam beberapa kasus, Anda bisa memulai +aktivitas untuk menerima hasil, dalam hal ini, aktivitas juga akan mengembalikan hasil +dalam {@link android.content.Intent} (misalnya Anda bisa mengeluarkan intent agar +pengguna bisa memilih kontak pribadi dan memintanya dikembalikan kepada Anda—intent yang dikembalikan menyertakan URI yang +menunjuk ke kontak yang dipilih).

+ +

Untuk penerima siaran, intent hanya mendefinisikan +pengumuman yang sedang disiarkan (misalnya, siaran untuk menunjukkan baterai perangkat hampir habis +hanya menyertakan string tindakan yang menunjukkan "baterai hampir habis").

+ +

Tipe komponen lainnya dan penyedia konten, tidak diaktifkan oleh intent. Melainkan +diaktifkan saat ditargetkan oleh permintaan dari {@link android.content.ContentResolver}. Resolver +konten menangani semua transaksi langsung dengan penyedia konten sehingga komponen yang melakukan +transaksi dengan penyedia tidak perlu dan sebagai gantinya memanggil metode pada objek {@link +android.content.ContentResolver}. Ini membuat lapisan abstraksi antara penyedia +konten dan komponen yang meminta informasi (demi keamanan).

+ +

Ada beberapa metode terpisah untuk mengaktifkan masing-masing tipe komponen:

+ + +

Untuk informasi selengkapnya tentang menggunakan intent, lihat dokumen Intent dan Filter + Intent. Informasi selengkapnya tentang mengaktifkan komponen +tertentu juga tersedia dalam dokumen berikut: Aktivitas, Layanan, {@link +android.content.BroadcastReceiver} dan Penyedia Konten.

+ + +

File Manifes

+ +

Sebelum sistem Android bisa memulai komponen aplikasi, sistem harus mengetahui +keberadaan komponen dengan membaca file {@code AndroidManifest.xml} aplikasi (file +"manifes"). Aplikasi Anda harus mendeklarasikan semua komponennya dalam file ini, yang harus menjadi akar +dari direktori proyek aplikasi.

+ +

Manifes melakukan banyak hal selain mendeklarasikan komponen aplikasi, +seperti:

+ + + +

Mendeklarasikan komponen

+ +

Tugas utama manifes adalah menginformasikan komponen aplikasi pada sistem. Misalnya, +file manifes bisa mendeklarasikan aktivitas sebagai berikut:

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<manifest ... >
+    <application android:icon="@drawable/app_icon.png" ... >
+        <activity android:name="com.example.project.ExampleActivity"
+                  android:label="@string/example_label" ... >
+        </activity>
+        ...
+    </application>
+</manifest>
+ +

Dalam elemen <application> +, atribut {@code android:icon} menunjuk ke sumber daya untuk ikon yang mengidentifikasi +aplikasi.

+ +

Dalam elemen <activity>, +atribut {@code android:name} menetapkan nama kelas yang sepenuhnya memenuhi syarat subkelas {@link +android.app.Activity} dan atribut {@code android:label} menetapkan string yang akan +digunakan sebagai label yang terlihat oleh pengguna untuk aktivitas tersebut.

+ +

Anda harus mendeklarasikan semua komponen aplikasi dengan cara ini:

+ + +

Aktivitas, layanan, dan penyedia konten yang Anda sertakan dalam kode sumber, namun tidak +dideklarasikan dalam manifes, tidak akan terlihat pada sistem dan, akibatnya, tidak pernah bisa berjalan. Akan tetapi, +penerima siaran +bisa dideklarasikan dalam manifes atau dibuat secara dinamis dalam kode (sebagai objek +{@link android.content.BroadcastReceiver}) dan didaftarkan pada sistem dengan memanggil +{@link android.content.Context#registerReceiver registerReceiver()}.

+ +

Untuk informasi selengkapnya tentang cara menstrukturkan file manifes untuk aplikasi Anda, +lihat dokumentasi File AndroidManifest.xml.

+ + + +

Mendeklarasikan kemampuan komponen

+ +

Seperti telah dibahas di atas, dalam Mengaktifkan Komponen, Anda bisa menggunakan +{@link android.content.Intent} untuk memulai aktivitas, layanan, dan penerima siaran. Anda bisa +melakukannya dengan menamai komponen sasaran secara eksplisit (menggunakan nama kelas komponen) dalam intent. Akan tetapi, +kemampuan intent sebenarnya ada pada konsep intent implisit. Intent implisit +cuma menjelaskan tipe tindakan yang akan dilakukan (dan, secara opsional, data tempat Anda ingin +melakukan tindakan) dan memungkinkan sistem untuk menemukan komponen pada perangkat yang bisa melakukan +tindakan tersebut dan memulainya. Jika ada banyak komponen yang bisa melakukan tindakan yang dijelaskan oleh intent, +maka pengguna bisa memilih komponen yang akan digunakan.

+ +

Cara sistem mengidentifikasi komponen yang bisa merespons intent adalah dengan membandingkan +intent yang diterima dengan filter intent yang disediakan dalam file manifes aplikasi lainnya pada +perangkat.

+ +

Bila mendeklarasikan aktivitas dalam manifes aplikasi, secara opsional Anda bisa menyertakan +filter intent yang mendeklarasikan kemampuan aktivitas agar bisa merespons intent dari +aplikasi lain. Anda bisa mendeklarasikan filter intent untuk komponen dengan +menambahkan elemen {@code +<intent-filter>} sebagai anak elemen deklarasi komponen.

+ +

Misalnya, jika Anda telah membangun aplikasi email dengan aktivitas untuk menulis email baru, Anda bisa +mendeklarasikan filter intent untuk merespons intent "kirim" (untuk mengirim email baru) seperti ini:

+
+<manifest ... >
+    ...
+    <application ... >
+        <activity android:name="com.example.project.ComposeEmailActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.SEND" />
+                <data android:type="*/*" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </activity>
+    </application>
+</manifest>
+
+ +

Kemudian, jika aplikasi lain membuat intent dengan tindakan {@link +android.content.Intent#ACTION_SEND} dan meneruskannya ke {@link android.app.Activity#startActivity +startActivity()}, sistem bisa memulai aktivitas Anda agar pengguna bisa menulis draf dan mengirim +email.

+ +

Untuk informasi selengkapnya tentang membuat filter intent, lihat dokumen Intent dan Filter Intent. +

+ + + +

Mendeklarasikan kebutuhan aplikasi

+ +

Ada berbagai macam perangkat yang didukung oleh Android dan tidak +semuanya menyediakan fitur dan kemampuan yang sama. Agar aplikasi Anda tidak dihapus pada perangkat yang tidak memiliki +fitur yang diperlukan aplikasi, Anda harus jelas mendefinisikan profil mengenai +tipe perangkat yang didukung aplikasi dengan mendeklarasikan kebutuhan perangkat dan perangkat lunak dalam file +manifes. Kebanyakan deklarasi ini hanya bersifat informasi dan sistem tidak +membacanya, namun layanan eksternal seperti Google Play akan membacanya untuk menyediakan +penyaringan bagi pengguna saat mereka mencari aplikasi dari perangkat.

+ +

Misalnya, jika aplikasi memerlukan kamera dan menggunakan API yang disediakan dalam Android 2.1 (API Level 7) +, Anda harus mendeklarasikannya sebagai kebutuhan dalam file manifes seperti ini:

+ +
+<manifest ... >
+    <uses-feature android:name="android.hardware.camera.any"
+                  android:required="true" />
+    <uses-sdk android:minSdkVersion="7" android:targetSdkVersion="19" />
+    ...
+</manifest>
+
+ +

Sekarang, perangkat yang tidak memiliki kamera dan menggunakan +Android versi lebih rendah dari 2.1 tidak bisa menginstal aplikasi Anda dari Google Play.

+ +

Akan tetapi, bisa juga mendeklarasikan bahwa aplikasi Anda menggunakan kamera, namun tidak +mengharuskannya. Dalam hal itu, aplikasi Anda harus mengatur atribut {@code required} + ke {@code "false"} dan memeriksa saat runtime apakah +perangkat memiliki kamera dan menonaktifkan setiap fitur kamera yang sesuai.

+ +

Informasi selengkapnya tentang cara mengelola kompatibilitas aplikasi dengan +perangkat yang berbeda disediakan dalam dokumen +Kompatibilitas Perangkat.

+ + + +

Sumber Daya Aplikasi

+ +

Aplikasi Android tidak hanya terdiri dari kode—Aplikasi memerlukan sumber daya yang +terpisah dari kode sumber, seperti gambar, file audio, dan apa saja yang berkaitan dengan +presentasi visual dari aplikasi. Misalnya, Anda harus mendefinisikan animasi, menu, gaya, warna, +dan layout antarmuka pengguna aktivitas dengan file XML. Penggunaan sumber daya aplikasi +mempermudah pembaruan berbagai karakteristik aplikasi Anda tanpa memodifikasi kode dan—dengan menyediakan +seperangkat sumber daya alternatif—memungkinkan Anda mengoptimalkan aplikasi untuk berbagai konfigurasi +perangkat berbeda (seperti bahasa dan ukuran layar yang berbeda).

+ +

Untuk setiap sumber daya yang Anda sertakan dalam proyek Android, alat bawaan SDK akan mendefinisikan ID integer +unik, yang bisa Anda gunakan untuk mengacu sumber daya dari kode aplikasi atau dari sumber daya lainnya yang +didefinisikan dalam XML. Misalnya, jika aplikasi berisi file gambar bernama {@code +logo.png} (disimpan dalam direktori {@code res/drawable/}), alat SDK akan menghasilkan ID sumber daya +bernama {@code R.drawable.logo}, yang bisa Anda gunakan untuk mengacu gambar dan memasukkannya dalam +antarmuka pengguna.

+ +

Salah satu aspek paling penting dari penyediaan sumber daya yang terpisah dari +kode sumber adalah kemampuan Anda menyediakan sumber daya alternatif untuk konfigurasi perangkat +yang berbeda. Misalnya, dengan mendefinisikan string UI dalam XML, Anda bisa menerjemahkan string ke dalam +bahasa lain dan menyimpan string itu dalam file terpisah. Kemudian, berdasarkan qualifier +bahasa yang ditambahkan ke nama direktori sumber daya (seperti {@code res/values-fr/} untuk nilai +string Prancis) dan pengaturan bahasa pengguna, sistem Android akan menerapkan string bahasa yang sesuai +untuk UI Anda.

+ +

Android mendukung banyak qualifier berbeda untuk sumber daya alternatif Anda. Qualifier +adalah string pendek yang Anda sertakan dalam nama direktori sumber +daya untuk mendefinisikan konfigurasi perangkat yang harus digunakan sumber daya tersebut. Contoh lainnya, +Anda harus sering membuat layout berbeda untuk aktivitas, bergantung pada +orientasi layar dan ukuran perangkat. Misalnya, saat layar perangkat dalam orientasi +tegak, Anda mungkin ingin layout tombolnya vertikal, tetapi saat layar dalam orientasi +mendatar, tombolnya harus sejajar horizontal. Untuk mengubah layout +sesuai orientasi, Anda bisa mendefinisikan dua layout berbeda dan menerapkan qualifier yang +tepat untuk setiap nama direktori layout. Kemudian, sistem secara otomatis menerapkan +layout yang tepat sesuai dengan orientasi perangkat saat ini.

+ +

Untuk informasi selengkapnya tentang berbagai jenis sumber daya yang bisa disertakan dalam aplikasi dan cara +membuat sumber daya alternatif untuk konfigurasi perangkat berbeda, bacalah Menyediakan Sumber Daya.

+ + + +
+
+

Teruskan membaca tentang:

+
+
Intent dan Filter Intent +
+
Informasi tentang cara menggunakan API {@link android.content.Intent} untuk + mengaktifkan komponen aplikasi, seperti aktivitas dan layanan, dan cara menyediakan komponen aplikasi + untuk digunakan oleh aplikasi lain.
+
Aktivitas
+
Informasi tentang cara membuat instance kelas {@link android.app.Activity}, +yang menyediakan layar tersendiri dalam aplikasi bersama antarmuka pengguna.
+
Menyediakan Sumber Daya
+
Informasi tentang cara aplikasi Android disusun untuk memisahkan sumber daya aplikasi dari +kode aplikasi, termasuk cara Anda bisa menyediakan sumber daya alternatif untuk +konfigurasi perangkat tertentu. +
+
+
+
+

Anda juga mungkin tertarik dengan:

+
+
Kompatibilitas Perangkat
+
Informasi tentang cara kerja Android pada berbagai tipe perangkat dan +pengenalan mengenai cara mengoptimalkan aplikasi untuk setiap perangkat atau membatasi ketersediaan aplikasi Anda untuk +perangkat berbeda.
+
Izin Sistem
+
Informasi tentang cara Android membatasi akses aplikasi pada API tertentu dengan sistem izin +yang mengharuskan persetujuan pengguna agar aplikasi dapat menggunakan API tersebut.
+
+
+
+ diff --git a/docs/html-intl/intl/in/guide/components/index.jd b/docs/html-intl/intl/in/guide/components/index.jd new file mode 100644 index 0000000000000000000000000000000000000000..a8dd5f8820fcb038cc7577fb42e7cac5304bc8c8 --- /dev/null +++ b/docs/html-intl/intl/in/guide/components/index.jd @@ -0,0 +1,57 @@ +page.title=Komponen Aplikasi +page.landing=true +page.landing.intro=Kerangka kerja aplikasi Android memungkinkan Anda membuat aplikasi yang kaya dan inovatif menggunakan seperangkat komponen yang dapat digunakan kembali. Bagian ini menjelaskan cara membangun komponen yang mendefinisikan blok pembangun aplikasi Anda dan cara menghubungkannya bersama menggunakan intent. +page.metaDescription=Kerangka kerja aplikasi Android memungkinkan Anda membuat aplikasi yang kaya dan inovatif menggunakan seperangkat komponen yang dapat digunakan kembali. Bagian ini menjelaskan cara membangun komponen yang mendefinisikan blok pembangun aplikasi Anda dan cara menghubungkannya bersama menggunakan intent. +page.landing.image=images/develop/app_components.png +page.image=images/develop/app_components.png + +@jd:body + +
+ +
+

Artikel Blog

+ + +

Menggunakan DialogFragments

+

Dalam posting ini, saya akan menunjukkan cara menggunakan DialogFragments dengan pustaka dukungan v4 (untuk kompatibilitas mundur pada perangkat sebelum Honeycomb) untuk menunjukkan dialog edit sederhana dan mengembalikan hasil ke Aktivitas pemanggil menggunakan antarmuka.

+
+ + +

Fragmen Untuk Semua

+

Hari ini kami telah merilis pustaka statis yang memperlihatkan API Fragment yang sama (serta LoaderManager baru dan beberapa kelas lain) agar aplikasi yang kompatibel dengan Android 1.6 atau yang lebih baru bisa menggunakan fragmen untuk membuat antarmuka pengguna yang kompatibel dengan tablet.

+
+ + +

Multithreading untuk Kinerja

+

Praktik yang baik dalam membuat aplikasi yang responsif adalah memastikan thread UI utama Anda +melakukan pekerjaan minimum. Setiap tugas yang berpotensi lama dan dapat membuat aplikasi mogok harus +ditangani di thread berbeda.

+
+
+ +
+

Pelatihan

+ + +

Mengelola Daur Hidup Aktivitas

+

Bagian ini menjelaskan pentingnya metode callback daur hidup yang diterima setiap instance Aktivitas +dan cara menggunakannya sehingga aktivitas Anda melakukan yang diharapkan pengguna dan tidak menghabiskan sumber daya sistem +saat aktivitas tidak membutuhkannya.

+
+ + +

Membangun UI Dinamis dengan Fragmen

+

Bagian ini menunjukkan kepada Anda cara membuat pengalaman pengguna yang dinamis dengan fragmen dan mengoptimalkan +pengalaman pengguna aplikasi Anda dengan berbagai ukuran layar, sekaligus terus mendukung +perangkat yang menjalankan versi sebelumnya, sesudah versi Android 1.6.

+
+ + +

Berbagi Konten

+

Bagian ini membahas beberapa cara umum untuk mengirim dan menerima konten antar +aplikasi menggunakan API Intent dan objek ActionProvider.

+
+
+ +
diff --git a/docs/html-intl/intl/in/guide/components/intents-filters.jd b/docs/html-intl/intl/in/guide/components/intents-filters.jd new file mode 100644 index 0000000000000000000000000000000000000000..8e89b5db81c74be165a2d8495fa55b792c02ac94 --- /dev/null +++ b/docs/html-intl/intl/in/guide/components/intents-filters.jd @@ -0,0 +1,899 @@ +page.title=Intent dan Filter Intent +page.tags="IntentFilter" +@jd:body + +
+
+ +

Dalam dokumen ini

+
    +
  1. Tipe Intent
  2. +
  3. Membangun Intent +
      +
    1. Contoh intent eksplisit
    2. +
    3. Contoh intent implisit
    4. +
    5. Memaksakan pemilih aplikasi
    6. +
    +
  4. +
  5. Menerima Intent Implisit +
      +
    1. Contoh filter
    2. +
    +
  6. +
  7. Menggunakan Intent Tertunda
  8. +
  9. Resolusi Intent +
      +
    1. Pengujian tindakan
    2. +
    3. Pengujian kategori
    4. +
    5. Pengujian data
    6. +
    7. Pencocokan intent
    8. +
    +
  10. +
+ +

Lihat juga

+
    +
  1. Berinteraksi dengan Aplikasi Lain
  2. +
  3. Berbagi Konten
  4. +
+ +
+
+ + + + +

{@link android.content.Intent} merupakan objek pertukaran pesan yang bisa Anda gunakan untuk meminta tindakan +dari komponen aplikasi lain. +Walaupun intent memudahkan komunikasi antarkomponen dalam beberapa cara, ada tiga +kasus-penggunaan dasar:

+ + + + + + +

Tipe Intent

+ +

Ada dua tipe intent:

+ + + +

Saat Anda membuat intent eksplisit untuk memulai aktivitas atau layanan, sistem akan segera +memulai komponen aplikasi yang telah ditetapkan dalam objek {@link android.content.Intent}.

+ +
+ +

Gambar 1. Ilustrasi yang menggambarkan cara intent implisit +disampaikan melalui sistem untuk memulai aktivitas lain: [1] Aktivitas A membuat sebuah +{@link android.content.Intent} dengan keterangan tindakan dan meneruskannya ke {@link +android.content.Context#startActivity startActivity()}. [2] Sistem Android akan mencari semua +aplikasi untuk filter intent yang cocok dengan intent tersebut. Bila cocok, [3] sistem akan +memulai aktivitas mencocokkan (Aktivitas B) dengan memanggil metode {@link +android.app.Activity#onCreate onCreate()} dan meneruskannya ke {@link android.content.Intent}. +

+
+ +

Bila Anda membuat intent implisit, sistem Android akan menemukan komponen yang sesuai untuk memulai +dengan membandingkan konten intent dengan filter intent yang dideklarasikan dalam file manifes aplikasi lain di +perangkat. Jika intent cocok dengan filter intent, sistem akan memulai komponen tersebut dan mengiriminya +objek {@link android.content.Intent}. Jika banyak filter intent yang kompatibel, sistem +menampilkan dialog sehingga pengguna bisa memilih aplikasi yang akan digunakan.

+ +

Filter intent adalah ekspresi dalam file manifes aplikasi yang +menetapkan tipe intent yang akan diterima +komponen. Misalnya, dengan mendeklarasikan intent filter untuk aktivitas, +Anda akan memungkinkan aplikasi lain untuk langsung memulai aktivitas Anda dengan intent tertentu. +Demikian pula, jika Anda tidak mendeklarasikan filter intent untuk suatu aktivitas, maka aktivitas tersebut hanya bisa dimulai +dengan intent eksplisit.

+ +

Perhatian: Untuk memastikan aplikasi Anda aman, selalu gunakan intent +eksplisit saat memulai {@link android.app.Service} dan jangan +mendeklarasikan filter intent untuk layanan. Menggunakan intent implisit untuk memulai layanan akan menimbulkan +bahaya keamanan karena Anda tidak bisa memastikan layanan apa yang akan merespons intent, +dan pengguna tidak bisa melihat layanan mana yang dimulai. Mulai dari Android 5.0 (API level 21), sistem +melontarkan eksepsi jika Anda memanggil {@link android.content.Context#bindService bindService()} +dengan intent implisit.

+ + + + + +

Membangun Intent

+ +

Objek {@link android.content.Intent} membawa informasi yang digunakan sistem Android +untuk menentukan komponen mana yang akan dimulai (misalnya nama persis dari suatu komponen atau kategori +komponen yang seharusnya menerima intent), ditambah informasi yang digunakan komponen penerima untuk +melakukan tindakan dengan benar (misalnya tindakan yang harus dilakukan dan data yang harus diolah).

+ + +

Informasi utama yang dimuat dalam {@link android.content.Intent} adalah sebagai berikut:

+ +
+ +
Nama komponen
+
Nama komponen yang akan dimulai. + +

Ini opsional, namun merupakan bagian informasi penting yang membuat intent +menjadi eksplisit, yaitu intent harus dikirim hanya ke komponen aplikasi +yang didefinisikan oleh nama komponen. Tanpa nama komponen, intent menjadi implisit dan +sistem akan memutuskan komponen mana yang harus menerima intent berdasarkan informasi intent lain +(misalnya tindakan, data, dan kategori—yang dijelaskan di bawah ini). Jadi jika Anda ingin memulai komponen +tertentu dalam aplikasi, Anda harus menetapkan nama komponen tersebut.

+ +

Catatan: Saat memulai {@link android.app.Service}, Anda harus +selalu menetapkan nama komponen. Jika tidak, maka Anda tidak bisa memastikan layanan apa +yang akan merespons intent tersebut, dan pengguna tidak bisa melihat layanan mana yang dimulai.

+ +

Bidang {@link android.content.Intent} ini adalah objek +{@link android.content.ComponentName}, yang bisa Anda tetapkan menggunakan +nama kelas yang sepenuhnya memenuhi syarat dari komponen target, termasuk nama paket aplikasi. Misalnya, +{@code com.example.ExampleActivity}. Anda bisa mengatur nama komponen dengan {@link +android.content.Intent#setComponent setComponent()}, {@link android.content.Intent#setClass +setClass()}, {@link android.content.Intent#setClassName(String, String) setClassName()}, atau dengan konstruktor +{@link android.content.Intent}.

+ +
+ +

Tindakan
+
String yang menetapkan tindakan generik untuk dilakukan (misalnya lihat atau pilih). + +

Dalam hal intent siaran, ini adalah tindakan yang terjadi dan dilaporkan. +Tindakan ini sangat menentukan bagaimana keseluruhan intent disusun—terutama +apa yang dimuat dalam data dan ekstra. + +

Anda bisa menetapkan tindakan sendiri yang akan digunakan oleh intent dalam aplikasi Anda (atau digunakan oleh aplikasi +lain untuk memanggil komponen dalam aplikasi Anda), namun Anda harus menggunakan konstanta tindakan +yang didefinisikan oleh kelas {@link android.content.Intent} atau kelas kerangka kerja lain. Berikut ini adalah beberapa +tindakan umum untuk memulai sebuah aktivitas:

+ +
+
{@link android.content.Intent#ACTION_VIEW}
+
Gunakan tindakan ini dalam intent dengan {@link + android.content.Context#startActivity startActivity()} saat Anda memiliki beberapa informasi yang + bisa ditampilkan aktivitas kepada pengguna, misalnya foto yang bisa dilihat dalam aplikasi galeri, atau alamat + yang bisa dilihat dalam aplikasi peta.
+ +
{@link android.content.Intent#ACTION_SEND}
+
Juga dikenal dengan intent "berbagi", Anda harus menggunakannya dalam intent dengan {@link + android.content.Context#startActivity startActivity()} bila Anda memiliki data yang bisa digunakan pengguna untuk + berbagi melalui aplikasi lain, misalnya aplikasi email atau aplikasi jaringan sosial.
+
+ +

Lihat referensi kelas {@link android.content.Intent} untuk konstanta +selengkapnya yang mendefinisikan tindakan generik. Tindakan lain yang didefinisikan +di tempat lain dalam kerangka kerja Android, misalnya dalam {@link android.provider.Settings} untuk tindakan +yang membuka layar tertentu dalam aplikasi Settings di sistem.

+ +

Anda bisa menetapkan tindakan untuk sebuah intent dengan {@link android.content.Intent#setAction +setAction()} atau dengan konstruktor {@link android.content.Intent}.

+ +

Jika mendefinisikan tindakan Anda sendiri, pastikan untuk memasukkan nama paket aplikasi Anda +sebagai awalan. Misalnya:

+
static final String ACTION_TIMETRAVEL = "com.example.action.TIMETRAVEL";
+
+ +
Data
+
URI (objek {@link android.net.Uri}) yang mengacu data untuk diolah dan/atau +tipe MIME dari data tersebut. Tipe data yang disediakan umumnya didikte oleh tindakan intent. +Misalnya, jika tindakan merupakan {@link android.content.Intent#ACTION_EDIT}, data harus berisi +URI dari dokumen untuk diedit. + +

Saat membuat intent, +seringkali tipe data (tipe MIME-nya) selain URI perlu ditetapkan. +Misalnya, aktivitas yang mampu menampilkan gambar mungkin tidak mampu +memutar file audio, walaupun format URI mungkin serupa. +Jadi menetapkan tipe MIME data Anda akan membantu sistem +Android menemukan komponen terbaik untuk diterima intent. +Akan tetapi, tipe MIME seringkali bisa diambil dari URI—terutama saat datanya merupakan URI +{@code content:}, yang menunjukkan data tersebut berada di perangkat dan dikontrol oleh +{@link android.content.ContentProvider}, yang membuat data tipe MIME terlihat di sistem.

+ +

Untuk mengatur data URI saja, panggil {@link android.content.Intent#setData setData()}. +Untuk mengatur tipe MIME saja, panggil {@link android.content.Intent#setType setType()}. Jika perlu, Anda +bisa mengatur keduanya secara eksplisit dengan {@link +android.content.Intent#setDataAndType setDataAndType()}.

+ +

Perhatian: Jika ingin mengatur tipe URI dan MIME, +jangan panggil {@link android.content.Intent#setData setData()} dan +{@link android.content.Intent#setType setType()} karena mereka saling menghapuskan nilai satu sama lain. +Selalu gunakan {@link android.content.Intent#setDataAndType setDataAndType()} untuk mengatur +tipe URI maupun MIME.

+
+ +

Kategori
+
String yang berisi informasi tambahan tentang jenis komponen +yang harus menangani intent. Keterangan kategori dalam jumlah berapa pun bisa +dimasukkan dalam intent, namun sebagian besar intent tidak memerlukan kategori. +Berikut ini adalah beberapa kategori umum: + +
+
{@link android.content.Intent#CATEGORY_BROWSABLE}
+
Aktivitas target memungkinkannya dimulai oleh browser web untuk menampilkan data +yang diacu oleh tautan—misalnya gambar atau pesan e-mail. +
+
{@link android.content.Intent#CATEGORY_LAUNCHER}
+
Aktivitas tersebut adalah aktivitas awal dari sebuah tugas dan dicantumkan dalam + launcher aplikasi sistem. +
+
+ +

Lihat keterangan kelas {@link android.content.Intent} untuk mengetahui daftar lengkap +kategori.

+ +

Anda bisa menetapkan kategori dengan {@link android.content.Intent#addCategory addCategory()}.

+
+
+ + +

Properti yang tercantum di atas (nama komponen, tindakan, data, dan kategori) menyatakan +karakteristik yang mendefinisikan intent. Dengan membaca properti ini, sistem Android +mampu memutuskan komponen aplikasi yang harus dimulainya.

+ +

Akan tetapi, intent bisa membawa informasi tambahan yang tidak memengaruhi +cara intent ditetapkan pada komponen aplikasi. Intent juga bisa menyediakan:

+ +
+
Ekstra
+
Pasangan nilai-kunci yang membawa informasi yang diperlukan untuk menghasilkan tindakan yang diminta. +Seperti halnya beberapa tindakan menggunakan jenis tertentu URI data, beberapa tindakan juga menggunakan ekstra tertentu. + +

Anda bisa menambahkan data ekstra dengan beragam metode {@link android.content.Intent#putExtra putExtra()}, +masing-masing menerima dua parameter: nama kunci dan nilainya. +Anda juga bisa membuat objek {@link android.os.Bundle} dengan semua data ekstra, kemudian memasukkan +{@link android.os.Bundle} dalam {@link android.content.Intent} dengan {@link +android.content.Intent#putExtras putExtras()}.

+ +

Misalnya, saat membuat intent yang akan dikirimkan bersama email +{@link android.content.Intent#ACTION_SEND}, Anda bisa menetapkan penerima "kepada" dengan kunci +{@link android.content.Intent#EXTRA_EMAIL}, dan menetapkan "subjek" dengan kunci +{@link android.content.Intent#EXTRA_SUBJECT}.

+ +

Kelas {@link android.content.Intent} menetapkan beberapa konstanta {@code EXTRA_*} +untuk tipe data standar. Jika Anda ingin mendeklarasikan kunci ekstra sendiri (untuk intent yang +diterima aplikasi Anda), pastikan untuk memasukkan nama paket aplikasi +sebagai awalan. Misalnya:

+
static final String EXTRA_GIGAWATTS = "com.example.EXTRA_GIGAWATTS";
+
+ +
Flag
+
Flag didefinisikan dalam kelas {@link android.content.Intent} yang berfungsi sebagai metadata untuk +intent. Flag menginstruksikan cara meluncurkan aktivitas (misalnya, +tugas mana yang harus dimiliki suatu aktivitas +) dan cara memperlakukannya setelah diluncurkan (misalnya, apakah aktivitas tersebut masuk ke dalam daftar aktivitas +terbaru) pada sistem Android. + +

Untuk informasi selengkapnya, lihat metode {@link android.content.Intent#setFlags setFlags()} .

+
+ +
+ + + + +

Contoh intent eksplisit

+ +

Intent eksplisit adalah intent yang Anda gunakan untuk meluncurkan komponen aplikasi tertentu, seperti +aktivitas tertentu atau layanan dalam aplikasi Anda. Untuk membuat intent eksplisit, definisikan +nama komponen untuk objek {@link android.content.Intent} —semua +properti intent lain bersifat opsional.

+ +

Misalnya, jika Anda ingin membangun layanan dalam aplikasi Anda, bernama {@code DownloadService}, +yang didesain untuk mengunduh file dari web, Anda bisa memulainya dengan kode berikut ini:

+ +
+// Executed in an Activity, so 'this' is the {@link android.content.Context}
+// The fileUrl is a string URL, such as "http://www.example.com/image.png"
+Intent downloadIntent = new Intent(this, DownloadService.class);
+downloadIntent.setData({@link android.net.Uri#parse Uri.parse}(fileUrl));
+startService(downloadIntent);
+
+ +

Konstruktor {@link android.content.Intent#Intent(Context,Class)} + menyediakan {@link android.content.Context} aplikasi dan +objek {@link java.lang.Class} pada komponen. Dengan demikian, +intent ini memulai secara eksplisit kelas {@code DownloadService} dalam aplikasi.

+ +

Untuk informasi selengkapnya tentang membangun dan memulai layanan, lihat panduan +Layanan.

+ + + + +

Contoh intent implisit

+ +

Intent implisit menetapkan tindakan yang bisa memanggil aplikasi pada perangkat yang mampu +melakukan tindakan. Menggunakan intent implisit berguna bila aplikasi Anda tidak bisa melakukan +tindakan, namun aplikasi lain mungkin bisa melakukannya dan Anda ingin pengguna untuk memilih aplikasi mana yang ingin digunakan.

+ +

Misalnya, jika memiliki konten yang Anda ingin agar pengguna berbagi konten itu dengan orang lain, buatlah intent +dengan tindakan {@link android.content.Intent#ACTION_SEND} +dan tambahkan ekstra yang menetapkan konten yang akan dibagikan. Bila Anda memanggil +{@link android.content.Context#startActivity startActivity()} dengan intent tersebut, pengguna bisa +memilih aplikasi yang akan digunakan untuk berbagi konten.

+ +

Perhatian: Ada kemungkinan pengguna tidak memiliki suatu +aplikasi yang menangani intent implisit yang Anda kirimkan ke {@link android.content.Context#startActivity +startActivity()}. Jika itu terjadi, panggilan akan gagal dan aplikasi Anda akan crash. Untuk memeriksa +apakah aktivitas bisa menerima intent, panggil {@link android.content.Intent#resolveActivity +resolveActivity()} pada objek {@link android.content.Intent} Anda. Jika hasilnya bukan nol, +berarti setidaknya ada satu aplikasi yang bisa menangani intent tersebut dan aman untuk memanggil +{@link android.content.Context#startActivity startActivity()}. Jika hasilnya nol, +Anda tidak boleh menggunakan intent tersebut dan, jika memungkinkan, Anda harus menonaktifkan fitur yang mengeluarkan +intent tersebut.

+ + +
+// Create the text message with a string
+Intent sendIntent = new Intent();
+sendIntent.setAction(Intent.ACTION_SEND);
+sendIntent.putExtra(Intent.EXTRA_TEXT, textMessage);
+sendIntent.setType("text/plain");
+
+// Verify that the intent will resolve to an activity
+if (sendIntent.resolveActivity(getPackageManager()) != null) {
+    startActivity(sendIntent);
+}
+
+ +

Catatan: Dalam hal ini, URI tidak digunakan, namun tipe data intent +dideklarasikan untuk menetapkan konten yang dibawa oleh ekstra.

+ + +

Saat {@link android.content.Context#startActivity startActivity()} dipanggil, sistem akan +memeriksa semua aplikasi yang terinstal untuk menentukan aplikasi mana yang bisa menangani intent jenis ini ( +intent dengan tindakan {@link android.content.Intent#ACTION_SEND} dan yang membawa data +"teks/polos"). Jika hanya ada satu aplikasi yang bisa menanganinya, aplikasi tersebut akan langsung terbuka dan diberi +intent tersebut. Jika banyak aktivitas menerima intent, sistem akan +menampilkan dialog sehingga pengguna bisa memilih aplikasi mana yang digunakan.

+ + +
+ +

Gambar 2. Dialog pemilih.

+
+ +

Memaksakan pemilih aplikasi

+ +

Bila ada lebih dari satu aplikasi yang merespons intent implisit Anda, +pengguna bisa memilih aplikasi mana yang digunakan dan membuat aplikasi tersebut pilihan default untuk +tindakan tersebut. Ini sangat membantu saat melakukan tindakan di mana pengguna +mungkin ingin menggunakan aplikasi yang sama untuk seterusnya, seperti saat membuka halaman web (pengguna +biasanya memilih hanya satu browser web).

+ +

Akan tetapi, jika ada banyak aplikasi yang bisa merespons intent tersebut dan pengguna mungkin ingin menggunakan aplikasi +yang berbeda untuk setiap kalinya, Anda harus menampilkan dialog pemilih secara eksplisit. Dialog pemilih akan meminta +pengguna memilih aplikasi yang akan digunakan untuk tindakan tertentu setiap kali (pengguna tidak bisa memilih aplikasi default untuk +tindakan tersebut). Misalnya, saat aplikasi Anda melakukan "berbagi" dengan tindakan {@link +android.content.Intent#ACTION_SEND}, pengguna mungkin ingin berbagi menggunakan aplikasi berbeda sesuai +dengan situasi mereka saat itu, jadi Anda harus selalu menggunakan dialog pemilih, seperti yang ditampilkan dalam gambar 2.

+ + + + +

Untuk menampilkan pemilih, buatlah {@link android.content.Intent} menggunakan {@link +android.content.Intent#createChooser createChooser()} dan teruskan ke {@link +android.app.Activity#startActivity startActivity()}. Misalnya:

+ +
+Intent sendIntent = new Intent(Intent.ACTION_SEND);
+...
+
+// Always use string resources for UI text.
+// This says something like "Share this photo with"
+String title = getResources().getString(R.string.chooser_title);
+// Create intent to show the chooser dialog
+Intent chooser = Intent.createChooser(sendIntent, title);
+
+// Verify the original intent will resolve to at least one activity
+if (sendIntent.resolveActivity(getPackageManager()) != null) {
+    startActivity(chooser);
+}
+
+ +

Ini menampilkan dialog dengan daftar aplikasi yang merespons intent yang diteruskan ke metode {@link +android.content.Intent#createChooser createChooser()} dan menggunakan teks yang disediakan sebagai +judul dialog.

+ + + + + + + + + +

Menerima Intent Implisit

+ +

Untuk mengiklankan intent implisit yang bisa diterima aplikasi Anda, deklarasikan satu atau beberapa filter intent untuk +tiap komponen aplikasi dengan elemen {@code <intent-filter>} +dalam file manifes Anda. +Tiap filter intent menetapkan tipe intent yang diterimanya berdasarkan tindakan intent, +data, dan kategori. Sistem akan mengirim intent implisit ke komponen aplikasi Anda hanya jika +intent tersebut bisa diteruskan melalui salah satu filter intent.

+ +

Catatan: Intent eksplisit selalu dikirimkan ke targetnya, +apa pun filter intent yang dideklarasikan komponen.

+ +

Komponen aplikasi harus mendeklarasikan filter terpisah untuk setiap pekerjaan unik yang bisa dilakukannya. +Misalnya, satu aktivitas dalam aplikasi galeri gambar bisa memiliki dua filter: satu filter +untuk melihat gambar, dan filter lainnya untuk mengedit gambar. Bila aktivitas dimulai, +aktivitas akan memeriksa {@link android.content.Intent} dan menentukan cara berperilaku berdasarkan informasi +dalam {@link android.content.Intent} (misalnya menampilkan kontrol editor atau tidak).

+ +

Tiap filter intent didefinisikan oleh elemen {@code <intent-filter>} +dalam file manifes aplikasi, yang tersarang dalam komponen aplikasi terkait (seperti +elemen {@code <activity>} +). Di dalam {@code <intent-filter>}, +Anda bisa menetapkan tipe intent yang akan diterima dengan menggunakan salah satu atau beberapa +dari tiga elemen ini:

+ +
+
{@code <action>}
+
Mendeklarasikan tindakan intent yang diterima, dalam atribut {@code name}. Nilai + haruslah nilai string literal dari tindakan, bukan konstanta kelas.
+
{@code <data>}
+
Mendeklarasikan tipe data yang diterima, menggunakan salah satu atau beberapa atribut yang menetapkan beragam + aspek URI data (scheme, host, port, + path, dll.) dan tipe MIME.
+
{@code <category>}
+
Mendeklarasikan kategori intent yang diterima, dalam atribut {@code name}. Nilai + haruslah nilai string literal dari tindakan, bukan konstanta kelas. + +

Catatan: Untuk menerima intent implisit, Anda + harus menyertakan kategori +{@link android.content.Intent#CATEGORY_DEFAULT} dalam filter intent. Metode + {@link android.app.Activity#startActivity startActivity()}dan + {@link android.app.Activity#startActivityForResult startActivityForResult()} memperlakukan semua intent + seolah-olah mendeklarasikan kategori {@link android.content.Intent#CATEGORY_DEFAULT}. + Jika tidak mendeklarasikan kategori ini dalam filter intent Anda, tidak ada intent implisit yang ditetapkan untuk + aktivitas Anda.

+
+
+ +

Misalnya, ini adalah deklarasi aktivitas dengan filter intent yang diterima intent +{@link android.content.Intent#ACTION_SEND} bila tipe data berupa teks:

+ +
+<activity android:name="ShareActivity">
+    <intent-filter>
+        <action android:name="android.intent.action.SEND"/>
+        <category android:name="android.intent.category.DEFAULT"/>
+        <data android:mimeType="text/plain"/>
+    </intent-filter>
+</activity>
+
+ +

Anda bisa membuat filter yang menyertakan lebih dari satu instance +{@code <action>}, +{@code <data>}, atau +{@code <category>}. +Jika Anda melakukannya, Anda hanya perlu memastikan bahwa komponen bisa menangani semua kombinasi +elemen filter tersebut.

+ +

Bila ingin menangani beragam jenis intent, namun hanya dalam kombinasi +tindakan, data, dan tipe kategori tertentu, maka Anda harus membuat banyak filter intent.

+ + + + +

Intent implisit diuji terhadap filter dengan membandingkan intent dengan masing-masing +dari ketiga elemen. Agar dikirim ke komponen, intent harus lolos ketiga pengujian tersebut. +Jika intent gagal dalam salah satu pengujian, sistem Android tidak akan mengirim intent ke +komponen. Akan tetapi, karena sebuah komponen dapat memiliki beberapa filter intent, intent yang tidak +lolos melalui salah satu filter komponen mungkin akan lolos di filter lain. +Informasi selengkapnya tentang cara sistem menetapkan intent disediakan dalam bagian di bawah ini +tentang Resolusi Intent.

+ +

Perhatian: Untuk menghindari menjalankan +{@link android.app.Service} aplikasi yang berbeda secara tidak sengaja, selalu gunakan intent eksplisit untuk memulai layanan Anda sendiri dan jangan +deklarasikan filter intent untuk layanan Anda.

+ +

Catatan: +Untuk semua aktivitas, Anda harus mendeklarasikan filter intent dalam file manifes. +Akan tetapi, filter untuk penerima siaran bisa didaftarkan secara dinamis dengan memanggil +{@link android.content.Context#registerReceiver(BroadcastReceiver, IntentFilter, String, +Handler) registerReceiver()}. Anda nanti bisa mencabut pendaftaran penerima dengan {@link +android.content.Context#unregisterReceiver unregisterReceiver()}. Dengan begitu aplikasi Anda +bisa mendengarkan siaran tertentu hanya selama periode waktu yang telah ditetapkan saat aplikasi Anda +berjalan.

+ + + + + + + +

Contoh filter

+ +

Untuk lebih memahami beberapa perilaku filter intent, lihatlah cuplikan berikut +dari file manifes aplikasi berbagi di jaringan sosial.

+ +
+<activity android:name="MainActivity">
+    <!-- This activity is the main entry, should appear in app launcher -->
+    <intent-filter>
+        <action android:name="android.intent.action.MAIN" />
+        <category android:name="android.intent.category.LAUNCHER" />
+    </intent-filter>
+</activity>
+
+<activity android:name="ShareActivity">
+    <!-- This activity handles "SEND" actions with text data -->
+    <intent-filter>
+        <action android:name="android.intent.action.SEND"/>
+        <category android:name="android.intent.category.DEFAULT"/>
+        <data android:mimeType="text/plain"/>
+    </intent-filter>
+    <!-- This activity also handles "SEND" and "SEND_MULTIPLE" with media data -->
+    <intent-filter>
+        <action android:name="android.intent.action.SEND"/>
+        <action android:name="android.intent.action.SEND_MULTIPLE"/>
+        <category android:name="android.intent.category.DEFAULT"/>
+        <data android:mimeType="application/vnd.google.panorama360+jpg"/>
+        <data android:mimeType="image/*"/>
+        <data android:mimeType="video/*"/>
+    </intent-filter>
+</activity>
+
+ +

Aktivitas pertama, {@code MainActivity}, merupakan titik masuk utama aplikasi—aplikasi yang +terbuka saat pengguna meluncurkan aplikasi dengan ikon launcher:

+ +

Keduanya harus dipasangkan bersama agar aktivitas muncul dalam launcher aplikasi.

+ +

Aktivitas kedua, {@code ShareActivity}, dimaksudkan untuk memudahkan berbagi teks dan konten +media. Walaupun pengguna mungkin memasuki aktivitas ini dengan mengarah ke aktivitas dari {@code MainActivity}, +pengguna juga bisa memasukkan {@code ShareActivity} secara langsung dari aplikasi lain yang mengeluarkan intent +implisit yang cocok dengan salah satu dari kedua filter intent.

+ +

Catatan: Tipe MIME, +{@code +application/vnd.google.panorama360+jpg}, merupakan tipe data khusus yang menetapkan +foto panorama, yang bisa Anda tangani dengan API panorama +Google.

+ + + + + + + + + + + + + +

Menggunakan Intent Tertunda

+ +

Objek {@link android.app.PendingIntent} merupakan pembungkus objek {@link +android.content.Intent}. Tujuan utama {@link android.app.PendingIntent} +adalah memberikan izin pada aplikasi asing +untuk menggunakan {@link android.content.Intent} yang termuat seolah-olah dieksekusi dari +proses aplikasi Anda sendiri.

+ +

Kasus penggunaan utama untuk intent tertunda antara lain:

+ + +

Karena setiap objek {@link android.content.Intent} didesain untuk ditangani oleh tipe +tertentu dari komponen aplikasi (baik {@link android.app.Activity}, {@link android.app.Service}, maupun + {@link android.content.BroadcastReceiver}), jadi {@link android.app.PendingIntent} harus +dibuat dengan pertimbangan yang sama. Saat menggunakan intent tertunda, aplikasi Anda tidak akan +mengeksekusi intent dengan panggilan seperti {@link android.content.Context#startActivity +startActivity()}. Anda harus mendeklarasikan tipe komponen yang dimaksud saat membuat +{@link android.app.PendingIntent} dengan memanggil metode kreator masing-masing:

+ + + +

Kecuali jika aplikasi Anda menerima intent tertunda dari aplikasi lain, +metode di atas untuk membuat {@link android.app.PendingIntent} menjadi satu-satunya metode +{@link android.app.PendingIntent} yang mungkin Anda butuhkan.

+ +

Tiap metode mengambil {@link android.content.Context} aplikasi saat itu, +{@link android.content.Intent} yang ingin Anda bungkus, dan satu atau beberapa flag yang menetapkan +cara penggunaan intent (misalnya apakah intent bisa digunakan lebih dari sekali).

+ +

Informasi selengkapnya tentang intent tertunda disediakan pada dokumentasi untuk setiap +kasus penggunaan yang bersangkutan, seperti dalam panduan API Notifications +dan App Widgets.

+ + + + + + + +

Resolusi Intent

+ + +

Saat sistem menerima intent implisit yang memulai suatu aktivitas, sistem tersebut akan mencari +aktivitas terbaik untuk intent dengan membandingkan intent dengan filter intent berdasarkan tiga aspek:

+ + + +

Bagian berikut menjelaskan cara pencocokan intent dengan komponen yang sesuai +sehubungan dengan cara pendeklarasian filter intent dalam file manifes aplikasi.

+ + +

Pengujian tindakan

+ +

Untuk menetapkan tindakan intent yang diterima, filter intent bisa mendeklarasikan nol atau beberapa elemen +{@code +<action>}. Misalnya:

+ +
+<intent-filter>
+    <action android:name="android.intent.action.EDIT" />
+    <action android:name="android.intent.action.VIEW" />
+    ...
+</intent-filter>
+
+ +

Untuk melewati filter ini, tindakan yang ditetapkan dalam {@link android.content.Intent} +harus sesuai dengan salah satu tindakan yang tercantum dalam filter.

+ +

Jika filter tidak mencantumkan tindakan apa pun, maka tidak ada intent +yang dicocokkan, jadi semua intent gagal dalam pengujian. Akan tetapi, jika sebuah {@link android.content.Intent} +tidak menetapkan suatu tindakan, maka akan lolos pengujian (asalkan filter +berisi setidaknya satu tindakan).

+ + + +

Pengujian kategori

+ +

Untuk menetapkan kategori intent yang diterima, filter intent bisa mendeklarasikan nol atau beberapa elemen +{@code +<category>}. Misalnya:

+ +
+<intent-filter>
+    <category android:name="android.intent.category.DEFAULT" />
+    <category android:name="android.intent.category.BROWSABLE" />
+    ...
+</intent-filter>
+
+ +

Agar intent bisa lolos pengujian kategori, setiap kategori dalam {@link android.content.Intent} +harus sesuai dengan kategori dalam filter. Kebalikannya tidak diperlukan—filter intent bisa +mendeklarasikan kategori lebih banyak daripada yang ditetapkan dalam {@link android.content.Intent} dan +{@link android.content.Intent} tetap akan lolos. Oleh karena itu, intent tanpa kategori harus +selalu lolos pengujian ini, kategori apa pun yang dideklarasikan dalam filter.

+ +

Catatan: +Android secara otomatis menerapkan kategori {@link android.content.Intent#CATEGORY_DEFAULT} +untuk semua intent implisit yang diteruskan ke {@link +android.content.Context#startActivity startActivity()} dan {@link +android.app.Activity#startActivityForResult startActivityForResult()}. +Jadi jika ingin aktivitas Anda menerima intent implisit, aktivitas tersebut harus +menyertakan kategori untuk{@code "android.intent.category.DEFAULT"} dalam filter intent (seperti +yang ditampilkan dalam contoh{@code <intent-filter>} sebelumnya.

+ + + +

Pengujian data

+ +

Untuk menetapkan data intent yang diterima, filter intent bisa mendeklarasikan nol atau beberapa elemen +{@code +<data>}. Misalnya:

+ +
+<intent-filter>
+    <data android:mimeType="video/mpeg" android:scheme="http" ... />
+    <data android:mimeType="audio/mpeg" android:scheme="http" ... />
+    ...
+</intent-filter>
+
+ +

Tiap elemen <data> +bisa menetapkan struktur URI dan tipe data (tipe media MIME). Ada atribut +terpisah — {@code scheme}, {@code host}, {@code port}, +dan {@code path} — untuk setiap bagian URI: +

+ +

{@code <scheme>://<host>:<port>/<path>}

+ +

+Misalnya: +

+ +

{@code content://com.example.project:200/folder/subfolder/etc}

+ +

Dalam URI ini, skemanya adalah {@code content}, host-nya adalah {@code com.example.project}, +port-nya adalah {@code 200}, dan path-nya adalah {@code folder/subfolder/etc}. +

+ +

Tiap atribut bersifat opsional dalam elemen {@code <data>}, +namun ada dependensi linear:

+ + +

Bila URI dalam intent dibandingkan dengan spesifikasi URI dalam filter, +pembandingannya hanya dengan bagian URI yang disertakan dalam filter. Misalnya:

+ + +

Catatan: Spesifikasi path bisa berisi +wildcard bintang (*) untuk hanya mencocokkan nama path secara parsial.

+ +

Pengujian data membandingkan URI maupun tipe MIME dalam intent dengan URI +dan tipe MIME yang ditetapkan dalam filter. Aturannya adalah sebagai berikut: +

+ +
    +
  1. Intent yang tidak berisi URI maupun tipe MIME hanya akan lolos +pengujian jika filter tersebut tidak menetapkan URI atau tipe MIME apa pun.
  2. + +
  3. Intent yang berisi URI namun tidak berisi tipe MIME (baik secara eksplisit maupun tidak langsung dari +URI) hanya akan lolos pengujian jika URI-nya cocok dengan format URI filter +dan filternya juga tidak menetapkan tipe MIME.
  4. + +
  5. Intent yang berisi tipe MIME namun tidak berisi URI hanya akan lolos pengujian +jika filter mencantumkan tipe MIME yang sama dan tidak menetapkan format URI.
  6. + +
  7. Intent yang berisi URI maupun tipe MIME (baik secara eksplisit maupun tidak langsung dari +URI) hanya akan lolos pengujian bagian tipe MIME jika +tipe tersebut cocok dengan tipe yang dicantumkan dalam filter. Ini akan lolos pengujian bagian URI +jika URI-nya cocok dengan URI dalam filter atau memiliki {@code content:} +atau URI {@code file:} dan filter tidak menetapkan URI. Dengan kata lain, +komponen dianggap mendukung data {@code content:} dan {@code file:} jika +filternya hanya mencantumkan tipe MIME.

  8. +
+ +

+Aturan terakhir ini, aturan (d), mencerminkan harapan +bahwa komponen mampu mendapatkan data lokal dari file atau penyedia konten. +Oleh karena itu, filter mereka mencatumkan tipe data saja dan tidak secara eksplisit +harus menamai skema {@code content:} dan {@code file:}. +Ini adalah kasus umum. Elemen {@code <data>} +seperti berikut ini, misalnya, memberi tahu Android bahwa komponen bisa mengambil data gambar dari penyedia +konten dan menampilkannya: +

+ +
+<intent-filter>
+    <data android:mimeType="image/*" />
+    ...
+</intent-filter>
+ +

+Karena sebagian besar data yang tersedia dikeluarkan oleh penyedia konten, filter yang +menetapkan tipe data namun bukan URI mungkin adalah yang paling umum. +

+ +

+Konfigurasi umum yang lain adalah filter dengan skema dan tipe data. Misalnya +, elemen {@code <data>} + seperti berikut ini akan memberi tahu Android bahwa +komponen bisa mengambil data video dari jaringan untuk melakukan tindakan: +

+ +
+<intent-filter>
+    <data android:scheme="http" android:type="video/*" />
+    ...
+</intent-filter>
+ + + +

Pencocokan intent

+ +

Intent dicocokkan dengan filter intent selain untuk menemukan komponen +target yang akan diaktifkan, juga untuk menemukan sesuatu tentang rangkaian +komponen pada perangkat. Misalnya, aplikasi Home akan menempatkan launcher aplikasi +dengan mencari semua aktivitas dengan filter intent yang menetapkan tindakan +{@link android.content.Intent#ACTION_MAIN} dan +kategori {@link android.content.Intent#CATEGORY_LAUNCHER}.

+ +

Aplikasi Anda bisa menggunakan pencocokan intent dengan cara serupa. +{@link android.content.pm.PackageManager} memiliki seperangkat metode {@code query...()} +yang mengembalikan semua komponen yang bisa menerima intent tertentu, dan +serangkaian metode{@code resolve...()} serupa yang menentukan komponen +terbaik untuk merespons intent. Misalnya, +{@link android.content.pm.PackageManager#queryIntentActivities +queryIntentActivities()} akan mengembalikan daftar semua aktivitas yang bisa melakukan +intent yang diteruskan sebagai argumen, dan {@link +android.content.pm.PackageManager#queryIntentServices +queryIntentServices()} akan mengembalikan daftar layanan serupa. +Tidak ada metode yang akan mengaktifkan komponen; mereka hanya mencantumkan komponen yang +bisa merespons. Ada metode serupa, +{@link android.content.pm.PackageManager#queryBroadcastReceivers +queryBroadcastReceivers()}, untuk penerima siaran. +

+ + + + diff --git a/docs/html-intl/intl/in/guide/components/loaders.jd b/docs/html-intl/intl/in/guide/components/loaders.jd new file mode 100644 index 0000000000000000000000000000000000000000..cd0379bf1cd99b5fc97325fad1e8749f07f991d5 --- /dev/null +++ b/docs/html-intl/intl/in/guide/components/loaders.jd @@ -0,0 +1,494 @@ +page.title=Aktivitas +parent.title=Loader +parent.link=activities.html +@jd:body +
+
+

Dalam dokumen ini

+
    +
  1. Rangkuman Loader API
  2. +
  3. Menggunakan Loader dalam Aplikasi +
      +
    1. +
    2. Memulai Loader
    3. +
    4. Me-restart Loader
    5. +
    6. Menggunakan Callback LoaderManager
    7. +
    +
  4. +
  5. Contoh +
      +
    1. Contoh Selengkapnya
    2. +
    +
  6. +
+ +

Kelas-kelas utama

+
    +
  1. {@link android.app.LoaderManager}
  2. +
  3. {@link android.content.Loader}
  4. + +
+ +

Contoh-contoh terkait

+
    +
  1. +LoaderCursor
  2. +
  3. +LoaderThrottle
  4. +
+
+
+ +

Diperkenalkan di Android 3.0, loader memudahkan pemuatan data asinkron +dalam aktivitas atau fragmen. Loader memiliki karakteristik ini:

+ + +

Rangkuman Loader API

+ +

Ada beberapa kelas dan antarmuka yang mungkin dilibatkan dalam menggunakan +loader pada aplikasi. Semuanya dirangkum dalam tabel ini:

+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Kelas/AntarmukaKeterangan
{@link android.app.LoaderManager}Kelas abstrak yang dikaitkan dengan {@link android.app.Activity} atau +{@link android.app.Fragment} untuk mengelola satu atau beberapa instance {@link +android.content.Loader}. Ini membantu aplikasi mengelola +operasi berjalan lebih lama bersamaan dengan daur hidup {@link android.app.Activity} +atau {@link android.app.Fragment}; penggunaan paling umumnya adalah dengan +{@link android.content.CursorLoader}, akan tetapi aplikasi bebas menulis loader-nya + sendiri untuk memuat tipe data lainnya. +
+
+ Hanya ada satu {@link android.app.LoaderManager} per aktivitas atau fragmen. Namun {@link android.app.LoaderManager} bisa memiliki +beberapa loader.
{@link android.app.LoaderManager.LoaderCallbacks}Antarmuka callback untuk klien berinteraksi dengan {@link +android.app.LoaderManager}. Misalnya, Anda menggunakan metode callback {@link +android.app.LoaderManager.LoaderCallbacks#onCreateLoader onCreateLoader()} +untuk membuat loader baru.
{@link android.content.Loader}Kelas abstrak yang melakukan pemuatan data asinkron. Ini +adalah kelas dasar untuk loader. Biasanya Anda akan menggunakan {@link +android.content.CursorLoader}, namun Anda bisa menerapkan subkelas sendiri. Selagi +loader aktif, loader harus memantau sumber datanya dan memberikan hasil +baru bila konten berubah.
{@link android.content.AsyncTaskLoader}Loader abstrak yang menyediakan {@link android.os.AsyncTask} untuk melakukan pekerjaan.
{@link android.content.CursorLoader}Subkelas {@link android.content.AsyncTaskLoader} yang meng-query +{@link android.content.ContentResolver} dan mengembalikan {@link +android.database.Cursor}. Kelas ini mengimplementasikan protokol {@link +android.content.Loader} dengan cara standar untuk query kursor, +yang dibuat berdasarkan {@link android.content.AsyncTaskLoader} untuk melakukan query kursor +pada thread latar belakang agar tidak memblokir UI aplikasi. Menggunakan loader +ini merupakan cara terbaik untuk memuat data secara asinkron dari {@link +android.content.ContentProvider}, sebagai ganti melakukan query terkelola melalui +fragmen atau API aktivitas.
+ +

Kelas dan antarmuka dalam tabel di atas merupakan komponen +esensial yang akan Anda gunakan untuk mengimplementasikan loader dalam aplikasi Anda. Anda tidak memerlukan semuanya +untuk setiap loader yang dibuat, namun Anda akan selalu memerlukan acuan ke {@link +android.app.LoaderManager} untuk memulai loader dan implementasi +kelas {@link android.content.Loader} seperti {@link +android.content.CursorLoader}. Bagian berikut ini menunjukkan kepada Anda cara menggunakan +kelas dan antarmuka ini dalam aplikasi.

+ +

Menggunakan Loader dalam Aplikasi

+

Bagian ini menjelaskan cara menggunakan loader dalam aplikasi Android. Aplikasi +yang menggunakan loader biasanya berisi yang berikut ini:

+ +

Memulai Loader

+ +

{@link android.app.LoaderManager} mengelola satu atau beberapa instance {@link +android.content.Loader} dalam {@link android.app.Activity} atau +{@link android.app.Fragment}. Hanya ada satu {@link +android.app.LoaderManager} per aktivitas atau fragmen.

+ +

Anda biasanya +memulai {@link android.content.Loader} dalam metode {@link +android.app.Activity#onCreate onCreate()} aktivitas, atau dalam metode +{@link android.app.Fragment#onActivityCreated onActivityCreated()} fragmen. Anda +melakukannya dengan cara berikut ini:

+ +
// Prepare the loader.  Either re-connect with an existing one,
+// or start a new one.
+getLoaderManager().initLoader(0, null, this);
+ +

Metode {@link android.app.LoaderManager#initLoader initLoader()} mengambil +parameter berikut:

+ +

Panggilan {@link android.app.LoaderManager#initLoader initLoader()} memastikan bahwa loader +telah dimulai dan aktif. Ia memiliki dua kemungkinan hasil:

+ +

Dalam hal ini, implementasi {@link android.app.LoaderManager.LoaderCallbacks} +yang ditentukan akan dikaitkan dengan loader, dan akan dipanggil bila +status loader berubah. Jika saat panggilan ini status pemanggil sudah +dimulai, dan loader yang diminta sudah ada dan telah menghasilkan +datanya, maka sistem segera memanggil {@link +android.app.LoaderManager.LoaderCallbacks#onLoadFinished onLoadFinished()} +(selama {@link android.app.LoaderManager#initLoader initLoader()}), +sehingga Anda harus siap bila hal ini terjadi. Lihat +onLoadFinished untuk diskusi selengkapnya mengenai callback ini

+ +

Perhatikan bahwa metode {@link android.app.LoaderManager#initLoader initLoader()} +mengembalikan {@link android.content.Loader} yang dibuat, namun Anda tidak +perlu menangkap acuan ke sana. {@link android.app.LoaderManager} mengelola +masa hidup loader secara otomatis. {@link android.app.LoaderManager} +memulai dan menghentikan pemuatan jika perlu, dan menjaga status loader +dan konten terkaitnya. Seperti yang tersirat di sini, Anda akan jarang berinteraksi dengan loader +secara langsung (meskipun misalnya menggunakan metode loader untuk menyempurnakan perilaku +loader, lihat contoh LoaderThrottle). +Anda paling sering akan menggunakan metode {@link +android.app.LoaderManager.LoaderCallbacks} untuk mengintervensi proses +pemuatan saat terjadi kejadian tertentu. Untuk diskusi selengkapnya mengenai topik ini, lihat Menggunakan Callback LoaderManager.

+ +

Me-restart Loader

+ +

Bila Anda menggunakan {@link android.app.LoaderManager#initLoader initLoader()}, seperti +ditampilkan di atas, loader yang ada akan digunakan dengan ID yang ditetapkan jika ada. +Jika tidak ada, ID akan dibuat. Namun kadang-kadang Anda perlu membuang data lama +dan mulai dari awal.

+ +

Untuk membuang data lama, gunakan {@link +android.app.LoaderManager#restartLoader restartLoader()}. Misalnya, implementasi +{@link android.widget.SearchView.OnQueryTextListener} ini akan me-restart +bila query pengguna berubah. Loader perlu di-restart +agar dapat menggunakan filter pencarian yang telah direvisi untuk melakukan query baru:

+ +
+public boolean onQueryTextChanged(String newText) {
+    // Called when the action bar search text has changed.  Update
+    // the search filter, and restart the loader to do a new query
+    // with this filter.
+    mCurFilter = !TextUtils.isEmpty(newText) ? newText : null;
+    getLoaderManager().restartLoader(0, null, this);
+    return true;
+}
+ +

Menggunakan Callback LoaderManager

+ +

{@link android.app.LoaderManager.LoaderCallbacks} adalah antarmuka callback +yang memungkinkan klien berinteraksi dengan {@link android.app.LoaderManager}.

+

Loader, khususnya {@link android.content.CursorLoader}, diharapkan +mempertahankan datanya setelah dihentikan. Ini memungkinkan aplikasi mempertahankan +datanya di aktivitas atau metode {@link android.app.Activity#onStop +onStop()} fragmen dan {@link android.app.Activity#onStart onStart()}, sehingga +bila pengguna kembali ke aplikasi, mereka tidak harus menunggu data +dimuat kembali. Anda menggunakan metode {@link android.app.LoaderManager.LoaderCallbacks} +untuk mengetahui waktu membuat loader baru, dan memberi tahu aplikasi kapan +berhenti menggunakan data loader.

+ +

{@link android.app.LoaderManager.LoaderCallbacks} berisi metode +ini:

+ + + +

Metode ini dijelaskan lebih detail dalam bagian berikutnya.

+ +

onCreateLoader

+ +

Saat Anda mencoba mengakses loader (misalnya, melalui {@link +android.app.LoaderManager#initLoader initLoader()}), ia akan memeriksa untuk mengetahui adanya +loader yang ditetapkan oleh ID. Jika tidak ada, ia akan memicu metode {@link +android.app.LoaderManager.LoaderCallbacks} {@link +android.app.LoaderManager.LoaderCallbacks#onCreateLoader onCreateLoader()}. Di +sinilah Anda membuat loader baru. Biasanya ini adalah {@link +android.content.CursorLoader}, namun Anda bisa mengimplementasikan sendiri subkelas {@link +android.content.Loader}.

+ +

Dalam contoh ini, metode callback {@link +android.app.LoaderManager.LoaderCallbacks#onCreateLoader onCreateLoader()} + akan membuat {@link android.content.CursorLoader}. Anda harus membuat +{@link android.content.CursorLoader} menggunakan metode konstruktornya, yang +memerlukan set informasi lengkap untuk melakukan query ke {@link +android.content.ContentProvider}. Secara khusus, ia memerlukan:

+ +

Misalnya:

+
+ // If non-null, this is the current filter the user has provided.
+String mCurFilter;
+...
+public Loader<Cursor> onCreateLoader(int id, Bundle args) {
+    // This is called when a new Loader needs to be created.  This
+    // sample only has one Loader, so we don't care about the ID.
+    // First, pick the base URI to use depending on whether we are
+    // currently filtering.
+    Uri baseUri;
+    if (mCurFilter != null) {
+        baseUri = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI,
+                  Uri.encode(mCurFilter));
+    } else {
+        baseUri = Contacts.CONTENT_URI;
+    }
+
+    // Now create and return a CursorLoader that will take care of
+    // creating a Cursor for the data being displayed.
+    String select = "((" + Contacts.DISPLAY_NAME + " NOTNULL) AND ("
+            + Contacts.HAS_PHONE_NUMBER + "=1) AND ("
+            + Contacts.DISPLAY_NAME + " != '' ))";
+    return new CursorLoader(getActivity(), baseUri,
+            CONTACTS_SUMMARY_PROJECTION, select, null,
+            Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC");
+}
+

onLoadFinished

+ +

Metode ini dipanggil bila loader yang dibuat sebelumnya selesai dimuat. +Metode ini dijamin dipanggil sebelum pelepasan data terakhir +yang disediakan untuk loader ini. Di titik ini Anda harus menyingkirkan semua penggunaan +data lama (karena akan segera dilepas), namun jangan melepas sendiri +data tersebut karena loader memilikinya dan akan menanganinya.

+ + +

Loader akan melepas data setelah mengetahui bahwa aplikasi tidak +lagi menggunakannya. Misalnya, jika data adalah kursor dari {@link +android.content.CursorLoader}, Anda tidak boleh memanggil {@link +android.database.Cursor#close close()} sendiri. Jika kursor ditempatkan +dalam {@link android.widget.CursorAdapter}, Anda harus menggunakan metode {@link +android.widget.SimpleCursorAdapter#swapCursor swapCursor()} agar +{@link android.database.Cursor} lama tidak ditutup. Misalnya:

+ +
+// This is the Adapter being used to display the list's data.
SimpleCursorAdapter mAdapter; +... + +public void onLoadFinished(Loader<Cursor> loader, Cursor data) { + // Swap the new cursor in.  (The framework will take care of closing the + // old cursor once we return.) + mAdapter.swapCursor(data); +}
+ +

onLoaderReset

+ +

Metode ini dipanggil bila loader yang dibuat sebelumnya sedang di-reset, sehingga datanya +tidak tersedia. Callback ini memungkinkan Anda mengetahui +kapan data akan dilepas sehingga dapat menghapus acuannya ke callback.  

+

Implementasi ini memanggil +{@link android.widget.SimpleCursorAdapter#swapCursor swapCursor()} +dengan nilai null:

+ +
+// This is the Adapter being used to display the list's data.
+SimpleCursorAdapter mAdapter;
+...
+
+public void onLoaderReset(Loader<Cursor> loader) {
+    // This is called when the last Cursor provided to onLoadFinished()
+    // above is about to be closed.  We need to make sure we are no
+    // longer using it.
+    mAdapter.swapCursor(null);
+}
+ + +

Contoh

+ +

Sebagai contoh, berikut ini adalah implementasi penuh {@link +android.app.Fragment} yang menampilkan {@link android.widget.ListView} berisi +hasil query terhadap penyedia konten kontak. Ia menggunakan {@link +android.content.CursorLoader} untuk mengelola query pada penyedia.

+ +

Agar aplikasi dapat mengakses kontak pengguna, seperti yang ditampilkan dalam contoh ini, +manifesnya harus menyertakan izin +{@link android.Manifest.permission#READ_CONTACTS READ_CONTACTS}.

+ +
+public static class CursorLoaderListFragment extends ListFragment
+        implements OnQueryTextListener, LoaderManager.LoaderCallbacks<Cursor> {
+
+    // This is the Adapter being used to display the list's data.
+    SimpleCursorAdapter mAdapter;
+
+    // If non-null, this is the current filter the user has provided.
+    String mCurFilter;
+
+    @Override public void onActivityCreated(Bundle savedInstanceState) {
+        super.onActivityCreated(savedInstanceState);
+
+        // Give some text to display if there is no data.  In a real
+        // application this would come from a resource.
+        setEmptyText("No phone numbers");
+
+        // We have a menu item to show in action bar.
+        setHasOptionsMenu(true);
+
+        // Create an empty adapter we will use to display the loaded data.
+        mAdapter = new SimpleCursorAdapter(getActivity(),
+                android.R.layout.simple_list_item_2, null,
+                new String[] { Contacts.DISPLAY_NAME, Contacts.CONTACT_STATUS },
+                new int[] { android.R.id.text1, android.R.id.text2 }, 0);
+        setListAdapter(mAdapter);
+
+        // Prepare the loader.  Either re-connect with an existing one,
+        // or start a new one.
+        getLoaderManager().initLoader(0, null, this);
+    }
+
+    @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+        // Place an action bar item for searching.
+        MenuItem item = menu.add("Search");
+        item.setIcon(android.R.drawable.ic_menu_search);
+        item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
+        SearchView sv = new SearchView(getActivity());
+        sv.setOnQueryTextListener(this);
+        item.setActionView(sv);
+    }
+
+    public boolean onQueryTextChange(String newText) {
+        // Called when the action bar search text has changed.  Update
+        // the search filter, and restart the loader to do a new query
+        // with this filter.
+        mCurFilter = !TextUtils.isEmpty(newText) ? newText : null;
+        getLoaderManager().restartLoader(0, null, this);
+        return true;
+    }
+
+    @Override public boolean onQueryTextSubmit(String query) {
+        // Don't care about this.
+        return true;
+    }
+
+    @Override public void onListItemClick(ListView l, View v, int position, long id) {
+        // Insert desired behavior here.
+        Log.i("FragmentComplexList", "Item clicked: " + id);
+    }
+
+    // These are the Contacts rows that we will retrieve.
+    static final String[] CONTACTS_SUMMARY_PROJECTION = new String[] {
+        Contacts._ID,
+        Contacts.DISPLAY_NAME,
+        Contacts.CONTACT_STATUS,
+        Contacts.CONTACT_PRESENCE,
+        Contacts.PHOTO_ID,
+        Contacts.LOOKUP_KEY,
+    };
+    public Loader<Cursor> onCreateLoader(int id, Bundle args) {
+        // This is called when a new Loader needs to be created.  This
+        // sample only has one Loader, so we don't care about the ID.
+        // First, pick the base URI to use depending on whether we are
+        // currently filtering.
+        Uri baseUri;
+        if (mCurFilter != null) {
+            baseUri = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI,
+                    Uri.encode(mCurFilter));
+        } else {
+            baseUri = Contacts.CONTENT_URI;
+        }
+
+        // Now create and return a CursorLoader that will take care of
+        // creating a Cursor for the data being displayed.
+        String select = "((" + Contacts.DISPLAY_NAME + " NOTNULL) AND ("
+                + Contacts.HAS_PHONE_NUMBER + "=1) AND ("
+                + Contacts.DISPLAY_NAME + " != '' ))";
+        return new CursorLoader(getActivity(), baseUri,
+                CONTACTS_SUMMARY_PROJECTION, select, null,
+                Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC");
+    }
+
+    public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
+        // Swap the new cursor in.  (The framework will take care of closing the
+        // old cursor once we return.)
+        mAdapter.swapCursor(data);
+    }
+
+    public void onLoaderReset(Loader<Cursor> loader) {
+        // This is called when the last Cursor provided to onLoadFinished()
+        // above is about to be closed.  We need to make sure we are no
+        // longer using it.
+        mAdapter.swapCursor(null);
+    }
+}
+

Contoh Selengkapnya

+ +

Ada beberapa contoh berbeda dalam ApiDemos yang +mengilustrasikan cara menggunakan loader:

+ + +

Untuk informasi tentang mengunduh dan menginstal contoh SDK, lihat Mendapatkan +Contoh.

+ diff --git a/docs/html-intl/intl/in/guide/components/processes-and-threads.jd b/docs/html-intl/intl/in/guide/components/processes-and-threads.jd new file mode 100644 index 0000000000000000000000000000000000000000..44051bf492e0acf8eba217fc29d482a48cf90a73 --- /dev/null +++ b/docs/html-intl/intl/in/guide/components/processes-and-threads.jd @@ -0,0 +1,411 @@ +page.title=Proses dan Thread +page.tags=daur hidup,latar belakang + +@jd:body + +
+
+ +

Dalam dokumen ini

+
    +
  1. Proses +
      +
    1. Daur hidup proses
    2. +
    +
  2. +
  3. Thread +
      +
    1. Thread pekerja
    2. +
    3. Metode thread-safe
    4. +
    +
  4. +
  5. Komunikasi antarproses
  6. +
+ +
+
+ +

Bila komponen aplikasi dimulai dan tidak ada komponen aplikasi lain yang +berjalan, sistem Android akan memulai proses Linux baru untuk aplikasi dengan satu thread +eksekusi. Secara default, semua komponen aplikasi yang sama berjalan dalam proses dan +thread yang sama (disebut thread "utama"). Jika komponen aplikasi dimulai dan sudah ada +proses untuk aplikasi itu (karena komponen lain dari aplikasi itu sudah ada), maka komponen +akan dimulai dalam proses itu dan menggunakan thread eksekusi yang sama. Akan tetapi, Anda bisa +mengatur komponen berbeda di aplikasi agar berjalan di proses terpisah, dan Anda bisa membuat thread tambahan untuk +setiap proses.

+ +

Dokumen ini membahas cara kerja proses dan thread di aplikasi Android.

+ + +

Proses

+ +

Secara default, semua komponen aplikasi yang sama berjalan dalam proses yang sama dan kebanyakan +aplikasi tidak boleh mengubah ini. Akan tetapi, jika Anda merasa perlu mengontrol proses milik +komponen tertentu, Anda dapat melakukannya dalam file manifes.

+ +

Entri manifes untuk setiap tipe elemen komponen—{@code +<activity>}, {@code +<service>}, {@code +<receiver>}, dan {@code +<provider>}—mendukung atribut {@code android:process} yang bisa menetapkan +dalam proses mana komponen harus dijalankan. Anda bisa mengatur atribut ini agar setiap komponen +berjalan dalam prosesnya sendiri atau agar beberapa komponen menggunakan proses yang sama sementara yang lainnya tidak. Anda juga bisa mengatur +{@code android:process} agar komponen aplikasi yang berbeda berjalan dalam proses yang sama +—sepanjang aplikasi menggunakan ID Linux yang sama dan ditandatangani +dengan sertifikat yang sama.

+ +

Elemen {@code +<application>} juga mendukung atribut {@code android:process}, untuk mengatur +nilai default yang berlaku bagi semua komponen.

+ +

Android bisa memutuskan untuk mematikan proses pada waktu tertentu, bila memori tinggal sedikit dan diperlukan oleh +proses lain yang lebih mendesak untuk melayani pengguna. Komponen +aplikasi yang berjalan dalam proses yang dimatikan maka sebagai konsekuensinya juga akan dimusnahkan. Proses dimulai +kembali untuk komponen itu bila ada lagi pekerjaan untuk mereka lakukan.

+ +

Saat memutuskan proses yang akan dimatikan, sistem Android akan mempertimbangkan kepentingan relatifnya bagi +pengguna. Misalnya, sistem lebih mudah menghentikan proses yang menjadi host aktivitas yang tidak + lagi terlihat di layar, dibandingkan dengan proses yang menjadi host aktivitas yang terlihat. Karena itu, keputusan +untuk menghentikan proses bergantung pada keadaan komponen yang berjalan dalam proses tersebut. Aturan +yang digunakan untuk menentukan proses yang akan dihentikan dibahas di bawah ini.

+ + +

Daur hidup proses

+ +

Sistem Android mencoba mempertahankan proses aplikasi selama mungkin, namun +pada akhirnya perlu menghapus proses lama untuk mengambil kembali memori bagi proses baru atau yang lebih penting. Untuk +menentukan proses yang akan +dipertahankan dan yang harus dimatikan, sistem menempatkan setiap proses ke dalam "hierarki prioritas" berdasarkan +komponen yang berjalan dalam proses dan status komponen tersebut. Proses yang memiliki +prioritas terendah akan dimatikan terlebih dahulu, kemudian yang terendah berikutnya, dan seterusnya, jika perlu +untuk memulihkan sumber daya sistem.

+ +

Ada lima tingkatan dalam hierarki prioritas. Daftar berikut berisi beberapa +tipe proses berdasarkan urutan prioritas (proses pertama adalah yang terpenting dan +dimatikan terakhir):

+ +
    +
  1. Proses latar depan +

    Proses yang diperlukan untuk aktivitas yang sedang dilakukan pengguna. Proses +dianggap berada di latar depan jika salah satu kondisi berikut terpenuhi:

    + + + +

    Secara umum, hanya ada beberapa proses latar depan pada waktu yang diberikan. Proses dimatikan hanya sebagai +upaya terakhir— jika memori hampir habis sehingga semuanya tidak bisa terus berjalan. Pada umumnya, pada +titik itu, perangkat dalam keadaan memory paging, sehingga menghentikan beberapa proses latar depan +diperlukan agar antarmuka pengguna tetap responsif.

  2. + +
  3. Proses yang terlihat +

    Proses yang tidak memiliki komponen latar depan, namun masih bisa +memengaruhi apa yang dilihat pengguna di layar. Proses dianggap terlihat jika salah satu kondisi +berikut terpenuhi:

    + + + +

    Proses yang terlihat dianggap sangat penting dan tidak akan dimatikan kecuali jika hal itu +diperlukan agar semua proses latar depan tetap berjalan.

    +
  4. + +
  5. Proses layanan +

    Proses yang menjalankan layanan yang telah dimulai dengan metode {@link +android.content.Context#startService startService()} dan tidak termasuk dalam salah satu dari dua kategori +yang lebih tinggi. Walaupun proses pelayanan tidak langsung terkait dengan semua yang dilihat oleh pengguna, proses ini +umumnya melakukan hal-hal yang dipedulikan pengguna (seperti memutar musik di latar belakang +atau mengunduh data di jaringan), jadi sistem membuat proses tetap berjalan kecuali memori tidak cukup untuk +mempertahankannya bersama semua proses latar depan dan proses yang terlihat.

    +
  6. + +
  7. Proses latar belakang +

    Proses yang menampung aktivitas yang saat ini tidak terlihat oleh pengguna (metode +{@link android.app.Activity#onStop onStop()} aktivitas telah dipanggil). Proses ini tidak memiliki dampak +langsung pada pengalaman pengguna, dan sistem bisa menghentikannya kapan saja untuk memperoleh kembali memori bagi +proses latar depan, proses yang terlihat, +atau proses layanan. Biasanya ada banyak proses latar belakang yang berjalan, sehingga disimpan +dalam daftar LRU (least recently used atau paling sedikit digunakan) untuk memastikan bahwa proses dengan aktivitas yang paling baru +terlihat oleh pengguna sebagai yang terakhir untuk dimatikan. Jika aktivitas mengimplementasikan metode + daur hidupnya dengan benar, dan menyimpan statusnya saat ini, menghentikan prosesnya tidak akan memiliki efek +yang terlihat pada pengalaman pengguna, karena ketika pengguna kembali ke aktivitas, aktivitas itu memulihkan +semua statusnya yang terlihat. Lihat dokumen Aktivitas + untuk mendapatkan informasi tentang menyimpan dan memulihkan status.

    +
  8. + +
  9. Proses kosong +

    Sebuah proses yang tidak berisi komponen aplikasi aktif apa pun. Alasan satu-satunya mempertahankan proses +seperti ini tetap hidup adalah untuk keperluan caching, meningkatkan waktu mulai (startup) bila +nanti komponen perlu dijalankan di dalamnya. Sistem sering menghentikan proses ini untuk menyeimbangkan sumber +daya sistem secara keseluruhan antara proses cache dan cache kernel yang mendasarinya.

    +
  10. +
+ + +

Android sebisa mungkin memeringkat proses setinggi +mungkin, berdasarkan prioritas komponen yang sedang aktif dalam proses. Misalnya, jika suatu proses menjadi host sebuah layanan dan +aktivitas yang terlihat, proses akan diperingkat sebagai proses yang terlihat, bukan sebagai proses layanan.

+ +

Selain itu, peringkat proses dapat meningkat karena adanya proses lain yang bergantung padanya +—proses yang melayani proses lain tidak bisa diperingkat lebih rendah daripada proses yang +sedang dilayaninya. Misalnya, jika penyedia konten dalam proses A melayani klien dalam proses B, atau +jika layanan dalam proses A terikat dengan komponen dalam proses B, proses A selalu dipertimbangkan sebagai paling rendah +prioritasnya dibandingkan dengan proses B.

+ +

Karena proses yang menjalankan layanan diperingkat lebih tinggi daripada aktivitas latar belakang, +aktivitas yang memulai operasi yang berjalan lama mungkin lebih baik memulai layanan untuk operasi itu, daripada hanya +membuat thread pekerja—khususnya jika operasi mungkin akan berlangsung lebih lama daripada aktivitas. + Misalnya, aktivitas yang mengunggah gambar ke situs web harus memulai layanan +untuk mengunggah sehingga unggahan bisa terus berjalan di latar belakang meskipun pengguna meninggalkan aktivitas tersebut. +Menggunakan layanan akan memastikan operasi paling tidak memiliki prioritas "proses layanan", +apa pun yang terjadi pada aktivitas. Ini menjadi alasan yang sama yang membuat penerima siaran harus +menjalankan layanan daripada hanya menempatkan operasi yang menghabiskan waktu di thread.

+ + + + +

Thread

+ +

Bila aplikasi diluncurkan, sistem akan membuat thread eksekusi untuk aplikasi tersebut, yang diberi nama, +"main". Thread ini sangat penting karena bertugas mengirim kejadian ke widget +antarmuka pengguna yang sesuai, termasuk kejadian menggambar. Ini juga merupakan thread yang +membuat aplikasi berinteraksi dengan komponen dari Android UI toolkit (komponen dari paket {@link +android.widget} dan {@link android.view}). Karena itu, thread 'main' juga terkadang +disebut thread UI.

+ +

Sistem ini tidak membuat thread terpisah untuk setiap instance komponen. Semua +komponen yang berjalan di proses yang sama akan dibuat instance-nya dalam thread UI, dan sistem akan memanggil +setiap komponen yang dikirim dari thread itu. Akibatnya, metode yang merespons callback sistem + (seperti {@link android.view.View#onKeyDown onKeyDown()} untuk melaporkan tindakan pengguna atau metode callback daur hidup) + selalu berjalan di thread UI proses.

+ +

Misalnya saat pengguna menyentuh tombol pada layar, thread UI aplikasi akan mengirim kejadian +sentuh ke widget, yang selanjutnya menetapkan status ditekan dan mengirim permintaan yang tidak divalidasi ke +antrean kejadian. Thread UI akan menghapus antrean permintaan dan memberi tahu widget bahwa widget harus menggambar +dirinya sendiri.

+ +

Saat aplikasi melakukan pekerjaan intensif sebagai respons terhadap interaksi pengguna, model +thread tunggal ini bisa menghasilkan kinerja yang buruk kecuali jika Anda mengimplementasikan aplikasi dengan benar. Khususnya jika + semua terjadi di thread UI, melakukan operasi yang panjang seperti akses ke jaringan atau query +database akan memblokir seluruh UI. Bila thread diblokir, tidak ada kejadian yang bisa dikirim, +termasuk kejadian menggambar. Dari sudut pandang pengguna, aplikasi +tampak mogok (hang). Lebih buruk lagi, jika thread UI diblokir selama lebih dari beberapa detik +(saat ini sekitar 5 detik) pengguna akan ditampilkan dialog "aplikasi tidak +merespons" (ANR) yang populer karena reputasi buruknya. Pengguna nanti bisa memutuskan untuk keluar dari aplikasi dan menghapus aplikasi +jika mereka tidak suka.

+ +

Selain itu, toolkit Android UI bukan thread-safe. Jadi, Anda tidak harus memanipulasi +UI dari thread pekerja—Anda harus melakukan semua manipulasi pada antarmuka pengguna dari thread +UI. Sehingga hanya ada dua aturan untuk model thread tunggal Android:

+ +
    +
  1. Jangan memblokir thread UI +
  2. Jangan mengakses toolkit Android UI dari luar thread UI +
+ +

Thread pekerja

+ +

Karena model thread tunggal yang dijelaskan di atas, Anda dilarang memblokir thread +UI demi daya respons UI aplikasi. Jika memiliki operasi untuk dijalankan +yang tidak seketika, Anda harus memastikan untuk melakukannya di thread terpisah (thread "latar belakang" atau +thread "pekerja").

+ +

Misalnya, berikut ini beberapa kode untuk listener klik yang mengunduh gambar dari +thread terpisah dan menampilkannya dalam {@link android.widget.ImageView}:

+ +
+public void onClick(View v) {
+    new Thread(new Runnable() {
+        public void run() {
+            Bitmap b = loadImageFromNetwork("http://example.com/image.png");
+            mImageView.setImageBitmap(b);
+        }
+    }).start();
+}
+
+ +

Awalnya hal ini tampak bekerja dengan baik, karena menciptakan thread baru untuk menangani +operasi jaringan. Akan tetapi, hal tersebut melanggar aturan kedua model thread tunggal: jangan mengakses + toolkit Android UI dari luar thread UI—sampel ini memodifikasi {@link +android.widget.ImageView} dari thread pekerja sebagai ganti thread UI. Ini bisa +mengakibatkan perilaku yang tidak terdefinisi dan tidak diharapkan, yang bisa menyulitkan dan menghabiskan waktu untuk melacaknya.

+ +

Untuk memperbaiki masalah ini, Android menawarkan beberapa cara untuk mengakses thread UI dari +thread lainnya. Berikut ini daftar metode yang bisa membantu:

+ + + +

Misalnya, Anda bisa memperbaiki kode di atas dengan menggunakan metode {@link +android.view.View#post(java.lang.Runnable) View.post(Runnable)}:

+ +
+public void onClick(View v) {
+    new Thread(new Runnable() {
+        public void run() {
+            final Bitmap bitmap = loadImageFromNetwork("http://example.com/image.png");
+            mImageView.post(new Runnable() {
+                public void run() {
+                    mImageView.setImageBitmap(bitmap);
+                }
+            });
+        }
+    }).start();
+}
+
+ +

Kini implementasi ini thread-safe: operasi jaringan dilakukan terpisah dari thread + sementara {@link android.widget.ImageView} dimanipulasi dari thread UI.

+ +

Akan tetapi, karena operasi semakin kompleks, jenis kode seperti ini bisa semakin rumit +dan sulit dipertahankan. Untuk menangani interaksi yang lebih kompleks dengan thread pekerja, Anda bisa mempertimbangkan + penggunaan {@link android.os.Handler}di thread pekerja, untuk memproses pesan yang dikirim dari + thread UI. Mungkin solusi terbaiknya adalah memperpanjang kelas {@link android.os.AsyncTask}, +yang akan menyederhanakan eksekusi tugas-tugas thread pekerja yang perlu berinteraksi dengan UI.

+ + +

Menggunakan AsyncTask

+ +

Dengan {@link android.os.AsyncTask}, Anda bisa melakukan pekerjaan asinkron pada antarmuka +pengguna. AsyncTask memblokir operasi di thread pekerja kemudian mempublikasikan hasilnya +di thread UI, tanpa mengharuskan Anda untuk menangani sendiri thread dan/atau handler sendiri.

+ +

Untuk menggunakannya, Anda harus menempatkan {@link android.os.AsyncTask} sebagai subkelas dan mengimplementasikan metode callback {@link +android.os.AsyncTask#doInBackground doInBackground()} yang berjalan di kumpulan +thread latar belakang. Untuk memperbarui UI, Anda harus mengimplementasikan {@link +android.os.AsyncTask#onPostExecute onPostExecute()}, yang memberikan hasil dari {@link +android.os.AsyncTask#doInBackground doInBackground()} dan berjalan di thread UI, jadi Anda bisa +memperbarui UI dengan aman. Selanjutnya Anda bisa menjalankan tugas dengan memanggil {@link android.os.AsyncTask#execute execute()} +dari thread UI.

+ +

Misalnya, Anda bisa mengimplementasikan contoh sebelumnya menggunakan {@link android.os.AsyncTask} dengan cara +ini:

+ +
+public void onClick(View v) {
+    new DownloadImageTask().execute("http://example.com/image.png");
+}
+
+private class DownloadImageTask extends AsyncTask<String, Void, Bitmap> {
+    /** The system calls this to perform work in a worker thread and
+      * delivers it the parameters given to AsyncTask.execute() */
+    protected Bitmap doInBackground(String... urls) {
+        return loadImageFromNetwork(urls[0]);
+    }
+    
+    /** The system calls this to perform work in the UI thread and delivers
+      * the result from doInBackground() */
+    protected void onPostExecute(Bitmap result) {
+        mImageView.setImageBitmap(result);
+    }
+}
+
+ +

Kini UI aman dan kode jadi lebih sederhana, karena memisahkan pekerjaan ke +dalam bagian-bagian yang harus dilakukan pada thread pekerja dan thread UI.

+ +

Anda harus membaca acuan {@link android.os.AsyncTask} untuk memahami sepenuhnya +cara menggunakan kelas ini, namun berikut ini ikhtisar singkat cara kerjanya:

+ + + +

Perhatian: Masalah lain yang mungkin Anda temui saat menggunakan +thread pekerja adalah restart tak terduga dalam aktivitas karena perubahan konfigurasi runtime + (seperti saat pengguna mengubah orientasi layar), yang bisa memusnahkan thread pekerja. Untuk +melihat cara mempertahankan tugas selama restart ini dan cara membatalkan +tugas dengan benar saat aktivitas dimusnahkan, lihat kode sumber untuk aplikasi sampel Shelves.

+ + +

Metode thread-safe

+ +

Dalam beberapa situasi, metode yang Anda implementasikan bisa dipanggil dari lebih dari satu thread, +dan karena itu harus ditulis agar menjadi thread-safe.

+ +

Ini terutama terjadi untuk metode yang bisa dipanggil dari jauh —seperti metode dalam layanan terikat. Bila sebuah panggilan pada +metode yang dijalankan dalam {@link android.os.IBinder} berasal dari proses yang sama di mana +{@link android.os.IBinder IBinder} berjalan, metode ini akan dieksekusi di thread pemanggil. +Akan tetapi, bila panggilan berasal proses lain, metode akan dieksekusi dalam thread yang dipilih dari + kumpulan (pool) thread yang dipertahankan sistem dalam proses yang sama seperti{@link android.os.IBinder +IBinder} (tidak dieksekusi dalam thread UI proses). Misalnya, karena metode +{@link android.app.Service#onBind onBind()} layanan akan dipanggil dari thread UI +proses layanan, metode yang diimplementasikan dalam objek yang dikembalikan {@link android.app.Service#onBind +onBind()} (misalnya, subkelas yang mengimplementasikan metode RPC) akan dipanggil dari thread +di pool. Karena layanan bisa memiliki lebih dari satu klien, maka lebih dari satu pool thread bisa melibatkan + metode {@link android.os.IBinder IBinder} yang sama sekaligus. Metode {@link android.os.IBinder +IBinder} karenanya harus diimplementasikan sebagai thread-safe.

+ +

Penyedia konten juga bisa menerima permintaan data yang berasal dalam proses lain. +Meskipun kelas {@link android.content.ContentResolver} dan {@link android.content.ContentProvider} + menyembunyikan detail cara komunikasi antarproses dikelola, metode {@link +android.content.ContentProvider} yang merespons permintaan itu—metode {@link +android.content.ContentProvider#query query()}, {@link android.content.ContentProvider#insert +insert()}, {@link android.content.ContentProvider#delete delete()}, {@link +android.content.ContentProvider#update update()}, dan {@link android.content.ContentProvider#getType +getType()}— dipanggil dari pool thread pada proses penyedia konten, bukan thread UI +untuk proses tersebut. Mengingat metode ini bisa dipanggil dari thread mana pun +sekaligus, metode-metode ini juga harus diimplementasikan sebagai thread-safe.

+ + +

Komunikasi Antarproses

+ +

Android menawarkan mekanisme komunikasi antarproses (IPC) menggunakan panggilan prosedur jauh + (RPC), yang mana metode ini dipanggil oleh aktivitas atau komponen aplikasi lain, namun dieksekusi dari +jauh (di proses lain), bersama hasil yang dikembalikan ke +pemanggil. Ini mengharuskan penguraian panggilan metode dan datanya ke tingkat yang bisa +dipahami sistem operasi, mentransmisikannya dari proses lokal dan ruang alamat untuk proses jauh +dan ruang proses, kemudian merakit kembali dan menetapkannya kembali di sana. Nilai-nilai yang dikembalikan +akan ditransmisikan dalam arah berlawanan. Android menyediakan semua kode untuk melakukan transaksi IPC + ini, sehingga Anda bisa fokus pada pendefinisian dan implementasi antarmuka pemrograman RPC.

+ +

Untuk melakukan IPC, aplikasi Anda harus diikat ke layanan, dengan menggunakan {@link +android.content.Context#bindService bindService()}. Untuk informasi selengkapnya, lihat panduan pengembang Layanan.

+ + + diff --git a/docs/html-intl/intl/in/guide/components/recents.jd b/docs/html-intl/intl/in/guide/components/recents.jd new file mode 100644 index 0000000000000000000000000000000000000000..dcfcda7a7d5f9d64e41b392a2eacf7607577f100 --- /dev/null +++ b/docs/html-intl/intl/in/guide/components/recents.jd @@ -0,0 +1,256 @@ +page.title=Layar Ikhtisar +page.tags="recents","overview" + +@jd:body + +
+
+ +

Dalam dokumen ini

+
    +
  1. Menambahkan Tugas ke Layar Ikhtisar +
      +
    1. Menggunakan flag Intent untuk menambahkan tugas
    2. +
    3. Menggunakan atribut Aktivitas untuk menambahkan tugas
    4. +
    +
  2. +
  3. Menghapus Tugas +
      +
    1. Menggunakan kelas AppTask untuk menghapus tugas
    2. +
    3. Mempertahankan tugas yang telah selesai
    4. +
    +
  4. +
+ +

Kelas-kelas utama

+
    +
  1. {@link android.app.ActivityManager.AppTask}
  2. +
  3. {@link android.content.Intent}
  4. +
+ +

Kode contoh

+
    +
  1. Aplikasi yang berorientasi dokumen
  2. +
+ +
+
+ +

Layar ikhtisar (juga disebut sebagai layar terbaru, daftar tugas terbaru, atau aplikasi terbaru) +UI tingkat sistem yang mencantumkan +aktivitas dan tugas yang baru saja diakses. Pengguna +bisa menyusuri daftar ini dan memilih satu tugas untuk dilanjutkan, atau pengguna bisa menghapus tugas dari +daftar dengan gerakan mengusap. Dengan dirilisnya Android 5.0 (API level 21), beberapa instance aktivitas yang +sama yang berisi dokumen berbeda dapat muncul sebagai tugas di layar ikhtisar. Misalnya, +Google Drive mungkin memiliki satu tugas untuk setiap beberapa dokumen Google. Setiap dokumen muncul sebagai +tugas dalam layar ikhtisar.

+ + +

Gambar 1. Layar ikhtisar menampilkan tiga dokumen +Google Drive, masing-masing dinyatakan sebagai tugas terpisah.

+ +

Biasanya Anda harus mengizinkan sistem mendefinisikan cara menyatakan tugas dan +aktivitas di layar ikhtisar, dan Anda tidak perlu memodifikasi perilaku ini. +Akan tetapi, aplikasi Anda dapat menentukan cara dan waktu munculnya aktivitas di layar ikhtisar. Kelas +{@link android.app.ActivityManager.AppTask} memungkinkan Anda mengelola tugas, dan flag + aktivitas kelas {@link android.content.Intent} memungkinkan Anda menentukan kapan aktivitas ditambahkan atau dihapus dari +layar ikhtisar. Selain itu, atribut +<activity> memungkinkan Anda menetapkan perilaku di manifes.

+ +

Menambahkan Tugas ke Layar Ikhtisar

+ +

Penggunaan flag kelas {@link android.content.Intent} untuk menambahkan tugas memberi kontrol lebih besar +atas waktu dan cara dokumen dibuka atau dibuka kembali di layar ikhtisar. Bila menggunakan atribut +<activity> +, Anda dapat memilih antara selalu membuka dokumen dalam tugas baru atau menggunakan kembali tugas +yang ada untuk dokumen tersebut.

+ +

Menggunakan flag Intent untuk menambahkan tugas

+ +

Bila membuat dokumen baru untuk aktivitas, Anda memanggil metode +{@link android.app.ActivityManager.AppTask#startActivity(android.content.Context, android.content.Intent, android.os.Bundle) startActivity()} + dari kelas {@link android.app.ActivityManager.AppTask}, dengan meneruskannya ke intent yang +menjalankan aktivitas tersebut. Untuk menyisipkan jeda logis agar sistem memperlakukan aktivitas Anda sebagai tugas +baru di layar ikhtisar, teruskan flag {@link android.content.Intent#FLAG_ACTIVITY_NEW_DOCUMENT} +dalam metode {@link android.content.Intent#addFlags(int) addFlags()} dari {@link android.content.Intent} +yang memulai aktivitas itu.

+ +

Catatan: Flag {@link android.content.Intent#FLAG_ACTIVITY_NEW_DOCUMENT} +menggantikan flag {@link android.content.Intent#FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET}, +yang tidak digunakan lagi pada Android 5.0 (API level 21).

+ +

Jika Anda menetapkan flag {@link android.content.Intent#FLAG_ACTIVITY_MULTIPLE_TASK} saat membuat +dokumen baru, sistem akan selalu membuat tugas baru dengan aktivitas target sebagai akar. +Dengan pengaturan ini, dokumen yang sama dapat dibuka di lebih dari satu tugas. Kode berikut memperagakan +cara aktivitas utama melakukannya:

+ +

+DocumentCentricActivity.java

+
+public void createNewDocument(View view) {
+      final Intent newDocumentIntent = newDocumentIntent();
+      if (useMultipleTasks) {
+          newDocumentIntent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
+      }
+      startActivity(newDocumentIntent);
+  }
+
+  private Intent newDocumentIntent() {
+      boolean useMultipleTasks = mCheckbox.isChecked();
+      final Intent newDocumentIntent = new Intent(this, NewDocumentActivity.class);
+      newDocumentIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
+      newDocumentIntent.putExtra(KEY_EXTRA_NEW_DOCUMENT_COUNTER, incrementAndGet());
+      return newDocumentIntent;
+  }
+
+  private static int incrementAndGet() {
+      Log.d(TAG, "incrementAndGet(): " + mDocumentCounter);
+      return mDocumentCounter++;
+  }
+}
+
+ +

Catatan: Aktivitas yang dimulai dengan flag {@code FLAG_ACTIVITY_NEW_DOCUMENT} + harus telah menetapkan nilai atribut {@code android:launchMode="standard"} (default) dalam +manifes.

+ +

Bila aktivitas utama memulai aktivitas baru, sistem akan mencari tugas yang intent +-nya cocok dengan nama komponen intent dalam tugas-tugas yang sudah ada dan mencari aktivitas dalam data Intent. Jika tugas +tidak ditemukan, atau intent ada dalam flag {@link android.content.Intent#FLAG_ACTIVITY_MULTIPLE_TASK} +, tugas baru akan dibuat dengan aktivitas tersebut sebagai akarnya. Jika ditemukan, sistem akan +mengedepankan tugas itu dan meneruskan intent baru ke {@link android.app.Activity#onNewIntent onNewIntent()}. +Aktivitas baru akan mendapatkan intent dan membuat dokumen baru di layar ikhtisar, seperti dalam +contoh berikut:

+ +

+NewDocumentActivity.java

+
+@Override
+protected void onCreate(Bundle savedInstanceState) {
+    super.onCreate(savedInstanceState);
+    setContentView(R.layout.activity_new_document);
+    mDocumentCount = getIntent()
+            .getIntExtra(DocumentCentricActivity.KEY_EXTRA_NEW_DOCUMENT_COUNTER, 0);
+    mDocumentCounterTextView = (TextView) findViewById(
+            R.id.hello_new_document_text_view);
+    setDocumentCounterText(R.string.hello_new_document_counter);
+}
+
+@Override
+protected void onNewIntent(Intent intent) {
+    super.onNewIntent(intent);
+    /* If FLAG_ACTIVITY_MULTIPLE_TASK has not been used, this activity
+    is reused to create a new document.
+     */
+    setDocumentCounterText(R.string.reusing_document_counter);
+}
+
+ + +

Menggunakan atribut Aktivitas untuk menambahkan tugas

+ +

Aktivitas juga dapat menetapkan dalam manifesnya agar selalu dimulai ke dalam tugas baru dengan menggunakan +atribut <activity> +, +{@code android:documentLaunchMode}. Atribut ini memiliki empat nilai yang menghasilkan efek berikut +bila pengguna membuka dokumen dengan aplikasi:

+ +
+
"{@code intoExisting}"
+
Aktivitas menggunakan kembali tugas yang ada untuk dokumen tersebut. Ini sama dengan mengatur flag + {@link android.content.Intent#FLAG_ACTIVITY_NEW_DOCUMENT} tanpa mengatur flag + {@link android.content.Intent#FLAG_ACTIVITY_MULTIPLE_TASK}, seperti dijelaskan dalam + Menggunakan flag Intent untuk menambahkan tugas, di atas.
+ +
"{@code always}"
+
Aktivitas ini membuat tugas baru untuk dokumen, meski dokumen sudah dibuka. Menggunakan + nilai ini sama dengan menetapkan flag {@link android.content.Intent#FLAG_ACTIVITY_NEW_DOCUMENT} + maupun {@link android.content.Intent#FLAG_ACTIVITY_MULTIPLE_TASK}.
+ +
"{@code none”}"
+
Aktivitas ini tidak membuat tugas baru untuk dokumen. Layar ikhtisar memperlakukan + aktivitas seperti itu secara default: satu tugas ditampilkan untuk aplikasi, yang +dilanjutkan dari aktivitas apa pun yang terakhir dipanggil pengguna.
+ +
"{@code never}"
+
Aktivitas ini tidak membuat tugas baru untuk dokumen. Mengatur nilai ini akan mengesampingkan + perilaku flag {@link android.content.Intent#FLAG_ACTIVITY_NEW_DOCUMENT} + dan {@link android.content.Intent#FLAG_ACTIVITY_MULTIPLE_TASK}, jika salah satunya ditetapkan di +intent, dan layar ikhtisar menampilkan satu tugas untuk aplikasi, yang dilanjutkan dari + aktivitas apa pun yang terakhir dipanggil pengguna.
+
+ +

Catatan: Untuk nilai selain {@code none} dan {@code never}, +aktivitas harus didefinisikan dengan {@code launchMode="standard"}. Jika atribut ini tidak ditetapkan, maka +{@code documentLaunchMode="none"} akan digunakan.

+ +

Menghapus Tugas

+ +

Secara default, tugas dokumen secara otomatis dihapus dari layar ikhtisar bila aktivitasnya +selesai. Anda bisa mengesampingkan perilaku ini dengan kelas {@link android.app.ActivityManager.AppTask}, +dengan flag {@link android.content.Intent} atau atribut +<activity>.

+ +

Kapan saja Anda bisa mengecualikan tugas dari layar ikhtisar secara keseluruhan dengan menetapkan atribut +<activity> +, +{@code android:excludeFromRecents} hingga {@code true}.

+ +

Anda bisa menetapkan jumlah maksimum tugas yang dapat disertakan aplikasi Anda dalam layar ikhtisar dengan menetapkan +atribut <activity> + {@code android:maxRecents} + ke satu nilai integer. Nilai default-nya adalah 16. Bila telah mencapai jumlah maksimum, tugas yang terakhir +digunakan akan dihapus dari layar ikhtisar. Nilai maksimum {@code android:maxRecents} + adalah 50 (25 pada perangkat dengan memori sedikit); nilai yang kurang dari 1 tidak berlaku.

+ +

Menggunakan kelas AppTask untuk menghapus tugas

+ +

Dalam aktivitas yang membuat tugas baru di layar ikhtisar, Anda bisa +menetapkan kapan menghapus tugas dan menyelesaikan semua aktivitas yang terkait dengannya +dengan memanggil metode {@link android.app.ActivityManager.AppTask#finishAndRemoveTask() finishAndRemoveTask()}.

+ +

+NewDocumentActivity.java

+
+public void onRemoveFromRecents(View view) {
+    // The document is no longer needed; remove its task.
+    finishAndRemoveTask();
+}
+
+ +

Catatan: Penggunaan metode +{@link android.app.ActivityManager.AppTask#finishAndRemoveTask() finishAndRemoveTask()} +akan mengesampingkan penggunaan tag {@link android.content.Intent#FLAG_ACTIVITY_RETAIN_IN_RECENTS}, seperti +dibahas di bawah ini.

+ +

Mempertahankan tugas yang telah selesai

+ +

Jika Anda ingin mempertahankan tugas di layar ikhtisar, sekalipun aktivitas sudah selesai, teruskan +flag {@link android.content.Intent#FLAG_ACTIVITY_RETAIN_IN_RECENTS} dalam metode +{@link android.content.Intent#addFlags(int) addFlags()} dari Intent yang memulai aktivitas itu.

+ +

+DocumentCentricActivity.java

+
+private Intent newDocumentIntent() {
+    final Intent newDocumentIntent = new Intent(this, NewDocumentActivity.class);
+    newDocumentIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT |
+      android.content.Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS);
+    newDocumentIntent.putExtra(KEY_EXTRA_NEW_DOCUMENT_COUNTER, incrementAndGet());
+    return newDocumentIntent;
+}
+
+ +

Untuk memperoleh efek yang sama, tetapkan atribut +<activity> + +{@code android:autoRemoveFromRecents} hingga {@code false}. Nilai default-nya adalah {@code true} +untuk aktivitas dokumen, dan {@code false} untuk aktivitas biasa. Penggunaan atribut ini akan mengesampingkan flag +{@link android.content.Intent#FLAG_ACTIVITY_RETAIN_IN_RECENTS}, yang telah dibahas sebelumnya.

+ + + + + + + diff --git a/docs/html-intl/intl/in/guide/components/services.jd b/docs/html-intl/intl/in/guide/components/services.jd new file mode 100644 index 0000000000000000000000000000000000000000..7ecd8cd38db9295c6d432de4a973dae6ef6e6877 --- /dev/null +++ b/docs/html-intl/intl/in/guide/components/services.jd @@ -0,0 +1,813 @@ +page.title=Layanan +@jd:body + +
+
    +

    Dalam dokumen ini

    +
      +
    1. Dasar-Dasar
    2. +
        +
      1. Mendeklarasikan layanan dalam manifes
      2. +
      +
    3. Membuat Layanan yang Sudah Dimulai +
        +
      1. Memperluas kelas IntentService
      2. +
      3. Memperluas kelas Layanan
      4. +
      5. Memulai layanan
      6. +
      7. Menghentikan layanan
      8. +
      +
    4. +
    5. Membuat Layanan Terikat
    6. +
    7. Mengirim Pemberitahuan ke Pengguna
    8. +
    9. Menjalankan Layanan di Latar Depan
    10. +
    11. Mengelola Daur Hidup Layanan +
        +
      1. Mengimplementasikan callback daur hidup
      2. +
      +
    12. +
    + +

    Kelas-kelas utama

    +
      +
    1. {@link android.app.Service}
    2. +
    3. {@link android.app.IntentService}
    4. +
    + +

    Contoh

    +
      +
    1. {@code + ServiceStartArguments}
    2. +
    3. {@code + LocalService}
    4. +
    + +

    Lihat juga

    +
      +
    1. Layanan Terikat
    2. +
    + +
+ + +

{@link android.app.Service} adalah sebuah komponen aplikasi yang bisa melakukan +operasi yang berjalan lama di latar belakang dan tidak menyediakan antarmuka pengguna. Komponen +aplikasi lain bisa memulai layanan dan komponen aplikasi tersebut akan terus berjalan +di latar belakang walaupun pengguna beralih ke aplikasi lain. Selain itu, komponen bisa mengikat ke layanan +untuk berinteraksi dengannya dan bahkan melakukan komunikasi antarproses (IPC). Misalnya, layanan mungkin +menangani transaksi jaringan, memutar musik, melakukan file I/O, atau berinteraksi dengan penyedia konten +dari latar belakang.

+ +

Ada dua bentuk dasar layanan:

+ +
+
Sudah Dimulai
+
Layanan "sudah dimulai" bila komponen aplikasi (misalnya aktivitas) memulainya dengan +memanggil {@link android.content.Context#startService startService()}. Sesudah dimulai, layanan +bisa berjalan terus-menerus di latar belakang walaupun komponen yang memulainya telah dimusnahkan. Biasanya, +layanan yang sudah dimulai akan melakukan operasi tunggal dan tidak mengembalikan hasil ke pemanggilnya. +Misalnya, layanan bisa mengunduh atau mengunggah file melalui jaringan. Bila operasi selesai, +layanan seharusnya berhenti sendiri.
+
Terikat
+
Layanan "terikat" bila komponen aplikasi mengikat kepadanya dengan memanggil {@link +android.content.Context#bindService bindService()}. Layanan terikat menawarkan antarmuka +klien-server yang memungkinkan komponen berinteraksi dengan layanan tersebut, mengirim permintaan, mendapatkan hasil dan bahkan +melakukannya pada sejumlah proses dengan komunikasi antarproses (IPC). Layanan terikat hanya berjalan selama +ada komponen aplikasi lain yang terikat padanya. Sejumlah komponen bisa terikat pada layanan secara bersamaan, +namun bila semuanya melepas ikatan, layanan tersebut akan dimusnahkan.
+
+ +

Walaupun dokumentasi ini secara umum membahas kedua jenis layanan secara terpisah, layanan +Anda bisa menggunakan keduanya—layanan bisa dimulai (untuk berjalan terus-menerus) sekaligus memungkinkan pengikatan. +Cukup mengimplementasikan dua metode callback: {@link +android.app.Service#onStartCommand onStartCommand()} untuk memungkinkan komponen memulainya dan {@link +android.app.Service#onBind onBind()} untuk memungkinkan pengikatan.

+ +

Apakah aplikasi Anda sudah dimulai, terikat, atau keduanya, semua komponen aplikasi +bisa menggunakan layanan (bahkan dari aplikasi terpisah), demikian pula semua komponen bisa menggunakan +suatu aktivitas—dengan memulainya dengan {@link android.content.Intent}. Akan tetapi, Anda bisa mendeklarasikan +layanan sebagai privat, pada file manifes, dan memblokir akses dari aplikasi lain. Hal ini +dibahas selengkapnya di bagian tentang Mendeklarasikan layanan dalam +manifes.

+ +

Perhatian: Layanan berjalan di +thread utama proses yang menjadi host-nya—layanan tidak membuat thread-nya sendiri +dan tidak berjalan pada proses terpisah (kecuali bila Anda tentukan demikian). Artinya, +jika layanan Anda akan melakukan pekerjaan yang membutuhkan tenaga CPU besar atau operasi yang memblokir (seperti +pemutaran MP3 atau jaringan), Anda perlu membuat thread baru dalam layanan untuk melakukan pekerjaan tersebut. Dengan menggunakan +thread terpisah, Anda mengurangi risiko terjadinya kesalahan Aplikasi Tidak Merespons (Application Not Responding/ANR) dan +thread utama aplikasi bisa tetap dikhususkan pada interaksi pengguna dengan aktivitas Anda.

+ + +

Dasar-Dasar

+ + + +

Untuk membuat layanan, Anda harus membuat subkelas {@link android.app.Service} (atau +salah satu dari subkelasnya yang ada). Dalam implementasi, Anda perlu mengesampingkan sebagian metode callback yang +menangani aspek utama daur hidup layanan dan memberikan mekanisme bagi komponen untuk mengikat +pada layanan, bila dibutuhkan. Metode callback terpenting yang perlu Anda kesampingkan adalah:

+ +
+
{@link android.app.Service#onStartCommand onStartCommand()}
+
Sistem akan memanggil metode ini bila komponen lain, misalnya aktivitas, +meminta dimulainya layanan, dengan memanggil {@link android.content.Context#startService +startService()}. Setelah metode ini dieksekusi, layanan akan dimulai dan bisa berjalan di +latar belakang terus-menerus. Jika mengimplementasikan ini, Anda bertanggung jawab menghentikan layanan bila +bila pekerjaannya selesai, dengan memanggil {@link android.app.Service#stopSelf stopSelf()} atau {@link +android.content.Context#stopService stopService()}. (Jika hanya ingin menyediakan pengikatan, Anda tidak +perlu mengimplementasikan metode ini.)
+
{@link android.app.Service#onBind onBind()}
+
Sistem akan memanggil metode ini bila komponen lain ingin mengikat pada +layanan (misalnya untuk melakukan RPC), dengan memanggil {@link android.content.Context#bindService +bindService()}. Dalam mengimplementasikan metode ini, Anda harus menyediakan antarmuka yang digunakan +klien untuk berkomunikasi dengan layanan, dengan mengembalikan {@link android.os.IBinder}. Anda harus selalu +mengimplementasikan metode ini, namun jika tidak ingin mengizinkan pengikatan, Anda perlu mengembalikan null.
+
{@link android.app.Service#onCreate()}
+
Sistem memanggil metode ini bila layanan dibuat untuk pertama kalinya, untuk melakukan prosedur +penyiapan satu kali (sebelum memanggil {@link android.app.Service#onStartCommand onStartCommand()} atau +{@link android.app.Service#onBind onBind()}). Bila layanan sudah berjalan, metode ini tidak +dipanggil.
+
{@link android.app.Service#onDestroy()}
+
Sistem memanggil metode ini bila layanan tidak lagi digunakan dan sedang dimusnahkan. +Layanan Anda perlu mengimplementasikannya untuk membersihkan sumber daya seperti thread, listener +terdaftar, penerima, dll. Ini adalah panggilan terakhir yang diterima layanan.
+
+ +

Bila komponen memulai layanan dengan memanggil {@link +android.content.Context#startService startService()} (yang menyebabkan panggilan ke {@link +android.app.Service#onStartCommand onStartCommand()}), maka layanan +terus berjalan hingga terhenti sendiri dengan {@link android.app.Service#stopSelf()} atau bila komponen +lain menghentikannya dengan memanggil {@link android.content.Context#stopService stopService()}.

+ +

Bila komponen memanggil +{@link android.content.Context#bindService bindService()} untuk membuat layanan (dan {@link +android.app.Service#onStartCommand onStartCommand()} tidak dipanggil), maka layanan hanya berjalan +selama komponen terikat kepadanya. Setelah layanan dilepas ikatannya dari semua klien, +sistem akan menghancurkannya.

+ +

Sistem Android akan menghentikan paksa layanan hanya bila memori tinggal sedikit dan sistem harus memulihkan +sumber daya sistem untuk aktivitas yang mendapatkan fokus pengguna. Jika layanan terikat pada suatu aktivitas yang mendapatkan +fokus pengguna, layanan tersebut lebih kecil kemungkinannya untuk dimatikan, dan jika layanan dideklarasikan untuk berjalan di latar depan (akan dibahas kemudian), maka sudah hampir pasti ia tidak akan dimatikan. +Sebaliknya, bila layanan sudah dimulai dan berjalan lama, maka sistem akan menurunkan posisinya +dalam daftar tugas latar belakang seiring waktu dan layanan akan sangat rentan untuk +dimatikan—bila layanan Anda dimulai, maka Anda harus mendesainnya agar bisa menangani restart +oleh sistem dengan baik. Jika sistem mematikan layanan Anda, layanan akan dimulai kembali begitu sumber daya +kembali tersedia (tetapi ini juga bergantung pada nilai yang Anda kembalikan dari {@link +android.app.Service#onStartCommand onStartCommand()}, sebagaimana akan dibahas nanti). Untuk informasi selengkapnya +tentang kapan sistem mungkin akan memusnahkan layanan, lihat dokumen +Proses dan Threading.

+ +

Dalam bagian selanjutnya, Anda akan melihat bagaimana membuat masing-masing tipe layanan dan cara menggunakannya +dari komponen aplikasi lain.

+ + + +

Mendeklarasikan layanan dalam manifes

+ +

Sebagaimana aktivitas (dan komponen lainnya), Anda harus mendeklarasikan semua layanan dalam file manifes +aplikasi Anda.

+ +

Untuk mendeklarasikan layanan Anda, tambahkan sebuah elemen {@code <service>} +sebagai anak +elemen {@code <application>}. Misalnya:

+ +
+<manifest ... >
+  ...
+  <application ... >
+      <service android:name=".ExampleService" />
+      ...
+  </application>
+</manifest>
+
+ +

Lihat acuan elemen {@code <service>} +untuk informasi selengkapnya tentang cara mendeklarasikan layanan Anda dalam manifes.

+ +

Ada atribut lain yang bisa Anda sertakan dalam elemen {@code <service>} untuk +mendefinisikan properti seperti izin yang dibutuhkan untuk memulai layanan dan proses +tempat berjalannya layanan. {@code android:name} adalah satu-satunya atribut yang diperlukan +—atribut tersebut menetapkan nama kelas layanan. Setelah +mempublikasikan aplikasi, Anda tidak boleh mengubah nama ini, karena jika melakukannya, Anda bisa merusak +kode karena dependensi terhadap intent eksplisit untuk memulai atau mengikat layanan (bacalah posting blog berjudul Things +That Cannot Change). + +

Untuk memastikan aplikasi Anda aman, selalu gunakan intent eksplisit saat memulai atau mengikat +{@link android.app.Service} Anda dan jangan mendeklarasikan filter intent untuk layanan. Jika +Anda perlu membiarkan adanya ambiguitas tentang layanan mana yang dimulai, Anda bisa +menyediakan filter intent bagi layanan dan tidak memasukkan nama komponen pada {@link +android.content.Intent}, namun Anda juga harus menyesuaikan paket bagi intent tersebut dengan {@link +android.content.Intent#setPackage setPackage()}, yang memberikan klarifikasi memadai bagi +target layanan.

+ +

Anda juga bisa memastikan layanan tersedia hanya bagi aplikasi Anda dengan +menyertakan atribut {@code android:exported} +dan mengaturnya ke {@code "false"}. Hal ini efektif menghentikan aplikasi lain agar tidak memulai +layanan Anda, bahkan saat menggunakan intent eksplisit.

+ + + + +

Membuat Layanan yang Sudah Dimulai

+ +

Layanan yang sudah dimulai adalah layanan yang dimulai komponen lain dengan memanggil {@link +android.content.Context#startService startService()}, yang menyebabkan panggilan ke metode +{@link android.app.Service#onStartCommand onStartCommand()} layanan.

+ +

Bila layanan sudah dimulai, layanan tersebut memiliki daur hidup yang tidak bergantung pada +komponen yang memulainya dan bisa berjalan terus-menerus di latar belakang walaupun +komponen yang memulainya dimusnahkan. Dengan sendirinya, layanan akan berhenti sendiri bila pekerjaannya +selesai dengan memanggil {@link android.app.Service#stopSelf stopSelf()}, atau komponen lain bisa menghentikannya +dengan memanggil {@link android.content.Context#stopService stopService()}.

+ +

Komponen aplikasi seperti aktivitas bisa memulai layanan dengan memanggil {@link +android.content.Context#startService startService()} dan meneruskan {@link android.content.Intent} +yang menetapkan layanan dan menyertakan data untuk digunakan layanan. Layanan menerima +{@link android.content.Intent} ini dalam metode {@link android.app.Service#onStartCommand +onStartCommand()}.

+ +

Sebagai contoh, anggaplah aktivitas perlu menyimpan data ke database online. Aktivitas tersebut bisa +memulai layanan pendamping dan mengiriminya data untuk disimpan dengan meneruskan intent ke {@link +android.content.Context#startService startService()}. Layanan akan menerima intent dalam {@link +android.app.Service#onStartCommand onStartCommand()}, menghubungkan ke Internet dan melakukan +transaksi database. Bila transaksi selesai, layanan akan berhenti sendiri dan +dimusnahkan.

+ +

Perhatian: Layanan berjalan dalam proses yang sama dengan aplikasi +tempatnya dideklarasikan dan dalam thread utama aplikasi tersebut, secara default. Jadi, bila layanan Anda +melakukan operasi yang intensif atau operasi pemblokiran saat pengguna berinteraksi dengan aktivitas dari +aplikasi yang sama, layanan akan memperlambat kinerja aktivitas. Agar tidak memengaruhi +kinerja aplikasi, Anda harus memulai thread baru di dalam layanan.

+ +

Biasanya, ada dua kelas yang bisa Anda perluas untuk membuat layanan yang sudah dimulai:

+
+
{@link android.app.Service}
+
Ini adalah kelas dasar untuk semua layanan. Bila memperluas kelas ini, Anda perlu +membuat thread baru sebagai tempat melaksanakan semua pekerjaan layanan tersebut, karena layanan +menggunakan thread utama aplikasi Anda secara default, dan hal ini bisa memperlambat +kinerja aktivitas yang dijalankan aplikasi Anda.
+
{@link android.app.IntentService}
+
Ini adalah subkelas {@link android.app.Service} yang menggunakan thread pekerja untuk menangani +semua permintaan memulai, satu per satu. Ini adalah pilihan terbaik jika Anda tidak mengharuskan layanan +menangani beberapa permintaan sekaligus. Anda cukup mengimplementasikan {@link +android.app.IntentService#onHandleIntent onHandleIntent()}, yang menerima intent untuk setiap +permintaan memulai agar bisa melakukan pekerjaan latar belakang.
+
+ +

Bagian selanjutnya membahas cara mengimplementasikan layanan Anda menggunakan +salah satu dari kelas-kelas ini.

+ + +

Memperluas kelas IntentService

+ +

Mengingat kebanyakan layanan yang sudah dimulai tidak perlu menangani beberapa permintaan +sekaligus (yang bisa berupa skenario multi-threading berbahaya), mungkin Anda sebaiknya mengimplementasikan +layanan menggunakan kelas {@link android.app.IntentService}.

+ +

Berikut ini yang dilakukan {@link android.app.IntentService}:

+ + + +

Oleh karena itu, Anda hanya perlu mengimplementasikan {@link +android.app.IntentService#onHandleIntent onHandleIntent()} untuk melakukan pekerjaan yang diberikan oleh +klien. (Akan tetapi, Anda juga perlu menyediakan konstruktor kecil bagi layanan.)

+ +

Berikut ini contoh implementasi {@link android.app.IntentService}:

+ +
+public class HelloIntentService extends IntentService {
+
+  /**
+   * A constructor is required, and must call the super {@link android.app.IntentService#IntentService}
+   * constructor with a name for the worker thread.
+   */
+  public HelloIntentService() {
+      super("HelloIntentService");
+  }
+
+  /**
+   * The IntentService calls this method from the default worker thread with
+   * the intent that started the service. When this method returns, IntentService
+   * stops the service, as appropriate.
+   */
+  @Override
+  protected void onHandleIntent(Intent intent) {
+      // Normally we would do some work here, like download a file.
+      // For our sample, we just sleep for 5 seconds.
+      long endTime = System.currentTimeMillis() + 5*1000;
+      while (System.currentTimeMillis() < endTime) {
+          synchronized (this) {
+              try {
+                  wait(endTime - System.currentTimeMillis());
+              } catch (Exception e) {
+              }
+          }
+      }
+  }
+}
+
+ +

Anda hanya memerlukan: konstruktor dan implementasi {@link +android.app.IntentService#onHandleIntent onHandleIntent()}.

+ +

Jika Anda memutuskan untuk juga mengesampingkan metode callback lain, seperti {@link +android.app.IntentService#onCreate onCreate()}, {@link +android.app.IntentService#onStartCommand onStartCommand()}, atau {@link +android.app.IntentService#onDestroy onDestroy()}, pastikan memanggil implementasi super, sehingga +{@link android.app.IntentService} bisa menangani hidup thread pekerja dengan baik.

+ +

Misalnya, {@link android.app.IntentService#onStartCommand onStartCommand()} harus mengembalikan +implementasi default (yang merupakan cara penyampaian intent ke {@link +android.app.IntentService#onHandleIntent onHandleIntent()}):

+ +
+@Override
+public int onStartCommand(Intent intent, int flags, int startId) {
+    Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show();
+    return super.onStartCommand(intent,flags,startId);
+}
+
+ +

Selain {@link android.app.IntentService#onHandleIntent onHandleIntent()}, satu-satunya metode lain +yang tidak mengharuskan Anda memanggil super kelas adalah {@link android.app.IntentService#onBind +onBind()} (namun Anda hanya perlu mengimplementasikannya bila layanan mengizinkan pengikatan).

+ +

Dalam bagian berikutnya, Anda akan melihat bagaimana layanan serupa diimplementasikan saat +memperluas kelas {@link android.app.Service} basis, yang membutuhkan kode lebih banyak lagi, namun mungkin +cocok jika Anda perlu menangani beberapa permintaan memulai sekaligus.

+ + +

Memperluas kelas Layanan

+ +

Seperti telah Anda lihat di bagian sebelumnya, menggunakan {@link android.app.IntentService} membuat +implementasi layanan yang sudah dimulai jadi sangat sederhana. Namun, bila Anda mengharuskan layanan untuk +melakukan multi-threading (sebagai ganti memproses permintaan memulai melalui antrean pekerjaan), maka Anda +bisa memperluas kelas {@link android.app.Service} untuk menangani masing-masing intent.

+ +

Sebagai perbandingan, contoh kode berikut ini adalah implementasi kelas {@link +android.app.Service} yang melakukan pekerjaan yang persis sama dengan contoh di atas menggunakan {@link +android.app.IntentService}. Artinya, untuk setiap permintaan memulai, kode tersebut akan menggunakan thread pekerja +untuk melakukan pekerjaan dan memproses permintaan satu per satu.

+ +
+public class HelloService extends Service {
+  private Looper mServiceLooper;
+  private ServiceHandler mServiceHandler;
+
+  // Handler that receives messages from the thread
+  private final class ServiceHandler extends Handler {
+      public ServiceHandler(Looper looper) {
+          super(looper);
+      }
+      @Override
+      public void handleMessage(Message msg) {
+          // Normally we would do some work here, like download a file.
+          // For our sample, we just sleep for 5 seconds.
+          long endTime = System.currentTimeMillis() + 5*1000;
+          while (System.currentTimeMillis() < endTime) {
+              synchronized (this) {
+                  try {
+                      wait(endTime - System.currentTimeMillis());
+                  } catch (Exception e) {
+                  }
+              }
+          }
+          // Stop the service using the startId, so that we don't stop
+          // the service in the middle of handling another job
+          stopSelf(msg.arg1);
+      }
+  }
+
+  @Override
+  public void onCreate() {
+    // Start up the thread running the service.  Note that we create a
+    // separate thread because the service normally runs in the process's
+    // main thread, which we don't want to block.  We also make it
+    // background priority so CPU-intensive work will not disrupt our UI.
+    HandlerThread thread = new HandlerThread("ServiceStartArguments",
+            Process.THREAD_PRIORITY_BACKGROUND);
+    thread.start();
+
+    // Get the HandlerThread's Looper and use it for our Handler
+    mServiceLooper = thread.getLooper();
+    mServiceHandler = new ServiceHandler(mServiceLooper);
+  }
+
+  @Override
+  public int onStartCommand(Intent intent, int flags, int startId) {
+      Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show();
+
+      // For each start request, send a message to start a job and deliver the
+      // start ID so we know which request we're stopping when we finish the job
+      Message msg = mServiceHandler.obtainMessage();
+      msg.arg1 = startId;
+      mServiceHandler.sendMessage(msg);
+
+      // If we get killed, after returning from here, restart
+      return START_STICKY;
+  }
+
+  @Override
+  public IBinder onBind(Intent intent) {
+      // We don't provide binding, so return null
+      return null;
+  }
+
+  @Override
+  public void onDestroy() {
+    Toast.makeText(this, "service done", Toast.LENGTH_SHORT).show();
+  }
+}
+
+ +

Seperti yang bisa Anda lihat, ini membutuhkan lebih banyak pekerjaan daripada menggunakan {@link android.app.IntentService}.

+ +

Akan tetapi, karena Anda menangani sendiri setiap panggilan ke {@link android.app.Service#onStartCommand +onStartCommand()}, Anda bisa melakukan beberapa permintaan sekaligus. Itu bukan yang +dilakukan contoh ini, namun jika itu yang diinginkan, Anda bisa membuat thread baru untuk setiap +permintaan dan langsung menjalankannya (sebagai ganti menunggu permintaan sebelumnya selesai).

+ +

Perhatikan bahwa metode {@link android.app.Service#onStartCommand onStartCommand()} harus mengembalikan +integer. Integer tersebut merupakan nilai yang menjelaskan cara sistem melanjutkan layanan dalam +kejadian yang dimatikan oleh sistem (sebagaimana dibahas di atas, implementasi default {@link +android.app.IntentService} menangani hal ini untuk Anda, walaupun Anda bisa memodifikasinya). Nilai yang dikembalikan +dari {@link android.app.Service#onStartCommand onStartCommand()} harus berupa salah satu +konstanta berikut ini:

+ +
+
{@link android.app.Service#START_NOT_STICKY}
+
Jika sistem mematikan layanan setelah {@link android.app.Service#onStartCommand +onStartCommand()} dikembalikan, jangan membuat lagi layanan tersebut, kecuali jika ada intent +tertunda yang akan disampaikan. Inilah pilihan teraman untuk menghindari menjalankan layanan Anda +bila tidak diperlukan dan bila aplikasi Anda bisa me-restart pekerjaan yang belum selesai.
+
{@link android.app.Service#START_STICKY}
+
Jika sistem mematikan layanan setelah {@link android.app.Service#onStartCommand +onStartCommand()} dikembalikan, buat kembali layanan dan panggil {@link +android.app.Service#onStartCommand onStartCommand()}, namun jangan menyampaikan ulang intent terakhir. +Sebagai gantinya, sistem akan memanggil {@link android.app.Service#onStartCommand onStartCommand()} dengan +intent null, kecuali jika ada intent tertunda untuk memulai layanan, dan dalam hal ini, +intent tersebut disampaikan. Ini cocok bagi pemutar media (atau layanan serupa) yang tidak +mengeksekusi perintah, namun berjalan terus-menerus dan menunggu pekerjaan.
+
{@link android.app.Service#START_REDELIVER_INTENT}
+
Jika sistem mematikan layanan setelah {@link android.app.Service#onStartCommand +onStartCommand()} kembali, buat kembali layanan dan panggil {@link +android.app.Service#onStartCommand onStartCommand()} dengan intent terakhir yang disampaikan ke +layanan. Intent yang tertunda akan disampaikan pada gilirannya. Ini cocok bagi layanan yang +aktif melakukan pekerjaan yang harus segera dilanjutkan, misalnya mengunduh file.
+
+

Untuk detail selengkapnya tentang nilai pengembalian ini, lihat dokumentasi acuan untuk setiap +konstanta.

+ + + +

Memulai Layanan

+ +

Anda bisa memulai layanan dari aktivitas atau komponen aplikasi lain dengan meneruskan +{@link android.content.Intent} (yang menetapkan layanan yang akan dimulai) ke {@link +android.content.Context#startService startService()}. Sistem Android akan memanggil metode {@link +android.app.Service#onStartCommand onStartCommand()} layanan dan meneruskan {@link +android.content.Intent} padanya. (Jangan sekali-kali memanggil {@link android.app.Service#onStartCommand +onStartCommand()} secara langsung.)

+ +

Misalnya, aktivitas bisa memulai contoh layanan di bagian sebelumnya ({@code +HelloSevice}) menggunakan intent eksplisit dengan {@link android.content.Context#startService +startService()}:

+ +
+Intent intent = new Intent(this, HelloService.class);
+startService(intent);
+
+ +

Metode {@link android.content.Context#startService startService()} segera kembali dan +sistem Android akan memanggil metode {@link android.app.Service#onStartCommand +onStartCommand()} layanan. Jika layanan belum berjalan, sistem mula-mula memanggil {@link +android.app.Service#onCreate onCreate()}, kemudian memanggil {@link android.app.Service#onStartCommand +onStartCommand()}.

+ +

Jika layanan juga tidak menyediakan pengikatan, intent yang disampaikan dengan {@link +android.content.Context#startService startService()} adalah satu-satunya mode komunikasi antara +komponen aplikasi dan layanan. Akan tetapi, jika Anda ingin agar layanan mengirimkan hasilnya kembali, maka +klien yang memulai layanan bisa membuat {@link android.app.PendingIntent} untuk siaran +(dengan {@link android.app.PendingIntent#getBroadcast getBroadcast()}) dan menyampaikannya ke layanan +dalam {@link android.content.Intent} yang memulai layanan. Layanan kemudian bisa menggunakan +siaran untuk menyampaikan hasil.

+ +

Beberapa permintaan untuk memulai layanan menghasilkan beberapa panggilan pula ke +{@link android.app.Service#onStartCommand onStartCommand()} layanan. Akan tetapi, hanya satu permintaan untuk menghentikan +layanan (dengan {@link android.app.Service#stopSelf stopSelf()} atau {@link +android.content.Context#stopService stopService()}) dibutuhkan untuk menghentikannya.

+ + +

Menghentikan layanan

+ +

Layanan yang sudah dimulai harus mengelola daur hidupnya sendiri. Artinya, sistem tidak menghentikan atau +memusnahkan layanan kecuali jika harus memulihkan memori sistem dan layanan +terus berjalan setelah {@link android.app.Service#onStartCommand onStartCommand()} kembali. Jadi, +layanan tersebut harus berhenti sendiri dengan memanggil {@link android.app.Service#stopSelf stopSelf()} atau +komponen lain bisa menghentikannya dengan memanggil {@link android.content.Context#stopService stopService()}.

+ +

Setelah diminta untuk berhenti dengan {@link android.app.Service#stopSelf stopSelf()} atau {@link +android.content.Context#stopService stopService()}, sistem akan menghapus layanan +secepatnya.

+ +

Akan tetapi, bila layanan Anda menangani beberapa permintaan ke {@link +android.app.Service#onStartCommand onStartCommand()} sekaligus, Anda tidak boleh menghentikan +layanan bila Anda baru selesai memproses permintaan memulai, karena setelah itu mungkin Anda sudah menerima permintaan memulai +yang baru (berhenti pada permintaan pertama akan menghentikan permintaan kedua). Untuk menghindari +masalah ini, Anda bisa menggunakan {@link android.app.Service#stopSelf(int)} untuk memastikan bahwa permintaan +Anda untuk menghentikan layanan selalu berdasarkan pada permintaan memulai terbaru. Artinya, bila Anda memanggil {@link +android.app.Service#stopSelf(int)}, Anda akan meneruskan ID permintaan memulai (startId +yang disampaikan ke {@link android.app.Service#onStartCommand onStartCommand()}) yang terkait dengan permintaan berhenti +Anda. Kemudian jika layanan menerima permintaan memulai baru sebelum Anda bisa memanggil {@link +android.app.Service#stopSelf(int)}, maka ID tidak akan sesuai dan layanan tidak akan berhenti.

+ +

Perhatian: Aplikasi Anda perlu menghentikan layanannya +bila selesai bekerja untuk menghindari pemborosan sumber daya sistem dan tenaga baterai. Jika perlu, +komponen lain bisa menghentikan layanan secara eksplisit dengan memanggil {@link +android.content.Context#stopService stopService()}. Bahkan jika Anda mengaktifkan pengikatan bagi layanan, +Anda harus selalu menghentikan layanan sendiri jika layanan tersebut menerima panggilan ke {@link +android.app.Service#onStartCommand onStartCommand()}.

+ +

Untuk informasi selengkapnya tentang daur hidup layanan, lihat bagian di bawah ini tentang Mengelola Daur Hidup Layanan.

+ + + +

Membuat Layanan Terikat

+ +

Layanan terikat adalah layanan yang memungkinkan komponen aplikasi untuk mengikatnya dengan memanggil {@link +android.content.Context#bindService bindService()} guna membuat koneksi yang berlangsung lama +(dan umumnya tidak mengizinkan komponen untuk memulainya dengan memanggil {@link +android.content.Context#startService startService()}).

+ +

Anda sebaiknya membuat layanan terikat bila ingin berinteraksi dengan layanan dari aktivitas +dan komponen lain dalam aplikasi Anda atau mengeskpos sebagian fungsionalitas aplikasi Anda ke +ke aplikasi lain, melalui komunikasi antarproses (IPC).

+ +

Untuk membuat layanan terikat, Anda harus mengimplementasikan metode callback {@link +android.app.Service#onBind onBind()} untuk mengembalikan {@link android.os.IBinder} yang +mendefinisikan antarmuka bagi komunikasi dengan layanan. Komponen aplikasi lain kemudian bisa memanggil +{@link android.content.Context#bindService bindService()} untuk mengambil antarmuka dan +mulai memanggil metode pada layanan. Layanan hanya hidup untuk melayani komponen aplikasi yang +terikat padanya, jadi bila tidak ada komponen yang terikat pada layanan, sistem akan memusnahkannya +(Anda tidak perlu menghentikan layanan terikat seperti halnya bila layanan dimulai +melalui {@link android.app.Service#onStartCommand onStartCommand()}).

+ +

Untuk membuat layanan terikat, hal yang perlu dilakukan pertama kali adalah mendefinisikan antarmuka yang menetapkan +cara klien berkomunikasi dengan layanan. Antarmuka antara layanan +dan klien ini harus berupa implementasi {@link android.os.IBinder} dan yang harus dikembalikan +layanan Anda dari metode callback {@link android.app.Service#onBind +onBind()}. Setelah menerima {@link android.os.IBinder}, klien bisa mulai +berinteraksi dengan layanan melalui antarmuka tersebut.

+ +

Beberapa klien bisa mengikat ke layanan sekaligus. Bila klien selesai berinteraksi dengan +layanan, klien akan memanggil {@link android.content.Context#unbindService unbindService()} untuk melepas ikatan. Bila +tidak ada klien yang terikat pada layanan, sistem akan menghapus layanan tersebut.

+ +

Ada beberapa cara untuk mengimplementasikan layanan terikat dan implementasinya lebih +rumit daripada layanan yang sudah dimulai, jadi layanan terikat dibahas dalam dokumen +terpisah tentang Layanan Terikat.

+ + + +

Mengirim Pemberitahuan ke Pengguna

+ +

Setelah berjalan, layanan bisa memberi tahu pengguna tentang suatu kejadian menggunakan Pemberitahuan Toast atau Pemberitahuan Baris Status.

+ +

Pemberitahuan Toast adalah pesan yang muncul sebentar pada permukaan jendela saat ini +kemudian menghilang, sementara pemberitahuan baris status memberikan ikon di baris status dengan +pesan yang bisa dipilih oleh pengguna untuk melakukan suatu tindakan (misalnya memulai suatu aktivitas).

+ +

Biasanya, pemberitahuan baris status adalah teknik terbaik bila ada pekerjaan latar belakang yang sudah selesai +(misalnya file selesai +diunduh) dan pengguna kini bisa menggunakannya. Bila pengguna memilih pemberitahuan dari +tampilan diperluas, pemberitahuan akan bisa memulai aktivitas (misalnya menampilkan file yang baru diunduh).

+ +

Lihat panduan pengembang Pemberitahuan Toast atau Pemberitahuan Baris Status +untuk informasi selengkapnya.

+ + + +

Menjalankan Layanan di Latar Depan

+ +

Layanan latar depan adalah layanan yang dianggap sebagai sesuatu yang +diketahui secara aktif oleh pengguna, jadi bukan sesuatu yang akan dihapus oleh sistem bila memori menipis. Sebuah +layanan latar depan harus memberikan pemberitahuan bagi baris status, yang ditempatkan pada +heading "Ongoing" yang artinya pemberitahuan tersebut tidak bisa diabaikan kecuali jika layanan +dihentikan atau dihapus dari latar depan.

+ +

Misalnya, pemutar musik yang memutar musik dari suatu layanan harus diatur untuk berjalan di +latar depan, karena pengguna mengetahui operasi tersebut +secara eksplisit. Pemberitahuan di baris status bisa menunjukkan lagu saat ini dan memungkinkan +pengguna untuk menjalankan suatu aktivitas untuk berinteraksi dengan pemutar musik.

+ +

Untuk meminta agar layanan Anda berjalan di latar depan, panggil {@link +android.app.Service#startForeground startForeground()}. Metode ini memerlukan dua parameter: sebuah integer +yang mengidentifikasi pemberitahuan secara unik dan {@link +android.app.Notification} untuk baris status. Misalnya:

+ +
+Notification notification = new Notification(R.drawable.icon, getText(R.string.ticker_text),
+        System.currentTimeMillis());
+Intent notificationIntent = new Intent(this, ExampleActivity.class);
+PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);
+notification.setLatestEventInfo(this, getText(R.string.notification_title),
+        getText(R.string.notification_message), pendingIntent);
+startForeground(ONGOING_NOTIFICATION_ID, notification);
+
+ +

Perhatian: ID integer yang Anda berikan ke {@link +android.app.Service#startForeground startForeground()} tidak boleh 0.

+ + +

Untuk menghapus layanan dari latar depan, panggil {@link +android.app.Service#stopForeground stopForeground()}. Metode ini memerlukan boolean, yang menunjukkan +apakah pemberitahuan baris status juga akan dihapus. Metode ini tidak menghentikan +layanan. Akan tetapi, jika Anda menghentikan layanan saat masih berjalan di latar depan +maka pemberitahuan juga akan dihapus.

+ +

Untuk informasi selengkapnya tentang pemberitahuan, lihat Membuat Pemberitahuan +Baris Status.

+ + + +

Mengelola Daur Hidup Layanan

+ +

Daur hidup layanan jauh lebih sederhana daripada daur hidup aktivitas. Akan tetapi, lebih penting lagi adalah +memerhatikan dengan cermat bagaimana layanan Anda dibuat dan dimusnahkan, karena suatu layanan +bisa berjalan di latar belakang tanpa disadari oleh pengguna.

+ +

Daur hidup layanan—dari saat dibuat hingga dimusnahkan—bisa mengikuti +dua path berbeda:

+ + + +

Kedua path tersebut tidak benar-benar terpisah. Artinya, Anda bisa mengikat ke layanan yang sudah +dimulai dengan {@link android.content.Context#startService startService()}. Misalnya, layanan +musik latar belakang bisa dimulai dengan memanggil {@link android.content.Context#startService +startService()} dengan {@link android.content.Intent} yang mengidentifikasi musik yang akan diputar. Kemudian, +mungkin saat pengguna ingin mengontrol pemutar musik atau mendapatkan informasi +tentang lagu yang diputar, aktivitas bisa mengikat ke layanan dengan memanggil {@link +android.content.Context#bindService bindService()}. Dalam kasus seperti ini, {@link +android.content.Context#stopService stopService()} atau {@link android.app.Service#stopSelf +stopSelf()} tidak menghentikan layanan sampai semua klien melepas ikatan.

+ + +

Mengimplementasikan callback daur hidup

+ +

Seperti halnya aktivitas, layanan memiliki metode callback daur hidup yang bisa Anda implementasikan +untuk memantau perubahan status layanan dan melakukan pekerjaan pada waktu yang tepat. Layanan skeleton +berikut memperagakan setiap metode daur hidup:

+ +
+public class ExampleService extends Service {
+    int mStartMode;       // indicates how to behave if the service is killed
+    IBinder mBinder;      // interface for clients that bind
+    boolean mAllowRebind; // indicates whether onRebind should be used
+
+    @Override
+    public void {@link android.app.Service#onCreate onCreate}() {
+        // The service is being created
+    }
+    @Override
+    public int {@link android.app.Service#onStartCommand onStartCommand}(Intent intent, int flags, int startId) {
+        // The service is starting, due to a call to {@link android.content.Context#startService startService()}
+        return mStartMode;
+    }
+    @Override
+    public IBinder {@link android.app.Service#onBind onBind}(Intent intent) {
+        // A client is binding to the service with {@link android.content.Context#bindService bindService()}
+        return mBinder;
+    }
+    @Override
+    public boolean {@link android.app.Service#onUnbind onUnbind}(Intent intent) {
+        // All clients have unbound with {@link android.content.Context#unbindService unbindService()}
+        return mAllowRebind;
+    }
+    @Override
+    public void {@link android.app.Service#onRebind onRebind}(Intent intent) {
+        // A client is binding to the service with {@link android.content.Context#bindService bindService()},
+        // after onUnbind() has already been called
+    }
+    @Override
+    public void {@link android.app.Service#onDestroy onDestroy}() {
+        // The service is no longer used and is being destroyed
+    }
+}
+
+ +

Catatan: Tidak seperti metode callback daur hidup aktivitas, Anda +tidak perlu memanggil implementasi superkelas metode callback tersebut.

+ + +

Gambar 2. Daur hidup layanan. Diagram di sebelah kiri +menampilkan daur hidup bila layanan dibuat dengan {@link android.content.Context#startService +startService()} dan diagram di sebelah kanan menampilkan daur hidup bila layanan dibuat +dengan {@link android.content.Context#bindService bindService()}.

+ +

Dengan mengimplementasikan metode-metode ini, Anda bisa memantau dua loop tersarang (nested loop) daur hidup layanan:

+ + + +

Catatan: Meskipun layanan yang sudah dimulai dihentikan dengan panggilan ke +{@link android.app.Service#stopSelf stopSelf()} atau {@link +android.content.Context#stopService stopService()}, tidak ada callback tersendiri bagi +layanan tersebut (tidak ada callback {@code onStop()}). Jadi, kecuali jika layanan terikat ke klien, +sistem akan memusnahkannya bila layanan dihentikan—{@link +android.app.Service#onDestroy onDestroy()} adalah satu-satunya callback yang diterima.

+ +

Gambar 2 mengilustrasikan metode callback yang lazim bagi suatu layanan. Walaupun gambar tersebut memisahkan +layanan yang dibuat oleh {@link android.content.Context#startService startService()} dari layanan +yang dibuat oleh {@link android.content.Context#bindService bindService()}, ingatlah +bahwa suatu layanan, bagaimana pun dimulainya, bisa memungkinkan klien mengikat padanya. +Jadi, suatu layanan yang awalnya dimulai dengan {@link android.app.Service#onStartCommand +onStartCommand()} (oleh klien yang memanggil {@link android.content.Context#startService startService()}) +masih bisa menerima panggilan ke {@link android.app.Service#onBind onBind()} (bila klien memanggil +{@link android.content.Context#bindService bindService()}).

+ +

Untuk informasi selengkapnya tentang membuat layanan yang menyediakan pengikatan, lihat dokumen Layanan Terikat, +yang menyertakan informasi selengkapnya tentang metode callback {@link android.app.Service#onRebind onRebind()} +di bagian tentang Mengelola Daur Hidup +Layanan Terikat.

+ + + diff --git a/docs/html-intl/intl/in/guide/components/tasks-and-back-stack.jd b/docs/html-intl/intl/in/guide/components/tasks-and-back-stack.jd new file mode 100644 index 0000000000000000000000000000000000000000..279442f2ab381e511ecedbd383718e71ddfdfb2f --- /dev/null +++ b/docs/html-intl/intl/in/guide/components/tasks-and-back-stack.jd @@ -0,0 +1,578 @@ +page.title=Tugas dan Back-Stack +parent.title=Aktivitas +parent.link=activities.html +@jd:body + +
+
+ +

Dalam dokumen ini

+
    +
  1. Menyimpan Status Aktivitas
  2. +
  3. Mengelola Tugas +
      +
    1. Mendefinisikan mode peluncuran
    2. +
    3. Menangani afinitas
    4. +
    5. Menghapus back-stack
    6. +
    7. Memulai tugas
    8. +
    +
  4. +
+ +

Artikel

+
    +
  1. + Multitasking Ala Android
  2. +
+ +

Lihat juga

+
    +
  1. Desain Android: +Navigasi
  2. +
  3. Elemen manifes +{@code <activity>}
  4. +
  5. Layar Ikhtisar
  6. +
+
+
+ + +

Sebuah aplikasi biasanya berisi beberapa aktivitas. Setiap aktivitas +harus didesain dengan jenis tindakan tertentu yang bisa dilakukan pengguna dan bisa memulai aktivitas +lain. Misalnya, aplikasi email mungkin memiliki satu aktivitas untuk menampilkan daftar pesan baru. +Bila pengguna memilih sebuah pesan, aktivitas baru akan terbuka untuk melihat pesan tersebut.

+ +

Aktivitas bahkan bisa memulai aktivitas yang ada dalam aplikasi lain di perangkat. Misalnya +, jika aplikasi Anda ingin mengirim pesan email, Anda bisa mendefinisikan intent untuk melakukan tindakan +"kirim" dan menyertakan sejumlah data, seperti alamat email dan pesan. Aktivitas dari aplikasi +lain yang mendeklarasikan dirinya untuk menangani jenis intent ini akan terbuka. Dalam hal ini, intent +tersebut untuk mengirim email, sehingga aktivitas "menulis" pada aplikasi email akan dimulai (jika beberapa aktivitas +mendukung intent yang sama, maka sistem akan memungkinkan pengguna memilih mana yang akan digunakan). Bila email telah +dikirim, aktivitas Anda akan dilanjutkan dan seolah-olah aktivitas email adalah bagian dari aplikasi Anda. Meskipun +aktivitas mungkin dari aplikasi yang berbeda, Android akan tetap mempertahankan pengalaman pengguna yang mulus +dengan menjalankan kedua aktivitas dalam tugas yang sama.

+ +

Tugas adalah kumpulan aktivitas yang berinteraksi dengan pengguna +saat melakukan pekerjaan tertentu. Aktivitas tersebut diatur dalam tumpukan (back-stack), dalam +urutan membuka setiap aktivitas.

+ + + +

Layar Home perangkat adalah tempat memulai hampir semua tugas. Bila pengguna menyentuh ikon di launcher +aplikasi +(atau pintasan pada layar Home), tugas aplikasi tersebut akan muncul pada latar depan. Jika tidak ada +tugas untuk aplikasi (aplikasi tidak digunakan baru-baru ini), maka tugas baru +akan dibuat dan aktivitas "utama" untuk aplikasi tersebut akan terbuka sebagai aktivitas akar dalam back-stack.

+ +

Bila aktivitas saat ini dimulai lagi, aktivitas baru akan didorong ke atas back-stack dan +mengambil fokus. Aktivitas sebelumnya tetap dalam back-stack, namun dihentikan. Bila aktivitas +dihentikan, sistem akan mempertahankan status antarmuka penggunanya saat ini. Bila pengguna menekan tombol +Back +, aktivitas saat ini akan dikeluarkan dari atas back-stack (aktivitas dimusnahkan) dan + aktivitas sebelumnya dilanjutkan (status UI sebelumnya dipulihkan). Aktivitas dalam back-stack +tidak pernah disusun ulang, hanya didorong dan dikeluarkan dari back-stack—yang didorong ke back-stack saat dimulai oleh +aktivitas saat ini dan dikeluarkan bila pengguna meninggalkannya menggunakan tombol Back. Dengan demikian, +back-stack +beroperasi sebagai struktur objek "masuk terakhir, keluar pertama". Gambar 1 melukiskan perilaku +ini dengan jangka waktu yang menunjukkan kemajuan antar aktivitas beserta +back-stack pada setiap waktu.

+ + +

Gambar 1. Representasi tentang cara setiap aktivitas baru dalam +tugas menambahkan item ke back-stack. Bila pengguna menekan tombol Back, aktivitas +saat ini +akan dimusnahkan dan aktivitas sebelumnya dilanjutkan.

+ + +

Jika pengguna terus menekan Back, maka setiap aktivitas dalam back-stack akan dikeluarkan untuk +menampilkan +yang sebelumnya, sampai pengguna kembali ke layar Home (atau aktivitas mana pun yang sedang dijalankan saat tugas +dimulai. Bila semua aktivitas telah dihapus dari back-stack, maka tugas tidak akan ada lagi.

+ +
+

Gambar 2. Dua tugas: Tugas B menerima interaksi pengguna +di latar depan, sedangkan Tugas A di latar belakang, menunggu untuk dilanjutkan.

+
+
+

Gambar 3. Satu aktivitas dibuat instance-nya beberapa kali.

+
+ +

Tugas adalah unit kohesif yang bisa dipindahkan ke "latar belakang" bila pengguna memulai tugas baru atau masuk ke +layar Home, melalui tombolHome. Sementara di latar belakang, semua aktivitas dalam +tugas +dihentikan, namun back-stack untuk tugas tidak berubah—tugas kehilangan fokus saat +tugas lain berlangsung, seperti yang ditampilkan dalam gambar 2. Kemudian, tugas bisa kembali ke "latar depan" agar pengguna +bisa melanjutkan tugas di tempat menghentikannya. Anggaplah, misalnya, tugas saat ini (Tugas A) memiliki tiga +aktivitas dalam back-stack—dua pada aktivitas saat ini. Pengguna menekan tombol Home +, kemudian +memulai aplikasi baru dari launcher aplikasi. Bila muncul layar Home, Tugas A akan beralih +ke latar belakang. Bila aplikasi baru dimulai, sistem akan memulai tugas untuk aplikasi tersebut +(Tugas B) dengan back-stack aktivitas sendiri. Setelah berinteraksi dengan aplikasi +tersebut, pengguna akan kembali ke Home lagi dan memilih aplikasi yang semula +memulai Tugas A. Sekarang, Tugas A muncul di +latar depan—ketiga aktivitas dalam back-stack tidak berubah dan aktivitas di atas +back-stack akan dilanjutkan. Pada +titik ini pengguna juga bisa beralih kembali ke Tugas B dengan masuk ke Home dan memilih ikon aplikasi +yang memulai tugas tersebut (atau dengan memilih tugas aplikasi dari +layar ikhtisar). +Ini adalah contoh dari melakukan multitasking di Android.

+ +

Catatan: Beberapa tugas bisa berlangsung di latar belakang secara bersamaan. +Akan tetapi, jika pengguna menjalankan banyak tugas di latar belakang sekaligus, sistem mungkin mulai +menghapus aktivitas latar belakang untuk memulihkan memori, yang akan menyebabkan status aktivitas hilang. +Lihat bagian berikut tentang Status aktivitas.

+ +

Karena aktivitas di back-stack tidak pernah diatur ulang, jika aplikasi Anda memungkinkan +pengguna untuk memulai aktivitas tertentu dari lebih dari satu aktivitas, instance baru +aktivitas tersebut akan dibuat dan didorong ke back-stack (bukannya memunculkan instance sebelumnya dari +aktivitas ke atas). Dengan demikian, satu aktivitas pada aplikasi Anda mungkin dibuat beberapa +kali (bahkan dari beberapa tugas), seperti yang ditampilkan dalam gambar 3. Dengan demikian, jika pengguna mengarahkan mundur +menggunakan tombol Back, setiap instance aktivitas ini akan ditampilkan dalam urutan saat +dibuka (masing-masing +dengan status UI sendiri). Akan tetapi, Anda bisa memodifikasi perilaku ini jika tidak ingin aktivitas +dibuat instance-nya lebih dari sekali. Caranya dibahas di bagian selanjutnya tentang Mengelola Tugas.

+ + +

Untuk meringkas perilaku default aktivitas dan tugas:

+ + + + +
+

Desain Navigasi

+

Untuk mengetahui selengkapnya tentang cara kerja navigasi aplikasi di Android, baca panduan Navigasi Desain Android.

+
+ + +

Menyimpan Status Aktivitas

+ +

Seperti dibahas di atas, perilaku default sistem akan mempertahankan status aktivitas bila +dihentikan. Dengan cara ini, bila pengguna mengarah kembali ke aktivitas sebelumnya, antarmuka pengguna akan muncul +seperti saat ditinggalkan. Akan tetapi, Anda bisa—dan harus—secara proaktif mempertahankan +status aktivitas menggunakan metode callback, jika aktivitas ini dimusnahkan dan harus +dibuat kembali.

+ +

Bila sistem menghentikan salah satu aktivitas (seperti saat aktivitas baru dimulai atau tugas +dipindah ke latar belakang), sistem mungkin memusnahkan aktivitas sepenuhnya jika perlu memulihkan +memori sistem. Bila hal ini terjadi, informasi tentang status aktivitas akan hilang. Jika hal ini terjadi, sistem +masih +mengetahui bahwa aktivitas memiliki tempat di back-stack, namun saat aktivitas tersebut dibawa ke bagian teratas +back-stack, sistem harus membuatnya kembali (bukan melanjutkannya). Untuk +menghindari hilangnya pekerjaan pengguna, Anda harus secara proaktif mempertahankannya dengan menerapkan metode callback +{@link android.app.Activity#onSaveInstanceState onSaveInstanceState()} +dalam aktivitas.

+ +

Untuk informasi selengkapnya tentang cara menyimpan status aktivitas Anda, lihat dokumen +Aktivitas.

+ + + +

Mengelola Tugas

+ +

Cara Android mengelola tugas dan back-stack, seperti yang dijelaskan di atas—dengan menempatkan semua +aktivitas yang dimulai secara berurutan dalam tugas yang sama dan dalam back-stack "masuk terakhir, keluar pertama"—berfungsi +dengan baik untuk kebanyakan aplikasi dan Anda tidak perlu khawatir tentang cara mengaitkan aktivitas +dengan tugas atau cara penempatannya di back-stack. Akan tetapi, Anda bisa memutuskan apakah ingin menyela +perilaku normal. Mungkin Anda ingin agar suatu aktivitas dalam aplikasi untuk memulai tugas baru bila telah +dimulai (sebagai ganti menempatkannya dalam tugas saat ini); atau, bila memulai aktivitas, Anda ingin +memajukan instance yang ada (sebagai ganti membuat instance +baru pada bagian teratas back-stack); atau, Anda ingin back-stack dihapus dari semua +aktivitas selain untuk aktivitas akar bila pengguna meninggalkan tugas.

+ +

Anda bisa melakukan semua ini dan lainnya, dengan atribut dalam elemen manifes +{@code <activity>} +dan dengan flag pada intent yang Anda teruskan ke +{@link android.app.Activity#startActivity startActivity()}.

+ +

Dalam hal ini, atribut +{@code <activity>} utama yang bisa Anda gunakan adalah:

+ + + +

Dan flag intent utama yang bisa Anda gunakan adalah:

+ + + +

Dalam bagian berikut, Anda akan melihat cara menggunakan beberapa atribut manifes ini dan flag +intent untuk mendefinisikan cara mengaitkan aktivitas dengan tugas dan cara perilakunya di back-stack.

+ +

Juga, pertimbangan cara menyatakan dan mengelola tugas dan aktivitas +dibahas secara terpisah di layar ikhtisar. Lihat Layar Ikhtisar +untuk informasi selengkapnya. Biasanya Anda harus mengizinkan sistem mendefinisikan cara menyatakan tugas dan +aktivitas di layar ikhtisar, dan Anda tidak perlu memodifikasi perilaku ini.

+ +

Perhatian: Kebanyakan aplikasi tidak harus menyela perilaku +default untuk aktivitas dan tugas. Jika merasa bahwa aktivitas Anda perlu memodifikasi +perilaku default, lakukan dengan hati-hati dan pastikan menguji kegunaan aktivitas selama +dijalankan dan saat mengarahkan kembali ke sana dari aktivitas dan tugas lain dengan tombol Back. +Pastikan menguji perilaku navigasi yang mungkin bertentangan dengan perilaku yang diharapkan pengguna.

+ + +

Mendefinisikan mode peluncuran

+ +

Mode peluncuran memungkinkan Anda mendefinisikan cara mengaitkan instance baru dari suatu aktivitas dengan +tugas saat ini. Anda bisa mendefinisikan beragam mode peluncuran dalam dua cara:

+ + +

Dengan demikian, jika Aktivitas A memulai Aktivitas B, Aktivitas B bisa mendefinisikan dalam manifesnya cara +mengaitkan dengan tugas saat ini (jika sama sekali) dan Aktivitas A juga bisa meminta cara mengaitkan Aktivitas B +dengan tugas saat ini. Jika kedua aktivitas mendefinisikan cara mengaitkan Aktivitas B +dengan tugas, maka permintaan Aktivitas A (sebagaimana didefinisikan dalam intent) lebih dihargai daripada +permintaan Aktivitas B (sebagaimana didefinisikan dalam manifesnya).

+ +

Catatan: Beberapa mode peluncuran yang tersedia untuk file manifes +tidak tersedia sebagai flag untuk intent dan, juga, beberapa mode peluncuran yang tersedia sebagai flag +untuk intent tidak bisa didefinisikan dalam manifest.

+ + +

Menggunakan file manifes

+ +

Saat mendeklarasikan aktivitas dalam file manifes, Anda bisa menetapkan cara mengaitkan aktivitas +dengan tugas menggunakan {@code <activity>} +melalui atribut {@code +launchMode} elemen.

+ +

Atribut {@code +launchMode} menetapkan instruksi tentang cara meluncurkan aktivitas +ke dalam tugas. Ada empat macam mode peluncuran yang bisa Anda tetapkan ke atribut +launchMode +:

+ +
+
{@code "standard"} (mode default)
+
Default. Sistem membuat instance baru aktivitas dalam tugas yang +akan menjadi tempat memulainya dan mengarahkan intent ke sana. Aktivitas ini bisa dibuat instance-nya beberapa kali, +masing-masing instance bisa dimiliki oleh tugas berbeda, dan satu tugas bisa memiliki beberapa instance.
+
{@code "singleTop"}
+
Jika instance aktivitas sudah ada di bagian teratas tugas saat ini, sistem +akan mengarahkan intent ke instance tersebut melalui panggilan ke metode {@link +android.app.Activity#onNewIntent onNewIntent()}, bukan membuat instance baru dari +aktivitas tersebut. Aktivitas bisa dibuat instance-nya beberapa kali, masing-masing instance bisa dimiliki +oleh tugas berbeda, dan satu tugas bisa memiliki beberapa instance (namun hanya jika +aktivitas di bagian teratas back-stack bukan instance yang ada dari aktivitas tersebut). +

Misalnya, anggaplah back-stack tugas terdiri dari aktivitas A akar dengan aktivitas B, C, +dan D di bagian teratas (back-stack adalah A-B-C-D; D yang teratas). Intent masuk untuk aktivitas tipe D. +Jika D memiliki mode peluncuran {@code "standard"} default, instance baru dari kelas ini akan diluncurkan dan +back-stack menjadi A-B-C-D-D. Namun, jika mode peluncuran D adalah {@code "singleTop"}, instance +yang ada dari D akan menerima intent melalui {@link +android.app.Activity#onNewIntent onNewIntent()}, karena ada di bagian teratas back-stack— +back-stack tetap A-B-C-D. Akan tetapi, jika intent masuk untuk aktivitas tipe B, maka +instance B baru akan ditambahkan ke back-stack, sekalipun mode peluncuran adalah{@code "singleTop"}.

+

Catatan: Bila instance dari aktivitas baru telah dibuat, +pengguna bisa menekan tombol Back untuk kembali ke aktivitas sebelumnya. Namun bila instance +yang ada dari +aktivitas menangani intent baru, pengguna tidak bisa menekan tombol Back untuk kembali ke +status +aktivitas sebelum intent baru masuk di {@link android.app.Activity#onNewIntent +onNewIntent()}.

+
+ +
{@code "singleTask"}
+
Sistem membuat tugas baru dan membuat instance aktivitas di akar tugas baru. +Akan tetapi, jika instance aktivitas sudah ada dalam tugas terpisah, sistem akan mengarahkan +intent ke instance yang ada melalui panggilan ke metode {@link +android.app.Activity#onNewIntent onNewIntent()}, bukan membuat instance baru. Hanya +boleh ada satu instance aktivitas untuk setiap kalinya. +

Catatan: Meskipun aktivitas dimulai di tugas baru, tombol +Back tetap akan mengembalikan pengguna ke aktivitas sebelumnya.

+
{@code "singleInstance"}.
+
Sama seperti {@code "singleTask"}, namun sistem tidak meluncurkan aktivitas lain ke +tugas yang menyimpan instance. Aktivitas selalu satu dan satu-satunya anggota dari tugasnya; +aktivitas apa pun yang dimulai dengan ini akan dibuka di tugas yang terpisah.
+
+ + +

Sebagai contoh lainnya, aplikasi Browser Android mendeklarasikan bahwa aktivitas browser web harus +selalu dibuka dalam tugasnya sendiri—dengan menetapkan mode pembuka {@code singleTask} dalam elemen{@code <activity>}. +Ini berarti bahwa jika aplikasi Anda mengeluarkan +intent untuk membuka Browser Android, aktivitasnya tidak akan ditempatkan dalam tugas +yang sama seperti aplikasi Anda. Sebagai gantinya, tugas baru akan dimulai untuk Browser atau, jika Browser +sudah memiliki tugas yang berjalan di latar belakang, tugas tersebut akan dimajukan untuk menangani intent +baru.

+ +

Baik aktivitas dimulai dalam tugas baru atau maupun dalam tugas yang sama seperti aktivitas yang memulainya, tombol +Back selalu membawa pengguna ke aktivitas sebelumnya. Akan tetapi, jika +Anda memulai aktivitas yang menetapkan mode pembuka {@code singleTask}, maka jika instance +aktivitas tersebut ada dalam tugas latar belakang, seluruh tugas tersebut akan dibawa ke latar depan. Pada titik +ini, back-stack sekarang menyertakan semua aktivitas dari tugas yang dimajukan, di atas +back-stack. Gambar 4 mengilustrasikan tipe skenario ini.

+ + +

Gambar 4. Representasi tentang cara aktivitas dengan +mode pembuka "singleTask" ditambahkan ke back-stack. Jika aktivitas tersebut sudah menjadi bagian dari +tugas latar belakang dengan back-stack sendiri, maka seluruh back-stack juga +dimajukan, di atas tugas saat ini.

+ +

Untuk informasi selengkapnya tentang menggunakan mode pembuka dalam file manifes, lihat dokumentasi elemen +<activity> +, di mana atribut {@code launchMode} dan nilai-nilai yang diterima +akan dibahas selengkapnya.

+ +

Catatan: Perilaku yang Anda tentukan untuk aktivitas dengan atribut {@code launchMode} +bisa dikesampingkan dengan flag yang disertakan bersama intent yang memulai aktivitas Anda, seperti dibahas dalam +bagian berikutnya.

+ + + +

Menggunakan flag Intent

+ +

Saat memulai aktivitas, Anda bisa memodifikasi asosiasi default aktivitas pada tugasnya + dengan menyertakan flag dalam intent yang Anda kirimkan ke {@link +android.app.Activity#startActivity startActivity()}. Flag yang bisa Anda gunakan untuk memodifikasi perilaku default +adalah:

+ +

+

{@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK}
+
Memulai aktivitas dalam tugas baru. Jika tugas sudah dijalankan untuk aktivitas yang sekarang +Anda mulai, tugas tersebut akan dibawa ke latar depan dengan status terakhir yang dipulihkan dan aktivitas +akan menerima intent baru dalam {@link android.app.Activity#onNewIntent onNewIntent()}. +

Ini menghasilkan perilaku yang sama dengan nilai {@code "singleTask"} {@code launchMode} +yang dibahas di bagian sebelumnya.

+
{@link android.content.Intent#FLAG_ACTIVITY_SINGLE_TOP}
+
Jika aktivitas yang dimulai adalah aktivitas saat ini (di bagian teratas back-stack), maka +instance yang ada akan menerima panggilan ke {@link android.app.Activity#onNewIntent onNewIntent()} +sebagai ganti membuat instance baru aktivitas. +

Ini menghasilkan perilaku yang sama dengan nilai {@code "singleTop"} {@code launchMode} +yang dibahas di bagian sebelumnya.

+
{@link android.content.Intent#FLAG_ACTIVITY_CLEAR_TOP}
+
Jika aktivitas yang dimulai sudah berjalan dalam tugas saat ini, maka sebagai +ganti meluncurkan instance baru aktivitas tersebut, semua kegiatan lain di atasnya akan +dimusnahkan dan intent ini akan disampaikan ke instance aktivitas yang dilanjutkan (sekarang di atas), +melalui {@link android.app.Activity#onNewIntent onNewIntent()}). +

Tidak ada nilai untuk atribut {@code launchMode} + yang menghasilkan perilaku ini.

+

{@code FLAG_ACTIVITY_CLEAR_TOP} paling sering digunakan bersama dengan + {@code FLAG_ACTIVITY_NEW_TASK}. +Bila digunakan bersama-sama, flag ini adalah cara penempatan aktivitas yang ada +dalam tugas lain dan meletakkannya dalam posisi yang memungkinkannya merespons intent.

+

Catatan: Jika mode pembuka aktivitas yang didesain adalah +{@code "standard"}, +ini juga akan dihapus dari back-stack dan instance baru akan diluncurkan di tempatnya untuk menangani +intent yang masuk. Itu karena instance baru selalu dibuat untuk intent baru bila +mode peluncuran adalah {@code "standard"}.

+
+ + + + + + +

Menangani afinitas

+ +

Afinitas menunjukkan tugas mana yang disukai aktivitas untuk dimiliki. Secara default, semua +aktivitas aplikasi yang sama memiliki afinitas untuk satu sama lain. Jadi, secara default, semua +aktivitas dalam aplikasi yang sama lebih menyukai berada dalam tugas yang sama. Akan tetapi, Anda bisa memodifikasi +afinitas default untuk suatu aktivitas. Aktivitas yang didefinisikan dalam +aplikasi yang berbeda bisa berbagi afinitas, atau aktivitas yang didefinisikan dalam aplikasi yang sama bisa +diberi afinitas tugas yang berbeda.

+ +

Anda bisa memodifikasi afinitas untuk setiap yang diberikan +dengan atribut {@code taskAffinity} +elemen {@code <activity>}.

+ +

Atribut {@code taskAffinity} +mengambil nilai string, yang harus unik dari nama paket default +yang dideklarasikan dalam elemen +{@code <manifest>} +, karena sistem menggunakan nama untuk mengidentifikasi afinitas +tugas default untuk aplikasi.

+ +

Afinitas berperan dalam dua keadaan:

+ + +

Tip: Jika file {@code .apk} berisi lebih dari satu "aplikasi" +dari sudut pandang pengguna, Anda mungkin perlu menggunakan atribut {@code taskAffinity} + untuk menetapkan afinitas berbeda pada aktivitas yang terkait dengan setiap "aplikasi".

+ + + +

Menghapus back-stack

+ +

Jika pengguna meninggalkan tugas dalam waktu yang lama, sistem akan menghapus tugas semua aktivitas kecuali +aktivitas akar. Bila pengguna kembali ke tugas itu lagi, hanya aktivitas akar yang akan dipulihkan. +Sistem berperilaku seperti ini, karena, setelah sekian waktu, pengguna mungkin telah mengabaikan +apa yang mereka kerjakan sebelum dan kembali ke tugas itu untuk memulai sesuatu yang baru.

+ +

Ada beberapa atribut aktivitas yang bisa Anda gunakan untuk memodifikasi perilaku ini:

+ +
+
alwaysRetainTaskState +
+
Jika atribut ini ditetapkan ke {@code "true"} dalam aktivitas akar tugas, +perilaku default yang baru dijelaskan tidak akan terjadi. + Tugas akan mempertahankan semua aktivitas dalam back-stack bahkan setelah sekian lama.
+ +
clearTaskOnLaunch
+
Jika atribut ini diatur ke {@code "true"} dalam aktivitas akar tugas, back- +stack akan dihapus hingga aktivitas akar bila pengguna meninggalkan tugas +dan kembali lagi. Dengan kata lain, ini adalah lawan dari + +{@code alwaysRetainTaskState}. Pengguna selalu kembali ke tugas dengan +status awalnya, walaupun hanya sebentar meninggalkan tugas.
+ +
finishOnTaskLaunch +
+
Atribut ini seperti {@code clearTaskOnLaunch}, +namun beroperasi pada +satu aktivitas, bukan pada seluruh tugas. Hal ini juga bisa menyebabkan aktivitas +hilang, termasuk aktivitas akar. Bila ini diatur ke {@code "true"}, +aktivitas akan tetap menjadi bagian dari tugas hanya untuk sesi saat ini. Jika pengguna +keluar dan kemudian kembali ke tugas tersebut, tugas tidak akan ada lagi.
+
+ + + + +

Memulai tugas

+ +

Anda bisa mengatur aktivitas sebagai titik masuk untuk tugas dengan memberikan filter intent dengan +{@code "android.intent.action.MAIN"} sebagai tindakan yang ditetapkan dan +{@code "android.intent.category.LAUNCHER"} +sebagai kategori yang ditetapkan. Misalnya:

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

Filter intent semacam ini akan menyebabkan ikon dan label untuk +aktivitas ditampilkan dalam launcher aplikasi, yang akan memberi cara kepada pengguna untuk meluncurkan aktivitas dan +kembali ke tugas yang dibuatnya kapan saja setelah ia telah diluncurkan. +

+ +

Kemampuan kedua ini penting: Pengguna harus bisa meninggalkan tugas dan kemudian kembali ke tugas tersebut +nanti dengan menggunakan launcher aktivitas ini. Karena itu, kedua mode +pembuka yang menandai aktivitas selalu memulai tugas, {@code "singleTask"} dan +{@code "singleInstance"}, hanya boleh digunakan bila aktivitas memiliki filter +{@link android.content.Intent#ACTION_MAIN} +dan {@link android.content.Intent#CATEGORY_LAUNCHER}. Bayangkan, misalnya, apa yang akan +terjadi jika filter tidak ada: Intent meluncurkan aktivitas{@code "singleTask"}, memulai +tugas yang baru, dan pengguna menghabiskan lebih banyak waktu mengerjakan tugas tersebut. Pengguna kemudian menekan tombol +Home. Tugas kini dikirim ke latar belakang dan tidak terlihat. Sekarang pengguna tidak memiliki cara untuk kembali +ke tugas tersebut, karena tidak dinyatakan dalam launcher aplikasi.

+ +

Untuk kasus-kasus di mana Anda tidak ingin pengguna bisa kembali ke aktivitas, atur dalam +<activity> + pada +{@code finishOnTaskLaunch} +elemen ke {@code "true"} (lihat Menghapus back-stack).

+ +

Informasi lebih jauh tentang cara menyatakan dan mengelola tugas dan aktivitas dalam +layar ikhtisar tersedia dalam +Layar Ikhtisar.

+ + diff --git a/docs/html-intl/intl/in/guide/index.jd b/docs/html-intl/intl/in/guide/index.jd new file mode 100644 index 0000000000000000000000000000000000000000..f24fab690ed98c407ddbf363f3ce0358d3df341c --- /dev/null +++ b/docs/html-intl/intl/in/guide/index.jd @@ -0,0 +1,74 @@ +page.title=Pengantar Android + +@jd:body + + + + +

Android menyediakan kerangka kerja aplikasi yang kaya dan memungkinkan Anda membangun aplikasi dan permainan +inovatif untuk perangkat seluler di lingkungan bahasa pemrograman Java. Dokumen yang tercantum di navigasi +sebelah kiri menyediakan detail tentang cara membangun aplikasi menggunakan berbagai API Android.

+ +

Jika Anda masih baru dengan pengembangan Android, Anda perlu memahami +konsep dasar berikut mengenai kerangka kerja aplikasi Android:

+ + +
+ +
+ +

Aplikasi menyediakan beberapa titik masuk

+ +

Aplikasi Android dibangun sebagai kombinasi beragam komponen yang bisa dipanggil +satu per satu. Misalnya, satu aktivitas individual menyediakan satu +layar untuk antarmuka pengguna, dan layanan yang secara terpisah melakukan +tugas di latar belakang.

+ +

Dari satu komponen Anda dapat memulai komponen lainnya menggunakan intent. Anda bahkan dapat memulai +satu komponen dalam aplikasi berbeda, seperti aktivitas dalam aplikasi peta untuk menampilkan alamat. Model ini +menyediakan beberapa titik masuk untuk aplikasi tunggal dan memungkinkan setiap aplikasi untuk berfungsi sebagai "default" +pengguna bagi tindakan yang dapat dipanggil aplikasi lain.

+ + +

Ketahui selengkapnya:

+ + +
+ + +
+ +

Aplikasi beradaptasi dengan perangkat berbeda

+ +

Android menyediakan kerangka kerja aplikasi adaptif yang memungkinkan Anda menyediakan sumber daya unik +bagi konfigurasi perangkat yang berbeda-beda. Misalnya, Anda bisa membuat berbagai file layout +XML untuk ukuran layar yang berbeda-beda dan sistem akan menentukan +layout yang akan diterapkan berdasarkan ukuran layar perangkat yang ada saat ini.

+ +

Anda dapat melakukan query ketersediaan fitur perangkat saat dijalankan (runtime) jika ada fitur aplikasi yang memerlukan +perangkat keras spesifik seperti kamera. Jika diperlukan, Anda juga bisa mendeklarasikan fitur yang dibutuhkan aplikasi +agar pasar aplikasi seperti Google Play Store tidak mengizinkan instalasi pada perangkat yang tidak +mendukung fitur itu.

+ + +

Ketahui selengkapnya:

+ + +
+ +
+ + + diff --git a/docs/html-intl/intl/in/guide/topics/manifest/manifest-intro.jd b/docs/html-intl/intl/in/guide/topics/manifest/manifest-intro.jd new file mode 100644 index 0000000000000000000000000000000000000000..6d6a3ad252db41075c066ae1caf082cf34b2a17c --- /dev/null +++ b/docs/html-intl/intl/in/guide/topics/manifest/manifest-intro.jd @@ -0,0 +1,517 @@ +page.title=Manifes Aplikasi +@jd:body + +
+
+ +

Dalam dokumen ini

+
    +
  1. Struktur File Manifes
  2. +
  3. Konvensi File +
  4. Fitur File +
      +
    1. Filter Intent
    2. +
    3. Ikon dan Label
    4. +
    5. Izin
    6. +
    7. Pustaka
    8. +
  5. +
+
+
+ +

+ Setiap aplikasi harus memiliki file AndroidManifest.xml (bernama persis seperti ini) di direktori akar. + File manifes + menyediakan informasi penting tentang aplikasi ke sistem Android, + informasi yang harus dimiliki sistem agar bisa menjalankan setiap kode +aplikasi. Di antaranya, manifes melakukan hal berikut ini: +

+ + + + +

Struktur File Manifes

+ +

+Diagram di bawah ini menampilkan struktur umum file manifes dan setiap +elemen yang bisa ditampungnya. Setiap elemen, bersama +atributnya, didokumentasikan secara lengkap dalam file terpisah. Untuk melihat +informasi terperinci tentang setiap elemen, klik nama elemen dalam diagram, +dalam daftar abjad elemen yang mengikuti diagram, atau penyebutan nama +elemen lainnya. +

+ +
+<?xml version="1.0" encoding="utf-8"?>
+
+<manifest>
+
+    <uses-permission />
+    <permission />
+    <permission-tree />
+    <permission-group />
+    <instrumentation />
+    <uses-sdk />
+    <uses-configuration />  
+    <uses-feature />  
+    <supports-screens />  
+    <compatible-screens />  
+    <supports-gl-texture />  
+
+    <application>
+
+        <activity>
+            <intent-filter>
+                <action />
+                <category />
+                <data />
+            </intent-filter>
+            <meta-data />
+        </activity>
+
+        <activity-alias>
+            <intent-filter> . . . </intent-filter>
+            <meta-data />
+        </activity-alias>
+
+        <service>
+            <intent-filter> . . . </intent-filter>
+            <meta-data/>
+        </service>
+
+        <receiver>
+            <intent-filter> . . . </intent-filter>
+            <meta-data />
+        </receiver>
+
+        <provider>
+            <grant-uri-permission />
+            <meta-data />
+            <path-permission />
+        </provider>
+
+        <uses-library />
+
+    </application>
+
+</manifest>
+
+ +

+Semua elemen yang bisa muncul dalam file manifes tercantum di bawah ini +dalam urutan abjad. Ini adalah satu-satunya elemen legal; Anda tidak bisa +menambahkan elemen atau atribut sendiri. +

+ +

+<action> +
<activity> +
<activity-alias> +
<application> +
<category> +
<data> +
<grant-uri-permission> +
<instrumentation> +
<intent-filter> +
<manifest> +
<meta-data> +
<permission> +
<permission-group> +
<permission-tree> +
<provider> +
<receiver> +
<service> +
<supports-screens> +
<uses-configuration> +
<uses-feature> +
<uses-library> +
<uses-permission> +
<uses-sdk> +

+ + + + +

Konvensi File

+ +

+Sebagian konvensi dan aturan berlaku secara umum untuk semua elemen +dan atribut di manifes: +

+ +
+
Elemen
+
Hanya elemen +<manifest> dan +<application> +yang diwajibkan, masing-masing harus ada dan hanya boleh terjadi sekali. +Umumnya elemen lain bisa terjadi berkali-kali atau sama sekali tidak terjadi — meskipun +setidaknya sebagian dari elemen itu harus ada untuk agar manifes mencapai sesuatu yang +berarti. + +

+Jika elemen tidak berisi apa pun, berarti elemen itu berisi elemen lain. +Semua nilai diatur melalui atribut, bukan sebagai data karakter dalam elemen. +

+ +

+Elemen yang sama tingkatan umumnya tidak diurutkan. Misalnya, elemen +<activity>, +<provider>, dan +<service> +bisa dicampur dalam urutan apa pun. (Elemen +<activity-alias> + merupakan eksepsi untuk aturan ini: Elemen ini harus mengikuti +<activity> +ini aliasnya.) +

+ +
Atribut
+
Secara formal, semua atribut opsional. Akan tetapi, ada sebagian +yang harus ditetapkan agar elemen bisa mencapai tujuannya. Gunakan +dokumentasi sebagai panduan. Bagi atribut yang benar-benar opsional, ini menyebutkan +nilai default atau menyatakan apa yang terjadi jika tidak ada spesifikasi. + +

Selain untuk beberapa atribut elemen akar +<manifest>, + semua nama atribut dimulai dengan awalan {@code android:} — +misalnya, {@code android:alwaysRetainTaskState}. Karena awalan ini universal, dokumentasi umumnya meniadakannya saat mengacu atribut +dengan nama. +

+ +
Mendeklarasikan nama kelas
+
Banyak elemen berhubungan dengan objek Java, termasuk elemen +aplikasi itu sendiri (elemen +<application> +) dan aktivitas komponen — utamanya +(<activity>), +layanan +(<service>), +penerima siaran +(<receiver>), +dan penyedia konten +(<provider>). + +

+Jika mendefinisikan subkelas, seperti yang selalu Anda definisikan untuk kelas komponen +({@link android.app.Activity}, {@link android.app.Service}, +{@link android.content.BroadcastReceiver}, dan {@link android.content.ContentProvider}), +subkelas dideklarasikan melalui atribut {@code name}. Nama harus menyertakan tujuan +paket lengkap. +Misalnya, subkelas {@link android.app.Service} mungkin dideklarasikan sebagai berikut: +

+ +
<manifest . . . >
+    <application . . . >
+        <service android:name="com.example.project.SecretService" . . . >
+            . . .
+        </service>
+        . . .
+    </application>
+</manifest>
+ +

+Akan tetapi, sebagai shorthand, jika karakter pertama string adalah titik, +string akan ditambahkan ke nama paket aplikasi (seperti yang ditetapkan dalam elemen +<manifest> + melalui atribut +package +). Penetapan berikut sama dengan di atas: +

+ +
<manifest package="com.example.project" . . . >
+    <application . . . >
+        <service android:name=".SecretService" . . . >
+            . . .
+        </service>
+        . . .
+    </application>
+</manifest>
+ +

+Saat memulai komponen, Android akan membuat instance subkelas yang diberi nama. +Jika subkelas tidak ditetapkan, maka akak dibuat instance kelas dasar. +

+ +
Banyak nilai
+
Jika lebih dari satu nilai yang dapat ditetapkan, elemen ini hampir selalu +diulangi, bukan menampilkan daftar banyak nilai dalam satu elemen. +Misalnya, filter intent dapat mencantumkan beberapa tindakan: + +
<intent-filter . . . >
+    <action android:name="android.intent.action.EDIT" />
+    <action android:name="android.intent.action.INSERT" />
+    <action android:name="android.intent.action.DELETE" />
+    . . .
+</intent-filter>
+ +
Nilai sumber daya
+
Beberapa atribut memiliki nilai yang bisa ditampilkan kepada pengguna — misalnya +, label dan ikon aktivitas. Nilai atribut ini +harus dilokalkan dan karenanya ditetapkan dari sumber daya atau tema. Nilai sumber +daya dinyatakan dalam format berikut,

+ +

{@code @[package:]type:name}

+ +

+dalam hal ini nama package boleh dihilangkan jika sumber daya ada dalam paket yang sama dengan +dengan aplikasi, type adalah tipe sumber daya — seperti "string" atau +"drawable" — dan name adalah nama yang mengidentifikasi sumber daya tertentu. +Misalnya: +

+ +
<activity android:icon="@drawable/smallPic" . . . >
+ +

+Nilai tema diekspresikan dengan cara yang sama, namun dengan awal '{@code ?}' +dan bukan '{@code @}': +

+ +

{@code ?[package:]type:name} +

+ +
Nilai-nilai string
+
Bila nilai atribut adalah string, dua garis miring kiri ('{@code \\}') +harus digunakan untuk meninggalkan karakter — misalnya, '{@code \\n}' untuk +baris baru atau '{@code \\uxxxx}' untuk karakter Unicode.
+
+ + +

Fitur File

+ +

+Bagian berikut menjelaskan cara menerapkan sebagian fitur Android +dalam file manifest. +

+ + +

Filter Intent

+ +

+Komponen inti dari aplikasi (aktivitasnya, layanannya, dan penerima +siaran) diaktifkan oleh intent. Intent adalah +sekumpulan informasi (objek {@link android.content.Intent}) yang menjelaskan +tindakan yang diinginkan — termasuk data yang akan ditindaklanjuti, kategori +komponen yang harus melakukan tindakan, dan petunjuk terkait lainnya. +Android mencari komponen yang sesuai untuk merespons intent, meluncurkan +instance komponen baru jika diperlukan, dan meneruskannya ke +objek Intent. +

+ +

+Komponen mengiklankan kemampuannya — jenis intent yang bisa diresponsnya + — melalui filter intent. Karena sistem Android +harus mempelajari intent yang dapat ditangani komponen sebelum meluncurkan komponen, +filter intent ditetapkan dalam manifes sebagai elemen +<intent-filter> +. Sebuah komponen dapat memiliki filter dalam jumlah berapa saja, masing-masing menjelaskan +kemampuan yang berbeda. +

+ +

+Intent yang secara eksplisit menamai komponen target akan mengaktifkan komponen itu; +filter tidak berperan. Namun intent yang tidak menetapkan target +dengan nama dapat mengaktifkan komponen hanya jika dapat melewati salah satu filter +komponen. +

+ +

+Untuk informasi tentang cara objek Intent diuji terhadap filter intent, +lihat dokumen terpisah, +Intent +dan Filter Intent. +

+ + +

Ikon dan Label

+ +

+Sejumlah elemen memiliki atribut {@code icon} dan {@code label} untuk +ikon kecil dan label teks yang bisa ditampilkan kepada pengguna. Sebagian ada juga yang memiliki atribut +{@code description}untuk teks penjelasan yang lebih panjang yang juga bisa +ditampilkan pada layar. Misalnya, elemen +<permission> + memiliki ketiga atribut ini, jadi saat pengguna ditanya apakah akan +memberi izin bagi aplikasi yang memintanya, ikon yang mewakili +izin, nama izin, dan keterangan yang +mengikutinya bisa ditampilkan kepada pengguna. +

+ +

+Dalam setiap kasus, ikon dan label yang ditetapkan dalam elemen yang memuatnya menjadi +{@code icon} default dan pengaturan {@code label} untuk semua subelemen kontainer ini. +Karena itu, ikon dan label yang ditetapkan dalam elemen +<application> +adalah ikon dan label default untuk setiap komponen aplikasi. +Demikian pula, ikon dan label yang ditetapkan untuk komponen — misalnya, elemen +<activity> +— adalah pengaturan default untuk setiap elemen komponen +<intent-filter> +. Jika elemen +<application> +menetapkan label, namun suatu aktivitas dan filter intent-nya tidak menetapkan label, +maka label aplikasi akan dianggap sama-sama sebagai label aktvitas dan +filter intent. +

+ +

+Ikon dan label yang ditetapkan untuk filter intent digunakan untuk mewakili komponen +kapan saja komponen ditampilkan kepada pengguna saat memenuhi fungsi yang +diiklankan oleh filter. Misalnya, filter dengan pengaturan +"{@code android.intent.action.MAIN}" dan +"{@code android.intent.category.LAUNCHER}" mengiklankan aktivitas +sebagai aktivitas yang memulai aplikasi—, yaitu +sebagai salah satu aktivitas yang harus ditampilkan dalam launcher aplikasi. Ikon dan label yang +diatur dalam filter karenanya adalah ikon dan label yang ditampilkan dalam launcher. +

+ + +

Izin

+ +

+Sebuah izin adalah pembatasan yang membatasi akses ke bagian +kode atau ke data pada perangkat. Pembatasan diberlakukan untuk melindungi data dan kode +penting yang bisa disalahgunakan untuk mengganggu atau merusak pengalaman pengguna. +

+ +

+Setiap izin diidentifikasi melalui label yang unik. Sering kali, label menunjukkan +tindakan yang dibatasi. Misalnya, berikut ini adalah beberapa izin yang didefinisikan +oleh Android: +

+ +

{@code android.permission.CALL_EMERGENCY_NUMBERS} +
{@code android.permission.READ_OWNER_DATA} +
{@code android.permission.SET_WALLPAPER} +
{@code android.permission.DEVICE_POWER}

+ +

+Sebuah fitur bisa dilindungi paling banyak oleh satu izin. +

+ +

+Jika aplikasi memerlukan akses ke fitur yang dilindungi oleh izin, +aplikasi harus mendeklarasikan bahwa aplikasi memerlukan izin itu dengan elemen +<uses-permission> + dalam manifes. Kemudian, bila aplikasi telah diinstal pada +perangkat, installer akan menentukan apakah izin yang diminta akan diberikan atau tidak +dengan memeriksa otoritas yang menandatangani +sertifikat aplikasi dan, dalam beberapa kasus, bertanya pada pengguna. +Jika izin diberikan, aplikasi tersebut bisa menggunakan +fitur yang dilindungi. Jika tidak, upaya aplikasi untuk mengakses fitur tersebut akan gagal +tanpa ada pemberitahuan apa pun kepada pengguna. +

+ +

+Aplikasi juga bisa melindungi komponennya sendiri (aktivitas, layanan, +penerima siaran, dan penyedia konten) dengan izin. Aplikasi bisa menerapkan +izin mana pun yang didefinisikan oleh Android (tercantum dalam +{@link android.Manifest.permission android.Manifest.permission}) atau dideklarasikan +oleh aplikasi lain. Atau aplikasi bisa mendefinisikannya sendiri. Izin baru dideklarasikan +dengan elemen +<permission> +. Misalnya, aktivitas dapat dilindungi sebagai berikut: +

+ +
+<manifest . . . >
+    <permission android:name="com.example.project.DEBIT_ACCT" . . . />
+    <uses-permission android:name="com.example.project.DEBIT_ACCT" />
+    . . .
+    <application . . .>
+        <activity android:name="com.example.project.FreneticActivity"
+                  android:permission="com.example.project.DEBIT_ACCT"
+                  . . . >
+            . . .
+        </activity>
+    </application>
+</manifest>
+
+ +

+Perhatikan, dalam contoh ini izin {@code DEBIT_ACCT} tidak hanya +dideklarasikan dengan elemen +<permission> +, penggunaannya juga diminta dengan elemen +<uses-permission> +. Penggunaannya harus diminta agar komponen +aplikasi lainnya bisa menjalankan aktivitas yang dilindungi, meskipun perlindungan itu +diberlakukan oleh aplikasi itu sendiri. +

+ +

+Dalam contoh yang sama, jika atribut {@code permission} ditetapkan +untuk izin yang dideklarasikan di tempat lain +lain (seperti {@code android.permission.CALL_EMERGENCY_NUMBERS}, maka atribut +tidak perlu mendeklarasikannya lagi dengan elemen +<permission> +. Akan tetapi, penggunaannya masih perlu dengan +<uses-permission>. +

+ +

+Elemen +<permission-tree> +mendeklarasikan namespace untuk grup izin yang akan didefinisikan dalam +kode. Dan +<permission-group> +mendefinisikan label untuk seperangkat izin (yang sama-sama dideklarasikan dalam manifes dengan elemen +<permission> +dan yang dideklarasikan di tempat lain). Ini hanya memengaruhi cara izin +dikelompokkan saat ditampilkan kepada pengguna. Elemen +<permission-group> + tidak menetapkan izin mana dimiliki grup; +elemen hanya memberi nama grup. Izin ditempatkan dalam grup +dengan memberikan nama grup ke elemen +<permission> + melalui atribut +permissionGroup +. +

+ + +

Pustaka

+ +

+Setiap aplikasi ditautkan dengan pustaka default Android, yang +menyertakan paket dasar untuk membangun aplikasi (dengan kelas umum +seperti Activity, Service, Intent, View, Button, Application, ContentProvider, +dan sebagainya). +

+ +

+Akan tetapi, sebagian paket berada dalam pustakanya sendiri. Jika aplikasi Anda +menggunakan kode salah satu paket ini, aplikasi secara eksplisit meminta untuk ditautkan dengan +paket tersebut. Manifes harus berisi elemen +<uses-library> yang +terpisah untuk menamai setiap pustaka. (Nama pustaka bisa ditemukan +dalam dokumentasi paket.) +

diff --git a/docs/html-intl/intl/in/guide/topics/providers/calendar-provider.jd b/docs/html-intl/intl/in/guide/topics/providers/calendar-provider.jd new file mode 100644 index 0000000000000000000000000000000000000000..76bc9915cbd7658f9af80cdb9a94220cfad93e3c --- /dev/null +++ b/docs/html-intl/intl/in/guide/topics/providers/calendar-provider.jd @@ -0,0 +1,1184 @@ +page.title=Penyedia Kalender +@jd:body + +
+
+

Dalam dokumen ini

+
    +
  1. Dasar-Dasar
  2. +
  3. Izin Pengguna
  4. +
  5. Tabel kalender +
      +
    1. Membuat query kalender
    2. +
    3. Memodifikasi kalender
    4. +
    5. Menyisipkan kalender
    6. +
    +
  6. +
  7. Tabel Events +
      +
    1. Menambahkan Kejadian
    2. +
    3. Memperbarui Kejadian
    4. +
    5. Menghapus Kejadian
    6. +
    +
  8. +
  9. Tabel peserta +
      +
    1. Menambahkan Peserta
    2. +
    +
  10. +
  11. Tabel pengingat +
      +
    1. Menambahkan Pengingat
    2. +
    +
  12. +
  13. Tabel Instances +
      +
    1. Membuat query tabel Instance
    2. +
  14. +
  15. Intent Kalender +
      +
    1. Menggunakan intent untuk menyisipkan kejadian
    2. +
    3. Menggunakan intent untuk mengedit kejadian
    4. +
    5. Menggunakan intent untuk menampilkan data kalender
    6. +
    +
  16. + +
  17. Adaptor Sinkronisasi
  18. +
+ +

Kelas-kelas utama

+
    +
  1. {@link android.provider.CalendarContract.Calendars}
  2. +
  3. {@link android.provider.CalendarContract.Events}
  4. +
  5. {@link android.provider.CalendarContract.Attendees}
  6. +
  7. {@link android.provider.CalendarContract.Reminders}
  8. +
+
+
+ +

Penyedia Kalender adalah repository untuk kejadian kalender seorang pengguna. API +Penyedia Kalender memungkinkan Anda melakukan query, menyisipkan, memperbarui, dan menghapus +pada kalender, kejadian, peserta, pengingat, dan seterusnya.

+ + +

API Penyedia Kalender bisa digunakan oleh aplikasi dan adaptor sinkronisasi. Aturannya +bervariasi menurut tipe program yang membuat panggilan. Dokumen ini +terutama berfokus pada penggunaan API Penyedia Kalender sebagai sebuah aplikasi. Untuk +pembahasan ragam adaptor sinkronisasi, lihat +Adaptor Sinkronisasi.

+ + +

Biasanya, untuk membaca atau menulis data kalender, manifes aplikasi harus +berisi izin yang sesuai, yang dijelaskan dalam Izin +Pengguna. Untuk mempermudah dilakukannya operasi umum, +Penyedia Kalender menyediakan satu set intent, seperti dijelaskan dalam Intent +Kalender. Semua intent ini membawa pengguna ke aplikasi Kalender untuk menyisipkan, menampilkan, +dan mengedit kejadian. Pengguna berinteraksi dengan aplikasi Kalender kemudian +kembali ke aplikasi semula. Jadi, aplikasi Anda tidak perlu meminta izin, +juga tidak perlu menyediakan antarmuka pengguna untuk menampilkan atau membuat kejadian.

+ +

Dasar-Dasar

+ +

Penyedia konten menyimpan data dan menjadikannya bisa diakses oleh +aplikasi. Penyedia konten yang ditawarkan oleh platform Android (termasuk Penyedia Kalender) biasanya mengekspos data sebagai satu set tabel berdasarkan +model database relasional, dengan tiap baris berupa record dan tiap kolom berupa data +yang memiliki tipe dan arti tertentu. Melalui API Penyedia Kalender, aplikasi +dan adaptor sinkronisasi bisa mendapatkan akses baca/tulis ke tabel-tabel database yang menyimpan +data kalender seorang pengguna.

+ +

Setiap penyedia konten membuka sebuah URI publik (yang dibungkus sebagai objek +{@link android.net.Uri} +) yang mengidentifikasikan set datanya secara unik. Penyedia konten yang mengontrol + beberapa set data (beberapa tabel) mengekspos URI terpisah untuk tiap set. Semua +URI untuk penyedia diawali dengan string "content://". String ini +mengidentifikasi data sebagai dikontrol oleh penyedia konten. Penyedia Kalender +mendefinisikan konstanta untuk URI masing-masing kelas (tabel). URI ini +memiliki format <class>.CONTENT_URI. Misalnya, +{@link android.provider.CalendarContract.Events#CONTENT_URI Events.CONTENT_URI}.

+ +

Gambar 1 menampilkan representasi grafis model data Penyedia Kalender. Gambar ini menampilkan +tabel dan bidang utama yang saling berkaitan.

+ +Calendar Provider Data Model +

Gambar 1. Model data Penyedia Kalender.

+ +

Seorang pengguna bisa memiliki beberapa kalender, dan kalender yang berbeda bisa dikaitkan dengan tipe akun yang berbeda (Google Calendar, Exchange, dan seterusnya).

+ +

{@link android.provider.CalendarContract} mendefinisikan model data dari informasi yang terkait dengan kalender dan kejadian. Data ini disimpan di sejumlah tabel, yang dicantumkan di bawah ini.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tabel (Kelas)Keterangan

{@link android.provider.CalendarContract.Calendars}

Tabel ini menyimpan +informasi khusus kalender. Tiap baris dalam tabel ini berisi data untuk +satu kalender, seperti nama, warna, informasi sinkronisasi, dan seterusnya.
{@link android.provider.CalendarContract.Events}Tabel ini menyimpan +informasi khusus kejadian. Tiap baris dalam tabel ini berisi informasi untuk satu +kejadian—misalnya, judul kejadian, lokasi, waktu mulai, waktu +selesai, dan seterusnya. Kejadian bisa terjadi satu kali atau bisa berulang beberapa kali. Peserta, +pengingat, dan properti perluasan disimpan dalam tabel terpisah. +Masing-masing memiliki sebuah {@link android.provider.CalendarContract.AttendeesColumns#EVENT_ID} +yang mengacu {@link android.provider.BaseColumns#_ID} dalam tabel Events.
{@link android.provider.CalendarContract.Instances}Tabel ini menyimpan +waktu mulai dan waktu selesai setiap bentuk kejadian. Tiap baris dalam tabel ini +mewakili satu bentuk kejadian. Untuk kejadian satu kali ada pemetaan 1:1 +antara instance dan kejadian. Untuk kejadian berulang, beberapa baris akan dibuat +secara otomatis yang sesuai dengan beberapa kejadian itu.
{@link android.provider.CalendarContract.Attendees}Tabel ini menyimpan +informasi peserta (tamu) kejadian. Tiap baris mewakili satu tamu +kejadian. Ini menetapkan tipe tamu dan respons kehadiran tamu +untuk kejadian.
{@link android.provider.CalendarContract.Reminders}Tabel ini menyimpan +data peringatan/pemberitahuan. Tiap baris mewakili satu peringatan untuk sebuah kejadian. Sebuah +kejadian bisa memiliki beberapa pengingat. Jumlah maksimum pengingat per kejadian +ditetapkan dalam +{@link android.provider.CalendarContract.CalendarColumns#MAX_REMINDERS}, +yang diatur oleh adaptor sinkronisasi yang +memiliki kalender yang diberikan. Pengingat ditetapkan dalam menit sebelum kejadian +dan memiliki metode yang menentukan cara pengguna akan diperingatkan.
+ +

API Penyedia Kalender didesain agar luwes dan tangguh. Sementara itu +, Anda perlu memberikan pengalaman pengguna akhir yang baik dan +melindungi integritas kalender dan datanya. Untuk mencapainya, berikut ini adalah +beberapa hal yang harus diingat saat menggunakan API ini:

+ + + + +

Izin Pengguna

+ +

Untuk membaca data kalender, aplikasi harus menyertakan izin {@link +android.Manifest.permission#READ_CALENDAR} dalam file manifesnya. File +harus menyertakan izin {@link android.Manifest.permission#WRITE_CALENDAR} +untuk menghapus, menyisipkan, atau memperbarui data kalender:

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"...>
+    <uses-sdk android:minSdkVersion="14" />
+    <uses-permission android:name="android.permission.READ_CALENDAR" />
+    <uses-permission android:name="android.permission.WRITE_CALENDAR" />
+    ...
+</manifest>
+
+ + +

Tabel Kalender

+ +

Tabel {@link android.provider.CalendarContract.Calendars} berisi data +untuk tiap kalender. Kolom-kolom +berikut ini bisa ditulisi oleh aplikasi maupun adaptor sinkronisasi. +Untuk mengetahui daftar lengkap bidang-bidang yang didukung, lihat +acuan {@link android.provider.CalendarContract.Calendars}.

+ + + + + + + + + + + + + + + + + + + + + + + + + +
KonstantaKeterangan
{@link android.provider.CalendarContract.Calendars#NAME}Nama kalender.
{@link android.provider.CalendarContract.Calendars#CALENDAR_DISPLAY_NAME}Nama kalender ini yang ditampilkan kepada pengguna.
{@link android.provider.CalendarContract.Calendars#VISIBLE}Sebuah boolean yang menunjukkan apakah kalender dipilih untuk ditampilkan. Nilai +0 menunjukkan bahwa kejadian yang terkait dengan kalender ini tidak boleh +ditampilkan. Nilai 1 menunjukkan bahwa kejadian yang terkait dengan kalender ini harus +ditampilkan. Nilai ini memengaruhi pembuatan baris dalam tabel {@link +android.provider.CalendarContract.Instances}.
{@link android.provider.CalendarContract.CalendarColumns#SYNC_EVENTS}Sebuah boolean yang menunjukkan apakah kalender harus disinkronkan dan apakah +kejadiannya harus disimpan pada perangkat. Nilai 0 berarti jangan menyinkronkan kalender ini atau +simpan kejadiannya pada perangkat. Nilai 1 berarti menyinkronkan kejadian untuk kalender ini +dan simpan kejadiannya pada perangkat.
+ +

Membuat query kalender

+ +

Berikut ini adalah contoh yang menampilkan cara mendapatkan kalender yang dimiliki oleh +pengguna tertentu. Untuk memudahkan, dalam contoh ini, operasi query ditampilkan dalam +thread antarmuka pengguna ("thread utama"). Dalam praktiknya, hal ini harus dilakukan dalam +thread asinkron, sebagai ganti pada thread utama. Untuk diskusi selengkapnya, lihat +Loader. Jika Anda tidak sekadar +membaca data melainkan memodifikasinya, lihat {@link android.content.AsyncQueryHandler}. +

+ + +
+// Projection array. Creating indices for this array instead of doing
+// dynamic lookups improves performance.
+public static final String[] EVENT_PROJECTION = new String[] {
+    Calendars._ID,                           // 0
+    Calendars.ACCOUNT_NAME,                  // 1
+    Calendars.CALENDAR_DISPLAY_NAME,         // 2
+    Calendars.OWNER_ACCOUNT                  // 3
+};
+  
+// The indices for the projection array above.
+private static final int PROJECTION_ID_INDEX = 0;
+private static final int PROJECTION_ACCOUNT_NAME_INDEX = 1;
+private static final int PROJECTION_DISPLAY_NAME_INDEX = 2;
+private static final int PROJECTION_OWNER_ACCOUNT_INDEX = 3;
+ + + + + +

Di bagian berikutnya pada contoh ini, Anda akan membuat query. Pemilihan +akan menetapkan kriteria untuk query. Dalam contoh ini, query mencari +kalender yang memiliki ACCOUNT_NAME +"sampleuser@google.com", ACCOUNT_TYPE +"com.google", dan OWNER_ACCOUNT +"sampleuser@google.com". Jika Anda ingin melihat semua kalender yang +telah ditampilkan pengguna, bukan hanya kalender yang dimiliki pengguna, hilangkan OWNER_ACCOUNT. +Query tersebut akan menghasilkan objek {@link android.database.Cursor} +yang bisa Anda gunakan untuk menyusuri set hasil yang dikembalikan oleh +query database. Untuk diskusi selengkapnya tentang penggunaan query dalam penyedia konten, +lihat Penyedia Kalender.

+ + +
// Run query
+Cursor cur = null;
+ContentResolver cr = getContentResolver();
+Uri uri = Calendars.CONTENT_URI;   
+String selection = "((" + Calendars.ACCOUNT_NAME + " = ?) AND (" 
+                        + Calendars.ACCOUNT_TYPE + " = ?) AND ("
+                        + Calendars.OWNER_ACCOUNT + " = ?))";
+String[] selectionArgs = new String[] {"sampleuser@gmail.com", "com.google",
+        "sampleuser@gmail.com"}; 
+// Submit the query and get a Cursor object back. 
+cur = cr.query(uri, EVENT_PROJECTION, selection, selectionArgs, null);
+ +

Bagian berikutnya ini menggunakan kursor untuk merunut set hasil. Bagian ini menggunakan +konstanta yang disiapkan pada awal contoh ini untuk menghasilkan nilai-nilai +bagi tiap bidang.

+ +
// Use the cursor to step through the returned records
+while (cur.moveToNext()) {
+    long calID = 0;
+    String displayName = null;
+    String accountName = null;
+    String ownerName = null;
+      
+    // Get the field values
+    calID = cur.getLong(PROJECTION_ID_INDEX);
+    displayName = cur.getString(PROJECTION_DISPLAY_NAME_INDEX);
+    accountName = cur.getString(PROJECTION_ACCOUNT_NAME_INDEX);
+    ownerName = cur.getString(PROJECTION_OWNER_ACCOUNT_INDEX);
+              
+    // Do something with the values...
+
+   ...
+}
+
+ +

Memodifikasi kalender

+ +

Untuk melakukan pembaruan kalender, Anda bisa menyediakan {@link +android.provider.BaseColumns#_ID} kalender itu baik sebagai ID yang ditambahkan ke +URI + +({@link android.content.ContentUris#withAppendedId(android.net.Uri,long) withAppendedId()}) +atau sebagai item pemilihan pertama. Pemilihan +harus diawali dengan "_id=?", dan +selectionArg pertama harus berupa {@link +android.provider.BaseColumns#_ID} kalender. +Anda juga bisa melakukan pembaruan dengan menuliskan kode ID dalam URI. Contoh ini mengubah +nama tampilan kalender dengan pendekatan +({@link android.content.ContentUris#withAppendedId(android.net.Uri,long) withAppendedId()}) +:

+ +
private static final String DEBUG_TAG = "MyActivity";
+...
+long calID = 2;
+ContentValues values = new ContentValues();
+// The new display name for the calendar
+values.put(Calendars.CALENDAR_DISPLAY_NAME, "Trevor's Calendar");
+Uri updateUri = ContentUris.withAppendedId(Calendars.CONTENT_URI, calID);
+int rows = getContentResolver().update(updateUri, values, null, null);
+Log.i(DEBUG_TAG, "Rows updated: " + rows);
+ +

Menyisipkan kalender

+ +

Kalender didesain untuk dikelola terutama oleh sebuah adaptor sinkronisasi, sehingga Anda +hanya boleh menyisipkan kalender baru sebagai adaptor sinkronisasi. Biasanya, +aplikasi hanya bisa membuat perubahan semu pada kalender, misalnya mengubah nama tampilan. Jika +perlu membuat sebuah kalender lokal, aplikasi bisa melakukannya dengan melakukan +penyisipan kalender sebagai adaptor sinkronisasi, menggunakan {@link +android.provider.CalendarContract.SyncColumns#ACCOUNT_TYPE} dari {@link +android.provider.CalendarContract#ACCOUNT_TYPE_LOCAL}. +{@link android.provider.CalendarContract#ACCOUNT_TYPE_LOCAL} +adalah sebuah tipe akun khusus untuk kalender yang tidak +terkait dengan akun perangkat. Kalender tipe ini tidak disinkronkan dengan server. Untuk +diskusi tentang adaptor sinkronisasi, lihat Adaptor Sinkronisasi.

+ +

Tabel Events

+ +

Tabel {@link android.provider.CalendarContract.Events} berisi detail +untuk tiap kejadian. Untuk menambah, memperbarui, atau menghapus kejadian, aplikasi harus +menyertakan izin {@link android.Manifest.permission#WRITE_CALENDAR} dalam +file manifesnya.

+ +

Kolom-kolom Events berikut ini bisa ditulis oleh aplikasi maupun +adaptor sinkronisasi. Untuk mengetahui daftar lengkap bidang-bidang yang didukung, lihat acuan {@link +android.provider.CalendarContract.Events}.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
KonstantaKeterangan
{@link android.provider.CalendarContract.EventsColumns#CALENDAR_ID}{@link android.provider.BaseColumns#_ID} kalender yang dimiliki kejadian.
{@link android.provider.CalendarContract.EventsColumns#ORGANIZER}Email pengatur (pemilik) kejadian.
{@link android.provider.CalendarContract.EventsColumns#TITLE}Judul kejadian.
{@link android.provider.CalendarContract.EventsColumns#EVENT_LOCATION}Tempat kejadian.
{@link android.provider.CalendarContract.EventsColumns#DESCRIPTION}Keterangan kejadian.
{@link android.provider.CalendarContract.EventsColumns#DTSTART}Waktu mulai kejadian dalam milidetik UTC sejak waktu patokan.
{@link android.provider.CalendarContract.EventsColumns#DTEND}Waktu selesai kejadian dalam milidetik UTC sejak waktu patokan.
{@link android.provider.CalendarContract.EventsColumns#EVENT_TIMEZONE}Zona waktu kejadian.
{@link android.provider.CalendarContract.EventsColumns#EVENT_END_TIMEZONE}Zona waktu untuk waktu selesai kejadian.
{@link android.provider.CalendarContract.EventsColumns#DURATION}Durasi kejadian dalam format RFC5545. +Misalnya, nilai "PT1H" menyatakan bahwa kejadian +akan berlangsung satu jam, dan nilai "P2W" menunjukkan +durasi 2 minggu.
{@link android.provider.CalendarContract.EventsColumns#ALL_DAY}Nilai 1 menunjukkan kejadian ini memakan waktu sehari penuh, seperti yang didefinisikan oleh +zona waktu lokal. Nilai 0 menunjukkan kejadian adalah kejadian biasa yang mungkin dimulai +dan selesai pada sembarang waktu selama suatu hari.
{@link android.provider.CalendarContract.EventsColumns#RRULE}Aturan perulangan untuk format kejadian. Misalnya, +"FREQ=WEEKLY;COUNT=10;WKST=SU". Anda bisa menemukan +contoh selengkapnya di sini.
{@link android.provider.CalendarContract.EventsColumns#RDATE}Tanggal perulangan kejadian. + Anda biasanya menggunakan {@link android.provider.CalendarContract.EventsColumns#RDATE} + bersama dengan {@link android.provider.CalendarContract.EventsColumns#RRULE} + untuk mendefinisikan satu set agregat +kejadian berulang. Untuk diskusi selengkapnya, lihat Spesifikasi RFC5545.
{@link android.provider.CalendarContract.EventsColumns#AVAILABILITY}Jika kejadian ini dihitung sebagai waktu sibuk atau waktu bebas yang bisa +dijadwalkan.
{@link android.provider.CalendarContract.EventsColumns#GUESTS_CAN_MODIFY}Apakah tamu bisa memodifikasi kejadian atau tidak.
{@link android.provider.CalendarContract.EventsColumns#GUESTS_CAN_INVITE_OTHERS}Apakah tamu bisa mengundang tamu lain atau tidak.
{@link android.provider.CalendarContract.EventsColumns#GUESTS_CAN_SEE_GUESTS}Apakah tamu bisa membaca daftar peserta atau tidak.
+ +

Menambahkan Kejadian

+ +

Bila aplikasi Anda menyisipkan kejadian baru, sebaiknya Anda menggunakan +Intent {@link android.content.Intent#ACTION_INSERT INSERT}, seperti dijelaskan dalam Menggunakan intent untuk menyisipkan kejadian. Akan tetapi, jika +perlu, Anda bisa menyisipkan kejadian secara langsung. Bagian ini menjelaskan +caranya.

+ + +

Berikut ini adalah aturan untuk menyisipkan kejadian baru:

+ + +

Berikut ini adalah contoh penyisipan kejadian. Penyisipan ini dilakukan dalam thread UI +demi kemudahan. Dalam praktiknya, penyisipan dan pembaruan harus dilakukan di +thread asinkron untuk memindahkan tindakan ke dalam thread latar belakang. Untuk +informasi selengkapnya, lihat {@link android.content.AsyncQueryHandler}.

+ + +
+long calID = 3;
+long startMillis = 0; 
+long endMillis = 0;     
+Calendar beginTime = Calendar.getInstance();
+beginTime.set(2012, 9, 14, 7, 30);
+startMillis = beginTime.getTimeInMillis();
+Calendar endTime = Calendar.getInstance();
+endTime.set(2012, 9, 14, 8, 45);
+endMillis = endTime.getTimeInMillis();
+...
+
+ContentResolver cr = getContentResolver();
+ContentValues values = new ContentValues();
+values.put(Events.DTSTART, startMillis);
+values.put(Events.DTEND, endMillis);
+values.put(Events.TITLE, "Jazzercise");
+values.put(Events.DESCRIPTION, "Group workout");
+values.put(Events.CALENDAR_ID, calID);
+values.put(Events.EVENT_TIMEZONE, "America/Los_Angeles");
+Uri uri = cr.insert(Events.CONTENT_URI, values);
+
+// get the event ID that is the last element in the Uri
+long eventID = Long.parseLong(uri.getLastPathSegment());
+// 
+// ... do something with event ID
+//
+//
+ +

Catatan: Perhatikan cara contoh ini menangkap ID kejadian +setelah kejadian dibuat. Inilah cara termudah untuk mendapatkan ID kejadian. Anda akan sering +memerlukan ID kejadian untuk melakukan operasi kalender lainnya—misalnya, untuk menambahkan +peserta atau pengingat ke kejadian.

+ + +

Memperbarui Kejadian

+ +

Bila aplikasi Anda ingin memperbolehkan pengguna mengedit kejadian, sebaiknya +gunakan Intent {@link android.content.Intent#ACTION_EDIT EDIT}, seperti +dijelaskan dalam Menggunakan intent untuk mengedit kejadian. +Akan tetapi, jika perlu, Anda bisa mengedit kejadian secara langsung. Untuk melakukan pembaruan +suatu kejadian, Anda bisa memberikan _ID +kejadian itu sebagai ID yang ditambahkan ke URI ({@link +android.content.ContentUris#withAppendedId(android.net.Uri,long) withAppendedId()}) +atau sebagai item pemilihan pertama. +Pemilihan harus dimulai dengan "_id=?", dan +selectionArg yang pertama harus berupa _ID kejadian. Anda juga bisa +melakukan pembaruan dengan menggunakan pemilihan tanpa ID. Berikut ini adalah contoh pembaruan +kejadian. Contoh ini mengubah judul kejadian dengan pendekatan +{@link android.content.ContentUris#withAppendedId(android.net.Uri,long) withAppendedId()} +:

+ + +
private static final String DEBUG_TAG = "MyActivity";
+...
+long eventID = 188;
+...
+ContentResolver cr = getContentResolver();
+ContentValues values = new ContentValues();
+Uri updateUri = null;
+// The new title for the event
+values.put(Events.TITLE, "Kickboxing"); 
+updateUri = ContentUris.withAppendedId(Events.CONTENT_URI, eventID);
+int rows = getContentResolver().update(updateUri, values, null, null);
+Log.i(DEBUG_TAG, "Rows updated: " + rows);  
+ +

Menghapus Kejadian

+ +

Anda bisa menghapus kejadian dengan {@link +android.provider.BaseColumns#_ID} sebagai ID yang ditambahkan pada URI, atau dengan +pemilihan standar. Jika Anda menggunakan ID yang ditambahkan, Anda tidak bisa melakukan pemilihan. +Ada dua versi penghapusan: sebagai aplikasi dan sebagai adaptor sinkronisasi. Penghapusan +aplikasi mengatur kolom yang dihapus ke 1. Flag ini yang memberi tahu +adaptor sinkronisasi bahwa baris telah dihapus dan bahwa penghapusan ini harus +diberitahukan ke server. Penghapusan adaptor sinkronisasi menghapus kejadian dari +database bersama semua data terkaitnya. Berikut ini adalah contoh aplikasi +yang menghapus kejadian melalui {@link android.provider.BaseColumns#_ID}-nya:

+ + +
private static final String DEBUG_TAG = "MyActivity";
+...
+long eventID = 201;
+...
+ContentResolver cr = getContentResolver();
+ContentValues values = new ContentValues();
+Uri deleteUri = null;
+deleteUri = ContentUris.withAppendedId(Events.CONTENT_URI, eventID);
+int rows = getContentResolver().delete(deleteUri, null, null);
+Log.i(DEBUG_TAG, "Rows deleted: " + rows);  
+
+ +

Tabel Peserta

+ +

Tiap baris tabel {@link android.provider.CalendarContract.Attendees} +mewakili satu peserta atau tamu dari sebuah kejadian. Memanggil +{@link android.provider.CalendarContract.Reminders#query(android.content.ContentResolver, long, java.lang.String[]) query()} +akan menghasilkan daftar peserta untuk +kejadian dengan {@link android.provider.CalendarContract.AttendeesColumns#EVENT_ID} yang diberikan. +{@link android.provider.CalendarContract.AttendeesColumns#EVENT_ID} ini +harus cocok dengan {@link +android.provider.BaseColumns#_ID} kejadian tertentu.

+ +

Tabel berikut mencantumkan +bidang-bidang yang bisa ditulis. Saat menyisipkan peserta baru, Anda harus menyertakan semuanya +kecuali ATTENDEE_NAME. +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
KonstantaKeterangan
{@link android.provider.CalendarContract.AttendeesColumns#EVENT_ID}ID kejadian.
{@link android.provider.CalendarContract.AttendeesColumns#ATTENDEE_NAME}Nama peserta.
{@link android.provider.CalendarContract.AttendeesColumns#ATTENDEE_EMAIL}Alamat email peserta.
{@link android.provider.CalendarContract.AttendeesColumns#ATTENDEE_RELATIONSHIP}

Hubungan peserta dengan kejadian. Salah satu dari:

+
    +
  • {@link android.provider.CalendarContract.AttendeesColumns#RELATIONSHIP_ATTENDEE}
  • +
  • {@link android.provider.CalendarContract.AttendeesColumns#RELATIONSHIP_NONE}
  • +
  • {@link android.provider.CalendarContract.AttendeesColumns#RELATIONSHIP_ORGANIZER}
  • +
  • {@link android.provider.CalendarContract.AttendeesColumns#RELATIONSHIP_PERFORMER}
  • +
  • {@link android.provider.CalendarContract.AttendeesColumns#RELATIONSHIP_SPEAKER}
  • +
+
{@link android.provider.CalendarContract.AttendeesColumns#ATTENDEE_TYPE}

Tipe peserta. Salah satu dari:

+
    +
  • {@link android.provider.CalendarContract.AttendeesColumns#TYPE_REQUIRED}
  • +
  • {@link android.provider.CalendarContract.AttendeesColumns#TYPE_OPTIONAL}
  • +
{@link android.provider.CalendarContract.AttendeesColumns#ATTENDEE_STATUS}

Status kehadiran peserta. Salah satu dari:

+
    +
  • {@link android.provider.CalendarContract.AttendeesColumns#ATTENDEE_STATUS_ACCEPTED}
  • +
  • {@link android.provider.CalendarContract.AttendeesColumns#ATTENDEE_STATUS_DECLINED}
  • +
  • {@link android.provider.CalendarContract.AttendeesColumns#ATTENDEE_STATUS_INVITED}
  • +
  • {@link android.provider.CalendarContract.AttendeesColumns#ATTENDEE_STATUS_NONE}
  • +
  • {@link android.provider.CalendarContract.AttendeesColumns#ATTENDEE_STATUS_TENTATIVE}
  • +
+ +

Menambahkan Peserta

+ +

Berikut ini adalah contoh yang menambahkan satu peserta ke kejadian. Perhatikan bahwa +{@link android.provider.CalendarContract.AttendeesColumns#EVENT_ID} +diperlukan:

+ +
+long eventID = 202;
+...
+ContentResolver cr = getContentResolver();
+ContentValues values = new ContentValues();
+values.put(Attendees.ATTENDEE_NAME, "Trevor");
+values.put(Attendees.ATTENDEE_EMAIL, "trevor@example.com");
+values.put(Attendees.ATTENDEE_RELATIONSHIP, Attendees.RELATIONSHIP_ATTENDEE);
+values.put(Attendees.ATTENDEE_TYPE, Attendees.TYPE_OPTIONAL);
+values.put(Attendees.ATTENDEE_STATUS, Attendees.ATTENDEE_STATUS_INVITED);
+values.put(Attendees.EVENT_ID, eventID);
+Uri uri = cr.insert(Attendees.CONTENT_URI, values);
+
+ +

Tabel Pengingat

+ +

Tiap baris tabel {@link android.provider.CalendarContract.Reminders} +mewakili satu pengingat untuk sebuah kejadian. Memanggil +{@link android.provider.CalendarContract.Reminders#query(android.content.ContentResolver, long, java.lang.String[]) query()} akan menghasilkan daftar pengingat untuk +kejadian dengan +{@link android.provider.CalendarContract.AttendeesColumns#EVENT_ID} yang diberikan.

+ + +

Tabel berikut mencantumkan bidang-bidang yang bisa ditulis untuk pengingat. Semua bidang harus +disertakan saat menyisipkan pengingat baru. Perhatikan bahwa adaptor sinkronisasi menetapkan +tipe pengingat yang didukungnya dalam tabel {@link +android.provider.CalendarContract.Calendars}. Lihat +{@link android.provider.CalendarContract.CalendarColumns#ALLOWED_REMINDERS} +untuk detailnya.

+ + + + + + + + + + + + + + + + + + + +
KonstantaKeterangan
{@link android.provider.CalendarContract.RemindersColumns#EVENT_ID}ID kejadian.
{@link android.provider.CalendarContract.RemindersColumns#MINUTES}Menit yang ditunggu untuk memicu kejadian pengingat.
{@link android.provider.CalendarContract.RemindersColumns#METHOD}

Metode alarm, seperti yang diatur pada server. Salah satu dari:

+
    +
  • {@link android.provider.CalendarContract.RemindersColumns#METHOD_ALERT}
  • +
  • {@link android.provider.CalendarContract.RemindersColumns#METHOD_DEFAULT}
  • +
  • {@link android.provider.CalendarContract.RemindersColumns#METHOD_EMAIL}
  • +
  • {@link android.provider.CalendarContract.RemindersColumns#METHOD_SMS}
  • +
+ +

Menambahkan Pengingat

+ +

Contoh ini menambahkan pengingat ke kejadian. Pengingat dipicu 15 +menit sebelum kejadian.

+
+long eventID = 221;
+...
+ContentResolver cr = getContentResolver();
+ContentValues values = new ContentValues();
+values.put(Reminders.MINUTES, 15);
+values.put(Reminders.EVENT_ID, eventID);
+values.put(Reminders.METHOD, Reminders.METHOD_ALERT);
+Uri uri = cr.insert(Reminders.CONTENT_URI, values);
+ +

Tabel Instances

+ +

Tabel +{@link android.provider.CalendarContract.Instances} menyimpan +waktu mulai dan waktu selesai kejadian. Tiap baris dalam tabel ini +mewakili satu bentuk kejadian. Tabel instance tidak bisa ditulis dan hanya +menyediakan sebuah cara untuk membuat query kejadian.

+ +

Tabel berikut mencantumkan beberapa bidang yang bisa Anda query untuk suatu instance. Perhatikan +bahwa zona waktu didefinisikan oleh +{@link android.provider.CalendarContract.CalendarCache#KEY_TIMEZONE_TYPE} +dan +{@link android.provider.CalendarContract.CalendarCache#KEY_TIMEZONE_INSTANCES}.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
KonstantaKeterangan
{@link android.provider.CalendarContract.Instances#BEGIN}Waktu mulai instance, dalam milidetik UTC.
{@link android.provider.CalendarContract.Instances#END}Waktu selesai instance, dalam milidetik UTC.
{@link android.provider.CalendarContract.Instances#END_DAY}Hari selesai Julian dari instance, relatif terhadap +zona waktu Kalender. + +
{@link android.provider.CalendarContract.Instances#END_MINUTE}Menit selesai dari instance yang diukur dari tengah malam di zona waktu +Kalender.
{@link android.provider.CalendarContract.Instances#EVENT_ID}Kejadian _ID untuk instance ini.
{@link android.provider.CalendarContract.Instances#START_DAY}Hari mulai Julian dari instance, relatif terhadap zona waktu Kalender. +
{@link android.provider.CalendarContract.Instances#START_MINUTE}Menit mulai dari instance yang diukur dari tengah malam, relatif terhadap +zona waktu Kalender. +
+ +

Membuat query tabel Instance

+ +

Untuk membuat query tabel Instances, Anda perlu menetapkan rentang waktu query +dalam URI. Dalam contoh ini, {@link android.provider.CalendarContract.Instances} +mendapatkan akses ke bidang {@link +android.provider.CalendarContract.EventsColumns#TITLE} melalui +implementasi antarmuka {@link android.provider.CalendarContract.EventsColumns}-nya. +Dengan kata lain, {@link +android.provider.CalendarContract.EventsColumns#TITLE} dihasilkan melalui +tampilan database, bukan melalui query terhadap tabel {@link +android.provider.CalendarContract.Instances} mentah.

+ +
+private static final String DEBUG_TAG = "MyActivity";
+public static final String[] INSTANCE_PROJECTION = new String[] {
+    Instances.EVENT_ID,      // 0
+    Instances.BEGIN,         // 1
+    Instances.TITLE          // 2
+  };
+  
+// The indices for the projection array above.
+private static final int PROJECTION_ID_INDEX = 0;
+private static final int PROJECTION_BEGIN_INDEX = 1;
+private static final int PROJECTION_TITLE_INDEX = 2;
+...
+
+// Specify the date range you want to search for recurring
+// event instances
+Calendar beginTime = Calendar.getInstance();
+beginTime.set(2011, 9, 23, 8, 0);
+long startMillis = beginTime.getTimeInMillis();
+Calendar endTime = Calendar.getInstance();
+endTime.set(2011, 10, 24, 8, 0);
+long endMillis = endTime.getTimeInMillis();
+  
+Cursor cur = null;
+ContentResolver cr = getContentResolver();
+
+// The ID of the recurring event whose instances you are searching
+// for in the Instances table
+String selection = Instances.EVENT_ID + " = ?";
+String[] selectionArgs = new String[] {"207"};
+
+// Construct the query with the desired date range.
+Uri.Builder builder = Instances.CONTENT_URI.buildUpon();
+ContentUris.appendId(builder, startMillis);
+ContentUris.appendId(builder, endMillis);
+
+// Submit the query
+cur =  cr.query(builder.build(), 
+    INSTANCE_PROJECTION, 
+    selection, 
+    selectionArgs, 
+    null);
+   
+while (cur.moveToNext()) {
+    String title = null;
+    long eventID = 0;
+    long beginVal = 0;    
+    
+    // Get the field values
+    eventID = cur.getLong(PROJECTION_ID_INDEX);
+    beginVal = cur.getLong(PROJECTION_BEGIN_INDEX);
+    title = cur.getString(PROJECTION_TITLE_INDEX);
+              
+    // Do something with the values. 
+    Log.i(DEBUG_TAG, "Event:  " + title); 
+    Calendar calendar = Calendar.getInstance();
+    calendar.setTimeInMillis(beginVal);  
+    DateFormat formatter = new SimpleDateFormat("MM/dd/yyyy");
+    Log.i(DEBUG_TAG, "Date: " + formatter.format(calendar.getTime()));    
+    }
+ }
+ +

Intent Kalender

+

Aplikasi Anda tidak memerlukan izin untuk membaca dan menulis data kalender. Sebagai gantinya, aplikasi bisa menggunakan intent yang didukung oleh aplikasi Kalender Android untuk menyerahkan operasi baca dan tulis ke aplikasi itu. Tabel berikut mencantumkan intent yang didukung oleh Penyedia Kalender:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TindakanURIKeteranganEkstra

+ {@link android.content.Intent#ACTION_VIEW VIEW}

content://com.android.calendar/time/<ms_since_epoch>

+ Anda juga bisa mengacu ke URI dengan +{@link android.provider.CalendarContract#CONTENT_URI CalendarContract.CONTENT_URI}. +Untuk contoh yang menggunakan intent ini, lihat Menggunakan intent untuk menampilkan data kalender. + +
Membuka kalender pada waktu yang ditetapkan oleh <ms_since_epoch>.Tidak ada.

{@link android.content.Intent#ACTION_VIEW VIEW}

+ +

content://com.android.calendar/events/<event_id>

+ + Anda juga bisa mengacu ke URI dengan +{@link android.provider.CalendarContract.Events#CONTENT_URI Events.CONTENT_URI}. +Untuk contoh yang menggunakan intent ini, lihat Menggunakan intent untuk menampilkan data kalender. + +
Menampilkan kejadian yang ditetapkan oleh <event_id>.{@link android.provider.CalendarContract#EXTRA_EVENT_BEGIN_TIME CalendarContract.EXTRA_EVENT_BEGIN_TIME}
+
+
+ {@link android.provider.CalendarContract#EXTRA_EVENT_END_TIME CalendarContract.EXTRA_EVENT_END_TIME}
{@link android.content.Intent#ACTION_EDIT EDIT}

content://com.android.calendar/events/<event_id>

+ + Anda juga bisa mengacu ke URI dengan +{@link android.provider.CalendarContract.Events#CONTENT_URI Events.CONTENT_URI}. +Untuk contoh penggunaan intent ini, lihat Menggunakan intent untuk mengedit kejadian. + + +
Mengedit kejadian yang ditetapkan oleh <event_id>.{@link android.provider.CalendarContract#EXTRA_EVENT_BEGIN_TIME CalendarContract.EXTRA_EVENT_BEGIN_TIME}
+
+
+ {@link android.provider.CalendarContract#EXTRA_EVENT_END_TIME CalendarContract.EXTRA_EVENT_END_TIME}
{@link android.content.Intent#ACTION_EDIT EDIT}
+
+ {@link android.content.Intent#ACTION_INSERT INSERT}

content://com.android.calendar/events

+ + Anda juga bisa mengacu ke URI dengan +{@link android.provider.CalendarContract.Events#CONTENT_URI Events.CONTENT_URI}. +Untuk contoh penggunaan intent ini, lihat Menggunakan intent untuk menyisipkan kejadian. + +
Membuat sebuah kejadian.Ekstra apa saja yang tercantum dalam tabel di bawah.
+ +

Tabel berikut mencantumkan ekstra intent yang didukung oleh Penyedia Kalender: +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Ekstra IntentKeterangan
{@link android.provider.CalendarContract.EventsColumns#TITLE Events.TITLE}Nama kejadian.
{@link android.provider.CalendarContract#EXTRA_EVENT_BEGIN_TIME +CalendarContract.EXTRA_EVENT_BEGIN_TIME}Waktu mulai kejadian dalam milidetik sejak waktu patokan.
{@link android.provider.CalendarContract#EXTRA_EVENT_END_TIME +CalendarContract.EXTRA_EVENT_END_TIME}Waktu selesai kejadian dalam milidetik sejak waktu patokan.
{@link android.provider.CalendarContract#EXTRA_EVENT_ALL_DAY +CalendarContract.EXTRA_EVENT_ALL_DAY}Sebuah boolean yang menunjukkan bahwa kejadian sehari penuh. Nilai bisa +true atau false.
{@link android.provider.CalendarContract.EventsColumns#EVENT_LOCATION +Events.EVENT_LOCATION}Lokasi kejadian.
{@link android.provider.CalendarContract.EventsColumns#DESCRIPTION +Events.DESCRIPTION}Keterangan kejadian.
+ {@link android.content.Intent#EXTRA_EMAIL Intent.EXTRA_EMAIL}Alamat email mereka yang harus diundang berupa daftar yang dipisahkan koma.
+ {@link android.provider.CalendarContract.EventsColumns#RRULE Events.RRULE}Aturan perulangan kejadian.
+ {@link android.provider.CalendarContract.EventsColumns#ACCESS_LEVEL +Events.ACCESS_LEVEL}Apakah kejadian bersifat privat atau publik.
{@link android.provider.CalendarContract.EventsColumns#AVAILABILITY +Events.AVAILABILITY}Jika kejadian ini dihitung sebagai waktu sibuk atau waktu bebas yang bisa dijadwalkan.
+

Bagian berikut menjelaskan cara menggunakan semua intent ini.

+ + +

Menggunakan intent untuk menyisipkan kejadian

+ +

Penggunaan Intent {@link android.content.Intent#ACTION_INSERT INSERT} +akan memungkinkan aplikasi Anda menyerahkan tugas penyisipan kejadian ke Kalender itu sendiri. +Dengan pendekatan ini, aplikasi Anda bahkan tidak perlu menyertakan izin {@link +android.Manifest.permission#WRITE_CALENDAR} dalam file manifesnya.

+ + +

Bila pengguna menjalankan aplikasi yang menggunakan pendekatan ini, aplikasi akan mengirim +izin ke Kalender untuk menyelesaikan penambahan kejadian. Intent {@link +android.content.Intent#ACTION_INSERT INSERT} menggunakan bidang-bidang ekstra +untuk mengisi formulir lebih dahulu dengan detail kejadian dalam Kalender. Pengguna nanti bisa +membatalkan kejadian, mengedit formulir sebagaimana diperlukan, atau menyimpan kejadian ke +kalender mereka.

+ + + +

Berikut ini adalah cuplikan kode yang menjadwalkan kejadian pada tanggal 19 Januari 2012, yang berjalan +dari 7:30 pagi hingga 8:30 pagi Perhatikan hal-hal berikut tentang cuplikan kode ini:

+ + +
+Calendar beginTime = Calendar.getInstance();
+beginTime.set(2012, 0, 19, 7, 30);
+Calendar endTime = Calendar.getInstance();
+endTime.set(2012, 0, 19, 8, 30);
+Intent intent = new Intent(Intent.ACTION_INSERT)
+        .setData(Events.CONTENT_URI)
+        .putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, beginTime.getTimeInMillis())
+        .putExtra(CalendarContract.EXTRA_EVENT_END_TIME, endTime.getTimeInMillis())
+        .putExtra(Events.TITLE, "Yoga")
+        .putExtra(Events.DESCRIPTION, "Group class")
+        .putExtra(Events.EVENT_LOCATION, "The gym")
+        .putExtra(Events.AVAILABILITY, Events.AVAILABILITY_BUSY)
+        .putExtra(Intent.EXTRA_EMAIL, "rowan@example.com,trevor@example.com");
+startActivity(intent);
+
+ +

Menggunakan intent untuk mengedit kejadian

+ +

Anda bisa memperbarui kejadian secara langsung, seperti dijelaskan dalam Memperbarui kejadian. Namun penggunaan Intent {@link +android.content.Intent#ACTION_EDIT EDIT} memungkinkan aplikasi yang +tidak memiliki izin untuk menyerahkan pengeditan kejadian ke aplikasi Kalender. +Bila pengguna selesai mengedit kejadian dalam Kalender, pengguna akan dikembalikan ke +aplikasi semula.

Berikut ini adalah contoh intent yang mengatur +judul baru bagi kejadian yang ditetapkan dan memungkinkan pengguna mengedit kejadian dalam Kalender.

+ + +
long eventID = 208;
+Uri uri = ContentUris.withAppendedId(Events.CONTENT_URI, eventID);
+Intent intent = new Intent(Intent.ACTION_EDIT)
+    .setData(uri)
+    .putExtra(Events.TITLE, "My New Title");
+startActivity(intent);
+ +

Menggunakan intent untuk menampilkan data kalender

+

Penyedia Kalender menyediakan dua cara menggunakan Intent {@link android.content.Intent#ACTION_VIEW VIEW}:

+ +

Berikut ini adalah contoh yang menampilkan cara membuka Kalender pada tanggal tertentu:

+
// A date-time specified in milliseconds since the epoch.
+long startMillis;
+...
+Uri.Builder builder = CalendarContract.CONTENT_URI.buildUpon();
+builder.appendPath("time");
+ContentUris.appendId(builder, startMillis);
+Intent intent = new Intent(Intent.ACTION_VIEW)
+    .setData(builder.build());
+startActivity(intent);
+ +

Berikut ini adalah contoh yang menampilkan cara membuka kejadian untuk menampilkan:

+
long eventID = 208;
+...
+Uri uri = ContentUris.withAppendedId(Events.CONTENT_URI, eventID);
+Intent intent = new Intent(Intent.ACTION_VIEW)
+   .setData(uri);
+startActivity(intent);
+
+ + +

Adaptor Sinkronisasi

+ + +

Hanya ada perbedaan kecil dalam cara aplikasi dan adaptor sinkronisasi +mengakses Penyedia Kalender:

+ + + +

Berikut ini adalah metode pembantu yang bisa Anda gunakan untuk menghasilkan URI bagi penggunaan dengan adaptor sinkronisasi:

+
 static Uri asSyncAdapter(Uri uri, String account, String accountType) {
+    return uri.buildUpon()
+        .appendQueryParameter(android.provider.CalendarContract.CALLER_IS_SYNCADAPTER,"true")
+        .appendQueryParameter(Calendars.ACCOUNT_NAME, account)
+        .appendQueryParameter(Calendars.ACCOUNT_TYPE, accountType).build();
+ }
+
+

Untuk contoh implementasi adaptor sinkronisasi (yang tidak terkait secara khusus dengan Kalender), lihat +SampleSyncAdapter. diff --git a/docs/html-intl/intl/in/guide/topics/providers/contacts-provider.jd b/docs/html-intl/intl/in/guide/topics/providers/contacts-provider.jd new file mode 100644 index 0000000000000000000000000000000000000000..994c56b5300b0d144f63987f39b9c027a2948434 --- /dev/null +++ b/docs/html-intl/intl/in/guide/topics/providers/contacts-provider.jd @@ -0,0 +1,2356 @@ +page.title=Penyedia Kontak +@jd:body +

+
+

Tampilan Cepat

+ +

Dalam dokumen ini

+
    +
  1. + Organisasi Penyedia Kontak +
  2. +
  3. + Kontak mentah +
  4. +
  5. + Data +
  6. +
  7. + Kontak +
  8. +
  9. + Data Dari Adaptor Sinkronisasi +
  10. +
  11. + Izin yang Diperlukan +
  12. +
  13. + Profil Pengguna +
  14. +
  15. + Metadata Penyedia Kontak +
  16. +
  17. + Akses Penyedia Kontak +
  18. +
  19. +
  20. + Adaptor Sinkronisasi Penyedia Kontak +
  21. +
  22. + Data Aliran Sosial +
  23. +
  24. + Fitur Tambahan Penyedia Kontak +
  25. +
+

Kelas-kelas utama

+
    +
  1. {@link android.provider.ContactsContract.Contacts}
  2. +
  3. {@link android.provider.ContactsContract.RawContacts}
  4. +
  5. {@link android.provider.ContactsContract.Data}
  6. +
  7. {@code android.provider.ContactsContract.StreamItems}
  8. +
+

Contoh-Contoh Terkait

+
    +
  1. + + Contact Manager + +
  2. +
  3. + + Contoh Adaptor Sinkronisasi +
  4. +
+

Lihat Juga

+
    +
  1. + + Dasar-Dasar Penyedia Konten + +
  2. +
+
+
+

+ Penyedia Kontak adalah komponen Android yang tangguh dan fleksibel dalam mengelola + repository data pusat tentang orang di perangkat. Penyedia Kontak adalah sumber data + yang Anda lihat dalam aplikasi kontak perangkat, dan Anda juga bisa mengakses datanya dalam aplikasi + Anda sendiri serta mentransfer data antara perangkat dan layanan online. Penyedia mengakomodasi + berbagai sumber data dan mencoba mengelola data sebanyak mungkin untuk setiap orang, sehingga + organisasinya menjadi kompleks. Karena itu, API penyedia menyertakan + satu set kelas kontrak dan antarmuka ekstensif yang membantu pengambilan dan + modifikasi data. +

+

+ Panduan ini menjelaskan hal-hal berikut: +

+ +

+ Panduan ini beranggapan bahwa Anda mengetahui dasar-dasar penyedia konten Android. Untuk mengetahui selengkapnya + tentang penyedia konten Android, bacalah + panduan + Dasar-Dasar Penyedia Konten. Contoh aplikasi + Sample Sync Adapter + adalah contoh penggunaan adaptor sinkronisasi untuk mentransfer data antara Penyedia Kontak + dan contoh aplikasi yang memiliki host di Google Web Services. +

+

Organisasi Penyedia Kontak

+

+ Penyedia Kontak adalah komponen penyedia konten Android. Komponen ini memelihara tiga tipe + data tentang seseorang, masing-masing disesuaikan dengan tabel yang ditawarkan oleh penyedia, seperti + yang terlihat dalam gambar 1: +

+ +

+ Gambar 1. Struktur tabel Penyedia Kontak. +

+

+ Ketiga tabel disebut secara umum menurut nama kelas kontrak. Kelas + mendefinisikan konstanta untuk URI konten, nama kolom, dan nilai kolom yang digunakan oleh tabel-tabel: +

+
+
+ Tabel {@link android.provider.ContactsContract.Contacts} +
+
+ Baris mewakili orang yang berbeda, berdasarkan agregrasi baris kontak mentah. +
+
+ Tabel {@link android.provider.ContactsContract.RawContacts} +
+
+ Baris berisi rangkuman data seseorang, untuk tipe dan akun pengguna tertentu. +
+
+ Tabel {@link android.provider.ContactsContract.Data} +
+
+ Baris berisi data untuk kontak mentah, seperti alamat email atau nomor telepon. +
+
+

+ Tabel lain yang diwakili oleh kelas kontrak dalam {@link android.provider.ContactsContract} + adalah tabel tambahan yang digunakan Penyedia Kontak untuk mengelola operasinya atau mendukung + fungsi tertentu dalam kontak atau aplikasi telepon perangkat. +

+

Kontak mentah

+

+ Kontak mentah mewakili data seseorang yang berasal dari satu tipe akun dan nama + akun. Karena Penyedia Kontak memungkinkan lebih dari satu layanan online sebagai sumber + data untuk satu orang, Penyedia Kontak memungkinkan multikontak mentah untuk orang yang sama. + Multikontak mentah juga memungkinkan seorang pengguna mengombinasikan data seseorang dari lebih dari satu akun + bertipe akun yang sama. +

+

+ Sebagian besar data untuk kontak mentah tidak disimpan dalam + tabel {@link android.provider.ContactsContract.RawContacts}. Sebagai gantinya, data tersebut disimpan dalam satu atau beberapa baris + dalam tabel {@link android.provider.ContactsContract.Data}. Setiap baris data memiliki kolom + {@link android.provider.ContactsContract.DataColumns#RAW_CONTACT_ID Data.RAW_CONTACT_ID} yang + berisi nilai {@code android.provider.BaseColumns#_ID RawContacts._ID} dari + baris {@link android.provider.ContactsContract.RawContacts} induknya. +

+

Kolom-kolom kontak mentah yang penting

+

+ Kolom-kolom penting dalam tabel {@link android.provider.ContactsContract.RawContacts} + tercantum pada tabel 1. Bacalah catatan yang diberikan setelah tabel: +

+

+ Tabel 1. Kolom-kolom kontak mentah yang penting. +

+ + + + + + + + + + + + + + + + + + + + +
Nama kolomKegunaanCatatan
+ {@link android.provider.ContactsContract.SyncColumns#ACCOUNT_NAME} + + Nama akun untuk tipe akun yang merupakan sumber kontak mentah ini. + Misalnya, nama akun dari akun Google adalah salah satu alamat Gmail + pemilik perangkat. Lihat entri berikutnya untuk + {@link android.provider.ContactsContract.SyncColumns#ACCOUNT_TYPE} untuk informasi + selengkapnya. + + Format nama ini khusus untuk tipe akun ini. Format ini tidak + harus alamat email. +
+ {@link android.provider.ContactsContract.SyncColumns#ACCOUNT_TYPE} + + Tipe akun yang merupakan sumber kontak mentah ini. Misalnya, tipe + akun dari akun Google adalah com.google. Selalu batasi tipe akun Anda + dengan identifier domain untuk domain yang Anda miliki atau kontrol. Hal ini akan memastikan bahwa tipe + akun Anda bersifat unik. + + Tipe akun yang menawarkan data kontak biasanya memiliki adaptor sinkronisasi terkait yang + menyinkronkan dengan Penyedia Kontak. +
+ {@link android.provider.ContactsContract.RawContactsColumns#DELETED} + + Flag "deleted" untuk kontak mentah. + + Flag ini memungkinkan Penyedia Kontak memelihara baris secara internal hingga adaptor + sinkronisasi bisa menghapus baris dari server mereka dan akhirnya menghapus baris + dari repository. +
+

Catatan

+

+ Berikut ini adalah catatan penting tentang + tabel {@link android.provider.ContactsContract.RawContacts}: +

+ +

Sumber data kontak mentah

+

+ Untuk memahami cara kerja kontak mentah, perhatikan pengguna "Emily Dickinson" yang mendefinisikan + tiga akun pengguna berikut pada perangkatnya: +

+ +

+ Pengguna ini telah mengaktifkan Sync Contacts untuk ketiga akun dalam pengaturan + Accounts. +

+

+ Anggaplah Emily Dickinson membuka jendela browser, masuk ke Gmail sebagai + emily.dickinson@gmail.com, membuka + Contacts, dan menambahkan "Thomas Higginson". Kemudian, ia masuk ke Gmail sebagai + emilyd@gmail.com dan mengirimkan email kepada "Thomas Higginson", yang + menambahkan Thomas secara otomatis sebagai kontak. Ia juga mengikuti "colonel_tom" (ID Twitter Thomas Higginson) di + Twitter. +

+

+ Penyedia Kontak membuat tiga kontak mentah akibat pekerjaan ini: +

+
    +
  1. + Kontak mentah untuk "Thomas Higginson" yang dikaitkan dengan emily.dickinson@gmail.com. + Tipe akun penggunanya adalah Google. +
  2. +
  3. + Kontak mentah kedua untuk "Thomas Higginson" yang dikaitkan dengan emilyd@gmail.com. + Tipe akun pengguna juga Google. Ada kontak mentah kedua + meskipun nama tersebut identik dengan nama sebelumnya karena orang bersangkutan ditambahkan untuk + akun pengguna yang berbeda. +
  4. +
  5. + Kontak mentah ketiga untuk "Thomas Higginson" yang dikaitkan dengan "belle_of_amherst". Tipe + akun penggunanya adalah Twitter. +
  6. +
+

Data

+

+ Seperti yang telah disebutkan, data untuk kontak mentah disimpan dalam + baris {@link android.provider.ContactsContract.Data} yang ditautkan dengan nilai + _ID kontak mentah. Cara ini memungkinkan satu kontak mentah memiliki beberapa instance tipe data + yang sama dengan alamat email atau nomor telepon. Misalnya, jika + "Thomas Higginson" untuk {@code emilyd@gmail.com} (baris kontak mentah untuk Thomas Higginson + yang dikaitkan dengan akun Google emilyd@gmail.com) memiliki alamat email rumah + thigg@gmail.com dan alamat email kerja + thomas.higginson@gmail.com, Penyedia Kontak akan menyimpan dua baris alamat + email dan menautkan keduanya ke kontak mentah. +

+

+ Perhatikan bahwa tipe data yang berbeda disimpan dalam satu tabel ini. Baris-baris nama tampilan, + nomor telepon, email, alamat surat, foto, dan data situs web semuanya bisa ditemukan dalam + tabel {@link android.provider.ContactsContract.Data}. Untuk membantu mengelola ini, + tabel {@link android.provider.ContactsContract.Data} memiliki beberapa kolom dengan nama deskriptif, + dalam kolom lain dengan nama generik. Konten kolom bernama deskriptif memiliki arti yang sama + terlepas dari tipe data dalam barisnya, sedangkan konten kolom bernama generik memiliki + arti yang berbeda-beda sesuai dengan tipe data. +

+

Nama kolom deskriptif

+

+ Beberapa contoh nama kolom deskriptif adalah: +

+
+
+ {@link android.provider.ContactsContract.Data#RAW_CONTACT_ID} +
+
+ Nilai kolom _ID kontak mentah untuk data ini. +
+
+ {@link android.provider.ContactsContract.Data#MIMETYPE} +
+
+ Tipe data yang disimpan dalam baris ini, dinyatakan berupa tipe MIME custom. Penyedia Kontak + menggunakan tipe MIME yang didefinisikan dalam subkelas + {@link android.provider.ContactsContract.CommonDataKinds}. Tipe MIME ini adalah sumber terbuka, + dan bisa digunakan oleh setiap aplikasi atau adaptor sinkronisasi yang bisa digunakan bersama Penyedia Kontak. +
+
+ {@link android.provider.ContactsContract.DataColumns#IS_PRIMARY} +
+
+ Jika tipe baris data ini bisa terjadi lebih dari satu kali untuk suatu kontak mentah, + kolom {@link android.provider.ContactsContract.DataColumns#IS_PRIMARY} + menandai baris data yang berisi data utama untuk tipe itu. Misalnya, jika + pengguna menekan lama sebuah nomor telepon untuk kontak dan memilih Set default, + maka baris {@link android.provider.ContactsContract.Data} yang berisi angka itu + mengatur kolom {@link android.provider.ContactsContract.DataColumns#IS_PRIMARY}-nya ke suatu + nilai bukan nol. +
+
+

Nama kolom generik

+

+ Ada 15 kolom generik bernama DATA1 hingga + DATA15 yang tersedia secara umum dan empat kolom generik + tambahan SYNC1 hingga SYNC4 yang harus digunakan hanya oleh adaptor + sinkronisasi. Konstanta nama kolom generik selalu berfungsi, terlepas dari tipe + data dalam baris . +

+

+ Kolom DATA1 diindeks. Penyedia Kontak selalu menggunakan kolom ini untuk + data yang diharapkan penyedia akan menjadi target yang paling sering dari suatu query. Misalnya, + dalam baris email, kolom ini berisi alamat email sebenarnya. +

+

+ Sesuai konvensi, kolom DATA15 dicadangkan untuk menyimpan data Binary Large Object + (BLOB) seperti thumbnail foto. +

+

Nama kolom bertipe spesifik

+

+ Guna memudahkan pekerjaan dengan kolom untuk tipe baris tertentu, Penyedia Kontak + juga menyediakan konstanta nama kolom bertipe spesifik, yang didefinisikan dalam subkelas + {@link android.provider.ContactsContract.CommonDataKinds}. Konstanta cuma memberikan nama + konstanta yang berbeda ke nama kolom yang sama, yang membantu Anda mengakses data dalam baris + bertipe spesifik. +

+

+ Misalnya, kelas {@link android.provider.ContactsContract.CommonDataKinds.Email} mendefinisikan + konstanta nama kolom bertipe spesifik untuk baris {@link android.provider.ContactsContract.Data} + yang memiliki tipe MIME + {@link android.provider.ContactsContract.CommonDataKinds.Email#CONTENT_ITEM_TYPE + Email.CONTENT_ITEM_TYPE}. Kelas ini berisi konstanta + {@link android.provider.ContactsContract.CommonDataKinds.Email#ADDRESS} untuk kolom + alamat email. Nilai sesungguhnya dari + {@link android.provider.ContactsContract.CommonDataKinds.Email#ADDRESS} adalah "data1", yang + sama dengan nama generik kolom. +

+

+ Perhatian: Jangan tambahkan data custom Anda sendiri ke + tabel {@link android.provider.ContactsContract.Data} dengan menggunakan baris yang memiliki salah satu + tipe MIME yang telah didefinisikan penyedia. Jika melakukannya, Anda bisa kehilangan data atau menyebabkan penyedia + gagal berfungsi. Misalnya, Anda seharusnya tidak menambahkan baris bertipe MIME + {@link android.provider.ContactsContract.CommonDataKinds.Email#CONTENT_ITEM_TYPE + Email.CONTENT_ITEM_TYPE} yang berisi nama pengguna sebagai ganti alamat email dalam + kolom DATA1. Jika Anda menggunakan tipe MIME custom sendiri untuk baris bersangkutan, maka Anda bebas + untuk mendefinisikan nama kolom bertipe spesifik dan menggunakan kolom sekehendak Anda. +

+

+ Gambar 2 menampilkan cara kolom deskriptif dan kolom data muncul dalam + baris {@link android.provider.ContactsContract.Data}, dan cara nama kolom bertipe spesifik "melapisi" + nama kolom generik +

+How type-specific column names map to generic column names +

+ Gambar 2. Nama kolom bertipe spesifik dan nama kolom generik. +

+

Kelas nama kolom bertipe spesifik

+

+ Tabel 2 berisi daftar kelas nama kolom bertipe spesifik yang paling umum digunakan: +

+

+ Tabel 2. Kelas nama kolom bertipe spesifik

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Kelas pemetaanTipe dataCatatan
{@link android.provider.ContactsContract.CommonDataKinds.StructuredName}Data nama untuk kontak mentah yang dikaitkan dengan baris data ini.Kontak mentah hanya memiliki salah satu baris ini.
{@link android.provider.ContactsContract.CommonDataKinds.Photo}Foto utama untuk kontak mentah yang dikaitkan dengan baris data ini.Kontak mentah hanya memiliki salah satu baris ini.
{@link android.provider.ContactsContract.CommonDataKinds.Email}Alamat email untuk kontak mentah yang dikaitkan dengan baris data ini.Kontak mentah bisa memiliki beberapa alamat email.
{@link android.provider.ContactsContract.CommonDataKinds.StructuredPostal}Alamat pos untuk kontak mentah yang dikaitkan dengan baris data ini.Kontak mentah bisa memiliki beberapa alamat email.
{@link android.provider.ContactsContract.CommonDataKinds.GroupMembership}Identifier yang menautkan kontak mentah ke salah satu grup dalam Penyedia Kontak. + Grup adalah fitur opsional pada tipe akun dan nama akun. Grup dijelaskan + lebih detail di bagian Grup kontak. +
+

Kontak

+

+ Penyedia Kontak mengombinasikan baris kontak mentah di semua tipe akun dan nama akun + untuk membentuk kontak. Hal ini memudahkan menampilkan dan memodifikasi semua data + yang telah dikumpulkan pengguna untuk seseorang. Penyedia Kontak mengelola pembuatan baris + kontak baru, dan agregasi kontak mentah dengan baris kontak yang ada. Baik aplikasi maupun adaptor sinkronisasi + tidak boleh menambahkan kontak dan sebagian kolom dalam baris kontak yang bersifat hanya baca. +

+

+ Catatan: Jika Anda mencoba menambahkan kontak ke Penyedia Kontak dengan + {@link android.content.ContentResolver#insert(Uri,ContentValues) insert()}, Anda akan mendapatkan + eksepsi {@link java.lang.UnsupportedOperationException}. Jika Anda mencoba memperbarui sebuah kolom + yang tercantum sebagai "hanya-baca", pembaruan akan diabaikan. +

+

+ Penyedia Kontak membuat kontak baru untuk merespons penambahan kontak mentah baru + yang tidak cocok dengan kontak yang ada. Penyedia juga melakukan ini jika data + kontak mentah yang ada berubah sehingga tidak lagi cocok dengan kontak yang + sebelumnya dihubungkan. Jika aplikasi atau adaptor sinkronisasi membuat kontak mentah baru yang + memang cocok dengan kontak yang ada, kontak mentah baru akan diagregasikan ke kontak + yang ada. +

+

+ Penyedia Kontak menautkan baris kontak ke baris kontak mentahnya dengan kolom + _ID dari baris kontak dalam tabel {@link android.provider.ContactsContract.Contacts Contacts}. + Kolom CONTACT_ID tabel kontak mentah + {@link android.provider.ContactsContract.RawContacts} berisi nilai _ID untuk + baris kontak yang dikaitkan dengan tiap baris kontak mentah. +

+

+ Tabel {@link android.provider.ContactsContract.Contacts} juga memiliki kolom + {@code android.provider.ContactsContract.ContactsColumns#LOOKUP_KEY} yang merupakan + tautan "permanen" ke baris kontak. Karena memelihara kontak + secara otomatis, Penyedia Kontak bisa mengubah nilai {@code android.provider.BaseColumns#_ID} baris kontak + untuk merespons agregasi atau sinkronisasi. Sekalipun ini terjadi, URI konten + {@link android.provider.ContactsContract.Contacts#CONTENT_LOOKUP_URI} yang dikombinasikan dengan + {@code android.provider.ContactsContract.ContactsColumns#LOOKUP_KEY} kontak akan tetap + menunjuk ke baris kontak itu, sehingga Anda bisa menggunakan + {@code android.provider.ContactsContract.ContactsColumns#LOOKUP_KEY} + untuk memelihara tautan ke kontak "favorit", dan seterusnya. Kolom ini memiliki formatnya sendiri, yang + tidak terkait dengan format kolom {@code android.provider.BaseColumns#_ID}. +

+

+ Gambar 3 menampilkan cara ketiga tabel utama terkait satu sama lain. +

+Contacts provider main tables +

+ Gambar 3. Hubungan tabel Contacts, Raw Contacts, dan Details. +

+

Data Dari Adaptor Sinkronisasi

+

+ Pengguna memasukkan data kontak secara langsung ke dalam perangkat, namun data juga mengalir masuk ke Penyedia Kontak + dari layanan web melalui adaptor sinkronisasi, yang mengotomatiskan + transfer data antara perangkat dan layanan. Adaptor sinkronisasi berjalan di latar belakang + di bawah kontrol sistem, dan memanggil metode {@link android.content.ContentResolver} + untuk mengelola data. +

+

+ Di Android, layanan web yang digunakan adaptor sinkronisasi diidentifikasi melalui tipe akun. + Setiap adaptor sinkronisasi bekerja dengan satu tipe akun, tetapi bisa mendukung beberapa nama akun untuk + tipe itu. Tipe akun dan nama akun dijelaskan secara singkat di bagian + Sumber data kontak mentah. Definisi berikut menyediakan + detail selengkapnya, dan menjelaskan cara tipe dan nama akun berkaitan dengan adaptor sinkronisasi dan layanan. +

+
+
+ Tipe akun +
+
+ Mengidentifikasi layanan tempat pengguna menyimpan data. Sering kali, pengguna harus + mengautentikasi diri dengan layanan. Misalnya, Google Contacts adalah tipe akun, yang diidentifikasi + dengan kode google.com. Nilai ini sesuai dengan tipe akun yang digunakan oleh + {@link android.accounts.AccountManager}. +
+
+ Nama akun +
+
+ Mengidentifikasi akun atau login tertentu untuk suatu tipe akun. Akun Google Contacts + sama dengan akun Google, yang memiliki alamat email sebagai nama akun. + Layanan lain mungkin menggunakan nama pengguna satu-kata atau identitas berupa angka. +
+
+

+ Tipe akun tidak harus unik. Pengguna boleh mengonfigurasi beberapa akun Google Contacts + dan mengunduh data ke Penyedia Kontak; ini mungkin terjadi jika pengguna memiliki satu set + kontak pribadi untuk satu nama akun pribadi, dan satu set lagi untuk pekerjaan. Nama akun + biasanya unik. Bersama-sama, keduanya mengidentifikasi aliran data tertentu antara Penyedia Kontak dan + layanan eksternal. +

+

+ Jika Anda ingin mentransfer data layanan ke Penyedia Kontak, Anda perlu menulis + adaptor sinkronisasi sendiri. Hal ini dijelaskan lebih detail di bagian + Adaptor Sinkronisasi Penyedia Kontak. +

+

+ Gambar 4 menampilkan cara Penyedia Kontak dimasukkan ke dalam aliran data + tentang orang. Dalam kotak bertanda "sync adapters", setiap adaptor diberi label menurut tipe akunnya. +

+Flow of data about people +

+ Gambar 4. Aliran data Penyedia Kontak. +

+

Izin yang Diperlukan

+

+ Aplikasi yang ingin mengakses Penyedia Kontak harus meminta izin + berikut: +

+
+
Akses baca ke satu atau beberapa tabel
+
+ {@link android.Manifest.permission#READ_CONTACTS}, yang ditetapkan dalam + AndroidManifest.xml dengan elemen + + <uses-permission> sebagai + <uses-permission android:name="android.permission.READ_CONTACTS">. +
+
Akses tulis ke satu atau beberapa tabel
+
+ {@link android.Manifest.permission#WRITE_CONTACTS}, yang ditetapkan dalam + AndroidManifest.xml dengan elemen + + <uses-permission> sebagai + <uses-permission android:name="android.permission.WRITE_CONTACTS">. +
+
+

+ Izin ini tidak diperluas ke data profil pengguna. Profil pengguna dan izin + yang diperlukan dibahas di bagian berikut, + Profil Pengguna. +

+

+ Ingatlah bahwa data kontak pengguna bersifat pribadi dan sensitif. Pengguna mempersoalkan + privasinya, sehingga tidak ingin aplikasi mengumpulkan data tentang diri atau kontak mereka. + Jika alasan Anda memerlukan izin untuk mengakses data kontak tidak jelas, pengguna mungkin memberi + aplikasi Anda peringkat rendah atau langsung menolak menginstalnya. +

+

Profil Pengguna

+

+ Tabel {@link android.provider.ContactsContract.Contacts} berisi satu baris yang berisi + data profil untuk pengguna perangkat. Data ini menjelaskan data perangkat user bukannya + salah satu kontak pengguna. Baris kontak profil ditautkan ke baris + kontak mentah untuk setiap sistem yang menggunakan profil. + Setiap baris kontak mentah profil bisa memiliki beberapa baris data. Konstanta untuk mengakses profil + pengguna tersedia dalam kelas {@link android.provider.ContactsContract.Profile}. +

+

+ Akses ke profil pengguna memerlukan izin khusus. Selain itu, izin + {@link android.Manifest.permission#READ_CONTACTS} dan + {@link android.Manifest.permission#WRITE_CONTACTS} diperlukan untuk membaca dan menulis, akses + ke profil pengguna memerlukan masing-masing izin {@code android.Manifest.permission#READ_PROFILE} dan + {@code android.Manifest.permission#WRITE_PROFILE} untuk akses baca dan tulis. + +

+

+ Ingatlah bahwa Anda harus mempertimbangkan profil pengguna bersifat sensitif. Izin + {@code android.Manifest.permission#READ_PROFILE} memungkinkan Anda mengakses data yang mengidentifikasi secara pribadi + pengguna perangkat. Pastikan memberi tahu pengguna alasan + Anda memerlukan izin akses profil pengguna dalam keterangan aplikasi Anda. +

+

+ Untuk mengambil baris kontak berisi profil pengguna, + panggil {@link android.content.ContentResolver#query(Uri,String[], String, String[], String) + ContentResolver.query()}. Atur URI konten ke + {@link android.provider.ContactsContract.Profile#CONTENT_URI} dan jangan sediakan + kriteria pemilihan apa pun. Anda juga bisa menggunakan URI konten ini sebagai URI dasar untuk mengambil kontak + mentah atau data untuk profil. Misalnya, cuplikan kode ini mengambil data untuk profil: +

+
+// Sets the columns to retrieve for the user profile
+mProjection = new String[]
+    {
+        Profile._ID,
+        Profile.DISPLAY_NAME_PRIMARY,
+        Profile.LOOKUP_KEY,
+        Profile.PHOTO_THUMBNAIL_URI
+    };
+
+// Retrieves the profile from the Contacts Provider
+mProfileCursor =
+        getContentResolver().query(
+                Profile.CONTENT_URI,
+                mProjection ,
+                null,
+                null,
+                null);
+
+

+ Catatan: Jika Anda mengambil beberapa baris kontak, dan ingin menentukan apakah salah satu baris + adalah profil pengguna, uji + kolom {@link android.provider.ContactsContract.ContactsColumns#IS_USER_PROFILE} pada baris tersebut. Kolom ini + diatur ke "1" jika kontak adalah profil pengguna. +

+

Metadata Penyedia Kontak

+

+ Penyedia Kontak mengelola data yang mencatat status data kontak dalam + repository. Metadata repository ini disimpan di berbagai tempat, termasuk baris-baris tabel + Raw Contacts, Data, dan Contacts, + tabel {@link android.provider.ContactsContract.Settings}, dan + tabel {@link android.provider.ContactsContract.SyncState}. Tabel berikut menampilkan + efek setiap potongan metadata ini: +

+

+ Tabel 3. Metadata di Penyedia Kontak

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TabelKolomNilaiArti
{@link android.provider.ContactsContract.RawContacts}{@link android.provider.ContactsContract.SyncColumns#DIRTY}"0" - tidak berubah sejak sinkronisasi terakhir. + Menandai kontak mentah yang berubah pada perangkat dan telah disinkronkan kembali ke + server. Nilai diatur secara otomatis oleh Penyedia Kontak bila aplikasi + Android memperbarui baris. +

+ Adaptor sinkronisasi yang memodifikasi kontak mentah atau tabel data harus selalu menambahkan + string {@link android.provider.ContactsContract#CALLER_IS_SYNCADAPTER} ke + URI konten yang digunakannya. Ini mencegah penyedia menandai baris sebagai kotor. + Sebaliknya, modifikasi oleh adaptor sinkronisasi tampak seperti modifikasi lokal dan + dikirim ke server, meskipun server adalah sumber modifikasi. +

+
"1" - berubah sejak sinkronisasi terakhir, harus disinkronkan kembali ke server.
{@link android.provider.ContactsContract.RawContacts}{@link android.provider.ContactsContract.SyncColumns#VERSION}Nomor versi baris ini. + Penyedia Kontak menambahkan nilai ini secara otomatis bila baris atau + data terkaitnya berubah. +
{@link android.provider.ContactsContract.Data}{@link android.provider.ContactsContract.DataColumns#DATA_VERSION}Nomor versi baris ini. + Penyedia Kontak menambahkan nilai ini secara otomatis bila baris data + berubah. +
{@link android.provider.ContactsContract.RawContacts}{@link android.provider.ContactsContract.SyncColumns#SOURCE_ID} + Nilai string yang mengidentifikasi secara unik kontak mentah ini ke akun tempat + kontak dibuat. + + Bila adaptor sinkronisasi membuat kontak mentah baru, kolom ini harus diatur ke + ID unik server untuk kontak mentah itu. Bila aplikasi Android membuat kontak mentah + baru, aplikasi harus membiarkan kolom ini kosong. Ini mengisyaratkan pada adaptor + sinkronisasi bahwa adaptor harus membuat kontak mentah baru pada server, dan mendapatkan + nilai untuk {@link android.provider.ContactsContract.SyncColumns#SOURCE_ID}. +

+ Khususnya, id sumber harus unik untuk setiap tipe + akun dan stabil di semua sinkronisasi: +

+
    +
  • + Unik: Setiap kontak mentah untuk satu akun harus memiliki id sumbernya sendiri. Jika Anda + tidak memberlakukan aturan ini, masalah akan timbul dalam aplikasi kontak. + Perhatikan bahwa dua kontak mentah untuk tipe akun yang sama boleh memiliki + id sumber yang sama. Misalnya, kontak mentah "Thomas Higginson" untuk + akun {@code emily.dickinson@gmail.com} boleh memiliki id sumber + yang sama dengan kontak mentah "Thomas Higginson" untuk akun + {@code emilyd@gmail.com}. +
  • +
  • + Stabil: Id sumber adalah bagian tetap dari data layanan online untuk + kontak mentah. Misalnya, jika pengguna membersihkan Contacts Storage dari + pengaturan aplikasi dan menyinkronkan ulang, kontak mentah yang dipulihkan akan memiliki id sumber + yang sama dengan sebelumnya. Jika Anda tidak memberlakukan hal ini, pintasan akan berhenti + berfungsi. +
  • +
+
{@link android.provider.ContactsContract.Groups}{@link android.provider.ContactsContract.GroupsColumns#GROUP_VISIBLE}"0" - Kontak dalam grup ini tidak boleh terlihat dalam UI aplikasi Android. + Kolom ini digunakan untuk kompatibilitas dengan server yang memungkinkan pengguna menyembunyikan kontak dalam + grup tertentu. +
"1" - Kontak dalam grup ini boleh terlihat dalam UI aplikasi.
{@link android.provider.ContactsContract.Settings} + {@link android.provider.ContactsContract.SettingsColumns#UNGROUPED_VISIBLE} + "0" - Untuk akun dan tipe akun ini, kontak yang bukan milik grup + tidak akan terlihat pada UI aplikasi Android. + + Secara default, kontak tidak terlihat jika tidak satu pun kontak mentahnya milik grup + (Keanggotaan grup untuk kontak mentah ditandai oleh satu atau beberapa baris + {@link android.provider.ContactsContract.CommonDataKinds.GroupMembership} + dalam tabel {@link android.provider.ContactsContract.Data}). + Dengan mengatur flag ini dalam baris tabel {@link android.provider.ContactsContract.Settings} + untuk tipe akun dan akun, Anda bisa memaksakan kontak tanpa grup agar terlihat. + Satu kegunaan flag ini adalah menampilkan kontak dari server yang tidak menggunakan grup. +
+ "1" - Untuk akun dan tipe akun ini, kontak yang bukan milik grup + akan terlihat pada UI aplikasi. +
{@link android.provider.ContactsContract.SyncState}(semua) + Gunakan tabel ini untuk menyimpan metadata bagi adaptor sinkronisasi Anda. + + Dengan tabel ini, Anda bisa menyimpan status sinkronisasi dan data lain yang terkait dengan sinkronisasi secara persisten pada + perangkat. +
+

Akses Penyedia Kontak

+

+ Bagian ini menjelaskan panduan untuk mengakses data dari Penyedia Kontak, yang berfokus pada + hal-hal berikut: +

+ +

+ Membuat modifikasi dari adaptor sinkronisasi juga secara lebih detail di bagian + Adaptor Sinkronisasi Penyedia Kontak. +

+

Membuat query entitas

+

+ Karena disusun secara hierarki, tabel-tabel Penyedia Kontak sering kali berguna untuk + mengambil baris dan semua baris "anak" yang ditautkan dengannya. Misalnya, untuk menampilkan + semua informasi untuk satu orang, Anda mungkin ingin mengambil semua + baris {@link android.provider.ContactsContract.RawContacts} untuk satu baris + {@link android.provider.ContactsContract.Contacts}, atau semua + baris {@link android.provider.ContactsContract.CommonDataKinds.Email} untuk satu baris + {@link android.provider.ContactsContract.RawContacts}. Untuk memudahkan hal ini, Penyedia Kontak + menawarkan konstruksi entitas, yang berfungsi seperti gabungan database di antara + tabel-tabel. +

+

+ Entitas adalah seperti tabel yang terdiri atas kolom-kolom terpilih dari tabel induk dan tabel anaknya. + Bila membuat query sebuah entitas, Anda memberikan proyeksi dan kriteria pencarian berdasarkan kolom-kolom + yang tersedia dari entitas itu. Hasilnya adalah sebuah {@link android.database.Cursor} yang + berisi satu baris untuk setiap baris tabel anak yang diambil. Misalnya, jika Anda membuat query + {@link android.provider.ContactsContract.Contacts.Entity} untuk satu nama kontak + dan semua baris {@link android.provider.ContactsContract.CommonDataKinds.Email} untuk semua + kontak mentah bagi nama itu, Anda akan mendapatkan kembali {@link android.database.Cursor} berisi satu baris + untuk setiap baris {@link android.provider.ContactsContract.CommonDataKinds.Email}. +

+

+ Entitas menyederhanakan query. Dengan entitas, Anda bisa mengambil semua data kontak untuk satu + kontak atau kontak mentah sekaligus, sebagai ganti harus membuat query tabel induk terlebih dahulu untuk mendapatkan + ID, lalu harus membuat query tabel anak dengan ID itu. Selain itu, Penyedia Kontak akan memproses + query terhadap entitas dalam satu transaksi, yang memastikan bahwa data yang diambil + konsisten secara internal. +

+

+ Catatan: Entitas biasanya tidak berisi semua kolom tabel induk dan + anak. Jika Anda mencoba menggunakan nama kolom yang tidak ada dalam daftar konstanta + nama kolom untuk entitas, Anda akan mendapatkan {@link java.lang.Exception}. +

+

+ Cuplikan berikut menampilkan cara mengambil semua baris kontak mentah untuk sebuah kontak. Cuplikan ini + adalah bagian dari aplikasi lebih besar yang memiliki dua aktivitas, "main" dan "detail". Aktivitas utama + menampilkan daftar baris kontak; bila pengguna memilih satu baris, aktivitas akan mengirimkan ID-nya ke aktivitas + detail. Aktivitas detail menggunakan{@link android.provider.ContactsContract.Contacts.Entity} + untuk menampilkan semua baris data dari semua kontak mentah yang dikaitkan dengan kontak + terpilih. +

+

+ Cuplikan ini diambil dari aktivitas "detail": +

+
+...
+    /*
+     * Appends the entity path to the URI. In the case of the Contacts Provider, the
+     * expected URI is content://com.google.contacts/#/entity (# is the ID value).
+     */
+    mContactUri = Uri.withAppendedPath(
+            mContactUri,
+            ContactsContract.Contacts.Entity.CONTENT_DIRECTORY);
+
+    // Initializes the loader identified by LOADER_ID.
+    getLoaderManager().initLoader(
+            LOADER_ID,  // The identifier of the loader to initialize
+            null,       // Arguments for the loader (in this case, none)
+            this);      // The context of the activity
+
+    // Creates a new cursor adapter to attach to the list view
+    mCursorAdapter = new SimpleCursorAdapter(
+            this,                        // the context of the activity
+            R.layout.detail_list_item,   // the view item containing the detail widgets
+            mCursor,                     // the backing cursor
+            mFromColumns,                // the columns in the cursor that provide the data
+            mToViews,                    // the views in the view item that display the data
+            0);                          // flags
+
+    // Sets the ListView's backing adapter.
+    mRawContactList.setAdapter(mCursorAdapter);
+...
+@Override
+public Loader<Cursor> onCreateLoader(int id, Bundle args) {
+
+    /*
+     * Sets the columns to retrieve.
+     * RAW_CONTACT_ID is included to identify the raw contact associated with the data row.
+     * DATA1 contains the first column in the data row (usually the most important one).
+     * MIMETYPE indicates the type of data in the data row.
+     */
+    String[] projection =
+        {
+            ContactsContract.Contacts.Entity.RAW_CONTACT_ID,
+            ContactsContract.Contacts.Entity.DATA1,
+            ContactsContract.Contacts.Entity.MIMETYPE
+        };
+
+    /*
+     * Sorts the retrieved cursor by raw contact id, to keep all data rows for a single raw
+     * contact collated together.
+     */
+    String sortOrder =
+            ContactsContract.Contacts.Entity.RAW_CONTACT_ID +
+            " ASC";
+
+    /*
+     * Returns a new CursorLoader. The arguments are similar to
+     * ContentResolver.query(), except for the Context argument, which supplies the location of
+     * the ContentResolver to use.
+     */
+    return new CursorLoader(
+            getApplicationContext(),  // The activity's context
+            mContactUri,              // The entity content URI for a single contact
+            projection,               // The columns to retrieve
+            null,                     // Retrieve all the raw contacts and their data rows.
+            null,                     //
+            sortOrder);               // Sort by the raw contact ID.
+}
+
+

+ Bila selesai dimuat, {@link android.app.LoaderManager} akan memicu callback ke + {@link android.app.LoaderManager.LoaderCallbacks#onLoadFinished(Loader, D) + onLoadFinished()}. Salah satu argumen masuk pada metode ini adalah + {@link android.database.Cursor} bersama hasil query. Dalam aplikasi Anda sendiri, Anda bisa memperoleh + data dari {@link android.database.Cursor} ini untuk menampilkannya atau menggunakannya lebih jauh. +

+

Modifikasi batch

+

+ Bila memungkinkan, Anda harus menyisipkan, memperbarui, dan menghapus data dalam Penyedia Kontak dengan + "batch mode", dengan membuat {@link java.util.ArrayList} dari + objek-objek {@link android.content.ContentProviderOperation} dan memanggil + {@link android.content.ContentResolver#applyBatch(String, ArrayList) applyBatch()}. Karena + Penyedia Kontak menjalankan semua operasi dalam satu + {@link android.content.ContentResolver#applyBatch(String, ArrayList) applyBatch()} transaksi, + modifikasi Anda tidak akan pernah meninggalkan repository kontak dalam keadaan + tidak konsisten. Modifikasi batch juga memudahkan penyisipan kontak mentah dan data detailnya + sekaligus. +

+

+ Catatan: Untuk memodifikasi satu kontak mentah, pertimbangkan untuk mengirim intent ke + aplikasi kontak perangkat daripada menangani modifikasi dalam aplikasi Anda. + Cara ini dijelaskan lebih detail di bagian + Pengambilan dan modifikasi dengan intent. +

+

Yield point

+

+ Modifikasi batch yang berisi operasi dalam jumlah besar bisa memblokir proses lain, + yang mengakibatkan pengalaman pengguna yang buruk secara keseluruhan. Untuk menata semua modifikasi yang ingin Anda + jalankan dalam sesedikit mungkin daftar terpisah, sambil mencegah modifikasi dari + memblokir sistem, Anda harus menetapkan yield point untuk satu atau beberapa operasi. + Yield point (titik hasil) adalah objek {@link android.content.ContentProviderOperation} yang mengatur + nilai {@link android.content.ContentProviderOperation#isYieldAllowed()}-nya ke + true. Bila menemui yield point, Penyedia Kontak akan menghentikan pekerjaannya untuk + membiarkan proses lain berjalan dan menutup transaksi saat ini. Bila dimulai lagi, penyedia akan + melanjutkan dengan operasi berikutnya di {@link java.util.ArrayList} dan memulai transaksi + baru. +

+

+ Yield point memang menyebabkan lebih dari satu transaksi per panggilan ke + {@link android.content.ContentResolver#applyBatch(String, ArrayList) applyBatch()}. Karena + itu, Anda harus menetapkan yield point pada operasi terakhir untuk satu set baris terkait. + Misalnya, Anda harus menetapkan yield point pada operasi terakhir di satu set yang menambahkan + baris kontak mentah dan baris data terkait, atau operasi terakhir untuk satu set baris yang terkait + dengan satu kontak. +

+

+ Yield point juga merupakan unit operasi atomis. Semua akses antara dua yield point bisa + saja berhasil atau gagal sebagai satu unit. Jika Anda mengatur yield point, operasi + atomis terkecil adalah seluruh batch operasi. Jika menggunakan yield point, Anda akan mencegah + operasi menurunkan kinerja sistem, sekaligus memastikan subset + operasi bersifat atomis. +

+

Acuan balik modifikasi

+

+ Saat Anda menyisipkan baris kontak mentah baru dan baris data terkaitnya sebagai satu set + objek {@link android.content.ContentProviderOperation}, Anda harus menautkan baris data ke + baris kontak mentah dengan memasukkan nilai + {@code android.provider.BaseColumns#_ID} kontak mentah sebagai + nilai {@link android.provider.ContactsContract.DataColumns#RAW_CONTACT_ID}. Akan tetapi, nilai + ini tidak tersedia saat Anda membuat {@link android.content.ContentProviderOperation} + untuk baris data, karena Anda belum menerapkan + {@link android.content.ContentProviderOperation} untuk baris kontak mentah. Solusinya, + kelas {@link android.content.ContentProviderOperation.Builder} memiliki metode + {@link android.content.ContentProviderOperation.Builder#withValueBackReference(String, int) withValueBackReference()}. + Metode ini memungkinkan Anda menyisipkan atau mengubah kolom dengan + hasil dari operasi sebelumnya. +

+

+ Metode {@link android.content.ContentProviderOperation.Builder#withValueBackReference(String, int) withValueBackReference()} + memiliki dua argumen: +

+
+
+ key +
+
+ Kunci dari pasangan kunci-nilai. Nilai argumen ini harus berupa nama kolom + dalam tabel yang Anda modifikasi. +
+
+ previousResult +
+
+ Indeks berbasis 0 dari nilai pada larik + objek {@link android.content.ContentProviderResult} dari + {@link android.content.ContentResolver#applyBatch(String, ArrayList) applyBatch()}. Saat + operasi batch diterapkan, hasil tiap operasi akan disimpan dalam + larik hasil antara. Nilai previousResult adalah indeks + dari salah satu hasil ini, yang diambil dan disimpan bersama nilai key. + Cara ini memungkinkan Anda menyisipkan record kontak mentah baru dan mendapatkan kembali nilai + {@code android.provider.BaseColumns#_ID}-nya, lalu membuat "acuan balik" ke + nilai itu saat Anda menambahkan baris {@link android.provider.ContactsContract.Data}. +

+ Seluruh larik hasil dibuat saat Anda memanggil + {@link android.content.ContentResolver#applyBatch(String, ArrayList) applyBatch()} untuk pertama kali, + dengan ukuran setara dengan ukuran {@link java.util.ArrayList} dari + objek {@link android.content.ContentProviderOperation} yang Anda sediakan. Akan tetapi, semua + elemen dalam larik hasil diatur ke null, dan jika Anda mencoba + melakukan acuan balik ke hasil untuk operasi yang belum diterapkan, +{@link android.content.ContentProviderOperation.Builder#withValueBackReference(String, int) withValueBackReference()} + akan mengeluarkan {@link java.lang.Exception}. + +

+
+
+

+ Cuplikan kode berikut menampilkan cara menyisipkan kontak mentah baru dan data secara batch. Cuplikan kode ini + menyertakan kode yang menetapkan yield point dan menggunakan acuan balik. Cuplikan kode ini adalah + versi perluasan dari metodecreateContacEntry(), yang merupakan bagian dari kelas + ContactAdder dalam + aplikasi contoh + Contact Manager. +

+

+ Cuplikan pertama mengambil data kontak dari UI. Pada saat ini, pengguna sudah + memilih akun tempat kontak mentah baru harus ditambahkan. +

+
+// Creates a contact entry from the current UI values, using the currently-selected account.
+protected void createContactEntry() {
+    /*
+     * Gets values from the UI
+     */
+    String name = mContactNameEditText.getText().toString();
+    String phone = mContactPhoneEditText.getText().toString();
+    String email = mContactEmailEditText.getText().toString();
+
+    int phoneType = mContactPhoneTypes.get(
+            mContactPhoneTypeSpinner.getSelectedItemPosition());
+
+    int emailType = mContactEmailTypes.get(
+            mContactEmailTypeSpinner.getSelectedItemPosition());
+
+

+ Cuplikan berikutnya membuat operasi untuk menyisipkan baris kontak mentah ke dalam + tabel {@link android.provider.ContactsContract.RawContacts}: +

+
+    /*
+     * Prepares the batch operation for inserting a new raw contact and its data. Even if
+     * the Contacts Provider does not have any data for this person, you can't add a Contact,
+     * only a raw contact. The Contacts Provider will then add a Contact automatically.
+     */
+
+     // Creates a new array of ContentProviderOperation objects.
+    ArrayList<ContentProviderOperation> ops =
+            new ArrayList<ContentProviderOperation>();
+
+    /*
+     * Creates a new raw contact with its account type (server type) and account name
+     * (user's account). Remember that the display name is not stored in this row, but in a
+     * StructuredName data row. No other data is required.
+     */
+    ContentProviderOperation.Builder op =
+            ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI)
+            .withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, mSelectedAccount.getType())
+            .withValue(ContactsContract.RawContacts.ACCOUNT_NAME, mSelectedAccount.getName());
+
+    // Builds the operation and adds it to the array of operations
+    ops.add(op.build());
+
+

+ Berikutnya, kode akan membuat baris data untuk baris-baris nama tampilan, telepon, dan email. +

+

+ Setiap objek pembangun operasi menggunakan + {@link android.content.ContentProviderOperation.Builder#withValueBackReference(String, int) withValueBackReference()} + untuk mendapatkan + {@link android.provider.ContactsContract.DataColumns#RAW_CONTACT_ID}. Acuan menunjuk + balik ke objek {@link android.content.ContentProviderResult} dari operasi pertama, + yang menambahkan baris kontak mentah dan mengembalikan nilai {@code android.provider.BaseColumns#_ID} + barunya. Hasilnya, setiap data ditautkan secara otomatis oleh + {@link android.provider.ContactsContract.DataColumns#RAW_CONTACT_ID}-nya + ke baris {@link android.provider.ContactsContract.RawContacts} baru yang memilikinya. +

+

+ Objek {@link android.content.ContentProviderOperation.Builder} yang menambahkan baris email + diberi flag {@link android.content.ContentProviderOperation.Builder#withYieldAllowed(boolean) + withYieldAllowed()}, yang mengatur yield point: +

+
+    // Creates the display name for the new raw contact, as a StructuredName data row.
+    op =
+            ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
+            /*
+             * withValueBackReference sets the value of the first argument to the value of
+             * the ContentProviderResult indexed by the second argument. In this particular
+             * call, the raw contact ID column of the StructuredName data row is set to the
+             * value of the result returned by the first operation, which is the one that
+             * actually adds the raw contact row.
+             */
+            .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
+
+            // Sets the data row's MIME type to StructuredName
+            .withValue(ContactsContract.Data.MIMETYPE,
+                    ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE)
+
+            // Sets the data row's display name to the name in the UI.
+            .withValue(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, name);
+
+    // Builds the operation and adds it to the array of operations
+    ops.add(op.build());
+
+    // Inserts the specified phone number and type as a Phone data row
+    op =
+            ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
+            /*
+             * Sets the value of the raw contact id column to the new raw contact ID returned
+             * by the first operation in the batch.
+             */
+            .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
+
+            // Sets the data row's MIME type to Phone
+            .withValue(ContactsContract.Data.MIMETYPE,
+                    ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE)
+
+            // Sets the phone number and type
+            .withValue(ContactsContract.CommonDataKinds.Phone.NUMBER, phone)
+            .withValue(ContactsContract.CommonDataKinds.Phone.TYPE, phoneType);
+
+    // Builds the operation and adds it to the array of operations
+    ops.add(op.build());
+
+    // Inserts the specified email and type as a Phone data row
+    op =
+            ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
+            /*
+             * Sets the value of the raw contact id column to the new raw contact ID returned
+             * by the first operation in the batch.
+             */
+            .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
+
+            // Sets the data row's MIME type to Email
+            .withValue(ContactsContract.Data.MIMETYPE,
+                    ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE)
+
+            // Sets the email address and type
+            .withValue(ContactsContract.CommonDataKinds.Email.ADDRESS, email)
+            .withValue(ContactsContract.CommonDataKinds.Email.TYPE, emailType);
+
+    /*
+     * Demonstrates a yield point. At the end of this insert, the batch operation's thread
+     * will yield priority to other threads. Use after every set of operations that affect a
+     * single contact, to avoid degrading performance.
+     */
+    op.withYieldAllowed(true);
+
+    // Builds the operation and adds it to the array of operations
+    ops.add(op.build());
+
+

+ Cuplikan terakhir menampilkan panggilan ke + {@link android.content.ContentResolver#applyBatch(String, ArrayList) applyBatch()} yang + menyisipkan baris-baris kontak mentah dan data baru. +

+
+    // Ask the Contacts Provider to create a new contact
+    Log.d(TAG,"Selected account: " + mSelectedAccount.getName() + " (" +
+            mSelectedAccount.getType() + ")");
+    Log.d(TAG,"Creating contact: " + name);
+
+    /*
+     * Applies the array of ContentProviderOperation objects in batch. The results are
+     * discarded.
+     */
+    try {
+
+            getContentResolver().applyBatch(ContactsContract.AUTHORITY, ops);
+    } catch (Exception e) {
+
+            // Display a warning
+            Context ctx = getApplicationContext();
+
+            CharSequence txt = getString(R.string.contactCreationFailure);
+            int duration = Toast.LENGTH_SHORT;
+            Toast toast = Toast.makeText(ctx, txt, duration);
+            toast.show();
+
+            // Log exception
+            Log.e(TAG, "Exception encountered while inserting contact: " + e);
+    }
+}
+
+

+ Operasi batch juga memungkinkan Anda menerapkan kontrol konkurensi optimistis, + sebuah metode yang menerapkan transaksi modifikasi tanpa harus mengunci repository yang mendasari. + Untuk menggunakan metode ini, terapkan transaksi dan periksa modifikasi lain yang + mungkin telah dibuat bersamaan. Jika ternyata modifikasi tidak konsisten, Anda + mengembalikan transaksi ke kondisi semula dan mencobanya kembali. +

+

+ Kontrol konkurensi optimistis berguna untuk perangkat seluler, apabila hanya ada satu pengguna setiap + kalinya, dan akses simultan ke repository data jarang terjadi. Karena penguncian tidak digunakan, + tidak ada waktu yang terbuang untuk memasang kunci atau menunggu transaksi lain untuk melepas kunci. +

+

+ Untuk menggunakan kontrol konkurensi optimistis saat memperbarui satu baris + {@link android.provider.ContactsContract.RawContacts}, ikuti langkah-langkah ini: +

+
    +
  1. + Ambil kolom {@link android.provider.ContactsContract.SyncColumns#VERSION} + kontak mentah bersama data lain yang Anda ambil. +
  2. +
  3. + Buat sebuah objek {@link android.content.ContentProviderOperation.Builder} yang cocok untuk + memberlakukan batasan, dengan menggunakan metode + {@link android.content.ContentProviderOperation#newAssertQuery(Uri)}. Untuk URI konten, + gunakan {@link android.provider.ContactsContract.RawContacts#CONTENT_URI + RawContacts.CONTENT_URI} + dengan {@code android.provider.BaseColumns#_ID} kontak mentah yang ditambahkan padanya. +
  4. +
  5. + Untuk objek {@link android.content.ContentProviderOperation.Builder}, panggil + {@link android.content.ContentProviderOperation.Builder#withValue(String, Object) + withValue()} untuk membandingkan kolom {@link android.provider.ContactsContract.SyncColumns#VERSION} + dengan nomor versi yang baru saja Anda ambil. +
  6. +
  7. + Untuk {@link android.content.ContentProviderOperation.Builder} yang sama, panggil + {@link android.content.ContentProviderOperation.Builder#withExpectedCount(int) + withExpectedCount()} untuk memastikan bahwa hanya satu baris yang diuji oleh pernyataan ini. +
  8. +
  9. + Panggil {@link android.content.ContentProviderOperation.Builder#build()} untuk membuat + objek {@link android.content.ContentProviderOperation}, kemudian tambahkan objek ini sebagai + objek pertama di {@link java.util.ArrayList} yang Anda teruskan ke + {@link android.content.ContentResolver#applyBatch(String, ArrayList) applyBatch()}. +
  10. +
  11. + Terapkan transaksi batch. +
  12. +
+

+ Jika baris kontak mentah diperbarui oleh operasi lain antara waktu Anda membaca baris dan + waktu Anda mencoba memodifikasinya, "asert" {@link android.content.ContentProviderOperation} + akan gagal, dan seluruh batch operasi akan dibatalkan. Anda nanti bisa memilih untuk mencoba ulang + batch atau melakukan tindakan lain. +

+

+ Cuplikan berikut memperagakan cara membuat "asert" + {@link android.content.ContentProviderOperation} setelah membuat query satu kontak mentah yang menggunakan + {@link android.content.CursorLoader}: +

+
+/*
+ * The application uses CursorLoader to query the raw contacts table. The system calls this method
+ * when the load is finished.
+ */
+public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
+
+    // Gets the raw contact's _ID and VERSION values
+    mRawContactID = cursor.getLong(cursor.getColumnIndex(BaseColumns._ID));
+    mVersion = cursor.getInt(cursor.getColumnIndex(SyncColumns.VERSION));
+}
+
+...
+
+// Sets up a Uri for the assert operation
+Uri rawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, mRawContactID);
+
+// Creates a builder for the assert operation
+ContentProviderOperation.Builder assertOp = ContentProviderOperation.netAssertQuery(rawContactUri);
+
+// Adds the assertions to the assert operation: checks the version and count of rows tested
+assertOp.withValue(SyncColumns.VERSION, mVersion);
+assertOp.withExpectedCount(1);
+
+// Creates an ArrayList to hold the ContentProviderOperation objects
+ArrayList ops = new ArrayList<ContentProviderOperationg>;
+
+ops.add(assertOp.build());
+
+// You would add the rest of your batch operations to "ops" here
+
+...
+
+// Applies the batch. If the assert fails, an Exception is thrown
+try
+    {
+        ContentProviderResult[] results =
+                getContentResolver().applyBatch(AUTHORITY, ops);
+
+    } catch (OperationApplicationException e) {
+
+        // Actions you want to take if the assert operation fails go here
+    }
+
+

Pengambilan dan modifikasi dengan intent

+

+ Mengirimkan intent ke aplikasi kontak perangkat memungkinkan Anda mengakses Penyedia Kontak + secara tidak langsung. Intent akan memulai UI aplikasi kontak perangkat, tempat pengguna bisa + melakukan pekerjaan yang terkait dengan kontak. Dengan tipe akses ini, pengguna bisa: +

+

+ Jika pengguna menyisipkan atau memperbarui data, Anda bisa mengumpulkan data lebih dahulu dan mengirimkannya sebagai + bagian dari intent. +

+

+ Bila Anda menggunakan intent untuk mengakses Penyedia Kontak melalui aplikasi kontak perangkat, Anda + tidak perlu menulis UI atau kode sendiri untuk mengakses penyedia. Anda juga tidak harus + meminta izin untuk membaca dari atau menulis ke penyedia. Aplikasi kontak perangkat bisa + mendelegasikan izin membaca untuk kontak kepada Anda, dan karena Anda membuat modifikasi pada + penyedia melalui aplikasi lain, Anda tidak perlu memiliki izin menulis. +

+

+ Proses umum pengiriman intent untuk mengakses penyedia dijelaskan secara detail dalam panduan + + Dasar-Dasar Penyedia Konten di bagian "Akses data melalui intent". Tindakan, + tipe MIME, dan nilai data yang Anda gunakan untuk tugas yang tersedia dirangkum dalam Tabel 4, sedangkan + nilai ekstra yang bisa Anda gunakan bersama + {@link android.content.Intent#putExtra(String, String) putExtra()} tercantum dalam + dokumentasi acuan untuk {@link android.provider.ContactsContract.Intents.Insert}: +

+

+ Tabel 4. Intent Penyedia Kontak. +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TugasTindakanDataTipe MIMECatatan
Memilih kontak dari daftar{@link android.content.Intent#ACTION_PICK} + Salah satu dari: +
    +
  • +{@link android.provider.ContactsContract.Contacts#CONTENT_URI Contacts.CONTENT_URI}, + yang menampilkan daftar kontak. +
  • +
  • +{@link android.provider.ContactsContract.CommonDataKinds.Phone#CONTENT_URI Phone.CONTENT_URI}, + yang menampilkan daftar nomor telepon untuk kontak mentah. +
  • +
  • +{@link android.provider.ContactsContract.CommonDataKinds.StructuredPostal#CONTENT_URI +StructuredPostal.CONTENT_URI}, + yang menampilkan daftar alamat pos untuk kontak mentah. +
  • +
  • +{@link android.provider.ContactsContract.CommonDataKinds.Email#CONTENT_URI Email.CONTENT_URI}, + yang menampilkan daftar alamat email untuk kontak baru. +
  • +
+
+ Tidak digunakan + + Menampilkan daftar kontak mentah atau daftar data dari kontak mentah, sesuai dengan tipe + URI konten yang Anda sediakan. +

+ Panggil + {@link android.app.Activity#startActivityForResult(Intent, int) startActivityForResult()}, + yang menghasilkan URI konten dari baris terpilih. Bentuk URI adalah + URI konten tabel dengan LOOKUP_ID baris yang ditambahkan padanya. + Aplikasi kontak perangkat mendelegasikan izin membaca dan menulis untuk URI konten ini + selama masa pakai aktivitas Anda. Lihat panduan + + Dasar-Dasar Penyedia Konten untuk detail selengkapnya. +

+
Menyisipkan kontak mentah baru{@link android.provider.ContactsContract.Intents.Insert#ACTION Insert.ACTION}N/A + {@link android.provider.ContactsContract.RawContacts#CONTENT_TYPE + RawContacts.CONTENT_TYPE}, tipe MIME untuk satu set kontak mentah. + + Menampilkan layar Add Contact aplikasi kontak perangkat. Nilai + ekstra yang Anda tambahkan ke intent akan ditampilkan. Jika dikirimkan bersama + {@link android.app.Activity#startActivityForResult(Intent, int) startActivityForResult()}, + URI konten dari kontak mentah yang baru saja ditambahkan akan dikembalikan ke + {@link android.app.Activity#onActivityResult(int, int, Intent) onActivityResult()} + metode callback aktivitas Anda pada argumen {@link android.content.Intent}, di + bidang "data". Untuk mendapatkan nilainya, panggil {@link android.content.Intent#getData()}. +
Mengedit kontak{@link android.content.Intent#ACTION_EDIT} + {@link android.provider.ContactsContract.Contacts#CONTENT_LOOKUP_URI} untuk + kontak. Aktivitas editor memungkinkan pengguna mengedit setiap data yang dikaitkan + dengan kontak ini. + + {@link android.provider.ContactsContract.Contacts#CONTENT_ITEM_TYPE + Contacts.CONTENT_ITEM_TYPE}, kontak tunggal. + Menampilkan layar Edit Contact dalam aplikasi kontak. Nilai ekstra yang Anda tambahkan + ke intent akan ditampilkan. Bila pengguna mengklik Done untuk menyimpan + hasil edit, aktivitas Anda kembali ke latar depan. +
Menampilkan picker yang juga bisa menambahkan data.{@link android.content.Intent#ACTION_INSERT_OR_EDIT} + N/A + + {@link android.provider.ContactsContract.Contacts#CONTENT_ITEM_TYPE} + + Intent ini selalu menampilkan layar picker aplikasi kontak. Pengguna bisa memilih + kontak untuk diedit, atau menambahkan kontak baru. Layar edit atau layar tambah + akan muncul, sesuai dengan pilihan pengguna, dan data ekstra yang Anda kirimkan dalam intent + akan ditampilkan. Jika aplikasi Anda menampilkan data kontak seperti email atau nomor telepon, gunakan + intent ini untuk memungkinkan pengguna menambahkan data ke kontak yang ada. + +

+ Catatan: Tidak perlu mengirimkan nilai nama dalam ekstra intent ini, + karena pengguna selalu mengambil nama yang ada atau menambahkan nama baru. Lebih-lebih, + jika Anda mengirimkan nama, dan pengguna memilih untuk melakukan edit, aplikasi kontak akan + menampilkan nama yang Anda kirimkan, yang menimpa nilai sebelumnya. Jika pengguna tidak + menyadari hal ini dan menyimpan hasil edit, nilai lama akan hilang. +

+
+

+ Aplikasi kontak perangkat tidak memperbolehkan Anda menghapus kontak mentah atau datanya dengan + intent. Sebagai gantinya, untuk menghapus kontak mentah, gunakan + {@link android.content.ContentResolver#delete(Uri, String, String[]) ContentResolver.delete()} + atau {@link android.content.ContentProviderOperation#newDelete(Uri) + ContentProviderOperation.newDelete()}. +

+

+ Cuplikan berikut menampilkan cara menyusun dan mengirimkan intent yang menyisipkan kontak dan data + mentah baru: +

+
+// Gets values from the UI
+String name = mContactNameEditText.getText().toString();
+String phone = mContactPhoneEditText.getText().toString();
+String email = mContactEmailEditText.getText().toString();
+
+String company = mCompanyName.getText().toString();
+String jobtitle = mJobTitle.getText().toString();
+
+// Creates a new intent for sending to the device's contacts application
+Intent insertIntent = new Intent(ContactsContract.Intents.Insert.ACTION);
+
+// Sets the MIME type to the one expected by the insertion activity
+insertIntent.setType(ContactsContract.RawContacts.CONTENT_TYPE);
+
+// Sets the new contact name
+insertIntent.putExtra(ContactsContract.Intents.Insert.NAME, name);
+
+// Sets the new company and job title
+insertIntent.putExtra(ContactsContract.Intents.Insert.COMPANY, company);
+insertIntent.putExtra(ContactsContract.Intents.Insert.JOB_TITLE, jobtitle);
+
+/*
+ * Demonstrates adding data rows as an array list associated with the DATA key
+ */
+
+// Defines an array list to contain the ContentValues objects for each row
+ArrayList<ContentValues> contactData = new ArrayList<ContentValues>();
+
+
+/*
+ * Defines the raw contact row
+ */
+
+// Sets up the row as a ContentValues object
+ContentValues rawContactRow = new ContentValues();
+
+// Adds the account type and name to the row
+rawContactRow.put(ContactsContract.RawContacts.ACCOUNT_TYPE, mSelectedAccount.getType());
+rawContactRow.put(ContactsContract.RawContacts.ACCOUNT_NAME, mSelectedAccount.getName());
+
+// Adds the row to the array
+contactData.add(rawContactRow);
+
+/*
+ * Sets up the phone number data row
+ */
+
+// Sets up the row as a ContentValues object
+ContentValues phoneRow = new ContentValues();
+
+// Specifies the MIME type for this data row (all data rows must be marked by their type)
+phoneRow.put(
+        ContactsContract.Data.MIMETYPE,
+        ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE
+);
+
+// Adds the phone number and its type to the row
+phoneRow.put(ContactsContract.CommonDataKinds.Phone.NUMBER, phone);
+
+// Adds the row to the array
+contactData.add(phoneRow);
+
+/*
+ * Sets up the email data row
+ */
+
+// Sets up the row as a ContentValues object
+ContentValues emailRow = new ContentValues();
+
+// Specifies the MIME type for this data row (all data rows must be marked by their type)
+emailRow.put(
+        ContactsContract.Data.MIMETYPE,
+        ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE
+);
+
+// Adds the email address and its type to the row
+emailRow.put(ContactsContract.CommonDataKinds.Email.ADDRESS, email);
+
+// Adds the row to the array
+contactData.add(emailRow);
+
+/*
+ * Adds the array to the intent's extras. It must be a parcelable object in order to
+ * travel between processes. The device's contacts app expects its key to be
+ * Intents.Insert.DATA
+ */
+insertIntent.putParcelableArrayListExtra(ContactsContract.Intents.Insert.DATA, contactData);
+
+// Send out the intent to start the device's contacts app in its add contact activity.
+startActivity(insertIntent);
+
+

Integritas data

+

+ Karena repository kontak berisi data penting dan sensitif yang diharapkan pengguna agar + benar dan terbaru. Penyedia Kontak memiliki aturan yang didefinisikan dengan baik demi integritas data. Anda + bertanggung jawab untuk mematuhi aturan ini saat memodifikasi data kontak. Aturan-aturan penting itu + dicantumkan di sini: +

+
+
+ Selalu tambahkan baris {@link android.provider.ContactsContract.CommonDataKinds.StructuredName} + untuk setiap baris {@link android.provider.ContactsContract.RawContacts} yang Anda tambahkan. +
+
+ Baris {@link android.provider.ContactsContract.RawContacts} tanpa + baris {@link android.provider.ContactsContract.CommonDataKinds.StructuredName} dalam + tabel {@link android.provider.ContactsContract.Data} bisa menyebabkan masalah selama + agregasi. +
+
+ Selalu tautkan baris {@link android.provider.ContactsContract.Data} baru ke baris + {@link android.provider.ContactsContract.RawContacts} induknya. +
+
+ Baris {@link android.provider.ContactsContract.Data} yang tidak ditautkan ke + {@link android.provider.ContactsContract.RawContacts} tidak akan terlihat dalam aplikasi kontak + perangkat, dan itu bisa menimbulkan masalah dengan adaptor sinkronisasi. +
+
+ Ubah data hanya untuk kontak mentah yang Anda miliki. +
+
+ Ingatlah bahwa Penyedia Kontak biasanya mengelola data dari berbagai + tipe akun/layanan online. Anda harus memastikan bahwa aplikasi Anda hanya memodifikasi + atau menghapus data untuk baris milik Anda, dan bahwa aplikasi hanya menyisipkan data dengan + tipe akun dan nama yang Anda kontrol. +
+
+ Selalu gunakan konstanta yang didefinisikan dalam {@link android.provider.ContactsContract} dan + subkelasnya untuk otoritas, URI konten, URI path, nama kolom, tipe MIME, dan + nilai {@link android.provider.ContactsContract.CommonDataKinds.CommonColumns#TYPE}. +
+
+ Menggunakan konstanta ini membantu Anda menghindari kesalahan. Anda juga akan diberi tahu dengan peringatan + compiler jika salah satu konstanta sudah usang. +
+
+

Baris data custom

+

+ Dengan membuat dan menggunakan tipe MIME custom sendiri, Anda bisa menyisipkan, mengedit, menghapus, dan mengambil + baris data sendiri dalam tabel {@link android.provider.ContactsContract.Data}. Baris Anda + dibatasi untuk menggunakan kolom yang didefinisikan dalam + {@link android.provider.ContactsContract.DataColumns}, meskipun Anda bisa memetakan nama kolom + bertipe spesifik sendiri ke nama kolom default. Dalam aplikasi kontak perangkat, + data untuk baris Anda ditampilkan, tetapi tidak bisa diedit atau dihapus, dan pengguna tidak bisa menambahkan + data lain. Untuk memudahkan pengguna mengubah baris data custom Anda, Anda harus menyediakan aktivitas + editor dalam aplikasi Anda sendiri. +

+

+ Untuk menampilkan data custom, sediakan file contacts.xml berisi elemen + <ContactsAccountType> dan satu atau beberapa elemen anak + <ContactsDataKind>. Hal ini dijelaskan lebih detail di + bagian <ContactsDataKind> element. +

+

+ Untuk mengetahui selengkapnya tentang tipe MIME custom, bacalah panduan + + Membuat Penyedia Konten. +

+

Adaptor Sinkronisasi Penyedia Kontak

+

+ Penyedia Kontak didesain khusus untuk menangani sinkronisasi + data kontak antara perangkat dan layanan online. Hal ini memungkinkan pengguna mengunduh + data yang ada dari perangkat baru dan mengunggah data yang ada ke akun baru. + Sinkronisasi juga memastikan bahwa pengguna memiliki data terbaru, apa pun + sumber penambahan dan perubahan itu. Keuntungan lain dari sinkronisasi adalah membuat + data kontak tersedia sekalipun perangkat tidak terhubung ke jaringan. +

+

+ Walaupun Anda bisa menerapkan sinkronisasi dengan berbagai cara, sistem Android menyediakan + kerangka kerja sinkronisasi plug-in yang mengotomatiskan tugas-tugas berikut: +

+

+ Untuk menggunakan kerangka kerja ini, Anda harus menyediakan plug-in adaptor sinkronisasi. Setiap adaptor sinkronisasi bersifat unik bagi + layanan dan penyedia konten, tetapi mampu menangani beberapa nama akun untuk layanan yang sama. Kerangka + kerja ini juga memungkinkan beberapa adaptor sinkronisasi untuk layanan dan penyedia yang sama. +

+

Kelas dan file adaptor sinkronisasi

+

+ Anda mengimplementasikan adaptor sinkronisasi sebagai subkelas + {@link android.content.AbstractThreadedSyncAdapter} dan menginstalnya sebagai bagian dari aplikasi + Android. Sistem akan mempelajari adaptor sinkronisasi dari elemen-elemen di manifes + aplikasi Anda dan dari file XML khusus yang ditunjuk oleh manifes. File XML mendefinisikan + tipe akun untuk layanan online dan otoritas untuk penyedia konten, yang bersama-sama + mengidentifikasi adaptor secara unik. Adaptor sinkronisasi tidak menjadi aktif hingga pengguna menambahkan + akun untuk tipe akun adaptor sinkronisasi dan memungkinkan sinkronisasi untuk penyedia + konten yang disinkronkan dengan adaptor sinkronisasi. Pada saat itu, sistem mulai mengelola adaptor, + memanggilnya seperlunya untuk menyinkronkan antara penyedia konten dan server. +

+

+ Catatan: Menggunakan tipe akun sebagai bagian dari identifikasi adaptor sinkronisasi memungkinkan + sistem mendeteksi dan menghimpun adaptor-adaptor sinkronisasi yang mengakses berbagai layanan dari + organisasi yang sama. Misalnya, adaptor sinkronisasi untuk semua layanan online Google semuanya memiliki tipe akun + yang sama com.google. Bila pengguna menambahkan akun Google ke perangkatnya, semua + adaptor sinkronisasi yang terinstal untuk layanan Google akan dicantumkan bersama; setiap adaptor sinkronisasi + yang tercantum akan menyinkronkan diri dengan berbagai penyedia konten pada perangkat. +

+

+ Karena sebagian besar layanan mengharuskan pengguna untuk memeriksa identitas sebelum mengakses + data, sistem Android menawarkan kerangka kerja autentikasi yang serupa dengan, dan sering kali + digunakan bersama, kerangka kerja adaptor sinkronisasi. Kerangka kerja autentikasi menggunakan + autentikator plug-in yang merupakan subkelas + {@link android.accounts.AbstractAccountAuthenticator}. Autentikator memeriksa + identitas pengguna dalam langkah-langkah berikut: +

    +
  1. + Mengumpulkan nama pengguna, kata sandi, atau informasi serupa ( + kredensial pengguna). +
  2. +
  3. + Mengirimkan kredensial ke layanan +
  4. +
  5. + Memeriksa balasan layanan. +
  6. +
+

+ Jika layanan menerima kredensial, autentikator bisa + menyimpan kredensial itu untuk digunakan nanti. Karena kerangka kerja autentikator plug-in, + {@link android.accounts.AccountManager} bisa menyediakan akses ke setiap token autentikasi yang didukung suatu autentikator + dan dipilihnya untuk diekspos, seperti token autentikasi OAuth2. +

+

+ Meskipun autentikasi tidak diharuskan, sebagian besar layanan kontak menggunakannya. + Akan tetapi, Anda tidak wajib menggunakan kerangka kerja autentikasi Android untuk melakukan autentikasi. +

+

Implementasi adaptor sinkronisasi

+

+ Untuk mengimplementasikan adaptor sinkronisasi bagi Penyedia Kontak, perlu Anda memulai dengan membuat + aplikasi Android yang berisi elemen-elemen berikut: +

+
+
+ Komponen {@link android.app.Service} yang merespons permintaan sistem untuk + mengikat ke adaptor sinkronisasi. +
+
+ Bila sistem ingin menjalankan sinkronisasi, sistem akan memanggil metode + {@link android.app.Service#onBind(Intent) onBind()} layanan untuk mendapatkan + {@link android.os.IBinder} bagi adaptor sinkronisasi. Hal ini memungkinkan sistem melakukan + panggilan lintas proses ke metode adaptor. +

+ Dalam contoh aplikasi + Sample Sync Adapter, nama kelas layanan ini adalah + com.example.android.samplesync.syncadapter.SyncService. +

+
+
+ Adaptor sinkronisasi yang sesungguhnya, diimplementasikan sebagai subkelas konkret dari + {@link android.content.AbstractThreadedSyncAdapter}. +
+
+ Kelas ini melakukan pekerjaan mengunduh data dari server, mengunggah data ke + perangkat, dan menyelesaikan konflik. Pekerjaan utama adaptor + diselesaikan dengan metode {@link android.content.AbstractThreadedSyncAdapter#onPerformSync( + Account, Bundle, String, ContentProviderClient, SyncResult) + onPerformSync()}. Instance kelas ini harus dibuat sebagai singleton. +

+ Dalam contoh aplikasi + Sample Sync Adapter, adaptor sinkronisasi didefinisikan dalam kelas + com.example.android.samplesync.syncadapter.SyncAdapter. +

+
+
+ Subkelas {@link android.app.Application}. +
+
+ Kelas ini berfungsi sebagai pabrik untuk singleton adaptor sinkronisasi. Gunakan + metode {@link android.app.Application#onCreate()} untuk membuat instance adaptor sinkronisasi , dan + menyediakan metode "getter" statis untuk mengembalikan singleton ke + metode {@link android.app.Service#onBind(Intent) onBind()} dari layanan + adaptor sinkronisasi. +
+
+ Opsional: Komponen {@link android.app.Service} yang merespons + permintaan dari sistem untuk autentikasi pengguna. +
+
+ {@link android.accounts.AccountManager} memulai layanan ini untuk memulai proses + autentikasi. Metode {@link android.app.Service#onCreate()} layanan membuat instance + objek autentikator. Bila sistem ingin mengautentikasi akun pengguna untuk + adaptor sinkronisasi aplikasi, sistem akan memanggil metode +{@link android.app.Service#onBind(Intent) onBind()} layanan guna mendapatkan + {@link android.os.IBinder} bagi autentikator. Hal ini memungkinkan sistem melakukan + panggilan lintas proses ke metode autentikator. +

+ Dalam contoh aplikasi + Sample Sync Adapter, nama kelas layanan ini adalah + com.example.android.samplesync.authenticator.AuthenticationService. +

+
+
+ Opsional: Subkelas konkret + {@link android.accounts.AbstractAccountAuthenticator} yang menangani permintaan + autentikasi. +
+
+ Kelas ini menyediakan metode yang dipicu {@link android.accounts.AccountManager} + untuk mengautentikasi kredensial pengguna dengan layanan. Detail + proses autentikasi sangat bervariasi, berdasarkan teknologi server yang digunakan. Anda harus + mengacu ke dokumentasi bagi perangkat lunak server untuk mengetahui selengkapnya tentang autentikasi. +

+ Dalam contoh aplikasi + Sample Sync Adapter, autentikator didefinisikan dalam kelas + com.example.android.samplesync.authenticator.Authenticator. +

+
+
+ File XML yang mendefinisikan adaptor sinkronisasi dan autentikator bagi sistem. +
+
+ Komponen-komponen layanan adaptor sinkronisasi dan autentikator + didefinisikan dalam elemen-elemen +<service> + di manifes aplikasi. Elemen-elemen ini + berisi +<meta-data> +elemen anak yang menyediakan data tertentu ke + sistem: + +
+
+

Data Aliran Sosial

+

+ Tabel-tabel {@code android.provider.ContactsContract.StreamItems} dan + {@code android.provider.ContactsContract.StreamItemPhotos} + mengelola data yang masuk dari jaringan sosial. Anda bisa menulis adaptor sinkronisasi yang menambahkan data aliran + dari jaringan Anda sendiri ke tabel-tabel ini, atau Anda bisa membaca data aliran dari tabel-tabel ini dan + menampilkannya dalam aplikasi sendiri, atau keduanya. Dengan fitur-fitur ini, layanan dan aplikasi + jaringan sosial Anda bisa diintegrasikan ke dalam pengalaman jaringan sosial Android. +

+

Teks aliran sosial

+

+ Item aliran selalu dikaitkan dengan kontak mentah. + {@code android.provider.ContactsContract.StreamItemsColumns#RAW_CONTACT_ID} menautkan ke + nilai _ID untuk kontak mentah. Tipe akun dan nama akun kontak + mentah juga disimpan dalam baris item aliran. +

+

+ Simpanlah data dari aliran Anda dalam kolom-kolom berikut: +

+
+
+ {@code android.provider.ContactsContract.StreamItemsColumns#ACCOUNT_TYPE} +
+
+ Diperlukan. Tipe akun pengguna untuk kontak mentah yang dikaitkan dengan + item aliran ini. Ingatlah untuk mengatur nilai ini saat Anda menyisipkan item aliran. +
+
+ {@code android.provider.ContactsContract.StreamItemsColumns#ACCOUNT_NAME} +
+
+ Diperlukan. Nama akun pengguna untuk kontak mentah yang dikaitkan dengan + item aliran ini. Ingatlah untuk mengatur nilai ini saat Anda menyisipkan item aliran. +
+
+ Kolom identifier +
+
+ Diperlukan. Anda harus memasukkan kolom identifier berikut saat + menyisipkan item aliran: + +
+
+ {@code android.provider.ContactsContract.StreamItemsColumns#COMMENTS} +
+
+ Opsional. Menyimpan informasi rangkuman yang bisa Anda tampilkan di awal item aliran. +
+
+ {@code android.provider.ContactsContract.StreamItemsColumns#TEXT} +
+
+ Teks item aliran, baik konten yang diposting oleh sumber item, + maupun keterangan beberapa tindakan yang menghasilkan item aliran. Kolom ini bisa berisi + sembarang gambar sumber daya pemformatan dan tertanam yang bisa dirender oleh + {@link android.text.Html#fromHtml(String) fromHtml()}. Penyedia bisa memotong atau + menghapus konten yang panjang, tetapi penyedia akan mencoba menghindari memutus tag. +
+
+ {@code android.provider.ContactsContract.StreamItemsColumns#TIMESTAMP} +
+
+ String teks berisi waktu item aliran yang disisipkan atau diperbarui, berupa + milidetik sejak waktu patokan. Aplikasi yang menyisipkan atau memperbarui item aliran + bertanggung jawab memelihara kolom ini; aplikasi tidak dipelihara secara otomatis oleh + Penyedia Kontak. +
+
+

+ Untuk menampilkan informasi pengidentifikasi item aliran Anda, gunakan + {@code android.provider.ContactsContract.StreamItemsColumns#RES_ICON}, + {@code android.provider.ContactsContract.StreamItemsColumns#RES_LABEL}, dan + {@code android.provider.ContactsContract.StreamItemsColumns#RES_PACKAGE} untuk menautkan ke sumber daya + dalam aplikasi Anda. +

+

+ Tabel {@code android.provider.ContactsContract.StreamItems} juga berisi kolom-kolom + {@code android.provider.ContactsContract.StreamItemsColumns#SYNC1} hingga + {@code android.provider.ContactsContract.StreamItemsColumns#SYNC4} untuk penggunaan eksklusif oleh + adaptor sinkronisasi. +

+

Foto aliran sosial

+

+ Tabel {@code android.provider.ContactsContract.StreamItemPhotos} menyimpan foto-foto yang dikaitkan + dengan item aliran. Kolom +{@code android.provider.ContactsContract.StreamItemPhotosColumns#STREAM_ITEM_ID} tabel ini + menautkan ke nilai dalam kolom {@code android.provider.BaseColumns#_ID} + tabel {@code android.provider.ContactsContract.StreamItems}. Acuan foto disimpan dalam + tabel pada kolom-kolom ini: +

+
+
+ Kolom {@code android.provider.ContactsContract.StreamItemPhotos#PHOTO} (BLOB). +
+
+ Representasi biner foto, yang diubah ukurannya oleh penyedia untuk penyimpanan dan tampilan. + Kolom ini tersedia untuk kompatibilitas ke belakang dengan versi Penyedia Kontak + sebelumnya yang menggunakannya untuk menyimpan foto. Akan tetapi, pada versi saat ini + Anda tidak boleh menggunakan kolom ini untuk menyimpan foto. Sebagai gantinya, gunakan + {@code android.provider.ContactsContract.StreamItemPhotosColumns#PHOTO_FILE_ID} atau + {@code android.provider.ContactsContract.StreamItemPhotosColumns#PHOTO_URI} (keduanya + dijelaskan dalam poin-poin berikut) untuk menyimpan foto di file. Kolom ini sekarang + berisi thumbnail foto, yang tersedia untuk dibaca. +
+
+ {@code android.provider.ContactsContract.StreamItemPhotosColumns#PHOTO_FILE_ID} +
+
+ Identifier numerik foto untuk kontak mentah. Tambahkan nilai ini ke konstanta + {@link android.provider.ContactsContract.DisplayPhoto#CONTENT_URI DisplayPhoto.CONTENT_URI} + untuk mendapatkan URI konten yang menunjuk ke satu file foto, kemudian panggil + {@link android.content.ContentResolver#openAssetFileDescriptor(Uri, String) + openAssetFileDescriptor()} untuk mendapatkan handle ke file foto. +
+
+ {@code android.provider.ContactsContract.StreamItemPhotosColumns#PHOTO_URI} +
+
+ URI konten menunjuk langsung ke file foto untuk foto yang diwakili oleh baris ini. + Panggillah {@link android.content.ContentResolver#openAssetFileDescriptor(Uri, String) + openAssetFileDescriptor()} dengan URI ini untuk mendapatkan handle ke file foto. +
+
+

Menggunakan tabel aliran sosial

+

+ Tabel-tabel ini sama fungsinya dengan tabel-tabel utama lainnya dalam Penyedia Kontak, kecuali: +

+ + +

+ Kelas {@code android.provider.ContactsContract.StreamItems.StreamItemPhotos} mendefinisikan + subtabel {@code android.provider.ContactsContract.StreamItemPhotos} yang berisi + baris foto untuk satu item aliran. +

+

Interaksi aliran sosial

+

+ Data aliran sosial yang dikelola oleh Penyedia Kontak, bersama aplikasi kontak + perangkat, menawarkan cara andal untuk menghubungkan sistem jaringan sosial Anda + dengan kontak yang ada. Tersedia fitur-fitur berikut: +

+ +

+ Sinkronisasi rutin item aliran dengan Penyedia Kontak sama dengan + sinkronisasi lainnya. Untuk mengetahui selengkapnya tentang sinkronisasi, lihat bagian + Adaptor Sinkronisasi Penyedia Kontak. Mendaftarkan pemberitahuan dan + mengundang kontak dibahas dalam dua bagian berikutnya. +

+

Pendaftaran untuk menangani tampilan jaringan sosial

+

+ Untuk mendaftarkan adaptor sinkronisasi agar menerima pemberitahuan saat pengguna menampilkan kontak + yang dikelola oleh adaptor sinkronisasi Anda: +

+
    +
  1. + Buat file yang bernama contacts.xml dalam direktori res/xml/ + proyek Anda. Jika sudah memiliki file ini, langkah ini boleh dilewati. +
  2. +
  3. + Dalam file ini, tambahkan elemen +<ContactsAccountType xmlns:android="http://schemas.android.com/apk/res/android">. + Jika elemen ini sudah ada, langkah ini boleh dilewati. +
  4. +
  5. + Untuk mendaftarkan layanan yang diberitahukan saat pengguna membuka halaman detail kontak dalam + aplikasi kontak perangkat, tambahkan atribut + viewContactNotifyService="serviceclass" ke elemen, dengan + serviceclass sebagai nama kelas mutlak (fully qualified) dari layanan + yang seharusnya menerima intent dari aplikasi kontak perangkat. Untuk layanan + notifier, gunakan kelas yang memperluas {@link android.app.IntentService}, guna memudahkan layanan + untuk menerima intent. Data dalam intent yang masuk berisi URI konten dari kontak + mentah yang diklik pengguna. Untuk layanan notifier, Anda bisa mengikatnya ke kemudian memanggil + adaptor sinkronisasi Anda untuk memperbarui data bagi kontak mentah. +
  6. +
+

+ Untuk mendaftarkan aktivitas agar dipanggil saat pengguna mengklik item aliran atau foto atau keduanya: +

+
    +
  1. + Buat file yang bernama contacts.xml dalam direktori res/xml/ + proyek Anda. Jika sudah memiliki file ini, langkah ini boleh dilewati. +
  2. +
  3. + Dalam file ini, tambahkan elemen +<ContactsAccountType xmlns:android="http://schemas.android.com/apk/res/android">. + Jika elemen ini sudah ada, langkah ini boleh dilewati. +
  4. +
  5. + Untuk mendaftarkan salah satu aktivitas Anda guna menangani klik oleh pengguna pada item aliran dalam + aplikasi kontak perangkat, tambahkan atribut + viewStreamItemActivity="activityclass" ke elemen, dengan + activityclass sebagai nama kelas mutlak (fully-qualified) dari aktivitas + yang harus menerima intent dari aplikasi kontak perangkat. +
  6. +
  7. + Untuk mendaftarkan salah satu aktivitas Anda guna menangani klik oleh pengguna pada foto aliran dalam + aplikasi kontak perangkat, tambahkan atribut + viewStreamItemPhotoActivity="activityclass" ke elemen, dengan + activityclass adalah kelas nama mutlak aktivitas + yang harus menerima intent dari aplikasi kontak perangkat. +
  8. +
+

+ Elemen <ContactsAccountType> dijelaskan lebih detail di + bagian Elemen <ContactsAccountType>. +

+

+ Intent yang masuk berisi URI konten dari materi atau foto yang diklik pengguna. + Untuk mendapatkan aktivitas terpisah bagi item teks dan foto, gunakan kedua atribut dalam file yang sama. +

+

Berinteraksi dengan layanan jaringan sosial Anda

+

+ Pengguna tidak harus meninggalkan aplikasi perangkat kontak untuk mengundang kontak ke situs + jaringan sosial Anda. Sebagai gantinya, Anda bisa meminta aplikasi kontak perangkat mengirimkan intent untuk mengundang + kontak ke salah satu aktivitas Anda. Untuk mempersiapkannya: +

+
    +
  1. + Buat file yang bernama contacts.xml dalam direktori res/xml/ + proyek Anda. Jika sudah memiliki file ini, langkah ini boleh dilewati. +
  2. +
  3. + Dalam file ini, tambahkan elemen +<ContactsAccountType xmlns:android="http://schemas.android.com/apk/res/android">. + Jika elemen ini sudah ada, langkah ini boleh dilewati. +
  4. +
  5. + Tambahkan atribut-atribut berikut: + + Nilai activityclass adalah nama kelas mutlak + aktivitas yang harus menerima intent ini. Nilai invite_action_label + adalah string teks yang ditampilkan dalam menu Add Connection dalam + aplikasi kontak perangkat. +
  6. +
+

+ Catatan: ContactsSource adalah nama tag yang sudah usang untuk + ContactsAccountType. +

+

Acuan contacts.xml

+

+ File contacts.xml berisi elemen XML yang mengontrol interaksi adaptor sinkronisasi + Anda dan aplikasi dengan aplikasi kontak dan Penyedia Kontak. Elemen-elemen ini + dijelaskan di bagian-bagian selanjutnya. +

+

Elemen <ContactsAccountType>

+

+ Elemen <ContactsAccountType> mengontrol interaksi aplikasi + Anda dengan aplikasi kontak. Sintaksnya adalah sebagai berikut: +

+
+<ContactsAccountType
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        inviteContactActivity="activity_name"
+        inviteContactActionLabel="invite_command_text"
+        viewContactNotifyService="view_notify_service"
+        viewGroupActivity="group_view_activity"
+        viewGroupActionLabel="group_action_text"
+        viewStreamItemActivity="viewstream_activity_name"
+        viewStreamItemPhotoActivity="viewphotostream_activity_name">
+
+

+ dimuat dalam: +

+

+ res/xml/contacts.xml +

+

+ bisa berisi: +

+

+ <ContactsDataKind> +

+

+ Keterangan: +

+

+ Mendeklarasikan komponen Android dan label UI yang memungkinkan pengguna mengundang salah satu kontak ke + jaringan sosial, memberi tahu pengguna bila salah satu aliran jaringan sosial diperbarui, dan + seterusnya. +

+

+ Perhatikan bahwa awalan atribut android: tidak perlu untuk atribut-atribut + <ContactsAccountType>. +

+

+ Atribut: +

+
+
{@code inviteContactActivity}
+
+ Nama kelas mutlak aktivitas dalam aplikasi yang Anda ingin + aktifkan bila pengguna memilih Add connection dari aplikasi kontak + perangkat. +
+
{@code inviteContactActionLabel}
+
+ String teks yang ditampilkan untuk aktivitas yang ditetapkan dalam + {@code inviteContactActivity}, dalam menu Add connection. + Misalnya, Anda bisa menggunakan string "Ikuti di jaringan saya". Anda bisa menggunakan identifier sumber daya + string untuk tabel ini. +
+
{@code viewContactNotifyService}
+
+ Nama kelas mutlak layanan dalam aplikasi Anda yang harus menerima + pemberitahuan saat pengguna menampilkan kontak. Pemberitahuan ini dikirimkan oleh aplikasi kontak + perangkat; hal ini memungkinkan aplikasi Anda menunda operasi yang banyak memproses data + hingga dibutuhkan. Misalnya, aplikasi Anda bisa merespons pemberitahuan ini + dengan membaca dalam dan menampilkan foto resolusi tinggi kontak dan item aliran sosial + terbaru. Fitur ini dijelaskan lebih detail di bagian + Interaksi aliran sosial. Anda bisa melihat + contoh layanan pemberitahuan dalam file NotifierService.java dalam contoh aplikasi + Sample Sync Adapter. + +
+
{@code viewGroupActivity}
+
+ Nama kelas mutlak aktivitas dalam aplikasi yang bisa menampilkan + informasi grup. Bila pengguna mengklik label grup dalam aplikasi + kontak perangkat, UI aktivitas ini akan ditampilkan. +
+
{@code viewGroupActionLabel}
+
+ Label yang ditampilkan aplikasi kontak untuk kontrol UI yang memungkinkan + pengguna melihat grup dalam aplikasi Anda. +

+ Misalnya, jika Anda menginstal aplikasi Google+ di perangkat dan menyinkronkan + Google+ dengan aplikasi kontak, Anda akan melihat lingkaran Google+ tercantum sebagai grup + dalam tab Groups aplikasi kontak Anda. Jika Anda mengklik + lingkaran Google+, Anda akan melihat orang-orang di lingkaran itu tercantum sebagai satu "grup". Di atas + tampilan, Anda akan melihat ikon Google+; jika mengklik ikon itu, kontrol akan beralih ke + aplikasi Google+. Aplikasi kontak melakukan ini dengan + {@code viewGroupActivity}, yang menggunakan ikon Google+ sebagai nilai + {@code viewGroupActionLabel}. +

+

+ Identifier sumber daya string diperbolehkan untuk atribut ini. +

+
+
{@code viewStreamItemActivity}
+
+ Nama kelas mutlak aktivitas dalam aplikasi Anda + yang diluncurkan aplikasi kontak perangkat bila pengguna mengklik item aliran untuk kontak mentah. +
+
{@code viewStreamItemPhotoActivity}
+
+ Nama kelas mutlak aktivitas yang diluncurkan + aplikasi kontak perangkat bila pengguna mengklik foto dalam item aliran + untuk kontak mentah. +
+
+

Elemen <ContactsDataKind>

+

+ Elemen <ContactsDataKind> mengontrol tampilan baris data custom + aplikasi Anda dalam UI aplikasi kontak. Sintaksnya adalah sebagai berikut: +

+
+<ContactsDataKind
+        android:mimeType="MIMEtype"
+        android:icon="icon_resources"
+        android:summaryColumn="column_name"
+        android:detailColumn="column_name">
+
+

+ dimuat dalam: +

+<ContactsAccountType> +

+ Keterangan: +

+

+ Gunakan elemen ini untuk memerintahkan aplikasi kontak agar menampilkan konten baris data custom sebagai + bagian dari detail kontak mentah. Setiap elemen anak <ContactsDataKind> + <ContactsAccountType> mewakili tipe baris data custom yang + ditambahkan adaptor sinkronisasi Anda ke tabel {@link android.provider.ContactsContract.Data}. Tambahkan satu + elemen <ContactsDataKind> untuk setiap tipe MIME custom yang Anda gunakan. Anda tidak harus + menambahkan elemen jika Anda memiliki baris data custom yang datanya tidak ingin ditampilkan. +

+

+ Atribut: +

+
+
{@code android:mimeType}
+
+ Tipe MIME custom yang telah Anda definisikan untuk salah satu tipe baris data custom dalam + tabel {@link android.provider.ContactsContract.Data}. Misalnya, nilai + vnd.android.cursor.item/vnd.example.locationstatus bisa berupa tipe MIME + custom untuk baris data yang mencatat lokasi kontak yang terakhir diketahui. +
+
{@code android:icon}
+
+ + Sumber daya drawable + Android yang ditampilkan aplikasi kontak di samping data Anda. Gunakan ini untuk menunjukkan kepada + pengguna bahwa data berasal dari layanan Anda. +
+
{@code android:summaryColumn}
+
+ Nama kolom untuk yang pertama dari dua nilai yang diambil dari baris data. Nilai + ditampilkan sebagai baris pertama entri untuk baris data ini. Baris pertama + dimaksudkan untuk digunakan sebagai rangkuman data, tetapi itu bersifat opsional. Lihat juga + android:detailColumn. +
+
{@code android:detailColumn}
+
+ Nama kolom untuk yang kedua dari dua nilai yang diambil dari baris data. Nilai + ditampilkan sebagai baris kedua entri untuk baris data ini. Lihat juga + {@code android:summaryColumn}. +
+
+

Fitur Tambahan Penyedia Kontak

+

+ Di samping fitur-fitur utama yang dijelaskan di bagian sebelumnya, Penyedia Kontak menawarkan + fitur-fitur berguna ini untuk digunakan bersama data kontak: +

+ +

Grup kontak

+

+ Penyedia Kontak secara opsional bisa melabeli kumpulan kontak terkait dengan data + grup. Jika server yang dikaitkan dengan akun pengguna + ingin mempertahankan grup, adaptor sinkronisasi untuk tipe akun dari akun itu harus mentransfer + data grup antara Penyedia Kontak dan server. Bila pengguna menambahkan kontak baru ke + server, kemudian memasukkan kontak ini dalam grup baru, adaptor sinkronisasi harus menambahkan grup baru + ke tabel {@link android.provider.ContactsContract.Groups}. Grup atau grup-grup yang memiliki kontak + disimpan dalam tabel {@link android.provider.ContactsContract.Data}, dengan menggunakan + tipe MIME {@link android.provider.ContactsContract.CommonDataKinds.GroupMembership}. +

+

+ Jika Anda mendesain adaptor sinkronisasi yang akan menambahkan data kontak mentah dari + server ke Penyedia Kontak, dan Anda tidak menggunakan grup, maka Anda harus memberi tahu + penyedia itu agar membuat data Anda terlihat. Dalam kode yang dijalankan bila pengguna menambahkan akun + ke perangkat, perbarui baris {@link android.provider.ContactsContract.Settings} + yang ditambahkan Penyedia Kontak untuk akunnya. Dalam baris ini, atur nilai kolom + {@link android.provider.ContactsContract.SettingsColumns#UNGROUPED_VISIBLE + Settings.UNGROUPED_VISIBLE} ke 1. Bila melakukannya, Penyedia Kontak akan selalu + membuat data kontak Anda terlihat, meskipun Anda tidak menggunakan grup. +

+

Foto kontak

+

+ Tabel {@link android.provider.ContactsContract.Data} menyimpan foto sebagai baris dengan tipe MIME + {@link android.provider.ContactsContract.CommonDataKinds.Photo#CONTENT_ITEM_TYPE + Photo.CONTENT_ITEM_TYPE}. Kolom + {@link android.provider.ContactsContract.RawContactsColumns#CONTACT_ID} baris yang ditautkan ke + kolom {@code android.provider.BaseColumns#_ID} kontak mentah yang memiliki kolom itu. + Kelas {@link android.provider.ContactsContract.Contacts.Photo} mendefinisikan subtabel + {@link android.provider.ContactsContract.Contacts} yang berisi informasi foto untuk foto + utama kontak, yang merupakan foto utama dari kontak mentah utama kontak itu. Demikian pula, + kelas {@link android.provider.ContactsContract.RawContacts.DisplayPhoto} mendefinisikan subtabel + {@link android.provider.ContactsContract.RawContacts} yang berisi informasi foto untuk + foto utama kontak mentah. +

+

+ Dokumentasi acuan untuk {@link android.provider.ContactsContract.Contacts.Photo} dan + {@link android.provider.ContactsContract.RawContacts.DisplayPhoto} berisi contoh-contoh + pengambilan informasi foto. Tidak ada kelas praktis untuk mengambil + thumbnail utama kontak mentah, tetapi Anda bisa mengirim query ke + tabel {@link android.provider.ContactsContract.Data}, dengan memilih + {@code android.provider.BaseColumns#_ID} kontak mentah, + {@link android.provider.ContactsContract.CommonDataKinds.Photo#CONTENT_ITEM_TYPE + Photo.CONTENT_ITEM_TYPE}, dan kolom {@link android.provider.ContactsContract.Data#IS_PRIMARY} + untuk menemukan baris foto utama kontak mentah. +

+

+ Data aliran sosial untuk seseorang bisa juga disertai foto. Data ini disimpan dalam + tabel {@code android.provider.ContactsContract.StreamItemPhotos}, yang dijelaskan lebih detail + di bagian Foto aliran sosial. +

diff --git a/docs/html-intl/intl/in/guide/topics/providers/content-provider-basics.jd b/docs/html-intl/intl/in/guide/topics/providers/content-provider-basics.jd new file mode 100644 index 0000000000000000000000000000000000000000..c4003caba88f3c589462e5a49ff3fab1f00f2524 --- /dev/null +++ b/docs/html-intl/intl/in/guide/topics/providers/content-provider-basics.jd @@ -0,0 +1,1196 @@ +page.title=Dasar-Dasar Penyedia Konten +@jd:body +
+
+ +

Dalam dokumen ini

+
    +
  1. + Ikhtisar +
      +
    1. + Mengakses penyedia +
    2. +
    3. + URI Konten +
    4. +
    +
  2. +
  3. + Mengambil Data dari Penyedia +
      +
    1. + Meminta izin akses baca +
    2. +
    3. + Membuat query +
    4. +
    5. + Menampilkan hasil query +
    6. +
    7. + Mendapatkan data dari hasil query +
    8. +
    +
  4. +
  5. + Izin Penyedia Konten +
  6. +
  7. + Menyisipkan, Memperbarui, dan Menghapus Data +
      +
    1. + Menyisipkan data +
    2. +
    3. + Memperbarui data +
    4. +
    5. + Menghapus data +
    6. +
    +
  8. +
  9. + Tipe Data Penyedia +
  10. +
  11. + Bentuk-Bentuk Alternatif Akses Penyedia +
      +
    1. + Akses batch +
    2. +
    3. + Akses data melalui intent +
    4. +
    +
  12. +
  13. + Kelas-kelas Kontrak +
  14. +
  15. + Acuan Tipe MIME +
  16. +
+ + +

Kelas-kelas utama

+
    +
  1. + {@link android.content.ContentProvider} +
  2. +
  3. + {@link android.content.ContentResolver} +
  4. +
  5. + {@link android.database.Cursor} +
  6. +
  7. + {@link android.net.Uri} +
  8. +
+ + +

Contoh-Contoh Terkait

+
    +
  1. + + Kursor (Orang) +
  2. +
  3. + + Kursor (Telepon) +
  4. +
+ + +

Lihat juga

+
    +
  1. + + Membuat Penyedia Konten +
  2. +
  3. + + Penyedia Kalender +
  4. +
+
+
+ + +

+ Penyedia konten mengelola akses ke repository data pusat. Penyedia + adalah bagian dari aplikasi Android, yang sering menyediakan UI-nya sendiri untuk menggunakan + data. Akan tetapi, penyedia konten terutama dimaksudkan untuk digunakan oleh + aplikasi lain, yang mengakses penyedia itu melalui objek klien penyedia. Bersama-sama, penyedia + dan klien penyedia menawarkan antarmuka standar yang konsisten ke data yang juga menangani + komunikasi antar-proses dan akses data aman. +

+

+ Topik ini menerangkan dasar-dasar dari hal-hal berikut: +

+ + + +

Ikhtisar

+

+ Penyedia konten menyajikan data ke aplikasi eksternal sebagai satu atau beberapa tabel yang + serupa dengan tabel-tabel yang ditemukan dalam database relasional. Sebuah baris mewakili instance beberapa tipe + data yang dikumpulkan penyedia, dan tiap kolom dalam baris mewakili sepotong + data yang dikumpulkan untuk sebuah instance. +

+

+ Misalnya, salah satu penyedia bawaan di platform Android adalah kamus pengguna, yang + menyimpan ejaan kata-kata tidak-standar yang ingin disimpan pengguna. Tabel 1 mengilustrasikan + wujud data yang mungkin ada dalam tabel penyedia ini: +

+

+ Tabel 1: Contoh tabel kamus pengguna. +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
wordapp idfrequencylocale_ID
mapreduceuser1100en_US1
precompileruser14200fr_FR2
appletuser2225fr_CA3
constuser1255pt_BR4
intuser5100en_UK5
+

+ Dalam tabel 1, tiap baris mewakili instance sebuah kata yang mungkin tidak + ditemukan dalam kamus standar. Tiap kolom mewakili beberapa data untuk kata itu, misalnya + bahasa lokal tempat kata itu ditemukan kali pertama. Header kolom adalah nama kolom yang disimpan dalam + penyedia. Untuk mengacu ke bahasa lokal suatu baris, Anda mengacu ke kolom locale-nya. Untuk + penyedia ini, kolom _ID berfungsi sebagai "kunci utama" kolom yang + dipelihara oleh penyedia secara otomatis. +

+

+ Catatan: Penyedia tidak diharuskan memiliki kunci utama, dan tidak diharuskan + menggunakan _ID sebagai nama kolom kunci utama jika kunci itu ada. Akan tetapi, + jika Anda ingin mengikat data dari penyedia ke {@link android.widget.ListView}, salah satu + nama kolom harus _ID. Ketentuan ini dijelaskan secara lebih detail di bagian + Menampilkan hasil query. +

+

Mengakses penyedia

+

+ Aplikasi mengakses data dari penyedia konten dengan + sebuah objek klien {@link android.content.ContentResolver}. Objek ini memiliki metode yang memanggil + metode dengan nama identik dalam objek penyedia, instance salah satu + subkelas konkret dari {@link android.content.ContentProvider}. Metode-metode + {@link android.content.ContentResolver} menyediakan fungsi-fungsi dasar + "CRUD" (create, retrieve, update, dan delete) pada penyimpanan yang persisten. +

+

+ Objek {@link android.content.ContentResolver} dalam + proses aplikasi klien dan objek {@link android.content.ContentProvider} dalam aplikasi yang memiliki + penyedia itu secara otomatis akan menangani komunikasi antar-proses. + {@link android.content.ContentProvider} juga berfungsi sebagai lapisan abstraksi antara + repository datanya dan penampilan eksternal data sebagai tabel. +

+

+ Catatan: Untuk mengakses penyedia, aplikasi Anda biasanya harus meminta + izin tertentu dalam file manifesnya. Hal ini dijelaskan lebih detail di bagian + Izin Penyedia Konten +

+

+ Misalnya, untuk mendapatkan daftar kata dan lokalnya dari Penyedia Kamus Pengguna, + Anda memanggil {@link android.content.ContentResolver#query ContentResolver.query()}. + Metode {@link android.content.ContentResolver#query query()} memanggil + metode {@link android.content.ContentProvider#query ContentProvider.query()} yang didefinisikan oleh + Penyedia Kamus Pengguna. Baris-baris kode berikut menunjukkan sebuah + panggilan {@link android.content.ContentResolver#query ContentResolver.query()}: +

+

+// Queries the user dictionary and returns results
+mCursor = getContentResolver().query(
+    UserDictionary.Words.CONTENT_URI,   // The content URI of the words table
+    mProjection,                        // The columns to return for each row
+    mSelectionClause                    // Selection criteria
+    mSelectionArgs,                     // Selection criteria
+    mSortOrder);                        // The sort order for the returned rows
+
+

+ Tabel 2 menampilkan cara argumen untuk + {@link android.content.ContentResolver#query + query(Uri,projection,selection,selectionArgs,sortOrder)} cocok dengan sebuah pernyataan SELECT di SQL: +

+

+ Tabel 2: Query() dibandingkan dengan query SQL. +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Argumen query()Kata kunci/parameter SELECTCatatan
UriFROM table_nameUri memetakan ke tabel dalam penyedia yang bernama table_name.
projectioncol,col,col,... + projection adalah satu larik kolom yang harus disertakan untuk tiap baris + yang diambil. +
selectionWHERE col = valueselection menetapkan kriteria untuk memilih baris.
selectionArgs + (Tidak ada padanan persis. Argumen pemilihan mengganti ? placeholder dalam + klausa pemilihan.) +
sortOrderORDER BY col,col,... + sortOrder menetapkan urutan munculnya baris dalam + {@link android.database.Cursor} yang dihasilkan. +
+

URI Konten

+

+ URI konten adalah URI yang mengidentifikasi data dalam penyedia. URI Konten + menyertakan nama simbolis seluruh penyedia (otoritasnya) dan sebuah + nama yang menunjuk ke tabel (path). Bila Anda memanggil + metode klien untuk mengakses tabel dalam penyedia, URI konten untuk tabel itu adalah salah satu + argumennya. +

+

+ Dalam baris kode sebelumnya, konstanta + {@link android.provider.UserDictionary.Words#CONTENT_URI} mengandung URI konten dari + tabel "words" kamus pengguna. Objek {@link android.content.ContentResolver} + akan mengurai otoritas URI, dan menggunakannya untuk "mengetahui" penyedia dengan + membandingkan otoritas tersebut dengan sebuah tabel sistem berisi penyedia yang dikenal. +{@link android.content.ContentResolver} kemudian bisa mengirim argumen query ke penyedia + yang benar. +

+

+ {@link android.content.ContentProvider} menggunakan bagian path dari URI konten untuk memilih + tabel yang akan diakses. Penyedia biasanya memiliki path untuk tiap tabel yang dieksposnya. +

+

+ Dalam baris kode sebelumnya, URI lengkap untuk tabel "words" adalah: +

+
+content://user_dictionary/words
+
+

+ dalam hal ini string user_dictionary adalah otoritas penyedia, dan string + words adalah path tabel. String + content:// (skema) selalu ada, + dan mengidentifikasinya sebagai URI konten. +

+

+ Banyak penyedia yang memperbolehkan Anda mengakses satu baris dalam tabel dengan menambahkan sebuah ID nilai + ke akhir URI. Misalnya, untuk mengambil sebuah baris yang _ID-nya adalah + 4 dari kamus pengguna, Anda bisa menggunakan URI konten ini: +

+
+Uri singleUri = ContentUris.withAppendedId(UserDictionary.Words.CONTENT_URI,4);
+
+

+ Anda akan sering menggunakan nilai-nilai ID bila telah mengambil satu set baris kemudian ingin memperbarui atau menghapus + salah satunya. +

+

+ Catatan: Kelas-kelas {@link android.net.Uri} dan {@link android.net.Uri.Builder} + berisi metode praktis untuk membangun objek dari string URI yang tersusun dengan baik. +{@link android.content.ContentUris} berisi metode praktis untuk menambahkan nilai ID ke + URI. Cuplikan kode sebelumnya menggunakan {@link android.content.ContentUris#withAppendedId + withAppendedId()} untuk menambahkan id ke URI konten User Dictionary. +

+ + + +

Mengambil Data dari Penyedia

+

+ Bagian ini menerangkan cara mengambil data dari penyedia, dengan menggunakan Penyedia Kamus Pengguna + sebagai contoh. +

+

+ Demi kejelasan, cuplikan kode di bagian ini memanggil + {@link android.content.ContentResolver#query ContentResolver.query()} pada "UI thread"". Akan tetapi, dalam + kode sesungguhnya, Anda harus melakukan query secara asinkron pada sebuah thread terpisah. Satu cara melakukannya + adalah menggunakan kelas {@link android.content.CursorLoader}, yang dijelaskan + lebih detail dalam panduan + Loader. Juga, baris-baris kode tersebut hanyalah cuplikan; tidak menunjukkan sebuah aplikasi + lengkap. +

+

+ Untuk mengambil data dari penyedia, ikutilah langkah-langkah dasar ini: +

+
    +
  1. + Minta izin akses baca untuk penyedia itu. +
  2. +
  3. + Definisikan kode yang mengirim query ke penyedia. +
  4. +
+

Meminta izin akses baca

+

+ Untuk mengambil data dari penyedia, aplikasi Anda memerlukan "izin akses baca" untuk + penyedia itu. Anda tidak bisa meminta izin ini saat runtime; sebagai gantinya, Anda harus menetapkan bahwa + Anda memerlukan izin ini dalam manifes, dengan menggunakan elemen +<uses-permission> + dan nama persis izin yang didefinisikan oleh + penyedia itu. Bila menetapkan elemen ini dalam manifes, Anda secara efektif "meminta" + izin ini untuk aplikasi Anda. Bila pengguna menginstal aplikasi Anda, mereka secara implisit akan memberikan + permintaan ini. +

+

+ Untuk menemukan nama persis dari izin akses baca untuk penyedia yang sedang Anda gunakan, serta + nama-nama izin akses lain yang digunakan oleh penyedia, lihatlah dalam + dokumentasi penyedia. +

+

+ Peran izin dalam yang mengakses penyedia dijelaskan lebih detail di bagian + Izin Penyedia Konten. +

+

+ Penyedia Kamus Pengguna mendefinisikan izin + android.permission.READ_USER_DICTIONARY dalam file manifesnya, sehingga + aplikasi yang ingin membaca dari penyedia itu harus meminta izin ini. +

+ +

Membuat query

+

+ Langkah berikutnya dalam mengambil data penyedia adalah membuat query. Cuplikan kode pertama ini + mendefinisikan beberapa variabel untuk mengakses Penyedia Kamus Pengguna: +

+
+
+// A "projection" defines the columns that will be returned for each row
+String[] mProjection =
+{
+    UserDictionary.Words._ID,    // Contract class constant for the _ID column name
+    UserDictionary.Words.WORD,   // Contract class constant for the word column name
+    UserDictionary.Words.LOCALE  // Contract class constant for the locale column name
+};
+
+// Defines a string to contain the selection clause
+String mSelectionClause = null;
+
+// Initializes an array to contain selection arguments
+String[] mSelectionArgs = {""};
+
+
+

+ Cuplikan berikutnya menampilkan cara menggunakan + {@link android.content.ContentResolver#query ContentResolver.query()}, dengan menggunakan Penyedia Kamus Pengguna + sebagai contoh. Query klien penyedia serupa dengan query SQL, dan berisi satu + set kolom yang akan dihasilkan, satu set kriteria pemilihan, dan urutan sortir. +

+

+ Set kolom yang harus dikembalikan query disebut dengan proyeksi + (variabel mProjection). +

+

+ Ekspresi yang menetapkan baris yang harus diambil dipecah menjadi klausa pemilihan dan + argumen pemilihan. Klausa pemilihan adalah kombinasi ekspresi logis dan boolean, + nama kolom, dan nilai (variabel mSelectionClause). Jika Anda menetapkan + parameter ? yang bisa diganti, sebagai ganti nilai, metode query akan mengambil nilai + dari larik argumen pemilihan (variabel mSelectionArgs). +

+

+ Dalam cuplikan berikutnya, jika pengguna tidak memasukkan sebuah kata, klausa pemilihan akan diatur ke + null, dan query menghasilkan semua kata dalam penyedia. Jika pengguna memasukkan + sebuah kata, klausa pemilihan akan diatur ke UserDictionary.Words.WORD + " = ?" dan + elemen pertama larik argumen pemilihan diatur ke kata yang dimasukkan pengguna. +

+
+/*
+ * This defines a one-element String array to contain the selection argument.
+ */
+String[] mSelectionArgs = {""};
+
+// Gets a word from the UI
+mSearchString = mSearchWord.getText().toString();
+
+// Remember to insert code here to check for invalid or malicious input.
+
+// If the word is the empty string, gets everything
+if (TextUtils.isEmpty(mSearchString)) {
+    // Setting the selection clause to null will return all words
+    mSelectionClause = null;
+    mSelectionArgs[0] = "";
+
+} else {
+    // Constructs a selection clause that matches the word that the user entered.
+    mSelectionClause = UserDictionary.Words.WORD + " = ?";
+
+    // Moves the user's input string to the selection arguments.
+    mSelectionArgs[0] = mSearchString;
+
+}
+
+// Does a query against the table and returns a Cursor object
+mCursor = getContentResolver().query(
+    UserDictionary.Words.CONTENT_URI,  // The content URI of the words table
+    mProjection,                       // The columns to return for each row
+    mSelectionClause                   // Either null, or the word the user entered
+    mSelectionArgs,                    // Either empty, or the string the user entered
+    mSortOrder);                       // The sort order for the returned rows
+
+// Some providers return null if an error occurs, others throw an exception
+if (null == mCursor) {
+    /*
+     * Insert code here to handle the error. Be sure not to use the cursor! You may want to
+     * call android.util.Log.e() to log this error.
+     *
+     */
+// If the Cursor is empty, the provider found no matches
+} else if (mCursor.getCount() < 1) {
+
+    /*
+     * Insert code here to notify the user that the search was unsuccessful. This isn't necessarily
+     * an error. You may want to offer the user the option to insert a new row, or re-type the
+     * search term.
+     */
+
+} else {
+    // Insert code here to do something with the results
+
+}
+
+

+ Query ini analog dengan pernyataan SQL: +

+
+SELECT _ID, word, locale FROM words WHERE word = <userinput> ORDER BY word ASC;
+
+

+ Dalam pernyataan SQL ini, nama kolom yang sesungguhnya digunakan sebagai ganti konstanta kelas kontrak. +

+

Melindungi dari input merusak

+

+ Jika data dikelola oleh penyedia konten berada dalam database SQL, memasukkan data tak dipercaya eksternal + ke dalam pernyataan SQL mentah bisa menyebabkan injeksi SQL. +

+

+ Perhatikan klausa pemilihan ini: +

+
+// Constructs a selection clause by concatenating the user's input to the column name
+String mSelectionClause =  "var = " + mUserInput;
+
+

+ Jika melakukannya, Anda akan membuat pengguna menyambungkan SQL merusak ke pernyataan SQL Anda. + Misalnya, pengguna bisa memasukkan "nothing; DROP TABLE *;" untuk mUserInput, yang + akan menghasilkan klausa pemilihan var = nothing; DROP TABLE *;. Karena + klausa pemilihan diperlakukan sebagai pernyataan SQL, hal ini bisa menyebabkan penyedia itu menghapus semua + tabel dalam database SQLite yang mendasarinya (kecuali penyedia disiapkan untuk menangkap upaya + injeksi SQL). +

+

+ Untuk menghindari masalah ini, gunakan klausa pemilihan yang menggunakan ? sebagai + parameter yang bisa diganti dan larik argumen pemilihan yang terpisah. Bila Anda melakukannya, input pengguna + akan dibatasi secara langsung pada query agar tidak ditafsirkan sebagai bagian dari pernyataan SQL. + Karena tidak diperlakukan sebagai SQL, input pengguna tidak bisa menyuntikkan SQL merusak. Sebagai ganti menggunakan + penyambungan untuk menyertakan input pengguna, gunakan klausa pemilihan ini: +

+
+// Constructs a selection clause with a replaceable parameter
+String mSelectionClause =  "var = ?";
+
+

+ Buat larik argumen pemilihan seperti ini: +

+
+// Defines an array to contain the selection arguments
+String[] selectionArgs = {""};
+
+

+ Masukkan nilai dalam larik argumen pemilihan seperti ini: +

+
+// Sets the selection argument to the user's input
+selectionArgs[0] = mUserInput;
+
+

+ Sebuah klausa pemilihan yang menggunakan ? sebagai parameter yang bisa diganti dan sebuah larik + argumen pemilihan adalah cara yang lebih disukai untuk menyebutkan pemilihan, sekalipun penyedia tidak + dibuat berdasarkan database SQL. +

+ +

Menampilkan hasil query

+

+ Metode klien {@link android.content.ContentResolver#query ContentResolver.query()} selalu + menghasilkan {@link android.database.Cursor} berisi kolom-kolom yang ditetapkan oleh + proyeksi query untuk baris yang cocok dengan kriteria pemilihan query. Objek + {@link android.database.Cursor} menyediakan akses baca acak ke baris dan kolom yang + dimuatnya. Dengan metode {@link android.database.Cursor}, Anda bisa mengulang baris-baris dalam + hasil, menentukan tipe data tiap kolom, mengambil data dari kolom, dan memeriksa + properti lain dari hasil. Beberapa implementasi {@link android.database.Cursor} + akan memperbarui objek secara otomatis bila data penyedia berubah, atau memicu metode dalam objek pengamat + bila {@link android.database.Cursor} berubah, atau keduanya. +

+

+ Catatan: Penyedia bisa membatasi akses ke kolom berdasarkan sifat + objek yang membuat query. Misalnya, Penyedia Kontak membatasi akses untuk beberapa kolom pada + adaptor sinkronisasi, sehingga tidak akan mengembalikannya ke aktivitas atau layanan. +

+

+ Jika tidak ada baris yang cocok dengan kriteria pemilihan, penyedia + akan mengembalikan objek {@link android.database.Cursor} dengan + {@link android.database.Cursor#getCount Cursor.getCount()} adalah 0 (kursor kosong). +

+

+ Jika terjadi kesalahan internal, hasil query akan bergantung pada penyedia tertentu. Penyedia bisa + memilih untuk menghasilkan null, atau melontarkan {@link java.lang.Exception}. +

+

+ Karena {@link android.database.Cursor} adalah "daftar" baris, cara yang cocok untuk menampilkan + konten {@link android.database.Cursor} adalah mengaitkannya dengan {@link android.widget.ListView} + melalui {@link android.widget.SimpleCursorAdapter}. +

+

+ Cuplikan berikut melanjutkan kode dari cuplikan sebelumnya. Cuplikan ini membuat + objek {@link android.widget.SimpleCursorAdapter} berisi {@link android.database.Cursor} + yang diambil oleh query, dan mengatur objek ini menjadi adaptor bagi + {@link android.widget.ListView}: +

+
+// Defines a list of columns to retrieve from the Cursor and load into an output row
+String[] mWordListColumns =
+{
+    UserDictionary.Words.WORD,   // Contract class constant containing the word column name
+    UserDictionary.Words.LOCALE  // Contract class constant containing the locale column name
+};
+
+// Defines a list of View IDs that will receive the Cursor columns for each row
+int[] mWordListItems = { R.id.dictWord, R.id.locale};
+
+// Creates a new SimpleCursorAdapter
+mCursorAdapter = new SimpleCursorAdapter(
+    getApplicationContext(),               // The application's Context object
+    R.layout.wordlistrow,                  // A layout in XML for one row in the ListView
+    mCursor,                               // The result from the query
+    mWordListColumns,                      // A string array of column names in the cursor
+    mWordListItems,                        // An integer array of view IDs in the row layout
+    0);                                    // Flags (usually none are needed)
+
+// Sets the adapter for the ListView
+mWordList.setAdapter(mCursorAdapter);
+
+

+ Catatan: Untuk mendukung {@link android.widget.ListView} dengan + {@link android.database.Cursor}, kursor harus berisi kolom bernama _ID. + Karena itu, query yang ditampilkan sebelumnya mengambil kolom _ID untuk + tabel "words", walaupun {@link android.widget.ListView} tidak menampilkannya. + Pembatasan ini juga menjelaskan mengapa sebagian besar penyedia memiliki kolom _ID untuk masing-masing + tabelnya. +

+ + +

Mendapatkan data dari hasil query

+

+ Daripada sekadar menampilkan hasil query, Anda bisa menggunakannya untuk tugas-tugas lain. Misalnya, + Anda bisa mengambil ejaan dari kamus pengguna kemudian mencarinya dalam + penyedia lain. Caranya, ulangi baris-baris dalam {@link android.database.Cursor}: +

+
+
+// Determine the column index of the column named "word"
+int index = mCursor.getColumnIndex(UserDictionary.Words.WORD);
+
+/*
+ * Only executes if the cursor is valid. The User Dictionary Provider returns null if
+ * an internal error occurs. Other providers may throw an Exception instead of returning null.
+ */
+
+if (mCursor != null) {
+    /*
+     * Moves to the next row in the cursor. Before the first movement in the cursor, the
+     * "row pointer" is -1, and if you try to retrieve data at that position you will get an
+     * exception.
+     */
+    while (mCursor.moveToNext()) {
+
+        // Gets the value from the column.
+        newWord = mCursor.getString(index);
+
+        // Insert code here to process the retrieved word.
+
+        ...
+
+        // end of while loop
+    }
+} else {
+
+    // Insert code here to report an error if the cursor is null or the provider threw an exception.
+}
+
+

+ Implementasi {@link android.database.Cursor} berisi beberapa metode "get" untuk + mengambil berbagai tipe data dari objek. Misalnya, cuplikan sebelumnya + menggunakan {@link android.database.Cursor#getString getString()}. Implementasi juga memiliki + metode {@link android.database.Cursor#getType getType()} yang menghasilkan nilai yang menunjukkan + tipe data kolom. +

+ + + +

Izin Penyedia Konten

+

+ Aplikasi penyedia bisa menetapkan izin yang harus dimiliki aplikasi lain untuk + mengakses data penyedia. Izin ini akan memastikan bahwa pengguna mengetahui data + yang coba diakses oleh aplikasi. Berdasarkan ketentuan penyedia, aplikasi lain + meminta izin yang diperlukannya untuk mengakses penyedia. Pengguna akhir akan melihat + izin yang diminta saat menginstal aplikasi. +

+

+ Jika aplikasi penyedia tidak menetapkan izin apa pun, maka aplikasi lain tidak memiliki + akses ke data penyedia. Akan tetapi, komponen-komponen dalam aplikasi penyedia selalu memiliki + akses penuh untuk baca dan tulis, izin apa pun yang ditetapkan. +

+

+ Seperti disebutkan sebelumnya, Penyedia Kamus Pengguna mensyaratkan izin + android.permission.READ_USER_DICTIONARY untuk mengambil data darinya. + Penyedia memiliki izin android.permission.WRITE_USER_DICTIONARY + yang terpisah untuk menyisipkan, memperbarui, atau menghapus data. +

+

+ Untuk mendapatkan izin yang diperlukan untuk mengakses penyedia, aplikasi memintanya dengan elemen +<uses-permission> + dalam file manifesnya. Bila Android Package Manager memasang aplikasi, pengguna + harus menyetujui semua izin yang diminta aplikasi. Jika pengguna menyetujui semuanya, + Package Manager akan melanjutkan instalasi; jika pengguna tidak menyetujui, Package Manager + akan membatalkan instalasi. +

+

+ Elemen +<uses-permission> + berikut meminta akses baca ke Penyedia Kamus Pengguna: +

+
+    <uses-permission android:name="android.permission.READ_USER_DICTIONARY">
+
+

+ Dampak izin pada akses penyedia dijelaskan secara lebih detail dalam panduan + Keamanan dan Izin. +

+ + + +

Menyisipkan, Memperbarui, dan Menghapus Data

+

+ Lewat cara yang sama dengan cara mengambil data dari penyedia, Anda juga menggunakan interaksi antara + klien penyedia dan {@link android.content.ContentProvider} penyedia untuk memodifikasi data. + Anda memanggil metode {@link android.content.ContentResolver} dengan argumen yang diteruskan ke + metode {@link android.content.ContentProvider} yang sesuai. Penyedia dan klien penyedia + menangani secara otomatis keamanan dan komunikasi antar-proses. +

+

Menyisipkan data

+

+ Untuk menyisipkan data ke penyedia, Anda memanggil metode + {@link android.content.ContentResolver#insert ContentResolver.insert()}. + Metode ini menyisipkan sebuah baris baru ke penyedia itu dan menghasilkan URI konten untuk baris itu. + Cuplikan ini menampilkan cara menyisipkan sebuah kata baru ke Penyedia Kamus Pengguna: +

+
+// Defines a new Uri object that receives the result of the insertion
+Uri mNewUri;
+
+...
+
+// Defines an object to contain the new values to insert
+ContentValues mNewValues = new ContentValues();
+
+/*
+ * Sets the values of each column and inserts the word. The arguments to the "put"
+ * method are "column name" and "value"
+ */
+mNewValues.put(UserDictionary.Words.APP_ID, "example.user");
+mNewValues.put(UserDictionary.Words.LOCALE, "en_US");
+mNewValues.put(UserDictionary.Words.WORD, "insert");
+mNewValues.put(UserDictionary.Words.FREQUENCY, "100");
+
+mNewUri = getContentResolver().insert(
+    UserDictionary.Word.CONTENT_URI,   // the user dictionary content URI
+    mNewValues                          // the values to insert
+);
+
+

+ Data untuk baris baru masuk ke dalam satu objek {@link android.content.ContentValues}, yang + serupa bentuknya dengan kursor satu-baris. Kolom dalam objek ini tidak perlu memiliki + tipe data yang sama, dan jika Anda tidak ingin menetapkan nilai sama sekali, Anda bisa mengatur kolom + ke null dengan menggunakan {@link android.content.ContentValues#putNull ContentValues.putNull()}. +

+

+ Cuplikan ini tidak menambahkan kolom _ID, karena kolom ini dipelihara + secara otomatis. Penyedia menetapkan sebuah nilai unik _ID ke setiap baris yang + ditambahkan. Penyedia biasanya menggunakan nilai ini sebagai kunci utama tabel. +

+

+ URI konten yang dihasilkan dalam newUri akan mengidentifikasi baris yang baru ditambahkan, dengan + format berikut: +

+
+content://user_dictionary/words/<id_value>
+
+

+ <id_value> adalah konten _ID untuk baris baru. + Kebanyakan penyedia bisa mendeteksi bentuk URI konten ini secara otomatis kemudian melakukan + operasi yang diminta pada baris tersebut. +

+

+ Untuk mendapatkan nilai _ID dari {@link android.net.Uri} yang dihasilkan, panggil + {@link android.content.ContentUris#parseId ContentUris.parseId()}. +

+

Memperbarui data

+

+ Untuk memperbarui sebuah baris, gunakan objek {@link android.content.ContentValues} dengan + nilai-nilai yang diperbarui, persis seperti yang Anda lakukan pada penyisipan, dan kriteria pemilihan persis seperti yang Anda lakukan pada query. + Metode klien yang Anda gunakan adalah + {@link android.content.ContentResolver#update ContentResolver.update()}. Anda hanya perlu menambahkan + nilai-nilai ke objek {@link android.content.ContentValues} untuk kolom yang sedang Anda perbarui. Jika Anda + ingin membersihkan konten kolom, aturlah nilai ke null. +

+

+ Cuplikan berikut mengubah semua baris yang kolom lokalnya memiliki bahasa "en" ke + lokal null. Nilai hasil adalah jumlah baris yang diperbarui: +

+
+// Defines an object to contain the updated values
+ContentValues mUpdateValues = new ContentValues();
+
+// Defines selection criteria for the rows you want to update
+String mSelectionClause = UserDictionary.Words.LOCALE +  "LIKE ?";
+String[] mSelectionArgs = {"en_%"};
+
+// Defines a variable to contain the number of updated rows
+int mRowsUpdated = 0;
+
+...
+
+/*
+ * Sets the updated value and updates the selected words.
+ */
+mUpdateValues.putNull(UserDictionary.Words.LOCALE);
+
+mRowsUpdated = getContentResolver().update(
+    UserDictionary.Words.CONTENT_URI,   // the user dictionary content URI
+    mUpdateValues                       // the columns to update
+    mSelectionClause                    // the column to select on
+    mSelectionArgs                      // the value to compare to
+);
+
+

+ Anda juga harus membersihkan input pengguna bila memanggil + {@link android.content.ContentResolver#update ContentResolver.update()}. Untuk mengetahui selengkapnya tentang + hal ini, bacalah bagian Melindungi dari input merusak. +

+

Menghapus data

+

+ Menghapus baris serupa dengan mengambil baris data: Anda menetapkan kriteria pemilihan untuk baris + yang ingin Anda hapus dan metode klien akan menghasilkan jumlah baris yang dihapus. + Cuplikan berikut menghapus baris yang appid-nya sama dengan "user". Metode menghasilkan + jumlah baris yang dihapus. +

+
+
+// Defines selection criteria for the rows you want to delete
+String mSelectionClause = UserDictionary.Words.APP_ID + " LIKE ?";
+String[] mSelectionArgs = {"user"};
+
+// Defines a variable to contain the number of rows deleted
+int mRowsDeleted = 0;
+
+...
+
+// Deletes the words that match the selection criteria
+mRowsDeleted = getContentResolver().delete(
+    UserDictionary.Words.CONTENT_URI,   // the user dictionary content URI
+    mSelectionClause                    // the column to select on
+    mSelectionArgs                      // the value to compare to
+);
+
+

+ Anda juga harus membersihkan input pengguna bila memanggil + {@link android.content.ContentResolver#delete ContentResolver.delete()}. Untuk mengetahui selengkapnya tentang + hal ini, bacalah bagian Melindungi dari input merusak. +

+ +

Tipe Data Penyedia

+

+ Penyedia konten bisa menawarkan berbagai tipe data. Penyedia Kamus Pengguna hanya menawarkan + teks, namun penyedia juga bisa menawarkan format berikut: +

+ +

+ Tipe data lain yang sering digunakan penyedia adalah Binary Large OBject (BLOB) yang diimplementasikan sebagai + larik byte 64 KB. Anda bisa melihat tipe data yang tersedia dengan memperhatikan metode "get" + kelas {@link android.database.Cursor}. +

+

+ Tipe data tiap kolom dalam penyedia biasanya tercantum dalam dokumentasinya. + Tipe data untuk Penyedia Kamus Pengguna tercantum dalam dokumentasi acuan + untuk kelas kontraknya {@link android.provider.UserDictionary.Words} (kelas kontrak + dijelaskan di bagian Kelas-kelas Kontrak). + Anda juga bisa menentukan tipe data dengan memanggil {@link android.database.Cursor#getType + Cursor.getType()}. +

+

+ Penyedia juga memelihara informasi tipe data MIME untuk tiap URI konten yang didefinisikannya. Anda bisa + menggunakan informasi tipe MIME untuk mengetahui apakah aplikasi Anda bisa menangani data yang + disediakan penyedia, atau memilih tipe penanganan berdasarkan tipe MIME. Anda biasanya memerlukan + tipe MIME saat menggunakan penyedia yang berisi + struktur atau file data yang kompleks. Misalnya, tabel {@link android.provider.ContactsContract.Data} + dalam Penyedia Kontak menggunakan tipe MIME untuk memberi label tipe data kontak yang disimpan di tiap + baris. Untuk mendapatkan tipe MIME yang sesuai dengan URI konten, panggil + {@link android.content.ContentResolver#getType ContentResolver.getType()}. +

+

+ Bagian Acuan Tipe MIME menerangkan + sintaks tipe MIME baik yang standar maupun custom. +

+ + + +

Bentuk-Bentuk Alternatif Akses Penyedia

+

+ Tiga bentuk alternatif akses penyedia adalah penting dalam pengembangan aplikasi: +

+ +

+ Akses batch dan modifikasi melalui intent dijelaskan dalam bagian-bagian berikut. +

+

Akses batch

+

+ Akses batch ke penyedia berguna untuk menyisipkan baris dalam jumlah besar, atau menyisipkan + baris ke dalam beberapa tabel dalam panggilan metode yang sama, atau biasanya melakukan satu set + operasi lintas batas proses sebagai transaksi (operasi atomik). +

+

+ Untuk mengakses penyedia dalam "mode batch", + buat satu larik objek {@link android.content.ContentProviderOperation}, kemudian + kirim larik itu ke penyedia konten dengan + {@link android.content.ContentResolver#applyBatch ContentResolver.applyBatch()}. Anda meneruskan + otoritas penyedia konten ke metode ini, daripada URI konten tertentu. + Ini memungkinkan tiap objek {@link android.content.ContentProviderOperation} dalam larik untuk bekerja + terhadap tabel yang berbeda. Panggilan ke {@link android.content.ContentResolver#applyBatch + ContentResolver.applyBatch()} menghasilkan satu larik hasil. +

+

+ Keterangan kelas kontrak {@link android.provider.ContactsContract.RawContacts} + menyertakan cuplikan kode yang memperagakan penyisipan batch. Contoh aplikasi + Contacts Manager + berisi contoh akses batch dalam file sumber ContactAdder.java-nya +. +

+ +

Akses data melalui intent

+

+ Intent bisa menyediakan akses tidak langsung ke penyedia konten. Anda memperbolehkan pengguna mengakses + data dalam penyedia sekalipun aplikasi Anda tidak memiliki izin akses, baik dengan + mendapatkan intent yang dihasilkan aplikasi yang memiliki izin, atau dengan mengaktifkan + aplikasi yang memiliki izin dan membiarkan pengguna melakukan pekerjaan di dalamnya. +

+

Mendapatkan akses dengan izin sementara

+

+ Anda bisa mengakses data dalam penyedia konten, sekalipun tidak memiliki + izin akses yang sesuai, dengan mengirimkan intent ke aplikasi yang memang memiliki izin dan + menerima hasil berupa intent berisi izin "URI". + Inilah izin untuk URI konten tertentu yang berlaku hingga aktivitas yang menerima + izin selesai. Aplikasi yang memiliki izin tetap akan memberikan + izin sementara dengan mengatur flag dalam intent yang dihasilkan: +

+ +

+ Catatan: Flag ini tidak memberikan akses baca atau tulis umum ke penyedia + yang otoritasnya dimuat dalam URI konten. Aksesnya hanya untuk URI itu sendiri. +

+

+ Penyedia mendefinisikan izin URI untuk URI konten dalam manifesnya, dengan menggunakan atribut +android:grantUriPermission + dari elemen +<provider> +, serta elemen anak +<grant-uri-permission> + dari elemen +<provider>. + Mekanisme izin URI dijelaskan secara lebih detail dalam panduan + Keamanan dan Izin, + di bagian "Izin URI". +

+

+ Misalnya, Anda bisa mengambil data untuk satu kontak di Penyedia Kontak, sekalipun tidak + memiliki izin {@link android.Manifest.permission#READ_CONTACTS}. Anda mungkin ingin melakukan + ini dalam aplikasi yang mengirim kartu ucapan elektronik ke seorang kenalan pada hari ulang tahunnya. Sebagai ganti + meminta {@link android.Manifest.permission#READ_CONTACTS}, yang memberi Anda akses ke semua + kontak pengguna dan semua informasinya, Anda lebih baik membiarkan pengguna mengontrol + kontak-kontak yang akan digunakan oleh aplikasi Anda. Caranya, gunakan proses berikut: +

+
    +
  1. + Aplikasi Anda akan mengirim intent berisi tindakan + {@link android.content.Intent#ACTION_PICK} dan tipe MIME "contacts" + {@link android.provider.ContactsContract.RawContacts#CONTENT_ITEM_TYPE}, dengan menggunakan + metode {@link android.app.Activity#startActivityForResult + startActivityForResult()}. +
  2. +
  3. + Karena intent ini cocok dengan filter intent untuk + aktivitas "pemilihan" aplikasi People, aktivitas akan muncul ke latar depan. +
  4. +
  5. + Dalam aktivitas pemilihan, pengguna memilih sebuah + kontak untuk diperbarui. Bila ini terjadi, aktivitas pemilihan akan memanggil + {@link android.app.Activity#setResult setResult(resultcode, intent)} + untuk membuat intent yang akan diberikan kembali ke aplikasi Anda. Intent itu berisi URI konten + kontak yang dipilih pengguna, dan flag "extras" + {@link android.content.Intent#FLAG_GRANT_READ_URI_PERMISSION}. Semua flag ini memberikan + izin URI ke aplikasi Anda untuk membaca data kontak yang ditunjuk oleh + URI konten. Aktivitas pemilihan kemudian memanggil {@link android.app.Activity#finish()} untuk + mengembalikan kontrol ke aplikasi Anda. +
  6. +
  7. + Aktivitas Anda akan kembali ke latar depan, dan sistem memanggil metode + {@link android.app.Activity#onActivityResult onActivityResult()} + aktivitas Anda. Metode ini menerima intent yang dihasilkan oleh aktivitas pemilihan dalam + aplikasi People. +
  8. +
  9. + Dengan URI konten dari intent yang dihasilkan, Anda bisa membaca data kontak + dari Penyedia Kontak, sekalipun Anda tidak meminta izin akses baca tetap + ke penyedia dalam manifes Anda. Anda kemudian bisa mendapatkan informasi hari ulang tahun si kontak + atau alamat emailnya, kemudian mengirim kartu ucapan elektronik. +
  10. +
+

Menggunakan aplikasi lain

+

+ Satu cara mudah agar pengguna bisa memodifikasi data yang izin aksesnya tidak Anda miliki adalah + mengaktifkan aplikasi yang memiliki izin dan membiarkan pengguna melakukan pekerjaannya di sana. +

+

+ Misalnya, aplikasi Kalender menerima + intent {@link android.content.Intent#ACTION_INSERT}, yang memungkinkan Anda mengaktifkan + UI penyisipan aplikasi itu. Anda bisa meneruskan data "extras" dalam intent ini, yang + digunakan aplikasi untuk mengisi dahulu UI-nya. Karena kejadian berulang memiliki sintaks yang rumit, + cara yang lebih disukai untuk menyisipkan kejadian ke dalam Penyedia Kalender adalah mengaktifkan aplikasi Kalender dengan + {@link android.content.Intent#ACTION_INSERT}, kemudian membiarkan pengguna menyisipkan kejadian di sana. +

+ +

Kelas-kelas Kontrak

+

+ Kelas kontrak mendefinisikan konstanta yang membantu aplikasi menggunakan URI konten, nama + kolom, tindakan intent, dan fitur lain pada penyedia konten. Kelas kontrak tidak + disertakan secara otomatis bersama penyedia; pengembang penyedia harus mendefinisikannya kemudian + membuatnya tersedia bagi pengembang lain. Banyak penyedia yang disertakan pada platform Android + memiliki kelas kontrak yang sesuai dalam {@link android.provider} paketnya. +

+

+ Misalnya, Penyedia Kamus Pengguna memiliki kelas kontrak + {@link android.provider.UserDictionary} yang berisi URI konten dan konstanta nama kolom. URI + konten untuk tabel "words" didefinisikan dalam konstanta + {@link android.provider.UserDictionary.Words#CONTENT_URI UserDictionary.Words.CONTENT_URI}. + Kelas {@link android.provider.UserDictionary.Words} juga berisi konstanta nama kolom, + yang digunakan dalam cuplikan contoh pada panduan ini. Misalnya, sebuah proyeksi query bisa + didefinisikan sebagai: +

+
+String[] mProjection =
+{
+    UserDictionary.Words._ID,
+    UserDictionary.Words.WORD,
+    UserDictionary.Words.LOCALE
+};
+
+

+ Kelas kontrak lain adalah {@link android.provider.ContactsContract} untuk Penyedia Kontak. + Dokumentasi acuan untuk kelas ini menyertakan contoh cuplikan kode. Salah satu + subkelasnya, {@link android.provider.ContactsContract.Intents.Insert}, adalah + kelas kontrak yang berisi konstanta untuk intent dan data intent. +

+ + + +

Acuan Tipe MIME

+

+ Penyedia konten bisa menghasilkan tipe media MIME standar, atau string tipe MIME custom, atau keduanya. +

+

+ Tipe MIME memiliki format +

+
+type/subtype
+
+

+ Misalnya, tipe MIME text/html yang dikenal luas memiliki tipe text dan + subtipe html. Jika penyedia menghasilkan tipe ini untuk sebuah URI, artinya + query dengan URI itu akan menghasilkan teks berisi tag HTML. +

+

+ String tipe MIME custom, yang juga disebut dengan tipe MIME "khusus vendor", memiliki nilai-nilai + tipe dan subtipe yang lebih kompleks. Nilai tipe selalu +

+
+vnd.android.cursor.dir
+
+

+ untuk beberapa baris, atau +

+
+vnd.android.cursor.item
+
+

+ untuk satu baris. +

+

+ Subtipe adalah khusus penyedia. Penyedia bawaan Android biasanya memiliki subtipe + sederhana. Misalnya, bila aplikasi Contacts membuat satu baris untuk nomor telepon, + aplikasi akan mengatur tipe MIME berikut di baris itu: +

+
+vnd.android.cursor.item/phone_v2
+
+

+ Perhatikan bahwa nilai subtipe adalah sekadar phone_v2. +

+

+ Pengembang penyedia lain bisa membuat pola subtipe sendiri berdasarkan + otoritas dan nama-nama tabel penyedia. Misalnya, perhatikan penyedia yang berisi jadwal kereta api. + Otoritas penyedia adalah com.example.trains, dan berisi tabel-tabel + Line1, Line2, dan Line3. Untuk merespons URI konten +

+

+

+content://com.example.trains/Line1
+
+

+ untuk tabel Line1, penyedia menghasilkan tipe MIME +

+
+vnd.android.cursor.dir/vnd.example.line1
+
+

+ Untuk merespons URI konten +

+
+content://com.example.trains/Line2/5
+
+

+ untuk baris 5 di tabel Line2, penyedia menghasilkan tipe MIME +

+
+vnd.android.cursor.item/vnd.example.line2
+
+

+ Kebanyakan penyedia konten mendefinisikan konstanta kelas kontrak untuk tipe MIME yang digunakannya. Kelas kontrak + {@link android.provider.ContactsContract.RawContacts} pada Penyedia Kontak + misalnya, mendefinisikan konstanta + {@link android.provider.ContactsContract.RawContacts#CONTENT_ITEM_TYPE} untuk tipe MIME + baris kontak mentah tunggal. +

+

+ URI konten untuk baris-baris tunggal dijelaskan di bagian + URI Konten. +

diff --git a/docs/html-intl/intl/in/guide/topics/providers/content-provider-creating.jd b/docs/html-intl/intl/in/guide/topics/providers/content-provider-creating.jd new file mode 100644 index 0000000000000000000000000000000000000000..fefce703c2c8c8af1bdaad0dc4a8ded1212a093e --- /dev/null +++ b/docs/html-intl/intl/in/guide/topics/providers/content-provider-creating.jd @@ -0,0 +1,1214 @@ +page.title=Membuat Penyedia Konten +@jd:body +
+
+ + +

Dalam dokumen ini

+
    +
  1. + Mendesain Penyimpanan Data +
  2. +
  3. + Mendesain URI Konten +
  4. +
  5. + Mengimplementasikan Kelas ContentProvider +
      +
    1. + Metode-Metode yang Diperlukan +
    2. +
    3. + Mengimplementasikan metode query() +
    4. +
    5. + Mengimplementasikan metode insert() +
    6. +
    7. + Mengimplementasikan metode delete() +
    8. +
    9. + Mengimplementasikan metode update() +
    10. +
    11. + Mengimplementasikan metode onCreate() +
    12. +
    +
  6. +
  7. + Mengimplementasikan Tipe MIME Penyedia Konten +
      +
    1. + Tipe MIME untuk tabel +
    2. +
    3. + Tipe MIME untuk file +
    4. +
    +
  8. +
  9. + Mengimplementasikan Kelas Kontrak +
  10. +
  11. + Mengimplementasikan Izin Penyedia Konten +
  12. +
  13. + Elemen <provider> +
  14. +
  15. + Intent dan Akses Data +
  16. +
+

Kelas-kelas utama

+
    +
  1. + {@link android.content.ContentProvider} +
  2. +
  3. + {@link android.database.Cursor} +
  4. +
  5. + {@link android.net.Uri} +
  6. +
+

Contoh-Contoh Terkait

+
    +
  1. + + Aplikasi contoh Note Pad + +
  2. +
+

Lihat juga

+
    +
  1. + + Dasar-Dasar Penyedia Konten +
  2. +
  3. + + Penyedia Kalender +
  4. +
+
+
+ + +

+ Penyedia konten mengelola akses ke repository data pusat. Anda mengimplementasikan + penyedia sebagai satu atau beberapa kelas dalam aplikasi Android, bersama elemen-elemen dalam + file manifes. Salah satu kelas Anda mengimplementasikan subkelas + {@link android.content.ContentProvider}, yang merupakan antarmuka antara penyedia Anda dan + aplikasi lain. Walaupun penyedia konten dimaksudkan untuk menyediakan data bagi + aplikasi lain, Anda tentu saja bisa memiliki aktivitas dalam aplikasi yang memungkinkan pengguna + melakukan query dan memodifikasi data yang dikelola oleh penyedia Anda. +

+

+ Bagian selebihnya dalam topik ini adalah daftar langkah-langkah dasar untuk membangun penyedia konten dan daftar + API yang akan digunakan. +

+ + + +

Sebelum Anda Mulai Membangun

+

+ Sebelum Anda mulai membangun penyedia, lakukanlah hal-hal berikut: +

+
    +
  1. + Putuskan apakah Anda memerlukan penyedia konten. Anda perlu membangun sebuah + penyedia konten jika ingin menyediakan salah satu atau beberapa dari fitur berikut: + +

    + Anda tidak mengharuskan penyedia untuk menggunakan database SQLite jika hanya digunakan dalam + aplikasi sendiri. +

    +
  2. +
  3. + Jika Anda belum siap melakukannya, bacalah topik + + Dasar-Dasar Penyedia Konten untuk mengetahui selengkapnya tentang penyedia. +
  4. +
+

+ Berikutnya, ikuti langkah-langkah ini untuk membangun penyedia: +

+
    +
  1. + Desain penyimpanan mentah untuk data Anda. Penyedia konten menawarkan data dengan dua cara: +
    +
    + Data file +
    +
    + Data yang biasanya masuk ke dalam file, misalnya + foto, audio, atau video. Simpan file dalam ruang privat + aplikasi Anda. Untuk merespons permintaan file dari aplikasi lain, + penyedia Anda bisa menawarkan handle ke file tersebut. +
    +
    + Data "terstruktur" +
    +
    + Data yang biasanya masuk ke dalam database, larik, atau struktur serupa. + Simpan data dalam bentuk yang kompatibel dengan tabel berisi baris dan kolom. Baris + mewakili entitas, misalnya satu orang atau satu barang inventori. Kolom mewakili + beberapa data untuk entitas itu, misalnya nama orang atau harga barang. Cara umum untuk + menyimpan tipe data ini adalah dalam database SQLite, namun Anda bisa menggunakan tipe + penyimpanan apa saja yang persisten. Untuk mengetahui selengkapnya tentang tipe penyimpanan yang tersedia di + sistem Android, lihat bagian + Mendesain Penyimpanan Data. +
    +
    +
  2. +
  3. + Definisikan sebuah implementasi konkret kelas {@link android.content.ContentProvider} dan + metode yang diperlukannya. Kelas ini adalah antarmuka antara data Anda dan bagian selebihnya pada + sistem Android. Untuk informasi selengkapnya tentang kelas ini, lihat bagian + Mengimplementasikan Kelas ContentProvider. +
  4. +
  5. + Definisikan string otoritas, semua URI isinya, dan nama-nama kolom penyedia. Jika Anda ingin + penyedia aplikasi menangani intent, definisikan juga semua tindakan intent, data ekstra, + dan flag. Definisikan juga izin yang akan Anda syaratkan terhadap aplikasi yang ingin + mengakses data Anda. Anda harus mempertimbangkan pendefinisian semua nilai ini sebagai konstanta di + kelas kontrak terpisah; nantinya, Anda bisa mengekspos kelas ini kepada pengembang lain. Untuk + informasi selengkapnya tentang URI konten, lihat + bagian Mendesain URI Konten. + Untuk informasi selengkapnya tentang intent, lihat + bagian Intent dan Akses Data. +
  6. +
  7. + Tambahkan bagian opsional lainnya, seperti data contoh atau implementasi + {@link android.content.AbstractThreadedSyncAdapter} yang bisa menyinkronkan data antara + penyedia dan data berbasis cloud. +
  8. +
+ + + +

Mendesain Penyimpanan Data

+

+ Penyedia konten adalah antarmuka ke data yang disimpan dalam format terstruktur. Sebelum membuat + antarmuka, Anda harus memutuskan cara menyimpan data. Anda bisa menyimpan data dalam bentuk apa saja yang Anda + sukai, kemudian mendesain antarmuka untuk membaca dan menulis data yang diperlukan. +

+

+ Berikut ini adalah beberapa teknologi penyimpanan data yang tersedia di Android: +

+ +

+ Pertimbangan desain data +

+

+ Berikut ini adalah beberapa tip untuk mendesain struktur data penyedia: +

+ + +

Mendesain URI Konten

+

+ URI konten adalah URI yang mengidentifikasi data dalam penyedia. URI Konten + berisi nama simbolis seluruh penyedia (otoritasnya) dan sebuah + nama yang menunjuk ke tabel atau file (path). Bagian id opsional menunjuk ke + satu baris dalam tabel. Setiap metode akses data + {@link android.content.ContentProvider} memiliki sebuah URI konten sebagai argumen; hal ini memungkinkan Anda + menentukan tabel, baris, atau file yang akan diakses. +

+

+ Dasar-dasar URI konten dijelaskan dalam topik + + Dasar-Dasar Penyedia Konten. +

+

Mendesain otoritas

+

+ Penyedia biasanya memiliki otoritas tunggal, yang berfungsi sebagai nama internal Android-nya. Untuk + menghindari konflik dengan penyedia lain, Anda harus menggunakan kepemilikan domain internet (secara terbalik) + sebagai basis otoritas penyedia Anda. Karena saran ini juga berlaku untuk + nama-nama paket Android, Anda bisa mendefinisikan otoritas penyedia sebagai perluasan dari nama + paket yang berisi penyedia. Misalnya, jika nama paket Android Anda adalah + com.example.<appname>, Anda harus memberikan penyedia Anda + otoritas com.example.<appname>.provider. +

+

Mendesain struktur path

+

+ Pengembang biasanya membuat URI konten dari otoritas dengan menambahkan path yang menunjuk ke + masing-masing tabel. Misalnya, jika Anda memiliki dua tabel table1 dan + table2, Anda mengombinasikan otoritas dari contoh sebelumnya untuk menghasilkan + URI konten + com.example.<appname>.provider/table1 dan + com.example.<appname>.provider/table2. Path tidak + dibatasi pada segmen tunggal, dan tidak harus berupa tabel untuk masing-masing tingkat path. +

+

Menangani ID URI konten

+

+ Berdasarkan standar, penyedia menawarkan akses ke satu baris dalam tabel dengan menerima URI konten + dengan sebuah nilai ID untuk baris itu di akhir URI. Juga berdasarkan standar, penyedia mencocokkan + nilai ID dengan kolom _ID tabel, dan melakukan akses yang diminta terhadap baris + yang cocok. +

+

+ Standar ini memudahkan pola desain umum untuk aplikasi yang mengakses penyedia. Aplikasi + melakukan query terhadap penyedia dan menampilkan {@link android.database.Cursor} yang dihasilkan + dalam {@link android.widget.ListView} dengan menggunakan {@link android.widget.CursorAdapter}. + Definisi {@link android.widget.CursorAdapter} mengharuskan salah satu kolom dalam + {@link android.database.Cursor} berupa _ID +

+

+ Pengguna kemudian mengambil salah satu baris yang ditampilkan dari UI untuk menemukan atau memodifikasi + data. Aplikasi mengambil baris yang sesuai dari {@link android.database.Cursor} yang mendukung + {@link android.widget.ListView}, mengambil nilai _ID untuk baris ini, menambahkannya ke + URI konten, dan mengirim permintaan akses ke penyedia. Penyedia nanti bisa melakukan + query atau modifikasi terhadap baris yang persis diambil pengguna. +

+

Pola URI konten

+

+ Untuk membantu Anda memilih tindakan yang diambil bagi URI konten yang masuk, API penyedia menyertakan + kelas praktis {@link android.content.UriMatcher}, yang memetakan "pola-pola" URI konten ke + nilai-nilai integer. Anda bisa menggunakan nilai-nilai integer dalam pernyataan switch yang + memilih tindakan yang diinginkan untuk URI konten atau URI yang cocok dengan pola tertentu. +

+

+ Pola URI konten mencocokkan dengan URI konten menggunakan karakter wildcard: +

+ +

+ Sebagai contoh desain dan pemrograman penanganan URI konten, perhatikan penyedia dengan + otoritas com.example.app.provider yang mengenali URI konten berikut + yang menunjuk ke tabel-tabel: +

+ +

+ Penyedia juga mengenali URI konten ini jika baris ID ditambahkan ke URI, + misalnya content://com.example.app.provider/table3/1 untuk baris yang diidentifikasi oleh + 1 dalam table3. +

+

+ Pola-pola URI konten berikut akan menjadi mungkin: +

+
+
+ content://com.example.app.provider/* +
+
+ Mencocokkan URI konten di penyedia. +
+
+ content://com.example.app.provider/table2/*: +
+
+ Mencocokkan URI konten untuk tabel-tabel dataset1 + dan dataset2, namun tidak mencocokkan URI konten untuk table1 atau + table3. +
+
+ content://com.example.app.provider/table3/#: Mencocokkan URI konten + untuk satu baris di table3, misalnya + content://com.example.app.provider/table3/6 untuk baris yang diidentifikasi oleh + 6. +
+
+

+ Cuplikan kode berikut menunjukkan cara kerja metode di {@link android.content.UriMatcher}. + Kode ini menangani URI seluruh tabel secara berbeda dengan URI untuk + satu baris, menggunakan pola URI konten + content://<authority>/<path> untuk tabel, dan + content://<authority>/<path>/<id> untuk satu baris. +

+

+ Metode {@link android.content.UriMatcher#addURI(String, String, int) addURI()} memetakan + otoritas dan path ke nilai integer. Metode {@link android.content.UriMatcher#match(Uri) + match()} menghasilkan nilai integer URI. Pernyataan switch + memilih antara melakukan query seluruh tabel dan melakukan query satu record: +

+
+public class ExampleProvider extends ContentProvider {
+...
+    // Creates a UriMatcher object.
+    private static final UriMatcher sUriMatcher;
+...
+    /*
+     * The calls to addURI() go here, for all of the content URI patterns that the provider
+     * should recognize. 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
+     */
+    sUriMatcher.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.
+     */
+    sUriMatcher.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 (sUriMatcher.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 is not recognized, you should do some error handling here.
+        }
+        // call the code to actually do the query
+    }
+
+

+ Kelas lainnya, {@link android.content.ContentUris}, menyediakan metode praktis untuk menggunakan + bagian id URI konten. Kelas-kelas {@link android.net.Uri} dan + {@link android.net.Uri.Builder} menyertakan metode praktis untuk mengurai + objek {@link android.net.Uri} yang ada dan membuat objek baru. +

+ + +

Mengimplementasikan Kelas ContentProvider

+

+ Instance {@link android.content.ContentProvider} mengelola akses + ke satu set data terstruktur dengan menangani permintaan dari aplikasi lain. Semua bentuk + akses pada akhirnya akan memanggil {@link android.content.ContentResolver}, yang kemudian memanggil + metode konkret {@link android.content.ContentProvider} untuk mendapatkan akses. +

+

Metode-metode yang diperlukan

+

+ Kelas abstrak {@link android.content.ContentProvider} mendefinisikan enam metode abstrak yang + harus Anda implementasikan sebagai bagian dari subkelas konkret Anda sendiri. Semua metode ini kecuali + {@link android.content.ContentProvider#onCreate() onCreate()} dipanggil oleh aplikasi klien + yang berupaya mengakses penyedia konten Anda: +

+
+
+ {@link android.content.ContentProvider#query(Uri, String[], String, String[], String) + query()} +
+
+ Mengambil data dari penyedia Anda. Menggunakan argumen untuk memilih tabel yang akan + di-query, baris dan kolom yang akan dihasilkan, dan urutan sortir hasilnya. + Menghasilkan data berupa objek {@link android.database.Cursor}. +
+
+ {@link android.content.ContentProvider#insert(Uri, ContentValues) insert()} +
+
+ Menyisipkan baris baru ke dalam penyedia Anda. Menggunakan argumen untuk memilih + tabel tujuan dan mendapatkan nilai-nilai kolom yang akan digunakan. Menghasilkan URI konten untuk + baris yang baru disisipkan. +
+
+ {@link android.content.ContentProvider#update(Uri, ContentValues, String, String[]) + update()} +
+
+ Memperbarui baris yang ada di penyedia Anda. Menggunakan argumen untuk memilih tabel dan baris + yang akan diperbarui dan mendapatkan nilai-nilai kolom yang diperbarui. Menghasilkan jumlah baris yang diperbarui. +
+
+ {@link android.content.ContentProvider#delete(Uri, String, String[]) delete()} +
+
+ Menghapus baris dari penyedia Anda. Menggunakan argumen untuk memilih tabel dan baris yang akan + dihapus. Menghasilkan jumlah baris yang dihapus. +
+
+ {@link android.content.ContentProvider#getType(Uri) getType()} +
+
+ Menghasilkan tipe MIME yang sesuai dengan URI konten. Metode ini dijelaskan lebih detail + di bagian Mengimplementasikan Tipe MIME Penyedia Konten. +
+
+ {@link android.content.ContentProvider#onCreate() onCreate()} +
+
+ Inisialisasi penyedia Anda. Sistem Android memanggil metode ini segera setelah + membuat penyedia Anda. Perhatikan bahwa penyedia Anda tidak dibuat hingga + objek {@link android.content.ContentResolver} mencoba mengaksesnya. +
+
+

+ Perhatikan bahwa metode-metode ini memiliki signature yang sama dengan + metode-metode {@link android.content.ContentResolver} yang sama namanya. +

+

+ Implementasi metode-metode ini harus memperhitungkan hal-hal berikut: +

+ +

Mengimplementasikan metode query()

+

+ Metode + {@link android.content.ContentProvider#query(Uri, String[], String, String[], String) + ContentProvider.query()} harus menghasilkan objek {@link android.database.Cursor}, atau jika + gagal, melontarkan {@link java.lang.Exception}. Jika menggunakan database SQLite sebagai + penyimpanan data, Anda bisa mengembalikan{@link android.database.Cursor} yang dikembalikan oleh salah satu metode + query() dari kelas {@link android.database.sqlite.SQLiteDatabase}. + Jika query tidak mencocokkan baris apa pun, Anda harus mengembalikan instance {@link android.database.Cursor} + yang metode {@link android.database.Cursor#getCount()}-nya mengembalikan 0. + Anda harus mengembalikan null hanya jika terjadi kesalahan internal selama proses query. +

+

+ Jika Anda tidak menggunakan database SQLite sebagai penyimpanan data, gunakan salah satu subkelas konkret + {@link android.database.Cursor}. Misalnya, kelas {@link android.database.MatrixCursor} + mengimplementasikan kursor dengan masing-masing baris berupa larik {@link java.lang.Object}. Dengan kelas ini, + gunakan {@link android.database.MatrixCursor#addRow(Object[]) addRow()} untuk menambahkan baris baru. +

+

+ Ingatlah bahwa sistem Android harus mampu mengomunikasikan {@link java.lang.Exception} + lintas batas proses. Android bisa melakukannya untuk eksepsi berikut yang mungkin berguna + dalam menangani kesalahan query: +

+ +

Mengimplementasikan metode insert()

+

+ Metode {@link android.content.ContentProvider#insert(Uri, ContentValues) insert()} menambahkan satu + baris baru ke tabel yang sesuai, dengan menggunakan nilai-nilai dalam argumen {@link android.content.ContentValues}. + Jika kolom nama tidak ada dalam argumen {@link android.content.ContentValues}, Anda + mungkin perlu menyediakan nilai default untuknya, baik dalam kode penyedia atau dalam skema database + Anda. +

+

+ Metode ini harus mengembalikan URI konten untuk baris baru. Untuk membuatnya, tambahkan nilai + _ID baris baru (atau kunci utama lainnya) ke tabel URI konten, dengan menggunakan + {@link android.content.ContentUris#withAppendedId(Uri, long) withAppendedId()}. +

+

Mengimplementasikan metode delete()

+

+ Metode {@link android.content.ContentProvider#delete(Uri, String, String[]) delete()} + tidak harus menghapus baris-baris dari penyimpanan data Anda secara fisik. Jika menggunakan adaptor sinkronisasi + bersama penyedia, Anda harus mempertimbangkan penandaan baris yang dihapus + dengan flag "delete"; bukan menghilangkan baris itu sepenuhnya. Adaptor sinkronisasi bisa + memeriksa baris yang dihapus dan menghilangkannya dari server sebelum menghapusnya dari penyedia. +

+

Mengimplementasikan metode update()

+

+ Metode {@link android.content.ContentProvider#update(Uri, ContentValues, String, String[]) + update()} mengambil argumen {@link android.content.ContentValues} yang sama dengan yang digunakan oleh + {@link android.content.ContentProvider#insert(Uri, ContentValues) insert()}, dan + argumen-argumen selection dan selectionArgs yang sama dengan yang digunakan oleh + {@link android.content.ContentProvider#delete(Uri, String, String[]) delete()} dan + {@link android.content.ContentProvider#query(Uri, String[], String, String[], String) + ContentProvider.query()}. Hal ini bisa memungkinkan Anda menggunakan kembali kode di antara metode-metode ini. +

+

Mengimplementasikan metode onCreate()

+

+ Sistem Android memanggil {@link android.content.ContentProvider#onCreate() + onCreate()} saat memulai penyedia. Anda harus melakukan tugas-tugas inisialisasi yang berjalan cepat saja + dalam metode ini, serta menunda pembuatan database dan pemuatan data hingga penyedia benar-benar + menerima permintaan terhadap data. Jika Anda melakukan tugas yang memakan waktu dalam + {@link android.content.ContentProvider#onCreate() onCreate()}, Anda akan memperlambat + startup penyedia. Pada gilirannya, hal ini akan memperlambat respons dari penyedia terhadap + aplikasi lain. +

+

+ Misalnya, jika menggunakan database SQLite, Anda bisa membuat + sebuah objek {@link android.database.sqlite.SQLiteOpenHelper} baru di + {@link android.content.ContentProvider#onCreate() ContentProvider.onCreate()}, + kemudian membuat tabel-tabel SQL saat pertama kali membuka database itu. Untuk memperlancar hal ini, + saat pertama Anda memanggil {@link android.database.sqlite.SQLiteOpenHelper#getWritableDatabase + getWritableDatabase()}, metode ini memanggil metode + {@link android.database.sqlite.SQLiteOpenHelper#onCreate(SQLiteDatabase) + SQLiteOpenHelper.onCreate()} secara otomatis. +

+

+ Dua cuplikan berikut memperagakan interaksi antara + {@link android.content.ContentProvider#onCreate() ContentProvider.onCreate()} dan + {@link android.database.sqlite.SQLiteOpenHelper#onCreate(SQLiteDatabase) + SQLiteOpenHelper.onCreate()}. Cuplikan pertama adalah implementasi + {@link android.content.ContentProvider#onCreate() ContentProvider.onCreate()}: +

+
+public class ExampleProvider extends ContentProvider
+
+    /*
+     * Defines a handle to the database helper object. The MainDatabaseHelper class is defined
+     * in a following snippet.
+     */
+    private MainDatabaseHelper mOpenHelper;
+
+    // Defines the database name
+    private static final String DBNAME = "mydb";
+
+    // Holds the database object
+    private SQLiteDatabase db;
+
+    public boolean onCreate() {
+
+        /*
+         * Creates a new helper object. This method always returns quickly.
+         * Notice that the database itself isn't created or opened
+         * until SQLiteOpenHelper.getWritableDatabase is called
+         */
+        mOpenHelper = new MainDatabaseHelper(
+            getContext(),        // the application context
+            DBNAME,              // the name of the database)
+            null,                // uses the default SQLite cursor
+            1                    // the version number
+        );
+
+        return true;
+    }
+
+    ...
+
+    // Implements the provider's insert method
+    public Cursor insert(Uri uri, ContentValues values) {
+        // Insert code here to determine which table to open, handle error-checking, and so forth
+
+        ...
+
+        /*
+         * Gets a writeable database. This will trigger its creation if it doesn't already exist.
+         *
+         */
+        db = mOpenHelper.getWritableDatabase();
+    }
+}
+
+

+ Cuplikan berikutnya adalah implementasi + {@link android.database.sqlite.SQLiteOpenHelper#onCreate(SQLiteDatabase) + SQLiteOpenHelper.onCreate()}, yang menyertakan kelas helper: +

+
+...
+// A string that defines the SQL statement for creating a table
+private static final String SQL_CREATE_MAIN = "CREATE TABLE " +
+    "main " +                       // Table's name
+    "(" +                           // The columns in the table
+    " _ID INTEGER PRIMARY KEY, " +
+    " WORD TEXT"
+    " FREQUENCY INTEGER " +
+    " LOCALE TEXT )";
+...
+/**
+ * Helper class that actually creates and manages the provider's underlying data repository.
+ */
+protected static final class MainDatabaseHelper extends SQLiteOpenHelper {
+
+    /*
+     * Instantiates an open helper for the provider's SQLite data repository
+     * Do not do database creation and upgrade here.
+     */
+    MainDatabaseHelper(Context context) {
+        super(context, DBNAME, null, 1);
+    }
+
+    /*
+     * Creates the data repository. This is called when the provider attempts to open the
+     * repository and SQLite reports that it doesn't exist.
+     */
+    public void onCreate(SQLiteDatabase db) {
+
+        // Creates the main table
+        db.execSQL(SQL_CREATE_MAIN);
+    }
+}
+
+ + + +

Mengimplementasikan Tipe MIME Penyedia Konten

+

+ Kelas {@link android.content.ContentProvider} memiliki dua metode untuk menghasilkan tipe-tipe MIME: +

+
+
+ {@link android.content.ContentProvider#getType(Uri) getType()} +
+
+ Salah satu metode wajib yang harus Anda implementasikan untuk setiap penyedia. +
+
+ {@link android.content.ContentProvider#getStreamTypes(Uri, String) getStreamTypes()} +
+
+ Sebuah metode yang diharapkan untuk Anda implementasikan jika penyedia Anda menawarkan file. +
+
+

Tipe MIME untuk tabel

+

+ Metode {@link android.content.ContentProvider#getType(Uri) getType()} mengembalikan + {@link java.lang.String} dengan format MIME yang menjelaskan tipe data yang dikembalikan oleh + argumen URI konten. Argumen {@link android.net.Uri} bisa berupa pola, bukannya URI tertentu; + dalam hal ini, Anda harus mengembalikan tipe data terkait URI konten yang cocok dengan + polanya. +

+

+ Untuk tipe data umum misalnya teks, HTML, atau JPEG, + {@link android.content.ContentProvider#getType(Uri) getType()} harus mengembalikan + tipe MIME standar untuk data itu. Daftar lengkap tipe standar ini tersedia di situs web + IANA MIME Media Types. + +

+

+ Untuk URI konten yang menunjuk ke baris atau baris-baris data tabel, + {@link android.content.ContentProvider#getType(Uri) getType()} harus mengembalikan + tipe MIME dalam format MIME khusus vendor Android: +

+ +

+ Misalnya, jika otoritas penyedia adalah + com.example.app.provider, dan penyedia mengekspos tabel bernama + table1, tipe MIME untuk beberapa baris dalam table1 adalah: +

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

+ Untuk satu baris table1, tipe MIME adalah: +

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

Tipe MIME untuk file

+

+ Jika penyedia Anda menawarkan file, implementasikan + {@link android.content.ContentProvider#getStreamTypes(Uri, String) getStreamTypes()}. + Metode ini menghasilkan larik {@link java.lang.String} tipe MIME untuk file + yang bisa dikembalikan penyedia Anda untuk URI konten bersangkutan. Anda harus memfilter tipe MIME yang Anda tawarkan dengan argumen filter + tipe MIME, sehingga Anda hanya mengembalikan tipe MIME yang ingin ditangani klien. +

+

+ Misalnya, perhatikan penyedia yang menawarkan gambar foto sebagai file dalam format .jpg, + .png, dan .gif. + Jika aplikasi memanggil {@link android.content.ContentResolver#getStreamTypes(Uri, String) + ContentResolver.getStreamTypes()} dengan string filter image/* (sesuatu yang + merupakan "gambar"), + maka metode {@link android.content.ContentProvider#getStreamTypes(Uri, String) + ContentProvider.getStreamTypes()} harus mengembalikan larik: +

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

+ Jika aplikasi tertarik pada file .jpg, maka aplikasi bisa memanggil + {@link android.content.ContentResolver#getStreamTypes(Uri, String) + ContentResolver.getStreamTypes()} dengan string filter *\/jpeg, dan + {@link android.content.ContentProvider#getStreamTypes(Uri, String) + ContentProvider.getStreamTypes()} harus mengembalikan: +

+{"image/jpeg"}
+
+

+ Jika penyedia Anda tidak menawarkan tipe MIME apa pun yang diminta dalam string filter, + {@link android.content.ContentProvider#getStreamTypes(Uri, String) getStreamTypes()} + harus mengembalikan null. +

+ + + +

Mengimplementasikan Kelas Kontrak

+

+ Kelas kontrak adalah kelas public final yang berisi definisi konstanta untuk URI, nama kolom, tipe MIME, dan +metadata lain yang melekat ke penyedia. Kelas + membentuk sebuah kontrak antara penyedia dan aplikasi lain dengan memastikan bahwa penyedia + bisa diakses dengan benar sekalipun ada perubahan pada nilai URI sesungguhnya, nama kolom, + dan seterusnya. +

+

+ Kelas kontrak juga membantu pengembang karena kelas ini biasanya memiliki nama-nama simbolik untuk konstantanya, + sehingga memperkecil kemungkinan pengembang menggunakan nilai yang salah untuk nama kolom atau URI. Karena berupa + kelas, kelas ini bisa berisi dokumentasi Javadoc. Lingkungan pengembangan terpadu (IDE) seperti + Eclipse secara otomatis bisa melengkapi nama-nama konstanta dari kelas kontrak dan menampilkan Javadoc untuk + konstanta. +

+

+ Pengembang tidak bisa mengakses file kelas milik kelas kontrak dari aplikasi Anda, namun bisa + mengompilasinya secara statis ke dalam aplikasi mereka dari file .jar yang Anda sediakan. +

+

+ Kelas {@link android.provider.ContactsContract} dan kelas-kelas tersarangnya adalah contoh + kelas kontrak. +

+

Mengimplementasikan Izin Penyedia Konten

+

+ Izin dan akses untuk semua aspek sistem Android dijelaskan secara detail dalam + topik Keamanan dan Izin. + Topik Penyimpanan Data juga + menjelaskan keamanan dan izin terkait dengan berbagai tipe penyimpanan. + Singkatnya, poin-poin pentingnya adalah: +

+ +

+ Jika Anda ingin menggunakan izin penyedia konten untuk mengontrol akses ke data Anda, maka Anda harus + menyimpan data Anda dalam file internal, database SQLite, atau "cloud" (misalnya, + di server jauh), dan Anda harus membuat file dan database tetap privat bagi aplikasi Anda. +

+

Mengimplementasikan izin

+

+ Semua aplikasi bisa membaca dari atau menulis ke penyedia Anda, sekalipun data yang mendasari adalah + privat, karena secara default penyedia Anda tidak mengatur izin. Untuk mengubahnya, + atur izin untuk penyedia dalam file manifes, dengan menggunakan atribut atau elemen anak + dari elemen + <provider>. Anda bisa mengatur izin yang berlaku pada seluruh penyedia, + atau pada tabel tertentu, atau bahkan pada record tertentu, atau ketiganya. +

+

+ Anda mendefinisikan izin untuk penyedia dengan satu atau beberapa elemen + + <permission> dalam file manifes. Untuk membuat + izin unik bagi penyedia, gunakan scoping (pengaturan lingkup) bergaya Java untuk + atribut + android:name. Misalnya, beri nama izin membaca dengan + com.example.app.provider.permission.READ_PROVIDER. + +

+

+ Daftar berikut menjelaskan lingkup penyedia izin, mulai dengan + izin yang berlaku pada seluruh penyedia kemudian menjadi semakin sempit. + Izin yang lebih sempit akan didahulukan daripada izin yang berlingkup lebih luas: +

+
+
+ Izin baca-tulis tunggal tingkat penyedia +
+
+ Suatu izin yang mengontrol akses baca-tulis bagi seluruh penyedia, ditetapkan + dengan atribut + android:permission dari + elemen + <provider>. +
+
+ Izin baca-tulis terpisah tingkat penyedia +
+
+ Satu izin baca dan satu izin tulis untuk seluruh penyedia. Anda menetapkan keduanya + dengan atribut + android:readPermission dan + + android:writePermission dari elemen + + <provider>. Kedua izin akan didahulukan daripada izin yang diharuskan oleh + + android:permission. +
+
+ Izin tingkat path +
+
+ Izin baca, tulis, atau baca/tulis untuk URI konten dalam penyedia Anda. Anda menetapkan + tiap URI yang ingin dikontrol dengan elemen anak + + <path-permission> dari + elemen + <provider>. Untuk setiap URI konten yang ditetapkan, Anda bisa menetapkan + satu izin baca/tulis, satu izin baca, atau satu izin tulis, atau ketiganya. Izin baca dan + tulis akan didahulukan daripada izin baca/tulis. Juga, + izin tingkat path akan didahulukan daripada izin tingkat penyedia. +
+
+ Izin sementara +
+
+ Tingkat izin yang memberikan akses sementara ke aplikasi, sekalipun aplikasi itu + tidak memiliki izin yang biasanya diminta. Fitur akses + sementara mengurangi jumlah izin yang harus diminta aplikasi dalam + manifesnya. Bila Anda mengaktifkan izin sementara, satu-satunya aplikasi yang memerlukan + izin "permanen" untuk penyedia adalah aplikasi yang mengakses terus-menerus semua + data Anda. +

+ Perhatikan izin yang Anda perlukan untuk mengimplementasikan penyedia dan aplikasi email, bila Anda + ingin memperbolehkan aplikasi penampil gambar dari luar menampilkan lampiran foto dari + penyedia Anda. Untuk memberikan akses yang diperlukan kepada penampil gambar tanpa mengharuskan izin, + siapkan izin sementara untuk URI konten bagi foto. Desainlah aplikasi email Anda agar + bila pengguna ingin menampilkan foto, aplikasi itu akan mengirim intent berisi + URI konten foto dan flag izin ke penampil gambar. Penampil gambar nanti bisa + melakukan query penyedia email untuk mengambil foto, sekalipun penampil itu tidak + memiliki izin baca normal untuk penyedia Anda. +

+

+ Untuk mengaktifkan izin sementara, atur atribut + + android:grantUriPermissions dari + elemen + <provider>, atau tambahkan satu atau beberapa elemen anak + + <grant-uri-permission> ke + elemen + <provider> Anda. Jika menggunakan izin sementara, Anda harus memanggil + {@link android.content.Context#revokeUriPermission(Uri, int) + Context.revokeUriPermission()} kapan saja Anda menghilangkan dukungan untuk URI konten dari + penyedia, dan URI konten dikaitkan dengan izin sementara. +

+

+ Nilai atribut menentukan seberapa banyak penyedia Anda yang dijadikan bisa diakses. + Jika atribut diatur ke true, maka sistem akan memberikan + izin sementara kepada seluruh penyedia, dengan mengesampingkan izin lain yang diharuskan + oleh izin tingkat penyedia atau tingkat path. +

+

+ Jika flag ini diatur ke false, maka Anda harus menambahkan elemen-elemen anak + + <grant-uri-permission> ke + elemen + <provider> Anda. Tiap elemen anak menetapkan URI konten atau + URI yang telah diberi akses sementara. +

+

+ Untuk mendelegasikan akses sementara ke sebuah aplikasi, intent harus berisi + {@link android.content.Intent#FLAG_GRANT_READ_URI_PERMISSION} atau flag + {@link android.content.Intent#FLAG_GRANT_WRITE_URI_PERMISSION}, atau keduanya. Keduanya + diatur dengan metode {@link android.content.Intent#setFlags(int) setFlags()}. +

+

+ Jika atribut + android:grantUriPermissions tidak ada, atribut ini diasumsikan sebagai + false. +

+
+
+ + + + +

Elemen <provider>

+

+ Seperti halnya komponen {@link android.app.Activity} dan {@link android.app.Service}, + subkelas {@link android.content.ContentProvider} + harus didefinisikan dalam file manifes untuk aplikasinya, dengan menggunakan elemen + + <provider>. Sistem Android mendapatkan informasi berikut dari + elemen: +

+
+ Otoritas + ({@code + android:authorities}) +
+
+ Nama-nama simbolik yang mengidentifikasi seluruh penyedia dalam sistem. Atribut + ini dijelaskan lebih detail di bagian + Mendesain URI Konten. +
+
+ Nama kelas penyedia + ( +android:name + ) +
+
+ Kelas yang mengimplementasikan {@link android.content.ContentProvider}. Kelas ini + dijelaskan lebih detail di bagian + Mengimplementasikan Kelas ContentProvider. +
+
+ Izin +
+
+ Atribut-atribut yang menetapkan izin yang harus dimiliki aplikasi lain untuk mengakses + data penyedia: + +

+ Izin dan atribut-atribut yang sesuai dijelaskan lebih detail + di bagian + Mengimplementasikan Izin Penyedia Konten. +

+
+
+ Atribut-atribut startup dan kontrol +
+
+ Atribut-atribut ini menentukan cara dan waktu sistem Android memulai penyedia, + karakteristik proses penyedia, dan pengaturan runtime lainnya: + +

+ Atribut-atribut ini didokumentasikan dengan lengkap dalam topik panduan pengembang untuk elemen + + <provider>. + +

+
+
+ Atribut-atribut informatif +
+
+ Ikon dan label opsional untuk penyedia: + +

+ Atribut-atribut ini didokumentasikan dengan lengkap dalam topik panduan pengembang untuk elemen + + <provider>. +

+
+
+ + +

Intent dan Akses Data

+

+ Aplikasi bisa mengakses penyedia konten secara tidak langsung dengan sebuah {@link android.content.Intent}. + Aplikasi tidak memanggil satu pun dari metode-metode {@link android.content.ContentResolver} atau + {@link android.content.ContentProvider}. Sebagai gantinya, aplikasi mengirim intent yang memulai aktivitas, + yang sering kali merupakan bagian dari aplikasi penyedia sendiri. Aktivitas tujuan bertugas + mengambil dan menampilkan data dalam UI-nya. Bergantung pada tindakan dalam intent, + aktivitas tujuan juga bisa meminta pengguna untuk membuat modifikasi pada data penyedia. + Intent juga bisa berisi data "ekstra" yang menampilkan aktivitas tujuan + dalam UI; pengguna nanti memiliki pilihan untuk mengubah data ini sebelum menggunakannya untuk mengubah + data di penyedia. +

+

+ +

+

+ Anda mungkin perlu menggunakan akses intent guna membantu memastikan integritas data. Penyedia Anda mungkin bergantung + pada data yang disisipkan, diperbarui, dan dihapusnya sesuai dengan logika bisnis yang didefinisikan dengan ketat. Jika + demikian halnya, memperbolehkan aplikasi lain mengubah data Anda secara langsung bisa menyebabkan + data yang tidak sah. Jika Anda ingin pengembang menggunakan akses intent, pastikan untuk mendokumentasikannya secara saksama. + Jelaskan kepada mereka alasan akses intent yang menggunakan UI aplikasi Anda sendiri adalah lebih baik daripada mencoba + memodifikasi data dengan kode mereka. +

+

+ Menangani sebuah intent masuk yang ingin memodifikasi data penyedia Anda tidak berbeda dengan + menangani intent lainnya. Anda bisa mengetahui selengkapnya tentang penggunaan intent dengan membaca topik + Intent dan Filter Intent. +

diff --git a/docs/html-intl/intl/in/guide/topics/providers/content-providers.jd b/docs/html-intl/intl/in/guide/topics/providers/content-providers.jd new file mode 100644 index 0000000000000000000000000000000000000000..fc6f12b6f569cb31c7187a6133d9b7cb6ef5eecc --- /dev/null +++ b/docs/html-intl/intl/in/guide/topics/providers/content-providers.jd @@ -0,0 +1,108 @@ +page.title=Penyedia konten +@jd:body +
+
+ + + +

Topik

+
    +
  1. + + Dasar-Dasar Penyedia Konten +
  2. +
  3. + + Membuat Penyedia Konten +
  4. +
  5. + Penyedia Kalender +
  6. +
  7. + Penyedia Kontak +
  8. +
+ + +

Contoh-Contoh Terkait

+
    +
  1. + + Aplikasi Contact Manager +
  2. +
  3. + + "Kursor (Orang)" + +
  4. +
  5. + + "Kursor (Telepon)" +
  6. +
  7. + + Contoh Adaptor Sinkronisasi +
  8. +
+
+
+

+ Penyedia konten mengelola akses ke set data terstruktur. Penyedia ini membungkus + data, dan menyediakan mekanisme untuk mendefinisikan keamanan data. Penyedia konten adalah antarmuka + standar yang menghubungkan data dalam satu proses dengan kode yang berjalan dalam proses lain. +

+

+ Bila Anda ingin mengakses data di penyedia konten, Anda menggunakan + {@link android.content.ContentResolver} objek dalam + {@link android.content.Context} aplikasi untuk berkomunikasi dengan penyedia sebagai klien. + Objek {@link android.content.ContentResolver} berkomunikasi dengan objek penyedia, yakni + instance kelas yang mengimplementasikan {@link android.content.ContentProvider}. Objek penyedia + menerima permintaan data dari klien, melakukan tindakan yang diminta, dan + mengembalikan hasilnya. +

+

+ Anda tidak perlu mengembangkan penyedia sendiri jika tidak bermaksud untuk berbagi data dengan + aplikasi lain. Akan tetapi, Anda memerlukan penyedia buatan sendiri untuk menyediakan saran pencarian custom + dalam aplikasi Anda sendiri. Anda juga memerlukan penyedia sendiri jika ingin menyalin dan +menempelkan data atau file yang kompleks dari aplikasi Anda ke aplikasi lain. +

+

+ Android sendiri berisi penyedia konten yang mengelola data seperti informasi audio, video, gambar, dan + kontak pribadi. Anda bisa melihat sebagian informasi ini tercantum dalam dokumentasi + acuan untuk paket + android.provider + . Dengan beberapa batasan, semua penyedia ini bisa diakses oleh aplikasi Android + apa saja. +

+ Topik-topik berikut menjelaskan penyedia konten secara lebih detail: +

+
+
+ + Dasar-Dasar Penyedia Konten +
+
+ Cara mengakses data di penyedia konten bila data disusun dalam tabel. +
+
+ + Membuat Penyedia Konten +
+
+ Cara membuat penyedia konten sendiri. +
+
+ + Penyedia Kalender +
+
+ Cara mengakses Penyedia Kalender yang merupakan bagian dari platform Android. +
+
+ + Penyedia Kontak +
+
+ Cara mengakses Penyedia Kontak yang merupakan bagian dari platform Android. +
+
diff --git a/docs/html-intl/intl/in/guide/topics/providers/document-provider.jd b/docs/html-intl/intl/in/guide/topics/providers/document-provider.jd new file mode 100644 index 0000000000000000000000000000000000000000..c066e85b283d4d87b0f93e5c5141631e3a8bc0d6 --- /dev/null +++ b/docs/html-intl/intl/in/guide/topics/providers/document-provider.jd @@ -0,0 +1,916 @@ +page.title=Storage Access Framework +@jd:body +
+
+ +

Dalam dokumen ini + + tampilkan maksimal +

+
    +
  1. + Ikhtisar +
  2. +
  3. + Arus Kontrol +
  4. +
  5. + Menulis Aplikasi Klien +
      +
    1. Mencari dokumen
    2. +
    3. Memproses hasil
    4. +
    5. Memeriksa metadata dokumen
    6. +
    7. Membuka dokumen
    8. +
    9. Membuat dokumen baru
    10. +
    11. Menghapus dokumen
    12. +
    13. Mengedit dokumen
    14. +
    15. Mempertahankan izin
    16. +
    +
  6. +
  7. Menulis Penyedia Dokumen Custom +
      +
    1. Manifes
    2. +
    3. Kontrak
    4. +
    5. Subkelas DocumentsProvider
    6. +
    7. Keamanan
    8. +
    +
  8. + +
+

Kelas-kelas utama

+
    +
  1. {@link android.provider.DocumentsProvider}
  2. +
  3. {@link android.provider.DocumentsContract}
  4. +
+ +

Video

+ +
    +
  1. +DevBytes: Android 4.4 Storage Access Framework: Penyedia
  2. +
  3. +DevBytes: Android 4.4 Storage Access Framework: Klien
  4. +
+ + +

Contoh Kode

+ +
    +
  1. +Penyedia Penyimpanan
  2. +
  3. +Klien Penyimpanan
  4. +
+ +

Lihat Juga

+
    +
  1. + + Dasar-Dasar Penyedia Konten + +
  2. +
+ +
+
+ + +

Android 4.4 (API level 19) memperkenalkan Storage Access Framework (SAF, Kerangka Kerja Akses Penyimpanan). SAF + memudahkan pengguna menyusuri dan membuka dokumen, gambar, dan file lainnya +di semua penyedia penyimpanan dokumen pilihannya. UI standar yang mudah digunakan +memungkinkan pengguna menyusuri file dan mengakses yang terbaru dengan cara konsisten di antara berbagai aplikasi dan penyedia.

+ +

Layanan penyimpanan cloud atau lokal bisa dilibatkan dalam ekosistem ini dengan mengimplementasikan sebuah +{@link android.provider.DocumentsProvider} yang membungkus layanannya. Aplikasi klien +yang memerlukan akses ke dokumen sebuah penyedia bisa berintegrasi dengan SAF cukup dengan beberapa +baris kode.

+ +

SAF terdiri dari berikut ini:

+ + + +

Beberapa fitur yang disediakan oleh SAF adalah sebagai berikut:

+ + +

Ikhtisar

+ +

SAF berpusat di seputar penyedia konten yang merupakan +subkelas dari kelas {@link android.provider.DocumentsProvider}. Dalam penyedia dokumen, data +distrukturkan sebagai hierarki file biasa:

+

data model

+

Gambar 1. Model data penyedia dokumen. Root menunjuk ke satu Document, +yang nanti memulai pemekaran seluruh pohon.

+ +

Perhatikan yang berikut ini:

+ + +

Arus Kontrol

+

Seperti dinyatakan di atas, model data penyedia dokumen dibuat berdasarkan hierarki file +biasa. Akan tetapi, Anda bisa menyimpan secara fisik data dengan cara apa pun yang disukai, +selama data bisa diakses melalui API {@link android.provider.DocumentsProvider}. Misalnya, Anda +bisa menggunakan penyimpanan cloud berbasis tag untuk data Anda.

+ +

Gambar 2 menampilkan contoh cara aplikasi foto bisa menggunakan SAF +untuk mengakses data tersimpan:

+

app

+ +

Gambar 2. Arus Storage Access Framework

+ +

Perhatikan yang berikut ini:

+ + +

Gambar 3 menunjukkan picker yang di digunakan pengguna mencari gambar telah memilih +akun Google Drive:

+ +

picker

+ +

Gambar 3. Picker

+ +

Bila pengguna memilih Google Drive, gambar-gambar akan ditampilkan, seperti yang ditampilkan dalam +gambar 4. Dari titik itu, pengguna bisa berinteraksi dengan gambar dengan cara apa pun +yang didukung oleh penyedia dan aplikasi klien. + +

picker

+ +

Gambar 4. Gambar

+ +

Menulis Aplikasi Klien

+ +

Pada Android 4.3 dan yang lebih rendah, jika Anda ingin aplikasi mengambil file dari +aplikasi lain, aplikasi Anda harus memanggil intent seperti {@link android.content.Intent#ACTION_PICK} +atau {@link android.content.Intent#ACTION_GET_CONTENT}. Pengguna nanti harus memilih +satu aplikasi yang akan digunakan untuk mengambil file dan aplikasi yang dipilih harus menyediakan antarmuka pengguna +bagi untuk menyusuri dan mengambil dari file yang tersedia.

+ +

Pada Android 4.4 dan yang lebih tinggi, Anda mempunyai opsi tambahan dalam menggunakan intent +{@link android.content.Intent#ACTION_OPEN_DOCUMENT}, +yang menampilkan UI picker yang dikontrol oleh sistem yang memungkinkan pengguna +menyusuri semua file yang disediakan aplikasi lain. Dari satu UI ini, pengguna +bisa mengambil file dari aplikasi apa saja yang didukung.

+ +

{@link android.content.Intent#ACTION_OPEN_DOCUMENT} +tidak dimaksudkan untuk menjadi pengganti {@link android.content.Intent#ACTION_GET_CONTENT}. +Yang harus Anda gunakan bergantung pada kebutuhan aplikasi:

+ + + + +

Bagian ini menjelaskan cara menulis aplikasi klien berdasarkan +{@link android.content.Intent#ACTION_OPEN_DOCUMENT} dan +intent {@link android.content.Intent#ACTION_CREATE_DOCUMENT}.

+ + + + +

+Cuplikan berikut menggunakan {@link android.content.Intent#ACTION_OPEN_DOCUMENT} +untuk mencari penyedia dokumen yang +berisi file gambar:

+ +
private static final int READ_REQUEST_CODE = 42;
+...
+/**
+ * Fires an intent to spin up the "file chooser" UI and select an image.
+ */
+public void performFileSearch() {
+
+    // ACTION_OPEN_DOCUMENT is the intent to choose a file via the system's file
+    // browser.
+    Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
+
+    // Filter to only show results that can be "opened", such as a
+    // file (as opposed to a list of contacts or timezones)
+    intent.addCategory(Intent.CATEGORY_OPENABLE);
+
+    // Filter to show only images, using the image MIME data type.
+    // If one wanted to search for ogg vorbis files, the type would be "audio/ogg".
+    // To search for all documents available via installed storage providers,
+    // it would be "*/*".
+    intent.setType("image/*");
+
+    startActivityForResult(intent, READ_REQUEST_CODE);
+}
+ +

Perhatikan yang berikut ini:

+ + +

Memproses Hasil

+ +

Setelah pengguna memilih dokumen di picker, +{@link android.app.Activity#onActivityResult onActivityResult()} akan dipanggil. +URI yang menunjuk ke dokumen yang dipilih dimasukkan dalam parameter {@code resultData} +. Ekstrak URI dengan {@link android.content.Intent#getData getData()}. +Setelah mendapatkannya, Anda bisa menggunakannya untuk mengambil dokumen yang diinginkan pengguna. Misalnya +:

+ +
@Override
+public void onActivityResult(int requestCode, int resultCode,
+        Intent resultData) {
+
+    // The ACTION_OPEN_DOCUMENT intent was sent with the request code
+    // READ_REQUEST_CODE. If the request code seen here doesn't match, it's the
+    // response to some other intent, and the code below shouldn't run at all.
+
+    if (requestCode == READ_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
+        // The document selected by the user won't be returned in the intent.
+        // Instead, a URI to that document will be contained in the return intent
+        // provided to this method as a parameter.
+        // Pull that URI using resultData.getData().
+        Uri uri = null;
+        if (resultData != null) {
+            uri = resultData.getData();
+            Log.i(TAG, "Uri: " + uri.toString());
+            showImage(uri);
+        }
+    }
+}
+
+ +

Memeriksa metadata dokumen

+ +

Setelah Anda memiliki URI untuk dokumen, Anda akan mendapatkan akses ke metadatanya. Cuplikan +ini memegang metadata sebuah dokumen yang disebutkan oleh URI, dan mencatatnya:

+ +
public void dumpImageMetaData(Uri uri) {
+
+    // The query, since it only applies to a single document, will only return
+    // one row. There's no need to filter, sort, or select fields, since we want
+    // all fields for one document.
+    Cursor cursor = getActivity().getContentResolver()
+            .query(uri, null, null, null, null, null);
+
+    try {
+    // moveToFirst() returns false if the cursor has 0 rows.  Very handy for
+    // "if there's anything to look at, look at it" conditionals.
+        if (cursor != null && cursor.moveToFirst()) {
+
+            // Note it's called "Display Name".  This is
+            // provider-specific, and might not necessarily be the file name.
+            String displayName = cursor.getString(
+                    cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME));
+            Log.i(TAG, "Display Name: " + displayName);
+
+            int sizeIndex = cursor.getColumnIndex(OpenableColumns.SIZE);
+            // If the size is unknown, the value stored is null.  But since an
+            // int can't be null in Java, the behavior is implementation-specific,
+            // which is just a fancy term for "unpredictable".  So as
+            // a rule, check if it's null before assigning to an int.  This will
+            // happen often:  The storage API allows for remote files, whose
+            // size might not be locally known.
+            String size = null;
+            if (!cursor.isNull(sizeIndex)) {
+                // Technically the column stores an int, but cursor.getString()
+                // will do the conversion automatically.
+                size = cursor.getString(sizeIndex);
+            } else {
+                size = "Unknown";
+            }
+            Log.i(TAG, "Size: " + size);
+        }
+    } finally {
+        cursor.close();
+    }
+}
+
+ +

Membuka dokumen

+ +

Setelah mendapatkan URI dokumen, Anda bisa membuka dokumen atau melakukan apa saja +yang diinginkan padanya.

+ +

Bitmap

+ +

Berikut ini adalah contoh cara membuka {@link android.graphics.Bitmap}:

+ +
private Bitmap getBitmapFromUri(Uri uri) throws IOException {
+    ParcelFileDescriptor parcelFileDescriptor =
+            getContentResolver().openFileDescriptor(uri, "r");
+    FileDescriptor fileDescriptor = parcelFileDescriptor.getFileDescriptor();
+    Bitmap image = BitmapFactory.decodeFileDescriptor(fileDescriptor);
+    parcelFileDescriptor.close();
+    return image;
+}
+
+ +

Perhatikan bahwa Anda tidak boleh melakukan operasi ini pada thread UI. Lakukan hal ini di latar belakang +, dengan menggunakan {@link android.os.AsyncTask}. Setelah membuka bitmap, Anda +bisa menampilkannya dalam {@link android.widget.ImageView}. +

+ +

Mendapatkan InputStream

+ +

Berikut ini adalah contoh cara mendapatkan {@link java.io.InputStream} dari URI. Dalam cuplikan ini +, baris-baris file dibaca ke dalam sebuah string:

+ +
private String readTextFromUri(Uri uri) throws IOException {
+    InputStream inputStream = getContentResolver().openInputStream(uri);
+    BufferedReader reader = new BufferedReader(new InputStreamReader(
+            inputStream));
+    StringBuilder stringBuilder = new StringBuilder();
+    String line;
+    while ((line = reader.readLine()) != null) {
+        stringBuilder.append(line);
+    }
+    fileInputStream.close();
+    parcelFileDescriptor.close();
+    return stringBuilder.toString();
+}
+
+ +

Membuat dokumen baru

+ +

Aplikasi Anda bisa membuat dokumen baru dalam penyedia dokumen dengan menggunakan intent +{@link android.content.Intent#ACTION_CREATE_DOCUMENT} +. Untuk membuat file, Anda memberikan satu tipe MIME dan satu nama file pada intent, dan +menjalankannya dengan kode permintaan yang unik. Selebihnya akan diurus untuk Anda:

+ + +
+// Here are some examples of how you might call this method.
+// The first parameter is the MIME type, and the second parameter is the name
+// of the file you are creating:
+//
+// createFile("text/plain", "foobar.txt");
+// createFile("image/png", "mypicture.png");
+
+// Unique request code.
+private static final int WRITE_REQUEST_CODE = 43;
+...
+private void createFile(String mimeType, String fileName) {
+    Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
+
+    // Filter to only show results that can be "opened", such as
+    // a file (as opposed to a list of contacts or timezones).
+    intent.addCategory(Intent.CATEGORY_OPENABLE);
+
+    // Create a file with the requested MIME type.
+    intent.setType(mimeType);
+    intent.putExtra(Intent.EXTRA_TITLE, fileName);
+    startActivityForResult(intent, WRITE_REQUEST_CODE);
+}
+
+ +

Setelah membuat dokumen baru, Anda bisa mendapatkan URI-nya dalam +{@link android.app.Activity#onActivityResult onActivityResult()}, sehingga Anda +bisa terus menulis ke dokumen itu.

+ +

Menghapus dokumen

+ +

Jika Anda memiliki URI dokumen dan +{@link android.provider.DocumentsContract.Document#COLUMN_FLAGS Document.COLUMN_FLAGS} + dokumen berisi +{@link android.provider.DocumentsContract.Document#FLAG_SUPPORTS_DELETE SUPPORTS_DELETE}, +Anda bisa menghapus dokumen tersebut. Misalnya:

+ +
+DocumentsContract.deleteDocument(getContentResolver(), uri);
+
+ +

Mengedit dokumen

+ +

Anda bisa menggunakan SAF untuk mengedit dokumen teks langsung di tempatnya. +Cuplikan ini memicu +intent {@link android.content.Intent#ACTION_OPEN_DOCUMENT} dan menggunakan +kategori {@link android.content.Intent#CATEGORY_OPENABLE} untuk menampilkan +dokumen yang bisa dibuka saja. Ini akan menyaring lebih jauh untuk menampilkan file teks saja:

+ +
+private static final int EDIT_REQUEST_CODE = 44;
+/**
+ * Open a file for writing and append some text to it.
+ */
+ private void editDocument() {
+    // ACTION_OPEN_DOCUMENT is the intent to choose a file via the system's
+    // file browser.
+    Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
+
+    // Filter to only show results that can be "opened", such as a
+    // file (as opposed to a list of contacts or timezones).
+    intent.addCategory(Intent.CATEGORY_OPENABLE);
+
+    // Filter to show only text files.
+    intent.setType("text/plain");
+
+    startActivityForResult(intent, EDIT_REQUEST_CODE);
+}
+
+ +

Berikutnya, dari {@link android.app.Activity#onActivityResult onActivityResult()} +(lihat Memproses hasil) Anda bisa memanggil kode untuk mengedit. +Cuplikan berikut mendapatkan {@link java.io.FileOutputStream} +dari {@link android.content.ContentResolver}. Secara default, snipet menggunakan mode “tulis”. +Inilah praktik terbaik untuk meminta jumlah akses minimum yang Anda perlukan, jadi jangan meminta +baca/tulis jika yang Anda perlukan hanyalah tulis:

+ +
private void alterDocument(Uri uri) {
+    try {
+        ParcelFileDescriptor pfd = getActivity().getContentResolver().
+                openFileDescriptor(uri, "w");
+        FileOutputStream fileOutputStream =
+                new FileOutputStream(pfd.getFileDescriptor());
+        fileOutputStream.write(("Overwritten by MyCloud at " +
+                System.currentTimeMillis() + "\n").getBytes());
+        // Let the document provider know you're done by closing the stream.
+        fileOutputStream.close();
+        pfd.close();
+    } catch (FileNotFoundException e) {
+        e.printStackTrace();
+    } catch (IOException e) {
+        e.printStackTrace();
+    }
+}
+ +

Mempertahankan izin

+ +

Bila aplikasi Anda membuka file untuk membaca atau menulis, sistem akan memberi +aplikasi Anda izin URI untuk file itu. Pemberian ini berlaku hingga perangkat pengguna di-restart. +Namun anggaplah aplikasi Anda adalah aplikasi pengeditan gambar, dan Anda ingin pengguna bisa +mengakses 5 gambar terakhir yang dieditnya, langsung dari aplikasi Anda. Jika perangkat pengguna telah +di-restart, maka Anda harus mengirim pengguna kembali ke picker sistem untuk menemukan +file, hal ini jelas tidak ideal.

+ +

Untuk mencegah terjadinya hal ini, Anda bisa mempertahankan izin yang diberikan +sistem ke aplikasi Anda. Secara efektif, aplikasi Anda akan "mengambil" pemberian izin URI yang bisa dipertahankan +yang ditawarkan oleh sistem. Hal ini memberi pengguna akses kontinu ke file +melalui aplikasi Anda, sekalipun perangkat telah di-restart:

+ + +
final int takeFlags = intent.getFlags()
+            & (Intent.FLAG_GRANT_READ_URI_PERMISSION
+            | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+// Check for the freshest data.
+getContentResolver().takePersistableUriPermission(uri, takeFlags);
+ +

Ada satu langkah akhir. Anda mungkin telah menyimpan +URI terbaru yang diakses aplikasi, namun URI itu mungkin tidak lagi valid,—aplikasi lain +mungkin telah menghapus atau memodifikasi dokumen. Karena itu, Anda harus selalu memanggil +{@code getContentResolver().takePersistableUriPermission()} untuk memeriksa +data terbaru.

+ +

Menulis Penyedia Dokumen Custom

+ +

+Jika Anda sedang mengembangkan aplikasi yang menyediakan layanan penyimpanan untuk file (misalnya +layanan penyimpanan cloud), Anda bisa menyediakan file melalui +SAF dengan menulis penyedia dokumen custom. Bagian ini menjelaskan +caranya.

+ + +

Manifes

+ +

Untuk mengimplementasikan penyedia dokumen custom, tambahkan yang berikut ini ke manifes aplikasi +Anda:

+ +

Berikut ini adalah kutipan contoh manifes berisi penyedia yang:

+ +
<manifest... >
+    ...
+    <uses-sdk
+        android:minSdkVersion="19"
+        android:targetSdkVersion="19" />
+        ....
+        <provider
+            android:name="com.example.android.storageprovider.MyCloudProvider"
+            android:authorities="com.example.android.storageprovider.documents"
+            android:grantUriPermissions="true"
+            android:exported="true"
+            android:permission="android.permission.MANAGE_DOCUMENTS"
+            android:enabled="@bool/atLeastKitKat">
+            <intent-filter>
+                <action android:name="android.content.action.DOCUMENTS_PROVIDER" />
+            </intent-filter>
+        </provider>
+    </application>
+
+</manifest>
+ +

Mendukung perangkat yang menjalankan Android 4.3 dan yang lebih rendah

+ +

Intent +{@link android.content.Intent#ACTION_OPEN_DOCUMENT} hanya tersedia +pada perangkat yang menjalankan Android 4.4 dan yang lebih tinggi. +Jika ingin aplikasi Anda mendukung {@link android.content.Intent#ACTION_GET_CONTENT} +untuk mengakomodasi perangkat yang menjalankan Android 4.3 dan yang lebih rendah, Anda harus +menonaktifkan filter inten {@link android.content.Intent#ACTION_GET_CONTENT} dalam +manifes untuk perangkat yang menjalankan Android 4.4 atau yang lebih tinggi. Penyedia +dokumen dan {@link android.content.Intent#ACTION_GET_CONTENT} harus dianggap +saling eksklusif. Jika Anda mendukung keduanya sekaligus, aplikasi Anda akan +muncul dua kali dalam UI picker sistem, yang menawarkan dua cara mengakses +data tersimpan Anda. Hal ini akan membingungkan pengguna.

+ +

Berikut ini adalah cara yang disarankan untuk menonaktifkan +filter intent {@link android.content.Intent#ACTION_GET_CONTENT} untuk perangkat +yang menjalankan Android versi 4.4 atau yang lebih tinggi:

+ +
    +
  1. Dalam file sumber daya {@code bool.xml} Anda di bawah {@code res/values/}, tambahkan +baris ini:
    <bool name="atMostJellyBeanMR2">true</bool>
  2. + +
  3. Dalam file sumber daya {@code bool.xml} Anda di bawah {@code res/values-v19/}, tambahkan +baris ini:
    <bool name="atMostJellyBeanMR2">false</bool>
  4. + +
  5. Tambahkan +alias +aktivitas untuk menonaktifkan filter intent {@link android.content.Intent#ACTION_GET_CONTENT} +bagi versi 4.4 (API level 19) dan yang lebih tinggi. Misalnya: + +
    +<!-- This activity alias is added so that GET_CONTENT intent-filter
    +     can be disabled for builds on API level 19 and higher. -->
    +<activity-alias android:name="com.android.example.app.MyPicker"
    +        android:targetActivity="com.android.example.app.MyActivity"
    +        ...
    +        android:enabled="@bool/atMostJellyBeanMR2">
    +    <intent-filter>
    +        <action android:name="android.intent.action.GET_CONTENT" />
    +        <category android:name="android.intent.category.OPENABLE" />
    +        <category android:name="android.intent.category.DEFAULT" />
    +        <data android:mimeType="image/*" />
    +        <data android:mimeType="video/*" />
    +    </intent-filter>
    +</activity-alias>
    +
    +
  6. +
+

Kontrak

+ +

Biasanya bila Anda menulis penyedia konten custom, salah satu tugas adalah +mengimplementasikan kelas kontrak, seperti dijelaskan dalam panduan pengembang + +Penyedia Konten. Kelas kontrak adalah kelas {@code public final} +yang berisi definisi konstanta untuk URI, nama kolom, tipe MIME, dan +metadata lain yang berkenaan dengan penyedia. SAF +menyediakan kelas-kelas kontrak ini untuk Anda, jadi Anda tidak perlu menulisnya +sendiri:

+ + + +

Misalnya, berikut ini adalah kolom-kolom yang bisa Anda hasilkan di kursor bila +penyedia dokumen Anda membuat query dokumen atau akar:

+ +
private static final String[] DEFAULT_ROOT_PROJECTION =
+        new String[]{Root.COLUMN_ROOT_ID, Root.COLUMN_MIME_TYPES,
+        Root.COLUMN_FLAGS, Root.COLUMN_ICON, Root.COLUMN_TITLE,
+        Root.COLUMN_SUMMARY, Root.COLUMN_DOCUMENT_ID,
+        Root.COLUMN_AVAILABLE_BYTES,};
+private static final String[] DEFAULT_DOCUMENT_PROJECTION = new
+        String[]{Document.COLUMN_DOCUMENT_ID, Document.COLUMN_MIME_TYPE,
+        Document.COLUMN_DISPLAY_NAME, Document.COLUMN_LAST_MODIFIED,
+        Document.COLUMN_FLAGS, Document.COLUMN_SIZE,};
+
+ +

Subkelas DocumentsProvider

+ +

Langkah berikutnya dalam menulis penyedia dokumen custom adalah menjadikan +kelas abstrak sebagai subkelas {@link android.provider.DocumentsProvider}. Setidaknya, Anda perlu + mengimplementasikan metode berikut:

+ + + +

Hanya inilah metode yang diwajibkan kepada Anda secara ketat untuk diimplementasikan, namun ada +banyak lagi yang mungkin Anda inginkan. Lihat {@link android.provider.DocumentsProvider} +untuk detailnya.

+ +

Mengimplementasikan queryRoots

+ +

Implementasi {@link android.provider.DocumentsProvider#queryRoots +queryRoots()} oleh Anda harus menghasilkan {@link android.database.Cursor} yang menunjuk ke semua +direktori akar penyedia dokumen, dengan menggunakan kolom-kolom yang didefinisikan dalam +{@link android.provider.DocumentsContract.Root}.

+ +

Dalam cuplikan berikut, parameter {@code projection} mewakili bidang-bidang +tertentu yang ingin didapatkan kembali oleh pemanggil. Cuplikan ini membuat kursor baru +dan menambahkan satu baris ke satu akar— kursor, satu direktori level atas, seperti +Downloads atau Images. Kebanyakan penyedia hanya mempunyai satu akar. Anda bisa mempunyai lebih dari satu, +misalnya, jika ada banyak akun pengguna. Dalam hal itu, cukup tambahkan sebuah +baris kedua ke kursor.

+ +
+@Override
+public Cursor queryRoots(String[] projection) throws FileNotFoundException {
+
+    // Create a cursor with either the requested fields, or the default
+    // projection if "projection" is null.
+    final MatrixCursor result =
+            new MatrixCursor(resolveRootProjection(projection));
+
+    // If user is not logged in, return an empty root cursor.  This removes our
+    // provider from the list entirely.
+    if (!isUserLoggedIn()) {
+        return result;
+    }
+
+    // It's possible to have multiple roots (e.g. for multiple accounts in the
+    // same app) -- just add multiple cursor rows.
+    // Construct one row for a root called "MyCloud".
+    final MatrixCursor.RowBuilder row = result.newRow();
+    row.add(Root.COLUMN_ROOT_ID, ROOT);
+    row.add(Root.COLUMN_SUMMARY, getContext().getString(R.string.root_summary));
+
+    // FLAG_SUPPORTS_CREATE means at least one directory under the root supports
+    // creating documents. FLAG_SUPPORTS_RECENTS means your application's most
+    // recently used documents will show up in the "Recents" category.
+    // FLAG_SUPPORTS_SEARCH allows users to search all documents the application
+    // shares.
+    row.add(Root.COLUMN_FLAGS, Root.FLAG_SUPPORTS_CREATE |
+            Root.FLAG_SUPPORTS_RECENTS |
+            Root.FLAG_SUPPORTS_SEARCH);
+
+    // COLUMN_TITLE is the root title (e.g. Gallery, Drive).
+    row.add(Root.COLUMN_TITLE, getContext().getString(R.string.title));
+
+    // This document id cannot change once it's shared.
+    row.add(Root.COLUMN_DOCUMENT_ID, getDocIdForFile(mBaseDir));
+
+    // The child MIME types are used to filter the roots and only present to the
+    //  user roots that contain the desired type somewhere in their file hierarchy.
+    row.add(Root.COLUMN_MIME_TYPES, getChildMimeTypes(mBaseDir));
+    row.add(Root.COLUMN_AVAILABLE_BYTES, mBaseDir.getFreeSpace());
+    row.add(Root.COLUMN_ICON, R.drawable.ic_launcher);
+
+    return result;
+}
+ +

Mengimplementasikan queryChildDocuments

+ +

Implementasi +{@link android.provider.DocumentsProvider#queryChildDocuments queryChildDocuments()} +oleh Anda harus menghasilkan {@link android.database.Cursor} yang menunjuk ke semua file dalam +direktori yang ditentukan, dengan menggunakan kolom-kolom yang didefinisikan dalam +{@link android.provider.DocumentsContract.Document}.

+ +

Metode ini akan dipanggil bila Anda memilih akar aplikasi dalam picker UI. +Metode mengambil dokumen anak dari direktori di bawah akar. Metode ini bisa dipanggil pada level apa saja dalam +hierarki file, bukan hanya akar. Cuplikan ini +membuat kursor baru dengan kolom-kolom yang diminta, lalu menambahkan informasi tentang +setiap anak langsung dalam direktori induk ke kursor. +Satu anak bisa berupa gambar, direktori lain—file apa saja:

+ +
@Override
+public Cursor queryChildDocuments(String parentDocumentId, String[] projection,
+                              String sortOrder) throws FileNotFoundException {
+
+    final MatrixCursor result = new
+            MatrixCursor(resolveDocumentProjection(projection));
+    final File parent = getFileForDocId(parentDocumentId);
+    for (File file : parent.listFiles()) {
+        // Adds the file's display name, MIME type, size, and so on.
+        includeFile(result, null, file);
+    }
+    return result;
+}
+
+ +

Mengimplementasikan queryDocument

+ +

Implementasi +{@link android.provider.DocumentsProvider#queryDocument queryDocument()} +oleh Anda harus menghasilkan {@link android.database.Cursor} yang menunjuk ke file yang disebutkan, +dengan menggunakan kolom-kolom yang didefinisikan dalam {@link android.provider.DocumentsContract.Document}. +

+ +

Metode {@link android.provider.DocumentsProvider#queryDocument queryDocument()} +menghasilkan informasi yang sama yang diteruskan dalam +{@link android.provider.DocumentsProvider#queryChildDocuments queryChildDocuments()}, +namun untuk file tertentu:

+ + +
@Override
+public Cursor queryDocument(String documentId, String[] projection) throws
+        FileNotFoundException {
+
+    // Create a cursor with the requested projection, or the default projection.
+    final MatrixCursor result = new
+            MatrixCursor(resolveDocumentProjection(projection));
+    includeFile(result, documentId, null);
+    return result;
+}
+
+ +

Mengimplementasikan openDocument

+ +

Anda harus mengimplementasikan {@link android.provider.DocumentsProvider#openDocument +openDocument()} untuk menghasilkan {@link android.os.ParcelFileDescriptor} yang mewakili +file yang disebutkan. Aplikasi lain bisa menggunakan {@link android.os.ParcelFileDescriptor} +yang dihasilkan untuk mengalirkan data. Sistem memanggil metode ini setelah pengguna memilih file +dan aplikasi klien meminta akses ke file itu dengan memanggil +{@link android.content.ContentResolver#openFileDescriptor openFileDescriptor()}. +Misalnya:

+ +
@Override
+public ParcelFileDescriptor openDocument(final String documentId,
+                                         final String mode,
+                                         CancellationSignal signal) throws
+        FileNotFoundException {
+    Log.v(TAG, "openDocument, mode: " + mode);
+    // It's OK to do network operations in this method to download the document,
+    // as long as you periodically check the CancellationSignal. If you have an
+    // extremely large file to transfer from the network, a better solution may
+    // be pipes or sockets (see ParcelFileDescriptor for helper methods).
+
+    final File file = getFileForDocId(documentId);
+
+    final boolean isWrite = (mode.indexOf('w') != -1);
+    if(isWrite) {
+        // Attach a close listener if the document is opened in write mode.
+        try {
+            Handler handler = new Handler(getContext().getMainLooper());
+            return ParcelFileDescriptor.open(file, accessMode, handler,
+                        new ParcelFileDescriptor.OnCloseListener() {
+                @Override
+                public void onClose(IOException e) {
+
+                    // Update the file with the cloud server. The client is done
+                    // writing.
+                    Log.i(TAG, "A file with id " +
+                    documentId + " has been closed!
+                    Time to " +
+                    "update the server.");
+                }
+
+            });
+        } catch (IOException e) {
+            throw new FileNotFoundException("Failed to open document with id "
+            + documentId + " and mode " + mode);
+        }
+    } else {
+        return ParcelFileDescriptor.open(file, accessMode);
+    }
+}
+
+ +

Keamanan

+ +

Anggaplah penyedia dokumen Anda sebuah layanan penyimpanan cloud yang dilindungi kata sandi +dan Anda ingin memastikan bahwa pengguna sudah login sebelum Anda mulai berbagi file mereka. +Apakah yang harus dilakukan aplikasi Anda jika pengguna tidak login? Solusinya adalah menghasilkan +akar nol dalam implementasi {@link android.provider.DocumentsProvider#queryRoots +queryRoots()} Anda. Yakni, sebuah kursor akar kosong:

+ +
+public Cursor queryRoots(String[] projection) throws FileNotFoundException {
+...
+    // If user is not logged in, return an empty root cursor.  This removes our
+    // provider from the list entirely.
+    if (!isUserLoggedIn()) {
+        return result;
+}
+
+ +

Langkah lainnya adalah memanggil {@code getContentResolver().notifyChange()}. +Ingat {@link android.provider.DocumentsContract}? Kita menggunakannya untuk membuat +URI ini. Cuplikan berikut memberi tahu sistem untuk membuat query akar penyedia dokumen Anda +kapan saja status login pengguna berubah. Jika pengguna tidak +login, panggilan ke {@link android.provider.DocumentsProvider#queryRoots queryRoots()} akan menghasilkan +kursor kosong, seperti yang ditampilkan di atas. Cara ini akan memastikan bahwa dokumen penyedia hanya +tersedia jika pengguna login ke penyedia itu.

+ +
private void onLoginButtonClick() {
+    loginOrLogout();
+    getContentResolver().notifyChange(DocumentsContract
+            .buildRootsUri(AUTHORITY), null);
+}
+
\ No newline at end of file diff --git a/docs/html-intl/intl/in/guide/topics/resources/accessing-resources.jd b/docs/html-intl/intl/in/guide/topics/resources/accessing-resources.jd new file mode 100644 index 0000000000000000000000000000000000000000..e4a0bea1e36ab36ad6cd83d4fe8c66786da897b3 --- /dev/null +++ b/docs/html-intl/intl/in/guide/topics/resources/accessing-resources.jd @@ -0,0 +1,337 @@ +page.title=Mengakses Sumber Daya +parent.title=Sumber Daya Aplikasi +parent.link=index.html +@jd:body + +
+
+

Tampilan Cepat

+ + +

Kelas-Kelas Utama

+
    +
  1. {@link android.content.res.Resources}
  2. +
+ +

Dalam dokumen ini

+
    +
  1. Mengakses Sumber Daya dari Kode
  2. +
  3. Mengakses Sumber Daya dari XML +
      +
    1. Mengacu atribut gaya
    2. +
    +
  4. +
  5. Mengakses Sumber Daya Platform
  6. +
+ +

Lihat juga

+
    +
  1. Menyediakan Sumber Daya
  2. +
  3. Tipe Sumber Daya
  4. +
+
+
+ + + + +

Setelah Anda menyediakan sumber daya dalam aplikasi Anda (yang dibicarakan di Menyediakan Sumber Daya), Anda bisa menerapkannya dengan +mengacu ID sumber dayanya. Semua ID sumber daya didefinisikan di kelas {@code R} proyek Anda, yang +dihasilkan oleh alat {@code aapt} secara otomatis.

+ +

Bila aplikasi Anda dikompilasi, {@code aapt} akan membuat kelas {@code R}, yang berisi +ID sumber daya untuk semua sumber daya dalam direktori {@code +res/} Anda. Untuk masing-masing tipe sumber daya, ada subkelas {@code R} (misalnya, +{@code R.drawable} untuk semua sumber daya yang bisa ditarik), dan untuk masing-masing sumber daya dari tipe itu, ada satu integer statis + (misalnya, {@code R.drawable.icon}). Integer ini adalah ID sumber daya yang bisa Anda gunakan +untuk mengambil sumber daya Anda.

+ +

Walaupun kelas {@code R} adalah tempat menyebutkan ID sumber daya, Anda tidak perlu +melihat ke sana untuk menemukan ID sumber daya. ID sumber daya selalu terdiri dari:

+ + +

Ada dua cara untuk mengakses sumber daya:

+ + + + +

Mengakses Sumber Daya dalam Kode

+ +

Anda bisa menggunakan sumber daya dalam kode dengan menyalurkan ID sumber daya sebagai sebuah parameter metode. Misalnya, + Anda bisa mengatur sebuah {@link android.widget.ImageView} agar menggunakan sumber daya{@code res/drawable/myimage.png} +dengan menggunakan {@link android.widget.ImageView#setImageResource(int) setImageResource()}:

+
+ImageView imageView = (ImageView) findViewById(R.id.myimageview);
+imageView.setImageResource(R.drawable.myimage);
+
+ +

Anda juga bisa mengambil tiap sumber daya dengan menggunakan berbagai metode di {@link +android.content.res.Resources}, di mana Anda bisa mendapatkan instance + {@link android.content.Context#getResources()}.

+ + + + +

Sintaks

+ +

Inilah sintaks untuk mengacu sumber daya dalam kode:

+ +
+[<package_name>.]R.<resource_type>.<resource_name>
+
+ + +

Lihat Tipe Sumber Daya untuk +informasi selengkapnya tentang masing-masing tipe sumber daya dan cara mengacunya.

+ + +

Kasus penggunaan

+ +

Ada banyak metode yang menerima parameter ID sumber daya dan Anda bisa mengambil sumber daya dengan menggunakan +metode di {@link android.content.res.Resources}. Anda bisa mengambil instance {@link +android.content.res.Resources} dengan {@link android.content.Context#getResources +Context.getResources()}.

+ + +

Berikut adalah beberapa contoh cara mengakses sumber daya dalam kode:

+ +
+// Load a background for the current screen from a drawable resource
+{@link android.app.Activity#getWindow()}.{@link
+android.view.Window#setBackgroundDrawableResource(int)
+setBackgroundDrawableResource}(R.drawable.my_background_image) ;
+
+// Set the Activity title by getting a string from the Resources object, because
+//  this method requires a CharSequence rather than a resource ID
+{@link android.app.Activity#getWindow()}.{@link android.view.Window#setTitle(CharSequence)
+setTitle}(getResources().{@link android.content.res.Resources#getText(int)
+getText}(R.string.main_title));
+
+// Load a custom layout for the current screen
+{@link android.app.Activity#setContentView(int)
+setContentView}(R.layout.main_screen);
+
+// Set a slide in animation by getting an Animation from the Resources object
+mFlipper.{@link android.widget.ViewAnimator#setInAnimation(Animation)
+setInAnimation}(AnimationUtils.loadAnimation(this,
+        R.anim.hyperspace_in));
+
+// Set the text on a TextView object using a resource ID
+TextView msgTextView = (TextView) findViewById(R.id.msg);
+msgTextView.{@link android.widget.TextView#setText(int)
+setText}(R.string.hello_message);
+
+ + +

Perhatian: Anda tidak boleh memodifikasi file {@code +R.java} secara manual—, ini dihasilkan oleh alat {@code aapt} bila proyek Anda telah +dikompilasi. Perubahan apa pun akan ditimpa bila nanti Anda mengompilasi.

+ + + +

Mengakses Sumber Daya dari XML

+ +

Anda bisa mendefinisikan nilai untuk beberapa atribut dan elemen XML dengan menggunakan +acuan ke sumber daya yang ada. Anda akan sering melakukannya saat membuat file layout, untuk +memasok string dan gambar bagi widget Anda.

+ +

Misalnya, jika Anda menambahkan sebuah {@link android.widget.Button} ke layout, Anda harus menggunakan +sebuah sumber daya string bagi teks tombolnya:

+ +
+<Button
+    android:layout_width="fill_parent"
+    android:layout_height="wrap_content"
+    android:text="@string/submit" />
+
+ + +

Sintaks

+ +

Berikut adalah sintaks untuk mengacu sumber daya di sumber daya XML:

+ +
+@[<package_name>:]<resource_type>/<resource_name>
+
+ + + +

Lihat Tipe Sumber Daya untuk +informasi selengkapnya tentang masing-masing tipe sumber daya dan cara mengacunya.

+ + +

Kasus penggunaan

+ +

Dalam beberapa kasus, Anda harus menggunakan sumber daya untuk suatu nilai dalam XML (misalnya, untuk menerapkan gambar yang bisa ditarik +pada widget), namun Anda juga bisa menggunakan sumber daya di XML mana saja yang menerima nilai sederhana. Misalnya, jika +Anda mempunyai file sumber daya berikut yang berisi sumber daya warna dan sumber daya string:

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+   <color name="opaque_red">#f00</color>
+   <string name="hello">Hello!</string>
+</resources>
+
+ +

Anda bisa menggunakan sumber daya ini dalam file layout berikut untuk mengatur warna teks dan +string teks:

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<EditText xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    android:textColor="@color/opaque_red"
+    android:text="@string/hello" />
+
+ +

Dalam hal ini, Anda tidak perlu menyebutkan nama paket dalam sumber daya acuan karena +sumber daya berasal dari paket Anda sendiri. Untuk +mengacu sumber daya sistem, Anda perlu memasukkan nama paketnya. Misalnya:

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<EditText xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    android:textColor="@android:color/secondary_text_dark"
+    android:text="@string/hello" />
+
+ +

Catatan: Anda harus menggunakan sumber daya string sepanjang +waktu, sehingga aplikasi Anda bisa dilokalkan untuk bahasa lain. +Untuk informasi tentang cara menciptakan +sumber daya alternatif (seperti string lokal), lihat Menyediakan Sumber Daya Alternatif +. Untuk panduan lengkap melokalkan aplikasi Anda ke bahasa lain, +lihat Pelokalan.

+ +

Anda bahkan bisa menggunakan sumber daya dalam XML untuk membuat alias. Misalnya, Anda bisa membuat +sumber daya yang bisa ditarik yang merupakan alias bagi sumber daya yang bisa ditarik lainnya:

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
+    android:src="@drawable/other_drawable" />
+
+ +

Hal ini terdengar berlebihan, namun bisa sangat berguna saat menggunakan sumber daya alternatif. Baca selengkapnya tentang +Membuat sumber daya alias.

+ + + +

Mengacu atribut gaya

+ +

Sumber daya atribut gaya memungkinkan Anda mengacu nilai +suatu atribut dalam tema yang diterapkan saat ini. Dengan mengacu sebuah atribut gaya memungkinkan Anda +menyesuaikan tampilan elemen UI dengan mengatur gayanya agar cocok dengan beragam variasi standar yang dipasok oleh +tema saat ini, sebagai ganti memasok nilai yang ditanamkan (hard-coded). Mengacu sebuah atribut gaya +pada dasarnya adalah "gunakan gaya yang didefinisikan oleh atribut ini, dalam tema saat ini".

+ +

Untuk mengacu sebuah atribut gaya, sintaks namanya hampir sama dengan format sumber daya normal +, namun sebagai ganti simbol @ ({@code @}), gunakan sebuah tanda tanya ({@code ?}), dan +porsi tipe sumber daya bersifat opsional. Sebagai contoh:

+ +
+?[<package_name>:][<resource_type>/]<resource_name>
+
+ +

Misalnya, begini cara Anda mengacu suatu atribut untuk mengatur warna teks agar cocok dengan +warna teks "utama" tema sistem:

+ +
+<EditText id="text"
+    android:layout_width="fill_parent"
+    android:layout_height="wrap_content"
+    android:textColor="?android:textColorSecondary"
+    android:text="@string/hello_world" />
+
+ +

Di sini, atribut {@code android:textColor} menyebutkan nama atribut gaya +dalam tema saat ini. Android kini menggunakan nilai yang diterapkan pada atribut gaya {@code android:textColorSecondary} +sebagai nilai untuk {@code android:textColor} dalam widget ini. Karena alat sumber daya +mengetahui bahwa atribut sumber daya diharapkan dalam konteks ini, +maka Anda tidak perlu menyatakan tipenyanya secara eksplisit (yang akan berupa +?android:attr/textColorSecondary)—Anda bisa mengecualikan tipe {@code attr}.

+ + + + +

Mengakses Sumber Daya Platform

+ +

Android berisi sejumlah sumber daya standar, seperti gaya, tema, dan layout. Untuk +mengakses semua sumber daya ini, tetapkan acuan sumber daya Anda dengan nama paket +android. Misalnya, Android menyediakan sumber daya layout yang bisa Anda gunakan untuk +item daftar dalam{@link android.widget.ListAdapter}:

+ +
+{@link android.app.ListActivity#setListAdapter(ListAdapter)
+setListAdapter}(new {@link
+android.widget.ArrayAdapter}<String>(this, android.R.layout.simple_list_item_1, myarray));
+
+ +

Dalam contoh ini, {@link android.R.layout#simple_list_item_1} adalah sumber daya layout yang didefinisikan oleh +platform untuk item di {@link android.widget.ListView}. Anda bisa menggunakannya sebagai ganti menciptakan +layout sendiri untuk item daftar. Untuk informasi selengkapnya, lihat panduan pengembang +List View.

+ diff --git a/docs/html-intl/intl/in/guide/topics/resources/overview.jd b/docs/html-intl/intl/in/guide/topics/resources/overview.jd new file mode 100644 index 0000000000000000000000000000000000000000..966800cf7daa52b0a9478683f8b06cafeaae1764 --- /dev/null +++ b/docs/html-intl/intl/in/guide/topics/resources/overview.jd @@ -0,0 +1,103 @@ +page.title=Ikhtisar Sumber Daya +@jd:body + +
+
+

Topik

+
    +
  1. Menyediakan Sumber Daya
  2. +
  3. Mengakses Sumber Daya
  4. +
  5. Menangani Perubahan Runtime
  6. +
  7. Pelokalan
  8. +
+ +

Acuan

+
    +
  1. Tipe Sumber Daya
  2. +
+
+
+ + +

Anda harus selalu mengeksternalkan sumber daya seperti gambar dan string dari kode +aplikasi, agar Anda bisa memeliharanya secara independen. Mengeksternalkan +sumber daya juga membuat Anda dapat menyediakan sumber daya alternatif yang mendukung konfigurasi +perangkat tertentu seperti bahasa atau ukuran layar yang berbeda, yang semakin penting +seiring semakin banyak tersedianya perangkat berbasis Android dengan konfigurasi berbeda. Untuk +memberikan kompatibilitas dengan konfigurasi berbeda, Anda harus menata sumber daya dalam +direktori {@code res/} proyek Anda, menggunakan berbagai subdirektori yang mengelompokkan sumber daya menurut tipe +dan konfigurasinya.

+ +
+ +

+Gambar 1. Dua perangkat berbeda, masing-masing menggunakan layout default +(aplikasi tidak menyediakan layout alternatif).

+
+ +
+ +

+Gambar 2. Dua perangkat berbeda, masing-masing menggunakan layout berbeda yang tersedia untuk +ukuran layar berbeda.

+
+ +

Bagi setiap tipe sumber daya, Anda bisa menetapkan sumber daya default dan sumber daya +alternatif untuk aplikasi Anda:

+ + +

Misalnya, walaupun layout +UI default Anda disimpan dalam direktori {@code res/layout/}, Anda dapat menetapkan layout berbeda +untuk digunakan saat layar dalam orientasi lanskap, dengan menyimpannya dalam direktori {@code res/layout-land/} +. Android secara otomatis memberlakukan sumber daya yang sesuai dengan mencocokkan konfigurasi perangkat +saat ini dengan nama direktori sumber daya.

+ +

Gambar 1 mengilustrasikan cara sistem memberlakukan layout yang sama untuk +dua perangkat berbeda saat sumber daya alternatif tidak tersedia. Gambar 2 menunjukkan +aplikasi yang sama saat menambahkan sumber daya layout alternatif untuk layar yang lebih besar.

+ +

Dokumen-dokumen berikut berisi panduan lengkap mengenai cara menata sumber daya aplikasi, +menetapkan sumber daya alternatif, mengaksesnya dalam aplikasi, dan banyak lagi:

+ +
+
Menyediakan Sumber Daya
+
Jenis sumber daya yang dapat Anda sediakan dalam aplikasi, tempat menyimpannya, dan cara membuat sumber daya +alternatif untuk konfigurasi perangkat tertentu.
+
Mengakses Sumber Daya
+
Cara menggunakan sumber daya yang telah Anda sediakan, baik dengan mengacunya dari kode +aplikasi Anda atau dari sumber daya XML lainnya.
+
Menangani Perubahan Runtime
+
Cara mengelola perubahan konfigurasi yang terjadi saat Aktivitas Anda berjalan.
+
Pelokalan
+
Panduan dari pengalaman untuk melokalkan aplikasi menggunakan sumber daya alternatif. Walaupun ini +hanya satu penggunaan tertentu dari sumber daya alternatif, hal ini sangat penting dalam meraih pengguna lebih +banyak.
+
Tipe Sumber Daya
+
Acuan dari berbagai tipe sumber daya yang dapat Anda sediakan, menjelaskan elemen-elemen XML, +atribut, dan sintaksnya. Misalnya, acuan ini menunjukkan kepada Anda cara membuat sumber daya untuk +menu aplikasi, drawable, animasi, dan lainnya.
+
+ + diff --git a/docs/html-intl/intl/in/guide/topics/resources/providing-resources.jd b/docs/html-intl/intl/in/guide/topics/resources/providing-resources.jd new file mode 100644 index 0000000000000000000000000000000000000000..d6bbfc58d91136555aaf4196893e37f67a7b1ff5 --- /dev/null +++ b/docs/html-intl/intl/in/guide/topics/resources/providing-resources.jd @@ -0,0 +1,1094 @@ +page.title=Menyediakan Sumber Daya +parent.title=Sumber Daya Aplikasi +parent.link=index.html +@jd:body + +
+
+

Tampilan Cepat

+ +

Dalam dokumen ini

+
    +
  1. Mengelompokkan Tipe Sumber Daya
  2. +
  3. Menyediakan Sumber Daya Alternatif +
      +
    1. Aturan penamaan qualifier
    2. +
    3. Membuat sumber daya alias
    4. +
    +
  4. +
  5. Menyediakan Kompatibilitas Perangkat Terbaik dengan Sumber Daya
  6. +
  7. Cara Android Menemukan Sumber Daya yang Paling Cocok
  8. +
+ +

Lihat juga

+
    +
  1. Mengakses Sumber Daya
  2. +
  3. Tipe Sumber Daya
  4. +
  5. Mendukung Beberapa +Layar
  6. +
+
+
+ +

Anda harus selalu mengeksternalkan sumber daya aplikasi seperti gambar dan string dari kode +, agar Anda bisa memeliharanya secara independen. Anda juga harus menyediakan sumber daya alternatif untuk +konfigurasi perangkat tertentu, dengan mengelompokkannya dalam direktori sumber daya bernama khusus. Saat +runtime, Android menggunakan sumber daya yang sesuai berdasarkan konfigurasi saat ini. Misalnya, Anda mungkin +ingin menyediakan layout UI berbeda bergantung pada ukuran layar atau string berbeda bergantung pada +pengaturan bahasa.

+ +

Setelah mengeksternalkan sumber daya aplikasi, Anda dapat mengaksesnya menggunakan +ID sumber daya yang dibuat dalam kelas {@code R} proyek Anda. Cara menggunakan +sumber daya dalam aplikasi dibahas dalam Mengakses +Sumber Daya. Dokumen ini menampilkan cara mengelompokkan sumber daya +dalam proyek Android Anda dan menyediakan sumber daya alternatif untuk konfigurasi perangkat tertentu.

+ + +

Mengelompokkan Tipe Sumber Daya

+ +

Anda harus menempatkan setiap tipe sumber daya dalam subdirektori spesifik pada direktori +{@code res/} proyek. Misalnya, inilah hierarki file untuk proyek sederhana:

+ +
+MyProject/
+    src/  
+        MyActivity.java  
+    res/
+        drawable/  
+            graphic.png  
+        layout/  
+            main.xml
+            info.xml
+        mipmap/  
+            icon.png 
+        values/  
+            strings.xml  
+
+ +

Seperti yang Anda lihat dalam contoh ini, direktori {@code res/} berisi semua sumber daya (dalam +subdirektori): sumber daya gambar, dua sumber daya layout, direktori {@code mipmap/} untuk ikon +launcher, dan satu file sumber daya string. Nama direktori +sumber daya penting dan dijelaskan dalam tabel 1.

+ +

Catatan: Untuk informasi selengkapnya tentang menggunakan folder mipmap, lihat +Mengelola Ikhtisar Proyek.

+ +

Tabel 1. Direktori sumber daya +didukung dalam direktori proyek {@code res/}.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
DirektoriTipe Sumber Daya
animator/File XML yang mendefinisikan animasi +properti.
anim/File XML yang mendefinisikan animasi +tween. (Animasi properti juga dapat disimpan dalam direktori ini, namun +direktori {@code animator/} lebih disukai bagi animasi properti agar kedua tipe +ini dapat dibedakan.)
color/File XML yang mendefinisikan daftar status warna. Lihat Sumber Daya +Daftar Status Warna
drawable/

File bitmap ({@code .png}, {@code .9.png}, {@code .jpg}, {@code .gif}) atau file XML yang +dikompilasi menjadi subtipe sumber daya drawable berikut:

+
    +
  • File bitmap
  • +
  • Nine-Patches (bitmap yang dapat diubah ukurannya)
  • +
  • Daftar status
  • +
  • Bentuk
  • +
  • Drawable animasi
  • +
  • Drawable lainnya
  • +
+

Lihat Sumber Daya Drawable.

+
mipmap/File drawable untuk densitas ikon launcher yang berbeda. Untuk informasi selengkapnya tentang + mengelola ikon launcher dengan folder {@code mipmap/}, lihat +Mengelola Ikhtisar Proyek.
layout/File XML yang mendefinisikan layout antarmuka pengguna. + Lihat Sumber Daya Layout.
menu/File XML yang mendefinisikan menu aplikasi, seperti Menu Opsi, Menu Konteks, atau Sub +Menu. Lihat Sumber Daya Menu.
raw/

File tak didukung yang akan disimpan dalam bentuk mentah. Untuk membuka sumber daya ini dengan +{@link java.io.InputStream} mentah, panggil {@link android.content.res.Resources#openRawResource(int) +Resources.openRawResource()} dengan ID sumber daya, yaitu {@code R.raw.filename}.

+

Akan tetapi, jika Anda butuh akses ke nama file asli dan hierarki file, Anda bisa mempertimbangkan +untuk menyimpan beberapa sumber daya dalam direktori {@code +assets/} (sebagai ganti {@code res/raw/}). File dalam {@code assets/} tidak diberi +ID sumber daya, jadi Anda bisa membacanya hanya dengan menggunakan {@link android.content.res.AssetManager}.

values/

File XML yang berisi nilai-nilai sederhana, seperti string, integer, dan warna.

+

Walaupun file sumber daya XML dalam subdirektori {@code res/} lainnya mendefinisikan satu sumber daya +berdasarkan nama file XML, file dalam direktori {@code values/} menggambarkan beberapa sumber daya. +Untuk file dalam direktori ini, setiap anak elemen {@code <resources>} mendefinisikan satu sumber +daya. Misalnya, elemen {@code <string>} membuat sumber daya +{@code R.string} dan elemen {@code <color>} membuat sumber daya {@code R.color} +.

+

Karena setiap sumber daya didefinisikan dengan elemen XML-nya sendiri, Anda bisa bebas menamai file +ini dan menempatkan tipe sumber daya berbeda dalam satu file. Akan tetapi, agar jelas, Anda mungkin +perlu menempatkan tipe sumber daya unik dalam file berbeda. Misalnya, berikut ini adalah beberapa ketentuan +penamaan file untuk sumber daya yang dapat Anda buat dalam direktori ini:

+ +

Lihat Sumber Daya String, + Sumber Daya Gaya, dan + Tipe Sumber Daya Lainnya.

+
xml/File XML tak didukung yang bisa dibaca saat runtime dengan memanggil {@link +android.content.res.Resources#getXml(int) Resources.getXML()}. Berbagai file konfigurasi XML +harus disimpan di sini, seperti konfigurasi yang dapat dicari. +
+ +

Perhatian: Jangan menyimpan file sumber daya secara langsung dalam +direktori {@code res/}— karena akan menyebabkan kesalahan compiler.

+ +

Untuk informasi selengkapnya tentang tipe sumber daya tertentu, lihat dokumentasi Tipe Sumber Daya.

+ +

Sumber daya yang disimpan dalam subdirektori yang didefinisikan dalam tabel 1 adalah sumber daya +"default" Anda. Berarti sumber daya ini mendefinisikan desain default dan konten untuk aplikasi Anda. +Akan tetapi, beberapa tipe perangkat berbasis Android mungkin memanggil tipe sumber daya yang berbeda. +Misalnya, jika perangkat memiliki layar yang lebih besar daripada layar normal, maka Anda harus +menyediakan sumber daya layout berbeda yang memanfaatkan ruang layar yang lebih besar. Atau, jika perangkat +memiliki pengaturan bahasa berbeda, maka Anda harus menyediakan sumber daya string berbeda yang menerjemahkan teks dalam +antarmuka pengguna Anda. Untuk menyediakan sumber daya berbeda ini bagi +konfigurasi perangkat yang berbeda, Anda harus menyediakan sumber daya alternatif, selain sumber +daya default.

+ + +

Menyediakan Sumber Daya Alternatif

+ + +
+ +

+Gambar 1. Dua perangkat berbeda, masing-masing menggunakan sumber daya layout berbeda.

+
+ +

Hampir setiap aplikasi harus menyediakan sumber daya alternatif untuk mendukung konfigurasi +perangkat tertentu. Misalnya, Anda harus menyertakan sumber daya drawable alternatif untuk densitas layar +berbeda dan sumber daya string alternatif untuk bahasa yang berbeda. Saat runtime, Android +akan mendeteksi konfigurasi perangkat aktif dan memuat +sumber daya yang sesuai untuk aplikasi Anda.

+ +

Untuk menyebutkan alternatif konfigurasi tertentu untuk satu set sumber daya:

+
    +
  1. Buat direktori baru dalam {@code res/} yang dinamai dalam bentuk {@code +<resources_name>-<config_qualifier>}. + +

    Anda bisa menambahkan lebih dari satu {@code <qualifier>}. Pisahkan masing-masing +dengan tanda hubung.

    +

    Perhatian: Saat menambahkan beberapa qualifier, Anda +harus menempatkannya dalam urutan yang sama dengan yang tercantum dalam tabel 2. Jika urutan qualifier +salah, sumber daya akan diabaikan.

    +
  2. +
  3. Simpan masing-masing sumber daya alternatif dalam direktori baru ini. File sumber daya harus dinamai +sama persis dengan file sumber daya default.
  4. +
+ +

Misalnya, berikut ini beberapa sumber daya default dan sumber daya alternatif:

+ +
+res/
+    drawable/   
+        icon.png
+        background.png    
+    drawable-hdpi/  
+        icon.png
+        background.png  
+
+ +

Qualifier {@code hdpi} menunjukkan bahwa sumber daya dalam direktori itu diperuntukkan bagi perangkat dengan +layar densitas tinggi. Gambar di masing-masing direktori drawable memiliki ukuran untuk densitas layar +tertentu, namun nama filenya persis +sama. Dengan demikian, ID sumber daya yang Anda gunakan untuk mengacu gambar {@code icon.png} atau @code +background.png} selalu sama, namun Android memilih +versi masing-masing sumber daya yang paling cocok dengan perangkat saat ini, dengan membandingkan informasi konfigurasi +perangkat dengan qualifier dalam nama direktori sumber daya.

+ +

Android mendukung beberapa qualifier konfigurasi dan Anda dapat +menambahkan beberapa qualifier ke satu nama direktori, dengan memisahkan setiap qualifier dengan tanda hubung. Tabel 2 +berisi daftar qualifier konfigurasi yang valid, dalam urutan prioritas—jika Anda menggunakan beberapa +qualifier sebagai direktori sumber daya, Anda harus menambahkannya ke nama direktori sesuai urutan +yang tercantum dalam tabel.

+ + +

Tabel 2. Nama-nama +qualifier konfigurasi.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
KonfigurasiNilai-nilai QualifierKeterangan
MCC dan MNCContoh:
+ mcc310
+ mcc310-mnc004
+ mcc208-mnc00
+ dll. +
+

Kode negara seluler (MCC), bisa diikuti dengan kode jaringan seluler (MNC) + dari kartu SIM dalam perangkat. Misalnya, mcc310 adalah AS untuk operator mana saja, + mcc310-mnc004 adalah AS untuk Verizon, dan mcc208-mnc00 Prancis untuk +Orange.

+

Jika perangkat menggunakan koneksi radio (ponsel GSM), nilai-nilai MCC dan MNC berasal +dari kartu SIM.

+

Anda juga dapat menggunakan MNC saja (misalnya, untuk menyertakan sumber daya legal +spesifik untuk negara itu di aplikasi Anda). Jika Anda perlu menetapkan hanya berdasarkan bahasa, maka gunakan qualifier +bahasa dan wilayah sebagai gantinya (akan dibahas nanti). Jika Anda memutuskan untuk menggunakan qualifier MCC dan +MNC, Anda harus melakukannya dengan hati-hati dan menguji apakah qualifier itu berjalan sesuai harapan.

+

Lihat juga bidang konfigurasi {@link +android.content.res.Configuration#mcc}, dan {@link +android.content.res.Configuration#mnc}, yang masing-masing menunjukkan kode negara seluler saat ini +dan kode jaringan seluler.

+
Bahasa dan wilayahContoh:
+ en
+ fr
+ en-rUS
+ fr-rFR
+ fr-rCA
+ dll. +

Bahasa didefinisikan oleh kode bahasa dua huruf ISO +639-1, bisa juga diikuti dengan kode wilayah +dua huruf ISO +3166-1-alpha-2 (diawali dengan huruf kecil "{@code r}"). +

+ Kode tidak membedakan huruf besar atau kecil; awalan {@code r} akan digunakan untuk +membedakan bagian wilayah. + Anda tidak bisa menetapkan wilayah saja.

+

Ini bisa berubah selama masa pakai +aplikasi Anda jika pengguna mengubah bahasanya dalam pengaturan sistem. Lihat Menangani Perubahan Runtime untuk informasi tentang +bagaimana hal ini dapat memengaruhi aplikasi Anda selama runtime.

+

Lihat Pelokalan untuk panduan lengkap melokalkan +aplikasi Anda ke bahasa lain.

+

Lihat juga bidang konfigurasi {@link android.content.res.Configuration#locale} yang menunjukkan +bahasa setempat yang digunakan saat ini.

+
Arah Layoutldrtl
+ ldltr
+

Arah layout aplikasi Anda. {@code ldrtl} berarti "arah layout dari kanan ke kiri". + {@code ldltr} berarti "arah layout dari kiri ke kanan" dan merupakan nilai implisit default. +

+

Ini bisa berlaku untuk sumber daya mana pun seperti layout, drawable, atau nilai-nilai. +

+

Misalnya, jika Anda ingin memberikan beberapa layout khusus untuk bahasa Arab dan beberapa +layout umum untuk setiap bahasa lainnya yang menggunakan "kanan-ke-kiri" lainnya (seperti bahasa Persia atau Ibrani) maka Anda akan memiliki: +

+
+res/
+    layout/   
+        main.xml  (Default layout)
+    layout-ar/  
+        main.xml  (Specific layout for Arabic)
+    layout-ldrtl/  
+        main.xml  (Any "right-to-left" language, except
+                  for Arabic, because the "ar" language qualifier
+                  has a higher precedence.)
+
+

Catatan: Untuk mengaktifkan fitur +layout kanan-ke-kiri untuk aplikasi, Anda harus mengatur {@code + supportsRtl} ke {@code "true"} dan mengatur {@code targetSdkVersion} ke 17 atau yang lebih tinggi.

+

Ditambahkan dalam API level 17.

+
smallestWidthsw<N>dp

+ Contoh:
+ sw320dp
+ sw600dp
+ sw720dp
+ dll. +
+

Ukuran dasar layar, sebagaimana yang ditunjukkan oleh dimensi terpendek dari area layar +yang tersedia. Secara spesifik, smallestWidth perangkat adalah yang terpendek dari +tinggi dan lebar layar yang tersedia (Anda dapat menganggapnya sebagai "lebar terkecil yang memungkinkan" untuk layar). Anda bisa +menggunakan qualifier ini untuk memastikan bahwa, apa pun orientasi layar saat ini, aplikasi +Anda memiliki paling tidak {@code <N>} dps dari lebar yang tersedia untuk UI-nya.

+

Misalnya, jika layout mengharuskan dimensi layar terkecilnya setiap saat paling tidak +600 dp, maka Anda dapat menggunakan qualifer ini untuk membuat sumber daya layout, {@code +res/layout-sw600dp/}. Sistem akan menggunakan sumber daya ini hanya bila dimensi layar terkecil yang +tersedia paling tidak 600 dp, tanpa mempertimbangkan apakah sisi 600 dp adalah tinggi atau +lebar yang dipersepsikan pengguna. SmallestWidth adalah karakteristik ukuran layar tetap dari perangkat; smallestWidth +perangkat tidak berubah saat orientasi layar berubah.

+

SmallestWidth perangkat memperhitungkan dekorasi layar dan UI sistem. Misalnya +, jika perangkat memiliki beberapa elemen UI persisten pada layar yang menghitung ruang di sepanjang +sumbu smallestWidth, sistem akan mendeklarasikan smallestWidth lebih kecil daripada ukuran layar sebenarnya, +karena itu adalah piksel layar yang tidak tersedia untuk UI Anda. Sehingga nilai yang Anda +gunakan haruslah merupakan dimensi terkecil sebenarnya yang dibutuhkan oleh layout Anda (biasanya, nilai ini adalah +"lebar terkecil" yang didukung layout Anda, apa pun orientasi layar saat ini).

+

Sebagian nilai yang mungkin Anda gunakan untuk ukuran layar umum:

+
    +
  • 320, untuk perangkat berkonfigurasi layar seperti: +
      +
    • 240x320 ldpi (handset QVGA)
    • +
    • 320x480 mdpi (handset)
    • +
    • 480x800 hdpi (handset densitas tinggi)
    • +
    +
  • +
  • 480, untuk layar seperti 480x800 mdpi (tablet/handset).
  • +
  • 600, untuk layar seperti 600x1024 mdpi (tablet 7").
  • +
  • 720, untuk layar seperti 720x1280 mdpi (tablet 10").
  • +
+

Bila aplikasi Anda menyediakan beberapa direktori sumber daya dengan nilai yang berbeda untuk +qualifier smallestWidth terkecil, sistem akan menggunakan nilai terdekat dengan (tanpa melebihi) +smallestWidth perangkat.

+

Ditambahkan dalam API level 13.

+

Lihat juga atribut {@code +android:requiresSmallestWidthDp}, yang mendeklarasikan smallestWidth minimum yang +kompatibel dengan aplikasi Anda, dan bidang konfigurasi {@link +android.content.res.Configuration#smallestScreenWidthDp}, yang menyimpan nilai +smallestWidth perangkat.

+

Untuk informasi selengkapnya tentang mendesain untuk layar berbeda dan menggunakan +qualifier ini, lihat panduan pengembang Mendukung +Multi Layar.

+
Lebar yang tersediaw<N>dp

+ Contoh:
+ w720dp
+ w1024dp
+ dll. +
+

Menetapkan lebar layar minimum yang tersedia, di unit {@code dp} yang +menggunakan sumber daya—yang didefinisikan oleh nilai <N>. Nilai konfigurasi ini + akan berubah bila orientasi +berubah antara lanskap dan potret agar cocok dengan lebar sebenarnya saat ini.

+

Bila aplikasi Anda menyediakan beberapa direktori sumber daya dengan nilai yang berbeda + untuk konfigurasi ini, sistem akan menggunakan nilai terdekat dengan (tanpa melebihi) + lebar layar perangkat saat ini. Nilai +di sini memperhitungkan dekorasi layar akun, jadi jika perangkat memiliki beberapa +elemen UI persisten di tepi kiri atau kanan, layar +menggunakan nilai lebar yang lebih kecil daripada ukuran layar sebenarnya, yang memperhitungkan +elemen UI ini dan mengurangi ruang aplikasi yang tersedia.

+

Ditambahkan dalam API level 13.

+

Lihat juga bidang konfigurasi {@link android.content.res.Configuration#screenWidthDp} + yang menyimpan lebar layar saat ini.

+

Untuk informasi selengkapnya tentang mendesain untuk layar berbeda dan menggunakan +qualifier ini, lihat panduan pengembang Mendukung +Multi Layar.

+
Tinggi yang tersediah<N>dp

+ Contoh:
+ h720dp
+ h1024dp
+ dll. +
+

Menetapkan tinggi layar minimum yang tersedia, dalam satuan "dp" yang harus digunakan +sumber daya —bersama nilai yang didefinisikan oleh <N>. Nilai konfigurasi ini + akan berubah saat orientasi +berubah antara lanskap dan potret agar cocok dengan tinggi sebenarnya saat ini.

+

Bila aplikasi menyediakan beberapa direktori sumber daya dengan nilai yang berbeda + untuk konfigurasi ini, sistem akan menggunakan nilai yang terdekat dengan (tanpa melebihi) + tinggi layar perangkat saat ini. Nilai +di sini memperhitungkan dekorasi layar akun, jadi jika perangkat memiliki beberapa +elemen UI persisten di tepi atas atau bawah, layar akan +menggunakan nilai tinggi yang lebih kecil daripada ukuran layar sebenarnya, memperhitungkan +elemen UI ini dan mengurangi ruang aplikasi yang tersedia. Dekorasi +layar yang tidak tetap (misalnya baris status (status-bar) telepon yang bisa +disembunyikan saat layar penuh) di sini tidak diperhitungkan, demikian pula +dekorasi jendela seperti baris judul (title-bar)atau baris tindakan (action-bar), jadi aplikasi harus disiapkan +untuk menangani ruang yang agak lebih kecil daripada yang ditetapkan. +

Ditambahkan dalam API level 13.

+

Lihat juga bidang konfigurasi {@link android.content.res.Configuration#screenHeightDp} + yang menyimpan lebar layar saat ini.

+

Untuk informasi selengkapnya tentang mendesain untuk layar berbeda dan menggunakan +qualifier ini, lihat panduan pengembang Mendukung +Multi Layar.

+
Ukuran layar + small
+ normal
+ large
+ xlarge +
+
    +
  • {@code small}: Layar yang berukuran serupa dengan +layar QVGA densitas rendah. Ukuran layout minimum untuk layar kecil +adalah sekitar 320x426 satuan dp. Misalnya QVGA densitas rendah +dan VGA densitas tinggi.
  • +
  • {@code normal}: Layar yang berukuran serupa dengan +layar HVGA densitas sedang. Ukuran layout minimum untuk +layar normal adalah sekitar 320x470 satuan dp. Contoh layar seperti itu adalah +WQVGA densitas rendah, HVGA densitas sedang, WVGA + densitas tinggi.
  • +
  • {@code large}: Layar yang berukuran serupa dengan +layar VGA densitas sedang. + Ukuran layout minimum untuk layar besar adalah sekitar 480x640 satuan dp. + Misalnya layar VGA dan WVGA densitas sedang.
  • +
  • {@code xlarge}: Layar yang jauh lebih besar dari layar HVGA +densitas sedang tradisional. Ukuran layout minimum untuk +layar ekstra besar adalah sekitar 720x960 satuan dp. Perangkat dengan layar ekstra besar +seringkali terlalu besar untuk dibawa dalam saku dan kemungkinan besar + berupa perangkat bergaya tablet. Ditambahkan dalam API level 9.
  • +
+

Catatan: Menggunakan qualifier ukuran tidak berarti bahwa +sumber daya hanya untuk layar ukuran itu saja. Jika Anda tidak menyediakan sumber +daya alternatif dengan qualifier yang lebih cocok dengan konfigurasi perangkat saat ini, sistem dapat menggunakan sumber daya +mana saja yang paling cocok.

+

Perhatian: Jika semua sumber daya Anda menggunakan +qualifier yang berukuran lebih besar daripada layar saat ini, sistem tidak akan menggunakannya dan aplikasi +Anda akan crash saat runtime (misalnya, jika semua sumber daya layout ditandai dengan qualifier {@code +xlarge}, namun perangkat memiliki ukuran layar normal).

+

Ditambahkan dalam API level 4.

+ +

Lihat Mendukung Beberapa +Layar untuk informasi selengkapnya.

+

Lihat juga bidang konfigurasi {@link android.content.res.Configuration#screenLayout}, + yang menunjukkan apakah layar berukuran kecil, normal, atau +besar.

+
Aspek layar + long
+ notlong +
+
    +
  • {@code long}: Layar panjang, seperti WQVGA, WVGA, FWVGA
  • +
  • {@code notlong}: Layar tidak panjang, seperti QVGA, HVGA, dan VGA
  • +
+

Ditambahkan dalam API level 4.

+

Ini berdasarkan sepenuhnya pada rasio aspek layar (layar "panjang" lebih lebar). Ini +tidak ada kaitannya dengan orientasi layar.

+

Lihat juga bidang konfigurasi {@link android.content.res.Configuration#screenLayout}, + yang menunjukkan apakah layar panjang.

+
Orientasi layar + port
+ land +
+
    +
  • {@code port}: Perangkat dalam orientasi potret (vertikal)
  • +
  • {@code land}: Perangkat dalam orientasi lanskap (horizontal)
  • + +
+

Ini bisa berubah selama masa pakai aplikasi Anda jika pengguna memutar +layar. Lihat Menangani Perubahan Runtime untuk + informasi tentang bagaimana hal ini memengaruhi aplikasi Anda selama runtime.

+

Lihat juga bidang konfigurasi {@link android.content.res.Configuration#orientation}, +yang menunjukkan orientasi perangkat saat ini.

+
Mode UI + car
+ desk
+ television
+ appliance + watch +
+
    +
  • {@code car}: Perangkat sedang menampilkan di dudukan perangkat di mobil
  • +
  • {@code desk}: Perangkat sedang menampilkan di dudukan perangkat di meja
  • +
  • {@code television}: Perangkat sedang menampilkan di televisi, yang menyediakan +pengalaman "sepuluh kaki" dengan UI-nya pada layar besar yang berada jauh dari pengguna, +terutama diorientasikan seputar DPAD atau +interaksi non-pointer lainnya
  • +
  • {@code appliance}: Perangkat berlaku sebagai +alat, tanpa tampilan
  • +
  • {@code watch}: Perangkat memiliki tampilan dan dikenakan di pergelangan tangan
  • +
+

Ditambahkan dalam API level 8, televisi ditambahkan dalam API 13, jam ditambahkan dalam API 20.

+

Untuk informasi tentang cara aplikasi merespons saat perangkat dimasukkan +ke dalam atau dilepaskan dari dudukannya, bacalah Menentukan +dan Memantau Kondisi dan Tipe Dudukan.

+

Ini bisa berubah selama masa pakai aplikasi jika pengguna menempatkan perangkat di +dudukannya. Anda dapat mengaktifkan atau menonaktifkan sebagian mode ini menggunakan {@link +android.app.UiModeManager}. Lihat Menangani Perubahan Runtime untuk +informasi tentang bagaimana hal ini memengaruhi aplikasi Anda selama runtime.

+
Mode malam + night
+ notnight +
+
    +
  • {@code night}: Waktu malam
  • +
  • {@code notnight}: Waktu siang
  • +
+

Ditambahkan dalam API level 8.

+

Ini bisa berubah selama masa pakai aplikasi jika mode malam dibiarkan dalam +mode otomatis (default), dalam hal ini perubahan mode berdasarkan pada waktu hari. Anda dapat mengaktifkan +atau menonaktifkan mode ini menggunakan {@link android.app.UiModeManager}. Lihat Menangani Perubahan Runtime untuk informasi tentang bagaimana hal ini memengaruhi +aplikasi Anda selama runtime.

+
Densitas piksel layar (dpi) + ldpi
+ mdpi
+ hdpi
+ xhdpi
+ xxhdpi
+ xxxhdpi
+ nodpi
+ tvdpi +
+
    +
  • {@code ldpi}: Layar densitas rendah; sekitar 120 dpi.
  • +
  • {@code mdpi}: Layar densitas sedang (pada HVGA tradisional); sekitar 160 dpi. +
  • +
  • {@code hdpi}: Layar densitas tinggi; sekitar 240 dpi.
  • +
  • {@code xhdpi}: Layar densitas ekstra tinggi; sekitar 320 dpi. Ditambahkan dalam API +Level 8.
  • +
  • {@code xxhdpi}: Layar densitas ekstra-ekstra-tinggi; sekitar 480 dpi. Ditambahkan dalam API +Level 16.
  • +
  • {@code xxxhdpi}: Densitas ekstra-ekstra-ekstra-tinggi (hanya ikon launcher, +lihat catatan + dalam Mendukung Beberapa Layar); sekitar 640 dpi. Ditambahkan dalam API +Level 18.
  • +
  • {@code nodpi}: Ini bisa digunakan untuk sumber daya bitmap yang tidak ingin Anda +skalakan agar sama dengan densitas perangkat.
  • +
  • {@code tvdpi}: Layar antara mdpi dan hdpi; sekitar 213 dpi. Ini +tidak dianggap sebagai kelompok densitas "utama". Sebagian besar ditujukan untuk televisi dan kebanyakan +aplikasi tidak memerlukannya —asalkan sumber daya mdpi dan hdpi cukup untuk sebagian besar aplikasi dan +sistem akan menskalakan sebagaimana mestinya. Qualifier ini diperkenalkan pada API level 13.
  • +
+

Terdapat rasio skala 3:4:6:8:12:16 antara enam densitas utama (dengan mengabaikan densitas +tvdpi). Jadi bitmap 9x9 di ldpi adalah 12x12 di mdpi, 18x18 di hdpi, 24x24 di xhdpi dan seterusnya. +

+

Jika Anda memutuskan bahwa sumber daya gambar tidak terlihat cukup baik di televisi +atau perangkat tertentu lainnya dan ingin mencoba sumber daya tvdpi, faktor skalanya adalah 1,33*mdpi. Misalnya, +gambar 100px x 100px untuk layar mdpi harus 133px x 133px untuk tvdpi.

+

Catatan: Menggunakan qualifier densitas tidak berarti bahwa +sumber daya hanya untuk layar dengan ukuran itu saja. Jika Anda tidak menyediakan sumber +daya alternatif dengan qualifier yang lebih cocok dengan konfigurasi perangkat saat ini, sistem dapat menggunakan sumber daya +mana saja yang paling cocok.

+

Lihat Mendukung Beberapa +Layar untuk informasi selengkapnya tentang cara menangani densitas layar yang berbeda dan cara Android +menurunkan skala bitmap Anda agar sesuai dengan densitas saat ini.

+
Tipe layar sentuh + notouch
+ finger +
+
    +
  • {@code notouch}: Perangkat tidak memiliki layar sentuh.
  • +
  • {@code finger}: Perangkat memiliki layar sentuh yang dimaksudkan untuk +digunakan melalui interaksi dengan jari pengguna.
  • +
+

Lihat juga bidang konfigurasi {@link android.content.res.Configuration#touchscreen}, yang +menunjukkan tipe layar sentuh pada perangkat.

+
Ketersediaan keyboard + keysexposed
+ keyshidden
+ keyssoft +
+
    +
  • {@code keysexposed}: Perangkat menyediakan keyboard. Jika perangkat mengaktifkan +keyboard perangkat lunak (kemungkinan), ini dapat digunakan bahkan saat keyboard fisik +tidak diekspos kepada pengguna, meskipun perangkat tidak memiliki keyboard fisik. Jika keyboard +perangkat lunak tidak disediakan atau dinonaktifkan, maka ini hanya digunakan bila +keyboard fisik diekspos.
  • +
  • {@code keyshidden}: Perangkat memiliki keyboard fisik yang tersedia +tetapi tersembunyi dan perangkat tidak mengaktifkan keyboard perangkat lunak.
  • +
  • {@code keyssoft}: Perangkat mengaktifkan keyboard perangkat lunak, +baik itu terlihat maupun tidak.
  • +
+

Jika Anda menyediakan sumber daya keysexposed, namun bukan sumber daya keyssoft +, sistem akan menggunakan sumber daya keysexposed baik keyboard +terlihat atau tidak, asalkan sistem telah mengaktifkan keyboard perangkat lunak.

+

Ini bisa berubah selama masa pakai aplikasi jika pengguna membuka keyboard +fisik. Lihat Menangani Perubahan Runtime untuk informasi tentang bagaimana +hal ini memengaruhi aplikasi Anda selama runtime.

+

Lihat juga bidang konfigurasi {@link +android.content.res.Configuration#hardKeyboardHidden} dan {@link +android.content.res.Configuration#keyboardHidden}, yang menunjukkan visibilitas +keyboard fisik dan visibilitas segala jenis keyboard (termasuk keyboard perangkat lunak), masing-masing.

+
Metode input teks utama + nokeys
+ qwerty
+ 12key +
+
    +
  • {@code nokeys}: Perangkat tidak memiliki tombol fisik untuk input teks.
  • +
  • {@code qwerty}: Perangkat memiliki keyboard fisik qwerty, baik terlihat maupun tidak pada +pengguna +.
  • +
  • {@code 12key}: Perangkat memiliki keyboard fisik 12 tombol, baik terlihat maupun tidak +pada pengguna.
  • +
+

Lihat juga bidang konfigurasi {@link android.content.res.Configuration#keyboard}, +yang menunjukkan metode utama input teks yang tersedia.

+
Versi Platform (level API)Contoh:
+ v3
+ v4
+ v7
+ dll.
+

Level API yang didukung perangkat. Misalnya, v1 untuk API level +1 (perangkat dengan Android 1.0 atau yang lebih tinggi) dan v4 untuk API level 4 (perangkat dengan Android +1.6 atau yang lebih tinggi). Lihat dokumen Level API Android untuk informasi selengkapnya +tentang nilai-nilai ini.

+
+ + +

Catatan: Sebagian qualifier konfigurasi telah ditambahkan sejak Android +1.0, jadi tidak semua versi Android mendukung semua qualifier. Menggunakan qualifier baru secara implisit +akan menambahkan qualifier versi platform sehingga perangkat yang lebih lama pasti mengabaikannya. Misalnya, menggunakan qualifier +w600dp secara otomatis akan menyertakan qualifier v13, karena +qualifier lebar yang tersedia baru di API level 13. Untuk menghindari masalah, selalu sertakan satu set +sumber daya default (satu set sumber daya tanpa qualifier). Untuk informasi selengkapnya, lihat +bagian tentang Menyediakan Kompatibilitas Perangkat Terbaik dengan +Sumber Daya.

+ + + +

Aturan penamaan qualifier

+ +

Inilah beberapa aturan tentang penggunaan nama qualifier konfigurasi:

+ + + +

Setelah Anda menyimpan sumber daya alternatif ke dalam direktori yang diberi nama dengan +qualifier ini, Android secara otomatis menerapkan sumber daya dalam +aplikasi Anda berdasarkan pada konfigurasi perangkat saat ini. Setiap kali sumber daya diminta, Android akan memeriksa direktori sumber daya +alternatif berisi file sumber daya yang diminta, lalu mencari sumber daya yang +paling cocok(dibahas di bawah). Jika tidak ada sumber daya alternatif yang cocok +dengan konfigurasi perangkat tertentu, Android akan menggunakan sumber daya default terkait (set +sumber daya untuk tipe sumber daya tertentu yang tidak termasuk qualifier +konfigurasi).

+ + + +

Membuat sumber daya alias

+ +

Bila memiliki sumber daya yang ingin Anda gunakan untuk lebih dari satu konfigurasi +perangkat (namun tidak ingin menyediakannya sebagai sumber daya default), Anda tidak perlu menempatkan sumber daya +yang sama di lebih dari satu direktori sumber daya alternatif. Sebagai gantinya, (dalam beberapa kasus) Anda bisa membuat +sumber daya alternatif +yang berfungsi sebagai alias untuk sumber daya yang disimpan dalam direktori sumber daya default.

+ +

Catatan: Tidak semua sumber daya menawarkan mekanisme yang memungkinkan Anda +membuat alias ke sumber daya lain. Khususnya, animasi, menu, raw, dan +sumber daya lain yang tidak ditetapkan dalam direktori {@code xml/} tidak menawarkan fitur ini.

+ +

Misalnya, bayangkan Anda memiliki ikon aplikasi {@code icon.png}, dan membutuhkan versi uniknya +untuk lokal berbeda. Akan tetapi, dua lokal, bahasa Inggris-Kanada dan bahasa Prancis-Kanada, harus menggunakan +versi yang sama. Anda mungkin berasumsi bahwa Anda perlu menyalin gambar +yang sama ke dalam direktori sumber daya baik untuk bahasa Inggris-Kanada maupun bahasa Prancis-Kanada, namun +bukan demikian. Sebagai gantinya, Anda bisa menyimpan gambar yang sama-sama digunakan sebagai {@code icon_ca.png} (nama +apa saja selain {@code icon.png}) dan memasukkannya +dalam direktori default {@code res/drawable/}. Lalu buat file {@code icon.xml} dalam {@code +res/drawable-en-rCA/} dan {@code res/drawable-fr-rCA/} yang mengacu ke sumber daya {@code icon_ca.png} +yang menggunakan elemen {@code <bitmap>}. Hal ini memungkinkan Anda menyimpan satu versi saja dari +file PNG dan dua file XML kecil yang menunjuk ke sana. (Contoh file XML ditampilkan di bawah.)

+ + +

Drawable

+ +

Untuk membuat alias ke drawable yang ada, gunakan elemen {@code <bitmap>}. +Misalnya:

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
+    android:src="@drawable/icon_ca" />
+
+ +

Jika Anda menyimpan file ini sebagai {@code icon.xml} (dalam direktori sumber daya alternatif, seperti +{@code res/drawable-en-rCA/}), maka file akan dikompilasi menjadi sumber daya yang dapat Anda acu +sebagai {@code R.drawable.icon}, namun sebenarnya merupakan alias untuk sumber daya {@code +R.drawable.icon_ca} (yang disimpan dalam {@code res/drawable/}).

+ + +

Layout

+ +

Untuk membuat alias ke layout yang ada, gunakan elemen {@code <include>}, +yang dibungkus dalam {@code <merge>}. Misalnya:

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<merge>
+    <include layout="@layout/main_ltr"/>
+</merge>
+
+ +

Jika Anda menyimpan file ini sebagai {@code main.xml}, file akan dikompilasi menjadi sumber daya yang dapat Anda acu +sebagai {@code R.layout.main}, namun sebenarnya merupakan alias untuk sumber daya {@code R.layout.main_ltr} +.

+ + +

String dan nilai-nilai sederhana lainnya

+ +

Untuk membuat alias ke string yang ada, cukup gunakan ID sumber daya +dari string yang diinginkan sebagai nilai untuk string baru. Misalnya:

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <string name="hello">Hello</string>
+    <string name="hi">@string/hello</string>
+</resources>
+
+ +

Sumber daya {@code R.string.hi} sekarang merupakan alias untuk {@code R.string.hello}.

+ +

Nilai sederhana lainnya sama +cara kerjanya. Misalnya, sebuah warna:

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <color name="yellow">#f00</color>
+    <color name="highlight">@color/red</color>
+</resources>
+
+ + + + +

Menyediakan Kompatibilitas Perangkat Terbaik dengan Sumber Daya

+ +

Agar aplikasi Anda mendukung beberapa konfigurasi perangkat, +Anda harus selalu menyediakan sumber daya default untuk setiap tipe sumber daya yang menggunakan aplikasi Anda.

+ +

Misalnya, jika aplikasi Anda mendukung beberapa bahasa, sertakan selalu direktori {@code +values/} (tempat string Anda disimpan) tanpa qualifier bahasa dan wilayah. Jika sebaliknya Anda menempatkan semua file +string dalam direktori yang memiliki qualifier bahasa dan wilayah, maka aplikasi Anda akan crash saat berjalan +pada perangkat yang telah diatur ke bahasa yang tidak didukung string Anda. Namun asalkan Anda menyediakan sumber daya default +{@code values/}, aplikasi akan berjalan lancar (meskipun pengguna +tidak memahami bahasa itu—, ini lebih baik daripada crash).

+ +

Demikian pula, jika Anda menyediakan sumber daya layout berbeda berdasarkan orientasi layar, Anda harus +memilih satu orientasi sebagai default. Misalnya, sebagai ganti menyediakan sumber daya dalam {@code +layout-land/} untuk lanskap dan {@code layout-port/} untuk potret, biarkan salah satu sebagai default, seperti +{@code layout/} untuk lanskap dan {@code layout-port/} untuk potret.

+ +

Sumber daya default perlu disediakan bukan hanya karena aplikasi mungkin berjalan pada +konfigurasi yang belum Anda antisipasi, namun juga karena versi baru Android terkadang menambahkan +qualifier konfigurasi yang tidak didukung oleh versi lama. Jika Anda menggunakan qualifier sumber daya baru, +namun mempertahankan kompatibilitas kode dengan versi Android yang lebih lama, maka saat versi lama +Android menjalankan aplikasi, aplikasi itu akan crash jika Anda tidak menyediakan sumber daya default, aplikasi +tidak bisa menggunakan sumber daya yang dinamai dengan qualifier baru. Misalnya, jika {@code +minSdkVersion} Anda diatur ke 4, dan Anda memenuhi syarat semua sumber daya drawable dengan menggunakan mode malam ({@code night} atau {@code notnight}, yang ditambahkan di API +Level 8), maka perangkat API level 4 tidak bisa mengakses sumber daya drawable dan akan crash. Dalam hal +ini, Anda mungkin ingin {@code notnight} menjadi sumber daya default, jadi Anda harus mengecualikan +qualifier itu agar sumber daya drawable Anda ada dalam {@code drawable/} atau {@code drawable-night/}.

+ +

Jadi, agar bisa menyediakan kompatibilitas perangkat terbaik, sediakan selalu sumber daya +default untuk sumber daya yang diperlukan aplikasi Anda untuk berjalan dengan benar. Selanjutnya buatlah sumber daya +alternatif untuk konfigurasi perangkat tertentu dengan menggunakan qualifier konfigurasi.

+ +

Ada satu eksepsi untuk aturan ini: Jika {@code minSdkVersion} aplikasi Anda adalah 4 atau +lebih, Anda tidak memerlukan sumber daya drawable default saat menyediakan sumber daya +drawable alternatif dengan qualifier densitas layar. Tanpa sumber daya +drawable default sekali pun, Android bisa menemukan yang paling cocok di antara densitas layar alternatif dan menskalakan +bitmap sesuai kebutuhan. Akan tetapi, demi pengalaman terbaik pada semua jenis perangkat, Anda harus +menyediakan drawable alternatif untuk ketiga tipe densitas.

+ + + +

Cara Android Menemukan Sumber Daya yang Paling Cocok

+ +

Saat Anda meminta sumber daya yang Anda berikan alternatifnya, Android akan memilih +sumber daya alternatif yang akan digunakan saat runtime, bergantung pada konfigurasi perangkat saat ini. Untuk +mendemonstrasikan cara Android memilih sumber daya alternatif, anggaplah direktori drawable berikut +masing-masing berisi versi berbeda dari gambar yang sama:

+ +
+drawable/
+drawable-en/
+drawable-fr-rCA/
+drawable-en-port/
+drawable-en-notouch-12key/
+drawable-port-ldpi/
+drawable-port-notouch-12key/
+
+ +

Dan anggaplah yang berikut ini merupakan konfigurasi perangkatnya:

+ +

+Lokal = en-GB
+Orientasi layar = port
+Densitas piksel layar = hdpi
+Tipe layar sentuh = notouch
+Metode input teks utama = 12key +

+ +

Dengan membandingkan konfigurasi perangkat dengan sumber daya alternatif yang tersedia, Android akan memilih +drawable dari {@code drawable-en-port}.

+ +

Sistem akan menentukan keputusannya mengenai sumber daya yang akan digunakan dengan logika +berikut:

+ + +
+ +

Gambar 2. Bagan alur cara Android menemukan +sumber daya yang paling cocok.

+
+ + +
    +
  1. Menghapus file sumber daya yang bertentangan dengan konfigurasi perangkat. +

    Direktori drawable-fr-rCA/ dihapus karena bertentangan +dengan lokal en-GB.

    +
    +drawable/
    +drawable-en/
    +drawable-fr-rCA/
    +drawable-en-port/
    +drawable-en-notouch-12key/
    +drawable-port-ldpi/
    +drawable-port-notouch-12key/
    +
    +

    Eksepsi: Densitas piksel layar adalah satu qualifier yang +tidak dihapus karena bertentangan. Meskipun densitas layar perangkat adalah hdpi, +drawable-port-ldpi/ tidak dihapus karena setiap densitas layar +dianggap cocok untuk saat ini. Informasi selengkapnya tersedia dalam dokumen Mendukung Beberapa +Layar.

  2. + +
  3. Pilih qualifier berkedudukan tertinggi (berikutnya) dalam daftar (tabel 2). +(Mulai dengan MCC, lalu pindah ke bawah.)
  4. +
  5. Apakah salah satu direktori sumber daya menyertakan qualifier ini?
  6. + + + +
  7. Hapus direktori sumber daya yang tidak menyertakan qualifier ini. Dalam contoh, sistem +menghapus semua direktori yang tidak menyertakan qualifier bahasa:
  8. +
    +drawable/
    +drawable-en/
    +drawable-en-port/
    +drawable-en-notouch-12key/
    +drawable-port-ldpi/
    +drawable-port-notouch-12key/
    +
    +

    Eksepsi: Jika qualifier yang dimaksud adalah densitas piksel layar, +Android akan memilih opsi yang paling cocok dengan densitas layar perangkat. +Secara umum, Android lebih suka menurunkan skala gambar asli yang lebih besar daripada menaikkan skala +atas gambar asli yang lebih kecil. Lihat Mendukung Beberapa +Layar.

    + + +
  9. Kembali dan ulangi langkah 2, 3, dan 4 hingga tersisa satu direktori. Dalam contoh ini, orientasi +layar adalah qualifier berikutnya yang memiliki kecocokan. +Jadi, sumber daya yang tidak menetapkan orientasi layar akan dihapus: +
    +drawable-en/
    +drawable-en-port/
    +drawable-en-notouch-12key/
    +
    +

    Direktori yang tersisa adalah {@code drawable-en-port}.

    +
  10. +
+ +

Meskipun prosedur dijalankan untuk setiap sumber daya yang diminta, sistem akan mengoptimalkan beberapa aspek +lebih lanjut. Satu optimalisasi tersebut adalah bahwa setelah konfigurasi perangkat diketahui, sistem mungkin +akan menghapus sumber daya alternatif yang sama sekali tidak cocok. Misalnya, jika bahasa konfigurasi +adalah bahasa Inggris ("en"), maka setiap direktori sumber daya yang memiliki qualifier bahasa akan diatur ke +selain bahasa Inggris tidak akan pernah disertakan dalam pool sumber daya yang diperiksa (meskipun +direktori sumber daya tanpa qualifier bahasa masih disertakan).

+ +

Saat memilih sumber daya berdasarkan qualifier ukuran layar, sistem akan menggunakan +sumber daya yang didesain untuk layar yang lebih kecil daripada layar saat ini jika tidak ada sumber daya yang lebih cocok +(misalnya, layar ukuran besar akan menggunakan sumber daya layar ukuran normal jika diperlukan). Akan tetapi, +jika satu-satunya sumber daya yang tersedia lebih besar daripada layar saat ini, sistem +tidak akan menggunakannya dan aplikasi Anda akan crash jika tidak ada sumber daya lain yang cocok dengan konfigurasi +perangkat (misalnya, jika semua sumber daya layout ditandai dengan qualifier {@code xlarge}, +namun perangkat memiliki ukuran layar normal).

+ +

Catatan: Kedudukan qualifier (dalam tabel 2) lebih penting +daripada jumlah qualifier yang benar-benar pas dengan perangkat. Misalnya, dalam langkah 4 di atas, pilihan +terakhir pada daftar berisi tiga qualifier yang bebar-benar cocok dengan perangkat (orientasi, tipe +layar sentuh, dan metode input), sementara drawable-en hanya memiliki satu parameter yang cocok +(bahasa). Akan tetapi, bahasa memiliki kedudukan lebih tinggi dari pada qualifier lainnya, sehingga +drawable-port-notouch-12key tidak masuk.

+ +

Untuk mengetahui selengkapnya tentang cara menggunakan sumber daya dalam aplikasi, lanjutkan ke Mengakses Sumber Daya.

diff --git a/docs/html-intl/intl/in/guide/topics/resources/runtime-changes.jd b/docs/html-intl/intl/in/guide/topics/resources/runtime-changes.jd new file mode 100644 index 0000000000000000000000000000000000000000..c9a5ead63c77d4b1e36e5f2429bffd361746fbe0 --- /dev/null +++ b/docs/html-intl/intl/in/guide/topics/resources/runtime-changes.jd @@ -0,0 +1,281 @@ +page.title=Menangani Perubahan Runtime +page.tags=aktivitas,daur hidup +@jd:body + +
+
+ +

Dalam dokumen ini

+
    +
  1. Mempertahankan Objek Selama Perubahan Konfigurasi
  2. +
  3. Menangani Sendiri Perubahan Konfigurasi +
+ +

Lihat juga

+
    +
  1. Menyediakan Sumber Daya
  2. +
  3. Mengakses Sumber Daya
  4. +
  5. Perubahan + Orientasi Layar yang Lebih Cepat
  6. +
+
+
+ +

Sebagian konfigurasi perangkat bisa berubah selama runtime +(seperti orientasi layar, ketersediaan keyboard, dan bahasa). Saat perubahan demikian terjadi, +Android akan me-restart +{@link android.app.Activity} yang berjalan ({@link android.app.Activity#onDestroy()} dipanggil, diikuti oleh {@link +android.app.Activity#onCreate(Bundle) onCreate()}). Perilaku restart didesain untuk membantu +aplikasi Anda beradaptasi dengan konfigurasi baru melalui pemuatan kembali aplikasi Anda secara otomatis dengan +sumber daya alternatif sumber yang sesuai dengan konfigurasi perangkat baru.

+ +

Untuk menangani restart dengan benar, aktivitas Anda harus mengembalikan +statusnya seperti semula melalui Daur hidup +aktivitas normal, dalam hal ini Android akan memanggil +{@link android.app.Activity#onSaveInstanceState(Bundle) onSaveInstanceState()} sebelum menghentikan +aktivitas Anda sehingga Anda dapat menyimpan data mengenai status aplikasi. Selanjutnya Anda bisa mengembalikan status +selama {@link android.app.Activity#onCreate(Bundle) onCreate()} atau {@link +android.app.Activity#onRestoreInstanceState(Bundle) onRestoreInstanceState()}.

+ +

Untuk menguji bahwa aplikasi me-restart sendiri dengan status tak berubah, Anda harus +memanggil perubahan konfigurasi (seperti mengubah orientasi layar) saat melakukan berbagai +tugas dalam aplikasi. Aplikasi Anda harus dapat me-restart setiap saat tanpa kehilangan +data pengguna atau status untuk menangani kejadian seperti perubahan konfigurasi atau bila pengguna menerima panggilan telepon +masuk lalu kembali ke aplikasi setelah proses +aplikasi Anda dimusnahkan. Untuk mengetahui cara mengembalikan status aktivitas, bacalah tentang Daur hidup aktivitas.

+ +

Akan tetapi, Anda mungkin menemui situasi ketika me-restart aplikasi dan +mengembalikan data dalam jumlah besar malah menjadi mahal dan menghasilkan pengalaman pengguna yang buruk. Dalam situasi +demikian, Anda memiliki dua opsi lain:

+ +
    +
  1. Mempertahankan objek selama perubahan konfigurasi +

    Izinkan aktivitas Anda me-restart saat konfigurasi berubah, namun bawa objek +berstatus (stateful) ke instance baru aktivitas Anda.

    + +
  2. +
  3. Menangani sendiri perubahan konfigurasi +

    Cegah sistem me-restart aktivitas selama perubahan konfigurasi +tertentu, namun terima callback saat konfigurasi benar-benar berubah, agar Anda bisa memperbarui +aktivitas secara manual bila diperlukan.

    +
  4. +
+ + +

Mempertahankan Objek Selama Perubahan Konfigurasi

+ +

Jika me-restart aktivitas mengharuskan pemulihan seperangkat data dalam jumlah besar, menghubungkan kembali koneksi +jaringan, atau melakukan operasi intensif lainnya, maka restart penuh karena perubahan konfigurasi mungkin +menjadi pengalaman pengguna yang lambat. Selain itu, Anda mungkin tidak bisa sepenuhnya mengembalikan status +aktivitas dengan {@link android.os.Bundle} yang disimpan sistem untuk Anda dengan callback {@link +android.app.Activity#onSaveInstanceState(Bundle) onSaveInstanceState()}—itu tidaklah +didesain untuk membawa objek besar (seperti bitmap) dan data di dalamnya harus diserialkan kemudian +dinon-serialkan, yang bisa menghabiskan banyak memori dan membuat perubahan konfigurasi menjadi lambat. Dalam situasi +demikian, Anda bisa meringankan beban memulai kembali aktivitas Anda dengan mempertahankan {@link +android.app.Fragment} saat aktivitas Anda di-restart karena perubahan konfigurasi. Fragmen ini +bisa berisi acuan ke objek stateful yang ingin Anda pertahankan.

+ +

Bila sistem Android menghentikan aktivitas Anda karena perubahan konfigurasi, fragmen +aktivitas yang telah ditandai untuk dipertahankan tidak akan dimusnahkan. Anda dapat menambahkan fragmen tersebut ke +aktivitas untuk mempertahankan objek stateful.

+ +

Untuk mempertahankan objek stateful dalam fragmen selama perubahan konfigurasi runtime:

+ +
    +
  1. Perluas kelas {@link android.app.Fragment} dan deklarasikan referensi ke objek stateful +Anda.
  2. +
  3. Panggil {@link android.app.Fragment#setRetainInstance(boolean)} saat fragmen dibuat. +
  4. +
  5. Tambahkan fragmen ke aktivitas.
  6. +
  7. Gunakan {@link android.app.FragmentManager} untuk mengambil fragmen saat aktivitas +di-restart.
  8. +
+ +

Misalnya, definisikan fragmen sebagai berikut:

+ +
+public class RetainedFragment extends Fragment {
+
+    // data object we want to retain
+    private MyDataObject data;
+
+    // this method is only called once for this fragment
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        // retain this fragment
+        setRetainInstance(true);
+    }
+
+    public void setData(MyDataObject data) {
+        this.data = data;
+    }
+
+    public MyDataObject getData() {
+        return data;
+    }
+}
+
+ +

Perhatian: Meskipun bisa menyimpan objek apa saja, Anda +sama sekali tidak boleh meneruskan objek yang terkait dengan {@link android.app.Activity}, seperti {@link +android.graphics.drawable.Drawable}, {@link android.widget.Adapter}, {@link android.view.View} +atau objek lainnya mana pun yang terkait dengan {@link android.content.Context}. Jika Anda melakukannya, hal tersebut akan +membocorkan semua tampilan dan sumber daya instance aktivitas semula. (Sumber daya yang bocor +berarti bahwa aplikasi Anda tetap menyimpannya dan tidak bisa dijadikan kumpulan sampah, sehingga bisa banyak +memori yang hilang.)

+ +

Selanjutnya gunakan {@link android.app.FragmentManager} untuk menambahkan fragmen ke aktivitas. +Anda bisa memperoleh objek data dari fragmen saat aktivitas memulai kembali selama perubahan +konfigurasi runtime. Misalnya, definisikan aktivitas Anda sebagai berikut:

+ +
+public class MyActivity extends Activity {
+
+    private RetainedFragment dataFragment;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.main);
+
+        // find the retained fragment on activity restarts
+        FragmentManager fm = getFragmentManager();
+        dataFragment = (DataFragment) fm.findFragmentByTag(“data”);
+
+        // create the fragment and data the first time
+        if (dataFragment == null) {
+            // add the fragment
+            dataFragment = new DataFragment();
+            fm.beginTransaction().add(dataFragment, “data”).commit();
+            // load the data from the web
+            dataFragment.setData(loadMyData());
+        }
+
+        // the data is available in dataFragment.getData()
+        ...
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+        // store the data in the fragment
+        dataFragment.setData(collectMyLoadedData());
+    }
+}
+
+ +

Dalam contoh ini, {@link android.app.Activity#onCreate(Bundle) onCreate()} menambahkan fragmen +atau mengembalikan referensinya. {@link android.app.Activity#onCreate(Bundle) onCreate()} juga +menyimpan objek stateful dalam instance fragmen. +{@link android.app.Activity#onDestroy() onDestroy()} akan memperbarui objek stateful dalam +instance fragmen yang dipertahankan.

+ + + + + +

Menangani Sendiri Perubahan Konfigurasi

+ +

Jika aplikasi Anda tidak memerlukan pembaruan sumber daya selama perubahan konfigurasi +tertentu dan Anda memiliki keterbatasan kinerja yang mengharuskan Anda untuk +menghindari restart aktivitas, maka Anda bisa mendeklarasikan agar aktivitas Anda menangani sendiri perubahan +konfigurasinya, sehingga mencegah sistem me-restart aktivitas.

+ +

Catatan: Menangani sendiri perubahan konfigurasi bisa jauh lebih +mempersulit penggunaan sumber daya alternatif, karena sistem tidak menerapkannya secara otomatis +untuk Anda. Teknik ini harus dianggap sebagai usaha terakhir bila Anda harus menghindari restart +karena perubahan konfigurasi dan tidak disarankan untuk sebagian besar aplikasi.

+ +

Untuk mendeklarasikan agar aktivitas Anda menangani perubahan konfigurasi, edit elemen {@code <activity>} yang sesuai +dalam file manifes Anda agar menyertakan atribut {@code +android:configChanges} dengan nilai yang mewakili konfigurasi yang ingin +ditangani. Nilai yang memungkinkan tercantum dalam dokumentasi untuk atribut {@code +android:configChanges} (nilai paling sering digunakan adalah {@code "orientation"} untuk +mencegah restart saat orientasi layar berubah dan {@code "keyboardHidden"} untuk mencegah +restart saat ketersediaan keyboard berubah). Anda dapat mendeklarasikan beberapa nilai konfigurasi +dalam atribut dengan memisahkannya menggunakan karakter pipa {@code |}.

+ +

Misalnya, kode manifes berikut menyatakan aktivitas yang menangani +perubahan orientasi layar maupun perubahan ketersediaan keyboard:

+ +
+<activity android:name=".MyActivity"
+          android:configChanges="orientation|keyboardHidden"
+          android:label="@string/app_name">
+
+ +

Sekarang, bila salah satu konfigurasi ini berubah, {@code MyActivity} tidak akan me-restart. +Sebagai gantinya, {@code MyActivity} akan menerima panggilan ke {@link +android.app.Activity#onConfigurationChanged(Configuration) onConfigurationChanged()}. Metode ini +meneruskan objek {@link android.content.res.Configuration} yang menetapkan +konfigurasi perangkat baru. Dengan membaca bidang-bidang dalam {@link android.content.res.Configuration}, +Anda dapat menentukan konfigurasi baru dan membuat perubahan yang sesuai dengan memperbarui +sumber daya yang digunakan dalam antarmuka. Pada saat +metode ini dipanggil, objek {@link android.content.res.Resources} aktivitas Anda akan diperbarui untuk +mengembalikan sumber daya berdasarkan konfigurasi baru, jadi Anda bisa dengan mudah +me-reset elemen UI tanpa membuat sistem me-restart aktivitas Anda.

+ +

Perhatian: Mulai Android 3.2 (API level 13), +"ukuran layar" juga berubah bila perangkat beralih orientasi antara potret +dan lanskap. Jadi jika Anda tidak ingin runtime di-restart karena perubahan orientasi saat mengembangkan +API level 13 atau yang lebih tinggi (sebagaimana dideklarasikan oleh atribut {@code minSdkVersion} dan {@code targetSdkVersion} +), Anda harus menyertakan nilai {@code "screenSize"} selain nilai {@code +"orientation"}. Yaitu Anda harus mendeklarasikan {@code +android:configChanges="orientation|screenSize"}. Akan tetapi, jika aplikasi Anda menargetkan API level +12 atau yang lebih rendah, maka aktivitas Anda akan selalu menangani sendiri perubahan konfigurasi ini (perubahan +konfigurasi ini tidak me-restart aktivitas Anda, bahkan saat berjalan pada perangkat Android 3.2 atau yang lebih tinggi).

+ +

Misalnya, implementasi {@link +android.app.Activity#onConfigurationChanged(Configuration) onConfigurationChanged()} berikut akan +memeriksa orientasi perangkat saat ini:

+ +
+@Override
+public void onConfigurationChanged(Configuration newConfig) {
+    super.onConfigurationChanged(newConfig);
+
+    // Checks the orientation of the screen
+    if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
+        Toast.makeText(this, "landscape", Toast.LENGTH_SHORT).show();
+    } else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT){
+        Toast.makeText(this, "portrait", Toast.LENGTH_SHORT).show();
+    }
+}
+
+ +

Objek {@link android.content.res.Configuration} mewakili semua konfigurasi +saat ini, tidak hanya konfigurasi yang telah berubah. Seringkali Anda tidak perlu memperhatikan dengan persis bagaimana +konfigurasi berubah dan cukup menetapkan kembali semua sumber daya yang memberikan alternatif untuk +konfigurasi sedang ditangani. Misalnya, karena objek {@link +android.content.res.Resources} sekarang diperbarui, Anda dapat me-reset +setiap {@link android.widget.ImageView} dengan {@link android.widget.ImageView#setImageResource(int) +setImageResource()} +dan sumber daya yang sesuai untuk konfigurasi baru yang digunakan (seperti dijelaskan dalam Menyediakan Sumber Daya).

+ +

Perhatikan bahwa nilai-nilai dari bidang {@link +android.content.res.Configuration} adalah integer yang sesuai dengan konstanta spesifik +dari kelas {@link android.content.res.Configuration}. Untuk dokumentasi tentang konstanta +yang harus digunakan di setiap bidang, lihat bidang yang sesuai dalam referensi {@link +android.content.res.Configuration}.

+ +

Ingatlah: Saat mendeklarasikan aktivitas untuk menangani perubahan +konfigurasi, Anda bertanggung jawab untuk me-reset setiap elemen yang alternatifnya Anda berikan. Jika Anda +mendeklarasikan aktivitas untuk menangani perubahan orientasi dan memiliki gambar yang harus berubah +antara lanskap dan potret, Anda harus menetapkan kembali setiap sumber daya elemen selama {@link +android.app.Activity#onConfigurationChanged(Configuration) onConfigurationChanged()}.

+ +

Jika Anda tidak perlu memperbarui aplikasi berdasarkan perubahan +konfigurasi ini, sebagai gantinya Anda bisa saja tidak mengimplementasikan {@link +android.app.Activity#onConfigurationChanged(Configuration) onConfigurationChanged()}. Dalam +hal ini, semua sumber daya yang digunakan sebelum perubahan konfigurasi akan tetap digunakan +dan Anda hanya menghindari restart aktivitas. Akan tetapi, aplikasi Anda harus selalu +bisa dimatikan dan di-restart dengan status sebelumnya tetap utuh, sehingga Anda jangan menganggap teknik +ini sebagai jalan keluar untuk mempertahankan status selama daur hidup aktivitas normal. Tidak hanya +karena ada perubahan konfigurasi lainnya yang tidak bisa Anda cegah untuk me-restart aplikasi, namun +juga karena Anda harus menangani kejadian seperti saat pengguna meninggalkan aplikasi dan +dimusnahkan sebelum pengguna kembali ke aplikasi.

+ +

Untuk informasi selengkapnya tentang perubahan konfigurasi yang bisa Anda tangani dalam aktivitas, lihat dokumentasi {@code +android:configChanges} dan kelas {@link android.content.res.Configuration} +.

diff --git a/docs/html-intl/intl/in/guide/topics/ui/controls.jd b/docs/html-intl/intl/in/guide/topics/ui/controls.jd new file mode 100644 index 0000000000000000000000000000000000000000..7ee2957980a898008c8d5df15d821184e356a249 --- /dev/null +++ b/docs/html-intl/intl/in/guide/topics/ui/controls.jd @@ -0,0 +1,90 @@ +page.title=Kontrol Input +parent.title=Antarmuka Pengguna +parent.link=index.html +@jd:body + +
+ +
+ +

Kontrol input adalah komponen interaktif dalam antarmuka pengguna aplikasi Anda. Android menyediakan +aneka ragam kontrol yang bisa Anda gunakan dalam UI, seperti tombol, bidang teks, bilah pencarian, +kotak cek, tombol zoom, tombol toggle, dan masih banyak lagi.

+ +

Menambahkan sebuah kontrol input ke UI adalah semudah menambahkan satu elemen XML ke layout XML. Misalnya, inilah sebuah +layout dengan satu bidang teks dan satu tombol:

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    android:orientation="horizontal">
+    <EditText android:id="@+id/edit_message"
+        android:layout_weight="1"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:hint="@string/edit_message" />
+    <Button android:id="@+id/button_send"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/button_send"
+        android:onClick="sendMessage" />
+</LinearLayout>
+
+ +

Tiap kontrol input mendukung satu set kejadian input sehingga Anda bisa menangani berbagai kejadian seperti saat +pengguna memasukkan teks atau menyentuh tombol.

+ + +

Kontrol Umum

+

Berikut adalah daftar beberapa kontrol umum yang bisa Anda gunakan dalam aplikasi. Ikuti tautan ini untuk mengetahui +selengkapnya tentang penggunaannya masing-masing.

+ +

Catatan: Android menyediakan beberapa kontrol lain yang tidak tercantum +di sini. Telusuri paket {@link android.widget} untuk mengetahui selengkapnya. Jika aplikasi Anda memerlukan +semacam kontrol input tertentu, Anda bisa membangun komponen custom sendiri.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tipe KontrolKeteranganKelas Terkait
TombolTombol tekan yang bisa ditekan, atau diklik, oleh pengguna untuk melakukan suatu tindakan.{@link android.widget.Button Button}
Bidang teksBidang teks yang bisa diedit. Anda bisa menggunakan widget AutoCompleteTextView untuk membuat widget entri teks yang menyediakan saran pelengkapan otomatis{@link android.widget.EditText EditText}, {@link android.widget.AutoCompleteTextView}
Kotak cekSwitch aktif/nonaktif yang bisa diubah oleh pengguna. Anda harus menggunakan kotak cek saat menampilkan sekumpulan opsi yang bisa dipilih pengguna dan bila keduanya mungkin terjadi bersamaan.{@link android.widget.CheckBox CheckBox}
Tombol radioMirip dengan kotak cek, hanya saja cuma satu opsi yang bisa dipilih dalam kumpulan tersebut.{@link android.widget.RadioGroup RadioGroup} +
{@link android.widget.RadioButton RadioButton}
Tombol toggleTombol aktif/nonaktif dengan indikator cahaya.{@link android.widget.ToggleButton ToggleButton}
SpinnerDaftar tarik-turun yang memungkinkan pengguna memilih salah satu dari serangkaian nilai.{@link android.widget.Spinner Spinner}
PickerDialog bagi pengguna untuk memilih satu nilai dari satu kumpulan dengan menggunakan tombol naik/turun atau dengan gerakan mengusap. Gunakan widget DatePickercode> untuk memasukkan nilai tanggal (bulan, hari, tahun) atau widget TimePicker untuk memasukkan nilai waktu (jam, menit, AM/PM), yang akan diformat secara otomatis untuk lokasi pengguna tersebut.{@link android.widget.DatePicker}, {@link android.widget.TimePicker}
diff --git a/docs/html-intl/intl/in/guide/topics/ui/declaring-layout.jd b/docs/html-intl/intl/in/guide/topics/ui/declaring-layout.jd new file mode 100644 index 0000000000000000000000000000000000000000..83ff7461e7ac0bf0100abe1524ea6751fb6f58a0 --- /dev/null +++ b/docs/html-intl/intl/in/guide/topics/ui/declaring-layout.jd @@ -0,0 +1,492 @@ +page.title=Layout +page.tags=view,viewgroup +@jd:body + +
+
+

Dalam dokumen ini

+
    +
  1. Tulis XML
  2. +
  3. Muat Sumber Daya XML
  4. +
  5. Atribut +
      +
    1. ID
    2. +
    3. Parameter Layout
    4. +
    +
  6. +
  7. Posisi Layout
  8. +
  9. Ukuran, Pengisi, dan Margin
  10. +
  11. Layout Umum
  12. +
  13. Membangun Layout dengan Adaptor +
      +
    1. Mengisi tampilan adaptor dengan data
    2. +
    3. Menangani kejadian klik
    4. +
    +
  14. +
+ +

Kelas-kelas utama

+
    +
  1. {@link android.view.View}
  2. +
  3. {@link android.view.ViewGroup}
  4. +
  5. {@link android.view.ViewGroup.LayoutParams}
  6. +
+ +

Lihat juga

+
    +
  1. Membangun Antarmuka Pengguna +Sederhana
+
+ +

Layout mendefinisikan struktur visual untuk antarmuka pengguna, seperti UI sebuah aktivitas atau widget aplikasi. +Anda dapat mendeklarasikan layout dengan dua cara:

+ + +

Kerangka kerja Android memberi Anda fleksibilitas untuk menggunakan salah satu atau kedua metode ini guna mendeklarasikan dan mengelola UI aplikasi Anda. Misalnya, Anda bisa mendeklarasikan layout default aplikasi Anda dalam XML, termasuk elemen-elemen layar yang akan muncul di dalamnya dan di propertinya. Anda nanti bisa menambahkan kode dalam aplikasi yang akan memodifikasi status objek layar, termasuk yang dideklarasikan dalam XML, saat runtime.

+ + + +

Keuntungan mendeklarasikan UI dalam XML adalah karena hal ini memungkinkan Anda memisahkan penampilan aplikasi dari kode yang mengontrol perilakunya dengan lebih baik. Keterangan UI Anda bersifat eksternal bagi kode aplikasi Anda, yang berarti bahwa Anda bisa memodifikasi atau menyesuaikannya tanpa harus memodifikasi dan mengompilasi ulang kode sumber. Misalnya, Anda bisa membuat layout XML untuk berbagai orientasi layar, berbagai ukuran layar perangkat, dan berbagai bahasa. Selain itu, mendeklarasikan layout dalam XML akan mempermudah Anda memvisualisasikan struktur UI, sehingga lebih mudah merunut masalahnya. Karena itu, dokumen ini berfokus pada upaya mengajari Anda cara mendeklarasikan layout dalam XML. Jika Anda +tertarik dalam membuat instance objek View saat runtime, lihat referensi kelas {@link android.view.ViewGroup} dan +{@link android.view.View}.

+ +

Secara umum, kosakata XML untuk mendeklarasikan elemen UI mengikuti dengan sangat mirip struktur serta penamaan kelas dan metode, dengan nama elemen dipadankan dengan nama kelas dan nama atribut dipadankan dengan metode. Sebenarnya, pemadanan ini kerap kali begitu jelas sehingga Anda bisa menebak atribut XML yang berpadanan dengan sebuah metode kelas, atau menebak kelas yang berpadanan dengan sebuah elemen XML. Akan tetapi, perhatikan bahwa tidak semua kosakata identik. Dalam beberapa kasus, ada sedikit perbedaan penamaan. Misalnya +, elemen EditText memiliki atribut text yang berpadanan dengan +EditText.setText().

+ +

Tip: Ketahui selengkapnya berbagai tipe layout dalam Objek +Layout Umum. Ada juga sekumpulan tutorial tentang cara membangun berbagai layout dalam panduan tutorial +Hello Views.

+ +

Tulis XML

+ +

Dengan menggunakan kosakata XML Android, Anda bisa mendesain secara cepat layout UI dan elemen layar yang dimuatnya, sama dengan cara membuat halaman web dalam HTML — dengan serangkaian elemen tersarang.

+ +

Tiap file layout harus berisi persis satu elemen akar, yang harus berupa sebuah objek View atau ViewGroup. Setelah mendefinisikan elemen akar, Anda bisa menambahkan objek atau widget layout tambahan sebagai elemen anak untuk membangun hierarki View yang mendefinisikan layout Anda secara bertahap. Misalnya, inilah layout XML yang menggunakan {@link android.widget.LinearLayout} +vertikal untuk menyimpan {@link android.widget.TextView} dan {@link android.widget.Button}:

+
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:layout_width="match_parent"
+              android:layout_height="match_parent"
+              android:orientation="vertical" >
+    <TextView android:id="@+id/text"
+              android:layout_width="wrap_content"
+              android:layout_height="wrap_content"
+              android:text="Hello, I am a TextView" />
+    <Button android:id="@+id/button"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Hello, I am a Button" />
+</LinearLayout>
+
+ +

Setelah Anda mendeklarasikan layout dalam XML, simpanlah file dengan ekstensi .xml, +dalam direktori res/layout/ proyek Android, sehingga nanti bisa dikompilasi dengan benar.

+ +

Informasi selengkapnya tentang sintaks untuk file XML layout tersedia dalam dokumen Sumber Daya Layout.

+ +

Muat Sumber Daya XML

+ +

Saat mengompilasi aplikasi, masing-masing file layout XML akan dikompilasi dalam sebuah sumber daya +{@link android.view.View}. Anda harus memuat sumber daya layout dari kode aplikasi, dalam implementasi +callback {@link android.app.Activity#onCreate(android.os.Bundle) Activity.onCreate()}. +Lakukan dengan memanggil {@link android.app.Activity#setContentView(int) setContentView()}, +dengan meneruskan acuan ke sumber daya layout berupa: +R.layout.layout_file_name. +Misalnya, jika XML layout Anda disimpan sebagai main_layout.xml, Anda akan memuatnya +untuk Activity seperti ini:

+
+public void onCreate(Bundle savedInstanceState) {
+    super.onCreate(savedInstanceState);
+    setContentView(R.layout.main_layout);
+}
+
+ +

Metode callback onCreate() dalam Activity dipanggil oleh kerangka kerja Android saat +Activity Anda dijalankan (lihat diskusi tentang daur hidup, dalam dokumen +Aktivitas +).

+ + +

Atribut

+ +

Setiap objek View dan ViewGroup mendukung variasi atribut XML-nya sendiri. +Sebagian atribut bersifat spesifik untuk objek View (misalnya, TextView mendukung atribut textSize +), namun atribut ini juga diwarisi oleh sembarang objek View yang dapat memperluas kelas ini. +Sebagian atribut bersifat umum untuk semua objek View, karena diwarisi dari kelas View akar (seperti +atribut id). Dan, atribut lain dianggap sebagai "parameter layout" yaitu +atribut yang menjelaskan orientasi layout tertentu dari objek View, seperti yang didefinisikan oleh objek ViewGroup induk +dari objek itu.

+ +

ID

+ +

Objek View apa saja dapat memiliki ID integer yang dikaitkan dengannya, untuk mengidentifikasi secara unik View dalam pohon. +Bila aplikasi dikompilasi, ID ini akan diacu sebagai integer, namun ID biasanya +ditetapkan dalam file XML layout sebagai string, dalam atribut id. +Ini atribut XML yang umum untuk semua objek View +(yang didefinisikan oleh kelas {@link android.view.View}) dan Anda akan sering sekali menggunakannya. +Sintaks untuk ID dalam tag XML adalah:

+
android:id="@+id/my_button"
+ +

Simbol "at" (@) pada awal string menunjukkan parser XML harus mengurai dan memperluas +ID string selebihnya dan mengenalinya sebagai ID sumber daya. Simbol tanda tambah (+) berarti ini nama sumber daya baru yang harus +dibuat dan ditambahkan ke sumber daya kita (dalam file R.java). Ada sejumlah sumber daya ID lain yang +ditawarkan oleh kerangka kerja Android. Saat mengacu sebuah ID sumber daya Android, Anda tidak memerlukan simbol tanda tambah, +namun harus menambahkan namespace paket android, sehingga:

+
android:id="@android:id/empty"
+

Dengan namespace paket android yang tersedia, kita sekarang mengacu ID dari kelas sumber daya android.R +, daripada kelas sumber daya lokal.

+ +

Untuk membuat tampilan dan mengacunya dari aplikasi, pola yang umum adalah:

+
    +
  1. Mendefinisikan tampilan/widget dalam file layout dan memberinya ID unik: +
    +<Button android:id="@+id/my_button"
    +        android:layout_width="wrap_content"
    +        android:layout_height="wrap_content"
    +        android:text="@string/my_button_text"/>
    +
    +
  2. +
  3. Kemudian buat instance objek tampilan dan tangkap instance itu dari layout +(biasanya dalam metode {@link android.app.Activity#onCreate(Bundle) onCreate()}): +
    +Button myButton = (Button) findViewById(R.id.my_button);
    +
    +
  4. +
+

Mendefinisikan ID untuk objek tampilan adalah penting saat membuat {@link android.widget.RelativeLayout}. +Dalam layout relatif, tampilan saudara bisa mendefinisikan layout secara relatif terhadap tampilan saudara lainnya, +yang diacu melalui ID unik.

+

ID tidak perlu unik di seluruh pohon, namun harus +unik di bagian pohon yang Anda cari (yang mungkin sering kali seluruh pohon, jadi lebih baik +benar-benar unik bila memungkinkan).

+ + +

Parameter Layout

+ +

Atribut layout XML bernama layout_something mendefinisikan +parameter layout View yang cocok untuk ViewGroup tempatnya berada.

+ +

Setiap kelas ViewGroup mengimplementasikan kelas tersarang yang memperluas {@link +android.view.ViewGroup.LayoutParams}. Subkelas ini +berisi tipe properti yang mendefinisikan ukuran dan posisi masing-masing tampilan anak, sebagaimana +mestinya untuk grup tampilan. Seperti yang bisa Anda lihat dalam gambar 1, +grup tampilan induk mendefinisikan parameter layout untuk masing-masing tampilan anak (termasuk grup tampilan anak).

+ + +

Gambar 1. Visualisasi hierarki tampilan dengan parameter layout +yang dikaitkan dengan tiap tampilan.

+ +

Perhatikan bahwa setiap subkelas LayoutParams memiliki sintaksnya sendiri untuk menetapkan +nilai-nilai. Tiap elemen anak harus mendefinisikan LayoutParams yang semestinya bagi induknya, +meskipun elemen itu bisa juga mendefinisikan LayoutParams untuk anak-anaknya sendiri.

+ +

Semua grup tampilan berisi lebar dan tinggi (layout_width dan +layout_height), dan masing-masing tampilan harus mendefinisikannya. Banyak +LayoutParams yang juga menyertakan margin dan border opsional.

+ +

Anda bisa menetapkan lebar dan tinggi dengan ukuran persis, meskipun Anda mungkin +tidak ingin sering-sering melakukannya. Lebih sering, Anda akan menggunakan salah satu konstanta ini untuk +mengatur lebar atau tinggi:

+ + + +

Secara umum, menetapkan lebar dan tinggi layout dengan satuan mutlak seperti +piksel tidaklah disarankan. Melainkan dengan menggunakan ukuran relatif seperti +satuan piksel yang tidak bergantung pada kerapatan (dp), wrap_content, atau +match_parent, adalah sebuah pendekatan yang lebih baik, karena membantu memastikan bahwa +aplikasi Anda akan ditampilkan dengan benar pada berbagai ukuran layar perangkat. +Tipe ukuran yang diterima didefinisikan dalam dokumen + +Sumber Daya yang Tersedia.

+ + +

Posisi Layout

+

+ Geometri tampilan adalah persegi panjang. Sebuah tampilan memiliki lokasi, + yang dinyatakan berupa sepasang koordinat kiri dan atas, dan + dua dimensi, yang dinyatakan berupa lebar dan tinggi. Satuan untuk lokasi + dan dimensi adalah piksel. +

+ +

+ Lokasi tampilan dapat diambil dengan memanggil metode + {@link android.view.View#getLeft()} dan {@link android.view.View#getTop()}. Metode terdahulu menghasilkan koordinat kiri, atau X, + persegi panjang yang mewakili tampilan. Metode selanjutnya menghasilkan koordinat + atas, atau Y, persegi panjang yang mewakili tampilan. Kedua metode ini + menghasilkan lokasi tampilan relatif terhadap induknya. Misalnya, + bila getLeft() menghasilkan 20, berarti tampilan berlokasi 20 piksel ke + kanan dari tepi kiri induk langsungnya. +

+ +

+ Selain itu, beberapa metode praktis ditawarkan untuk menghindari komputasi yang tidak perlu, + yakni {@link android.view.View#getRight()} dan {@link android.view.View#getBottom()}. + Kedua metode ini menghasilkan koordinat tepi kanan dan bawah + persegi panjang yang mewakili tampilan. Misalnya, memanggil {@link android.view.View#getRight()} + serupa dengan komputasi berikut: getLeft() + getWidth(). +

+ + +

Ukuran, Pengisi, dan Margin

+

+ Ukuran tampilan dinyatakan dengan lebar dan tinggi. Tampilan sebenarnya + memiliki dua pasang nilai lebar dan tinggi. +

+ +

+ Sepasang pertama disebut lebar terukur dan + tinggi terukur. Dimensi ini mendefinisikan seberapa besar tampilan yang diinginkan + dalam induknya. Dimensi + terukur bisa diperoleh dengan memanggil {@link android.view.View#getMeasuredWidth()} + dan {@link android.view.View#getMeasuredHeight()}. +

+ +

+ Sepasang kedua cukup disebut dengan lebar dan tinggi, atau + kadang-kadang lebar penggambaran dan tinggi penggambaran. Dimensi-dimensi ini + mendefinisikan ukuran tampilan sebenarnya pada layar, saat digambar dan + setelah layout. Nilai-nilai ini mungkin, namun tidak harus, berbeda dengan + lebar dan tinggi terukur. Lebar dan tinggi bisa diperoleh dengan memanggil + {@link android.view.View#getWidth()} dan {@link android.view.View#getHeight()}. +

+ +

+ Untuk mengukur dimensinya, tampilan akan memperhitungkan pengisinya (padding). Pengisi + dinyatakan dalam piksel untuk bagian kiri, atas, kanan, dan bawah tampilan. + Pengisi bisa digunakan untuk meng-offset isi tampilan dengan + piksel dalam jumlah tertentu. Misalnya, pengisi kiri sebesar 2 akan mendorong isi tampilan sebanyak + 2 piksel ke kanan dari tepi kiri. Pengisi bisa diatur menggunakan + metode {@link android.view.View#setPadding(int, int, int, int)} dan diketahui dengan memanggil + {@link android.view.View#getPaddingLeft()}, {@link android.view.View#getPaddingTop()}, + {@link android.view.View#getPaddingRight()}, dan {@link android.view.View#getPaddingBottom()}. +

+ +

+ Meskipun bisa mendefinisikan pengisi, tampilan tidak menyediakan dukungan untuk + margin. Akan tetapi, grup tampilan menyediakan dukungan tersebut. Lihat + {@link android.view.ViewGroup} dan + {@link android.view.ViewGroup.MarginLayoutParams} untuk informasi lebih jauh. +

+ +

Untuk informasi selengkapnya tentang dimensi, lihat + Nilai-Nilai Dimensi. +

+ + + + + + + + + + + +

Layout Umum

+ +

Tiap subkelas dari kelas {@link android.view.ViewGroup} menyediakan cara unik untuk menampilkan +tampilan yang Anda sarangkan di dalamnya. Di bawah ini adalah beberapa tipe layout lebih umum yang dibuat +ke dalam platform Android.

+ +

Catatan: Walaupun Anda bisa menyarangkan satu atau beberapa layout dalam +layout lain untuk mendapatkan desain UI, Anda harus berusaha menjaga hierarki layout sedangkal +mungkin. Layout Anda akan digambar lebih cepat jika memiliki layout tersarang yang lebih sedikit (hierarki tampilan yang melebar +lebih baik daripada hierarki tampilan yang dalam).

+ + + + +
+

Layout Linier

+ +

Layout yang mengatur anak-anaknya menjadi satu baris horizontal atau vertikal. Layout ini + akan membuat scrollbar jika panjang jendela melebihi panjang layar.

+
+ +
+

Layout Relatif

+ +

Memungkinkan Anda menentukan lokasi objek anak relatif terhadap satu sama lain (anak A di +kiri anak B) atau terhadap induk (disejajarkan dengan atas induknya).

+
+ +
+

Tampilan Web

+ +

Menampilkan halaman web.

+
+ + + + +

Membangun Layout dengan Adaptor

+ +

Bila isi layout bersifat dinamis atau tidak dipastikan sebelumnya, Anda bisa menggunakan layout yang menjadi +subkelas {@link android.widget.AdapterView} untuk mengisi layout dengan tampilan saat runtime. +Subkelas dari kelas {@link android.widget.AdapterView} menggunakan {@link android.widget.Adapter} untuk +mengikat data ke layoutnya. {@link android.widget.Adapter} berfungsi sebagai penghubung antara sumber data +dan layout{@link android.widget.AdapterView}—{@link android.widget.Adapter} +menarik data (dari suatu sumber seperti larik (array) atau query database) dan mengubah setiap entri +menjadi tampilan yang bisa ditambahkan ke dalam layout {@link android.widget.AdapterView}.

+ +

Layout umum yang didukung oleh adaptor meliputi:

+ +
+

Tampilan Daftar

+ +

Menampilkan daftar kolom tunggal yang bergulir.

+
+ +
+

Tampilan Petak

+ +

Menampilkan petak bergulir yang terdiri atas kolom dan baris.

+
+ + + +

Mengisi tampilan adaptor dengan data

+ +

Anda bisa mengisi {@link android.widget.AdapterView} seperti {@link android.widget.ListView} atau +{@link android.widget.GridView} dengan mengikat instance {@link android.widget.AdapterView} ke +{@link android.widget.Adapter}, yang akan mengambil data dari sumber eksternal dan membuat {@link +android.view.View} yang mewakili tiap entri data.

+ +

Android menyediakan beberapa subkelas {@link android.widget.Adapter} yang berguna untuk +menarik berbagai jenis data dan membangun tampilan untuk {@link android.widget.AdapterView}. Dua + adaptor yang paling umum adalah:

+ +
+
{@link android.widget.ArrayAdapter}
+
Gunakan adaptor ini bila sumber data Anda berupa larik. Secara default, {@link +android.widget.ArrayAdapter} akan membuat tampilan untuk tiap elemen larik dengan memanggil {@link +java.lang.Object#toString()} pada tiap elemen dan menempatkan isinya dalam {@link +android.widget.TextView}. +

Misalnya, jika Anda memiliki satu larik string yang ingin ditampilkan dalam {@link +android.widget.ListView}, buatlah {@link android.widget.ArrayAdapter} baru dengan konstruktor +untuk menentukan layout setiap string dan larik string:

+
+ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
+        android.R.layout.simple_list_item_1, myStringArray);
+
+

Argumen-argumen untuk konstruktor ini adalah:

+ +

Kemudian tinggal panggil +{@link android.widget.ListView#setAdapter setAdapter()} pada {@link android.widget.ListView} Anda:

+
+ListView listView = (ListView) findViewById(R.id.listview);
+listView.setAdapter(adapter);
+
+ +

Untuk menyesuaikan penampilan setiap item, Anda bisa mengesampingkan metode {@link +java.lang.Object#toString()} bagi objek dalam larik Anda. Atau, untuk membuat tampilan tiap +elemen selain {@link android.widget.TextView} (misalnya, jika Anda menginginkan +{@link android.widget.ImageView} bagi setiap item larik), perluas kelas {@link +android.widget.ArrayAdapter} dan kesampingkan {@link android.widget.ArrayAdapter#getView +getView()} agar memberikan tipe tampilan yang Anda inginkan bagi tiap item.

+ +
+ +
{@link android.widget.SimpleCursorAdapter}
+
Gunakan adaptor ini bila data Anda berasal dari {@link android.database.Cursor}. Saat +menggunakan {@link android.widget.SimpleCursorAdapter}, Anda harus menentukan layout yang akan digunakan untuk tiap +baris dalam {@link android.database.Cursor} dan di kolom mana di {@link android.database.Cursor} +harus memasukkan tampilan layout. Misalnya, jika Anda ingin untuk membuat daftar +nama orang dan nomor telepon, Anda bisa melakukan query yang menghasilkan {@link +android.database.Cursor} yang berisi satu baris untuk tiap orang dan kolom-kolom untuk nama dan +nomor. Selanjutnya Anda membuat larik string yang menentukan kolom dari {@link +android.database.Cursor} yang Anda inginkan dalam layout untuk setiap hasil dan larik integer yang menentukan +tampilan yang sesuai untuk menempatkan masing-masing kolom:

+
+String[] fromColumns = {ContactsContract.Data.DISPLAY_NAME,
+                        ContactsContract.CommonDataKinds.Phone.NUMBER};
+int[] toViews = {R.id.display_name, R.id.phone_number};
+
+

Bila Anda membuat instance {@link android.widget.SimpleCursorAdapter}, teruskan layout yang akan digunakan untuk +setiap hasil, {@link android.database.Cursor} yang berisi hasil tersebut, dan dua larik ini:

+
+SimpleCursorAdapter adapter = new SimpleCursorAdapter(this,
+        R.layout.person_name_and_number, cursor, fromColumns, toViews, 0);
+ListView listView = getListView();
+listView.setAdapter(adapter);
+
+

{@link android.widget.SimpleCursorAdapter} kemudian membuat tampilan untuk tiap baris dalam +{@link android.database.Cursor} dengan layout yang disediakan dengan memasukkan setiap item {@code +fromColumns} ke dalam tampilan {@code toViews} yang sesuai.

+
+ + +

Jika, selama aplikasi berjalan, Anda mengubah data sumber yang dibaca oleh +adaptor, maka Anda harus memanggil {@link android.widget.ArrayAdapter#notifyDataSetChanged()}. Hal ini akan +memberi tahu tampilan yang bersangkutan bahwa data telah berubah dan tampilan harus memperbarui dirinya sendiri.

+ + + +

Menangani kejadian klik

+ +

Anda bisa merespons kejadian klik pada setiap item dalam {@link android.widget.AdapterView} dengan +menerapkan antarmuka {@link android.widget.AdapterView.OnItemClickListener}. Misalnya:

+ +
+// Create a message handling object as an anonymous class.
+private OnItemClickListener mMessageClickedHandler = new OnItemClickListener() {
+    public void onItemClick(AdapterView parent, View v, int position, long id) {
+        // Do something in response to the click
+    }
+};
+
+listView.setOnItemClickListener(mMessageClickedHandler);
+
+ + + diff --git a/docs/html-intl/intl/in/guide/topics/ui/dialogs.jd b/docs/html-intl/intl/in/guide/topics/ui/dialogs.jd new file mode 100644 index 0000000000000000000000000000000000000000..65714a9e4c4dfd1387b2be5f93d25362ed1fdffe --- /dev/null +++ b/docs/html-intl/intl/in/guide/topics/ui/dialogs.jd @@ -0,0 +1,798 @@ +page.title=Dialog +page.tags=alertdialog,dialogfragment + +@jd:body + + + +
+
+

Dalam dokumen ini

+
    +
  1. Membuat Fragmen Dialog
  2. +
  3. Membuat Dialog Peringatan +
      +
    1. Menambahkan tombol
    2. +
    3. Menambahkan daftar
    4. +
    5. Membuat Layout Custom
    6. +
    +
  4. +
  5. Meneruskan Kejadian Kembali ke Host Dialog
  6. +
  7. Menampilkan Dialog
  8. +
  9. Menampilkan Dialog sebagai Layar Penuh atau Fragmen Tertanam +
      +
    1. Menampilkan aktivitas sebagai dialog pada layar besar
    2. +
    +
  10. +
  11. Menutup Dialog
  12. +
+ +

Kelas-kelas utama

+
    +
  1. {@link android.app.DialogFragment}
  2. +
  3. {@link android.app.AlertDialog}
  4. +
+ +

Lihat juga

+
    +
  1. Panduan desain dialog
  2. +
  3. Picker (dialog Tanggal/Waktu)
  4. +
+
+
+ +

Dialog adalah jendela kecil yang meminta pengguna untuk +membuat keputusan atau memasukkan informasi tambahan. Dialog tidak mengisi layar dan +biasanya digunakan untuk kejadian modal yang mengharuskan pengguna untuk melakukan tindakan sebelum bisa melanjutkan.

+ +
+

Desain Dialog

+

Untuk informasi tentang cara mendesain dialog, termasuk saran + untuk bahasa, bacalah panduan Desain dialog.

+
+ + + +

Kelas {@link android.app.Dialog} adalah kelas basis untuk dialog, namun Anda +harus menghindari pembuatan instance {@link android.app.Dialog} secara langsung. +Sebagai gantinya, gunakan salah satu subkelas berikut:

+
+
{@link android.app.AlertDialog}
+
Dialog yang bisa menampilkan judul, hingga tiga tombol, daftar + item yang dapat dipilih, atau layout custom.
+
{@link android.app.DatePickerDialog} atau {@link android.app.TimePickerDialog}
+
Dialog berisi UI yang sudah didefinisikan dan memungkinkan pengguna memilih tanggal atau waktu.
+
+ + + +

Kelas-kelas ini mendefinisikan gaya dan struktur dialog Anda, namun Anda harus +menggunakan {@link android.support.v4.app.DialogFragment} sebagai kontainer dialog Anda. +Kelas {@link android.support.v4.app.DialogFragment} menyediakan semua kontrol yang Anda +perlukan untuk membuat dialog dan mengelola penampilannya, sebagai ganti memanggil metode +pada objek {@link android.app.Dialog}.

+ +

Menggunakan {@link android.support.v4.app.DialogFragment} untuk mengelola dialog +akan memastikan bahwa kelas itu menangani kejadian daur hidup +dengan benar seperti ketika pengguna menekan tombol Back atau memutar layar. Kelas {@link +android.support.v4.app.DialogFragment} juga memungkinkan Anda menggunakan ulang dialog UI sebagai +komponen yang bisa ditanamkan dalam UI yang lebih besar, persis seperti {@link +android.support.v4.app.Fragment} tradisional (seperti saat Anda ingin dialog UI muncul berbeda +pada layar besar dan kecil).

+ +

Bagian-bagian berikutnya dalam panduan ini akan menjelaskan cara menggunakan {@link +android.support.v4.app.DialogFragment} yang dikombinasikan dengan objek {@link android.app.AlertDialog} +. Jika Anda ingin membuat picker tanggal atau waktu, Anda harus membaca panduan +Picker.

+ +

Catatan: +Karena kelas {@link android.app.DialogFragment} mulanya ditambahkan pada +Android 3.0 (API level 11), dokumen ini menjelaskan cara menggunakan kelas {@link +android.support.v4.app.DialogFragment} yang disediakan bersama Pustaka Dukungan. Dengan menambahkan pustaka ini +ke aplikasi, Anda bisa menggunakan {@link android.support.v4.app.DialogFragment} dan berbagai +API lain pada perangkat yang menjalankan Android 1.6 atau yang lebih tinggi. Jika versi minimum yang didukung aplikasi Anda +adalah API level 11 atau yang lebih tinggi, maka Anda bisa menggunakan versi kerangka kerja {@link +android.app.DialogFragment}, namun ketahuilah bahwa tautan dalam dokumen ini adalah untuk API +pustaka dukungan. Saat menggunakan pustaka dukungan, +pastikan Anda mengimpor kelas android.support.v4.app.DialogFragment +dan bukan android.app.DialogFragment.

+ + +

Membuat Fragmen Dialog

+ +

Anda bisa menghasilkan beragam rancangan dialog—termasuk +layout custom dan desain yang dijelaskan dalam panduan desain Dialog +—dengan memperluas +{@link android.support.v4.app.DialogFragment} dan membuat {@link android.app.AlertDialog} +dalam metode callback {@link android.support.v4.app.DialogFragment#onCreateDialog +onCreateDialog()}.

+ +

Misalnya, berikut ini sebuah {@link android.app.AlertDialog} dasar yang dikelola dalam +{@link android.support.v4.app.DialogFragment}:

+ +
+public class FireMissilesDialogFragment extends DialogFragment {
+    @Override
+    public Dialog onCreateDialog(Bundle savedInstanceState) {
+        // Use the Builder class for convenient dialog construction
+        AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+        builder.setMessage(R.string.dialog_fire_missiles)
+               .setPositiveButton(R.string.fire, new DialogInterface.OnClickListener() {
+                   public void onClick(DialogInterface dialog, int id) {
+                       // FIRE ZE MISSILES!
+                   }
+               })
+               .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
+                   public void onClick(DialogInterface dialog, int id) {
+                       // User cancelled the dialog
+                   }
+               });
+        // Create the AlertDialog object and return it
+        return builder.create();
+    }
+}
+
+ +
+ +

Gambar 1. +Dialog dengan satu pesan dan dua tombol tindakan.

+
+ +

Sekarang, bila Anda membuat instance kelas ini dan memanggil {@link +android.support.v4.app.DialogFragment#show show()} pada objek itu, dialog akan muncul seperti +yang ditampilkan dalam gambar 1.

+ +

Bagian berikutnya menjelaskan lebih jauh tentang penggunaan API {@link android.app.AlertDialog.Builder} +untuk membuat dialog.

+ +

Bergantung pada seberapa rumit dialog tersebut, Anda bisa menerapkan berbagai metode callback lain +dalam {@link android.support.v4.app.DialogFragment}, termasuk semua +metode daur hidup fragmen dasar. + + + + + +

Membuat Dialog Peringatan

+ + +

Kelas {@link android.app.AlertDialog} memungkinkan Anda membuat berbagai desain dialog dan +seringkali satu-satunya kelas dialog yang akan Anda perlukan. +Seperti yang ditampilkan dalam gambar 2, ada tiga area pada dialog peringatan:

+ +
+ +

Gambar 2. Layout dialog.

+
+ +
    +
  1. Judul +

    Area ini opsional dan hanya boleh digunakan bila area konten + ditempati oleh pesan terperinci, daftar, atau layout custom. Jika Anda perlu menyatakan + pesan atau pertanyaan sederhana (seperti dialog dalam gambar 1), Anda tidak memerlukan judul.

  2. +
  3. Area konten +

    Area ini bisa menampilkan pesan, daftar, atau layout custom lainnya.

  4. +
  5. Tombol tindakan +

    Tidak boleh ada lebih dari tiga tombol tindakan dalam sebuah dialog.

  6. +
+ +

Kelas {@link android.app.AlertDialog.Builder} + menyediakan API yang memungkinkan Anda membuat {@link android.app.AlertDialog} +dengan jenis konten ini, termasuk layout custom.

+ +

Untuk membuat {@link android.app.AlertDialog}:

+ +
+// 1. Instantiate an {@link android.app.AlertDialog.Builder} with its constructor
+AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+
+// 2. Chain together various setter methods to set the dialog characteristics
+builder.setMessage(R.string.dialog_message)
+       .setTitle(R.string.dialog_title);
+
+// 3. Get the {@link android.app.AlertDialog} from {@link android.app.AlertDialog.Builder#create()}
+AlertDialog dialog = builder.create();
+
+ +

Topik-topik selanjutnya menampilkan cara mendefinisikan berbagai atribut dialog dengan menggunakan +kelas {@link android.app.AlertDialog.Builder}.

+ + + + +

Menambahkan tombol

+ +

Untuk menambahkan tombol tindakan seperti dalam gambar 2, +panggil metode {@link android.app.AlertDialog.Builder#setPositiveButton setPositiveButton()} dan +{@link android.app.AlertDialog.Builder#setNegativeButton setNegativeButton()}:

+ +
+AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+// Add the buttons
+builder.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
+           public void onClick(DialogInterface dialog, int id) {
+               // User clicked OK button
+           }
+       });
+builder.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
+           public void onClick(DialogInterface dialog, int id) {
+               // User cancelled the dialog
+           }
+       });
+// Set other dialog properties
+...
+
+// Create the AlertDialog
+AlertDialog dialog = builder.create();
+
+ +

Metode set...Button() mengharuskan adanya judul bagi tombol (disediakan +oleh suatu sumber daya string) dan +{@link android.content.DialogInterface.OnClickListener} yang mendefinisikan tindakan yang diambil +bila pengguna menekan tombol.

+ +

Ada tiga macam tombol tindakan yang Anda bisa tambahkan:

+
+
Positif
+
Anda harus menggunakan tipe ini untuk menerima dan melanjutkan tindakan (tindakan "OK").
+
Negatif
+
Anda harus menggunakan tipe ini untuk membatalkan tindakan.
+
Netral
+
Anda harus menggunakan tipe ini bila pengguna mungkin tidak ingin melanjutkan tindakan, + namun tidak ingin membatalkan. Tipe ini muncul antara tombol positif dan +tombol negatif. Misalnya, tindakan bisa berupa "Ingatkan saya nanti".
+
+ +

Anda hanya bisa menambahkan salah satu tipe tombol ke {@link +android.app.AlertDialog}. Artinya, Anda tidak bisa memiliki lebih dari satu tombol "positif".

+ + + +
+ +

Gambar 3. +Dialog dengan satu judul dan daftar.

+
+ +

Menambahkan daftar

+ +

Ada tiga macam daftar yang tersedia pada API {@link android.app.AlertDialog}:

+ + +

Untuk membuat daftar pilihan tunggal seperti dalam gambar 3, +gunakan metode {@link android.app.AlertDialog.Builder#setItems setItems()}:

+ +
+@Override
+public Dialog onCreateDialog(Bundle savedInstanceState) {
+    AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+    builder.setTitle(R.string.pick_color)
+           .setItems(R.array.colors_array, new DialogInterface.OnClickListener() {
+               public void onClick(DialogInterface dialog, int which) {
+               // The 'which' argument contains the index position
+               // of the selected item
+           }
+    });
+    return builder.create();
+}
+
+ +

Karena daftar muncul dalam area konten dialog, +dialog tidak bisa menampilkan pesan dan daftar sekaligus dan Anda harus menetapkan judul untuk +dialog dengan {@link android.app.AlertDialog.Builder#setTitle setTitle()}. +Untuk menentukan item daftar, panggil {@link +android.app.AlertDialog.Builder#setItems setItems()}, dengan meneruskan larik. +Atau, Anda bisa menetapkan daftar menggunakan {@link +android.app.AlertDialog.Builder#setAdapter setAdapter()}. Hal ini memungkinkan Anda mendukung daftar +dengan data dinamis (seperti dari database) dengan menggunakan {@link android.widget.ListAdapter}.

+ +

Jika Anda memilih untuk mendukung daftar dengan {@link android.widget.ListAdapter}, +selalu gunakan sebuah {@link android.support.v4.content.Loader} agar konten dimuat +secara asinkron. Hal ini dijelaskan lebih jauh dalam panduan +Membuat Layout +dengan Adaptor dan Loader +.

+ +

Catatan: Secara default, menyentuh sebuah item daftar akan menutup dialog, +kecuali Anda menggunakan salah satu daftar pilihan persisten berikut ini.

+ +
+ +

Gambar 4. +Daftar item pilihan ganda.

+
+ + +

Menambahkan daftar pilihan ganda atau pilihan tunggal persisten

+ +

Untuk menambahkan daftar item pilihan ganda (kotak cek) atau +item pilihan tunggal (tombol radio), gunakan masing-masing metode +{@link android.app.AlertDialog.Builder#setMultiChoiceItems(Cursor,String,String, +DialogInterface.OnMultiChoiceClickListener) setMultiChoiceItems()}, atau +{@link android.app.AlertDialog.Builder#setSingleChoiceItems(int,int,DialogInterface.OnClickListener) +setSingleChoiceItems()}.

+ +

Misalnya, berikut ini cara membuat daftar pilihan ganda seperti +yang ditampilkan dalam gambar 4 yang menyimpan item +yang dipilih dalam {@link java.util.ArrayList}:

+ +
+@Override
+public Dialog onCreateDialog(Bundle savedInstanceState) {
+    mSelectedItems = new ArrayList();  // Where we track the selected items
+    AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+    // Set the dialog title
+    builder.setTitle(R.string.pick_toppings)
+    // Specify the list array, the items to be selected by default (null for none),
+    // and the listener through which to receive callbacks when items are selected
+           .setMultiChoiceItems(R.array.toppings, null,
+                      new DialogInterface.OnMultiChoiceClickListener() {
+               @Override
+               public void onClick(DialogInterface dialog, int which,
+                       boolean isChecked) {
+                   if (isChecked) {
+                       // If the user checked the item, add it to the selected items
+                       mSelectedItems.add(which);
+                   } else if (mSelectedItems.contains(which)) {
+                       // Else, if the item is already in the array, remove it 
+                       mSelectedItems.remove(Integer.valueOf(which));
+                   }
+               }
+           })
+    // Set the action buttons
+           .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
+               @Override
+               public void onClick(DialogInterface dialog, int id) {
+                   // User clicked OK, so save the mSelectedItems results somewhere
+                   // or return them to the component that opened the dialog
+                   ...
+               }
+           })
+           .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
+               @Override
+               public void onClick(DialogInterface dialog, int id) {
+                   ...
+               }
+           });
+
+    return builder.create();
+}
+
+ +

Walaupun daftar tradisional maupun daftar dengan tombol radio +menyediakan tindakan "pilihan tunggal", Anda harus menggunakan {@link +android.app.AlertDialog.Builder#setSingleChoiceItems(int,int,DialogInterface.OnClickListener) +setSingleChoiceItems()} jika ingin mempertahankan pilihan pengguna. +Yakni, jika nanti membuka dialog lagi untuk menunjukkan pilihan pengguna, +maka Anda perlu membuat daftar dengan tombol radio.

+ + + + + +

Membuat Layout Custom

+ +
+ +

Gambar 5. Layout dialog custom.

+
+ +

Jika Anda menginginkan layout custom dalam dialog, buatlah layout dan tambahkan ke +{@link android.app.AlertDialog} dengan memanggil {@link +android.app.AlertDialog.Builder#setView setView()} pada objek {@link +android.app.AlertDialog.Builder} Anda.

+ +

Secara default, layout custom akan mengisi jendela dialog, namun Anda masih bisa +menggunakan metode {@link android.app.AlertDialog.Builder} untuk menambahkan tombol dan judul.

+ +

Misalnya, berikut ini adalah file layout untuk dialog dalam Gambar 5:

+ +

res/layout/dialog_signin.xml

+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content">
+    <ImageView
+        android:src="@drawable/header_logo"
+        android:layout_width="match_parent"
+        android:layout_height="64dp"
+        android:scaleType="center"
+        android:background="#FFFFBB33"
+        android:contentDescription="@string/app_name" />
+    <EditText
+        android:id="@+id/username"
+        android:inputType="textEmailAddress"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="16dp"
+        android:layout_marginLeft="4dp"
+        android:layout_marginRight="4dp"
+        android:layout_marginBottom="4dp"
+        android:hint="@string/username" />
+    <EditText
+        android:id="@+id/password"
+        android:inputType="textPassword"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="4dp"
+        android:layout_marginLeft="4dp"
+        android:layout_marginRight="4dp"
+        android:layout_marginBottom="16dp"
+        android:fontFamily="sans-serif"
+        android:hint="@string/password"/>
+</LinearLayout>
+
+ +

Tip: Secara default, bila Anda telah mengatur sebuah elemen {@link android.widget.EditText} +agar menggunakan tipe input {@code "textPassword"}, keluarga font akan diatur ke spasi tunggal, sehingga +Anda harus mengubah keluarga font ke {@code "sans-serif"} sehingga kedua bidang teks menggunakan +gaya font yang cocok.

+ +

Untuk memekarkan layout dalam {@link android.support.v4.app.DialogFragment} Anda, +ambillah {@link android.view.LayoutInflater} dengan +{@link android.app.Activity#getLayoutInflater()} dan panggil +{@link android.view.LayoutInflater#inflate inflate()}, dengan parameter pertama +adalah ID sumber daya layout dan parameter kedua adalah tampilan induk untuk layout. +Selanjutnya Anda bisa memanggil {@link android.app.AlertDialog#setView setView()} +untuk menempatkan layout dalam dialog.

+ +
+@Override
+public Dialog onCreateDialog(Bundle savedInstanceState) {
+    AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+    // Get the layout inflater
+    LayoutInflater inflater = getActivity().getLayoutInflater();
+
+    // Inflate and set the layout for the dialog
+    // Pass null as the parent view because its going in the dialog layout
+    builder.setView(inflater.inflate(R.layout.dialog_signin, null))
+    // Add action buttons
+           .setPositiveButton(R.string.signin, new DialogInterface.OnClickListener() {
+               @Override
+               public void onClick(DialogInterface dialog, int id) {
+                   // sign in the user ...
+               }
+           })
+           .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
+               public void onClick(DialogInterface dialog, int id) {
+                   LoginDialogFragment.this.getDialog().cancel();
+               }
+           });      
+    return builder.create();
+}
+
+ +
+

Tip: Jika Anda menginginkan dialog custom, +Anda bisa menampilkan {@link android.app.Activity} sebagai dialog +daripada menggunakan API {@link android.app.Dialog}. Cukup buat satu aktivitas dan mengatur temanya ke +{@link android.R.style#Theme_Holo_Dialog Theme.Holo.Dialog} +di elemen manifes {@code +<activity>}:

+ +
+<activity android:theme="@android:style/Theme.Holo.Dialog" >
+
+

Demikian saja. Aktivitas sekarang ditampilkan dalam jendela dialog, sebagai ganti layar penuh.

+
+ + + +

Meneruskan Kejadian Kembali ke Host Dialog

+ +

Bila pengguna menyentuh salah satu tombol tindakan dialog atau memilih satu item dari daftarnya, +{@link android.support.v4.app.DialogFragment} Anda bisa melakukan sendiri tindakan yang diperlukan +, namun sering kali Anda perlu mengirim kejadian itu ke aktivitas atau fragmen yang +membuka dialog. Caranya, definisikan antarmuka dengan sebuah metode untuk masing-masing tipe kejadian klik. +Lalu implementasikan antarmuka itu dalam komponen host yang akan +menerima kejadian tindakan dari dialog.

+ +

Misalnya, berikut ini adalah {@link android.support.v4.app.DialogFragment} yang mendefinisikan +antarmuka yang akan digunakan untuk mengirim kembali suatu kejadian ke aktivitas host:

+ +
+public class NoticeDialogFragment extends DialogFragment {
+    
+    /* The activity that creates an instance of this dialog fragment must
+     * implement this interface in order to receive event callbacks.
+     * Each method passes the DialogFragment in case the host needs to query it. */
+    public interface NoticeDialogListener {
+        public void onDialogPositiveClick(DialogFragment dialog);
+        public void onDialogNegativeClick(DialogFragment dialog);
+    }
+    
+    // Use this instance of the interface to deliver action events
+    NoticeDialogListener mListener;
+    
+    // Override the Fragment.onAttach() method to instantiate the NoticeDialogListener
+    @Override
+    public void onAttach(Activity activity) {
+        super.onAttach(activity);
+        // Verify that the host activity implements the callback interface
+        try {
+            // Instantiate the NoticeDialogListener so we can send events to the host
+            mListener = (NoticeDialogListener) activity;
+        } catch (ClassCastException e) {
+            // The activity doesn't implement the interface, throw exception
+            throw new ClassCastException(activity.toString()
+                    + " must implement NoticeDialogListener");
+        }
+    }
+    ...
+}
+
+ +

Aktivitas yang menjadi host dialog tersebut akan membuat instance dialog +dengan konstruktor fragmen dialog dan menerima kejadian dialog +melalui implementasi antarmuka {@code NoticeDialogListener}:

+ +
+public class MainActivity extends FragmentActivity
+                          implements NoticeDialogFragment.NoticeDialogListener{
+    ...
+    
+    public void showNoticeDialog() {
+        // Create an instance of the dialog fragment and show it
+        DialogFragment dialog = new NoticeDialogFragment();
+        dialog.show(getSupportFragmentManager(), "NoticeDialogFragment");
+    }
+
+    // The dialog fragment receives a reference to this Activity through the
+    // Fragment.onAttach() callback, which it uses to call the following methods
+    // defined by the NoticeDialogFragment.NoticeDialogListener interface
+    @Override
+    public void onDialogPositiveClick(DialogFragment dialog) {
+        // User touched the dialog's positive button
+        ...
+    }
+
+    @Override
+    public void onDialogNegativeClick(DialogFragment dialog) {
+        // User touched the dialog's negative button
+        ...
+    }
+}
+
+ +

Karena aktivitas host mengimplementasikan {@code NoticeDialogListener}—yang +diberlakukan oleh metode callback {@link android.support.v4.app.Fragment#onAttach onAttach()} +di atas,—fragmen dialog bisa menggunakan +metode callback antarmuka untuk mengirimkan kejadian klik ke aktivitas:

+ +
+public class NoticeDialogFragment extends DialogFragment {
+    ...
+
+    @Override
+    public Dialog onCreateDialog(Bundle savedInstanceState) {
+        // Build the dialog and set up the button click handlers
+        AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+        builder.setMessage(R.string.dialog_fire_missiles)
+               .setPositiveButton(R.string.fire, new DialogInterface.OnClickListener() {
+                   public void onClick(DialogInterface dialog, int id) {
+                       // Send the positive button event back to the host activity
+                       mListener.onDialogPositiveClick(NoticeDialogFragment.this);
+                   }
+               })
+               .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
+                   public void onClick(DialogInterface dialog, int id) {
+                       // Send the negative button event back to the host activity
+                       mListener.onDialogNegativeClick(NoticeDialogFragment.this);
+                   }
+               });
+        return builder.create();
+    }
+}
+
+ + + +

Menampilkan Dialog

+ +

Bila Anda ingin menampilkan dialog, buatlah instance {@link +android.support.v4.app.DialogFragment} dan panggil {@link android.support.v4.app.DialogFragment#show +show()}, dengan meneruskan {@link android.support.v4.app.FragmentManager} dan nama tag +untuk fragmen dialognya.

+ +

Anda bisa mendapatkan {@link android.support.v4.app.FragmentManager} dengan memanggil +{@link android.support.v4.app.FragmentActivity#getSupportFragmentManager()} dari + {@link android.support.v4.app.FragmentActivity} atau {@link +android.support.v4.app.Fragment#getFragmentManager()} dari {@link +android.support.v4.app.Fragment}. Misalnya:

+ +
+public void confirmFireMissiles() {
+    DialogFragment newFragment = new FireMissilesDialogFragment();
+    newFragment.show(getSupportFragmentManager(), "missiles");
+}
+
+ +

Argumen kedua, {@code "missiles"}, adalah nama tag unik yang digunakan sistem untuk menyimpan +dan memulihkan status fragmen bila diperlukan. Tag ini juga memungkinkan Anda mendapatkan handle ke +fragmen dengan memanggil {@link android.support.v4.app.FragmentManager#findFragmentByTag +findFragmentByTag()}.

+ + + + +

Menampilkan Dialog sebagai Layar Penuh atau Fragmen Tertanam

+ +

Anda mungkin memiliki desain UI yang di dalamnya Anda ingin UI muncul sebagai dialog dalam beberapa +situasi, namun sebagai layar penuh atau fragmen tertanam dalam situasi lain (mungkin bergantung pada apakah +perangkat memiliki layar besar atau layar kecil). Kelas {@link android.support.v4.app.DialogFragment} +menawarkan fleksibilitas ini karena masih bisa berperilaku sebagai {@link +android.support.v4.app.Fragment} yang bisa ditanamkan.

+ +

Akan tetapi, dalam hal ini Anda tidak bisa menggunakan {@link android.app.AlertDialog.Builder AlertDialog.Builder} +atau objek {@link android.app.Dialog} lain untuk membangun dialog. Jika +Anda ingin {@link android.support.v4.app.DialogFragment} +bisa ditanamkan, Anda harus mendefinisikan dialog UI dalam layout, lalu memuat layout itu dalam metode callback +{@link android.support.v4.app.DialogFragment#onCreateView +onCreateView()}.

+ +

Berikut ini adalah contoh {@link android.support.v4.app.DialogFragment} yang bisa muncul sebagai +dialog maupun fragmen yang bisa ditanamkan (menggunakan layout bernama purchase_items.xml):

+ +
+public class CustomDialogFragment extends DialogFragment {
+    /** The system calls this to get the DialogFragment's layout, regardless
+        of whether it's being displayed as a dialog or an embedded fragment. */
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+            Bundle savedInstanceState) {
+        // Inflate the layout to use as dialog or embedded fragment
+        return inflater.inflate(R.layout.purchase_items, container, false);
+    }
+  
+    /** The system calls this only when creating the layout in a dialog. */
+    @Override
+    public Dialog onCreateDialog(Bundle savedInstanceState) {
+        // The only reason you might override this method when using onCreateView() is
+        // to modify any dialog characteristics. For example, the dialog includes a
+        // title by default, but your custom layout might not need it. So here you can
+        // remove the dialog title, but you must call the superclass to get the Dialog.
+        Dialog dialog = super.onCreateDialog(savedInstanceState);
+        dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
+        return dialog;
+    }
+}
+
+ +

Dan berikut ini adalah beberapa kode yang memutuskan apakah akan menampilkan fragmen sebagai dialog +atau UI layar penuh, berdasarkan ukuran layar:

+ +
+public void showDialog() {
+    FragmentManager fragmentManager = getSupportFragmentManager();
+    CustomDialogFragment newFragment = new CustomDialogFragment();
+    
+    if (mIsLargeLayout) {
+        // The device is using a large layout, so show the fragment as a dialog
+        newFragment.show(fragmentManager, "dialog");
+    } else {
+        // The device is smaller, so show the fragment fullscreen
+        FragmentTransaction transaction = fragmentManager.beginTransaction();
+        // For a little polish, specify a transition animation
+        transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
+        // To make it fullscreen, use the 'content' root view as the container
+        // for the fragment, which is always the root view for the activity
+        transaction.add(android.R.id.content, newFragment)
+                   .addToBackStack(null).commit();
+    }
+}
+
+ +

Untuk informasi selengkapnya tentang melakukan transaksi fragmen, lihat panduan +Fragmen.

+ +

Dalam contoh ini, nilai boolean mIsLargeLayout menentukan apakah perangkat saat ini +harus menggunakan desain layout besar aplikasi (dan dengan demikian menampilkan fragmen ini sebagai dialog, bukan +layar penuh). Cara terbaik untuk mengatur jenis boolean ini adalah mendeklarasikan +nilai sumber daya boolean +dengan nilai sumber daya alternatif untuk berbagai ukuran layar. Misalnya, berikut ini adalah dua +versi sumber daya boolean untuk berbagai ukuran layar:

+ +

res/values/bools.xml

+
+<!-- Default boolean values -->
+<resources>
+    <bool name="large_layout">false</bool>
+</resources>
+
+ +

res/values-large/bools.xml

+
+<!-- Large screen boolean values -->
+<resources>
+    <bool name="large_layout">true</bool>
+</resources>
+
+ +

Selanjutnya Anda bisa menetapkan nilai {@code mIsLargeLayout} selama +metode {@link android.app.Activity#onCreate onCreate()} aktivitas:

+ +
+boolean mIsLargeLayout;
+
+@Override
+public void onCreate(Bundle savedInstanceState) {
+    super.onCreate(savedInstanceState);
+    setContentView(R.layout.activity_main);
+
+    mIsLargeLayout = getResources().getBoolean(R.bool.large_layout);
+}
+
+ + + +

Menampilkan aktivitas sebagai dialog pada layar besar

+ +

Sebagai ganti menampilkan dialog berupa UI layar penuh saat di layar kecil, Anda bisa memperoleh +hasil yang sama dengan menampilkan {@link android.app.Activity} sebagai dialog saat di +layar besar. Pendekatan yang Anda pilih bergantung pada desain aplikasi, namun +menampilkan aktivitas sebagai dialog sering kali berguna bila aplikasi Anda sudah didesain untuk +layar kecil dan Anda ingin meningkatkan pengalaman pada tablet dengan menampilkan aktivitas berjangka pendek +sebagai dialog.

+ +

Untuk menampilkan aktivitas sebagai dialog hanya saat di layar besar, +terapkan tema {@link android.R.style#Theme_Holo_DialogWhenLarge Theme.Holo.DialogWhenLarge} + pada elemen manifes {@code +<activity>}:

+ +
+<activity android:theme="@android:style/Theme.Holo.DialogWhenLarge" >
+
+ +

Untuk informasi selengkapnya tentang mengatur gaya aktivitas Anda dengan tema, lihat panduan Gaya dan Tema.

+ + + +

Menutup Dialog

+ +

Bila pengguna menyentuh salah satu tombol tindakan yang dibuat dengan +{@link android.app.AlertDialog.Builder}, sistem akan menutup dialog untuk Anda.

+ +

Sistem juga menutup dialog bila pengguna menyentuh sebuah item dalam daftar dialog, kecuali +bila daftar itu menggunakan tombol radio atau kotak cek. Jika tidak, Anda bisa menutup dialog secara manual +dengan memanggil {@link android.support.v4.app.DialogFragment#dismiss()} pada {@link +android.support.v4.app.DialogFragment} Anda.

+ +

Jika Anda perlu melakukan +tindakan tertentu saat dialog menghilang, Anda bisa menerapkan metode {@link +android.support.v4.app.DialogFragment#onDismiss onDismiss()} dalam {@link +android.support.v4.app.DialogFragment} Anda.

+ +

Anda juga bisa membatalkan dialog. Ini merupakan kejadian khusus yang menunjukkan bahwa pengguna +secara eksplisit meninggalkan dialog tanpa menyelesaikan tugas. Hal ini terjadi jika pengguna menekan tombol +Back, menyentuh layar di luar area dialog, +atau jika Anda secara eksplisit memanggil {@link android.app.Dialog#cancel()} pada {@link +android.app.Dialog} (seperti saat merespons tombol "Cancel" dalam dialog).

+ +

Seperti yang ditampilkan dalam contoh di atas, Anda bisa merespons kejadian batal dengan menerapkan +{@link android.support.v4.app.DialogFragment#onCancel onCancel()} dalam kelas {@link +android.support.v4.app.DialogFragment} Anda.

+ +

Catatan: Sistem akan memanggil +{@link android.support.v4.app.DialogFragment#onDismiss onDismiss()} pada tiap kejadian yang +memanggil callback {@link android.support.v4.app.DialogFragment#onCancel onCancel()}. Akan tetapi, +jika Anda memanggil {@link android.app.Dialog#dismiss Dialog.dismiss()} atau {@link +android.support.v4.app.DialogFragment#dismiss DialogFragment.dismiss()}, +sistem akan memanggil {@link android.support.v4.app.DialogFragment#onDismiss onDismiss()} namun +bukan {@link android.support.v4.app.DialogFragment#onCancel onCancel()}. Jadi biasanya Anda harus +memanggil {@link android.support.v4.app.DialogFragment#dismiss dismiss()} bila pengguna menekan tombol +positif dalam dialog untuk menghilangkan tampilan dialog.

+ + diff --git a/docs/html-intl/intl/in/guide/topics/ui/menus.jd b/docs/html-intl/intl/in/guide/topics/ui/menus.jd new file mode 100644 index 0000000000000000000000000000000000000000..2cd3e6a33aa5ed330072ce116e9a0b91b4df6eb7 --- /dev/null +++ b/docs/html-intl/intl/in/guide/topics/ui/menus.jd @@ -0,0 +1,1031 @@ +page.title=Menu +parent.title=Antarmuka Pengguna +parent.link=index.html +@jd:body + +
+
+

Dalam dokumen ini

+
    +
  1. Mendefinisikan Menu dalam XML
  2. +
  3. Membuat Menu Opsi +
      +
    1. Menangani kejadian klik
    2. +
    3. Mengubah item menu saat runtime
    4. +
    +
  4. +
  5. Membuat Menu Kontekstual +
      +
    1. Membuat menu konteks mengambang
    2. +
    3. Menggunakan mode tindakan kontekstual
    4. +
    +
  6. +
  7. Membuat Menu Popup +
      +
    1. Menangani kejadian klik
    2. +
    +
  8. +
  9. Membuat Grup Menu +
      +
    1. Menggunakan item menu yang bisa diberi tanda cek
    2. +
    +
  10. +
  11. Menambahkan Item Menu Berdasarkan Intent +
      +
    1. Memungkinkan aktivitas Anda ditambahkan ke menu lain
    2. +
    +
  12. +
+ +

Kelas-kelas utama

+
    +
  1. {@link android.view.Menu}
  2. +
  3. {@link android.view.MenuItem}
  4. +
  5. {@link android.view.ContextMenu}
  6. +
  7. {@link android.view.ActionMode}
  8. +
+ +

Lihat juga

+
    +
  1. Action-Bar
  2. +
  3. Sumber Daya Menu
  4. +
  5. Ucapkan +Selamat Tinggal pada Tombol Menu
  6. +
+
+
+ +

Menu adalah komponen antarmuka pengguna yang lazim dalam banyak tipe aplikasi. Untuk menyediakan pengalaman pengguna yang sudah akrab +dan konsisten, Anda harus menggunakan API {@link android.view.Menu} untuk menyajikan +tindakan dan opsi lain dalam aktivitas kepada pengguna.

+ +

Mulai dengan Android 3.0 (API level 11), perangkat berbasis Android tidak perlu lagi +menyediakan tombol Menu tersendiri. Dengan perubahan ini, aplikasi Android harus bermigrasi dari +dependensi pada panel menu 6 item biasa, dan sebagai ganti menyediakan action-bar untuk menyajikan +berbagai tindakan pengguna yang lazim.

+ +

Walaupun desain dan pengalaman pengguna untuk sebagian item menu telah berubah, semantik untuk mendefinisikan +serangkaian tindakan dan opsi masih berdasarkan pada API {@link android.view.Menu}. Panduan ini +menampilkan cara membuat tiga tipe dasar penyajian menu atau tindakan pada semua +versi Android:

+ +
+
Menu opsi dan action-bar
+
Menu opsi adalah kumpulan item menu utama untuk suatu +aktivitas. Inilah tempat Anda harus menempatkan tindakan yang berdampak global pada aplikasi, seperti +"Cari", "Tulis email", dan "Pengaturan". +

Jika Anda mengembangkan aplikasi untuk Android 2.3 atau yang lebih rendah, pengguna bisa +menampilkan panel menu opsi dengan menekan tombol Menu.

+

Pada Android 3.0 dan yang lebih tinggi, item menu opsi disajikan melalui action-bar sebagai kombinasi item tindakan +di layar dan opsi overflow. Mulai dengan Android 3.0, tombol Menu dihilangkan (sebagian +perangkat +tidak memilikinya), sehingga Anda harus bermigrasi ke penggunaan action-bar untuk menyediakan akses ke tindakan dan +opsi lainnya.

+

Lihat bagian tentang Membuat Menu Opsi.

+
+ +
Menu konteks dan mode tindakan kontekstual
+ +
Menu konteks adalah menu mengambang yang muncul saat +pengguna mengklik lama pada suatu elemen. Menu ini menyediakan tindakan yang memengaruhi konten atau +bingkai konteks yang dipilih. +

Saat mengembangkan aplikasi untuk Android 3.0 dan yang lebih tinggi, sebagai gantinya Anda harus menggunakan mode tindakan kontekstual untuk memungkinkan tindakan pada konten yang dipilih. Mode ini menampilkan +item tindakan yang memengaruhi konten yang dipilih dalam baris di bagian atas layar dan memungkinkan pengguna +memilih beberapa item sekaligus.

+

Lihat bagian tentang Membuat Menu Kontekstual.

+
+ +
Menu popup
+
Menu popup menampilkan daftar item secara vertikal yang dipasang pada tampilan yang +memanggil menu. Ini cocok untuk menyediakan kelebihan tindakan yang terkait dengan konten tertentu atau +untuk menyediakan opsi bagi bagian kedua dari suatu perintah. Tindakan di menu popup +tidak boleh memengaruhi secara langsung konten yang bersangkutan—yang diperuntukkan bagi +tindakan kontekstual. Melainkan, menu popup adalah untuk tindakan tambahan yang terkait dengan wilayah konten dalam +aktivitas Anda. +

Lihat bagian tentang Membuat Menu Popup.

+
+
+ + + +

Mendefinisikan Menu dalam XML

+ +

Untuk semua tipe menu, Android menyediakan sebuah format XML standar untuk mendefinisikan item menu. +Sebagai ganti membuat menu dalam kode aktivitas, Anda harus mendefinisikan menu dan semua itemnya dalam +sumber daya menu XML. Anda kemudian bisa +memekarkan sumber daya menu (memuatnya sebagai objek {@link android.view.Menu}) dalam aktivitas atau +fragmen.

+ +

Menggunakan sumber daya menu adalah praktik yang baik karena beberapa alasan:

+ + +

Untuk mendefinisikan menu, buatlah sebuah file XML dalam direktori res/menu/ +proyek dan buat menu dengan elemen-elemen berikut:

+
+
<menu>
+
Mendefinisikan {@link android.view.Menu}, yang merupakan sebuah kontainer untuk item menu. Elemen +<menu> harus menjadi simpul akar untuk file dan bisa menampung salah satu atau beberapa dari elemen +<item> dan <group>.
+ +
<item>
+
Membuat {@link android.view.MenuItem}, yang mewakili satu item menu. Elemen ini +bisa berisi elemen <menu> tersarang guna untuk membuat submenu.
+ +
<group>
+
Kontainer opsional tak terlihat untuk elemen-elemen {@code <item>}. Kontainer ini memungkinkan Anda +mengelompokkan item menu untuk berbagi properti seperti status aktif dan visibilitas. Untuk informasi +selengkapnya, lihat bagian tentang Membuat Grup Menu.
+
+ + +

Berikut ini adalah contoh menu bernama game_menu.xml:

+
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:id="@+id/new_game"
+          android:icon="@drawable/ic_new_game"
+          android:title="@string/new_game"
+          android:showAsAction="ifRoom"/>
+    <item android:id="@+id/help"
+          android:icon="@drawable/ic_help"
+          android:title="@string/help" />
+</menu>
+
+ +

Elemen <item> mendukung beberapa atribut yang bisa Anda gunakan untuk mendefinisikan penampilan dan perilaku +item. Item menu di atas mencakup atribut berikut:

+ +
+
{@code android:id}
+
ID sumber daya unik bagi item, yang memungkinkan aplikasi mengenali item +bila pengguna memilihnya.
+
{@code android:icon}
+
Acuan ke drawable untuk digunakan sebagai ikon item.
+
{@code android:title}
+
Acuan ke string untuk digunakan sebagai judul item.
+
{@code android:showAsAction}
+
Menetapkan waktu dan cara item ini muncul sebagai item tindakan di action-bar.
+
+ +

Ini adalah atribut-atribut terpenting yang harus Anda gunakan, namun banyak lagi yang tersedia. +Untuk informasi tentang semua atribut yang didukung, lihat dokumen Sumber Daya Menu.

+ +

Anda bisa menambahkan submenu ke sebuah item di menu (kecuali submenu) apa saja dengan menambahkan elemen {@code <menu>} +sebagai anak {@code <item>}. Submenu berguna saat aplikasi Anda memiliki banyak +fungsi yang bisa ditata ke dalam topik-topik, seperti item dalam sebuah baris menu aplikasi PC (File, +Edit, Lihat, dsb.). Misalnya:

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:id="@+id/file"
+          android:title="@string/file" >
+        <!-- "file" submenu -->
+        <menu>
+            <item android:id="@+id/create_new"
+                  android:title="@string/create_new" />
+            <item android:id="@+id/open"
+                  android:title="@string/open" />
+        </menu>
+    </item>
+</menu>
+
+ +

Untuk menggunakan menu dalam aktivitas, Anda perlu memekarkan sumber daya menu (mengonversi sumber daya XML +menjadi objek yang bisa diprogram) dengan menggunakan {@link android.view.MenuInflater#inflate(int,Menu) +MenuInflater.inflate()}. Di bagian berikut, Anda akan melihat cara memekarkan menu untuk tiap +tipe menu.

+ + + +

Membuat Menu Opsi

+ +
+ +

Gambar 1. Menu opsi di +Browser, pada Android 2.3.

+
+ +

Menu opsi adalah tempat Anda harus menyertakan tindakan dan opsi lain yang relevan dengan +konteks aktivitas saat ini, seperti "Cari", "Tulis email", dan "Pengaturan".

+ +

Tempat item dalam menu opsi muncul di layar bergantung pada versi aplikasi yang Anda +kembangkan:

+ + + + +

Gambar 2. Action-bar dari aplikasi Honeycomb Gallery, yang menampilkan +tab-tab navigasi dan item tindakan kamera (plus tombol kelebihan tindakan).

+ +

Anda bisa mendeklarasikan item untuk menu opsi dari subkelas {@link android.app.Activity} +atau subkelas {@link android.app.Fragment}. Jika aktivitas maupun fragmen Anda +mendeklarasikan item menu opsi, keduanya akan dikombinasikan dalam UI. Item aktivitas akan muncul +lebih dahulu, diikuti oleh item tiap fragmen sesuai dengan urutan penambahan fragmen ke +aktivitas. Jika perlu, Anda bisa menyusun ulang item menu dengan atribut {@code android:orderInCategory} +dalam setiap {@code <item>} yang perlu Anda pindahkan.

+ +

Untuk menetapkan menu opsi suatu aktivitas, kesampingkan {@link +android.app.Activity#onCreateOptionsMenu(Menu) onCreateOptionsMenu()} (fragmen-fragmen menyediakan +callback {@link android.app.Fragment#onCreateOptionsMenu onCreateOptionsMenu()} sendiri). Dalam metode ini +, Anda bisa memekarkan sumber daya menu (yang didefinisikan dalam XML) menjadi {@link +android.view.Menu} yang disediakan dalam callback. Misalnya:

+ +
+@Override
+public boolean onCreateOptionsMenu(Menu menu) {
+    MenuInflater inflater = {@link android.app.Activity#getMenuInflater()};
+    inflater.inflate(R.menu.game_menu, menu);
+    return true;
+}
+
+ +

Anda juga bisa menambahkan item menu dengan menggunakan {@link android.view.Menu#add(int,int,int,int) +add()} dan mengambil item dengan {@link android.view.Menu#findItem findItem()} untuk merevisi propertinya +dengan API {@link android.view.MenuItem}.

+ +

Jika Anda mengembangkan aplikasi untuk Android 2.3.x dan yang lebih rendah, sistem akan memanggil {@link +android.app.Activity#onCreateOptionsMenu(Menu) onCreateOptionsMenu()} untuk membuat menu opsi +bila pengguna membuka menu untuk pertama kali. Jika Anda mengembangkan aplikasi untuk Android 3.0 dan yang lebih tinggi, +sistem akan memanggil {@link android.app.Activity#onCreateOptionsMenu(Menu) onCreateOptionsMenu()} saat +memulai aktivitas, untuk menampilkan item menu pada action-bar.

+ + + +

Menangani kejadian klik

+ +

Bila pengguna memilih item dari menu opsi (termasuk item tindakan dalam action-bar), +sistem akan memanggil metode {@link android.app.Activity#onOptionsItemSelected(MenuItem) +onOptionsItemSelected()} aktivitas Anda. Metode ini meneruskan {@link android.view.MenuItem} yang dipilih. Anda +bisa mengidentifikasi item dengan memanggil {@link android.view.MenuItem#getItemId()}, yang menghasilkan +ID unik untuk item menu itu (yang didefinisikan oleh atribut {@code android:id} dalam sumber daya menu atau dengan +integer yang diberikan ke metode {@link android.view.Menu#add(int,int,int,int) add()}). Anda bisa mencocokkan +ID ini dengan item menu yang diketahui untuk melakukan tindakan yang sesuai. Misalnya:

+ +
+@Override
+public boolean onOptionsItemSelected(MenuItem item) {
+    // Handle item selection
+    switch (item.getItemId()) {
+        case R.id.new_game:
+            newGame();
+            return true;
+        case R.id.help:
+            showHelp();
+            return true;
+        default:
+            return super.onOptionsItemSelected(item);
+    }
+}
+
+ +

Bila Anda berhasil menangani sebuah item menu, kembalikan {@code true}. Jika tidak menangani item menu +, Anda harus memanggil implementasi superkelas {@link +android.app.Activity#onOptionsItemSelected(MenuItem) onOptionsItemSelected()} (implementasi default +menghasilkan false).

+ +

Jika aktivitas Anda menyertakan fragmen, sistem akan memanggil lebih dahulu {@link +android.app.Activity#onOptionsItemSelected(MenuItem) onOptionsItemSelected()} untuk aktivitas, kemudian +untuk setiap fragmen (sesuai dengan urutan penambahan fragmen) hingga satu fragmen mengembalikan +{@code true} atau semua fragmen telah dipanggil.

+ +

Tip: Android 3.0 menambahkan kemampuan mendefinisikan perilaku on-click +untuk item menu dalam XML, dengan menggunakan atribut {@code android:onClick}. Nilai atribut +harus berupa nama metode yang didefinisikan aktivitas dengan menggunakan menu. Metode +harus bersifat publik dan menerima satu parameter {@link android.view.MenuItem}—bila sistem +memanggilnya, metode ini akan meneruskan item menu yang dipilih. Untuk informasi selengkapnya dan contoh, lihat dokumen Sumber Daya Menu.

+ +

Tip: Jika aplikasi Anda berisi banyak aktivitas dan +sebagian menyediakan menu opsi yang sama, pertimbangkan untuk membuat +aktivitas yang tidak mengimplementasikan apa-apa kecuali metode {@link android.app.Activity#onCreateOptionsMenu(Menu) +onCreateOptionsMenu()} dan {@link android.app.Activity#onOptionsItemSelected(MenuItem) +onOptionsItemSelected()}. Kemudian perluas kelas ini untuk setiap aktivitas yang harus menggunakan +menu opsi yang sama. Dengan begini, Anda bisa mengelola satu set kode untuk menangani tindakan menu +dan setiap kelas turunan mewarisi perilaku menu. +Jika ingin menambahkan item menu ke salah satu aktivitas turunan, +kesampingkan {@link android.app.Activity#onCreateOptionsMenu(Menu) +onCreateOptionsMenu()} dalam aktivitas itu. Panggil {@code super.onCreateOptionsMenu(menu)} agar +item menu asli dibuat, kemudian tambahkan item menu yang baru dengan {@link +android.view.Menu#add(int,int,int,int) menu.add()}. Anda juga bisa mengesampingkan +perilaku superkelas untuk setiap item menu.

+ + +

Mengubah item menu saat runtime

+ +

Setelah sistem memanggil {@link android.app.Activity#onCreateOptionsMenu(Menu) +onCreateOptionsMenu()}, sistem akan mempertahankan instance {@link android.view.Menu} yang Anda tempatkan dan +tidak akan memanggil {@link android.app.Activity#onCreateOptionsMenu(Menu) onCreateOptionsMenu()} +lagi kecuali menu diinvalidkan karena suatu alasan. Akan tetapi, Anda harus menggunakan {@link +android.app.Activity#onCreateOptionsMenu(Menu) onCreateOptionsMenu()} hanya untuk membuat +status menu awal dan tidak untuk membuat perubahan selama daur hidup aktivitas.

+ +

Jika Anda ingin mengubah menu opsi berdasarkan +kejadian yang terjadi selama daur hidup aktivitas, Anda bisa melakukannya dalam metode +{@link android.app.Activity#onPrepareOptionsMenu(Menu) onPrepareOptionsMenu()}. Metode ini +meneruskan objek {@link android.view.Menu} sebagaimana adanya saat ini sehingga Anda bisa mengubahnya, +seperti menambah, menghapus, atau menonaktifkan item. (Fragmen juga menyediakan callback {@link +android.app.Fragment#onPrepareOptionsMenu onPrepareOptionsMenu()}.)

+ +

Pada Android 2.3.x dan yang lebih rendah, sistem akan memanggil {@link +android.app.Activity#onPrepareOptionsMenu(Menu) +onPrepareOptionsMenu()} setiap kali pengguna membuka menu opsi (menekan tombol Menu +).

+ +

Pada Android 3.0 dan yang lebih tinggi, menu opsi dianggap sebagai selalu terbuka saat item menu +ditampilkan pada action-bar. Bila ada kejadian dan Anda ingin melakukan pembaruan menu, Anda harus +memanggil {@link android.app.Activity#invalidateOptionsMenu invalidateOptionsMenu()} untuk meminta +sistem memanggil {@link android.app.Activity#onPrepareOptionsMenu(Menu) onPrepareOptionsMenu()}.

+ +

Catatan: +Anda tidak boleh mengubah item dalam menu opsi berdasarkan {@link android.view.View} yang saat ini +difokus. Saat dalam mode sentuh (bila pengguna tidak sedang menggunakan trackball atau d-pad), tampilan +tidak bisa mengambil fokus, sehingga Anda tidak boleh menggunakan fokus sebagai dasar untuk mengubah +item dalam menu opsi. Jika Anda ingin menyediakan item menu yang peka konteks pada {@link +android.view.View}, gunakan Menu Konteks.

+ + + + +

Membuat Menu Kontekstual

+ +
+ +

Gambar 3. Cuplikan layar menu konteks mengambang (kiri) +dan action-bar kontekstual (kanan).

+
+ +

Menu kontekstual menawarkan tindakan yang memengaruhi item atau bingkai konteks tertentu dalam UI. Anda +bisa menyediakan menu konteks untuk setiap tampilan, tetapi menu ini paling sering digunakan untuk item pada {@link +android.widget.ListView}, {@link android.widget.GridView}, atau kumpulan tampilan lainnya yang bisa digunakan +pengguna untuk melakukan tindakan langsung pada setiap item.

+ +

Ada dua cara menyediakan tindakan kontekstual:

+ + +

Catatan: Mode tindakan kontekstual tersedia pada Android 3.0 (API +level 11) dan yang lebih tinggi dan merupakan teknik yang lebih disukai untuk menampilkan tindakan kontekstual bila +tersedia. Jika aplikasi Anda mendukung versi yang lebih rendah daripada 3.0, maka Anda harus mundur ke +menu konteks mengambang pada perangkat-perangkat itu.

+ + +

Membuat menu konteks mengambang

+ +

Untuk menyediakan menu konteks mengambang:

+
    +
  1. Daftarkan {@link android.view.View} ke menu konteks yang harus dikaitkan dengan +memanggil {@link android.app.Activity#registerForContextMenu(View) registerForContextMenu()} dan teruskan +{@link android.view.View} ke menu itu. +

    Jika aktivitas Anda menggunakan {@link android.widget.ListView} atau {@link android.widget.GridView} dan +Anda ingin setiap item untuk menyediakan menu konteks yang sama, daftarkan semua item ke menu konteks dengan +meneruskan {@link android.widget.ListView} atau {@link android.widget.GridView} ke {@link +android.app.Activity#registerForContextMenu(View) registerForContextMenu()}.

    +
  2. + +
  3. Implementasikan metode {@link +android.view.View.OnCreateContextMenuListener#onCreateContextMenu onCreateContextMenu()} +dalam {@link android.app.Activity} atau {@link android.app.Fragment} Anda. +

    Bila tampilan yang terdaftar menerima kejadian klik-lama, sistem akan memanggil metode {@link +android.view.View.OnCreateContextMenuListener#onCreateContextMenu onCreateContextMenu()} +Anda. Inilah tempat Anda mendefinisikan item menu, biasanya dengan memekarkan sumber daya menu. Misalnya: +

    +
    +@Override
    +public void onCreateContextMenu(ContextMenu menu, View v,
    +                                ContextMenuInfo menuInfo) {
    +    super.onCreateContextMenu(menu, v, menuInfo);
    +    MenuInflater inflater = getMenuInflater();
    +    inflater.inflate(R.menu.context_menu, menu);
    +}
    +
    + +

    {@link android.view.MenuInflater} memungkinkan Anda untuk memekarkan menu konteks sumber daya menu. Parameter metode callback +menyertakan {@link android.view.View} +yang dipilih pengguna dan objek {@link android.view.ContextMenu.ContextMenuInfo} yang menyediakan +informasi tambahan tentang item yang dipilih. Jika aktivitas Anda memiliki beberapa tampilan yang masing-masingnya menyediakan +menu konteks berbeda, Anda bisa menggunakan parameter ini untuk menentukan menu konteks yang harus +dimekarkan.

    +
  4. + +
  5. Implementasikan {@link android.app.Activity#onContextItemSelected(MenuItem) +onContextItemSelected()}. +

    Bila pengguna memilih item menu, sistem akan memanggil metode ini sehingga Anda bisa melakukan +tindakan yang sesuai. Misalnya:

    + +
    +@Override
    +public boolean onContextItemSelected(MenuItem item) {
    +    AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo();
    +    switch (item.getItemId()) {
    +        case R.id.edit:
    +            editNote(info.id);
    +            return true;
    +        case R.id.delete:
    +            deleteNote(info.id);
    +            return true;
    +        default:
    +            return super.onContextItemSelected(item);
    +    }
    +}
    +
    + +

    Metode {@link android.view.MenuItem#getItemId()} melakukan query ID untuk +item menu yang dipilih, yang harus Anda tetapkan ke setiap item menu dalam XML dengan menggunakan atribut {@code +android:id}, seperti yang ditampilkan di bagian tentang Mendefinisikan Menu dalam +XML.

    + +

    Bila Anda berhasil menangani sebuah item menu, kembalikan {@code true}. Jika tidak menangani item menu, +Anda harus meneruskan item menu ke implementasi superkelas. Jika aktivitas Anda menyertakan fragmen, +aktivitas akan menerima callback ini lebih dahulu. Dengan memanggil superkelas bila tidak ditangani, sistem +meneruskan kejadian ke metode callback di setiap fragmen, satu per satu (sesuai dengan urutan +penambahan fragmen) hingga {@code true} atau {@code false} dikembalikan. (Implementasi default +untuk {@link android.app.Activity} dan {@code android.app.Fragment} mengembalikan {@code +false}, sehingga Anda harus selalu memanggil superkelas bila tidak ditangani.)

    +
  6. +
+ + +

Menggunakan mode tindakan kontekstual

+ +

Mode tindakan kontekstual adalah implementasi sistem {@link android.view.ActionMode} yang +memfokuskan interaksi pengguna pada upaya melakukan tindakan kontekstual. Bila seorang +pengguna mengaktifkan mode ini dengan memilih item, action-bar kontekstual akan muncul di bagian atas +layar untuk menampilkan tindakan yang bisa dilakukan pengguna pada item yang dipilih saat ini. Selagi mode ini +diaktifkan, pengguna bisa memilih beberapa item (jika Anda mengizinkan), membatalkan pilihan item, dan melanjutkan +penelusuran dalam aktivitas (sebanyak yang ingin Anda izinkan). Mode tindakan dinonaktifkan +dan action-bar kontekstual menghilang bila pengguna membatalkan pilihan semua item, menekan tombol BACK, +atau memilih tindakan Done di sisi kiri action-bar.

+ +

Catatan: Action-bar kontekstual tidak harus +terkait dengan action-bar. Action-bar ini beroperasi +secara independen, walaupun action-bar kontekstual secara visual mengambil alih +posisi action-bar.

+ +

Jika Anda mengembangkan aplikasi untuk Android 3.0 (API level 11) atau yang lebih tinggi, Anda +biasanya harus menggunakan mode tindakan kontekstual untuk menampilkan tindakan kontekstual, sebagai ganti menu konteks mengambang.

+ +

Untuk tampilan yang menyediakan tindakan kontekstual, Anda biasanya harus memanggil mode tindakan kontekstual +pada salah satu dari dua kejadian (atau keduanya):

+ + +

Cara aplikasi memanggil mode tindakan kontekstual dan mendefinisikan perilaku setiap +tindakan bergantung pada desain Anda. Pada dasarnya ada dua desain:

+ + +

Bagian berikut ini menjelaskan penyiapan yang diperlukan untuk setiap skenario.

+ + +

Mengaktifkan mode tindakan kontekstual untuk tampilan individual

+ +

Jika Anda ingin memanggil mode tindakan kontekstual hanya bila pengguna memilih +tampilan tertentu, Anda harus:

+
    +
  1. Mengimplementasikan antarmuka {@link android.view.ActionMode.Callback}. Dalam metode callback-nya, Anda +bisa menetapkan tindakan untuk action-bar kontekstual, merespons kejadian klik pada item tindakan, dan +menangani kejadian daur hidup lainnya untuk mode tindakan itu.
  2. +
  3. Memanggil {@link android.app.Activity#startActionMode startActionMode()} bila Anda ingin menampilkan +action-bar (seperti saat pengguna mengklik-lama pada tampilan).
  4. +
+ +

Misalnya:

+ +
    +
  1. Implementasikan antarmuka {@link android.view.ActionMode.Callback ActionMode.Callback}: +
    +private ActionMode.Callback mActionModeCallback = new ActionMode.Callback() {
    +
    +    // Called when the action mode is created; startActionMode() was called
    +    @Override
    +    public boolean onCreateActionMode(ActionMode mode, Menu menu) {
    +        // Inflate a menu resource providing context menu items
    +        MenuInflater inflater = mode.getMenuInflater();
    +        inflater.inflate(R.menu.context_menu, menu);
    +        return true;
    +    }
    +
    +    // Called each time the action mode is shown. Always called after onCreateActionMode, but
    +    // may be called multiple times if the mode is invalidated.
    +    @Override
    +    public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
    +        return false; // Return false if nothing is done
    +    }
    +
    +    // Called when the user selects a contextual menu item
    +    @Override
    +    public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
    +        switch (item.getItemId()) {
    +            case R.id.menu_share:
    +                shareCurrentItem();
    +                mode.finish(); // Action picked, so close the CAB
    +                return true;
    +            default:
    +                return false;
    +        }
    +    }
    +
    +    // Called when the user exits the action mode
    +    @Override
    +    public void onDestroyActionMode(ActionMode mode) {
    +        mActionMode = null;
    +    }
    +};
    +
    + +

    Perhatikan bahwa kejadian callback ini hampir persis sama dengan callback untuk menu opsi, hanya saja setiap callback ini juga meneruskan objek {@link +android.view.ActionMode} yang terkait dengan kejadian. Anda bisa menggunakan API {@link +android.view.ActionMode} untuk membuat berbagai perubahan pada CAB, seperti merevisi judul dan +subjudul dengan {@link android.view.ActionMode#setTitle setTitle()} dan {@link +android.view.ActionMode#setSubtitle setSubtitle()} (berguna untuk menunjukkan jumlah item +yang dipilih).

    + +

    Juga perhatikan bahwa contoh di atas mengatur variabel {@code mActionMode} ke nol bila +mode tindakan dimusnahkan. Dalam langkah berikutnya, Anda akan melihat cara variabel diinisialisasi dan kegunaan menyimpan +variabel anggota dalam aktivitas atau fragmen.

    +
  2. + +
  3. Panggil {@link android.app.Activity#startActionMode startActionMode()} untuk mengaktifkan +mode tindakan kontekstual bila sesuai, seperti saat merespons klik-lama pada {@link +android.view.View}:

    + +
    +someView.setOnLongClickListener(new View.OnLongClickListener() {
    +    // Called when the user long-clicks on someView
    +    public boolean onLongClick(View view) {
    +        if (mActionMode != null) {
    +            return false;
    +        }
    +
    +        // Start the CAB using the ActionMode.Callback defined above
    +        mActionMode = getActivity().startActionMode(mActionModeCallback);
    +        view.setSelected(true);
    +        return true;
    +    }
    +});
    +
    + +

    Bila Anda memanggil {@link android.app.Activity#startActionMode startActionMode()}, sistem akan mengembalikan +{@link android.view.ActionMode} yang dibuat. Dengan menyimpannya dalam variabel anggota, Anda bisa +membuat perubahan ke action-bar kontekstual sebagai respons terhadap kejadian lainnya. Dalam contoh di atas, +{@link android.view.ActionMode} digunakan untuk memastikan bahwa instance {@link android.view.ActionMode} +tidak dibuat kembali jika sudah aktif, dengan memeriksa apakah anggota bernilai nol sebelum memulai +mode tindakan.

    +
  4. +
+ + + +

Mengaktifkan tindakan kontekstual batch dalam ListView atau GridView

+ +

Jika Anda memiliki sekumpulan item dalam {@link android.widget.ListView} atau {@link +android.widget.GridView} (atau ekstensi {@link android.widget.AbsListView} lainnya) dan ingin +mengizinkan pengguna melakukan tindakan batch, Anda harus:

+ + + +

Misalnya:

+ +
+ListView listView = getListView();
+listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL);
+listView.setMultiChoiceModeListener(new MultiChoiceModeListener() {
+
+    @Override
+    public void onItemCheckedStateChanged(ActionMode mode, int position,
+                                          long id, boolean checked) {
+        // Here you can do something when items are selected/de-selected,
+        // such as update the title in the CAB
+    }
+
+    @Override
+    public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
+        // Respond to clicks on the actions in the CAB
+        switch (item.getItemId()) {
+            case R.id.menu_delete:
+                deleteSelectedItems();
+                mode.finish(); // Action picked, so close the CAB
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    @Override
+    public boolean onCreateActionMode(ActionMode mode, Menu menu) {
+        // Inflate the menu for the CAB
+        MenuInflater inflater = mode.getMenuInflater();
+        inflater.inflate(R.menu.context, menu);
+        return true;
+    }
+
+    @Override
+    public void onDestroyActionMode(ActionMode mode) {
+        // Here you can make any necessary updates to the activity when
+        // the CAB is removed. By default, selected items are deselected/unchecked.
+    }
+
+    @Override
+    public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
+        // Here you can perform updates to the CAB due to
+        // an {@link android.view.ActionMode#invalidate} request
+        return false;
+    }
+});
+
+ +

Demikian saja. Kini bila pengguna memilih item dengan klik-lama, sistem akan memanggil metode {@link +android.widget.AbsListView.MultiChoiceModeListener#onCreateActionMode onCreateActionMode()} +dan menampilkan action-bar kontekstual bersama tindakan yang ditetapkan. Saat +action-bar kontekstual terlihat, pengguna bisa memilih item tambahan.

+ +

Dalam beberapa kasus di mana tindakan kontekstual menyediakan item tindakan umum, Anda mungkin +ingin menambahkan kotak cek atau elemen UI serupa yang memungkinkan pengguna memilih item, karena pengguna +mungkin tidak menemukan perilaku klik-lama. Bila pengguna memilih kotak cek itu, Anda +bisa memanggil mode tindakan kontekstual dengan mengatur item daftar yang bersangkutan ke +status diberi tanda cek dengan {@link android.widget.AbsListView#setItemChecked setItemChecked()}.

+ + + + +

Membuat Menu Popup

+ +
+ +

Gambar 4. Menu popup dalam aplikasi Gmail, dikaitkan pada +tombol kelebihan di sudut kanan atas.

+
+ +

{@link android.widget.PopupMenu} adalah menu modal yang dikaitkan pada {@link android.view.View}. +Menu ini muncul di bawah tampilan jangkar jika ada ruang, atau di atas tampilan jika tidak ada. Menu ini berguna untuk:

+ + + +

Catatan: {@link android.widget.PopupMenu} tersedia dengan API +level 11 dan yang lebih tinggi.

+ +

Jika Anda mendefinisikan menu dalam XML, berikut ini adalah cara Anda menampilkan menu popup:

+
    +
  1. Buat instance {@link android.widget.PopupMenu} bersama konstruktornya, yang mengambil +aplikasi saat ini {@link android.content.Context} dan {@link android.view.View} yang akan menjadi tempat mengaitkan +menu.
  2. +
  3. Gunakan {@link android.view.MenuInflater} untuk memekarkan sumber daya menu Anda ke dalam objek {@link +android.view.Menu} yang dikembalikan oleh {@link +android.widget.PopupMenu#getMenu() PopupMenu.getMenu()}. Pada API level 14 ke atas, Anda bisa menggunakan +{@link android.widget.PopupMenu#inflate PopupMenu.inflate()} sebagai gantinya.
  4. +
  5. Panggil {@link android.widget.PopupMenu#show() PopupMenu.show()}.
  6. +
+ +

Misalnya, berikut ini adalah tombol dengan atribut {@link android.R.attr#onClick android:onClick} +yang menampilkan menu popup:

+ +
+<ImageButton
+    android:layout_width="wrap_content" 
+    android:layout_height="wrap_content" 
+    android:src="@drawable/ic_overflow_holo_dark"
+    android:contentDescription="@string/descr_overflow_button"
+    android:onClick="showPopup" />
+
+ +

Aktivitas nanti bisa menampilkan menu popup seperti ini:

+ +
+public void showPopup(View v) {
+    PopupMenu popup = new PopupMenu(this, v);
+    MenuInflater inflater = popup.getMenuInflater();
+    inflater.inflate(R.menu.actions, popup.getMenu());
+    popup.show();
+}
+
+ +

Dalam API level 14 dan yang lebih tinggi, Anda bisa menggabungkan dua baris yang memekarkan menu dengan {@link +android.widget.PopupMenu#inflate PopupMenu.inflate()}.

+ +

Menu akan menghilang bila pengguna memilih item atau menyentuh di luar +area menu. Anda bisa mendengarkan kejadian menghilangkan dengan menggunakan {@link +android.widget.PopupMenu.OnDismissListener}.

+ +

Menangani kejadian klik

+ +

Untuk melakukan suatu +tindakan bila pengguna memilih item menu, Anda harus mengimplementasikan antarmuka {@link +android.widget.PopupMenu.OnMenuItemClickListener} dan mendaftarkannya pada {@link +android.widget.PopupMenu} dengan memanggil {@link android.widget.PopupMenu#setOnMenuItemClickListener +setOnMenuItemclickListener()}. Bila pengguna memilih item, sistem akan memanggil callback {@link +android.widget.PopupMenu.OnMenuItemClickListener#onMenuItemClick onMenuItemClick()} dalam +antarmuka Anda.

+ +

Misalnya:

+ +
+public void showMenu(View v) {
+    PopupMenu popup = new PopupMenu(this, v);
+
+    // This activity implements OnMenuItemClickListener
+    popup.setOnMenuItemClickListener(this);
+    popup.inflate(R.menu.actions);
+    popup.show();
+}
+
+@Override
+public boolean onMenuItemClick(MenuItem item) {
+    switch (item.getItemId()) {
+        case R.id.archive:
+            archive(item);
+            return true;
+        case R.id.delete:
+            delete(item);
+            return true;
+        default:
+            return false;
+    }
+}
+
+ + +

Membuat Grup Menu

+ +

Grup menu adalah sekumpulan item menu yang sama-sama memiliki ciri (trait) tertentu. Dengan grup, Anda +bisa:

+ + +

Anda bisa membuat grup dengan menyarangkan elemen-elemen {@code <item>} dalam elemen {@code <group>} +dalam sumber daya menu atau dengan menetapkan ID grup dengan metode {@link +android.view.Menu#add(int,int,int,int) add()}.

+ +

Berikut ini adalah contoh sumber daya menu yang berisi sebuah grup:

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:id="@+id/menu_save"
+          android:icon="@drawable/menu_save"
+          android:title="@string/menu_save" />
+    <!-- menu group -->
+    <group android:id="@+id/group_delete">
+        <item android:id="@+id/menu_archive"
+              android:title="@string/menu_archive" />
+        <item android:id="@+id/menu_delete"
+              android:title="@string/menu_delete" />
+    </group>
+</menu>
+
+ +

Item yang berada dalam grup akan muncul pada level yang sama dengan item pertama—ketiga item +dalam menu adalah bersaudara. Akan tetapi, Anda bisa memodifikasi ciri kedua +item dalam grup dengan mengacu ID grup dan menggunakan metode yang tercantum di atas. Sistem +juga tidak akan memisahkan item yang telah dikelompokkan. Misalnya, jika Anda mendeklarasikan {@code +android:showAsAction="ifRoom"} untuk tiap item, item tersebut akan muncul dalam +action-bar atau dalam kelebihan tindakan.

+ + +

Menggunakan item menu yang bisa diberi tanda cek

+ +
+ +

Gambar 5. Cuplikan layar submenu dengan +item yang bisa diberi tanda cek.

+
+ +

Menu bisa digunakan sebagai antarmuka untuk mengaktifkan dan menonaktifkan opsi, menggunakan kotak cek untuk +opsi mandiri, atau tombol radio untuk grup +opsi yang saling eksklusif. Gambar 5 menampilkan submenu dengan item yang bisa diberi tanda cek dengan +tombol radio.

+ +

Catatan: Item menu dalam Icon Menu (dari menu opsi) tidak bisa +menampilkan kotak cek atau tombol radio. Jika Anda memilih untuk membuat item dalam Icon Menu yang bisa diberi tanda cek, +Anda harus menandai status diberi tanda cek secara manual dengan menukar ikon dan/atau teks +tiap kali statusnya berubah.

+ +

Anda bisa mendefinisikan perilaku yang bisa diberi tanda cek untuk tiap item menu dengan menggunakan atribut {@code +android:checkable} dalam elemen {@code <item>}, atau untuk seluruh grup dengan +atribut {@code android:checkableBehavior} dalam elemen {@code <group>}. Misalnya +, semua item dalam grup menu ini bisa diberi tanda cek dengan tombol radio:

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+    <group android:checkableBehavior="single">
+        <item android:id="@+id/red"
+              android:title="@string/red" />
+        <item android:id="@+id/blue"
+              android:title="@string/blue" />
+    </group>
+</menu>
+
+ +

Atribut {@code android:checkableBehavior} menerima: +

+
{@code single}
+
Hanya satu item dari grup ini yang bisa diberi tanda cek (tombol radio)
+
{@code all}
+
Semua item bisa diberi tanda cek (kotak cek)
+
{@code none}
+
Tidak ada item yang bisa diberi tanda cek
+
+ +

Anda bisa menerapkan status diberi tanda cek default pada suatu item dengan menggunakan atribut {@code android:checked} dalam +elemen {@code <item>} dan mengubahnya dalam kode dengan metode {@link +android.view.MenuItem#setChecked(boolean) setChecked()}.

+ +

Bila item yang bisa diberi tanda cek dipilih, sistem akan memanggil metode callback setiap item yang dipilih +(seperti {@link android.app.Activity#onOptionsItemSelected(MenuItem) onOptionsItemSelected()}). Di sinilah +Anda harus mengatur status kotak cek itu, karena kotak cek atau tombol radio tidak +mengubah statusnya secara otomatis. Anda bisa melakukan query status saat ini suatu item (seperti sebelum +pengguna memilihnya) dengan {@link android.view.MenuItem#isChecked()} kemudian mengatur status diberi tanda cek dengan +{@link android.view.MenuItem#setChecked(boolean) setChecked()}. Misalnya:

+ +
+@Override
+public boolean onOptionsItemSelected(MenuItem item) {
+    switch (item.getItemId()) {
+        case R.id.vibrate:
+        case R.id.dont_vibrate:
+            if (item.isChecked()) item.setChecked(false);
+            else item.setChecked(true);
+            return true;
+        default:
+            return super.onOptionsItemSelected(item);
+    }
+}
+
+ +

Jika Anda tidak mengatur status diberi tanda cek dengan cara ini, maka status item (kotak cek atau +tombol radio) yang terlihat tidak akan +berubah bila pengguna memilihnya. Bila Anda telah mengatur status, aktivitas akan menjaga status diberi tanda cek +suatu item sehingga bila nanti pengguna membuka menu, status diberi tanda cek yang Anda +atur akan terlihat.

+ +

Catatan: +Item menu yang bisa diberi tanda cek dimaksudkan untuk digunakan hanya atas dasar per sesi dan tidak disimpan setelah +aplikasi dimusnahkan. Jika Anda memiliki pengaturan aplikasi yang ingin disimpan untuk pengguna, +Anda harus menyimpan data dengan menggunakan Shared Preferences.

+ + + +

Menambahkan Item Menu Berdasarkan Intent

+ +

Kadang-kadang Anda ingin supaya item menu menjalankan aktivitas dengan menggunakan {@link android.content.Intent} +(baik aktivitas berada dalam aplikasi Anda maupun di aplikasi lain). Bila Anda mengetahui intent +yang ingin digunakan dan memiliki item menu tertentu yang harus memulai intent, Anda bisa mengeksekusi +intent dengan {@link android.app.Activity#startActivity(Intent) startActivity()} selama +metode callback bila-item-dipilih yang sesuai (seperti callback {@link +android.app.Activity#onOptionsItemSelected(MenuItem) onOptionsItemSelected()}).

+ +

Akan tetapi, jika Anda tidak yakin apakah perangkat pengguna +berisi aplikasi yang menangani intent, maka menambahkan item menu yang memanggilnya bisa mengakibatkan +item menu tidak berfungsi, karena intent tidak bisa diterjemahkan menjadi +aktivitas. Untuk mengatasi hal ini, Android memungkinkan Anda menambahkan item menu secara dinamis ke menu +bila Android menemukan aktivitas pada perangkat yang menangani intent Anda.

+ +

Untuk menambahkan item menu berdasarkan aktivitas tersedia yang menerima intent:

+
    +
  1. Definisikan +intent dengan kategori {@link android.content.Intent#CATEGORY_ALTERNATIVE} dan/atau +{@link android.content.Intent#CATEGORY_SELECTED_ALTERNATIVE}, plus kebutuhan lainnya.
  2. +
  3. Panggil {@link +android.view.Menu#addIntentOptions(int,int,int,ComponentName,Intent[],Intent,int,MenuItem[]) +Menu.addIntentOptions()}. Android kemudian akan mencari setiap aplikasi yang bisa melakukan intent +dan menambahkannya ke menu Anda.
  4. +
+ +

Jika tidak ada aplikasi terinstal +yang memenuhi intent, maka tidak ada item menu yang ditambahkan.

+ +

Catatan: +{@link android.content.Intent#CATEGORY_SELECTED_ALTERNATIVE} digunakan untuk menangani +elemen yang saat ini dipilih pada layar. Jadi, metode hanya digunakan saat membuat Menu dalam {@link +android.app.Activity#onCreateContextMenu(ContextMenu,View,ContextMenuInfo) +onCreateContextMenu()}.

+ +

Misalnya:

+ +
+@Override
+public boolean onCreateOptionsMenu(Menu menu){
+    super.onCreateOptionsMenu(menu);
+
+    // Create an Intent that describes the requirements to fulfill, to be included
+    // in our menu. The offering app must include a category value of Intent.CATEGORY_ALTERNATIVE.
+    Intent intent = new Intent(null, dataUri);
+    intent.addCategory(Intent.CATEGORY_ALTERNATIVE);
+
+    // Search and populate the menu with acceptable offering applications.
+    menu.addIntentOptions(
+         R.id.intent_group,  // Menu group to which new items will be added
+         0,      // Unique item ID (none)
+         0,      // Order for the items (none)
+         this.getComponentName(),   // The current activity name
+         null,   // Specific items to place first (none)
+         intent, // Intent created above that describes our requirements
+         0,      // Additional flags to control items (none)
+         null);  // Array of MenuItems that correlate to specific items (none)
+
+    return true;
+}
+ +

Untuk setiap aktivitas yang diketahui menyediakan filter intent yang cocok dengan intent yang didefinisikan, item menu +akan ditambahkan, menggunakan nilai dalam filter intent android:label sebagai +judul item menu dan ikon aplikasi sebagai ikon item menu. Metode +{@link android.view.Menu#addIntentOptions(int,int,int,ComponentName,Intent[],Intent,int,MenuItem[]) +addIntentOptions()} mengembalikan jumlah item menu yang ditambahkan.

+ +

Catatan: Bila Anda memanggil {@link +android.view.Menu#addIntentOptions(int,int,int,ComponentName,Intent[],Intent,int,MenuItem[]) +addIntentOptions()}, metode ini akan mengesampingkan setiap dan semua item menu menurut grup menu yang ditetapkan dalam argumen +pertama.

+ + +

Memungkinkan aktivitas Anda ditambahkan ke menu lain

+ +

Anda juga bisa menawarkan layanan aktivitas Anda pada aplikasi lainnya, sehingga +aplikasi Anda bisa disertakan dalam menu aplikasi lain (membalik peran yang dijelaskan di atas).

+ +

Agar bisa dimasukkan dalam menu aplikasi lain, Anda perlu mendefinisikan +filter intent seperti biasa, tetapi pastikan menyertakan nilai-nilai {@link android.content.Intent#CATEGORY_ALTERNATIVE} +dan/atau {@link android.content.Intent#CATEGORY_SELECTED_ALTERNATIVE} untuk +kategori filter intent. Misalnya:

+
+<intent-filter label="@string/resize_image">
+    ...
+    <category android:name="android.intent.category.ALTERNATIVE" />
+    <category android:name="android.intent.category.SELECTED_ALTERNATIVE" />
+    ...
+</intent-filter>
+
+ +

Baca selengkapnya tentang penulisan filter intent dalam dokumen +Intent dan Filter Intent.

+ +

Untuk contoh aplikasi yang menggunakan teknik ini, lihat contoh kode +Note +Pad.

diff --git a/docs/html-intl/intl/in/guide/topics/ui/notifiers/notifications.jd b/docs/html-intl/intl/in/guide/topics/ui/notifiers/notifications.jd new file mode 100644 index 0000000000000000000000000000000000000000..9033b9f744f3ec34d46dc4ca5d6b180c92e55b5a --- /dev/null +++ b/docs/html-intl/intl/in/guide/topics/ui/notifiers/notifications.jd @@ -0,0 +1,979 @@ +page.title=Pemberitahuan +@jd:body + +
+
+

Dalam dokumen ini

+
    +
  1. Pertimbangan Desain
  2. +
  3. Membuat Pemberitahuan +
      +
    1. Isi pemberitahuan yang diperlukan
    2. +
    3. Isi dan pengaturan pemberitahuan opsional
    4. +
    5. Tindakan pemberitahuan
    6. +
    7. Prioritas pemberitahuan
    8. +
    9. Membuat pemberitahuan sederhana
    10. +
    11. Menerapkan layout yang diperluas pada pemberitahuan
    12. +
    13. Menangani kompatibilitas
    14. +
    +
  4. +
  5. Mengelola Pemberitahuan +
      +
    1. Memperbarui pemberitahuan
    2. +
    3. Menghapus pemberitahuan
    4. +
    +
  6. +
  7. Mempertahankan Navigasi saat Memulai Aktivitas +
      +
    1. Menyiapkan PendingIntent aktivitas biasa
    2. +
    3. Menyiapkan PendingIntent aktivitas khusus
    4. +
    +
  8. +
  9. Menampilkan Kemajuan dalam Pemberitahuan +
      +
    1. Menampilkan indikator kemajuan berdurasi tetap
    2. +
    3. Menampilkan indikator aktivitas berlanjut
    4. +
    +
  10. +
  11. Metadata Pemberitahuan
  12. +
  13. Pemberitahuan Pendahuluan
  14. +
  15. Pemberitahuan Layar Kunci
  16. +
      +
    1. Mengatur Visibilitas
    2. +
    3. Mengontrol Pemutaran Media pada Layar Kunci
    4. +
    +
  17. Layout Pemberitahuan Custom
  18. +
+ +

Kelas-kelas utama

+
    +
  1. {@link android.app.NotificationManager}
  2. +
  3. {@link android.support.v4.app.NotificationCompat}
  4. +
+

Video

+
    +
  1. + + Pemberitahuan di 4.1 +
  2. +
+

Lihat juga

+
    +
  1. + Desain Android: Pemberitahuan +
  2. +
+
+
+

+ Pemberitahuan adalah pesan yang bisa Anda tampilkan kepada pengguna di luar + UI normal aplikasi. Bila Anda memberi tahu sistem untuk mengeluarkan pemberitahuan, pemberitahuan akan muncul lebih dahulu sebagai ikon dalam + area pemberitahuan. Untuk melihat detail pemberitahuan, pengguna membuka + laci pemberitahuan. Baik area pemberitahuan maupun laci pemberitahuan + adalah area-area yang dikontrol sistem yang bisa dilihat pengguna kapan saja. +

+ +

+ Gambar 1. Pemberitahuan di area pemberitahuan. +

+ +

+ Gambar 2. Pemberitahuan di laci pemberitahuan. +

+ +

Catatan: Kecuali disebutkan, panduan ini mengacu pada +kelas {@link android.support.v4.app.NotificationCompat.Builder NotificationCompat.Builder} +dalam Support Library versi 4. +Kelas {@link android.app.Notification.Builder Notification.Builder} telah ditambahkan pada Android +3.0 (API level 11).

+ +

Pertimbangan Desain

+ +

Pemberitahuan, sebagai bagian penting dari antarmuka pengguna Android, memiliki panduan desainnya sendiri. +Perubahan desain materi yang diperkenalkan dalam Android 5.0 (API level 21) adalah sangat +penting, dan Anda harus meninjau pelatihan Desain Bahan +untuk informasi selengkapnya. Untuk mengetahui cara mendesain pemberitahuan dan interaksinya, bacalah panduan desain +Pemberitahuan.

+ +

Membuat Pemberitahuan

+ +

Anda menetapkan informasi dan tindakan UI bagi pemberitahuan dalam +objek {@link android.support.v4.app.NotificationCompat.Builder NotificationCompat.Builder}. +Untuk membuat pemberitahuan itu sendiri, panggil +{@link android.support.v4.app.NotificationCompat.Builder#build NotificationCompat.Builder.build()}, +yang akan mengembalikan objek {@link android.app.Notification} berisi spesifikasi Anda. Untuk mengeluarkan +pemberitahuan, Anda meneruskan objek {@link android.app.Notification} ke sistem dengan memanggil +{@link android.app.NotificationManager#notify NotificationManager.notify()}.

+ +

Isi pemberitahuan yang diperlukan

+

+ Objek {@link android.app.Notification} harus berisi yang berikut ini: +

+ +

Isi dan pengaturan pemberitahuan opsional

+

+ Semua isi dan pengaturan pemberitahuan lainnya bersifat opsional. Untuk mengetahui selengkapnya tentang semua itu, + lihat dokumentasi acuan untuk {@link android.support.v4.app.NotificationCompat.Builder}. +

+ +

Tindakan pemberitahuan

+

+ Walaupun bersifat opsional, Anda harus menambahkan setidaknya satu tindakan pada pemberitahuan. + Tindakan memungkinkan pengguna beralih langsung dari pemberitahuan ke + {@link android.app.Activity} dalam aplikasi Anda, tempat pengguna bisa melihat satu atau beberapa kejadian + atau melakukan pekerjaan lebih jauh. +

+

+ Pemberitahuan bisa menyediakan beberapa tindakan sekaligus. Anda harus selalu mendefinisikan tindakan yang + akan diaktifkan bila pengguna mengklik pemberitahuan; biasanya tindakan ini akan membuka + {@link android.app.Activity} dalam aplikasi Anda. Anda juga bisa menambahkan tombol pada pemberitahuan + yang melakukan tindakan tambahan seperti mendiamkan alarm atau segera merespons + pesan teks; fitur ini tersedia mulai Android 4.1. Jika menggunakan tombol tindakan tambahan, Anda + juga harus membuat fungsionalitasnya tersedia dalam {@link android.app.Activity} di aplikasi Anda; lihat + bagian Menangani kompatibilitas untuk detail selengkapnya. +

+

+ Dalam {@link android.app.Notification}, tindakan itu sendiri didefinisikan oleh + {@link android.app.PendingIntent} berisi + {@link android.content.Intent} yang memulai + {@link android.app.Activity} dalam aplikasi Anda. Untuk mengaitkan + {@link android.app.PendingIntent} dengan gestur, panggil metode + {@link android.support.v4.app.NotificationCompat.Builder} yang sesuai. Misalnya, jika ingin memulai + {@link android.app.Activity} bila pengguna mengklik teks pemberitahuan pada + laci pemberitahuan, tambahkan {@link android.app.PendingIntent} dengan memanggil + {@link android.support.v4.app.NotificationCompat.Builder#setContentIntent setContentIntent()}. +

+

+ Memulai {@link android.app.Activity} bila pengguna mengklik pemberitahuan adalah + skenario tindakan yang paling umum. Anda juga bisa memulai {@link android.app.Activity} bila pengguna + menghilangkan pemberitahuan. Dalam Android 4.1 dan yang lebih baru, Anda bisa memulai + {@link android.app.Activity} dari tombol tindakan. Untuk mengetahui selengkapnya, bacalah panduan acuan untuk + {@link android.support.v4.app.NotificationCompat.Builder}. +

+ +

Prioritas pemberitahuan

+

+ Jika diinginkan, Anda bisa mengatur prioritas pemberitahuan. Prioritas berfungsi + sebagai petunjuk bagi UI perangkat tentang cara menampilkan pemberitahuan. + Untuk mengatur prioritas pemberitahuan, panggil {@link + android.support.v4.app.NotificationCompat.Builder#setPriority(int) + NotificationCompat.Builder.setPriority()} dan teruskan salah satu konstanta prioritas {@link + android.support.v4.app.NotificationCompat}. Ada + lima level prioritas, mulai dari {@link + android.support.v4.app.NotificationCompat#PRIORITY_MIN} (-2) hingga {@link + android.support.v4.app.NotificationCompat#PRIORITY_MAX} (2); jika tidak diatur, + prioritas default akan ditetapkan {@link + android.support.v4.app.NotificationCompat#PRIORITY_DEFAULT} (0). +

+

Untuk informasi tentang mengatur level prioritas, lihat "Mengatur + dan mengelola prioritas pemberitahuan dengan benar" dalam panduan +Desain Pemberitahuan. +

+ +

Membuat pemberitahuan sederhana

+

+ Cuplikan berikut mengilustrasikan pemberitahuan sederhana yang menetapkan aktivitas untuk dibuka bila + pengguna mengklik pemberitahuan. Perhatikan bahwa kode ini membuat + objek {@link android.support.v4.app.TaskStackBuilder} dan menggunakannya untuk membuat + {@link android.app.PendingIntent} untuk tindakan. Pola ini dijelaskan secara lebih detail + di bagian + Mempertahankan Navigasi saat Memulai Aktivitas: +

+
+NotificationCompat.Builder mBuilder =
+        new NotificationCompat.Builder(this)
+        .setSmallIcon(R.drawable.notification_icon)
+        .setContentTitle("My notification")
+        .setContentText("Hello World!");
+// Creates an explicit intent for an Activity in your app
+Intent resultIntent = new Intent(this, ResultActivity.class);
+
+// The stack builder object will contain an artificial back stack for the
+// started Activity.
+// This ensures that navigating backward from the Activity leads out of
+// your application to the Home screen.
+TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);
+// Adds the back stack for the Intent (but not the Intent itself)
+stackBuilder.addParentStack(ResultActivity.class);
+// Adds the Intent that starts the Activity to the top of the stack
+stackBuilder.addNextIntent(resultIntent);
+PendingIntent resultPendingIntent =
+        stackBuilder.getPendingIntent(
+            0,
+            PendingIntent.FLAG_UPDATE_CURRENT
+        );
+mBuilder.setContentIntent(resultPendingIntent);
+NotificationManager mNotificationManager =
+    (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+// mId allows you to update the notification later on.
+mNotificationManager.notify(mId, mBuilder.build());
+
+

Demikian saja. Pengguna Anda kini telah diberi tahu.

+ +

Menerapkan layout yang diperluas pada pemberitahuan

+

+ Agar pemberitahuan muncul dalam tampilan yang diperluas, buat dahulu + objek {@link android.support.v4.app.NotificationCompat.Builder} dengan opsi tampilan normal + yang Anda inginkan. Berikutnya, panggil {@link android.support.v4.app.NotificationCompat.Builder#setStyle + Builder.setStyle()} dengan objek layout yang diperluas sebagai argumennya. +

+

+ Ingatlah bahwa pemberitahuan yang diperluas tidak tersedia pada platform-platform sebelum Android 4.1. Untuk + mengetahui cara menangani pemberitahuan untuk Android 4.1 dan untuk platform-platform sebelumnya, bacalah + bagian Menangani kompatibilitas. +

+

+ Misalnya, cuplikan kode berikut memperagakan cara mengubah pemberitahuan yang dibuat + dalam cuplikan sebelumnya untuk menggunakan layout yang diperluas: +

+
+NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(this)
+    .setSmallIcon(R.drawable.notification_icon)
+    .setContentTitle("Event tracker")
+    .setContentText("Events received")
+NotificationCompat.InboxStyle inboxStyle =
+        new NotificationCompat.InboxStyle();
+String[] events = new String[6];
+// Sets a title for the Inbox in expanded layout
+inboxStyle.setBigContentTitle("Event tracker details:");
+...
+// Moves events into the expanded layout
+for (int i=0; i < events.length; i++) {
+
+    inboxStyle.addLine(events[i]);
+}
+// Moves the expanded layout object into the notification object.
+mBuilder.setStyle(inBoxStyle);
+...
+// Issue the notification here.
+
+ +

Menangani kompatibilitas

+ +

+ Tidak semua fitur pemberitahuan tersedia untuk versi tertentu, walaupun + metode untuk mengaturnya ada dalam kelas pustaka dukungan + {@link android.support.v4.app.NotificationCompat.Builder NotificationCompat.Builder}. + Misalnya, tombol tindakan, yang bergantung pada pemberitahuan yang diperluas, hanya muncul pada Android + 4.1 dan lebih tinggi, karena pemberitahuan yang diperluas itu sendiri hanya tersedia pada + Android 4.1 dan yang lebih tinggi. +

+

+ Untuk memastikan kompatibilitas terbaik, buatlah pemberitahuan dengan + {@link android.support.v4.app.NotificationCompat NotificationCompat} dan subkelasnya, + khususnya {@link android.support.v4.app.NotificationCompat.Builder + NotificationCompat.Builder}. Selain itu, ikutilah proses ini bila Anda mengimplementasikan pemberitahuan: +

+
    +
  1. + Sediakan semua fungsionalitas pemberitahuan kepada semua pengguna, terlepas dari versi + yang mereka gunakan. Caranya, pastikan semua fungsionalitas tersedia dari + {@link android.app.Activity} dalam aplikasi Anda. Anda mungkin perlu menambahkan sebuah + {@link android.app.Activity} baru untuk melakukannya. +

    + Misalnya, jika Anda ingin menggunakan + {@link android.support.v4.app.NotificationCompat.Builder#addAction addAction()} untuk + menyediakan kontrol yang menghentikan dan memulai pemutaran media, implementasikan dahulu + kontrol ini pada {@link android.app.Activity} dalam aplikasi Anda. +

    +
  2. +
  3. + Pastikan semua pengguna bisa memperoleh fungsionalitas dalam {@link android.app.Activity}, + dengan memulainya bila pengguna mengklik pemberitahuan. Caranya, + buatlah {@link android.app.PendingIntent} + untuk {@link android.app.Activity}. Panggil + {@link android.support.v4.app.NotificationCompat.Builder#setContentIntent + setContentIntent()} untuk menambahkan {@link android.app.PendingIntent} pada pemberitahuan. +
  4. +
  5. + Kini tambahkan fitur pemberitahuan diperluas yang ingin Anda gunakan pada pemberitahuan. Ingatlah + bahwa setiap fungsionalitas yang Anda tambahkan juga harus tersedia dalam {@link android.app.Activity} + yang akan dimulai bila pengguna mengklik pemberitahuan. +
  6. +
+ + + + +

Mengelola Pemberitahuan

+

+ Bila perlu mengeluarkan pemberitahuan beberapa kali untuk tipe kejadian yang sama, +hindari membuat pemberitahuan yang sama sekali baru. Sebagai gantinya, Anda harus mempertimbangkan untuk memperbarui + pemberitahuan sebelumnya, baik dengan mengubah sebagian nilainya atau dengan menambahkan nilai, atau keduanya. +

+

+ Misalnya, Gmail akan memberi tahu pengguna bila ada email baru dengan menambah hitungan + pesan tidak terbaca dan dengan menambahkan rangkuman tiap email ke pemberitahuan. Ini disebut dengan + "stacking" (menumpuk) pemberitahuan; hal ini dijelaskan lebih detail dalam panduan + Desain Pemberitahuan. +

+

+ Catatan: Fitur Gmail ini mensyaratkan layout "kotak masuk" diperluas, yang merupakan + bagian dari fitur pemberitahuan diperluas yang tersedia mulai Android 4.1. +

+

+ Bagian berikut menjelaskan cara memperbarui pemberitahuan dan cara menghapusnya. +

+

Memperbarui pemberitahuan

+

+ Untuk menyiapkan pemberitahuan agar bisa diperbarui, keluarkan pemberitahuan bersama ID pemberitahuan dengan + memanggil {@link android.app.NotificationManager#notify(int, android.app.Notification) NotificationManager.notify()}. + Untuk memperbarui pemberitahuan ini setelah Anda + mengeluarkan, memperbarui, atau membuat objek {@link android.support.v4.app.NotificationCompat.Builder}, + buat objek {@link android.app.Notification} darinya, dan keluarkan + {@link android.app.Notification} bersama ID yang sama dengan yang Anda gunakan sebelumnya. Jika + pemberitahuan sebelumnya tetap terlihat, sistem akan memperbaruinya dari konten + objek {@link android.app.Notification}. Jika pemberitahuan sebelumnya telah dihilangkan, sebuah + pemberitahuan baru akan dibuat. +

+

+ Cuplikan berikut memperagakan pemberitahuan yang diperbarui untuk mencerminkan + jumlah kejadian yang telah terjadi. Cuplikan ini menumpuk pemberitahuan, yang menampilkan rangkuman: +

+
+mNotificationManager =
+        (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+// Sets an ID for the notification, so it can be updated
+int notifyID = 1;
+mNotifyBuilder = new NotificationCompat.Builder(this)
+    .setContentTitle("New Message")
+    .setContentText("You've received new messages.")
+    .setSmallIcon(R.drawable.ic_notify_status)
+numMessages = 0;
+// Start of a loop that processes data and then notifies the user
+...
+    mNotifyBuilder.setContentText(currentText)
+        .setNumber(++numMessages);
+    // Because the ID remains unchanged, the existing notification is
+    // updated.
+    mNotificationManager.notify(
+            notifyID,
+            mNotifyBuilder.build());
+...
+
+ + +

Menghapus pemberitahuan

+

+ Pemberitahuan tetap terlihat hingga salah satu kejadian berikut terjadi: +

+ + + +

Mempertahankan Navigasi saat Memulai Aktivitas

+

+ Bila memulai {@link android.app.Activity} dari pemberitahuan, Anda harus mempertahankan + pengalaman navigasi yang diharapkan pengguna. Mengklik Back harus membawa pengguna kembali melalui + aliran pekerjaan normal aplikasi ke layar Home, dan mengklik Recents harus menampilkan + {@link android.app.Activity} sebagai tugas terpisah. Untuk mempertahankan pengalaman navigasi, Anda + harus memulai {@link android.app.Activity} dalam tugas baru. Cara menyiapkan + {@link android.app.PendingIntent} untuk memberi Anda tugas baru bergantung pada sifat + {@link android.app.Activity} yang Anda mulai. Ada dua situasi umum: +

+
+
+ Aktivitas rutin +
+
+ Anda memulai {@link android.app.Activity} yang merupakan bagian dari aliran pekerjaan normal + aplikasi. Dalam situasi ini, siapkan {@link android.app.PendingIntent} untuk + memulai tugas baru, dan sediakan {@link android.app.PendingIntent} bersama back-stack + yang meniru perilaku Back biasa. +

+ Pemberitahuan dari aplikasi Gmail memperagakan hal ini. Bila Anda mengklik pemberitahuan untuk + satu pesan email, Anda akan melihat pesan itu sendiri. Menyentuh Back akan membawa Anda + kembali melalui Gmail ke layar Home, persis seperti jika memasuki Gmail dari + layar Home bukannya memasukinya dari pemberitahuan. +

+

+ Hal ini terjadi terlepas dari aplikasi tempat Anda berada saat menyentuh + pemberitahuan. Misalnya, jika Anda dalam Gmail sedang menulis pesan, dan Anda mengklik + pemberitahuan untuk satu email, Anda akan segera dibawa ke email itu. Menyentuh Back + akan membawa Anda ke kotak masuk kemudian layar Home, bukannya membawa Anda ke + pesan yang sedang ditulis. +

+
+
+ Aktivitas khusus +
+
+ Pengguna hanya melihat {@link android.app.Activity} ini jika dimulai dari pemberitahuan. + Dalam beberapa hal, {@link android.app.Activity} akan memperluas pemberitahuan dengan menyediakan + informasi yang akan sulit untuk ditampilkan dalam pemberitahuan itu sendiri. Untuk situasi ini, + siapkan {@link android.app.PendingIntent} untuk dimulai dalam tugas baru. Tidak perlu + membuat back-stack, karena {@link android.app.Activity} yang dimulai bukan bagian dari + aliran aktivitas aplikasi. Mengklik Back tetap akan membawa pengguna ke + layar Home. +
+
+ +

Menyiapkan PendingIntent aktivitas biasa

+

+ Untuk menyiapkan {@link android.app.PendingIntent} yang memulai entri langsung + {@link android.app.Activity}, ikuti langkah-langkah ini: +

+
    +
  1. + Definisikan hierarki {@link android.app.Activity} aplikasi Anda dalam manifes. +
      +
    1. + Tambahkan dukungan untuk Android 4.0.3 dan yang terdahulu. Caranya, tetapkan induk + {@link android.app.Activity} yang Anda mulai dengan menambahkan elemen +<meta-data> + sebagai anak +<activity>. +

      + Untuk elemen ini, atur +android:name="android.support.PARENT_ACTIVITY". + Atur +android:value="<parent_activity_name>" + dengan <parent_activity_name> sebagai nilai +android:name + untuk elemen induk +<activity> +. Lihat XML berikut sebagai contoh. +

      +
    2. +
    3. + Juga tambahkan dukungan untuk Android 4.1 dan yang lebih baru. Caranya, tambahkan atribut +android:parentActivityName + pada elemen +<activity> + dari {@link android.app.Activity} yang Anda mulai. +
    4. +
    +

    + XML akhir akan terlihat seperti ini: +

    +
    +<activity
    +    android:name=".MainActivity"
    +    android:label="@string/app_name" >
    +    <intent-filter>
    +        <action android:name="android.intent.action.MAIN" />
    +        <category android:name="android.intent.category.LAUNCHER" />
    +    </intent-filter>
    +</activity>
    +<activity
    +    android:name=".ResultActivity"
    +    android:parentActivityName=".MainActivity">
    +    <meta-data
    +        android:name="android.support.PARENT_ACTIVITY"
    +        android:value=".MainActivity"/>
    +</activity>
    +
    +
  2. +
  3. + Buat back-stack berdasarkan {@link android.content.Intent} yang memulai + {@link android.app.Activity}: +
      +
    1. + Buat {@link android.content.Intent} untuk memulai {@link android.app.Activity}. +
    2. +
    3. + Buat stack-builder (pembangun tumpukan) dengan memanggil {@link android.app.TaskStackBuilder#create + TaskStackBuilder.create()}. +
    4. +
    5. + Tambahkan back-stack ke stack-builder dengan memanggil + {@link android.support.v4.app.TaskStackBuilder#addParentStack addParentStack()}. + Untuk setiap {@link android.app.Activity} dalam hierarki yang telah Anda definisikan dalam + manifes, back-stack berisi objek {@link android.content.Intent} yang + memulai {@link android.app.Activity}. Metode ini juga menambahkan flag yang memulai + back-stack dalam tugas baru. +

      + Catatan: Walaupun argumen untuk + {@link android.support.v4.app.TaskStackBuilder#addParentStack addParentStack()} + adalah acuan ke {@link android.app.Activity} yang dimulai, panggilan metode + tidak akan menambahkan {@link android.content.Intent} yang memulai + {@link android.app.Activity}. Sebagai gantinya, hal itu ditangani dalam langkah berikutnya. +

      +
    6. +
    7. + Tambahkan {@link android.content.Intent} yang memulai {@link android.app.Activity} + dari pemberitahuan, dengan memanggil + {@link android.support.v4.app.TaskStackBuilder#addNextIntent addNextIntent()}. + Teruskan {@link android.content.Intent} yang Anda buat dalam langkah pertama sebagai + argumen ke + {@link android.support.v4.app.TaskStackBuilder#addNextIntent addNextIntent()}. +
    8. +
    9. + Jika perlu, tambahkan argumen ke objek {@link android.content.Intent} pada + back-stack dengan memanggil {@link android.support.v4.app.TaskStackBuilder#editIntentAt + TaskStackBuilder.editIntentAt()}. Kadang-kadang perlu memastikan apakah + {@link android.app.Activity} target menampilkan data bermakna saat pengguna menelusurinya + dengan menggunakan Back. +
    10. +
    11. + Dapatkan {@link android.app.PendingIntent} untuk back-stack ini dengan memanggil + {@link android.support.v4.app.TaskStackBuilder#getPendingIntent getPendingIntent()}. + Anda nanti bisa menggunakan {@link android.app.PendingIntent} ini sebagai argumen untuk + {@link android.support.v4.app.NotificationCompat.Builder#setContentIntent + setContentIntent()}. +
    12. +
    +
  4. +
+

+ Cuplikan kode berikut memperagakan prosesnya: +

+
+...
+Intent resultIntent = new Intent(this, ResultActivity.class);
+TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);
+// Adds the back stack
+stackBuilder.addParentStack(ResultActivity.class);
+// Adds the Intent to the top of the stack
+stackBuilder.addNextIntent(resultIntent);
+// Gets a PendingIntent containing the entire back stack
+PendingIntent resultPendingIntent =
+        stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
+...
+NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
+builder.setContentIntent(resultPendingIntent);
+NotificationManager mNotificationManager =
+    (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+mNotificationManager.notify(id, builder.build());
+
+ +

Menyiapkan PendingIntent aktivitas khusus

+

+ Bagian berikut menjelaskan cara menyiapkan aktivitas khusus + {@link android.app.PendingIntent}. +

+

+ {@link android.app.Activity} khusus tidak memerlukan back-stack, sehingga Anda tidak perlu + mendefinisikan hierarki {@link android.app.Activity}-nya dalam manifes, dan Anda tidak perlu + memanggil + {@link android.support.v4.app.TaskStackBuilder#addParentStack addParentStack()} untuk membuat + back-stack. Sebagai gantinya, gunakan manifes untuk menyiapkan opsi tugas {@link android.app.Activity}, + dan buat {@link android.app.PendingIntent} dengan memanggil + {@link android.app.PendingIntent#getActivity getActivity()}: +

+
    +
  1. + Dalam manifes, tambahkan atribut berikut pada elemen +<activity> + untuk {@link android.app.Activity} +
    +
    +android:name="activityclass" +
    +
    + Nama kelas mutlak (fully qualified) aktivitas. +
    +
    +android:taskAffinity="" +
    +
    + Dikombinasikan dengan flag + {@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK FLAG_ACTIVITY_NEW_TASK} + yang Anda atur dalam kode, ini memastikan bahwa {@link android.app.Activity} ini tidak + masuk ke dalam tugas default aplikasi. Setiap tugas yang ada yang memiliki + afinitas default aplikasi tidak terpengaruh. +
    +
    +android:excludeFromRecents="true" +
    +
    + Mengecualikan tugas baru dari Recents, sehingga pengguna tidak bisa tanpa sengaja + mengarahkan kembali. +
    +
    +

    + Cuplikan ini menampilkan elemen: +

    +
    +<activity
    +    android:name=".ResultActivity"
    +...
    +    android:launchMode="singleTask"
    +    android:taskAffinity=""
    +    android:excludeFromRecents="true">
    +</activity>
    +...
    +
    +
  2. +
  3. + Buat dan keluarkan pemberitahuan: +
      +
    1. + Buat {@link android.content.Intent} yang memulai + {@link android.app.Activity}. +
    2. +
    3. + Atur {@link android.app.Activity} untuk dimulai dalam tugas kosong yang baru dengan memanggil + {@link android.content.Intent#setFlags setFlags()} dengan flag + {@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK FLAG_ACTIVITY_NEW_TASK} + dan + {@link android.content.Intent#FLAG_ACTIVITY_CLEAR_TASK FLAG_ACTIVITY_CLEAR_TASK}. +
    4. +
    5. + Atur setiap opsi lain yang Anda perlukan untuk {@link android.content.Intent}. +
    6. +
    7. + Buat {@link android.app.PendingIntent} dari {@link android.content.Intent} + dengan memanggil {@link android.app.PendingIntent#getActivity getActivity()}. + Anda nanti bisa menggunakan {@link android.app.PendingIntent} ini sebagai argumen untuk + {@link android.support.v4.app.NotificationCompat.Builder#setContentIntent + setContentIntent()}. +
    8. +
    +

    + Cuplikan kode berikut memperagakan prosesnya: +

    +
    +// Instantiate a Builder object.
    +NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
    +// Creates an Intent for the Activity
    +Intent notifyIntent =
    +        new Intent(this, ResultActivity.class);
    +// Sets the Activity to start in a new, empty task
    +notifyIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
    +                        | Intent.FLAG_ACTIVITY_CLEAR_TASK);
    +// Creates the PendingIntent
    +PendingIntent notifyPendingIntent =
    +        PendingIntent.getActivity(
    +        this,
    +        0,
    +        notifyIntent,
    +        PendingIntent.FLAG_UPDATE_CURRENT
    +);
    +
    +// Puts the PendingIntent into the notification builder
    +builder.setContentIntent(notifyPendingIntent);
    +// Notifications are issued by sending them to the
    +// NotificationManager system service.
    +NotificationManager mNotificationManager =
    +    (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
    +// Builds an anonymous Notification object from the builder, and
    +// passes it to the NotificationManager
    +mNotificationManager.notify(id, builder.build());
    +
    +
  4. +
+ + +

Menampilkan Kemajuan dalam Pemberitahuan

+

+ Pemberitahuan bisa menyertakan indikator kemajuan beranimasi yang menampilkan status +operasi yang berjalan kepada pengguna. Jika Anda bisa memperkirakan lamanya operasi berlangsung dan berapa banyak + yang sudah selesai pada suatu waktu, gunakan bentuk indikator yang "pasti" + (baris kemajuan). Jika Anda tidak bisa memperkirakan lamanya operasi, gunakan + bentuk indikator "tidak pasti" (indikator aktivitas). +

+

+ Indikator kemajuan ditampilkan bersama implementasi platform + kelas {@link android.widget.ProgressBar}. +

+

+ Untuk menggunakan indikator kemajuan pada platform mulai dari Android 4.0, panggil + {@link android.support.v4.app.NotificationCompat.Builder#setProgress setProgress()}. Untuk + versi sebelumnya, Anda harus membuat layout pemberitahuan custom sendiri yang +menyertakan tampilan {@link android.widget.ProgressBar}. +

+

+ Bagian berikut ini menjelaskan cara menampilkan kemajuan dalam pemberitahuan dengan menggunakan + {@link android.support.v4.app.NotificationCompat.Builder#setProgress setProgress()}. +

+ +

Menampilkan indikator kemajuan berdurasi tetap

+

+ Untuk menampilkan baris kemajuan pasti, tambahkan baris itu ke pemberitahuan dengan memanggil + {@link android.support.v4.app.NotificationCompat.Builder#setProgress + setProgress(max, progress, false)}, kemudian keluarkan pemberitahuan. Selagi operasi berlangsung, + tambah progress, dan perbarui pemberitahuan. Di akhir operasi, + progress harus sama dengan max. Satu cara umum memanggil + {@link android.support.v4.app.NotificationCompat.Builder#setProgress setProgress()} + adalah mengatur max ke 100, kemudian tambah progress sebagai + nilai "persen selesai"untuk operasi itu. +

+

+ Anda bisa membiarkan baris kemajuan ditampilkan saat operasi selesai, atau menghilangkannya. Dalam + hal apa pun, ingatlah memperbarui teks pemberitahuan untuk menampilkan bahwa operasi telah selesai. + Untuk menghapus baris kemajuan, panggil + {@link android.support.v4.app.NotificationCompat.Builder#setProgress + setProgress(0, 0, false)}. Misalnya: +

+
+...
+mNotifyManager =
+        (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+mBuilder = new NotificationCompat.Builder(this);
+mBuilder.setContentTitle("Picture Download")
+    .setContentText("Download in progress")
+    .setSmallIcon(R.drawable.ic_notification);
+// Start a lengthy operation in a background thread
+new Thread(
+    new Runnable() {
+        @Override
+        public void run() {
+            int incr;
+            // Do the "lengthy" operation 20 times
+            for (incr = 0; incr <= 100; incr+=5) {
+                    // Sets the progress indicator to a max value, the
+                    // current completion percentage, and "determinate"
+                    // state
+                    mBuilder.setProgress(100, incr, false);
+                    // Displays the progress bar for the first time.
+                    mNotifyManager.notify(0, mBuilder.build());
+                        // Sleeps the thread, simulating an operation
+                        // that takes time
+                        try {
+                            // Sleep for 5 seconds
+                            Thread.sleep(5*1000);
+                        } catch (InterruptedException e) {
+                            Log.d(TAG, "sleep failure");
+                        }
+            }
+            // When the loop is finished, updates the notification
+            mBuilder.setContentText("Download complete")
+            // Removes the progress bar
+                    .setProgress(0,0,false);
+            mNotifyManager.notify(ID, mBuilder.build());
+        }
+    }
+// Starts the thread by calling the run() method in its Runnable
+).start();
+
+ + +

Menampilkan indikator aktivitas berlanjut

+

+ Untuk menampilkan indikator aktivitas tidak pasti, tambahkan aktivitas ke pemberitahuan dengan + {@link android.support.v4.app.NotificationCompat.Builder#setProgress setProgress(0, 0, true)} + (dua argumen pertama akan diabaikan), dan keluarkan pemberitahuan. Hasilnya adalah indikator + yang memiliki gaya yang sama dengan baris kemajuan, hanya saja animasinya terus berjalan. +

+

+ Keluarkan pemberitahuan di awal operasi. Animasi akan berjalan hingga Anda + memodifikasi pemberitahuan. Bila operasi selesai, panggil + {@link android.support.v4.app.NotificationCompat.Builder#setProgress setProgress(0, 0, false)} + kemudian perbarui pemberitahuan untuk menghapus indikator aktivitas. + Selalu lakukan ini; jika makan animasi akan terus berjalan sekalipun operasi telah selesai. Juga + ingatlah mengubah teks pemberitahuan untuk menunjukkan bahwa operasi telah selesai. +

+

+ Untuk melihat cara kerja indikator aktivitas, lihat cuplikan terdahulu. Cari lokasi baris-baris berikut: +

+
+// Sets the progress indicator to a max value, the current completion
+// percentage, and "determinate" state
+mBuilder.setProgress(100, incr, false);
+// Issues the notification
+mNotifyManager.notify(0, mBuilder.build());
+
+

+ Ganti baris yang telah Anda temukan dengan baris berikut: +

+
+ // Sets an activity indicator for an operation of indeterminate length
+mBuilder.setProgress(0, 0, true);
+// Issues the notification
+mNotifyManager.notify(0, mBuilder.build());
+
+ +

Metadata Pemberitahuan

+ +

Pemberitahuan dapat disortir sesuai metadata yang Anda tetapkan dengan +metode {@link android.support.v4.app.NotificationCompat.Builder} berikut:

+ + + +
+ +

+ Gambar 3. Aktivitas layar penuh yang menampilkan pemberitahuan pendahuluan +

+
+ +

Pemberitahuan Pendahuluan

+ +

Dengan Android 5.0 (API level 21), pemberitahuan bisa muncul dalam jendela kecil mengambang +(yang disebut juga dengan pemberitahuan pendahuluan) saat perangkat aktif +(yakni, perangkat dibuka kuncinya dan layarnya menyala). Pemberitahuan ini +muncul seperti bentuk ringkas pemberitahuan Anda, hanya saja +pemberitahuan pendahuluan juga menampilkan tombol tindakan. Pengguna bisa menindaklanjuti atau mengabaikan, +pemberitahuan pendahuluan tanpa meninggalkan aplikasi saat ini.

+ +

Contoh-contoh kondisi yang dapat memicu pemberitahuan pendahuluan antara lain:

+ + + +

Pemberitahuan Layar Kunci

+ +

Dengan rilis Android 5.0 (API level 21), pemberitahuan kini dapat muncul pada +layar kunci. Aplikasi Anda bisa menggunakan fungsionalitas ini untuk menyediakan kontrol pemutaran media dan +tindakan umum lainnya. Pengguna bisa memilih lewat Settings apakah akan menampilkan pemberitahuan pada layar kunci, dan +Anda bisa mendesain apakah pemberitahuan aplikasi akan terlihat pada layar kunci.

+ +

Mengatur Visibilitas

+ +

Aplikasi Anda bisa mengatur level detail terlihat pada pemberitahuan yang ditampilkan di +layar kunci aman. Anda memanggil {@link android.support.v4.app.NotificationCompat.Builder#setVisibility(int) setVisibility()} +dan menetapkan salah satu nilai berikut:

+ + + +

Bila {@link android.support.v4.app.NotificationCompat#VISIBILITY_PRIVATE} telah diatur, Anda juga bisa +menyediakan versi alternatif isi pemberitahuan yang menyembunyikan detail tertentu. Misalnya, +aplikasi SMS dapat menampilkan pemberitahuan yang menampilkan Anda memiliki 3 pesan teks baru, namun menyembunyikan +isi dan pengirim pesan. Untuk menyediakan pemberitahuan alternatif ini, buat dahulu pemberitahuan +pengganti menggunakan {@link android.support.v4.app.NotificationCompat.Builder}. Bila Anda membuat +objek pemberitahuan privat, lampirkan pemberitahuan pengganti melalui metode +{@link android.support.v4.app.NotificationCompat.Builder#setPublicVersion(android.app.Notification) setPublicVersion()} +.

+ +

Mengontrol Pemutaran Media pada Layar Kunci

+ +

Dalam Android 5.0 (API level 21) layar kunci tidak lagi menampilkan kontrol media +berdasarkan {@link android.media.RemoteControlClient}, yang sekarang telah dihilangkan. Sebagai gantinya, gunakan +template {@link android.app.Notification.MediaStyle} dengan metode +{@link android.app.Notification.Builder#addAction(android.app.Notification.Action) addAction()} +, yang mengubah tindakan menjadi ikon yang bisa diklik.

+ +

Catatan: Template dan metode {@link android.app.Notification.Builder#addAction(android.app.Notification.Action) addAction()} +tidak disertakan dalam pustaka dukungan, sehingga fitur-fitur ini berjalan pada Android 5.0 dan yang lebih tinggi +saja.

+ +

Untuk menampilkan kontrol pemutaran media di layar kunci dalam Android 5.0, atur visibilitas +ke {@link android.support.v4.app.NotificationCompat#VISIBILITY_PUBLIC}, seperti dijelaskan di atas. Kemudian tambahkan +tindakan dan atur template {@link android.app.Notification.MediaStyle}, seperti dijelaskan dalam contoh kode +berikut:

+ +
+Notification notification = new Notification.Builder(context)
+    // Show controls on lock screen even when user hides sensitive content.
+    .setVisibility(Notification.VISIBILITY_PUBLIC)
+    .setSmallIcon(R.drawable.ic_stat_player)
+    // Add media control buttons that invoke intents in your media service
+    .addAction(R.drawable.ic_prev, "Previous", prevPendingIntent) // #0
+    .addAction(R.drawable.ic_pause, "Pause", pausePendingIntent)  // #1
+    .addAction(R.drawable.ic_next, "Next", nextPendingIntent)     // #2
+    // Apply the media style template
+    .setStyle(new Notification.MediaStyle()
+    .setShowActionsInCompactView(1 /* #1: pause button */)
+    .setMediaSession(mMediaSession.getSessionToken())
+    .setContentTitle("Wonderful music")
+    .setContentText("My Awesome Band")
+    .setLargeIcon(albumArtBitmap)
+    .build();
+
+ +

Catatan: Dihilangkannya {@link android.media.RemoteControlClient} +memiliki implikasi lebih jauh untuk mengontrol media. Lihat +Kontrol Pemutaran Media +untuk informasi selengkapnya tentang API baru untuk mengelola sesi media dan mengontrol pemutaran.

+ + + +

Layout Pemberitahuan Custom

+

+ Kerangka kerja pemberitahuan memungkinkan Anda mendefinisikan layout pemberitahuan custom, yang + mendefinisikan penampilan pemberitahuan dalam objek {@link android.widget.RemoteViews}. + Pemberitahuan dengan layout custom serupa pemberitahuan normal, namun dibuat berdasarkan + {@link android.widget.RemoteViews} yang didefinisikan dalam file layout XML. +

+

+ Tinggi yang tersedia untuk layout pemberitahuan custom bergantung pada tampilan pemberitahuan. Layout + tampilan normal dibatasi hingga 64 dp, dan layout tampilan yang diperluas dibatasi hingga 256 dp. +

+

+ Untuk mendefinisikan layout pemberitahuan custom, mulailah dengan membuat instance + objek {@link android.widget.RemoteViews} yang memekarkan file layout XML. Kemudian, + sebagai ganti memanggil metode seperti + {@link android.support.v4.app.NotificationCompat.Builder#setContentTitle setContentTitle()}, + panggil {@link android.support.v4.app.NotificationCompat.Builder#setContent setContent()}. Untuk mengatur + detail isi pemberitahuan custom, gunakan metode dalam + {@link android.widget.RemoteViews} untuk mengatur nilai anak tampilan: +

+
    +
  1. + Buat layout XML untuk pemberitahuan di file terpisah. Anda bisa menggunakan nama file +apa saja yang diinginkan, namun Anda harus menggunakan ekstensi .xml +
  2. +
  3. + Dalam aplikasi Anda, gunakan metode {@link android.widget.RemoteViews} untuk mendefinisikan + ikon dan teks pemberitahuan. Masukkan objek {@link android.widget.RemoteViews} ini ke dalam + {@link android.support.v4.app.NotificationCompat.Builder} Anda dengan memanggil + {@link android.support.v4.app.NotificationCompat.Builder#setContent setContent()}. Hindari + mengatur {@link android.graphics.drawable.Drawable} latar belakang pada + objek {@link android.widget.RemoteViews} Anda, karena warna teks bisa menjadi tidak terbaca. +
  4. +
+

+ Kelas {@link android.widget.RemoteViews} juga menyertakan metode yang bisa Anda gunakan untuk + menambahkan {@link android.widget.Chronometer} atau {@link android.widget.ProgressBar} +dengan mudah ke layout pemberitahuan Anda. Untuk informasi selengkapnya tentang cara membuat layout custom + pemberitahuan Anda, lihat dokumentasi acuan {@link android.widget.RemoteViews}. +

+

+ Perhatian: Bila Anda menggunakan layout pemberitahuan custom, berhati-hatilah + untuk memastikan bahwa layout custom itu bekerja pada berbagai orientasi dan resolusi perangkat. Walaupun + berlaku bagi semua layout View, nasihat ini khususnya penting untuk pemberitahuan karena + ruang di laci pemberitahuan sangat terbatas. Jangan buat layout custom terlalu + kompleks, dan pastikan mengujinya di berbagai konfigurasi. +

+ +

Menggunakan sumber daya gaya untuk teks pemberitahuan custom

+

+ Selalu gunakan sumber daya gaya untuk teks pemberitahuan custom. Warna latar belakang + pemberitahuan bisa bervariasi di berbagai perangkat dan versi, dan menggunakan sumber daya gaya + membantu Anda menangani hal ini. Mulai Android 2.3, sistem mendefinisikan sebuah gaya untuk + teks layout pemberitahuan standar. Jika Anda menggunakan gaya yang sama dalam aplikasi yang menargetkan Android + 2.3 atau yang lebih tinggi, Anda akan memastikan bahwa teks terlihat pada latar belakang tampilan. +

diff --git a/docs/html-intl/intl/in/guide/topics/ui/overview.jd b/docs/html-intl/intl/in/guide/topics/ui/overview.jd new file mode 100644 index 0000000000000000000000000000000000000000..a0b2b0645a900aa1fd20cfaf49f011e33c0352c7 --- /dev/null +++ b/docs/html-intl/intl/in/guide/topics/ui/overview.jd @@ -0,0 +1,71 @@ +page.title=Ikhtisar UI +@jd:body + + +

Semua elemen antarmuka pengguna dalam aplikasi Android dibangun menggunakan objek {@link android.view.View} dan +{@link android.view.ViewGroup}. {@link android.view.View} adalah objek yang menarik +sesuatu di layar dan dapat berinteraksi dengan pengguna. {@link android.view.ViewGroup} merupakan sebuah +objek yang menyimpan objek {@link android.view.View} lainnya (dan {@link android.view.ViewGroup}) untuk +mendefinisikan layout antarmuka.

+ +

Android menyediakan sekumpulan subkelas {@link android.view.View} dan {@link +android.view.ViewGroup} yang menawarkan kontrol input umum (seperti tombol dan bidang +teks) serta berbagai model layout (seperti layout linear atau relatif).

+ + +

Layout Antarmuka Pengguna

+ +

Antarmuka pengguna untuk setiap komponen aplikasi Anda didefinisikan menggunakan hierarki objek {link +android.view.View} dan {@link android.view.ViewGroup}, seperti yang ditampilkan dalam gambar 1. Setiap kelompok tampilan +merupakan kontainer tak terlihat yang mengelola tampilan anak, sementara tampilan anak ini dapat menjadi kontrol +input atau widget lain yang +menarik sebagian dari UI. Pohon hierarki ini bisa sederhana atau bisa juga kompleks bergantung kebutuhan +(namun yang sederhana paling baik untuk kinerja).

+ + +

Gambar 1. Ilustrasi dari hierarki tampilan, yang mendefinisikan layout +UI.

+ +

Untuk mendeklarasikan layout, Anda dapat menyediakan objek {@link android.view.View} dalam kode dan mulai +membangun pohon, namun cara termudah dan terefektif untuk mendefinisikan layout adalah dengan file XML. +XML menawarkan struktur layout yang dapat dibaca manusia, serupa dengan HTML.

+ +

Nama elemen XML untuk tampilan sesuai dengan kelas Android yang diwakilinya. Dengan demikian elemen +<TextView> membuat widget {@link android.widget.TextView} dalam UI Anda, +dan elemen <LinearLayout> membuat kelompok tampilan {@link android.widget.LinearLayout} +.

+ +

Misalnya, layout vertikal sederhana dengan tampilan teks dan tombol akan tampak seperti ini:

+
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:layout_width="fill_parent" 
+              android:layout_height="fill_parent"
+              android:orientation="vertical" >
+    <TextView android:id="@+id/text"
+              android:layout_width="wrap_content"
+              android:layout_height="wrap_content"
+              android:text="I am a TextView" />
+    <Button android:id="@+id/button"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="I am a Button" />
+</LinearLayout>
+
+ +

Saat Anda memuat sumber daya layout di aplikasi, Android akan menginisialisasi setiap simpul layout menjadi +objek runtime yang bisa Anda gunakan untuk mendefinisikan perilaku tambahan, query status objek, atau memodifikasi +layout.

+ +

Untuk mendapatkan panduan lengkap mengenai pembuatan layout UI, lihat Layout +XML. + + +

Komponen Antarmuka Pengguna

+ +

Anda tidak harus membuat semua UI menggunakan objek {@link android.view.View} dan {link +android.view.ViewGroup}. Android menyediakan beberapa komponen aplikasi yang menawarkan +layout UI standar yang tinggal Anda definisikan kontennya. Komponen UI ini masing-masing +memiliki set API unik yang dijelaskan dalam masing-masing dokumennya, seperti Action-Bar, Dialog, dan Pemberitahuan Status.

+ + diff --git a/docs/html-intl/intl/in/guide/topics/ui/settings.jd b/docs/html-intl/intl/in/guide/topics/ui/settings.jd new file mode 100644 index 0000000000000000000000000000000000000000..5ac61f928c19e9993b52b99aff77720543584e53 --- /dev/null +++ b/docs/html-intl/intl/in/guide/topics/ui/settings.jd @@ -0,0 +1,1202 @@ +page.title=Pengaturan +page.tags=preference,preferenceactivity,preferencefragment + +@jd:body + + +
+
+ +

Dalam dokumen ini

+
    +
  1. Ikhtisar +
      +
    1. Preferensi
    2. +
    +
  2. +
  3. Mendefinisikan Preferensi dalam XML +
      +
    1. Membuat grup pengaturan
    2. +
    3. Menggunakan intent
    4. +
    +
  4. +
  5. Membuat Aktivitas Preferensi
  6. +
  7. Menggunakan Fragmen Preferensi
  8. +
  9. Mengatur Nilai Default
  10. +
  11. Menggunakan Header Preferensi +
      +
    1. Membuat file header
    2. +
    3. Menampilkan header
    4. +
    5. Mendukung versi yang lebih lama dengan header preferensi
    6. +
    +
  12. +
  13. Preferensi Membaca +
      +
    1. Mendengarkan perubahan preferensi
    2. +
    +
  14. +
  15. Mengelola Penggunaan Jaringan
  16. +
  17. Membangun Preferensi Custom +
      +
    1. Menetapkan antarmuka pengguna
    2. +
    3. Menyimpan nilai pengaturan
    4. +
    5. Menginisialisasi nilai saat ini
    6. +
    7. Menyediakan nilai default
    8. +
    9. Menyimpan dan memulihkan status Preferensi
    10. +
    +
  18. +
+ +

Kelas-kelas utama

+
    +
  1. {@link android.preference.Preference}
  2. +
  3. {@link android.preference.PreferenceActivity}
  4. +
  5. {@link android.preference.PreferenceFragment}
  6. +
+ + +

Lihat juga

+
    +
  1. Panduan desain pengaturan
  2. +
+
+
+ + + + +

Aplikasi sering kali menyertakan pengaturan yang memungkinkan pengguna memodifikasi fitur dan perilaku aplikasi. Misalnya, +beberapa aplikasi memungkinkan pengguna untuk menetapkan apakah pemberitahuan diaktifkan atau menetapkan seberapa sering +aplikasi menyinkronkan data dengan cloud.

+ +

Jika ingin menyediakan pengaturan untuk aplikasi, Anda harus menggunakan +API Android {@link android.preference.Preference} untuk membangun antarmuka yang konsisten dengan +pengalaman pengguna di aplikasi Android yang lain (termasuk pengaturan sistem). Dokumen ini menjelaskan +cara membangun pengaturan aplikasi Anda menggunakan API {@link android.preference.Preference}.

+ +
+

Desain Pengaturan

+

Untuk informasi tentang cara mendesain pengaturan Anda, bacalah panduan desain Pengaturan.

+
+ + + +

Gambar 1. Cuplikan layar dari pengaturan +aplikasi Messaging Android. Memilih item yang didefinisikan oleh {@link android.preference.Preference} +akan membuka antarmuka untuk mengubah pengaturan.

+ + + + +

Ikhtisar

+ +

Sebagai ganti menggunakan objek {@link android.view.View} untuk membangun antarmuka pengguna, pengaturan +dibangun menggunakan berbagai subkelas dari kelas {@link android.preference.Preference} yang Anda +deklarasikan dalam file XML.

+ +

Objek {@link android.preference.Preference} adalah blok pembangun untuk pengaturan +tunggal. Setiap {@link android.preference.Preference} muncul sebagai item dalam daftar dan menyediakan UI +yang sesuai bagi pengguna untuk memodifikasi pengaturan. Misalnya, {@link +android.preference.CheckBoxPreference} membuat item daftar yang menampilkan kotak cek, dan {@link +android.preference.ListPreference} membuat item yang membuka dialog berisi daftar pilihan.

+ +

Setiap {@link android.preference.Preference} yang Anda tambahkan memiliki pasangan nilai-kunci yang sesuai yang +digunakan sistem untuk menyimpan pengaturan dalam file {@link android.content.SharedPreferences} +default untuk pengaturan aplikasi Anda. Bila pengguna mengubah pengaturan, sistem akan memperbarui nilai +yang bersangkutan dalam file {@link android.content.SharedPreferences} untuk Anda. Satu-satunya saat di mana Anda harus +berinteraksi langsung dengan file {@link android.content.SharedPreferences} yang terkait adalah bila Anda +perlu membaca nilai untuk menentukan perilaku aplikasi berdasarkan pengaturan pengguna.

+ +

Nilai yang tersimpan di {@link android.content.SharedPreferences} untuk setiap pengaturan bisa berupa +tipe data berikut:

+ + + +

Oleh karena UI pengaturan aplikasi Anda dibangun menggunakan objek {@link android.preference.Preference} +sebagai ganti +objek {@link android.view.View}, Anda perlu menggunakan {@link android.app.Activity} khusus atau +subkelas {@link android.app.Fragment} untuk menampilkan pengaturan daftar:

+ + + +

Cara mengatur {@link android.preference.PreferenceActivity} Anda dan instance {@link +android.preference.PreferenceFragment} dibahas di bagian tentang Membuat Aktivitas Preferensi dan Menggunakan +Fragmen Preferensi.

+ + +

Preferensi

+ +

Setiap pengaturan untuk aplikasi Anda diwakili oleh subkelas khusus dari kelas {@link +android.preference.Preference}. Setiap subkelas menyertakan seperangkat properti utama yang memungkinkan Anda +untuk menetapkan berbagai hal seperti judul pengaturan dan nilai default. Setiap subkelas juga menyediakan +antarmuka pengguna dan properti khusus miliknya sendiri. Misalnya, gambar 1 menampilkan cuplikan layar dari + pengaturan aplikasi Messaging. Setiap item daftar dalam layar pengaturan didukung oleh objek {@link +android.preference.Preference} berbeda.

+ +

Beberapa preferensi yang paling umum adalah:

+ +
+
{@link android.preference.CheckBoxPreference}
+
Menampilkan item dengan kotak cek untuk pengaturan yang diaktifkan atau dinonaktifkan. Nilai +tersimpan adalah boolean (true jika diberi tanda cek).
+ +
{@link android.preference.ListPreference}
+
Membuka dialog berisi daftar tombol radio. Nilai +tersimpan bisa berupa tipe nilai apa pun yang didukung (tercantum di atas).
+ +
{@link android.preference.EditTextPreference}
+
Membuka dialog berisi widget {@link android.widget.EditText}. Nilai tersimpan adalah {@link +java.lang.String}.
+
+ +

Lihat kelas {@link android.preference.Preference} untuk mengetahui daftar subkelas lain dan +propertinya.

+ +

Tentu saja, kelas bawaan tidak mengakomodasi setiap kebutuhan dan aplikasi Anda mungkin memerlukan +sesuatu yang lebih khusus. Misalnya, platform saat ini tidak menyediakan kelas {@link +android.preference.Preference} untuk mengambil nomor atau tanggal. Anda mungkin perlu mendefinisikan +subkelas {@link android.preference.Preference} sendiri. Untuk bantuan melakukannya, lihat bagian tentang Membangun Preferensi Custom.

+ + + +

Mendefinisikan Preferensi dalam XML

+ +

Meskipun bisa membuat instance objek {@link android.preference.Preference} baru saat runtime, Anda +harus mendefinisikan daftar pengaturan dalam XML dengan hierarki objek +{@link android.preference.Preference}. Menggunakan file XML untuk mendefinisikan sekumpulan pengaturan lebih disukai karena file +menyediakan struktur yang mudah dibaca dan diperbarui. Selain itu, pengaturan aplikasi Anda +umumnya telah ditetapkan sebelumnya, meskipun Anda masih bisa memodifikasi kumpulan tersebut saat runtime.

+ +

Setiap subkelas {@link android.preference.Preference} bisa dideklarasikan dengan elemen XML yang +cocok dengan nama kelas, seperti {@code <CheckBoxPreference>}.

+ +

Anda harus menyimpan file XML dalam direktori {@code res/xml/}. Meskipun bisa memberi nama file +sesuka Anda, biasanya file diberi nama {@code preferences.xml}. Biasanya Anda hanya memerlukan satu file, +karena cabang di hierarki (yang membuka daftar pengaturanny sendiri) dideklarasikan menggunakan instance +tersarang {@link android.preference.PreferenceScreen}.

+ +

Catatan: Jika ingin membuat layout multipanel untuk +pengaturan, Anda memerlukan file XML terpisah untuk setiap fragmen.

+ +

Simpul akar untuk file XML harus merupakan elemen {@link android.preference.PreferenceScreen +<PreferenceScreen>}. Dalam elemen inilah tempat Anda menambahkan setiap {@link +android.preference.Preference}. Setiap anak yang Anda tambahkan dalam elemen +{@link android.preference.PreferenceScreen <PreferenceScreen>} akan tampak sebagai item +tunggal dalam daftar pengaturan.

+ +

Misalnya:

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <CheckBoxPreference
+        android:key="pref_sync"
+        android:title="@string/pref_sync"
+        android:summary="@string/pref_sync_summ"
+        android:defaultValue="true" />
+    <ListPreference
+        android:dependency="pref_sync"
+        android:key="pref_syncConnectionType"
+        android:title="@string/pref_syncConnectionType"
+        android:dialogTitle="@string/pref_syncConnectionType"
+        android:entries="@array/pref_syncConnectionTypes_entries"
+        android:entryValues="@array/pref_syncConnectionTypes_values"
+        android:defaultValue="@string/pref_syncConnectionTypes_default" />
+</PreferenceScreen>
+
+ +

Dalam contoh ini, terdapat {@link android.preference.CheckBoxPreference} dan {@link +android.preference.ListPreference}. Kedua item tersebut menyertakan tiga atribut berikut:

+ +
+
{@code android:key}
+
Atribut ini diperlukan untuk preferensi yang mempertahankan nilai data. Ini menetapkan kunci +unik (string) yang digunakan sistem saat menyimpan nilai pengaturan ini dalam {@link +android.content.SharedPreferences}. +

Instance satu-satunya di mana atribut ini tidak diperlukan adalah bila preferensi berupa +{@link android.preference.PreferenceCategory} atau {@link android.preference.PreferenceScreen}, atau +preferensi menetapkan {@link android.content.Intent} untuk dipanggil (dengan elemen {@code <intent>}) atau {@link android.app.Fragment} untuk ditampilkan (dengan atribut {@code +android:fragment}).

+
+
{@code android:title}
+
Ini menyediakan nama pengaturan yang bisa dilihat oleh pengguna.
+
{@code android:defaultValue}
+
Ini menetapkan nilai awal yang harus diatur sistem dalam file {@link +android.content.SharedPreferences}. Anda harus memberikan nilai default untuk semua +pengaturan.
+
+ +

Untuk informasi tentang semua atribut lain yang didukung, lihat dokumentasi {@link +android.preference.Preference} (dan subkelas masing-masing).

+ + +
+ +

Gambar 2. Mengatur kategori + dengan judul.
1. Kategori ditetapkan oleh elemen {@link +android.preference.PreferenceCategory <PreferenceCategory>}.
2. Judul +ditetapkan dengan atribut {@code android:title}.

+
+ + +

Bila daftar pengaturan Anda melebihi sekitar 10 item, Anda mungkin perlu menambahkan judul untuk +mendefinisikan grup pengaturan atau menampilkan grup tersebut di +layar terpisah. Opsi ini dijelaskan di bagian berikut.

+ + +

Membuat grup pengaturan

+ +

Jika Anda menampilkan daftar 10 pengaturan atau lebih, pengguna +mungkin akan kesulitan dalam memindai, memahami dan memprosesnya. Anda bisa mengatasinya dengan +membagi sebagian atau semua pengaturan ke dalam beberapa grup, yang secara efektif akan mengubah satu daftar panjang menjadi beberapa daftar +yang lebih pendek. Suatu grup pengaturan terkait bisa ditampilkan dalam salah satu dari dua cara:

+ + + +

Anda bisa menggunakan salah satu atau keduanya untuk mengelola pengaturan aplikasi Anda. Saat +memutuskan mana yang akan digunakan dan cara membagi pengaturan, Anda harus mengikuti pedoman dalam +Panduan Pengaturan Desain Android.

+ + +

Menggunakan judul

+ +

Jika ingin menyediakan divider dengan heading di antara grup pengaturan (seperti yang ditampilkan dalam gambar 2), +tempatkan setiap grup objek {@link android.preference.Preference} di dalam {@link +android.preference.PreferenceCategory}.

+ +

Misalnya:

+ +
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <PreferenceCategory 
+        android:title="@string/pref_sms_storage_title"
+        android:key="pref_key_storage_settings">
+        <CheckBoxPreference
+            android:key="pref_key_auto_delete"
+            android:summary="@string/pref_summary_auto_delete"
+            android:title="@string/pref_title_auto_delete"
+            android:defaultValue="false"... />
+        <Preference 
+            android:key="pref_key_sms_delete_limit"
+            android:dependency="pref_key_auto_delete"
+            android:summary="@string/pref_summary_delete_limit"
+            android:title="@string/pref_title_sms_delete"... />
+        <Preference 
+            android:key="pref_key_mms_delete_limit"
+            android:dependency="pref_key_auto_delete"
+            android:summary="@string/pref_summary_delete_limit"
+            android:title="@string/pref_title_mms_delete" ... />
+    </PreferenceCategory>
+    ...
+</PreferenceScreen>
+
+ + +

Menggunakan sublayar

+ +

Jika ingin menempatkan grup pengaturan ke dalam sublayar (seperti yang ditampilkan dalam gambar 3), tempatkan grup +objek {@link android.preference.Preference} di dalam {@link +android.preference.PreferenceScreen}.

+ + +

Gambar 3. Mengatur sublayar. Elemen {@code +<PreferenceScreen>} +membuat item yang, bila dipilih, akan membuka daftar terpisah untuk menampilkan pengaturan tersarang.

+ +

Misalnya:

+ +
+<PreferenceScreen  xmlns:android="http://schemas.android.com/apk/res/android">
+    <!-- opens a subscreen of settings -->
+    <PreferenceScreen
+        android:key="button_voicemail_category_key"
+        android:title="@string/voicemail"
+        android:persistent="false">
+        <ListPreference
+            android:key="button_voicemail_provider_key"
+            android:title="@string/voicemail_provider" ... />
+        <!-- opens another nested subscreen -->
+        <PreferenceScreen
+            android:key="button_voicemail_setting_key"
+            android:title="@string/voicemail_settings"
+            android:persistent="false">
+            ...
+        </PreferenceScreen>
+        <RingtonePreference
+            android:key="button_voicemail_ringtone_key"
+            android:title="@string/voicemail_ringtone_title"
+            android:ringtoneType="notification" ... />
+        ...
+    </PreferenceScreen>
+    ...
+</PreferenceScreen>
+
+ + +

Menggunakan intent

+ +

Dalam beberapa kasus, Anda mungkin ingin item preferensi untuk membuka beberapa aktivitas sebagai ganti +layar pengaturan, seperti browser web untuk melihat halaman web. Untuk memanggil {@link +android.content.Intent} saat pengguna memilih item preferensi, tambahkan elemen {@code <intent>} +sebagai anak dari elemen {@code <Preference>} yang bersangkutan.

+ +

Misalnya, berikut ini cara menggunakan item preferensi untuk membuka halaman web:

+ +
+<Preference android:title="@string/prefs_web_page" >
+    <intent android:action="android.intent.action.VIEW"
+            android:data="http://www.example.com" />
+</Preference>
+
+ +

Anda bisa membuat intent implisit maupun eksplisit menggunakan atribut berikut:

+ +
+
{@code android:action}
+
Tindakan yang akan ditetapkan, sesuai metode +{@link android.content.Intent#setAction setAction()}.
+
{@code android:data}
+
Data yang akan ditetapkan, sesuai metode {@link android.content.Intent#setData setData()}.
+
{@code android:mimeType}
+
Tipe MIME yang akan ditetapkan, sesuai metode +{@link android.content.Intent#setType setType()}.
+
{@code android:targetClass}
+
Bagian kelas dari nama komponen, sesuai metode {@link android.content.Intent#setComponent +setComponent()}.
+
{@code android:targetPackage}
+
Bagian paket dari nama komponen, sesuai metode {@link +android.content.Intent#setComponent setComponent()}.
+
+ + + +

Membuat Aktivitas Preferensi

+ +

Untuk menampilkan pengaturan Anda dalam suatu aktivitas, perluas kelas {@link +android.preference.PreferenceActivity}. Ini adalah ekstensi dari kelas {@link +android.app.Activity} biasa yang menampilkan daftar pengaturan berdasarkan hierarki objek {@link +android.preference.Preference}. {@link android.preference.PreferenceActivity} +secara otomatis mempertahankan pengaturan yang dikaitkan dengan setiap {@link +android.preference.Preference} bila pengguna membuat perubahan.

+ +

Catatan: Jika Anda mengembangkan aplikasi untuk Android 3.0 dan +yang lebih tinggi, sebaiknya gunakan {@link android.preference.PreferenceFragment}. Pindah ke bagian +berikutnya tentang Menggunakan Fragmen Preferensi.

+ +

Hal paling penting untuk diingat adalah jangan memuat layout tampilan selama callback {@link +android.preference.PreferenceActivity#onCreate onCreate()}. Sebagai gantinya, panggil {@link +android.preference.PreferenceActivity#addPreferencesFromResource addPreferencesFromResource()} untuk +menambahkan preferensi yang telah Anda deklarasikan dalam file XML ke aktivitas. Misalnya, berikut ini adalah kode minimum +polos yang diperlukan untuk {@link android.preference.PreferenceActivity} fungsional:

+ +
+public class SettingsActivity extends PreferenceActivity {
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        addPreferencesFromResource(R.xml.preferences);
+    }
+}
+
+ +

Ini sebenarnya kode yang cukup untuk beberapa aplikasi, karena segera setelah pengguna memodifikasi preferensi, +sistem akan menyimpan perubahan tersebut ke file {@link android.content.SharedPreferences} default yang +bisa dibaca oleh komponen aplikasi Anda lainnya bila Anda perlu memeriksa pengaturan pengguna. Akan tetapi, +banyak aplikasi, yang memerlukan kode lebih sedikit untuk mendengarkan perubahan yang terjadi pada preferensi. +Untuk informasi tentang mendengarkan perubahan di file {@link android.content.SharedPreferences}, +lihat bagian tentang Preferensi Membaca.

+ + + + +

Menggunakan Fragmen Preferensi

+ +

Jika Anda mengembangkan Android 3.0 (API level 11) dan yang lebih tinggi, Anda harus menggunakan {@link +android.preference.PreferenceFragment} untuk menampilkan daftar objek {@link android.preference.Preference} +Anda. Anda bisa menambahkan {@link android.preference.PreferenceFragment} ke aktivitas apa pun,—Anda tidak +perlu menggunakan {@link android.preference.PreferenceActivity}.

+ +

Fragmen menyediakan arsitektur yang lebih +fleksibel untuk aplikasi Anda, dibandingkan hanya menggunakan aktivitas, apa pun jenis +aktivitas yang Anda bangun. Dengan sendirinya, kami menyarankan Anda menggunakan {@link +android.preference.PreferenceFragment} untuk mengontrol tampilan pengaturan Anda sebagai ganti {@link +android.preference.PreferenceActivity} bila memungkinkan.

+ +

Implementasi {@link android.preference.PreferenceFragment} Anda bisa semudah +mendefinisikan metode {@link android.preference.PreferenceFragment#onCreate onCreate()} untuk memuat +file preferensi dengan {@link android.preference.PreferenceFragment#addPreferencesFromResource +addPreferencesFromResource()}. Misalnya:

+ +
+public static class SettingsFragment extends PreferenceFragment {
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        // Load the preferences from an XML resource
+        addPreferencesFromResource(R.xml.preferences);
+    }
+    ...
+}
+
+ +

Anda nanti bisa menambahkan fragmen ini ke {@link android.app.Activity} seperti yang Anda lakukan untuk +{@link android.app.Fragment} lainnya. Misalnya:

+ +
+public class SettingsActivity extends Activity {
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        // Display the fragment as the main content.
+        getFragmentManager().beginTransaction()
+                .replace(android.R.id.content, new SettingsFragment())
+                .commit();
+    }
+}
+
+ +

Catatan: {@link android.preference.PreferenceFragment} tidak memiliki +objek {@link android.content.Context} sendiri. Jika memerlukan objek {@link android.content.Context} +, Anda bisa memanggil {@link android.app.Fragment#getActivity()}. Akan tetapi, berhati-hatilah untuk memanggil +{@link android.app.Fragment#getActivity()} hanya bila fragmen telah dikaitkan dengan aktivitas. Bila +fragmen belum dikaitkan, atau terlepas saat akhir daur hidupnya, {@link +android.app.Fragment#getActivity()} akan mengembalikan nol.

+ + +

Mengatur Nilai Default

+ +

Preferensi yang Anda buat mungkin mendefinisikan beberapa perilaku penting untuk aplikasi, jadi Anda +perlu menginisialisasi file {@link android.content.SharedPreferences} yang terkait dengan +nilai default untuk setiap {@link android.preference.Preference} bila pengguna menggunakan aplikasi +Anda untuk pertama kali.

+ +

Hal pertama yang harus Anda lakukan adalah menetapkan nilai default untuk setiap objek {@link +android.preference.Preference} +di file XML Anda menggunakan atribut {@code android:defaultValue}. Nilainya bisa berupa tipe data +apa saja yang sesuai untuk objek {@link android.preference.Preference} bersangkutan. Misalnya: +

+ +
+<!-- default value is a boolean -->
+<CheckBoxPreference
+    android:defaultValue="true"
+    ... />
+
+<!-- default value is a string -->
+<ListPreference
+    android:defaultValue="@string/pref_syncConnectionTypes_default"
+    ... />
+
+ +

Kemudian, dari metode {@link android.app.Activity#onCreate onCreate()} dalam aktivitas utama aplikasi +Anda—dan dalam aktivitas lainnya yang digunakan pengguna untuk masuk ke aplikasi Anda untuk pertama kali +—panggil {@link android.preference.PreferenceManager#setDefaultValues +setDefaultValues()}:

+ +
+PreferenceManager.setDefaultValues(this, R.xml.advanced_preferences, false);
+
+ +

Memanggil ini selama {@link android.app.Activity#onCreate onCreate()} akan memastikan aplikasi +Anda diinisialisasi dengan pengaturan default, yang mungkin perlu +dibaca oleh aplikasi Anda untuk menentukan beberapa perilaku (seperti apakah akan mengunduh data pada +jaringan seluler).

+ +

Metode ini membutuhkan tiga argumen:

+ + +

Selama Anda mengatur argumen ketiga ke false, Anda bisa dengan aman memanggil metode ini +setiap kali aktivitas Anda memulai tanpa mengesampingkan preferensi tersimpan pengguna dengan mengatur ulang preferensi tersebut ke +default. Akan tetapi, jika mengatur ke true, Anda akan mengesampingkan nilai +sebelumnya dengan default.

+ + + +

Menggunakan Header Preferensi

+ +

Dalam kasus yang jarang terjadi, Anda mungkin perlu mendesain pengaturan agar layar pertama +hanya menampilkan daftar sublayar (seperti dalam aplikasi Setting pada sistem, +seperti yang ditampilkan dalam gambar 4 dan 5). Bila mengembangkan desain seperti itu untuk Android 3.0 dan yang lebih tinggi, Anda +harus menggunakan fitur "header" yang baru di Android 3.0, sebagai ganti membangun sublayar dengan elemen +{@link android.preference.PreferenceScreen} tersarang.

+ +

Untuk membangun pengaturan dengan header, Anda perlu:

+
    +
  1. Memisahkan setiap grup pengaturan ke dalam instance {@link +android.preference.PreferenceFragment} terpisah. Ini berarti, setiap grup pengaturan memerlukan file XML +terpisah.
  2. +
  3. Membuat file header XML yang mencantumkan daftar setiap grup pengaturan dan mendeklarasikan fragmen mana +yang berisi daftar pengaturan yang sesuai.
  4. +
  5. Memperluas kelas {@link android.preference.PreferenceActivity} untuk menjadi host pengaturan Anda.
  6. +
  7. Mengimplementasikan callback {@link +android.preference.PreferenceActivity#onBuildHeaders onBuildHeaders()} untuk menetapkan file +header.
  8. +
+ +

Manfaat besar dalam menggunakan desain ini adalah karena {@link android.preference.PreferenceActivity} +secara otomatis akan menampilkan layout dua panel yang ditampilkan dalam gambar 4 bila dijalankan pada layar besar.

+ +

Bahkan jika aplikasi Anda mendukung versi Android yang lebih lama dari 3.0, Anda bisa membangun +aplikasi untuk menggunakan {@link android.preference.PreferenceFragment} bagi presentasi dua panel pada perangkat +yang lebih baru sementara tetap mendukung hierarki multilayar biasa pada perangkat +yang lebih lama (lihat bagian tentang Mendukung versi yang lebih lama dengan +header preferensi).

+ + +

Gambar 4. Layout dua panel dengan header.
1. Header +didefinisikan dengan file header XML.
2. Setiap grup pengaturan didefinisikan dengan +{@link android.preference.PreferenceFragment} yang ditetapkan oleh elemen {@code <header>} dalam +file header.

+ + +

Gambar 5. Perangkat handset dengan header pengaturan. Bila sebuah +item dipilih, {@link android.preference.PreferenceFragment} terkait akan menggantikan +header.

+ + +

Membuat file header

+ +

Setiap grup pengaturan dalam daftar header Anda akan ditetapkan oleh elemen {@code <header>} +tunggal dalam elemen {@code <preference-headers>} akar. Misalnya:

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<preference-headers xmlns:android="http://schemas.android.com/apk/res/android">
+    <header 
+        android:fragment="com.example.prefs.SettingsActivity$SettingsFragmentOne"
+        android:title="@string/prefs_category_one"
+        android:summary="@string/prefs_summ_category_one" />
+    <header 
+        android:fragment="com.example.prefs.SettingsActivity$SettingsFragmentTwo"
+        android:title="@string/prefs_category_two"
+        android:summary="@string/prefs_summ_category_two" >
+        <!-- key/value pairs can be included as arguments for the fragment. -->
+        <extra android:name="someKey" android:value="someHeaderValue" />
+    </header>
+</preference-headers>
+
+ +

Dengan atribut {@code android:fragment}, setiap header mendeklarasikan instance {@link +android.preference.PreferenceFragment} yang harus terbuka saat pengguna memilih header.

+ +

Elemen {@code <extras>} memungkinkan Anda meneruskan pasangan nilai-kunci ke fragmen di {@link +android.os.Bundle}. Fragmen bisa mengambil argumen dengan memanggil {@link +android.app.Fragment#getArguments()}. Anda bisa meneruskan argumen ke fragmen dengan berbagai +alasan, namun satu alasan yang baik adalah untuk menggunakan kembali subkelas yang sama dari {@link +android.preference.PreferenceFragment} untuk setiap grup dan menggunakan argumen untuk menetapkan file +XML preferensi mana yang harus dimuat fragmen.

+ +

Misalnya, ada fragmen yang bisa digunakan ulang untuk berbagai grup pengaturan, bila setiap +header mendefinisikan argumen {@code <extra>} dengan kunci {@code "settings"}:

+ +
+public static class SettingsFragment extends PreferenceFragment {
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        String settings = getArguments().getString("settings");
+        if ("notifications".equals(settings)) {
+            addPreferencesFromResource(R.xml.settings_wifi);
+        } else if ("sync".equals(settings)) {
+            addPreferencesFromResource(R.xml.settings_sync);
+        }
+    }
+}
+
+ + + +

Menampilkan header

+ +

Untuk menampilkan header preferensi, Anda harus mengimplementasikan metode callback {@link +android.preference.PreferenceActivity#onBuildHeaders onBuildHeaders()} dan memanggil +{@link android.preference.PreferenceActivity#loadHeadersFromResource +loadHeadersFromResource()}. Misalnya:

+ +
+public class SettingsActivity extends PreferenceActivity {
+    @Override
+    public void onBuildHeaders(List<Header> target) {
+        loadHeadersFromResource(R.xml.preference_headers, target);
+    }
+}
+
+ +

Bila pengguna memilih item dari daftar header, sistem akan membuka {@link +android.preference.PreferenceFragment} terkait.

+ +

Catatan: Saat menggunakan header preferensi, subkelas {@link +android.preference.PreferenceActivity} Anda tidak perlu mengimplementasikan metode {@link +android.preference.PreferenceActivity#onCreate onCreate()}, karena tugas +yang diperlukan untuk aktivitas hanyalah memuat header.

+ + +

Mendukung versi yang lebih lama dengan header preferensi

+ +

Jika aplikasi Anda mendukung versi Android yang lebih lama dari 3.0, Anda tetap bisa menggunakan header untuk +menyediakan layout dua panel saat berjalan pada Android 3.0 dan yang lebih tinggi. Anda hanya perlu membuat +file XML preferensi tambahan yang menggunakan elemen {@link android.preference.Preference +<Preference>} dasar yang berperilaku seperti item header (untuk digunakan oleh Android +versi yang lebih lama).

+ +

Akan tetapi, sebagai ganti membuka {@link android.preference.PreferenceScreen} baru, setiap elemen {@link +android.preference.Preference <Preference>} mengirimkan {@link android.content.Intent} ke +{@link android.preference.PreferenceActivity} yang menetapkan file XML preferensi mana yang +akan dimuat.

+ +

Misalnya, ini adalah file XML untuk header preferensi yang menggunakan Android 3.0 +dan yang lebih tinggi ({@code res/xml/preference_headers.xml}):

+ +
+<preference-headers xmlns:android="http://schemas.android.com/apk/res/android">
+    <header 
+        android:fragment="com.example.prefs.SettingsFragmentOne"
+        android:title="@string/prefs_category_one"
+        android:summary="@string/prefs_summ_category_one" />
+    <header 
+        android:fragment="com.example.prefs.SettingsFragmentTwo"
+        android:title="@string/prefs_category_two"
+        android:summary="@string/prefs_summ_category_two" />
+</preference-headers>
+
+ +

Dan ini adalah file preferensi yang menyediakan header yang sama untuk versi yang lebih lama dari +Android 3.0 ({@code res/xml/preference_headers_legacy.xml}):

+ +
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <Preference 
+        android:title="@string/prefs_category_one"
+        android:summary="@string/prefs_summ_category_one"  >
+        <intent 
+            android:targetPackage="com.example.prefs"
+            android:targetClass="com.example.prefs.SettingsActivity"
+            android:action="com.example.prefs.PREFS_ONE" />
+    </Preference>
+    <Preference 
+        android:title="@string/prefs_category_two"
+        android:summary="@string/prefs_summ_category_two" >
+        <intent 
+            android:targetPackage="com.example.prefs"
+            android:targetClass="com.example.prefs.SettingsActivity"
+            android:action="com.example.prefs.PREFS_TWO" />
+    </Preference>
+</PreferenceScreen>
+
+ +

Karena dukungan untuk {@code <preference-headers>} telah ditambahkan di Android 3.0, sistem akan memanggil +{@link android.preference.PreferenceActivity#onBuildHeaders onBuildHeaders()} di {@link +android.preference.PreferenceActivity} hanya saat berjalan pada Android 3.0 atau yang lebih tinggi. Untuk memuat +file header "lama" ({@code preference_headers_legacy.xml}), Anda harus memeriksa versi Android +dan, jika versi tersebut lebih lama dari Android 3.0 ({@link +android.os.Build.VERSION_CODES#HONEYCOMB}), panggil {@link +android.preference.PreferenceActivity#addPreferencesFromResource addPreferencesFromResource()} untuk +memuat file header lama. Misalnya:

+ +
+@Override
+public void onCreate(Bundle savedInstanceState) {
+    super.onCreate(savedInstanceState);
+    ...
+
+    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
+        // Load the legacy preferences headers
+        addPreferencesFromResource(R.xml.preference_headers_legacy);
+    }
+}
+
+// Called only on Honeycomb and later
+@Override
+public void onBuildHeaders(List<Header> target) {
+   loadHeadersFromResource(R.xml.preference_headers, target);
+}
+
+ +

Satu-satunya hal yang perlu dilakukan adalah menangani {@link android.content.Intent} yang diteruskan ke +aktivitas untuk mengidentifikasi file preferensi yang akan dimuat. Jadi ambillah tindakan intent dan bandingkan dengan +string tindakan yang diketahui yang telah Anda gunakan dalam tag {@code <intent>} XML preferensi:

+ +
+final static String ACTION_PREFS_ONE = "com.example.prefs.PREFS_ONE";
+...
+
+@Override
+public void onCreate(Bundle savedInstanceState) {
+    super.onCreate(savedInstanceState);
+
+    String action = getIntent().getAction();
+    if (action != null && action.equals(ACTION_PREFS_ONE)) {
+        addPreferencesFromResource(R.xml.preferences);
+    }
+    ...
+
+    else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
+        // Load the legacy preferences headers
+        addPreferencesFromResource(R.xml.preference_headers_legacy);
+    }
+}
+
+ +

Ketahuilah bahwa panggilan berturut-turut ke {@link +android.preference.PreferenceActivity#addPreferencesFromResource addPreferencesFromResource()} akan +menumpuk semua preferensi ke dalam satu daftar, jadi pastikan bahwa ini hanya dipanggil sekali dengan mengikatkan syarat +ke pernyataan else-if.

+ + + + + +

Preferensi Membaca

+ +

Secara default, semua preferensi aplikasi Anda disimpan ke file yang bisa diakses dari mana saja +di dalam aplikasi dengan memanggil metode statis {@link +android.preference.PreferenceManager#getDefaultSharedPreferences +PreferenceManager.getDefaultSharedPreferences()}. Ini akan mengembalikan objek {@link +android.content.SharedPreferences} berisi semua pasangan nilai-kunci yang terkait +dengan objek {@link android.preference.Preference} yang digunakan di {@link +android.preference.PreferenceActivity} Anda.

+ +

Misalnya, inilah cara membaca salah satu nilai preferensi dari aktivitas lain dalam aplikasi +Anda:

+ +
+SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this);
+String syncConnPref = sharedPref.getString(SettingsActivity.KEY_PREF_SYNC_CONN, "");
+
+ + + +

Mendengarkan perubahan preferensi

+ +

Ada beberapa alasan yang membuat Anda perlu mendapatkan pemberitahuan segera setelah pengguna mengubah salah satu +preferensi. Untuk menerima callback saat perubahan terjadi pada salah satu preferensi, +implementasikan antarmuka {@link android.content.SharedPreferences.OnSharedPreferenceChangeListener +SharedPreference.OnSharedPreferenceChangeListener} dan daftarkan listener untuk objek +{@link android.content.SharedPreferences} dengan memanggil {@link +android.content.SharedPreferences#registerOnSharedPreferenceChangeListener +registerOnSharedPreferenceChangeListener()}.

+ +

Antarmuka hanya memiliki satu metode callback, {@link +android.content.SharedPreferences.OnSharedPreferenceChangeListener#onSharedPreferenceChanged +onSharedPreferenceChanged()}, dan mungkin lebih mudah mengimplementasikan antarmuka sebagai bagian dari +aktivitas Anda. Misalnya:

+ +
+public class SettingsActivity extends PreferenceActivity
+                              implements OnSharedPreferenceChangeListener {
+    public static final String KEY_PREF_SYNC_CONN = "pref_syncConnectionType";
+    ...
+
+    public void onSharedPreferenceChanged(SharedPreferences sharedPreferences,
+        String key) {
+        if (key.equals(KEY_PREF_SYNC_CONN)) {
+            Preference connectionPref = findPreference(key);
+            // Set summary to be the user-description for the selected value
+            connectionPref.setSummary(sharedPreferences.getString(key, ""));
+        }
+    }
+}
+
+ +

Dalam contoh ini, metode akan memeriksa apakah pengaturan yang diubah adalah untuk kunci preferensi yang diketahui. Ini akan +memanggil {@link android.preference.PreferenceActivity#findPreference findPreference()} untuk mendapatkan objek +{@link android.preference.Preference} yang diubah agar bisa memodifikasi rangkuman item +menjadi keterangan pada pilihan pengguna. Ini berarti, bila pengaturan adalah {@link +android.preference.ListPreference} atau pengaturan multipilihan, Anda harus memanggil {@link +android.preference.Preference#setSummary setSummary()} bila pengaturan berubah ke tampilkan +status saat ini (seperti pengaturan Sleep yang ditampilkan dalam gambar 5).

+ +

Catatan: Seperti dijelaskan dalam dokumen Desain Android tentang Pengaturan, kami merekomendasikan Anda untuk memperbarui +rangkuman {@link android.preference.ListPreference} setiap kali pengguna mengubah preferensi untuk +menjelaskan pengaturan saat ini.

+ +

Untuk manajemen daur hidup yang baik di aktivitas, kami merekomendasikan Anda untuk mendaftarkan dan mencabut pendaftaran +{@link android.content.SharedPreferences.OnSharedPreferenceChangeListener} selama callback {@link +android.app.Activity#onResume} dan {@link android.app.Activity#onPause}:

+ +
+@Override
+protected void onResume() {
+    super.onResume();
+    getPreferenceScreen().getSharedPreferences()
+            .registerOnSharedPreferenceChangeListener(this);
+}
+
+@Override
+protected void onPause() {
+    super.onPause();
+    getPreferenceScreen().getSharedPreferences()
+            .unregisterOnSharedPreferenceChangeListener(this);
+}
+
+ +

Perhatian: Bila Anda memanggil {@link +android.content.SharedPreferences#registerOnSharedPreferenceChangeListener +registerOnSharedPreferenceChangeListener()}, pengelola preferensi saat ini tidak akan +menyimpan referensi kuat ke listener. Anda harus menyimpan referensi +kuat bagi listener, atau referensi akan rentan terhadap pengumpulan sampah. Kami +merekomendasikan Anda untuk mempertahankan referensi bagi listener dalam data instance objek +yang akan ada selama Anda memerlukan listener tersebut.

+ +

Misalnya, dalam kode berikut, caller tidak menyimpan referensi ke +listener. Akibatnya, listener akan dikenakan pengumpulan sampah, +dan suatu saat nanti akan gagal:

+ +
+prefs.registerOnSharedPreferenceChangeListener(
+  // Bad! The listener is subject to garbage collection!
+  new SharedPreferences.OnSharedPreferenceChangeListener() {
+  public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
+    // listener implementation
+  }
+});
+
+ +

Sebagai gantinya, simpan referensi ke listener dalam bidang data instance +objek yang akan ada selama listener dibutuhkan:

+ +
+SharedPreferences.OnSharedPreferenceChangeListener listener =
+    new SharedPreferences.OnSharedPreferenceChangeListener() {
+  public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
+    // listener implementation
+  }
+};
+prefs.registerOnSharedPreferenceChangeListener(listener);
+
+ +

Mengelola Penggunaan Jaringan

+ + +

Mulai Android 4.0, aplikasi Settings untuk sistem memungkinkan pengguna melihat seberapa besar +data jaringan yang digunakan aplikasi mereka saat berada di latar depan dan latar belakang. Kemudian pengguna bisa +menonaktifkan penggunaan data latar belakang untuk aplikasi individual. Agar pengguna tidak menonaktifkan akses +aplikasi ke data dari latar belakang, Anda harus menggunakan koneksi data secara efisien dan mengizinkan +pengguna untuk menyaring penggunaan data aplikasi melalui pengaturan aplikasi Anda.

+ +

Misalnya, Anda bisa mengizinkan pengguna untuk mengontrol seberapa sering aplikasi menyinkronkan data, apakah aplikasi +hanya melakukan pengunggahan/pengunduhan bila ada Wi-Fi, apakah aplikasi menggunakan data saat roaming, dll. Dengan +tersedianya kontrol ini bagi pengguna, mereka kemungkinan besar tidak akan menonaktifkan akses aplikasi ke data +saat mendekati batas yang mereka tetapkan dalam Settings pada sistem, karena mereka bisa mengontrol secara tepat +seberapa besar data yang digunakan aplikasi Anda.

+ +

Setelah menambahkan preferensi yang diperlukan dalam {@link android.preference.PreferenceActivity} Anda +untuk mengontrol kebiasaan data aplikasi, Anda harus menambahkan filter intent untuk {@link +android.content.Intent#ACTION_MANAGE_NETWORK_USAGE} dalam file manifes Anda. Misalnya:

+ +
+<activity android:name="SettingsActivity" ... >
+    <intent-filter>
+       <action android:name="android.intent.action.MANAGE_NETWORK_USAGE" />
+       <category android:name="android.intent.category.DEFAULT" />
+    </intent-filter>
+</activity>
+
+ +

Filter intent ini menunjukkan pada sistem bahwa ini adalah aktivitas yang mengontrol penggunaan +data aplikasi Anda. Jadi, saat pengguna memeriksa seberapa banyak data yang digunakan oleh aplikasi dari +aplikasi Settings pada sistem, tombol View application settings akan tersedia dan menjalankan +{@link android.preference.PreferenceActivity} sehingga pengguna bisa menyaring seberapa besar data yang digunakan +aplikasi Anda.

+ + + + + + + +

Membangun Preferensi Custom

+ +

Kerangka kerja Android menyertakan berbagai subkelas {@link android.preference.Preference} yang +memungkinkan Anda membangun UI untuk beberapa macam tipe pengaturan. +Akan tetapi, Anda mungkin menemukan pengaturan yang diperlukan bila tidak ada solusi bawaan, seperti +picker nomor atau picker tanggal. Dalam hal demikian, Anda akan perlu membuat preferensi custom dengan memperluas +kelas {@link android.preference.Preference} atau salah satu subkelas lainnya.

+ +

Bila memperluas kelas {@link android.preference.Preference}, ada beberapa hal +penting yang perlu Anda lakukan:

+ + + +

Bagian berikut menjelaskan cara melakukan setiap tugas ini.

+ + + +

Menetapkan antarmuka pengguna

+ +

Jika secara langsung memperluas kelas {@link android.preference.Preference}, Anda perlu mengimplementasikan +{@link android.preference.Preference#onClick()} untuk mendefinisikan tindakan yang terjadi bila pengguna +memilih item tersebut. Akan tetapi, sebagian besar pengaturan custom memperluas {@link android.preference.DialogPreference} untuk +menampilkan dialog, sehingga menyederhanakan prosedur. Bila memperluas {@link +android.preference.DialogPreference}, Anda harus memanggil {@link +android.preference.DialogPreference#setDialogLayoutResource setDialogLayoutResourcs()} selama di +konstruktor kelas untuk menetapkan layout dialog.

+ +

Misalnya, beri ini konstruktor untuk {@link +android.preference.DialogPreference} custom yang mendeklarasikan layout dan menetapkan teks untuk tombol dialog +negatif dan positif default:

+ +
+public class NumberPickerPreference extends DialogPreference {
+    public NumberPickerPreference(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        
+        setDialogLayoutResource(R.layout.numberpicker_dialog);
+        setPositiveButtonText(android.R.string.ok);
+        setNegativeButtonText(android.R.string.cancel);
+        
+        setDialogIcon(null);
+    }
+    ...
+}
+
+ + + +

Menyimpan nilai pengaturan

+ +

Anda bisa menyimpan nilai pengaturan kapan saja dengan memanggil salah satu metode {@code persist*()} kelas {@link +android.preference.Preference}, seperti {@link +android.preference.Preference#persistInt persistInt()} jika nilai pengaturan adalah integer atau +{@link android.preference.Preference#persistBoolean persistBoolean()} untuk menyimpan boolean.

+ +

Catatan: Setiap {@link android.preference.Preference} hanya bisa menyimpan satu +tipe data, jadi Anda harus menggunakan metode {@code persist*()} yang tepat untuk tipe data yang digunakan +oleh {@link android.preference.Preference} custom Anda.

+ +

Bila Anda memilih untuk mempertahankannya, pengaturan bisa bergantung pada kelas {@link +android.preference.Preference} yang Anda perluas. Jika Anda memperluas {@link +android.preference.DialogPreference}, maka Anda harus mempertahankan nilai hanya jika dialog +tertutup karena hasil positif (pengguna memilih tombol "OK").

+ +

Bila {@link android.preference.DialogPreference} tertutup, sistem akan memanggil metode {@link +android.preference.DialogPreference#onDialogClosed onDialogClosed()}. Metode mencakup argumen +boolean yang menetapkan apakah hasil pengguna "positif"—jika nilainya +true, maka pengguna memilih tombol positif dan Anda harus menyimpan nilai baru. Misalnya: +

+ +
+@Override
+protected void onDialogClosed(boolean positiveResult) {
+    // When the user selects "OK", persist the new value
+    if (positiveResult) {
+        persistInt(mNewValue);
+    }
+}
+
+ +

Dalam contoh ini, mNewValue adalah anggota kelas yang menampung nilai +pengaturan saat ini. Memanggil {@link android.preference.Preference#persistInt persistInt()} akan menyimpan nilai +ke file {@link android.content.SharedPreferences} (secara otomatis menggunakan kunci yang +ditetapkan dalam file XML untuk {@link android.preference.Preference} ini).

+ + +

Menginisialisasi nilai saat ini

+ +

Bila sistem menambahkan {@link android.preference.Preference} Anda ke layar, ia +akan memanggil {@link android.preference.Preference#onSetInitialValue onSetInitialValue()} untuk memberi tahu +Anda apakah pengaturan memiliki nilai yang dipertahankan. Jika tidak ada nilai yang dipertahankan, panggilan ini +akan menyediakan nilai default bagi Anda.

+ +

Metode {@link android.preference.Preference#onSetInitialValue onSetInitialValue()} akan meneruskan +boolean, restorePersistedValue, untuk menunjukkan apakah nilai dipertahankan +untuk pengaturan. Jika true, maka Anda harus mengambil nilai yang dipertahankan dengan memanggil +salah satu metode {@code getPersisted*()} kelas {@link +android.preference.Preference}, seperti {@link +android.preference.Preference#getPersistedInt getPersistedInt()} untuk nilai integer. Anda biasanya +perlu mengambil nilai yang dipertahankan agar bisa memperbarui UI dengan benar untuk merefleksikan +nilai yang tersimpan sebelumnya.

+ +

Jika restorePersistedValue adalah false, maka Anda +harus menggunakan nilai default yang diteruskan dalam argumen kedua.

+ +
+@Override
+protected void onSetInitialValue(boolean restorePersistedValue, Object defaultValue) {
+    if (restorePersistedValue) {
+        // Restore existing state
+        mCurrentValue = this.getPersistedInt(DEFAULT_VALUE);
+    } else {
+        // Set default state from the XML attribute
+        mCurrentValue = (Integer) defaultValue;
+        persistInt(mCurrentValue);
+    }
+}
+
+ +

Setiap metode {@code getPersisted*()} memerlukan argumen yang menetapkan +nilai default untuk digunakan jika tidak ada nilai yang dipertahankan atau kunci tidak ada. Dalam contoh +di atas, konstanta lokal yang digunakan untuk menetapkan nilai default dalam kasus {@link +android.preference.Preference#getPersistedInt getPersistedInt()} tidak bisa mengembalikan nilai yang dipertahankan.

+ +

Perhatian: Anda tidak bisa menggunakan +defaultValue sebagai nilai default dalam metode {@code getPersisted*()}, karena +nilainya selalu nol bila restorePersistedValue adalah true.

+ + +

Menyediakan nilai default

+ +

Jika instance kelas {@link android.preference.Preference} Anda menetapkan nilai default +(dengan atribut {@code android:defaultValue}), maka +sistem akan memanggil {@link android.preference.Preference#onGetDefaultValue +onGetDefaultValue()} bila membuat instance objek untuk mengambil nilai. Anda harus mengimplementasikan +metode ini agar sistem bisa menyimpan nilai default dalam {@link +android.content.SharedPreferences}. Misalnya:

+ +
+@Override
+protected Object onGetDefaultValue(TypedArray a, int index) {
+    return a.getInteger(index, DEFAULT_VALUE);
+}
+
+ +

Argumen metode menyediakan semua hal yang Anda perlukan: larik atribut dan posisi +indeks dari {@code android:defaultValue}, yang harus Anda ambil. Alasan Anda harus +mengimplementasikan metode ini untuk mengekstrak nilai default dari atribut adalah karena Anda harus menetapkan +nilai default lokal untuk atribut jika nilai tidak didefinisikan.

+ + + +

Menyimpan dan memulihkan status Preferensi

+ +

Seperti halnya {@link android.view.View} di layout, subkelas {@link android.preference.Preference} +Anda bertanggung jawab menyimpan dan memulihkan statusnya jika aktivitas atau fragmen +di-restart (seperti saat pengguna memutar layar). Untuk menyimpan +dan memulihkan status kelas {@link android.preference.Preference} dengan benar, Anda harus mengimplementasikan +metode callback daur hidup {@link android.preference.Preference#onSaveInstanceState +onSaveInstanceState()} dan {@link +android.preference.Preference#onRestoreInstanceState onRestoreInstanceState()}.

+ +

Status {@link android.preference.Preference} Anda didefinisikan oleh objek yang mengimplementasikan +antarmuka {@link android.os.Parcelable}. Kerangka kerja Android menyediakan objek seperti itu untuk Anda gunakan +sebagai titik mulai untuk mendefinisikan objek status Anda: kelas {@link +android.preference.Preference.BaseSavedState}.

+ +

Untuk mendefinisikan cara kelas {@link android.preference.Preference} menyimpan statusnya, Anda harus +memperluas kelas {@link android.preference.Preference.BaseSavedState}. Anda hanya perlu mengesampingkan + beberapa metode dan mendefinisikan objek {@link android.preference.Preference.BaseSavedState#CREATOR} +.

+ +

Untuk sebagian besar aplikasi, Anda bisa menyalin implementasi berikut dan cukup mengubah baris yang +menangani {@code value} jika subkelas {@link android.preference.Preference} Anda menyimpan tipe +data selain integer.

+ +
+private static class SavedState extends BaseSavedState {
+    // Member that holds the setting's value
+    // Change this data type to match the type saved by your Preference
+    int value;
+
+    public SavedState(Parcelable superState) {
+        super(superState);
+    }
+
+    public SavedState(Parcel source) {
+        super(source);
+        // Get the current preference's value
+        value = source.readInt();  // Change this to read the appropriate data type
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        super.writeToParcel(dest, flags);
+        // Write the preference's value
+        dest.writeInt(value);  // Change this to write the appropriate data type
+    }
+
+    // Standard creator object using an instance of this class
+    public static final Parcelable.Creator<SavedState> CREATOR =
+            new Parcelable.Creator<SavedState>() {
+
+        public SavedState createFromParcel(Parcel in) {
+            return new SavedState(in);
+        }
+
+        public SavedState[] newArray(int size) {
+            return new SavedState[size];
+        }
+    };
+}
+
+ +

Dengan implementasi {@link android.preference.Preference.BaseSavedState} di atas yang ditambahkan +ke aplikasi Anda (biasanya sebagai subkelas dari subkelas {@link android.preference.Preference}), Anda +nanti perlu mengimplementasikan metode {@link android.preference.Preference#onSaveInstanceState +onSaveInstanceState()} dan {@link +android.preference.Preference#onRestoreInstanceState onRestoreInstanceState()} untuk subkelas +{@link android.preference.Preference} Anda.

+ +

Misalnya:

+ +
+@Override
+protected Parcelable onSaveInstanceState() {
+    final Parcelable superState = super.onSaveInstanceState();
+    // Check whether this Preference is persistent (continually saved)
+    if (isPersistent()) {
+        // No need to save instance state since it's persistent,
+        // use superclass state
+        return superState;
+    }
+
+    // Create instance of custom BaseSavedState
+    final SavedState myState = new SavedState(superState);
+    // Set the state's value with the class member that holds current
+    // setting value
+    myState.value = mNewValue;
+    return myState;
+}
+
+@Override
+protected void onRestoreInstanceState(Parcelable state) {
+    // Check whether we saved the state in onSaveInstanceState
+    if (state == null || !state.getClass().equals(SavedState.class)) {
+        // Didn't save the state, so call superclass
+        super.onRestoreInstanceState(state);
+        return;
+    }
+
+    // Cast state to custom BaseSavedState and pass to superclass
+    SavedState myState = (SavedState) state;
+    super.onRestoreInstanceState(myState.getSuperState());
+    
+    // Set this Preference's widget to reflect the restored state
+    mNumberPicker.setValue(myState.value);
+}
+
+ diff --git a/docs/html-intl/intl/in/guide/topics/ui/ui-events.jd b/docs/html-intl/intl/in/guide/topics/ui/ui-events.jd new file mode 100644 index 0000000000000000000000000000000000000000..5068176fccaefce46166e9b4aceb85e35cfef955 --- /dev/null +++ b/docs/html-intl/intl/in/guide/topics/ui/ui-events.jd @@ -0,0 +1,291 @@ +page.title=Kejadian Input +parent.title=Antarmuka Pengguna +parent.link=index.html +@jd:body + +
+
+

Dalam dokumen ini

+
    +
  1. Event Listener
  2. +
  3. Event Handler
  4. +
  5. Mode Sentuh
  6. +
  7. Menangani Fokus
  8. +
+ +
+
+ +

Di Android, ada lebih dari satu cara untuk mencegat kejadian dari interaksi pengguna dengan aplikasi Anda. +Saat mempertimbangkan kejadian dalam antarmuka pengguna Anda, pendekatannya adalah menangkap kejadian +dari objek View tertentu yang digunakan pengguna untuk berinteraksi. Kelas View menyediakan sarana untuk melakukannya.

+ +

Dalam berbagai kelas View yang akan digunakan untuk menyusun layout, Anda mungkin melihat beberapa metode callback +publik yang tampak berguna untuk kejadian UI. Metode ini dipanggil oleh kerangka kerja Android ketika masing-masing +tindakan terjadi pada objek itu. Misalnya, bila View (seperti Button/Tombol) disentuh, +metode onTouchEvent() akan dipanggil pada objek itu. Akan tetapi, untuk mencegatnya, Anda harus memperluas +kelas dan mengesampingkan metode itu. Akan tetapi, memperluas setiap objek View +untuk menangani kejadian seperti itu tidaklah praktis. Karena itulah kelas View juga berisi +sekumpulan antarmuka tersarang dengan callback yang jauh lebih mudah didefinisikan. Antarmuka ini, +yang disebut event listener, merupakan tiket Anda untuk menangkap interaksi pengguna dengan UI.

+ +

Walaupun Anda akan lebih sering menggunakan event listener ini untuk interaksi pengguna, +mungkin ada saatnya Anda ingin memperluas kelas View, untuk membuat komponen custom. +Mungkin Anda ingin memperluas kelas {@link android.widget.Button} +untuk membuat sesuatu yang lebih menarik. Dalam hal ini, Anda akan dapat mendefinisikan perilaku kejadian default untuk kelas Anda dengan menggunakan +kelas event handler.

+ + +

Event Listener

+ +

Event listener merupakan antarmuka di kelas {@link android.view.View} yang berisi metode +callback tunggal. Metode ini akan dipanggil oleh kerangka kerja Android bila View yang +telah didaftarkan dengan listener dipicu oleh interaksi pengguna dengan item dalam UI.

+ +

Yang juga disertakan dalam antarmuka event listener adalah metode callback berikut ini:

+ +
+
onClick()
+
Dari {@link android.view.View.OnClickListener}. + Ini dipanggil baik saat pengguna menyentuh item + (bila dalam mode sentuh), maupun memfokuskan pada item dengan tombol navigasi atau trackball dan +menekan tombol "enter" yang sesuai atau menekan trackball.
+
onLongClick()
+
Dari {@link android.view.View.OnLongClickListener}. + Ini dipanggil baik saat pengguna menyentuh dan menahan item (bila dalam mode sentuh), +maupun memfokuskan pada item dengan tombol navigasi atau trackball dan +menekan serta menahan tombol "enter" yang sesuai atau menekan dan menahan trackball (selama satu detik).
+
onFocusChange()
+
Dari {@link android.view.View.OnFocusChangeListener}. + Ini dipanggil saat pengguna menyusuri ke atau dari item, dengan menggunakan tombol navigasi atau trackball.
+
onKey()
+
Dari {@link android.view.View.OnKeyListener}. + Ini dipanggil saat pengguna memfokuskan pada item dan menekan atau melepas tombol fisik pada perangkat.
+
onTouch()
+
Dari {@link android.view.View.OnTouchListener}. + Ini dipanggil saat pengguna melakukan tindakan yang digolongkan sebagai kejadian sentuh, termasuk penekanan, pelepasan, +atau gerak perpindahan pada layar (dalam batasan item itu).
+
onCreateContextMenu()
+
Dari {@link android.view.View.OnCreateContextMenuListener}. + Ini dipanggil saat Menu Konteks sedang dibuat (akibat "klik lama" terus-menerus). Lihat diskusi +tentang menu konteks di panduan pengembang Menu. +
+
+ +

Metode ini satu-satunya yang menempati antarmukanya masing-masing. Untuk mendefinisikan salah satu metode ini +dan menangani kejadian Anda, implementasikan antarmuka tersarang dalam Aktivitas Anda atau definisikan sebagai kelas anonim. +Kemudian, teruskan satu +instance implementasi Anda pada masing-masing metode View.set...Listener(). (Misalnya, panggil +{@link android.view.View#setOnClickListener(View.OnClickListener) setOnClickListener()} +dan teruskan implementasi {@link android.view.View.OnClickListener OnClickListener} Anda.)

+ +

Contoh di bawah menunjukkan cara mendaftarkan on-click listener untuk Button.

+ +
+// Create an anonymous implementation of OnClickListener
+private OnClickListener mCorkyListener = new OnClickListener() {
+    public void onClick(View v) {
+      // do something when the button is clicked
+    }
+};
+
+protected void onCreate(Bundle savedValues) {
+    ...
+    // Capture our button from layout
+    Button button = (Button)findViewById(R.id.corky);
+    // Register the onClick listener with the implementation above
+    button.setOnClickListener(mCorkyListener);
+    ...
+}
+
+ +

Anda juga akan merasa lebih praktis mengimplementasikan OnClickListener sebagai bagian dari Aktivitas. +Ini akan menghindari beban kelas ekstra dan alokasi objek. Misalnya:

+
+public class ExampleActivity extends Activity implements OnClickListener {
+    protected void onCreate(Bundle savedValues) {
+        ...
+        Button button = (Button)findViewById(R.id.corky);
+        button.setOnClickListener(this);
+    }
+
+    // Implement the OnClickListener callback
+    public void onClick(View v) {
+      // do something when the button is clicked
+    }
+    ...
+}
+
+ +

Perhatikan bahwa callback onClick() dalam contoh di atas tidak memiliki +nilai hasil, namun beberapa metode event listener lainnya harus mengembalikan boolean. Sebabnya +bergantung pada kejadian. Untuk sebagian yang mengembalikan boolean, ini sebabnya:

+ + +

Ingatlah bahwa kejadian tombol fisik selalu disampaikan ke View yang sedang difokus. Kejadian ini dikirim mulai dari atas +hierarki View, kemudian turun hingga tujuan yang sesuai. Jika View Anda (atau anak View Anda) +saat ini sedang fokus, maka Anda dapat melihat kejadian berpindah melalui metode.{@link android.view.View#dispatchKeyEvent(KeyEvent) +dispatchKeyEvent()} Sebagai pengganti untuk menangkap kejadian penting melalui View, Anda juga dapat menerima +semua kejadian dalam Aktivitas Anda dengan {@link android.app.Activity#onKeyDown(int,KeyEvent) onKeyDown()} +dan {@link android.app.Activity#onKeyUp(int,KeyEvent) onKeyUp()}.

+ +

Selain itu, saat memikirkan tentang input teks aplikasi Anda, ingatlah bahwa banyak perangkat yang hanya memiliki +metode input perangkat lunak. Metode seperti itu tidak harus berbasis tombol; sebagian mungkin menggunakan input suara, tulisan tangan, dan seterusnya. Meskipun +metode input menyajikan antarmuka seperti keyboard, itu umumnya tidak memicu keluarga kejadian +{@link android.app.Activity#onKeyDown(int,KeyEvent) onKeyDown()}. Anda sama sekali tidak boleh +membangun UI yang mengharuskan penekanan tombol tertentu dikontrol kecuali jika Anda ingin membatasi aplikasi Anda pada perangkat yang memiliki +keyboard fisik. Khususnya, jangan mengandalkan metode ini untuk memvalidasi input saat pengguna menekan tombol +enter; melainkan, gunakan tindakan seperti {@link android.view.inputmethod.EditorInfo#IME_ACTION_DONE} untuk menandai +metode input mengenai reaksi yang diharapkan aplikasi Anda, sehingga bisa mengubah UI-nya secara signifikan. Hindari anggapan +tentang bagaimana metode input perangkat lunak seharusnya bekerja dan percayalah bahwa metode akan menyediakan teks yang sudah diformat bagi aplikasi Anda.

+ +

Catatan: Android akan memanggil event handler terlebih dahulu kemudian handler +default yang sesuai dari definisi kelas. Karena itu, mengembalikan benar dari event listener ini akan menghentikan +penyebaran kejadian ke event listener lain dan juga akan memblokir callback ke +event handler default di View. Pastikan bahwa Anda ingin mengakhiri kejadian saat mengembalikan true.

+ + +

Event Handler

+ +

Jika Anda membuat komponen custom dari View, maka Anda dapat mendefinisikan penggunaan beberapa +metode callback sebagai event handler default. +Dalam dokumen tentang Komponen +Custom, Anda akan melihat penggunaan beberapa callback umum untuk penanganan kejadian, +termasuk:

+ +

Ada beberapa metode lain yang harus Anda ketahui, yang bukan bagian dari kelas View, +namun bisa berdampak langsung pada kemampuan Anda menangani kejadian. Jadi, saat mengelola kejadian yang lebih kompleks dalam +layout, pertimbangkanlah metode-metode lain ini:

+ + +

Mode Sentuh

+

+Saat pengguna menyusuri antarmuka pengguna dengan tombol pengarah atau trackball, Anda +perlu memberikan fokus pada item tindakan (seperti tombol) agar pengguna bisa mengetahui apa +yang akan menerima input. Akan tetapi jika perangkat memiliki kemampuan sentuh, dan pengguna +mulai berinteraksi dengan antarmuka dengan menyentuhnya, maka Anda tidak perlu lagi +menyorot item, atau memfokuskan pada View tertentu. Karena itu, ada mode +untuk interaksi yang bernama "mode sentuh". +

+

+Untuk perangkat berkemampuan sentuh, setelah pengguna menyentuh layar, perangkat +akan masuk ke mode sentuh. Dari sini dan selanjutnya, hanya View dengan +{@link android.view.View#isFocusableInTouchMode} benar yang akan dapat difokus, seperti widget pengedit teks. +View lain yang dapat disentuh, seperti tombol, tidak akan difokus bila disentuh; View ini akan +langsung memicu on-click listener bila ditekan. +

+

+Kapan saja pengguna menekan tombol pengarah atau menggulir dengan trackball, perangkat akan +keluar dari mode sentuh, dan mencari tampilan untuk difokuskan. Kini pengguna bisa melanjutkan interaksi +dengan antarmuka pengguna tanpa menyentuh layar. +

+

+Status mode sentuh dipertahankan di seluruh sistem (semua jendela dan aktivitas). +Untuk query status saat ini, Anda bisa memanggil +{@link android.view.View#isInTouchMode} untuk mengetahui apakah perangkat saat ini sedang dalam mode sentuh. +

+ + +

Menangani Fokus

+ +

Kerangka kerja ini akan menangani gerakan fokus rutin sebagai respons input pengguna. +Ini termasuk mengubah fokus saat View dihapus atau disembunyikan, atau saat tersedia View +baru. View menunjukkan kesediaannya untuk mengambil fokus +melalui metode {@link android.view.View#isFocusable()}. Untuk mengubah apakah View bisa mengambil +fokus, panggil {@link android.view.View#setFocusable(boolean) setFocusable()}. Saat dalam mode sentuh, +Anda dapat me-query apakah View memungkinkan fokus dengan {@link android.view.View#isFocusableInTouchMode()}. +Anda bisa mengubahnya dengan {@link android.view.View#setFocusableInTouchMode(boolean) setFocusableInTouchMode()}. +

+ +

Gerakan fokus berdasarkan pada algoritma yang mencari tetangga terdekat dalam +arah yang diberikan. Dalam kasus yang jarang terjadi, algoritma default mungkin +tidak cocok dengan perilaku yang diinginkan pengembang. Dalam situasi ini, Anda bisa memberikan +pengesampingan eksplisit dengan mengikuti atribut XML berikut dalam file layout: +nextFocusDown, nextFocusLeft, nextFocusRight, dan +nextFocusUp. Tambahkan salah satu atribut ini ke View dari mana fokus +meninggalkan. Definisikan nilai atribut untuk menjadi ID View +ke mana fokus harus diberikan. Misalnya:

+
+<LinearLayout
+    android:orientation="vertical"
+    ... >
+  <Button android:id="@+id/top"
+          android:nextFocusUp="@+id/bottom"
+          ... />
+  <Button android:id="@+id/bottom"
+          android:nextFocusDown="@+id/top"
+          ... />
+</LinearLayout>
+
+ +

Biasanya, dalam layout vertikal ini, navigasi ke atas dari Button pertama tidak akan membawa ke +mana pun, tidak pula akan menyusuri ke bawah dari Button kedua. Karena sekarang Button atas telah +mendefinisikan Button bawah sebagai nextFocusUp (dan sebaliknya), fokus navigasi akan +silih berganti dari atas ke bawah dan bawah ke atas.

+ +

Jika Anda ingin mendeklarasikan View sebagai dapat difokus dalam UI (bila biasanya tidak dapat difokus), +tambahkan atribut XML android:focusable ke View, dalam deklarasi layout Anda. +Atur nilai true. Anda juga bisa mendeklarasikan View +sebagai dapat difokus saat dalam Mode Sentuh dengan android:focusableInTouchMode.

+

Untuk meminta View tertentu difokus, panggil {@link android.view.View#requestFocus()}.

+

Untuk mendengarkan kejadian fokus (diberi tahu bila View menerima atau kehilangan fokus), gunakan +{@link android.view.View.OnFocusChangeListener#onFocusChange(View,boolean) onFocusChange()} +, seperti yang dibahas di bagian Event Listener, di atas.

+ + + + diff --git a/docs/html-intl/intl/in/sdk/index.jd b/docs/html-intl/intl/in/sdk/index.jd new file mode 100644 index 0000000000000000000000000000000000000000..f597312849e6a0a12907fc7486eee51cf7ddabe2 --- /dev/null +++ b/docs/html-intl/intl/in/sdk/index.jd @@ -0,0 +1,487 @@ +page.title=Unduh Android Studio dan SDK Tools +page.tags=sdk, android studio +page.template=sdk +header.hide=1 +page.metaDescription=Unduh Android IDE resmi dan alat pengembang untuk membuat aplikasi bagi ponsel, tablet, perangkat wearable, TV Android dan lainnya. + +studio.version=1.1.0 + +studio.linux_bundle_download=android-studio-ide-135.1740770-linux.zip +studio.linux_bundle_bytes=259336386 +studio.linux_bundle_checksum=e8d166559c50a484f83ebfec6731cc0e3f259208 + +studio.mac_bundle_download=android-studio-ide-135.1740770-mac.dmg +studio.mac_bundle_bytes=261303345 +studio.mac_bundle_checksum=f9745d0fec1eefd498f6160a2d6a1b5247d4cda3 + +studio.win_bundle_exe_download=android-studio-bundle-135.1740770-windows.exe +studio.win_bundle_exe_bytes=856233768 +studio.win_bundle_exe_checksum=7484b9989d2914e1de30995fbaa97a271a514b3f + +studio.win_notools_exe_download=android-studio-ide-135.1740770-windows.exe +studio.win_notools_exe_bytes=242135128 +studio.win_notools_exe_checksum=5ea77661cd2300cea09e8e34f4a2219a0813911f + +studio.win_bundle_download=android-studio-ide-135.1740770-windows.zip +studio.win_bundle_bytes=261667054 +studio.win_bundle_checksum=e903f17cc6a57c7e3d460c4555386e3e65c6b4eb + + + +sdk.linux_download=android-sdk_r24.1.2-linux.tgz +sdk.linux_bytes=168121693 +sdk.linux_checksum=68980e4a26cca0182abb1032abffbb72a1240c51 + +sdk.mac_download=android-sdk_r24.1.2-macosx.zip +sdk.mac_bytes=89151287 +sdk.mac_checksum=00e43ff1557e8cba7da53e4f64f3a34498048256 + +sdk.win_download=android-sdk_r24.1.2-windows.zip +sdk.win_bytes=159778618 +sdk.win_checksum=704f6c874373b98e061fe2e7eb34f9fcb907a341 + +sdk.win_installer=installer_r24.1.2-windows.exe +sdk.win_installer_bytes=111364285 +sdk.win_installer_checksum=e0ec864efa0e7449db2d7ed069c03b1f4d36f0cd + + + + + + +@jd:body + + + + + + + +
+ + + + + + + + + +
+ +
 
+ + + +
+ +

Android Studio

+ +

Android IDE resmi

+ +
    +
  • Android Studio IDE
  • +
  • Android SDK Tools
  • +
  • Platform Android 5.0 (Lollipop)
  • +
  • Citra sistem emulator Android 5.0 dengan Google API
  • +
+ + +Unduh Android Studio + + +

+Untuk mendapatkan Android Studio atau alat SDK mandiri, kunjungi developer.android.com/sdk/ +

+ + + +
+ + + + +

Editor kode cerdas

+ +
+ +
+ +
+

Yang menjadi inti Android Studio adalah editor kode cerdas dengan kemampuan + penyelesaian kode, optimalisasi, dan analisis kode yang canggih.

+

Editor kode yang andal ini membantu Anda menjadi pengembang aplikasi Android yang lebih produktif.

+
+ + + + + +

Template kode dan integrasi GitHub

+ +
+ +
+ +
+

Pemandu proyek yang baru membuat proses memulai proyek baru menjadi jauh lebih mudah.

+ +

Mulai proyek dengan menggunakan kode template untuk pola seperti navigation-drawer dan view-pager, + dan bahkan impor contoh kode Google dari GitHub.

+
+ + + + +

Pengembangan aplikasi multilayar

+ +
+ +
+ +
+

Buat aplikasi untuk ponsel dan tablet Android, Android Wear, + Android TV, Android Auto dan Google Glass.

+

Dengan Android Project View yang baru dan dukungan modul di Android Studio, jadi semakin mudah + mengelola proyek dan sumber daya aplikasi. +

+ + + + +

Perangkat virtual untuk semua ukuran dan bentuk

+ +
+ +
+ +
+

Android Studio sudah dikonfigurasi dengan citra emulator yang dioptimalkan.

+

Virtual Device Manager yang telah diperbarui dan dibuat lebih efisien menyediakan + profil perangkat yang sudah didefinisikan untuk perangkat Android umum.

+
+ + + + +

+Pembuatan Android berkembang dengan Gradle

+ +
+ +
+ +
+

Buatlah berbagai APK untuk aplikasi Android Anda dengan aneka fitur menggunakan proyek yang sama.

+

Kelola dependensi aplikasi dengan Maven.

+

Buat APK dari Android Studio atau baris perintah.

+
+ + + + +

Selengkapnya tentang Android Studio

+
+ +Unduh Android Studio + +
    +
  • Dibuat dengan IntelliJ IDEA Community Edition, JAVA IDE populer karya JetBrains.
  • +
  • Sistem pembuatan berbasis Gradle yang fleksibel.
  • +
  • Buat berbagai generasi APK dan variannya.
  • +
  • Dukungan template bertambah untuk Google Services dan aneka tipe perangkat.
  • +
  • Editor layout yang lengkap dengan dukungan untuk pengeditan tema.
  • +
  • Alat penambal untuk solusi kinerja, kegunaan, kompatibilitas versi, dan masalah lain.
  • +
  • ProGuard dan kemampuan penandatanganan aplikasi.
  • +
  • Dukungan bawaan untuk Google Cloud Platform, mempermudah integrasi Google Cloud + Messaging dan App Engine.
  • +
+ +

+Untuk detail selengkapnya tentang fitur-fitur yang tersedia di Android Studio, +bacalah panduan Dasar-Dasar Android Studio.

+
+ + +

Jika Anda menggunakan Eclipse dengan ADT, ingatlah bahwa Android Studio sekarang merupakan IDE resmi +untuk Android, jadi Anda harus beralih ke Android Studio untuk menerima semua +pembaruan terakhir IDE. Untuk bantuan dalam memindahkan proyek, +lihat Beralih ke Android +Studio.

+ + + + + + + +

Kebutuhan Sistem

+ +

Windows

+ + + + +

Mac OS X

+ + + +

Pada Mac OS, jalankan Android Studio dengan Java Runtime Environment (JRE) 6 untuk rendering +font yang dioptimalkan. Kemudian Anda bisa mengonfigurasi proyek untuk menggunakan Java Development Kit (JDK) 6 atau JDK 7.

+ + + +

Linux

+ + +

Telah diuji pada Ubuntu® 14.04, Trusty Tahr (distribusi 64-bit yang mampu menjalankan +aplikasi 32-bit).

+ + + + +

Opsi Unduhan Lain

+ + diff --git a/docs/html-intl/intl/in/sdk/installing/adding-packages.jd b/docs/html-intl/intl/in/sdk/installing/adding-packages.jd new file mode 100644 index 0000000000000000000000000000000000000000..b98e27b0a34afd0beb0649942830020c8f81ae01 --- /dev/null +++ b/docs/html-intl/intl/in/sdk/installing/adding-packages.jd @@ -0,0 +1,227 @@ +page.title=Menambahkan Paket SDK + +page.tags=sdk manager +helpoutsWidget=true + +@jd:body + + + + +

+Secara default, Android SDK tidak mencakup segala sesuatu yang Anda perlukan untuk memulai pengembangan. +SDK memisahkan alat, platform, dan komponen lain ke dalam paket yang bisa Anda +unduh bila diperlukan dengan menggunakan +Android SDK Manager. +Jadi, sebelum Anda bisa memulai, ada beberapa paket yang harus Anda tambahkan ke Android SDK Anda.

+ +

Untuk mulai menambahkan paket, jalankan Android SDK Manager dengan salah satu cara berikut:

+ + +

Bila Anda membuka SDK Manager untuk pertama kali, beberapa paket akan dipilih secara +default. Biarkan dipilih, namun pastikan bahwa Anda mempunyai semua yang Anda perlukan +untuk persiapan dengan mengikuti langkah-langkah ini:

+ + +
    +
  1. +

    Dapatkan alat-alat SDK terbaru

    + + + +

    Setidaknya saat menyiapkan Android SDK, + Anda harus mengunduh platform Android dan alat-alat terbaru:

    +
      +
    1. Buka direktori Tools dan pilih: +
        +
      • Android SDK Tools
      • +
      • Android SDK Platform-tools
      • +
      • Android SDK Build-tools (versi tertinggi)
      • +
      +
    2. +
    3. Buka folder Android X.X (versi terbaru) yang pertama dan pilih: +
        +
      • SDK Platform
      • +
      • Sebuah citra sistem untuk emulator, seperti
        + ARM EABI v7a System Image
      • +
      +
    4. +
    +
  2. + +
  3. +

    Dapatkan pustaka dukungan untuk API tambahan

    + + + +

    Android Support Library +menyediakan set API tambahan yang kompatibel dengan sebagian besar versi Android.

    + +

    Buka direktori Extras dan pilih:

    +
      +
    • Android Support Repository
    • +
    • Android Support Library
    • +
    + +

     

    +

     

    + +
  4. + + +
  5. +

    Dapatkan Google Play services untuk API yang lebih banyak lagi

    + + + +

    Untuk mengembangkan aplikasi dengan Google API, Anda memerlukan paket Google Play services:

    +

    Buka direktori Extras dan pilih:

    +
      +
    • Google Repository
    • +
    • Google Play services
    • +
    + +

    Catatan: API Google Play services tidak tersedia pada semua +perangkat berbasis Android, namun tersedia pada semua perangkat dengan Google Play Store. Untuk menggunakan API ini + dalam emulator Android, Anda juga harus menginstal citra sistem Google API + dari direktori Android X.X terbaru di SDK Manager.

    +
  6. + + +
  7. +

    Instal paket tersebut

    +

    Setelah Anda memilih semua paket yang diinginkan, teruskan untuk menginstal:

    +
      +
    1. Klik Install X packages.
    2. +
    3. Di jendela berikutnya, klik ganda masing-masing nama paket di sebelah kiri + untuk menyetujui perjanjian lisensinya masing-masing.
    4. +
    5. Klik Install.
    6. +
    +

    Kemajuan pengunduhan diperlihatkan di bagian bawah jendela SDK Manager. + Jangan keluar dari SDK Manager karena hal itu akan membatalkan pengunduhan.

    +
  8. + +
  9. +

    Bangun sesuatu!

    + +

    Dengan adanya semua paket di atas di Android SDK, maka Anda siap untuk membangun aplikasi +untuk Android. Dengan tersedianya berbagai alat baru dan API lainnya, maka tinggal jalankan SDK Manager +untuk mengunduh paket baru bagi SDK Anda.

    + +

    Inilah beberapa opsi cara Anda untuk melanjutkan:

    + +
    +
    +

    Persiapkan

    +

    Jika Anda masih baru dengan pengembangan Android, pelajari dasar-dasar aplikasi Android dengan mengikuti +panduan untuk Membangun Aplikasi Pertama Anda.

    + +
    +
    +

    Bangun untuk perangkat wearable

    +

    Jika Anda siap memulai pembangunan aplikasi untuk perangkat wearable Android, lihat panduan untuk +Membangun Aplikasi untuk Android Wear.

    + +
    +
    +

    Gunakan Google API

    +

    Untuk mulai menggunakan Google API, seperti Maps atau +layanan Play Game, lihat panduan untuk +Mempersiapkan Google Play +Services.

    + +
    +
    + + +
  10. + +
+ + diff --git a/docs/html-intl/intl/ja/guide/components/activities.jd b/docs/html-intl/intl/ja/guide/components/activities.jd new file mode 100644 index 0000000000000000000000000000000000000000..9b06d2c838bbc93e2a1cc7f94b962aaedcfafc55 --- /dev/null +++ b/docs/html-intl/intl/ja/guide/components/activities.jd @@ -0,0 +1,756 @@ +page.title=アクティビティ +page.tags=activity,intent +@jd:body + + + + + +

{@link android.app.Activity} は、電話をかける、写真を撮影する、メールを送る、マップを閲覧するといった操作をユーザーができる画面を提供するアプリケーション コンポーネントです。 + +各アクティビティには、ユーザー インターフェースを描画できるウィンドウがあります。一般的にはウィンドウは画面と同じ大きさになりますが、画面より小さくしたり、他のウィンドウ上にフローティングさせたりすることもできます。 + +

+ +

通常、アプリケーションは複数のアクティビティで構成されており、各アプリケーションはそれぞれ緩やかにつながっています。 +一般的には、アプリケーションの 1 つのアクティビティが「メイン」アクティビティとして指定され、ユーザーが初めてアプリケーションを起動したときに表示されるのがこのアクティビティになります。 +その後、各アクティビティで別のアクティビティを開始して別の操作を実行できます。 +新しいアクティビティの開始時には、前のアクティビティは停止しますが、そのアクティビティはシステムによってスタック(「バックスタック」)に維持されます + +新しいアクティビティが開始すると、それがバックスタックに入ってユーザーに表示されます。 +バックスタックは「後入れ先出し」の基本的なスタック メカニズムを順守するため、ユーザーが現在のアクティビティを完了して [戻る] ボタンを押すと、そのアクティビティはスタックから消え(破棄され)、前のアクティビティが再開します。 + +(バックスタックの詳細については、タスクとバックスタックドキュメントで説明します)。 + +

+ +

新しいアクティビティが開始したことで、別のアクティビティが停止した場合、その状態の変化がアクティビティのライフサイクル コールバック メソッド経由で通知されます。システムがアクティビティを作成しているのか、停止しているのか、再開しているのか、破棄しているのかという状態の変化によって、アクティビティが受け取るコールバック メソッドにはいくつかの種類があり、各コールバックではユーザーがその状態の変化に応じた特定の操作を実行できます。 + + +—— + +たとえば、アクティビティが停止した場合は、ネットワーク接続やデータベース接続などの大きなオブジェクトを解放することになります。 +アクティビティが再開した場合は、必要なリソースを再度取得し、中断したところから操作を再開できます。 +このような状態の推移はすべて、アクティビティのライフサイクルの一部です。 +

+ +

このドキュメントでは、さまざまなアクティビティの状態間の切り替えを正しく管理できるよう、アクティビティのライフサイクルの仕組みについてさらに詳しく説明する他、アクティビティのビルド方法と使用方法の基本について解説します。 + +

+ + + +

アクティビティを作成する

+ +

アクティビティを作成するには、{@link android.app.Activity} のサブクラス(またはその既存のサブクラス)を作成する必要があります。 +サブクラスでは、アクティビティのライフサイクルの状態の切り替え時(アクティビティの作成、停止、再開、破棄など)にシステムが呼び出すコールバック メソッドを実装する必要があります。 + +最も重要なコールバック メソッドは次の 2 つです。 +

+ +
+
{@link android.app.Activity#onCreate onCreate()}
+
このメソッドは必ず実装してください。システムはアクティビティ作成の際にこのメソッドを呼び出します。 +実装の際には、アクティビティの必須コンポーネントを初期化する必要があります。 + + さらに重要な点は、ここで {@link android.app.Activity#setContentView + setContentView()} を呼び出してアクティビティのユーザー インターフェースのレイアウトを定義する必要があるということです。
+
{@link android.app.Activity#onPause onPause()}
+
システムは、ユーザーがアクティビティを終了したことを始めて示すときに、このメソッドを呼び出します(アクティビティが破棄されていない場合も含む)。 +通常はここで、現在のユーザー セッション後も維持する必要のある変更点を保存しておきます(ユーザーが戻ってこない可能性があるため)。 + +
+
+ +

アクティビティ間の滑らかな操作感を実現し、アクティビティが停止したり破棄されたりする可能性のある予想外の中断に対応するために使用できるライフサイクル コールバック メソッドは他にもいくつかあります。 + +すべてのライフサイクル コールバック メソッドについては、アクティビティのライフサイクルを管理するのセクションで説明します。 +

+ + + +

ユーザー インターフェースを実装する

+ +

アクティビティのユーザー インターフェースは、ビューの階層、 {@link android.view.View} から派生したオブジェクトから提供されます。— +各ビューはアクティビティ ウィンドウ内の特定の長方形のエリアを制御し、ユーザーの操作に応答します。 +たとえば、1 つのビューが、ユーザーがタップしたときに操作を開始するボタンである場合があります。 +

+ +

Android には、レイアウトのデザインや整理に使用できる既成のビューが多数用意されています。 +「ウィジェット」は、ボタン、テキスト フィールド、チェックボックス、画像といった画像の視覚的(操作可能な)要素を提供するビューです。 +「レイアウト」は、{@link +android.view.ViewGroup} から派生したビューで、線形レイアウト、グリッド レイアウト、相対レイアウトなど、子ビューの特有のレイアウト モデルを提供するものです。 +また、{@link android.view.View} クラスと {@link android.view.ViewGroup} クラス(または既存のサブクラス)のサブクラスを作成し、独自のウィジェットやレイアウトを作ってアクティビティのレイアウトに適用することもできます。 + +

+ +

ビューを使用したレイアウトの定義で最も一般的なのは、XML レイアウト ファイルをアプリケーション リソースに保存する方法です。 +この方法では、ユーザー インターフェースのデザインを、アクティビティの挙動を定義するソース コードとは別に維持できます。 +{@link android.app.Activity#setContentView(int) setContentView()} を使用して、レイアウトのリソース ID を渡すと、アクティビティの UI としてレイアウトを設定できます。 + +ただし、アクティビティ コードに新しい {@link android.view.View} を作成して、{@link android.view.ViewGroup} に新しい {@link +android.view.View} を挿入してビュー階層をビルドし、ルートの {@link android.view.ViewGroup} を {@link android.app.Activity#setContentView(View) +setContentView()} に渡して、そのレイアウトを使うこともできます。 + +

+ +

ユーザー インターフェースの作成の詳細については、「ユーザー インターフェース」のドキュメントをご覧ください。

+ + + +

マニフェストでアクティビティを宣言する

+ +

アクティビティがシステムにアクセスできるようにするには、マニフェストでアクティビティを宣言する必要があります。 +アクティビティを宣言するには、マニフェスト ファイルを開いて、{@code <activity>} 要素を {@code <application>} の子要素として追加します。 + +次に例を示します。

+ +
+<manifest ... >
+  <application ... >
+      <activity android:name=".ExampleActivity" />
+      ...
+  </application ... >
+  ...
+</manifest >
+
+ +

この要素には他にも、アクティビティのラベル、アクティビティのアイコン、アクティビティの UI を決めるテーマなどのプロパティを定義する属性を含めることができます。{@code android:name} 属性は、アクティビティのクラス名を指定するもので、唯一の必須属性です。 + + +—アプリケーションを発行したら、この名前は変更できません。変更すると、アプリケーションのショートカットなどの一部の機能が破損する可能性があります(ブログの投稿「Things That Cannot Change」をご覧ください)。 + + +

+ +

マニフェストでのアクティビティの宣言に関する詳細については、{@code <activity>} 要素のリファレンスをご覧ください。 +

+ + +

インテント フィルタを使用する

+ +

{@code +<activity>} 要素でも — {@code +<intent-filter>} 要素を使用してさまざまなインテント フィルタを指定して — 他のアプリケーション コンポーネントでのアクティベート方法を宣言できます。 +

+ +

Android SDK ツールを使用して新しいアプリケーションを作成する際、自動的に作成されるスタブ アクティビティには、「メイン」アクションに応答するアクティビティで、「ランチャー」カテゴリに置かれるべきものを宣言するインテント フィルタが含まれます。 + +インテント フィルタは次のように表示されます。 +

+ +
+<activity android:name=".ExampleActivity" android:icon="@drawable/app_icon">
+    <intent-filter>
+        <action android:name="android.intent.action.MAIN" />
+        <category android:name="android.intent.category.LAUNCHER" />
+    </intent-filter>
+</activity>
+
+ +

{@code +<action>} 要素は、これがアプリケーションへの「メイン」エントリ ポイントであることを指定します。{@code +<category>} 要素は、アクティビティをシステムのアプリケーション ランチャーに入れるべきであると指定します(ユーザーがこのアクティビティを起動できるようにします)。 +

+ +

アプリケーションを自己完結型にして、他のアプリケーションでアクティビティをアクティベートできないようにする場合は、他のインテント フィルタは必要ありません。 +前の例のように、「メイン」アクションを持ち、「ランチャー」カテゴリにできるのは 1 つのアクティビティのみです。 +アクティビティを他のアプリケーションで利用できないようにする場合は、そのアクティビティにはインテント フィルタを使用せず、明示的なインテントを使用して自身でアクティビティを開始するようにできます(次のセクションで説明します)。 + +

+ +

ただし、他のアプリケーション(と自身のアプリケーション)から派生した暗黙的なインテントにアクティビティが応答するようにする場合は、アクティビティで追加のインテント フィルタを定義する必要があります。 + +応答するインテントのタイプごとに、{@code +<action>} 要素を含む {@code +<intent-filter>} と、任意で {@code +<category>} 要素や {@code +<data>} 要素を含める必要があります。 +これらの要素は、アクティビティが応答できるインテントのタイプを指定します。 +

+ +

アクティビティがインテントに応答する方法の詳細については、「インテントとインテント フィルタ」のドキュメントをご覧ください。 +

+ + + +

アクティビティを開始する

+ +

@link android.app.Activity#startActivity + startActivity()} を呼び出して、開始するアクティビティを記述する {@link android.content.Intent} を渡すと、新しいアクティビティを開始できます。 +インテントは開始するアクティビティを正確に指定するか、実行する操作のタイプを記述します(システムが適切なアクティビティを選択しますが、それが他のアプリケーションのアクティビティである場合もあります)。 + + +また、インテントには開始したアクティビティで使用する少量のデータを含めることもできます。 +

+ +

自身のアプリケーションを操作するとき、既知のアクティビティを起動することが頻繁にあります。 + そのような場合、クラス名を使用して開始するアクティビティを明示的に定義するインテントを作成できます。 +例として、1 つのアクティビティで {@code +SignInActivity} という名前の他のアクティビティを開始する方法を次に示します。

+ +
+Intent intent = new Intent(this, SignInActivity.class);
+startActivity(intent);
+
+ +

ただし、アクティビティからのデータを使用して、アプリケーションでメールやテキスト メッセージの送信、ステータスのアップデートといった操作を実行する場合もあります。 +アプリケーションにそのような操作を実行できるアクティビティがない場合、代わりに、端末上の他のアプリケーションによるアクティビティを活用できます。 + +ここが、インテントがその存在意義を発揮する場面です。実行する操作を記述するインテントを作成し、システムが適切なアクティビティを他のアプリケーションから起動します。 +— + +インテントを処理できるアクティビティが複数ある場合は、使用するアクティビティを 1 つユーザーが選択できます。 +たとえば、メールを送信できるようにする場合は、次のようなインテントを作成します。 + +

+ +
+Intent intent = new Intent(Intent.ACTION_SEND);
+intent.putExtra(Intent.EXTRA_EMAIL, recipientArray);
+startActivity(intent);
+
+ +

インテントに追加された {@link android.content.Intent#EXTRA_EMAIL} のエクストラは、メールの送信先となるメールアドレスの文字列配列です。 +メール アプリケーションがこのインテントに応答するとき、エクストラにある文字列配列を読み取り、それをメール作成フォームの「宛先」フィールドに置きます。 + +この場合、メール アプリケーションのアクティビティが開始してユーザーが操作を完了したときにアクティビティが再開します。 +

+ + + + +

結果待ちのアクティビティを開始する

+ +

開始するアクティビティから結果を受け取りたい場合は、{@link android.app.Activity#startActivityForResult + startActivityForResult()}({@link android.app.Activity#startActivity + startActivity()} の代わりに)を呼び出してアクティビティを開始します。 +その後のアクティビティから結果を受け取るには、 +{@link android.app.Activity#onActivityResult onActivityResult()} コールバック メソッドを実装します。 +後続のアクティビティが完了すると、{@link +android.content.Intent} の結果を {@link android.app.Activity#onActivityResult onActivityResult()} メソッドに返します。 +

+ +

たとえば、連絡先を 1 つ受け取って、アクティビティでその連絡先情報を使用する場合は、 +次のようにインテントを作成して結果を処理できます。 +

+ +
+private void pickContact() {
+    // Create an intent to "pick" a contact, as defined by the content provider URI
+    Intent intent = new Intent(Intent.ACTION_PICK, Contacts.CONTENT_URI);
+    startActivityForResult(intent, PICK_CONTACT_REQUEST);
+}
+
+@Override
+protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+    // If the request went well (OK) and the request was PICK_CONTACT_REQUEST
+    if (resultCode == Activity.RESULT_OK && requestCode == PICK_CONTACT_REQUEST) {
+        // Perform a query to the contact's content provider for the contact's name
+        Cursor cursor = getContentResolver().query(data.getData(),
+        new String[] {Contacts.DISPLAY_NAME}, null, null, null);
+        if (cursor.moveToFirst()) { // True if the cursor is not empty
+            int columnIndex = cursor.getColumnIndex(Contacts.DISPLAY_NAME);
+            String name = cursor.getString(columnIndex);
+            // Do something with the selected contact's name...
+        }
+    }
+}
+
+ +

この例では、アクティビティの結果を処理するために {@link +android.app.Activity#onActivityResult onActivityResult()} メソッドで使用すべき基本ロジックを示しています。 +1 つ目の条件では、要求が成功したかどうかを確認し、成功した場合は {@code resultCode} が{@link android.app.Activity#RESULT_OK} になり、この結果への要求が応答しているかどうかが判明します。この場合、{@code requestCode} が {@link android.app.Activity#startActivityForResult +startActivityForResult()} で送信された 2 つ目のパラメータに一致しています。— +—— + +そこから、コードが {@link android.content.Intent}({@code data} パラメータ)に返されたデータを照会することでアクティビティの結果を処理します。 +

+ +

ここで、{@link +android.content.ContentResolver} がコンテンツ プロバイダに対してクエリを実行し、照会されたデータを読み取れるようにする {@link android.database.Cursor} が返されます。 +詳細については、「コンテンツ プロバイダ」のドキュメントをご覧ください。 +

+ +

インテントの使用に関する詳細については、「インテントとインテント フィルタ」のドキュメントをご覧ください。 +

+ + +

アクティビティをシャットダウンする

+ +

アクティビティは、{@link android.app.Activity#finish +finish()} メソッドを呼び出すことでシャットダウンできます。また、{@link android.app.Activity#finishActivity finishActivity()} を呼び出すと以前に開始した別のアクティビティをシャットダウンすることもできます。 +

+ +

注: ほとんどの場合、これらのメソッドを用いてアクティビティを明示的に終了しないでください。 +後述のアクティビティのライフサイクルでも説明していますが、Android システム自体がアクティビティのライフサイクルを管理するため、アクティビティを自身で終了させる必要はありません。 + +これらのメソッドを呼び出すと、期待された操作性に影響を与えることがあるため、ユーザーが絶対にアクティビティのこのインスタンスに戻らないようにする場合にのみ使用するようにしてください。 + +

+ + +

アクティビティのライフサイクルを管理する

+ +

コールバック メソッドを実装したアクティビティのライフサイクルの管理は、強固で柔軟なアプリケーションの開発にとって重要です。 + +アクティビティのライフサイクルは、他のアクティビティ、タスク、バックスタックとの関連による影響を直接受けます。 +

+ +

基本的に、アクティビティには次の 3 つの状態があります。

+ +
+
再開状態
+
アクティビティが画面のフォアグラウンドにあり、ユーザー フォーカスのある状態。(この状態は「実行中」とも呼ばれます)。 +
+ +
一時停止状態
+
他のアクティビティがフォアグラウンドにあり、メインに表示されているが、このアクティビティも表示されている。つまり、このアクティビティの上に他のアクティビティが表示されており、他方のアクティビティは一部が透明であるか、画面全体を覆ってはいない状態です。 + +一時停止状態のアクティビティは完全に生きている状態ですが({@link android.app.Activity} オブジェクトがメモリに保持されており、すべての状態やメンバー情報が維持され、ウィンドウ マネージャーにもアタッチされたまま)、メモリ量が極端に低下した場合にはシステムによって強制停止される場合もあります。 + +
+ +
停止状態
+
アクティビティは、他のアクティビティによって完全に見えない状態です(アクティビティが「バックグラウンド」にある)。 +停止状態のアクティビティもまだ生きていますが({@link android.app.Activity} オブジェクトがメモリに保持されており、すべての状態やメンバー情報が維持されているが、ウィンドウ マネージャーにはアタッチされていない状態です)。 + +ただし、ユーザーには表示されておらず、別の場所でメモリが必要になればシステムによって強制終了される場合もあります。 +
+
+ +

アクティビティが一時停止か停止状態の場合、システムはアクティビティに終了するかどうかを尋ねる({@link android.app.Activity#finish finish()} メソッドを呼び出す)か、単純にプロセスを強制終了してメモリから解放できます。 + +アクティビティを再度開くとき(終了や強制終了後)は、もう一度最初から作成する必要があります。 +

+ + + +

ライフサイクル コールバックを実装する

+ +

アクティビティが上記の異なる状態の間を遷移するとき、さまざまなコールバック メソッドを介して通知されます。 +すべてのコールバック メソッドは、アクティビティの状態が変化したときに必要な操作を実行するようオーバーライドできるフックになります。 +次のスケルトン アクティビティには、基本的なライフサイクル メソッドがそれぞれ含まれています。 +

+ + +
+public class ExampleActivity extends Activity {
+    @Override
+    public void {@link android.app.Activity#onCreate onCreate}(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        // The activity is being created.
+    }
+    @Override
+    protected void {@link android.app.Activity#onStart onStart()} {
+        super.onStart();
+        // The activity is about to become visible.
+    }
+    @Override
+    protected void {@link android.app.Activity#onResume onResume()} {
+        super.onResume();
+        // The activity has become visible (it is now "resumed").
+    }
+    @Override
+    protected void {@link android.app.Activity#onPause onPause()} {
+        super.onPause();
+        // Another activity is taking focus (this activity is about to be "paused").
+    }
+    @Override
+    protected void {@link android.app.Activity#onStop onStop()} {
+        super.onStop();
+        // The activity is no longer visible (it is now "stopped")
+    }
+    @Override
+    protected void {@link android.app.Activity#onDestroy onDestroy()} {
+        super.onDestroy();
+        // The activity is about to be destroyed.
+    }
+}
+
+ +

注: これらのライフサイクル メソッドを実装する際は、上記の例のように、すべての操作の前にスーパークラスの実装を呼び出す必要があります。 +

+ +

これらのメソッドすべてで、アクティビティのライフサイクル全体を定義します。これらのメソッドを実装すると、アクティビティのライフサイクル内の次の 3 つのネストされたループを監視できます。 +

+ + + +

図 1 では、状態間でアクティビティがたどる可能性のあるループと経路を表しています。長方形はアクティビティが状態間で遷移したときの処理用に実装できるコールバック メソッドを表しています。 + +

+ + +

図 1. アクティビティのライフサイクル

+ +

表 1 には同じライフサイクル コールバック メソッドがリストされており、各コールバック メソッドの詳細と、アクティビティのライフサイクル全体でのそれぞれの位置関係、コールバック メソッドの完了後にシステムによってアクティビティが強制終了されるかどうかを示しています。 + + +

+ +

表 1. ライフサイクル コールバック メソッドの概要 +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
メソッド 説明 完了後の強制終了 次のメソッド
{@link android.app.Activity#onCreate onCreate()}アクティビティが最初に作成されるときに呼び出されます。 + ここで、ビューの作成、リストとのデータバインドといった、通常の静的なセットアップを行います。— +アクティビティの前の状態を含む Bundle オブジェクトを取り出せた場合、それをメソッドに渡します(後半のアクティビティの状態を保存するをご覧ください)。 + + + +

常に {@code onStart()} が後に続きます。

いいえ{@code onStart()}
    {@link android.app.Activity#onRestart +onRestart()}アクティビティが停止した後、再開する直前に呼び出されます。 + +

常に {@code onStart()} が後に続きます。

いいえ{@code onStart()}
{@link android.app.Activity#onStart onStart()}アクティビティがユーザーに見える状態になる直前に呼び出されます。 +

アクティビティがフォアグラウンドになったときは {@code onResume()} が後に続き、アクティビティが非表示になったときは {@code onStop()} が後に続きます。 +

いいえ{@code onResume()}
または
{@code onStop()}
    {@link android.app.Activity#onResume onResume()}アクティビティとユーザーとの操作が開始する直前に呼び出されます。 +この時点で、アクティビティはアクティビティ スタックの先頭にあり、ユーザー入力の準備ができています。 + +

常に {@code onPause()} が後に続きます。

いいえ{@code onPause()}
{@link android.app.Activity#onPause onPause()}システムが別のアクティビティを再開する直前に呼び出されます。 +通常、このメソッドは永続化データへの未保存の変更をコミットしたり、アニメーションや CPU を消費する可能性のあるその他の動作を停止したりする際に使用されます。 + +それが完了するまで次のアクティビティが再開できないため、それらの操作は迅速に行う必要があります。 + +

アクティビティが前面に戻るときは {@code onResume()} が後に続き、アクティビティが非表示になるときは {@code onStop()} が後に続きます。 + +

はい{@code onResume()}
または
{@code onStop()}
{@link android.app.Activity#onStop onStop()}アクティビティがユーザーに見えなくなると呼び出されます。これは、アクティビティが破棄されたか、別のアクティビティ(既存のアクティビティや新しいアクティビティ)が再開されてこのアクティビティを覆っている場合に起こります。 + + +

アクティビティのユーザー操作が可能に戻るときは {@code onRestart()} が後に続き、アクティビティがなくなるときは {@code onDestroy()} が後に続きます。 + +

はい{@code onRestart()}
または
{@code onDestroy()}
{@link android.app.Activity#onDestroy +onDestroy()}アクティビティが破棄される前に呼び出されます。これはアクティビティが受け取る最後の呼び出しです。 +アクティビティが終了した({@link android.app.Activity#finish + finish()} が呼び出された)か、アクティビティ領域を節約するためにシステムが一時的にこのアクティビティを破棄した場合に呼び出されます。 + +この 2 つのシナリオは、{@link + android.app.Activity#isFinishing isFinishing()} メソッドで区別できます。 +はいなし
+ +

「完了後の強制終了」の列は、メソッドが戻った後に、アクティビティのコードの後続行を実行することなく、アクティビティをホストするプロセスをシステムが強制終了できるかどうかを示しています。 + +3 つのメソッド({@link +android.app.Activity#onPause +onPause()}、{@link android.app.Activity#onStop onStop()}、{@link android.app.Activity#onDestroy +onDestroy()})が「はい」になっています。{@link android.app.Activity#onPause onPause()} は 3 つのなかで最初であるため、アクティビティが作成された後は{@link android.app.Activity#onPause onPause()} がプロセスが強制終了される可能性がある前に呼び出されることが保証される最後のメソッドです。システムが緊急でメモリを空ける必要がある場合は、{@link +android.app.Activity#onStop onStop()} と {@link android.app.Activity#onDestroy onDestroy()} は呼び出されない場合があります。 + +— + +そのため、重要な永続的データ(ユーザーの編集内容など)をストレージに書き込む際は、{@link android.app.Activity#onPause onPause()} を使用する必要があります。 +ただし、このメソッドで後続のアクティビティへの遷移をブロックしてしまい、ユーザー操作の速度を送らせてしまうことから、{@link android.app.Activity#onPause onPause()} 中にどんな情報を保持するかについては吟味する必要があります。 + + +

+ +

「強制終了」列で「いいえ」となっているメソッドでは、アクティビティが呼び出された時点から、アクティビティをホストするプロセスが強制終了されないよう保護します。 +つまり、アクティビティが強制終了される可能性があるのは、{@link android.app.Activity#onPause onPause()} が戻ってから、{@link android.app.Activity#onResume onResume()} が呼び出されるまでの間です。 + +{@link android.app.Activity#onPause onPause()} が再度呼び出されて戻るまでは、再度強制終了されることはありません。 +

+ +

注: 表 1 の定義では技術的に「強制終了」できないアクティビティでも、システムによって強制終了されることがありますが、そうなるのはリソース不足などの緊急時のみです。 +— +アクティビティが強制終了されるケースについては、Processes and Threading のドキュメントで説明しています。 + +

+ + +

アクティビティの状態を保存する

+ +

アクティビティのライフサイクルを管理するでも簡単に説明したように、アクティビティが一時停止や停止したとき、アクティビティの状態は保持されます。 + +これは、一時停止や停止されたときも {@link android.app.Activity} オブジェクトがメモリに保持されるためです — メンバーや現在の状態といったすべての情報は残っています。 + +つまり、アクティビティ内でユーザーが加えた変更点は保持されるため、アクティビティがフォアグラウンドに戻ったとき(「再開」したとき)、それらの変更点はそのまま表示されます。 + +

+ +

ただし、メモリを確保するためにシステムがアクティビティを破棄すると、 {@link +android.app.Activity} オブジェクトが破棄されるため、システムはそれをそのままの状態で再開できなくなります。 +代わりに、ユーザーがそれに戻る操作を行った場合、システムは {@link android.app.Activity} オブジェクトを再作成します。 +ユーザーにはシステムがアクティビティを破棄して再作成したことはわからないため、アクティビティが以前の状態のままであることを期待します。 + +この場合、アクティビティの状態情報を保存できる追加のコールバック メソッド({@link +android.app.Activity#onSaveInstanceState onSaveInstanceState()})を実装することで、アクティビティの状態に関する重要な情報を維持できます。 + +

+ +

アクティビティが破棄されるような状態になる前に、システムが {@link android.app.Activity#onSaveInstanceState onSaveInstanceState()} を呼び出します。 +システムはこのメソッドを {@link android.os.Bundle} に渡し、そこでアクティビティの状態情報を名前と値のペアとして {@link +android.os.Bundle#putString putString()} や {@link +android.os.Bundle#putInt putInt()} などのメソッドを使用して保存できます。 + +その後、システムがアプリケーション プロセスを強制終了して、ユーザーがアクティビティに戻った場合、システムはアクティビティを再作成して {@link android.os.Bundle} を {@link android.app.Activity#onCreate onCreate()} と {@link +android.app.Activity#onRestoreInstanceState onRestoreInstanceState()} の両方に渡します。 + +いずれのメソッドを使った場合でも、保存した状態を {@link android.os.Bundle} から抽出してアクティビティの状態を復元できます。 + +復元する状態情報がない場合は、{@link +android.os.Bundle} は null で渡されます(アクティビティを最初に作成した場合がこれにあたります)。 +

+ + +

図 2. アクティビティが前の状態のままでユーザー フォーカスに戻るには、アクティビティが破棄され、再作成された後にアクティビティが保存された以前の状態を復元する必要があるか、アクティビティが停止し、再開した後にアクティビティの状態が以前のままになるか、の 2 つの方法があります。 + + +

+ +

注: アクティビティが破棄される前に {@link +android.app.Activity#onSaveInstanceState onSaveInstanceState()} が呼び出される保証はありません。これは、状態を保存する必要がないケースがあるためです(ユーザーが [戻る] ボタンを使用してアクティビティを離れることで明示的にアクティビティを閉じた場合など)。 + + + +システムが {@link android.app.Activity#onSaveInstanceState +onSaveInstanceState()} を呼び出す場合、呼び出しは常に {@link +android.app.Activity#onStop onStop()} の前、場合によっては {@link android.app.Activity#onPause +onPause()} の前に行われます。

+ +

ただし、何もせず {@link +android.app.Activity#onSaveInstanceState onSaveInstanceState()} も実装しない場合でも、{@link android.app.Activity} クラスの {@link +android.app.Activity#onSaveInstanceState onSaveInstanceState()} のデフォルトの実装によって、アクティビティの状態が復元されるものもあります。 +具体的には、デフォルトの実装がレイアウト内のすべての {@link +android.view.View} の {@link +android.view.View#onSaveInstanceState onSaveInstanceState()} を呼び出すことで、各ビューが保存すべき情報を提供できるようになります。 + +Android フレームワークの大半のウィジェットが必要に応じてこのメソッドを実装しており、UI への視覚的な変更は自動的に保存され、アクティビティが再作成されると復元されるようになっています。 + +たとえば、{@link android.widget.EditText} ウィジェットではユーザーが入力したすべてのテキストを保存し、{@link android.widget.CheckBox} ウィジェットはオンにされたかどうかを保存するようになっています。 + +ここで必要な作業は、状態を保存する各ウィジェット用の一意の ID({@code android:id})を提供するだけです。 +ウィジェットに ID がないと、システムは状態を保存できません。 +

+ + + +

{@link +android.app.Activity#onSaveInstanceState onSaveInstanceState()} のデフォルトの実装によってアクティビティの UI に関する有用な情報は保存されますが、追加の情報を保存するようそれをオーバーライドすることもできます。例としては、アクティビティの期間に変更されたメンバー値を保存する必要があるケースなどがあります。(UI で復元された値に関連している場合でも、それらの UI 値を持つメンバーはデフォルトでは復元されません)。 + + + +

+ +

{@link +android.app.Activity#onSaveInstanceState onSaveInstanceState()} のデフォルトの実装で UI の状態を保存できるため、状態の追加情報を保存するようメソッドをオーバーライドする場合は、常に作業前に {@link android.app.Activity#onSaveInstanceState onSaveInstanceState()} のスーパークラス実装を呼び出す必要があります。 + + +同様に、オーバーライドする場合はデフォルトの実装でビューの状態を復元できるよう、{@link +android.app.Activity#onRestoreInstanceState onRestoreInstanceState()} のスーパークラスの実装も呼び出す必要があります。 +

+ +

注: {@link android.app.Activity#onSaveInstanceState +onSaveInstanceState()} は呼び出される保証がないため、これはアクティビティの一時的な状態の記録用にのみ使用し、永続的データの保存には使用しないようにします。 + +—代わりに {@link +android.app.Activity#onPause onPause()} を使用して、ユーザーがアクティビティを離れたときの永続的データ(データベースに保存するインストール必要のあるデータなど)を保存します。 +

+ +

アプリケーションが状態を復元できるかどうかテストするには、端末を回転してみて、方向が変化するかを確認します +画面の方向が変わるとき、システムがアクティビティを破棄して再作成し、新しい画面構成に利用可能な別のリソースを適用します。 + +アプリケーションの使用中にユーザーが端末を回転させるという場面は日常的にあるため、アクティビティが再作成されたときに状態を完全に復元することは非常に重要です。 + +

+ + +

構成の変更を処理する

+ +

端末の構成の中には、実行の際に変化するものがあります(画面の向き、キーボードの可用性、言語など)。 +そのような変化が生じたとき、Android は実行中のアクティビティを再作成します(システムが {@link android.app.Activity#onDestroy} を呼び出し、その後すぐに {@link +android.app.Activity#onCreate onCreate()})を呼び出します。 +この動作は、提供した別のリソース(異なる画面の向きやサイズに応じたレイアウトなど)を使用してアプリケーションを自動的にリロードすることで、アプリケーションを新しい構成に適応させることを目的としています。 + + +

+ +

前述のように画面の向きの変化による再起動を処理して、アクティビティの状態を復元するようアプリケーションを適切にデザインしていれば、アプリケーションはアクティビティのライフサイクルでの予期しない他のイベントに対しても回復力を持つことができます。 + +

+ +

このような再起動を処理するのに最適な方法は、前のセクションで説明したように、{@link + android.app.Activity#onSaveInstanceState onSaveInstanceState()} と {@link +android.app.Activity#onRestoreInstanceState onRestoreInstanceState()}(または {@link +android.app.Activity#onCreate onCreate()})を使用してアクティビティの状態を保存、復元する方法です。 +

+ +

実行の際に起こる構成の変更と、その処理方法の詳細については、「実行時の変更の処理」のガイドをご覧ください。 + +

+ + + +

アクティビティを連携する

+ +

1 つのアクティビティで別のアクティビティを開始すると、双方でライフサイクルの遷移が生じます。1 つ目のアクティビティが一時停止したり停止したりすると(バックグラウンドにある場合は停止しません)、もう一方のアクティビティが作成されます。 + +これらのアクティビティでディスクなどに保存されているデータを共有している場合は、2 つ目のアクティビティが作成される前に 1 つ目のアクティビティが完全に停止することはないということを理解しておくことが重要です。むしろ、2 つ目の開始プロセスは、1 つ目の停止プロセスにオーバーラップします。 + + +

+ +

特に 2 つのアクティビティが同じプロセスにあって 1 つが別のアクティビティを開始する場合、ライフサイクル コールバックの順序は厳密に定義されています。 +アクティビティ A がアクティビティ B を開始する場合の動作の順序を次に示します。 +

+ +
    +
  1. アクティビティ A の {@link android.app.Activity#onPause onPause()} メソッドが実行されます。
  2. + +
  3. アクティビティ B の {@link android.app.Activity#onCreate onCreate()}、{@link +android.app.Activity#onStart onStart()}、{@link android.app.Activity#onResume onResume()} メソッドが順次実行されます +(このとき、ユーザー フォーカスはアクティビティ B にあります)。
  4. + +
  5. 次に、アクティビティ A が画面から消えた場合、{@link +android.app.Activity#onStop onStop()} メソッドが実行されます。
  6. +
+ +

このライフサイクル コールバックの順序を予測しておくことで、1 つのアクティビティから他のアクティビティへの情報の遷移を管理できるようになります。 +たとえば、1 つ目のアクティビティが停止したときに、後続のアクティビティが読み取れるようにデータベースに書き込む必要がある場合、データベースに書き込むタイミングは {@link +android.app.Activity#onStop onStop()} ではなく {@link android.app.Activity#onPause onPause()} の間になります。 + +

+ + diff --git a/docs/html-intl/intl/ja/guide/components/bound-services.jd b/docs/html-intl/intl/ja/guide/components/bound-services.jd new file mode 100644 index 0000000000000000000000000000000000000000..d115e76ed04b9723d4161478c405abde5df83bb4 --- /dev/null +++ b/docs/html-intl/intl/ja/guide/components/bound-services.jd @@ -0,0 +1,658 @@ +page.title=バインドされたサービス +parent.title=サービス +parent.link=services.html +@jd:body + + +
+
    +

    本書の内容

    +
      +
    1. 基本
    2. +
    3. バインドされたサービスを作成する +
        +
      1. Binder クラスを拡張する
      2. +
      3. メッセンジャーを使用する
      4. +
      +
    4. +
    5. サービスにバインドする
    6. +
    7. バインドされたサービスのライフサイクルを管理する
    8. +
    + +

    キークラス

    +
      +
    1. {@link android.app.Service}
    2. +
    3. {@link android.content.ServiceConnection}
    4. +
    5. {@link android.os.IBinder}
    6. +
    + +

    サンプル

    +
      +
    1. {@code + RemoteService}
    2. +
    3. {@code + LocalService}
    4. +
    + +

    関連ドキュメント

    +
      +
    1. サービス
    2. +
    +
+ + +

バインドされたサービスは、クライアントサーバー インターフェースにおけるサーバーになります。バインドされたサービスでは、コンポーネント(アクティビティなど)をサービスにバインドしたり、送信を要求したり、受信を応答したり、さらにはプロセス間通信(IPC)を実行したりできるようにします。 + +通常、バインドされたサービスは他のアプリケーション コンポーネントを提供している間だけ機能し、バックグラウンドで無期限に実行されることはありません。 +

+ +

このドキュメントでは、他のアプリケーション コンポーネントからのサービスにバインドする方法を含む、バインドされたサービスの作成方法について説明します。 +ただし、サービスから通知を配信する方法や、フォアグラウンドでサービスを実行するよう設定する方法など、サービス全般の詳細については、「サービス」のドキュメントをご覧ください。 + +

+ + +

基本

+ +

バインドされたサービスは、他のアプリケーションのバインドとやり取りを可能にする {@link android.app.Service} クラスの実装です。 +サービスのバインドを提供するには、{@link android.app.Service#onBind onBind()} コールバック メソッドを実装する必要があります。 +このメソッドでは {@link android.os.IBinder} オブジェクトが返されます。このオブジェクトはクライアントがサービスとのやり取りに使用するプログラミング インターフェースを定義します。 + +

+ + + +

クライアントは、{@link android.content.Context#bindService +bindService()} を呼び出せばサービスにバインドできます。バインドするときは、サービスとの接続を監視する {@link +android.content.ServiceConnection} を実装する必要があります。{@link +android.content.Context#bindService bindService()} メソッドは値なしですぐに返されますが、Android システムがクライアントとサービス間の接続を作成すると {@link +android.content.ServiceConnection} の {@link +android.content.ServiceConnection#onServiceConnected onServiceConnected()} が呼び出され、クライアントがサービスとの通信に使用できる {@link android.os.IBinder} が配信されます。 + + +

+ +

複数のクライアントが同時にサービスに接続できます。ただし、1 つ目のクライアントのバインドの際にのみ、システムはサービスの {@link android.app.Service#onBind onBind()} メソッドを呼び出して{@link android.os.IBinder} を取得します。 + +その後システムは {@link android.app.Service#onBind onBind()} を呼び出すことなく、同じ {@link android.os.IBinder} をバインドしたすべてのクライアントに配信します。 +

+ +

最後のクライアントがサービスからアンバインドされると、システムはサービスを破棄します(サービスが {@link android.content.Context#startService startService()} でも開始された場合を除く)。 +

+ +

バインドされたサービスを実装するときに最も重要なのは、{@link android.app.Service#onBind onBind()} コールバック メソッドが返すインターフェースを定義することです。 +サービスの {@link android.os.IBinder} インターフェースの定義には複数の方法があり、次のセクションではそれぞれのテクニックについて説明していきます。 + +

+ + + +

バインドされたサービスを作成する

+ +

バインドを提供するサービスを作成するとき、クライアントがサービスとのやり取りに使用するプログラミング インターフェースを提供する {@link android.os.IBinder} を提示する必要があります。 +インターフェースを定義するには、次の 3 つの方法があります。 +

+ +
+
Binder クラスを拡張する
+
サービスが独自のアプリケーション専用であり、クライアントと同じプロセスで実行する場合(ほとんどのケースに該当)、{@link android.os.Binder} クラスを拡張して {@link android.app.Service#onBind onBind()} からそのインスタンスを返すことでインターフェースを作成します。 + + +クライアントは {@link android.os.Binder} を受け取り、それを使用して {@link android.os.Binder} の実装や {@link android.app.Service} で利用できる public メソッドに直接アクセスできます。 + + +

サービスが単にアプリケーションのバックグラウンド ワーカーである場合は、この方法が適しています。 +この方法が適していない唯一のケースは、サービスが他のアプリケーションや、別のプロセス間で使用されている場合です。 +

+ +
メッセンジャーを使用する
+
別のプロセス間で動作するインターフェースが必要な場合は、{@link android.os.Messenger} を使用してサービス用のインターフェースを作成できます。 +この方法では、サービスが異なるタイプの {@link +android.os.Message} オブジェクトに応答する {@link android.os.Handler} を定義します。 +この {@link android.os.Handler} を基本として {@link android.os.Messenger} は {@link android.os.IBinder} をクライアントと共有でき、 クライアントは {@link +android.os.Message} オブジェクトを使用してサービスにコマンドを送信できるようになります。 + +さらに、クライアントはサービスがメッセージを返信できるように独自の {@link android.os.Messenger} を定義できます。 + +

これは、最も簡単にプロセス間通信(IPC)を実行する方法であり、{@link +android.os.Messenger} がすべてのリクエストを 1 つのスレッドにキューイングするため、サービスをスレッドセーフにデザインする必要がありません。 +

+
+ +
AIDL を使用する
+
AIDL(Android インターフェース定義言語)は、オブジェクトをオペーレーティングシステムが理解できるプリミティブに分解するためのすべての処理のを実行し、プロセス間でそれを整理して IPC 実行します。{@link android.os.Messenger} を使用する前の方法は、実際には AIDL を基本構造としています。 + + +前述したように、{@link android.os.Messenger} はすべてのクライアントの要求のキューを 1 つのスレッドに作成するため、サービスは要求を一度に受け取ります。 +ただし、サービスで複数の要求を同時に処理する場合は、AIDL を直接使用できます。 + +その場合、サービスがマルチスレッドに対応しており、スレッドセーフで構築されている必要があります。 +

AIDL を直接使用するには、プログラミング インターフェースを定義する {@code .aidl} ファイルを作成する必要があります。 +Android SDK ツールはこのファイルを使用して、インターフェースを実装して IPC を処理する抽象クラスを生成し、それをサービス内に拡張できます。 + +

+
+
+ +

注: ほとんどのアプリケーションにおいて、マルチスレッド化が必要な点や、結果的により複雑な実装となってしまうことから、AIDL を使ったバインドされたサービスの作成はお勧めしません。 + +AIDL はほとんどのアプリケーションに適していないため、このドキュメントではサービスでの AIDL の使用方法については取り上げません。 +どうしても AIDL の直接使用が必要な場合は、「AIDL」のドキュメントをご覧ください。 + +

+ + + + +

Binder クラスを拡張する

+ +

サービスがローカルのアプリケーションでのみ使用されていて、プロセス間での作業が必要ない場合は、クライアントがサービスの public メソッドに直接アクセスできるようにする独自の {@link android.os.Binder} クラスを実装できます。 + +

+ +

注: この方法は、クライアントとサービスが同じアプリケーションとプロセスにある場合(最も一般的なケース)のみ使用できます。 +たとえば、バックグラウンドで音楽を再生する独自のサービスに、アクティビティをバインドする必要のある音楽アプリケーションに適しています。 + +

+ +

セットアップ方法は次のとおりです。

+
    +
  1. サービスで次のいずれかに該当する {@link android.os.Binder} のインスタンスを作成します。 +
      +
    • クライアントが呼び出せる public メソッドを含んでいる
    • +
    • クライアントが呼び出せる public メソッドのある現在の {@link android.app.Service} インスタンスを返す +
    • +
    • クライアントが呼び出せる public メソッドのあるサービスでホストされた他のクラスのインスタンスを返す +
    • +
    +
  2. {@link android.os.Binder} のインスタンスを {@link +android.app.Service#onBind onBind()} コールバック メソッドから返します。
  3. +
  4. クライアントで、{@link android.os.Binder} を {@link +android.content.ServiceConnection#onServiceConnected onServiceConnected()} コールバック メソッドから受け取り、提供されたメソッドを使用してバインドされたサービスを呼び出します。 +
  5. +
+ +

注: サービスとクライアントが同じアプリケーションになければならない理由は、クライアントが返されたオブジェクトをキャストでき、その API を適切に呼び出せるようにするためです。 +また、サービスとクライアントが同じプロセスになければならない理由は、この方法ではプロセス間の整理が一切行われないためです。 + +

+ +

以下は、{@link android.os.Binder} の実装を介してサービスのメソッドにクライアントアクセスを提供するサービスの例です。 +

+ +
+public class LocalService extends Service {
+    // Binder given to clients
+    private final IBinder mBinder = new LocalBinder();
+    // Random number generator
+    private final Random mGenerator = new Random();
+
+    /**
+     * Class used for the client Binder.  Because we know this service always
+     * runs in the same process as its clients, we don't need to deal with IPC.
+     */
+    public class LocalBinder extends Binder {
+        LocalService getService() {
+            // Return this instance of LocalService so clients can call public methods
+            return LocalService.this;
+        }
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return mBinder;
+    }
+
+    /** method for clients */
+    public int getRandomNumber() {
+      return mGenerator.nextInt(100);
+    }
+}
+
+ +

{@code LocalBinder} が {@code LocalService} の現在のインスタンスを取り出すための {@code getService()}メソッドをクライアントに提供します。 +これにより、クライアントがサービス内の public メソッドを呼び出せるようになります。 +たとえば、クライアントはサービスから {@code getRandomNumber()} を呼び出すことができます。

+ +

以下は、{@code LocalService} にバインドして、ボタンがクリックされたときに {@code getRandomNumber()} を呼び出すアクティビティの例です。 +

+ +
+public class BindingActivity extends Activity {
+    LocalService mService;
+    boolean mBound = false;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.main);
+    }
+
+    @Override
+    protected void onStart() {
+        super.onStart();
+        // Bind to LocalService
+        Intent intent = new Intent(this, LocalService.class);
+        bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
+    }
+
+    @Override
+    protected void onStop() {
+        super.onStop();
+        // Unbind from the service
+        if (mBound) {
+            unbindService(mConnection);
+            mBound = false;
+        }
+    }
+
+    /** Called when a button is clicked (the button in the layout file attaches to
+      * this method with the android:onClick attribute) */
+    public void onButtonClick(View v) {
+        if (mBound) {
+            // Call a method from the LocalService.
+            // However, if this call were something that might hang, then this request should
+            // occur in a separate thread to avoid slowing down the activity performance.
+            int num = mService.getRandomNumber();
+            Toast.makeText(this, "number: " + num, Toast.LENGTH_SHORT).show();
+        }
+    }
+
+    /** Defines callbacks for service binding, passed to bindService() */
+    private ServiceConnection mConnection = new ServiceConnection() {
+
+        @Override
+        public void onServiceConnected(ComponentName className,
+                IBinder service) {
+            // We've bound to LocalService, cast the IBinder and get LocalService instance
+            LocalBinder binder = (LocalBinder) service;
+            mService = binder.getService();
+            mBound = true;
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName arg0) {
+            mBound = false;
+        }
+    };
+}
+
+ +

上の例は、{@link android.content.ServiceConnection} の実装と {@link +android.content.ServiceConnection#onServiceConnected onServiceConnected()} コールバックを使用してクライアントがサービスにバインドする方法を示しています。 +次のセクションでは、サービスへのバインドのプロセスについて詳しく説明していきます。 +

+ +

注: 上記の例ではサービスから明示的にアンバウンドしていませんが、すべてのクライアントは適切なタイミング(アクティビティが一時停止したときなど)でアンバウンドする必要があります。 +

+ +

他のサンプル コードについては、ApiDemos{@code +LocalService.java} クラスと {@code +LocalServiceActivities.java} クラスをご覧ください。

+ + + + + +

メッセンジャーを使用する

+ + + +

リモート プロセスと通信するサービスが必要な場合は、{@link android.os.Messenger} を使用してサービスのインターフェースを提供できます。 +この方法では、AIDL を使用する必要なくプロセス間通信(IPC)を実行できます。 +

+ +

{@link android.os.Messenger} の使用方法の概要は以下のとおりです。

+ + + + +

この方法には、クライアントがサービスで呼び出す「メソッド」はありません。代わりに、クライアントはサービスが {@link android.os.Handler} で受け取る「メッセージ({@link android.os.Message} オブジェクト)」を配信します。 + +

+ +

以下は、{@link android.os.Messenger} インターフェースを使用するサービスの簡単な例です。

+ +
+public class MessengerService extends Service {
+    /** Command to the service to display a message */
+    static final int MSG_SAY_HELLO = 1;
+
+    /**
+     * Handler of incoming messages from clients.
+     */
+    class IncomingHandler extends Handler {
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_SAY_HELLO:
+                    Toast.makeText(getApplicationContext(), "hello!", Toast.LENGTH_SHORT).show();
+                    break;
+                default:
+                    super.handleMessage(msg);
+            }
+        }
+    }
+
+    /**
+     * Target we publish for clients to send messages to IncomingHandler.
+     */
+    final Messenger mMessenger = new Messenger(new IncomingHandler());
+
+    /**
+     * When binding to the service, we return an interface to our messenger
+     * for sending messages to the service.
+     */
+    @Override
+    public IBinder onBind(Intent intent) {
+        Toast.makeText(getApplicationContext(), "binding", Toast.LENGTH_SHORT).show();
+        return mMessenger.getBinder();
+    }
+}
+
+ +

{@link android.os.Handler} の {@link android.os.Handler#handleMessage handleMessage()} メソッドが、サービスが {@link android.os.Message} を受け取る場所であり、{@link android.os.Message#what} メンバーに基づいてその後の操作を決める場面であることに注目してください。 + +

+ +

クライアントで必要な操作は、サービスから返された {@link +android.os.IBinder} に基づいて {@link android.os.Messenger} を作成し、{@link +android.os.Messenger#send send()} を使用してメッセージを送信するだけです。例として、サービスにバインドして {@code MSG_SAY_HELLO} メッセージをサービスに送信する簡単なアクティビティを次に示します。 +

+ +
+public class ActivityMessenger extends Activity {
+    /** Messenger for communicating with the service. */
+    Messenger mService = null;
+
+    /** Flag indicating whether we have called bind on the service. */
+    boolean mBound;
+
+    /**
+     * Class for interacting with the main interface of the service.
+     */
+    private ServiceConnection mConnection = new ServiceConnection() {
+        public void onServiceConnected(ComponentName className, IBinder service) {
+            // This is called when the connection with the service has been
+            // established, giving us the object we can use to
+            // interact with the service.  We are communicating with the
+            // service using a Messenger, so here we get a client-side
+            // representation of that from the raw IBinder object.
+            mService = new Messenger(service);
+            mBound = true;
+        }
+
+        public void onServiceDisconnected(ComponentName className) {
+            // This is called when the connection with the service has been
+            // unexpectedly disconnected -- that is, its process crashed.
+            mService = null;
+            mBound = false;
+        }
+    };
+
+    public void sayHello(View v) {
+        if (!mBound) return;
+        // Create and send a message to the service, using a supported 'what' value
+        Message msg = Message.obtain(null, MessengerService.MSG_SAY_HELLO, 0, 0);
+        try {
+            mService.send(msg);
+        } catch (RemoteException e) {
+            e.printStackTrace();
+        }
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.main);
+    }
+
+    @Override
+    protected void onStart() {
+        super.onStart();
+        // Bind to the service
+        bindService(new Intent(this, MessengerService.class), mConnection,
+            Context.BIND_AUTO_CREATE);
+    }
+
+    @Override
+    protected void onStop() {
+        super.onStop();
+        // Unbind from the service
+        if (mBound) {
+            unbindService(mConnection);
+            mBound = false;
+        }
+    }
+}
+
+ +

この例では、サービスがクライアントにどのように応答できるのかは示されていません。サービスが応答するようにするには、クライアントで {@link android.os.Messenger} も作成する必要があります。 +その後、クライアントが {@link android.content.ServiceConnection#onServiceConnected +onServiceConnected()} コールバックを受け取るとき、{@link android.os.Messenger#send send()} メソッドの {@link android.os.Message#replyTo} パラメータにクライアントの{@link android.os.Messenger} を含めてサービスに {@link android.os.Message} を送信します。 + + +

+ +

双方向メッセージを提供する方法のサンプルについては、{@code +MessengerService.java}(サービス)と {@code +MessengerServiceActivities.java}(クライアント)のサンプルをご覧ください。

+ + + + + +

サービスにバインドする

+ +

アプリケーションのコンポーネント(クライアント)は、{@link android.content.Context#bindService bindService()} を呼び出してサービスにバインドできます。 +次に Android システムがサービスの {@link android.app.Service#onBind +onBind()} メソッドを呼び出し、そこからサービスとのやり取りに必要な {@link android.os.IBinder} が返されます。 +

+ +

バインドは非同期的に行われます。{@link android.content.Context#bindService +bindService()} は瞬時に返され、{@link android.os.IBinder} はクライアントには返されません。 +{@link android.os.IBinder} を受け取るには、クライアントが {@link +android.content.ServiceConnection} のインスタンスを作成し、それを {@link android.content.Context#bindService +bindService()} に渡す必要があります。{@link android.content.ServiceConnection} にはシステムが {@link android.os.IBinder} の配信用に呼び出すコールバック メソッドが含まれています。 +

+ +

注: サービスにバインドできるのは、アクティビティ、サービス、コンテンツ プロバイダのみです。ブロードキャスト レシーバーからサードパーティビスにはバインドできません。 +—

+ +

そのため、クライアントからサービスにバインドするには次の操作が必要です。

+
    +
  1. {@link android.content.ServiceConnection} を実装する。 +

    実装では次の 2 つのコールバック メソッドをオーバーライドする必要があります。

    +
    +
    {@link android.content.ServiceConnection#onServiceConnected onServiceConnected()}
    +
    システムがこれを呼び出して、サービスの {@link android.app.Service#onBind onBind()} メソッドから返された {@link android.os.IBinder} を配信します。 +
    +
    {@link android.content.ServiceConnection#onServiceDisconnected +onServiceDisconnected()}
    +
    サービスがクラッシュしたり強制終了されたりした場合など、サービスへの接続が予期せず失われたときに、Android システムがこれを呼び出します。 +これは、クライアントのアンバウンドの際には呼び出されません。 +
    +
    +
  2. +
  3. {@link +android.content.Context#bindService bindService()} を呼び出して、{@link +android.content.ServiceConnection} の実装を渡します。
  4. +
  5. システムが {@link android.content.ServiceConnection#onServiceConnected +onServiceConnected()} コールバック メソッドを呼び出すと、インターフェースで定義されたメソッドを使用してサービスへの呼び出しを開始できます。 +
  6. +
  7. サービスとの接続を切断するには、{@link +android.content.Context#unbindService unbindService()} を呼び出します。 +

    クライアントが破棄されたときにはサービスからアンバウンドしますが、サービスが使用されていないときはシャットダウンできるよう、サービスとのやり取りが終了したときや、アクティビティが停止したときは常にアンバウンドする必要があります + +(バインドとアンバインドの適切なタイミングについては後半でさらに詳しく説明します)。 +

    +
  8. +
+ +

たとえば、次のスニペットは Binder クラスを拡張して先ほど作成したサービスにクライアントを接続しており、返された {@link android.os.IBinder} を {@code LocalService} クラスにキャストして、{@code +LocalService} インスタンスを要求することだけが必要になります。 + +

+ +
+LocalService mService;
+private ServiceConnection mConnection = new ServiceConnection() {
+    // Called when the connection with the service is established
+    public void onServiceConnected(ComponentName className, IBinder service) {
+        // Because we have bound to an explicit
+        // service that is running in our own process, we can
+        // cast its IBinder to a concrete class and directly access it.
+        LocalBinder binder = (LocalBinder) service;
+        mService = binder.getService();
+        mBound = true;
+    }
+
+    // Called when the connection with the service disconnects unexpectedly
+    public void onServiceDisconnected(ComponentName className) {
+        Log.e(TAG, "onServiceDisconnected");
+        mBound = false;
+    }
+};
+
+ +

この {@link android.content.ServiceConnection} を使用して、クライアントは {@link android.content.Context#bindService bindService()} に渡すことでサービスにバインドできます。 +次に例を示します。

+ +
+Intent intent = new Intent(this, LocalService.class);
+bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
+
+ + + + +

その他の注意点

+ +

サービスへのバインドに関する重要な注意点は次のとおりです。

+ + +

他のサンプル コードについては、 ApiDemos{@code +RemoteService.java} クラスをご覧ください。

+ + + + + +

バインドされたサービスのライフサイクルを管理する

+ +

サービスがすべてのクライアントからアンバインドされると、Android システムがそれを破棄します({@link android.app.Service#onStartCommand onStartCommand()} でも開始された場合を除く)。 +純粋にバインドされたサービスであれば、サービスのライフサイクルを管理する必要はありません。サービスがクライアントにバインドされているかどうかに基づいて Android システムがそれを管理します。 + +—

+ +

ただし、{@link android.app.Service#onStartCommand +onStartCommand()} コールバック メソッドを実装する場合は、サービスは 開始されたとみなされるため、明示的に停止する必要があります。 +この場合、クライアントにバインドされているかどうかにかかわらず、サービスが {@link android.app.Service#stopSelf()} で自ら停止するまで、または他のコンポーネントが {@link +android.content.Context#stopService stopService()} を呼び出すまで実行し続けます。 + +

+ +

さらに、サービスが開始されてバインドを許可する場合、システムが {@link android.app.Service#onUnbind onUnbind()} メソッドを呼び出すとき、次回クライアントがサービスにバインドするときに {@link android.app.Service#onRebind +onRebind()} への呼び出しを受け取りたい場合は、{@code true} を返すようにすることもできます({@link +android.app.Service#onBind onBind()} への呼び出しを受け取る代わりに)。{@link android.app.Service#onRebind +onRebind()} からは void が返されますが、クライアントは {@link android.content.ServiceConnection#onServiceConnected onServiceConnected()} コールバックで {@link android.os.IBinder} を受け取ります。下の図 1 は、ライフサイクルのこの種の論理を表しています。 + + + +

+ + + +

図 1. 開始されたサービスでバインドを許可するサービスのライフサイクル +

+ + +

開始されたサービスのライフサイクルの詳細については、「サービス」のドキュメントをご覧ください。

+ + + + diff --git a/docs/html-intl/intl/ja/guide/components/fragments.jd b/docs/html-intl/intl/ja/guide/components/fragments.jd new file mode 100644 index 0000000000000000000000000000000000000000..31fb7f5020b22bdf40a70f8a8ec871c5ccebe218 --- /dev/null +++ b/docs/html-intl/intl/ja/guide/components/fragments.jd @@ -0,0 +1,812 @@ +page.title=フラグメント +parent.title=アクティビティ +parent.link=activities.html +@jd:body + + + +

{@link android.app.Fragment} は、{@link android.app.Activity} でのユーザー インターフェースの挙動や部位を表すものです。 +1 つのアクティビティに複数のフラグメントを組み合わせてマルチペインの UI をビルドしたり、複数のアクティビティでフラグメントを再利用したりできます。 +フラグメントとは、アクティビティのモジュール型セクションのようなもので、独自のライフサイクルを持ち、独自の入力イベントを受信します。フラグメントはアクティビティの実行中に追加したり削除したりできます(別のアクティビティで再利用できる「サブ アクティビティ」のようなものです)。 + + +

+ +

フラグメントは常にアクティビティに埋め込まれている必要があり、フラグメントのライフサイクルはホストのアクティビティのライフサイクルの影響を直接受けます。 +たとえば、アクティビティが一時停止しているとき、その中のすべてのフラグメントも一時停止し、アクティビティが破棄されると、すべてのフラグメントも破棄されます。 +ただし、アクティビティの実行中(ライフサイクル再開された状態)は、追加や削除といった操作は各フラグメントだけで行うことができます。 + +このようなフラグメントのトランザクションを実行するとき、アクティビティで管理されているバックスタックにそれを追加することもできます。アクティビティの各バックスタック エントリは、発生したフラグメントのトランザクションの記録になります。 + +— +バックスタックでは、[戻る] ボタンを押すとフラグメントのトランザクションを戻す(前に戻る)ことができます。 +

+ +

アクティビティのレイアウトの一部としてフラグメントを追加すると、フラグメントはアクティビティのビュー階層の {@link +android.view.ViewGroup} に置かれ、フラグメントが自身のビュー レイアウトを定義します。フラグメントをアクティビティのレイアウトに挿入するには、アクティビティのレイアウト ファイルでフラグメントを {@code <fragment>} として定義するか、アプリケーション コードで既存の {@link android.view.ViewGroup} に追加します。 + + + +ただし、フラグメントは必ずしもアクティビティの一部になる必要はなく、アクティビティの目に見えないワーカーとして、フラグメント独自の UI なしで使用することもできます。 + +

+ +

このドキュメントでは、アクティビティのバックスタックに追加した時のフラグメントの状態を維持する方法、アクティビティ内でアクティビティと他のフラグメントとイベントを共有する方法、アクティビティのアクションバーへの影響など、フラグメントを使用してアプリケーションをビルドする方法について説明します。 + + +

+ + +

デザインの指針

+ +

Android では、タブレットなどの大画面でのよりダイナミックで柔軟な UI デザインに対応するため、Android 3.0(API レベル 11)でフラグメントが採用されました。 +タブレットの画面はハンドセットよりもかなり大きいため、UI コンポーネントを組み合わせたり入れ替えたりできる領域が広くなります。 + +フラグメントでは、ビュー階層に複雑な変更を加えることなく、そのようなデザインを実現できます。 +アクティビティのレイアウトをフラグメントに分割することで、アクティビティの外観を実行時に変更でき、それらの変更をアクティビティが管理するバックスタックに保持できます。 + +

+ +

たとえば新しいアプリケーションでは、1 つのフラグメントを使って左側に記事の一覧を表示したり、別のフラグメントを使って右側に記事の内容を表示したりできます。両方のフラグメントが 1 つのアクティビティに横並びに表示され、それぞれのフラグメントには自身のライフサイクル メソッドがあり、それぞれのユーザー入力イベントを処理します。 +— + +つまり、1 つのアクティビティで記事を選択して、別のアクティビティで記事を閲覧するのではなく、図 1 のタブレットのレイアウトのように 1 つのアクティビティ内で記事を選択して閲覧できるようになります。 + +

+ +

各フラグメントはモジュール型の、再利用可能なアクティビティ コンポーネントとしてデザインする必要があります。各フラグメントは独自のライフサイクル コールバックを使用して自身のレイアウトと挙動とを定義するため、1 つのフラグメントを複数のアクティビティに含めることができます。このことから、再利用可能なデザインを用いることに加えて、1 つのフラグメントを他のフラグメントから直接操作しないようにする必要があります。 + + +これは、モジュール型のフラグメントでは画面のサイズごとにフラグメントの組み合わせを変更できる点からも、特に重要です。 +タブレットとハンドセットの両方に対応したアプリケーションをデザインする場合、異なるレイアウト構成でフラグメントを再利用することで、利用可能な画面の領域に応じて最適な使い心地を実現できます。 + +たとえばハンドセットの場合、同一のアクティビティ内に 2 つ以上のフラグメントが収まりきらないときは、フラグメントを分割してシングルペインの UI を提示する必要があることもあります。 + +

+ + +

図 1. 1 つのアクティビティ内で、フラグメントで定義された 2 つの UI モジュールを組み合わせたタブレット用デザインと、それぞれを分けたハンドセット用デザインの例。 + +

+ +

引き続きニュース アプリケーションの例を使うと、アプリケーションがタブレット サイズの端末で実行中は、 2 つのフラグメントをアクティビティ A に埋め込むことができます。—— +しかし、ハンドセット サイズの画面では両方のフラグメントを表示する領域が足りないため、アクティビティ A には記事の一覧のフラグメントだけが含まれます。記事を選択すると、記事を閲覧するための 2 つ目のフラグメントが含まれるアクティビティ B が開始されます。 + + +そのため、図 1 のようにアプリケーションはフラグメントを異なる組み合わせで再利用することで、タブレットとハンドセットの両方に対応できるようになります。 + +

+ +

異なる画面構成ごとに異なるフラグメントの組み合わせを用いたアプリケーションのデザインの詳細については、「Supporting Tablets and Handsets」のガイドをご覧ください。 +

+ + + +

フラグメントを作成する

+ +
+ +

図 2. フラグメントのライフサイクル(アクティビティの実行中) +

+
+ +

フラグメントを作成するには、{@link android.app.Fragment} のサブクラス(またはその既存のサブクラス)を作成する必要があります。 +{@link android.app.Fragment} クラスには、{@link android.app.Activity} に非常に似ているコードがあります。 +これには、{@link android.app.Fragment#onCreate onCreate()}、{@link android.app.Fragment#onStart onStart()}、{@link android.app.Fragment#onPause onPause()}、{@link android.app.Fragment#onStop onStop()} のようなアクティビティと同様のコールバック メソッドが含まれています。 + +実際に、既存の Android アプリケーションを変換してフラグメントを使用するには、アクティビティのコールバック メソッドから、フラグメントの各コールバック メソッドにコードを移動させるだけで。 + + +

+ +

通常は、少なくとも次のライフサイクル メソッドを実装する必要があります。

+ +
+
{@link android.app.Fragment#onCreate onCreate()}
+
フラグメントの作成時にシステムが呼び出します。実装内で、フラグメントが一時停止、停止、再開されたときに保持するフラグメントの必須コンポーネントを初期化する必要があります。 + +
+
{@link android.app.Fragment#onCreateView onCreateView()}
+
フラグメントが初めてユーザー インターフェースを描画するタイミングでシステムがこれを呼び出します。 +フラグメントの UI を描画するには、このメソッドからフラグメントのレイアウトのルートとなっている {@link android.view.View} を返す必要があります。 +フラグメントが UI を提示しない場合は、null を返すことができます。 +
+
{@link android.app.Activity#onPause onPause()}
+
ユーザーがフラグメントから離れたことを初めて示すときに、このメソッドを呼び出します(フラグメントが破棄されていない場合も含む)。 +通常はここで、現在のユーザー セッション後も維持する必要のある変更点を保存しておきます(ユーザーが戻ってこない可能性があるため)。 + +
+
+ +

ほとんどのアプリケーションでは、各フラグメントで少なくともこれら 3 つのメソッドを実装する必要がありますが、フラグメントのライフサイクルのさまざまなステージを処理する際に使用する他のコールバック メソッドもいくつかあります。 + +すべてのライフサイクル コールバック メソッドについては、フラグメントのライフサイクルの処理のセクションで説明します。 +

+ + +

基本の {@link +android.app.Fragment} クラスの代わりに拡張できるサブクラスもいくつかあります。

+ +
+
{@link android.app.DialogFragment}
+
フローティング ダイアログを表示します。{@link android.app.Activity} のダイアログ ヘルパー メソッド代わりにこのクラスを使用してダイアログを作成すると、アクティビティで管理されるフラグメントのバックスタックにフラグメント ダイアログを組み込むことができるため、ユーザーは終了したフラグメントに戻ることが可能になります。 + + +
+ +
{@link android.app.ListFragment}
+
{@link android.app.ListActivity} と同様に、アダプタで管理されるアイテムのリストを表示します({@link +android.widget.SimpleCursorAdapter} など)。クリック イベントを処理するための {@link +android.app.ListFragment#onListItemClick(ListView,View,int,long) onListItemClick()} コールバックなど、リスト ビューを管理するメソッドがいくつか提供されます。 + +
+ +
{@link android.preference.PreferenceFragment}
+
{@link android.preference.PreferenceActivity} と同様に、{@link android.preference.Preference} オブジェクトの階層をリストとして表示します。 +アプリケーションの「設定」アクティビティの作成時に便利です。 +
+
+ + +

ユーザー インターフェースを追加する

+ +

通常、フラグメントはアクティビティのユーザー インターフェースの一部であり、独自のレイアウトをアクティビティに提示します。 +

+ +

フラグメントのレイアウトを提供するには、{@link +android.app.Fragment#onCreateView onCreateView()} コールバック メソッドを実装する必要があります。これは、フラグメントがレイアウトを描画するタイミングで Android システムが呼び出します。 +このメソッドの実装では、フラグメントのレイアウトのルートである {@link android.view.View} を返す必要があります。 +

+ +

注: フラグメントは {@link +android.app.ListFragment} のサブクラスの場合、デフォルトの実装で {@link android.widget.ListView} が{@link android.app.Fragment#onCreateView onCreateView()} から返されるため、これを実装する必要はありません。 +

+ +

{@link +android.app.Fragment#onCreateView onCreateView()} からレイアウトを返すには、XML で定義したレイアウト リソースからインフレートできます。これを行うため、{@link android.app.Fragment#onCreateView onCreateView()} から {@link android.view.LayoutInflater} オブジェクトが提供されます。 + +

+ +

たとえば、次の例では {@link android.app.Fragment} のサブクラスが {@code example_fragment.xml} ファイルからレイアウトをロードします。 +

+ +
+public static class ExampleFragment extends Fragment {
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+                             Bundle savedInstanceState) {
+        // Inflate the layout for this fragment
+        return inflater.inflate(R.layout.example_fragment, container, false);
+    }
+}
+
+ + + +

{@link android.app.Fragment#onCreateView +onCreateView()} に渡される {@code container} パラメータは、フラグメントのレイアウトが挿入される {@link android.view.ViewGroup} の親になります(アクティビティのレイアウトから)。 + +{@code savedInstanceState} パラメータは、フラグメントが再開された場合にフラグメントの前のインスタンスに関する情報を提供する {@link android.os.Bundle} です(状態の復元の詳細については、フラグメントのライフサイクルの処理で説明します)。 + + +

+ +

{@link android.view.LayoutInflater#inflate(int,ViewGroup,boolean) inflate()} メソッドは、次の 3 つの引数を受け取ります。 +

+ + +

ここまで、レイアウトを提供するフラグメントの作成方法について説明しました。次は、フラグメントをアクティビティに追加する必要があります。 +

+ + + +

フラグメントをアクティビティに追加する

+ +

通常、フラグメントはホスト アクティビティに UI の一部を提供し、アクティビティの全体的なビュー階層の一部として埋め込まれます。 +アクティビティのレイアウトにフラグメントを追加する方法は 2 つあります。 +

+ + + + +

UI のないフラグメントを追加する

+ +

上の例では、UI を提供するためにフラグメントをアクティビティに追加する方法を紹介しましたが、UI を提示せずにアクティビティのバックグラウンド動作を提供するためにフラグメントを使うこともできます。 + +

+ +

UI のないフラグメントを追加するには、{@link +android.app.FragmentTransaction#add(Fragment,String)} を使用してアクティビティからフラグメントを追加します(ビュー ID ではなくフラグメントの一意の文字列である「タグ」を提供します)。 +これでフラグメントが追加されますが、アクティビティ レイアウトのビューには関連付けられていないため、{@link +android.app.Fragment#onCreateView onCreateView()} への呼び出しは受け取りません。 +そのため、このメソッドを実装する必要はありません。

+ +

フラグメントに文字列のタグを提供するのは UI のないフラグメントの場合だけではありません。UI のあるフラグメントにも文字列のタグを提供することはできますが、UI のないフラグメントにとっては、文字列のタグがフラグメントを識別する唯一の手段になります。— +— +後でアクティビティからフラグメントを取得する場合は、 {@link android.app.FragmentManager#findFragmentByTag +findFragmentByTag()} を使用する必要があります。 +

+ +

たとえば、フラグメントを UI を持たないバックグラウンド ワーカーとして使用するアクティビティの場合は、SDK サンプルに含まれている(Android SDK マネージャーで利用可能){@code +FragmentRetainInstance.java} のサンプルでシステムに <sdk_root>/APIDemos/app/src/main/java/com/example/android/apis/app/FragmentRetainInstance.java として置かれているものをご覧ください。 + +

+ + + +

フラグメントを管理する

+ +

アクティビティのフラグメントを管理するには、{@link android.app.FragmentManager} を使用する必要があります。フラグメントを取得するには、アクティビティから {@link android.app.Activity#getFragmentManager()} を呼び出します。 +

+ +

{@link android.app.FragmentManager} では、次の操作が可能です。

+ + + +

これらのメソッドや他のメソッドの詳細については、{@link +android.app.FragmentManager} クラスのドキュメントをご覧ください。

+ +

前のセクションで説明したように、{@link android.app.FragmentManager} を使用して {@link android.app.FragmentTransaction} を開くことで、フラグメントの追加や削除といったトランザクションを実行することもできます。 + +

+ + +

フラグメントのトランザクションを実行する

+ +

アクティビティでのフラグメントの使用における優れた機能として、フラグメントを追加、削除、置換したり、ユーザー操作への応答にフラグメントを使用して他のアクションを実行したりできる点が挙げられます。 +アクティビティに加える変更はそれぞれ 1 つのトランザクションとして、{@link +android.app.FragmentTransaction} の API を使用して実行できます。 +各トランザクションはアクティビティが管理するバックスタックに保存でき、ユーザーはフラグメントの変更を元に戻すことができます(アクティビティ間で元に戻す動作と似ています)。 + +

+ +

{@link android.app.FragmentTransaction} のインスタンスは、次のように {@link +android.app.FragmentManager} から作成できます。

+ +
+FragmentManager fragmentManager = {@link android.app.Activity#getFragmentManager()};
+FragmentTransaction fragmentTransaction = fragmentManager.{@link android.app.FragmentManager#beginTransaction()};
+
+ +

各トランザクションは、同時に実行する一連の変更点です。{@link +android.app.FragmentTransaction#add add()}、{@link android.app.FragmentTransaction#remove remove()}、{@link android.app.FragmentTransaction#replace replace()} などのメソッドを使って、トランザクションで実行するすべての変更点をセットアップできます。 + +次に、トランザクションをアクティビティに適用するには、{@link android.app.FragmentTransaction#commit()} を呼び出す必要があります。 +

+ + +

ただし、{@link +android.app.FragmentTransaction#commit()} を呼び出す前に、{@link +android.app.FragmentTransaction#addToBackStack addToBackStack()} を呼び出して、フラグメントのトランザクションのバックスタックにトランザクションを追加することもできます。 +このバックスタックはアクティビティによって管理され、ユーザーが [戻る] ボタンを押して前のフラグメントの状態に戻れるようにします。 +

+ +

例として、フラグメントを別のフラグメントに置き換えて、バックスタックで前の状態を保持する方法を次に示します。 +

+ +
+// Create new fragment and transaction
+Fragment newFragment = new ExampleFragment();
+FragmentTransaction transaction = getFragmentManager().beginTransaction();
+
+// Replace whatever is in the fragment_container view with this fragment,
+// and add the transaction to the back stack
+transaction.replace(R.id.fragment_container, newFragment);
+transaction.addToBackStack(null);
+
+// Commit the transaction
+transaction.commit();
+
+ +

この例では、{@code R.id.fragment_container} ID で識別されるレイアウト コンテナに現在あるフラグメント(存在する場合)を {@code newFragment} が置き換えます。{@link +android.app.FragmentTransaction#addToBackStack addToBackStack()} を呼び出すと、置き換えのトランザクションがバックスタックに保存されるため、ユーザーはトランザクションを元に戻して、[戻る] ボタンを押すことで前のフラグメントに戻れるようになります。 + + +

+ +

トランザクションに複数の変更を加えて({@link +android.app.FragmentTransaction#add add()} や {@link android.app.FragmentTransaction#remove +remove()} など)、{@link +android.app.FragmentTransaction#addToBackStack addToBackStack()} を呼び出した場合、{@link android.app.FragmentTransaction#commit commit()} を呼び出す前に適用されたすべての変更点が 1 つのトランザクションとしてバックスタックに追加され、[戻る] ボタンを押すとすべてが同時に元に戻るようになります。 + +

+ +

次の場合を除き、{@link android.app.FragmentTransaction} に加える変更の順序は影響しません。 +

+ + +

フラグメントを削除するトランザクションの実行時に {@link android.app.FragmentTransaction#addToBackStack(String) +addToBackStack()} を呼び出さない場合、トランザクションの実行時にそのフラグメントは破棄され、ユーザーは操作を元に戻すことができなくなります。 +一方で、フラグメントの削除時に {@link android.app.FragmentTransaction#addToBackStack(String) addToBackStack()} を呼び出した場合、フラグメントは停止状態になり、ユーザーが元に戻した時に再開します。 + + +

+ +

ヒント: それぞれのフラグメントのトランザクションでは、コミットする前に {@link android.app.FragmentTransaction#setTransition setTransition()} と呼ばれる遷移のアニメーションを適用できます。 + +

+ +

{@link android.app.FragmentTransaction#commit()} を呼び出してもトランザクションはすぐには実行されません。 +具体的には、アクティビティの UI スレッド(「メイン」スレッド)で実行可能になったときにすぐに実行されるようスケジュールされます。 +ただし、必要であれば UI スレッドから {@link +android.app.FragmentManager#executePendingTransactions()} を呼び出して、{@link android.app.FragmentTransaction#commit()} から送信されたトランザクションをすぐに実行することもできます。 +通常、トランザクションが他のスレッドのジョブに依存していない限り、この操作は必要ありません。 +

+ +

警告: {@link +android.app.FragmentTransaction#commit commit()} を使用したトランザクションをコミットできるのは、アクティビティが状態を保存する前(ユーザーがアクティビティを離れるとき)にのみに限定されます。 +それ以降にコミットしようとすると、例外がスローされます。 +これは、アクティビティの復元が必要な場合に、コミット後のステータスが失われてしまう可能性があるためです。 +コミットが失われてもよい場合は、{@link +android.app.FragmentTransaction#commitAllowingStateLoss()} を使用します。

+ + + + +

アクティビティと通信する

+ +

{@link android.app.Fragment} が {@link android.app.Activity} から独立したフラグメントとして実行されていて、複数のアクティビティ内で使用可能であっても、フラグメントの特定のインスタンスは、それが含まれるアクティビティに直接結び付いています。 + +

+ +

具体的には、フラグメントは {@link +android.app.Fragment#getActivity()} を使用して {@link android.app.Activity} インスタンスにアクセスでき、アクティビティのレイアウトでビューを見つけるなどのタスクを簡単に実行できます。 +

+ +
+View listView = {@link android.app.Fragment#getActivity()}.{@link android.app.Activity#findViewById findViewById}(R.id.list);
+
+ +

同様に、アクティビティが {@link +android.app.FragmentManager#findFragmentById findFragmentById()} や {@link +android.app.FragmentManager#findFragmentByTag findFragmentByTag()} を使って {@link android.app.FragmentManager} から {@link android.app.Fragment} への参照を取得することで、フラグメント内のメソッドを呼び出すこともできます。 +次に例を示します。

+ +
+ExampleFragment fragment = (ExampleFragment) getFragmentManager().findFragmentById(R.id.example_fragment);
+
+ + +

アクティビティへのイベント コールバックを作成する

+ +

アクティビティとイベントを共有するフラグメントが必要になる場面もあります。これを実現するには、フラグメント内でコールバック インターフェースを定義して、ホスト アクティビティがそれを実装するよう要求します。 + +アクティビティがインターフェースを介してコールバックを受け取ると、必要に応じてレイアウト内の他のフラグメントと情報を共有します。 +

+ +

たとえば、新しいアプリケーションでアクティビティ内に、記事のリストを表示するフラグメント(フラグメント A)と、記事を表示するフラグメント(フラグメント B)の 2 つのフラグメントがある場合、リストのアイテムが選択されたときに、フラグメント B に記事を表示するよう伝えるため、フラグメント A から選択されたことをアクティビティに伝える必要があります。— +— +ここでは、{@code OnArticleSelectedListener} インターフェースがフラグメント A で宣言されています。 +

+ +
+public static class FragmentA extends ListFragment {
+    ...
+    // Container Activity must implement this interface
+    public interface OnArticleSelectedListener {
+        public void onArticleSelected(Uri articleUri);
+    }
+    ...
+}
+
+ +

次に、フラグメントのホストであるアクティビティが {@code OnArticleSelectedListener} インターフェースを実装して {@code onArticleSelected()} をオーバーライドし、フラグメント B にフラグメント A からのイベントを通知します。ホスト アクティビティがこのインターフェースを確実に実装するようにするため、フラグメント A の {@link +android.app.Fragment#onAttach onAttach()} コールバック メソッド(フラグメントをアクティビティに追加するときにシステムが呼び出すメソッド)が、{@link android.app.Fragment#onAttach +onAttach()} に渡される {@link android.app.Activity} をキャストして {@code OnArticleSelectedListener} のインスタンスを登録します。 + + + + +

+ +
+public static class FragmentA extends ListFragment {
+    OnArticleSelectedListener mListener;
+    ...
+    @Override
+    public void onAttach(Activity activity) {
+        super.onAttach(activity);
+        try {
+            mListener = (OnArticleSelectedListener) activity;
+        } catch (ClassCastException e) {
+            throw new ClassCastException(activity.toString() + " must implement OnArticleSelectedListener");
+        }
+    }
+    ...
+}
+
+ +

アクティビティがインターフェースを実装していない場合は、フラグメントは {@link java.lang.ClassCastException} をスローします。成功すると、{@code mListener} メンバーがアクティビティの {@code OnArticleSelectedListener} の実装への参照を保持でき、フラグメント A が {@code OnArticleSelectedListener} インターフェースで定義されたコールバック メソッドを呼び出すことでアクティビティとイベントを共有できるようになります。 + + + +たとえば、フラグメント A は {@link android.app.ListFragment} の拡張で、ユーザーがリストアイテムをクリックするたびに、システムがフラグメントの {@link android.app.ListFragment#onListItemClick +onListItemClick()} を呼び出し、それが{@code onArticleSelected()} を呼び出してアクティビティとイベントを共有します。 + + +

+ +
+public static class FragmentA extends ListFragment {
+    OnArticleSelectedListener mListener;
+    ...
+    @Override
+    public void onListItemClick(ListView l, View v, int position, long id) {
+        // Append the clicked item's row ID with the content provider Uri
+        Uri noteUri = ContentUris.{@link android.content.ContentUris#withAppendedId withAppendedId}(ArticleColumns.CONTENT_URI, id);
+        // Send the event and Uri to the host activity
+        mListener.onArticleSelected(noteUri);
+    }
+    ...
+}
+
+ +

{@link +android.app.ListFragment#onListItemClick onListItemClick()} に渡される {@code id} パラメータはクリックされたアイテムの行 ID で、アクティビティ(または他のフラグメント)がアプリケーションの {@link +android.content.ContentProvider} から記事を取得する際に使用します。 +

+ +

コンテンツ プロバイダの使用に関する詳細については、「コンテンツ プロバイダ」のドキュメントをご覧ください。 +

+ + + +

アクションバーにアイテムを追加する

+ +

フラグメントは、{@link android.app.Fragment#onCreateOptionsMenu(Menu,MenuInflater) onCreateOptionsMenu()} を実装することでアクティビティのオプション メニュー(とそのアクション バー)にメニュー アイテムを提供することができます。 +ただし、このメソッドが呼び出しを受け取るには、{@link +android.app.Fragment#onCreate(Bundle) onCreate()} の間に {@link +android.app.Fragment#setHasOptionsMenu(boolean) setHasOptionsMenu()} を呼び出して、フラグメントがオプション メニューにアイテムを追加することを示す必要があります(これを行わない場合、フラグメントは {@link android.app.Fragment#onCreateOptionsMenu onCreateOptionsMenu()} への呼び出しを受け取りません)。 + + +

+ +

フラグメントから追加するアイテムはすべて、既存のメニュー アイテムに追加されます。 +また、メニュー アイテムが選択されたとき、フラグメントは {@link +android.app.Fragment#onOptionsItemSelected(MenuItem) onOptionsItemSelected()} へのコールバックも受け取ります。 +

+ +

また、{@link +android.app.Fragment#registerForContextMenu(View) registerForContextMenu()} を呼び出して、フラグメントのレイアウトにビューを登録してコンテキスト メニューを提供することもできます。ユーザーがコンテキスト メニューを開くと、フラグメントは {@link +android.app.Fragment#onCreateContextMenu(ContextMenu,View,ContextMenu.ContextMenuInfo) +onCreateContextMenu()} への呼び出しを受け取ります。 +ユーザーがアイテムを選択すると、フラグメントは {@link +android.app.Fragment#onContextItemSelected(MenuItem) onContextItemSelected()} への呼び出しを受け取ります。

+ +

注: 追加するメニュー アイテムごとにフラグメントは on-item-selected コールバックを受け取りますが、ユーザーがメニュー アイテムを選択したときに最初にそれぞれのコールバックを受け取るのはアクティビティになります。 + +アクティビティの on-item-selected コールバックの実装で、選択されたアイテムが処理されなかった場合、イベントはフラグメントのコールバックに渡されます。 +この挙動は、オプション メニューとコンテキスト メニューの両方に適用されます。 +

+ +

メニューの詳細については、デベロッパー ガイドの「メニュー」と「アクションバー」をご覧ください。

+ + + + +

フラグメントのライフサイクルを処理する

+ +
+ +

図 3. フラグメントのライフサイクルに対するアクティビティのライフサイクルの影響。 +

+
+ +

フラグメントのライフサイクルの管理は、アクティビティのライフサイクルの管理によく似ています。アクティビティと同様に、フラグメントには次の 3 つの状態があります。 +

+ +
+
再開状態
+
フラグメントが実行中のアクティビティで表示されている。
+ +
一時停止状態
+
他のアクティビティがフォアグラウンドにあり、フォーカスがあるが、このアクティビティも表示されている(フォアグラウンドにある別のアクティビティは半透明になっているか、全画面をカバーしていない)。 + +
+ +
停止状態
+
フラグメントは表示されていない。ホスト アクティビティが停止状態か、フラグメントがアクティビティから削除されたが、バックスタックに追加されている。 +停止状態のフラグメントはまだ存続しています(すべての状態とメンバー情報がシステムで保持されています)。 +ただし、アクティビティが破棄されるとユーザーには表示されなくなり、フラグメントも破棄されます。 +
+
+ +

さらに、アクティビティと同様に、アクティビティのプロセスが破棄されて、アクティビティが再作成されたときにフラグメントの状態を復元する必要がある場合は、 {@link +android.os.Bundle} を使用してフラグメントの状態を保持できます。 +フラグメントの {@link +android.app.Fragment#onSaveInstanceState onSaveInstanceState()} コールバックの間に状態を保存して、{@link android.app.Fragment#onCreate onCreate()}、{@link +android.app.Fragment#onCreateView onCreateView()}、{@link +android.app.Fragment#onActivityCreated onActivityCreated()} の間にそれを復元できます。 +状態の保存の詳細については、「Activities」のドキュメントをご覧ください。 + +

+ +

アクティビティとフラグメントのライフサイクルの最も重要な違いは、バックスタックでのそれぞれの保存方法です。 +アクティビティは、デフォルトで停止状態の時にシステムが管理するアクティビティのバックスタックに置かれますが(タスクとバックスタック で説明したようにユーザーが [戻る] ボタンで前に戻ることができるように)、フラグメントの場合は、フラグメントを削除するトランザクションの間に {@link +android.app.FragmentTransaction#addToBackStack(String) addToBackStack()} を呼び出してインスタンスを保存するよう明示的に要求した場合のみ、ホスト アクティビティが管理するバックスタックに置かれます。 + + + + +

+ +

これ以外については、フラグメントのライフサイクルの管理は、アクティビティのライフサイクルの管理とほとんど同じです。 +そのため、アクティビティのライフサイクルの管理におけるベスト プラクティスがフラグメントにも適用されます。 +もう 1 つ理解しておくべき事項は、アクティビティの寿命がどのようにフラグメントの寿命に影響を与えるかという点です。 +

+ +

警告: {@link android.app.Fragment} 内に {@link android.content.Context} オブジェクトが必要な場合は、{@link android.app.Fragment#getActivity()} を呼び出すことができます。ただし、{@link android.app.Fragment#getActivity()} はフラグメントがアクティビティにアタッチされている場合にのみ呼び出すようにしてください。 + + +フラグメントがまだアタッチされていない場合や、ライフサイクルの終了時にデタッチされた場合は、{@link android.app.Fragment#getActivity()} は null を返します。 +

+ + +

アクティビティのライフサイクルと連携する

+ +

フラグメントが含まれるアクティビティのライフサイクルは、フラグメントのライフサイクルに直接影響し、アクティビティに対する各ライフサイクル コールバックは、各フラグメントに対して同様のコールバックをもたらします。 + +たとえば、アクティビティが {@link android.app.Activity#onPause} を受け取ると、アクティビティ内の各フラグメントが {@link android.app.Fragment#onPause} を受け取ります。 +

+ +

ただし、フラグメントにはフラグメントの UI をビルドしたり破棄したりするアクションを実行する際のアクティビティとの独自のやり取りを処理する追加のライフサイクル コールバックがいくつかあります。これらの追加のコールバック メソッドは次のとおりです。 + +

+ +
+
{@link android.app.Fragment#onAttach onAttach()}
+
フラグメントがアクティビティと関連付けられたときに呼び出されます(ここで {@link +android.app.Activity} が渡されます)。
+
{@link android.app.Fragment#onCreateView onCreateView()}
+
フラグメントに関連付けられたビュー階層を作成する際に呼び出されます。
+
{@link android.app.Fragment#onActivityCreated onActivityCreated()}
+
アクティビティの {@link android.app.Activity#onCreate +onCreate()} メソッドが戻ったときに呼び出されます。
+
{@link android.app.Fragment#onDestroyView onDestroyView()}
+
フラグメントに関連付けられたビュー階層が削除されたときに呼び出されます。
+
{@link android.app.Fragment#onDetach onDetach()}
+
フラグメントとアクティビティとの関連付けが解除されたときに呼び出されます。
+
+ +

図 3 は、ホスト アクティビティの影響を受けたフラグメントのライフサイクルのフローを表しています。 +この図から、アクティビティの一連の状態によって、フラグメントが受け取るコールバック メソッドがどのように決められているかがわかります。 +たとえば、アクティビティが {@link +android.app.Activity#onCreate onCreate()} コールバックを受け取ると、アクティビティ内のフラグメントは {@link android.app.Fragment#onActivityCreated onActivityCreated()} コールバックまでを受け取ります。 +

+ +

アクティビティが再開状態になると、アクティビティでのフラグメントの追加や削除を自由に行えます。 +つまり、アクティビティが再開された状態が、フラグメントのライフサイクルを独立して変更できる唯一の期間になります。 +

+ +

ただし、アクティビティが再開状態でなくなると、フラグメントはアクティビティによって再度ライフサイクル間を通過することになります。 +

+ + + + +

+ +

このドキュメントで解説した内容をすべて網羅するため、2 つのフラグメントを使用して 2 つのペインのレイアウトを作成するアクティビティの例を紹介します。 +次のアクティビティには、シェイクスピア劇のタイトル リストを表示するフラグメントと、リストから劇が選択されたときに劇のあらすじを表示するフラグメントがあります。 + +また、ここでは画面構成によって異なるフラグメント構成を提供する方法も示しています。 +

+ +

注: このアクティビティのすべてのソース コードは、{@code +FragmentLayout.java} で入手できます。 +

+ +

メイン アクティビティでは、通常の方法で {@link +android.app.Activity#onCreate onCreate()} の間にレイアウトを適用します。

+ +{@sample development/samples/ApiDemos/src/com/example/android/apis/app/FragmentLayout.java main} + +

適用されるレイアウトは {@code fragment_layout.xml} です。

+ +{@sample development/samples/ApiDemos/res/layout-land/fragment_layout.xml layout} + +

このレイアウトを使って、アクティビティがレイアウトをロードするとすぐにシステムが {@code TitlesFragment}(これが劇のタイトルをリストします)のインスタンスを作成します。このとき、{@link android.widget.FrameLayout} (劇のあらすじを表示するフラグメントの目的地)が画面右側を使用し、残りは空白の状態です。 + + +ご覧のように、フラグメントが {@link android.widget.FrameLayout} に置かれるのはユーザーがアイテムを選択してからになります。 +

+ +

ただし、画面構成によっては、劇の一覧とあらすじを横並びに表示するほどの幅がない場合もあります。 +そのため、上記のレイアウトは横方向の画面構成の場合のみ使用され、{@code res/layout-land/fragment_layout.xml} に保存されます。 +

+ +

画面が縦方向の場合、システムは次のレイアウトを適用して {@code res/layout/fragment_layout.xml} に保存します。 +

+ +{@sample development/samples/ApiDemos/res/layout/fragment_layout.xml layout} + +

このレイアウトには、{@code TitlesFragment} のみが含まれます。つまり、端末が縦方向の場合、劇のタイトルのみが表示されます。 +そのため、この構成時にユーザーがリストのアイテムをクリックしたとき、アプリケーションは 2 つ目のフラグメントをロードする代わりに新しいアクティビティを開始してあらすじを表示します。 + +

+ +

次に、フラグメントのクラスでこれをどう実現するのかを見ていきます。まず、{@code +TitlesFragment} はシェイクスピア劇のタイトル リストを表示します。このフラグメントは {@link +android.app.ListFragment} を拡張し、リスト ビューの動作の処理のほとんどを委ねます。

+ +

このコードをよく見ると、ユーザーがリストのアイテムをクリックしたときの挙動が 2 つ考えられます。2 つのレイアウトのどちらがアクティブになっているかによって、新しいフラグメントを作成して表示することで同じアクティビティで詳細を表示するか({@link +android.widget.FrameLayout} にフラグメントを追加する)、新しいアクティビティを(フラグメントを表示できる場所に)開始するかのいずれかになります。 + +

+ +{@sample development/samples/ApiDemos/src/com/example/android/apis/app/FragmentLayout.java titles} + +

2 つ目のフラグメントである {@code DetailsFragment} は、{@code TitlesFragment} のリストで選択された劇のあらすじを表示します。 +

+ +{@sample development/samples/ApiDemos/src/com/example/android/apis/app/FragmentLayout.java details} + +

{@code TitlesFragment} クラスで説明したように、ユーザーがリストアイテムをクリックしたときに、現在のレイアウトに{@code R.id.details} ビュー({@code DetailsFragment} が属する場所)が含まれていない場合、アプリケーションは {@code DetailsActivity} のアクティビティを開始してアイテムのコンテンツを表示します。 + + +

+ +

次の {@code DetailsActivity} では、画面が縦方向のときに単純に {@code DetailsFragment} を埋め込んで選択された劇のあらすじを表示します。 +

+ +{@sample development/samples/ApiDemos/src/com/example/android/apis/app/FragmentLayout.java +details_activity} + +

構成が横方向の場合はアクティビティは自ら終了するため、メイン アクティビティが引き継いで {@code DetailsFragment} を {@code TitlesFragment} の横に表示できます。これは、ユーザーが縦方向のときに {@code DetailsActivity} を開始し、その後横方向に回転した(ここで現在のアクティビティが再開される)ときに起こることがあります。 + + +

+ + +

フラグメントを使用したその他のサンプルとこの例の完全なソース ファイルについては、ApiDemosSDK コンポーネントのサンプルから入手可能)にある API デモサンプルをご覧ください。 + +

+ + diff --git a/docs/html-intl/intl/ja/guide/components/fundamentals.jd b/docs/html-intl/intl/ja/guide/components/fundamentals.jd new file mode 100644 index 0000000000000000000000000000000000000000..46e98689a3ecc23aa6955577a8811ade1acedd0b --- /dev/null +++ b/docs/html-intl/intl/ja/guide/components/fundamentals.jd @@ -0,0 +1,480 @@ +page.title=アプリケーションの基礎 +@jd:body + + + +

Android アプリは Java プログラミング言語で記述されています。APKAndroid SDK ツールがコードを—データとリソース ファイルと共に、 +—APK (Android パッケージ)にコンパイルします。これは、 +{@code .apk} という接尾語の付いたアーカイブ ファイルです。1 つの APK ファイルにはAndroid アプリのすべてのコンテンツが含まれており、Android 端末はアプリをインストールする際にこのファイルを使用します。 +

+ +

端末にインストールすると、各 Android アプリはそれぞれのセキュリティ サンドボックス内で動作します。

+ + + +

このようにして、Android システムは最小権限の原則を実装しています。つまり、デフォルトでは各アプリにはコンポーネントの動作に必要な分だけのアクセス権が与えられます。 + +これにより、パーミッションの与えられていないシステムの一部にアプリはアクセスできないという非常に安全性の高い環境が作り出されます。 +

+ +

ただし、アプリが他のアプリとデータを共有したり、システムのサービスにアクセスしたりする方法もあります。 +

+ + + +

以上が、システム内の Android アプリの基本的な仕組みです。続いて、このドキュメントでは次の内容について説明します。 +

+ + + + +

アプリのコンポーネント

+ +

アプリのコンポーネントは、Android アプリに不可欠な構成要素です。各コンポーネントは、システムがアプリにアクセスするためのさまざまなエントリ ポイントになります。すべてのコンポーネントがユーザーの実際のエントリ ポイントになるわけではなく、中にはお互いに依存関係にあるものもありますが、それぞれが独自のエンティティとして存在し、特定の役割を担っています。各コンポーネントはアプリの全体的な動作を定義する固有の構成要素となっています。 + + +— +

+ +

アプリのコンポーネントには、4 つのタイプがあります。それぞれのタイプは、まったく異なる目的を果たし、コンポーネントの作成や破棄方法を定義するライフサイクルもそれぞれ異なります。 +

+ +

アプリのコンポーネントのタイプは次の 4 つです。

+ +
+ +
アクティビティ
+ +
アクティビティ 1 つのユーザー インターフェースで 1 つの画面を表すものです。たとえば、メール アプリには新着メールの一覧を表示する 1 つのアクティビティと、メールを作成するアクティビティ、メールを閲覧するアクティビティがそれぞれ別にあります。 + +メールアプリでは、複数のアクティビティが一体となって結合したユーザー操作を実現しますが、それぞれは他のものから独立しています。 + +それにより、別のアプリで複数のアクティビティの中から、1 つのアクティビティを開始できるようになります(メールアプリが許可している場合)。 +たとえば、カメラアプリでは写真を他のユーザーと共有するために、メール アプリの新規メールを作成するアクティビティを開始できます。 + + +

アクティビティは {@link android.app.Activity} のサブクラスとして実装されます。詳細については、デベロッパー ガイドの「Activities」をご覧ください。 + +

+
+ + +
サービス
+ +
サービス は、 長期間の操作やリモート プロセスを処理するためにバックグラウンドで実行するコンポーネントです。 +サービスは、ユーザー インターフェースを提供しません。 +たとえば、サービスはユーザーが別のアプリを使用している間にバックグラウンドで音楽を再生したり、ユーザーが別のアクティビティを操作している間にそれを妨げることなくネットワークからデータを取得したりします。 + +アクティビティなどの別のコンポーネントは、サービスを開始して実行したり、それをバインドしたりして操作することもできます。 + + +

サービスは {@link android.app.Service} のサブクラスとして実装されます。詳細については、デベロッパー ガイドの「サービス」をご覧ください。 + +

+
+ + +
コンテンツ プロバイダ
+ +
コンテンツ プロバイダ 共有されているアプリデータを管理します。データは、ファイル システム、SQLite データベース、ウェブ、アプリがアクセスできる、あらゆる永続性のストレージに保存できます。 + +コンテンツ プロバイダを介して、他のアプリがデータをクエリしたり、修正したりすることもできます(コンテンツ プロバイダが許可している場合)。 +たとえば、Android システムではユーザーの連絡先情報を管理するコンテンツ プロバイダを提供しています。 +このように、適切なパーミッションさえあれば、アプリからコンテンツ プロバイダの一部({@link +android.provider.ContactsContract.Data}など)に問い合わせて、特定の人物に関する情報を読み取ったり書き込んだりできます。 + + +

コンテンツ プロバイダは、アプリで非公開扱いの共有されていないデータを閲覧したり書き込んだりする場合にも役立ちます。 +たとえば、Note Pad のサンプル アプリでは、コンテンツ プロバイダを使用してメモを保存します。 +

+ +

コンテンツ プロバイダは {@link android.content.ContentProvider} のサブクラスとして実装され、他のアプリがトランザクションを実行できるようにする API の標準セットを実装する必要があります。 + +詳細については、デベロッパー ガイドの「Contetns Providers」をご覧ください。 +

+
+ + +
ブロードキャスト レシーバー
+ +
ブロードキャスト レシーバー システム全体のブロードキャスト アナウンスに応答するコンポーネントです。 +たとえば、画面がオフになった、バッテリ残量が低い、写真が撮影されたなど、システムに起因するブロードキャストはたくさんあります。アプリでもブロードキャストを開始でき、たとえば他のアプリに、一部のデータが端末にダウンロードされ、利用可能になったことを伝えることもできます。— + +— +ブロードキャスト レシーバーがユーザー インターフェースを表示することはありませんが、ステータスバー通知を作成して、ブロードキャスト イベントの発生時にユーザーに警告できます。 + +一般的には、ブロードキャスト レシーバーは他のコンポーネントへの単なる「入り口」であり、最小限の操作を行うことが前提となっています。 +たとえば、イベントに基づいた何らかの作業を実行するサービスを開始する場合などに適しています。 + + +

ブロードキャスト レシーバーは、{@link android.content.BroadcastReceiver} のサブクラスとして実装され、各ブロードキャストは {@link android.content.Intent} オブジェクトとして配信されます。 +詳細については、{@link android.content.BroadcastReceiver} クラスをご覧ください。 +

+
+ +
+ + + +

Android ならではのシステムデザインによって、どのアプリケーションからでも別のアプリケーションを開始できます。 +たとえば、ユーザーが端末のカメラで写真を撮影できるようにする場合、既にその機能を備えた別のアプリがあることを想定して、写真を撮影するアクティビティを自身で開発する代わりに、アプリがそれを使用できます。 + +カメラアプリを組み込んだり、コードにリンクしたりする必要もなく、単純に写真を撮影するカメラアプリのアクティビティを開始するだけです。 + + +完了後、写真はアプリに戻り、それを使用することもできます。ユーザー側には、カメラがアプリの一部であるかのように見えます。 +

+ +

システムがコンポーネントを開始すると、そのアプリのプロセスを開始し(まだ実行していない場合)、コンポーネントが必要とするクラスをインスタンス化します。 +たとえば、アプリで写真を撮影するカメラアプリのアクティビティを開始すると、そのアクティビティはアプリのプロセスではなく、カメラアプリのプロセスで実行します。そのため、他のシステムのアプリとは異なり、Android アプリのエントリ ポイントは 1 つではありません(たとえば、{@code main()} 関数はありません)。 + + + +

+ +

システムは、他のアプリへのアクセスを制限するファイル許可を使用して各アプリを別々のプロセスで実行するため、アプリから直接他のアプリのコンポーネントはアクティベートできませんが、Android システムはそれが可能です。 + +そのため、他のアプリのコンポーネントをアクティベートするには、特定のコンポーネントを開始するインテントを指定するメッセージをシステムに配信する必要があります。 + +その後、システムが代わりにコンポーネントをアクティベートします。

+ + +

コンポーネントをアクティベートする

+ +

4 つのコンポーネント タイプのなかで、アクティビティ、サービス、ブロードキャスト レシーバーの 3 つは、インテントと呼ばれる非同期メッセージによってアクティベートされます。コンポーネントがアプリに属していても他のものに属していても、インテントにより実行時に個別のコンポーネントがお互いに結び付けられます(他のコンポーネントからのアクションを要求するメッセンジャーのようなものです)。—— + + + +

+ +

インテントは {@link android.content.Intent} オブジェクトを使用して作成されます。このオブジェクトは特定のコンポーネントや特定のタイプのコンポーネントをアクティベートするようにメッセージを定義します。インテントはそれぞれ明示的、暗黙的のいずれかになります。— + +

+ +

アクティビティとサービスでは、インテントが実行するアクション(「閲覧」したり「送信」したりする)を定義し、操作に使うデータ(特に、開始されるコンポーネントが知っておく必要があるデータ)の URI を指定することもできます。 + +たとえば、インテントはアクティビティに画像を表示したり、ウェブページを開いたりするアクティビティに対して要求を伝達します。 +場合によっては、結果を受け取るアクティビティを開始でき、この場合にアクティビティが返す結果も {@link android.content.Intent} になります(たとえば、ユーザーが個人の連絡先を取り出し、それを返してくれるインテントを発行できます。返されたインテントには選択された連絡先を指す URI が含まれています)。 +— + + +

+ +

ブロードキャスト レシーバーの場合、インテントは単純にブロードキャストするアナウンスを定義します(たとえば、端末のバッテリ残量が少ないことを示すブロードキャストには、「バッテリが少ない」ことを示す既知のアクション文字列が含まれます)。 + +

+ +

他のコンポーネント タイプであるコンテンツ プロバイダは、インテントではアクティベートされず、{@link android.content.ContentResolver} からの要求の対象となったときにアクティベートされます。 +コンテンツリゾルバが、コンテンツプロバイダを使ってすべてのトランザクションを直接処理することで、プロバイダを使ってトランザクションを実行しているコンポーネントは処理する必要がなくなり、代わりに {@link +android.content.ContentResolver} オブジェクトのメソッドを呼び出します。 + +これによりコンテンツ プロバイダと情報を要求しているコンポーネントとの間に(セキュリティ目的で)抽象的な層ができます。 +

+ +

各コンポーネント タイプのアクティベート用に、個別のメソッドが用意されています。

+ + +

インテントの使用に関する詳細については、「インテントとインテント フィルタ」のドキュメントをご覧ください。 +特定のコンポーネントのアクティベートの詳細については、 +アクティビティサービス、{@link +android.content.BroadcastReceiver}、コンテンツ プロバイダ のドキュメントでも説明しています。

+ + +

マニフェスト ファイル

+ +

Android システムがコンポーネントを開始する前に、システムはアプリの {@code AndroidManifest.xml} ファイル(「マニフェスト」ファイル)を読み取って、コンポーネントの存在を認識する必要があります。 + +アプリはこのファイルですべてのコンポーネントを宣言し、このファイルはアプリ プロジェクトのディレクトリのルートに置く必要があります。 +

+ +

マニフェストはアプリのコンポーネントを宣言する他にも、次のようにさまざまな役割があります。 +

+ + + +

コンポーネントを宣言する

+ +

マニフェストの主なタスクは、システムにアプリ コンポーネントに関する情報を与えることです。たとえば、マニフェスト ファイルではアクティビティを次のように宣言できます。 +

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<manifest ... >
+    <application android:icon="@drawable/app_icon.png" ... >
+        <activity android:name="com.example.project.ExampleActivity"
+                  android:label="@string/example_label" ... >
+        </activity>
+        ...
+    </application>
+</manifest>
+ +

<application> 要素では、{@code android:icon} 属性でアプリを特定するアイコンのリソースを指します。 + +

+ +

<activity> 要素では、{@code android:name} 属性で{@link +android.app.Activity} サブクラスの完全修飾k裏スパイウェア名を指定し、{@code android:label} 属性でアクティビティのユーザーに表示するラベルとして使用する文字列を指定しています。 + +

+ +

すべてのアプリ コンポーネントは、次の方法で宣言する必要があります。

+ + +

ソースに含まれていながら、マニフェスト ファイルでは定義されていないアクティビティ、サービス、コンテンツ プロバイダはシステムには見えないため、実行されることはありません。 +ただし、ブロードキャスト レシーバーはマニフェストで宣言するか、コードで動的に作成({@link android.content.BroadcastReceiver} オブジェクトとして)して {@link android.content.Context#registerReceiver registerReceiver()} を呼び出すことでシステムに登録できます。 + + + +

+ +

アプリのマニフェスト ファイルの構築方法の詳細については、「The AndroidManifest.xml File」のドキュメントをご覧ください。 +

+ + + +

コンポーネントの機能を宣言する

+ +

コンポーネントをアクティベートするで説明したように、{@link android.content.Intent} を使用してアクティビティ、サービス、ブロードキャスト レシーバーを開始できます。 +開始するには、インテントでターゲットのコンポーネントの名前を(コンポーネントのクラス名を使って)明示的に指定する必要があります。 +ただし、インテントの本来の能力は、暗黙的インテントの概念にあります。 +暗黙的インテントは、単に実行するアクションのタイプを記述し(どのデータ上でアクションを実行するかも任意で記述できます)、それによりシステムがそのアクションを実行できる端末上のコンポーネントを見つけて開始できます。 + + +インテントで記述されたアクションを実行できるコンポーネントが複数ある場合は、使用するコンポーネントを 1 つ選択できます。 +

+ +

システムは、端末の他のアプリのマニフェスト ファイルに提供されたインテント フィルタが受け取った + インテントを比較して、 インテントに応答できるコンポーネントを識別します。 +

+ +

アプリのマニフェストでアクティビティを宣言するとき、任意でアクティビティの機能を宣言するインテント フィルタを含めて、他のアプリからのインテントに応答できるようにできます。 + +{@code +<intent-filter>} 要素を、コンポーネントを宣言している要素の子として追加することで、コンポーネントのインテント フィルタを宣言できます。 +

+ +

たとえば、新規メールを作成するアクティビティを持つメールアプリをビルドした場合、次のように「送信」インテント(新規メールを送信するための)に応答するインテント フィルタを宣言できます。 +

+
+<manifest ... >
+    ...
+    <application ... >
+        <activity android:name="com.example.project.ComposeEmailActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.SEND" />
+                <data android:type="*/*" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </activity>
+    </application>
+</manifest>
+
+ +

次に、別のアプリが {@link +android.content.Intent#ACTION_SEND} アクションを持つインテントを作成し、{@link android.app.Activity#startActivity +startActivity()} に渡す場合、ユーザーがメールを下書きして送信できるよう、システムがアクティビティを開始する場合があります。 +

+ +

インテント フィルタの作成の詳細については、「インテントとインテント フィルタ」のドキュメントをご覧ください。 +

+ + + +

アプリの要件を宣言する

+ +

Android が搭載された端末は数多くありますが、すべての端末が同じ機能や性能を備えているわけではありません。 +アプリに必要な機能を搭載していない端末にアプリをインストールしてしまわないよう、アプリがサポートする端末のタイプについてプロファイルで明確に定義し、マニフェスト ファイルで端末の要件やソフトウェア要件を宣言しておくことが重要です。 + + +これらの宣言のほとんどはただの情報で、システムがそれを読み取ることはありませんが、Google Play などの外部サービスはそれを読み取って、ユーザーが端末からアプリを検索したときにフィルタを提供します。 + +

+ +

たとえば、アプリでカメラを使用する必要があり、Android 2.1 で採用された API(API レベル 7)を使用する場合、次のようにマニフェスト ファイルでそれを要件として宣言する必要があります。 +

+ +
+<manifest ... >
+    <uses-feature android:name="android.hardware.camera.any"
+                  android:required="true" />
+    <uses-sdk android:minSdkVersion="7" android:targetSdkVersion="19" />
+    ...
+</manifest>
+
+ +

カメラのない端末で、Android バージョンが 2.1 以下の場合は、Google Play からアプリをインストールできません。 +

+ +

ただし、アプリでカメラを使用するが、それが必須ではないということを宣言することもできます。 +この場合、アプリで {@code required} 属性を {@code "false"} に設定し、端末にカメラがあるかどうかを実行時に確認して、必要に応じてすべてのカメラ機能を無効にする必要があります。 + +

+ +

異なる端末でのアプリの互換性を管理する方法については、「Device Compatibility」をご覧ください。 + +

+ + + +

アプリのリソース

+ +

Android アプリは、コードだけで構成されているわけではありません。ソース コードから分離された画像、オーディオ ファイル、その他アプリの外観に関連するあらゆるリソースが必要です。たとえば、アクティビティのアニメーション、メニュー、スタイル、色、レイアウトなどを XML ファイルで定義する必要があります。— + + +アプリのリソースを使用すると、コードを修正することなく別のリソースセットを提供することで、アプリのあらゆる特性を簡単にアップデートできるようになります。これにより、さまざまな端末の設定用(別の言語や画面サイズなど)にアプリを最適化できます。 +— +— +

+ +

Android プロジェクトに含めるすべてのリソースに対し、SDK ビルド ツールが一意の整数 ID を定義し、アプリコードや XML で定義された他のリソースからリソースにそれを使って参照できます。 + +たとえば、アプリに {@code +logo.png} という名前の画像ファイルがある場合({@code res/drawable/} ディレクトリ内に保存)、SDK ツールが {@code R.drawable.logo} という名前のリソース ID を生成し、これを使って画像を参照してユーザー インターフェースに挿入できます。 + +

+ +

リソースとソース コードを分離して提供する方法の最も重要な側面は、さまざまな端末設定に合わせて別のリソースを提供できるという点です。 + +たとえば、XML で UI 文字列を定義することで、その文字列を他の言語に翻訳して、それらの文字列を別のファイルに保存しておくことができます。 +それを、リソースのディレクトリ名に付けた言語の修飾子(フランス語の文字列値なら {@code res/values-fr/} のように)とユーザーの言語設定に基づいて、Android システムによって UI に適切な言語の文字列が適用されます。 + + +

+ +

Android では代替リソース用に多様な修飾子をサポートしています。修飾子は、リソースのディレクトリ名に含める短い文字列で、そのリソースを使用する端末構成を定義するものです。 + +例をもう 1 つ挙げると、端末の画面の向きやサイズによって、アクティビティのレイアウトを複数作成する必要があります。 + +たとえば、端末が縦方向(縦長)の場合、ボタンの付いたレイアウトを縦に並べ、端末が横方向(横長)の場合はボタンを横並びにする、といった場合です。 + +画面の方向によってレイアウトを変更するには、2 つの異なるレイアウトを定義して、それぞれのレイアウトのディレクトリ名に適切な修飾子を適用します。 + +そうすることで、現在の端末の向きによってシステムが自動的に適切なレイアウトを適用できます。 +

+ +

アプリケーションに含めることのできるリソースの種類や、異なる端末設定用の代替リソースの作成方法については、「リソースの提供」をご覧ください。 +

+ + + +
+
+

こちらもご覧ください。

+
+
インテントとインテント フィルタ +
+
{@link android.content.Intent} API を使用して、アクティビティやサービスなどのアプリのコンポーネントをアクティベートする方法や、アプリのコンポーネントを他のアプリで利用できるようにする方法について説明しています。 + +
+
Activities
+
ユーザー インターフェースを使って独特のアプリケーション画面を提供する {@link android.app.Activity} + クラスのインスタンスの作成方法について説明しています。
+
リソースの提供
+
特定の端末構成に対して代替リソースを提供する方法など、Android アプリでアプリのリソースをアプリコードから分離する仕組みについて説明しています。 + + +
+
+
+
+

関連ドキュメント

+
+
Device Compatibility
+
あらゆるタイプの端末での Android の動作と、端末ごとにアプリを最適化したり、別の端末でのアプリの利用を制限したりする方法について説明しています。 + +
+
System Permissions
+
アプリが特定の API を使用するのにユーザーの同意を必要とするパーミッション システムを使用して、アプリから特定の API へのアクセスを Android が制限する仕組みについて説明しています。 +
+
+
+
+ diff --git a/docs/html-intl/intl/ja/guide/components/index.jd b/docs/html-intl/intl/ja/guide/components/index.jd new file mode 100644 index 0000000000000000000000000000000000000000..803f99b2ebd213303c925f71be5d3c3d1d6a95d3 --- /dev/null +++ b/docs/html-intl/intl/ja/guide/components/index.jd @@ -0,0 +1,57 @@ +page.title=アプリ
コンポーネント +page.landing=true +page.landing.intro=Android のアプリケーション フレームワークでは、再利用可能なコンポーネント セットを使用して豊富な内容を備えた斬新なアプリを作成できます。このセクションでは、アプリの構成要素を定義するコンポーネントのビルド方法と、インテントを使用してそれらをつなぎ合わせる方法について説明します。 +page.metaDescription=Android のアプリケーション フレームワークでは、再利用可能なコンポーネント セットを使用して豊富な内容を備えた斬新なアプリを作成できます。このセクションでは、アプリの構成要素を定義するコンポーネントのビルド方法と、インテントを使用してそれらをつなぎ合わせる方法について説明します。 +page.landing.image=images/develop/app_components.png +page.image=images/develop/app_components.png + +@jd:body + +
+ + + + + +
diff --git a/docs/html-intl/intl/ja/guide/components/intents-filters.jd b/docs/html-intl/intl/ja/guide/components/intents-filters.jd new file mode 100644 index 0000000000000000000000000000000000000000..fe78eca74f2479a1a20f720a59bdb290e7b81f59 --- /dev/null +++ b/docs/html-intl/intl/ja/guide/components/intents-filters.jd @@ -0,0 +1,899 @@ +page.title=インテントとインテント フィルタ +page.tags="IntentFilter" +@jd:body + + + + + + +

{@link android.content.Intent} は、他のアプリ コンポーネントからのアクションを要求するときに使用するメッセージング オブジェクトです。インテントを使用することでコンポーネント間の通信を促進する方法はいくつかありますが、基本的な使用例は次の 3 つです。 + + +

+ + + + + + +

インテントのタイプ

+ +

インテントには 2 つのタイプがあります。

+ + + +

暗黙的インテントを使用してアクティビティやサービスを開始する際、システムによってただちに {@link android.content.Intent} オブジェクトで指定されたアプリ コンポーネントが開始されます。 +

+ +
+ +

図 1. 暗黙的インテントが他のアクティビティを開始するようシステム内に配信される仕組みを表しています。[1] Activity A がアクションの記述を使って {@link android.content.Intent} を作成し、それを {@link +android.content.Context#startActivity startActivity()} に渡します。[2] Android システムがすべてのアプリに対してインテントに一致するインテント フィルタを検索します。 + + +一致するものが見つかったら、[3] システムが {@link +android.app.Activity#onCreate onCreate()} メソッドを呼び出して、{@link android.content.Intent} に渡すことで、そのアクティビティ(Activity B)を開始します。 + +

+
+ +

暗黙的インテントを作成するとき、Android システムはインテントの内容を、端末上の他のアプリの マニフェスト ファイルで定義された インテント フィルタ と比較して、開始すべきコンポーネントを見つけます。 + +インテントがインテント フィルタに一致した場合、システムがそのコンポーネントを開始して、{@link android.content.Intent} を配信します。 +複数のインテント フィルタが競合する場合、ユーザーが仕様するアプリを選択できるようシステムによってダイアログが表示されます。 +

+ +

インテント フィルタは、コンポーネントが受け取りたいインテントのタイプを指定する式で、アプリのマニフェスト ファイルに含まれています。 + +たとえば、アクティビティのインテント フィルタを宣言すると、特定のインテント タイプを持つアクティビティを他のアプリから直接開始できるようになります。同様に、アクティビティでインテント フィルタを宣言しない場合は、明示的インテントでのみアクティビティを開始できます。 + + +

+ +

警告: アプリの安全性を保つため、{@link android.app.Service} を開始するときは常に明示的インテントを使用し、サービスでインテント フィルタを宣言しないようにしてください。 + +暗黙的インテントを使ってサービスを開始すると、どのサービスがインテントに応答するかを把握できず、ユーザーにはどのサービスが開始するのかがわからないため、セキュリティ上の危険が伴います。 + +Android 5.0(API レベル 21)以降では、暗黙的インテントで {@link android.content.Context#bindService bindService()} を呼び出すと、システムから例外がスローされます。 + +

+ + + + + +

インテントを作成する

+ +

{@link android.content.Intent} オブジェクトには Android システムが開始するコンポーネントを決定する際に使用する情報(インテントを受け取るべき正確なコンポーネント名やコンポーネントのカテゴリなど)に加えて、受け取る側のコンポーネントがアクションを適切に実行するために使用する情報(実行するアクションと、実行対象のデータなど)が含まれています。 + + +

+ + +

{@link android.content.Intent} に含まれる主な情報は次のとおりです。

+ +
+ +
コンポーネント名
+
開始するコンポーネントの名前です。 + +

これは省略可能ですが、インテントを明示的にするためには不可欠な情報です。つまり、コンポーネント名で定義されたアプリ コンポーネントにのみ、インテントが配信されます + +コンポーネント名がない場合、インテントは暗黙的になり、他のインテント情報(下記で説明するアクション、データ、カテゴリなど)に基づいてインテントを受け取るべきコンポーネントをシステムが決定します + +—そのため、アプリ内の特定のコンポーネントを開始する必要がある場合は、コンポーネント名を指定する必要があります。 +

+ +

注: {@link android.app.Service} を開始するときは、常にコンポーネント名を指定する必要があります。 +指定しない場合、どのサービスがインテントに応答するかを把握できず、ユーザーにはどのサービスが開始するのかがわからなくなります。 +

+ +

{@link android.content.Intent} のフィールドは {@link android.content.ComponentName} オブジェクトで、アプリのパッケージ名など、ターゲットのコンポーネントの完全修飾クラス名を使用して指定できます(例: {@code com.example.ExampleActivity})。 + + +コンポーネント名は、{@link +android.content.Intent#setComponent setComponent()}、{@link android.content.Intent#setClass +setClass()}、{@link android.content.Intent#setClassName(String, String) setClassName()}、{@link android.content.Intent} コンストラクタなどを使用して設定できます。 +

+ +
+ +

アクション
+
実行する全体的なアクションを指定する文字列です(閲覧選択など)。 + +

ブロードキャスト インテントの場合、これが発生済みで報告済みのアクションになります。ほとんどのアクションは、残りのインテントがどのように構成されているか、特にデータやエクストラに含まれている内容によって決まります。 +— + + +

インテントによって自身のアプリで使用する(または他のアプリで使用して自身のアプリのコンポーネントを呼び出す)独自のアクションを指定できますが、通常は {@link android.content.Intent} クラスや他のフレームワーク クラスで定義されたアクション定数を使用します。 + +アクティビティを開始する際の一般的なアクションをいくつか次に示します。 +

+ +
+
{@link android.content.Intent#ACTION_VIEW}
+
ギャラリー アプリで表示する写真や、マップアプリで表示する住所など、アクティビティ内にユーザーに表示する情報がある場合は、{@link + android.content.Context#startActivity startActivity()} を使ってインテントにこのアクションを使用します。 + +
+ +
{@link android.content.Intent#ACTION_SEND}
+
これは「共有」インテントとしても知られており、メールアプリやソーシャル シェアリング アプリなどの他のアプリ経由でユーザーが共有できるデータがある場合に、{@link + android.content.Context#startActivity startActivity()} でインテントに使用します。 +
+
+ +

全体的なアクションを定義するその他の定数については、{@link android.content.Intent} クラスのリファレンスをご覧ください。 +システムの設定アプリで特定の画面を開くアクションの {@link android.provider.Settings} など、他のアクションは Android フレームワークの別の場所で定義されます。 + +

+ +

インテントのアクションは、{@link android.content.Intent#setAction +setAction()} や {@link android.content.Intent} コンストラクタを使って指定できます。

+ +

独自のアクションを定義したら、接頭辞としてアプリのパッケージ名を必ず含めます。 +次に例を示します。

+
static final String ACTION_TIMETRAVEL = "com.example.action.TIMETRAVEL";
+
+ +
データ
+
アクションの実行対象であるデータや、データの MIME タイプを参照する URI({@link android.net.Uri} オブジェクト)です。 +提供されるデータタイプは、インテントのアクションによって決まります。たとえば、アクションが {@link android.content.Intent#ACTION_EDIT} の場合、データには編集するドキュメントの URI が含まれます。 + + + +

インテントの作成時、URI だけでなくデータタイプ(MINE タイプ)を指定することが重要な場面が多くあります。たとえば、画像を表示できるアクティビティの場合、URI 形式は似ていてもオーディオ ファイルは再生できません。そのため、データの MIME タイプを指定することで、Android システムがインテントを受け取るのに最適なコンポーネントを見つけることができます。ただし、MIME タイプは URI から推測できることもあります。特にデータが {@code content:} URI の場合は、データが端末上にあり、{@link android.content.ContentProvider} に制御されるデータであることを示し、データの MIME タイプがシステムから見えるようになります。 + + + + + +— + +

+ +

データ URI のみを設定するには、{@link android.content.Intent#setData setData()} を呼び出します。MIME タイプのみを設定するには、{@link android.content.Intent#setType setType()} を呼び出します。 +必要であれば、{@link +android.content.Intent#setDataAndType setDataAndType()} を使って両方を明示的に設定することもできます。 +

+ +

警告: URI と MIME タイプの両方を設定する場合は、お互いの値を無効にしてしまわないよう、{@link android.content.Intent#setData setData()} と {@link android.content.Intent#setType setType()} を呼び出さないでください。URI と MIME タイプの両方を設定する場合は、常に {@link android.content.Intent#setDataAndType setDataAndType()} を使用してください。 + + + +

+
+ +

カテゴリ
+
インテントを処理するコンポーネントの種類に関する追加情報が含まれた文字列です。 +カテゴリの記述は、インテントにいくつでも含めることができますが、ほとんどのインテントではカテゴリは必須ではありません。一般的なカテゴリの例を次に示します。 + + + +
+
{@link android.content.Intent#CATEGORY_BROWSABLE}
+
ターゲットのアクティビティは、ウェブブラウザから開始して画像やメール メッセージなど、リンクで参照されたデータを表示できます。 +— +
+
{@link android.content.Intent#CATEGORY_LAUNCHER}
+
このアクティビティはタスクの初期のアクティビティで、システムのアプリケーション ランチャーの一覧に表示されます。 + +
+
+ +

カテゴリの全一覧は、{@link android.content.Intent} クラスの説明をご覧ください。 +

+ +

カテゴリは、{@link android.content.Intent#addCategory addCategory()} を使って指定できます。

+
+
+ + +

上記のプロパティ(コンポーネント名、アクション、データ、カテゴリ)は、インテントの特性の定義を表しています。 +これらのプロパティを読み取ることで、Android システムはどのアプリ コンポーネントを開始すべきかを解決できます。 +

+ +

ただし、インテントにはアプリ コンポーネントの解決に影響を与えない追加情報を含めることもできます。 +インテントに含めることのできる情報は次のとおりです。

+ +
+
エクストラ
+
要求されたアクションの実行に必要な追加情報のキーと値のペアです。データの URI の特定の種類を使用するアクションがあるのと同様に、特定のエクストラを使用するアクションもあります。 + + +

エクストラはさまざまな {@link android.content.Intent#putExtra putExtra()} を使って追加でき、それぞれがキー名と値の 2 つのパラメータを受け入れます。また、すべてのエクストラ データを使って {@link android.os.Bundle} オブジェクトを作成し、{@link +android.content.Intent#putExtras putExtras()} を使って {@link android.os.Bundle} を {@link android.content.Intent} に挿入することもできます。 + + +

+ +

たとえば、{@link android.content.Intent#ACTION_SEND} を使ってメールを送信するインテントを作成するとき、{@link android.content.Intent#EXTRA_EMAIL} キーを使って「宛先」の受信者を指定したり、{@link android.content.Intent#EXTRA_SUBJECT} を使って「件名」を指定したりできます。 + + +

+ +

{@link android.content.Intent} クラスは多くの標準データタイプの {@code EXTRA_*} 定数を指定できます。 +独自のエクストラ キーを宣言する必要がある場合は(アプリが受け取るインテント用に)、アプリのパッケージ名を接頭辞として必ず含めます。 + +次に例を示します。

+
static final String EXTRA_GIGAWATTS = "com.example.EXTRA_GIGAWATTS";
+
+ +
フラグ
+
{@link android.content.Intent} クラスで定義されるフラグは、インテントのメタデータとして機能します。 +フラグは、Android システムにアクティビティの起動方法(アクティビティがどのタスクに属するかなど)や、起動後の取り扱い方法(最近のアクティビティの一覧に含めるかどうかなど)を指示します。 + + + + +

詳細については、{@link android.content.Intent#setFlags setFlags()} メソッドをご覧ください。

+
+ +
+ + + + +

明示的インテントの例

+ +

明示的インテントは、アプリ内の特定のアクティビティやサービスなど、特定のアプリ コンポーネントを起動する際に使用するものです。明示的インテントを作成するには、{@link android.content.Intent} オブジェクトでコンポーネント名を定義します。他のインテント プロパティはすべて省略可能です。 + + +—

+ +

たとえば、アプリでウェブからファイルをダウンロードするサービスを {@code DownloadService} という名前でビルドした場合、次のコードでそれを開始できます。 +

+ +
+// Executed in an Activity, so 'this' is the {@link android.content.Context}
+// The fileUrl is a string URL, such as "http://www.example.com/image.png"
+Intent downloadIntent = new Intent(this, DownloadService.class);
+downloadIntent.setData({@link android.net.Uri#parse Uri.parse}(fileUrl));
+startService(downloadIntent);
+
+ +

{@link android.content.Intent#Intent(Context,Class)} コンストラクタがアプリに {@link android.content.Context} を、コンポーネントに {@link java.lang.Class} オブジェクトを提供します。 + +このようにして、このインテントはアプリの {@code DownloadService} クラスを明示的に開始します。 +

+ +

サービスのビルドと開始の詳細については、「サービス」のガイドをご覧ください。 +

+ + + + +

暗黙的インテントの例

+ +

暗黙的インテントは、端末上のどんなアプリでも実行できるアクションを指定します。 +暗黙的インテントは、自身のアプリではそのアクションを実行できないが、他のアプリでは実行可能であり、どのアプリを使うかをユーザーに選ばせたい場合に便利です。 +

+ +

たとえば、他のユーザーと共有できるようにするコンテンツがある場合は、{@link android.content.Intent#ACTION_SEND} アクションを使ってインテントを作成し、共有するコンテンツを指定するエクストラを追加します。 + +そのインテントで {@link android.content.Context#startActivity startActivity()} を呼び出すと、ユーザーはどのアプリでコンテンツを共有するかを選択できます。 + +

+ +

警告: {@link android.content.Context#startActivity +startActivity()} に送る暗黙的インテントを処理できるアプリがユーザーが持っていない場合もあります。 +その場合、呼び出しは失敗し、アプリはクラッシュします。アクティビティが必ずインテントを受け取るようにするには、{@link android.content.Intent} オブジェクトで {@link android.content.Intent#resolveActivity +resolveActivity()} を呼び出します。 +結果が null 以外の場合は、インテントを処理できるアプリが少なくとも 1 つはあることを意味し、{@link android.content.Context#startActivity startActivity()} を安全に呼び出すことができます。 + +結果が null の場合は、そのインテントは使用せず、可能であればそのインテントを発行する機能を無効にする必要があります。 + +

+ + +
+// Create the text message with a string
+Intent sendIntent = new Intent();
+sendIntent.setAction(Intent.ACTION_SEND);
+sendIntent.putExtra(Intent.EXTRA_TEXT, textMessage);
+sendIntent.setType("text/plain");
+
+// Verify that the intent will resolve to an activity
+if (sendIntent.resolveActivity(getPackageManager()) != null) {
+    startActivity(sendIntent);
+}
+
+ +

注: この場合、URI は使用されませんが、エクストラに含まれるコンテンツを指定するためインテントのデータタイプは宣言されます。 +

+ + +

{@link android.content.Context#startActivity startActivity()} が呼び出されたとき、システムによってすべてのインストール済みアプリの中にこのタイプのインテント({@link android.content.Intent#ACTION_SEND} アクションとテキスト/プレーン データが含まれるインテント)を処理できるものがあるかどうかが確認されます + + +インテントを処理できるアプリが 1 つしかない場合は、そのアプリがすぐに開いてインテントを受け取ります。 +インテントを処理できるアクティビティが複数ある場合、ユーザーが使用するアプリを選択できるダイアログが表示されます。 +

+ + +
+ +

図 2. チューザのダイアログ

+
+ +

アプリチューザを表示する

+ +

暗黙的インテントに応答するアプリが 2 つ以上ある場合、ユーザーは使用するアプリを選択でき、そのアプリをアクションに対するデフォルトの選択肢にすることができます。 + +これは、ウェブ ページを開いたり、(1 つのカメラを選ぶ傾向にあるユーザーが) +写真を撮影したりといったアクションの実行時に、ユーザーが今後同じアプリの使用を希望するような場合に便利です(ユーザーは常に同じウェブブラウザを使用する傾向があります)。 +

+ +

ただし、インテントに応答するアプリが複数あって、ユーザーが毎回別のアプリを使用する可能性がある場合は、チューザのダイアログを明示的に表示する必要があります。 +チューザのダイアログでは、 +アクションのたびに使用するアプリをユーザーに選択させます(ユーザーはアクションのデフォルトのアプリを選択できません)。 +たとえば、アプリが {@link +android.content.Intent#ACTION_SEND} アクションを使って「共有」を実行するとき、 ユーザーが現在の状況によって別のアプリを使用して共有する場合もあるため、図 2 のようにチューザのダイアログは常に表示する必要があります。 +

+ + + + +

チューザを表示するには、{@link +android.content.Intent#createChooser createChooser()} を使用して {@link android.content.Intent} を作成し、{@link +android.app.Activity#startActivity startActivity()} に渡します。次に例を示します。

+ +
+Intent sendIntent = new Intent(Intent.ACTION_SEND);
+...
+
+// Always use string resources for UI text.
+// This says something like "Share this photo with"
+String title = getResources().getString(R.string.chooser_title);
+// Create intent to show the chooser dialog
+Intent chooser = Intent.createChooser(sendIntent, title);
+
+// Verify the original intent will resolve to at least one activity
+if (sendIntent.resolveActivity(getPackageManager()) != null) {
+    startActivity(chooser);
+}
+
+ +

これにより、{@link +android.content.Intent#createChooser createChooser()} メソッドに渡されたインテントに応答するアプリのリストを示すダイアログが表示され、 +指定されたテキストがダイアログのタイトルになります。

+ + + + + + + + + +

暗黙的インテントを受け取る

+ +

アプリが受け取ることのできる暗黙的インテントを通知するには、マニフェスト ファイル{@code <intent-filter>} 要素を使ってアプリのコンポーネントごとに 1 つ以上のインテント フィルタを宣言します。各インテント フィルタは、インテントのアクション、データ、カテゴリに基づいて受け入れるインテントのタイプを指定します。 + + + +インテントがいずれかのインテント フィルタを通過した場合のみ、システムが暗黙的インテントをアプリのコンポーネントに配信します。 +

+ +

注: 明示的インテントは、コンポーネントが宣言しているインテント フィルタに関係なく、常にターゲットに配信されます。 +

+ +

アプリのコンポーネントは固有のジョブそれぞれに対して個別のフィルタを宣言する必要があります。たとえば、画像キャラリーのアプリの 1 つのアクティビティには、画像を表示するフィルタと、画像を編集するフィルタの 2 つがあります。 + +アクティビティが開始すると、{@link android.content.Intent} を調べて {@link android.content.Intent} にある情報に基づいてどのように動作するかを決定します(編集コントロールを表示するかどうかなど)。 + +

+ +

各インテント フィルタは、アプリのマニフェスト ファイルの {@code <intent-filter>} 要素で定義され、これは対応するアプリのコンポーネント({@code <activity>} 要素など)にネストされます。 + + +{@code <intent-filter>} 内で、次の 3 つの要素のなかで 1 つ以上を使用して、受け入れるインテントのタイプを指定できます。 + +

+ +
+
{@code <action>}
+
{@code name} 属性で、受け入れるインテントのアクションを宣言します。値は、クラス定数ではなく、アクションのリテラル文字列値である必要があります。 +
+
{@code <data>}
+
データ URI (schemehostportpath など) や MIME タイプのさまざまな側面を指定する 1 つ以上の属性を使用して、受け入れるデータのタイプを宣言します。 + +
+
{@code <category>}
+
{@code name} 属性で、受け入れるインテントのカテゴリを宣言します。値は、クラス定数ではなく、アクションのリテラル文字列値である必要があります。 + + +

注: 暗黙的インテントを受け取るには、インテント フィルタに {@link android.content.Intent#CATEGORY_DEFAULT} カテゴリを含める必要があります。 + +{@link android.app.Activity#startActivity startActivity()} メソッドと {@link android.app.Activity#startActivityForResult startActivityForResult()}メソッドはすべてのインテントを、{@link android.content.Intent#CATEGORY_DEFAULT} カテゴリを宣言しているものとして処理します。 + + + + インテント フィルタでカテゴリを宣言していない場合、暗黙的インテントはアクティビティに紐付けされません。 +

+
+
+ +

次に、データ タイプがテキストの場合に、{@link android.content.Intent#ACTION_SEND} インテントを受け取るインテント フィルタを使用したアクティビティ宣言の例を示します。 +

+ +
+<activity android:name="ShareActivity">
+    <intent-filter>
+        <action android:name="android.intent.action.SEND"/>
+        <category android:name="android.intent.category.DEFAULT"/>
+        <data android:mimeType="text/plain"/>
+    </intent-filter>
+</activity>
+
+ +

{@code <action>}{@code <data>}{@code <category>} のなかで 2 つ以上のインスタンスを含むフィルタを作成することもできます。その場合、コンポーネントがそれらのフィルタ要素のすべての組み合わせを処理できることを確認しておきます。 + + + + +

+ +

複数のタイプのインテントに対応しながら、アクション、データ、カテゴリタイプの特定の組み合わせのみを処理する場合は、複数のインテント フィルタを作成する必要があります。 +

+ + + + +

暗黙的インテントは、3 つの要素それぞれへのインテントを比較して、フィルタでテストされます。 +コンポーネントに配信されるには、インテントが 3 つのテストすべてを通過する必要があります。一致しないものが 1 つでも場合、Android システムはインテントをコンポーネントに配信しません。 + +ただし、コンポーネントは複数のインテント フィルタを保持していることもあるため、1 つのインテント フィルタを通過できなかったインテントでも、別のフィルタを通過できる場合があります。システムによるインテント解決の詳細は、次のセクションのインテントの解決で説明します。 + + +

+ +

警告: 他のアプリの {@link android.app.Service} で誤って実行されないように、自身のサービスは常に明示的インテントを使用して開始し、サービスではインテント フィルタは宣言しないでください。 + +

+ +

注: すべてのアクティビティにおいて、インテント フィルタはマニフェスト ファイルで宣言する必要があります。ただし、ブロードキャスト レシーバーのフィルタは、{@link android.content.Context#registerReceiver(BroadcastReceiver, IntentFilter, String, +Handler) registerReceiver()} を呼び出すことで動的に登録できます。 + + +その後、レシーバーの登録を解除するには、{@link +android.content.Context#unregisterReceiver unregisterReceiver()} を使用します。これにより、アプリが実行中の特定の期間だけ、アプリが特定のブロードキャストをリッスンできるようになります。 + +

+ + + + + + + +

フィルタの例

+ +

インテント フィルタの動作をより深く理解するため、ソーシャル シェアリング アプリのマニフェスト ファイルからの次のスニペットをご覧ください。 +

+ +
+<activity android:name="MainActivity">
+    <!-- This activity is the main entry, should appear in app launcher -->
+    <intent-filter>
+        <action android:name="android.intent.action.MAIN" />
+        <category android:name="android.intent.category.LAUNCHER" />
+    </intent-filter>
+</activity>
+
+<activity android:name="ShareActivity">
+    <!-- This activity handles "SEND" actions with text data -->
+    <intent-filter>
+        <action android:name="android.intent.action.SEND"/>
+        <category android:name="android.intent.category.DEFAULT"/>
+        <data android:mimeType="text/plain"/>
+    </intent-filter>
+    <!-- This activity also handles "SEND" and "SEND_MULTIPLE" with media data -->
+    <intent-filter>
+        <action android:name="android.intent.action.SEND"/>
+        <action android:name="android.intent.action.SEND_MULTIPLE"/>
+        <category android:name="android.intent.category.DEFAULT"/>
+        <data android:mimeType="application/vnd.google.panorama360+jpg"/>
+        <data android:mimeType="image/*"/>
+        <data android:mimeType="video/*"/>
+    </intent-filter>
+</activity>
+
+ +

1 つ目のアクティビティである {@code MainActivity} は、アプリのメイン エントリ ポイントで、ユーザーがランチャー アイコンから初めてアプリを起動したときに開くアクティビティです。— +

+ +

アクティビティをアプリ ランチャーに表示するには、この 2 つをペアリングする必要があります。

+ +

2 つ目のアクティビティである {@code ShareActivity} が、テキストやメディア コンテンツの共有を促します。 +ユーザーは {@code MainActivity} に移動することでこのアクティビティに入ることもありますが、2 つのインテント フィルタのいずれかに一致する暗黙的インテントを発行する別のアプリから直接 {@code ShareActivity} に入ることもできます。 + +

+ +

注: MIME タイプ({@code +application/vnd.google.panorama360+jpg})は、パノラマ写真を指定する特別なデータタイプで、Google +panorama API を使って処理できます。 + +

+ + + + + + + + + + + + + +

ペンディング インテントを使用する

+ +

{@link android.app.PendingIntent} オブジェクトは、{@link +android.content.Intent} オブジェクトのラッパーです。{@link android.app.PendingIntent} の主な目的は、別のアプリケーションに {@link android.content.Intent} をアプリ自身のプロセスから実行したように使用できるパーミッションを付与することです。 + + +

+ +

ペンディング インテントの主な使用例には、次のようなものがあります。

+ + +

各 {@link android.content.Intent} オブジェクトはアプリの特定のタイプのコンポーネント({@link android.app.Activity}、{@link android.app.Service}、{@link android.content.BroadcastReceiver} のいずれか)で処理されることを前提としているため、{@link android.app.PendingIntent} も同様の前提で作成する必要があります。 + + +ペンディング インテントを使用する場合、アプリはインテントの実行時に {@link android.content.Context#startActivity +startActivity()} などの呼び出しを行いません。 +代わりに、{@link android.app.PendingIntent} の作成時にそれぞれのクリエーター メソッドを呼び出して目的のコンポーネント タイプを宣言する必要があります。 +

+ + + +

アプリが他のアプリからペンディング インテントを受け取る場合を除いて、{@link android.app.PendingIntent} の作成時に必要となる {@link android.app.PendingIntent} メソッドはほぼ上記の 3 つのみです。 + +

+ +

各メソッドが現在のアプリの {@link android.content.Context}、ラップする {@link android.content.Intent}、インテントの使用方法を指定する 1 つ以上のフラグ(インテントを 2 回以上できるかどうかなど)を受け取ります。 + +

+ +

ペンディング インテントの使用に関する詳細については、 +「通知」や「App Widgets」など、それぞれの使用例の API ガイドをご覧ください。 +

+ + + + + + + +

インテント解決

+ + +

システムがアクティビティを開始する暗黙的インテントを受け取ると、次の 3 つの側面に基づいてインテントをインテント フィルタと比較し、そのインテントに最適なアクティビティを検索します。 +

+ + + +

次のセクションでは、インテント フィルタがアプリのマニフェスト ファイルでどのように宣言されているかという観点から、インテントが適切なコンポーネントに紐付けられる方法について説明します。 +

+ + +

アクションのテスト

+ +

受け入れるインテントのアクションを指定するには、インテント フィルタでゼロ個以上の {@code +<action>} 要素を宣言します。 +次に例を示します。

+ +
+<intent-filter>
+    <action android:name="android.intent.action.EDIT" />
+    <action android:name="android.intent.action.VIEW" />
+    ...
+</intent-filter>
+
+ +

このフィルタを通過するには、{@link android.content.Intent} で指定されたアクションが、フィルタにリストされたアクションのいずれかに一致する必要があります。 +

+ +

フィルタのリストにアクションが 1 つもない場合は、インテントに一致するものがないため、すべてのインテントがテストに失敗します。 +ただし、{@link android.content.Intent} がアクションを指定していない場合は、テストに合格します(フィルタに少なくとも 1 つのアクションが含まれている必要があります)。 + +

+ + + +

カテゴリのテスト

+ +

受け入れるインテントのカテゴリを指定するには、インテント フィルタでゼロ個以上の {@code +<category>} 要素を宣言します。 +次に例を示します。

+ +
+<intent-filter>
+    <category android:name="android.intent.category.DEFAULT" />
+    <category android:name="android.intent.category.BROWSABLE" />
+    ...
+</intent-filter>
+
+ +

インテントがカテゴリのテストに合格するには、{@link android.content.Intent} 内のすべてのカテゴリが、フィルタ内のカテゴリに一致する必要があります。 +インテント フィルタでは、{@link android.content.Intent} で指定されたカテゴリよりも多くのカテゴリが宣言されている場合もあるため、すべてが {@link android.content.Intent} に一致しなくてもテストには合格します。— + +そのため、カテゴリのないインテントは、フィルタで宣言されているカテゴリに関係なく、常にこのテストに合格することになります。 +

+ +

注: Androidは自動的に、{@link +android.content.Context#startActivity startActivity()} と {@link +android.app.Activity#startActivityForResult startActivityForResult()} に渡されるすべての暗黙的インテントに {@link android.content.Intent#CATEGORY_DEFAULT} カテゴリを自動的に適用します。そのため、アクティビティで暗黙的インテントを受け取りたい場合は、前出の {@code <intent-filter>} の例のように、アクティビティのインテント フィルタに {@code "android.intent.category.DEFAULT"} のカテゴリを含める必要があります。 + + + + +

+ + + +

データのテスト

+ +

受け入れるインテント データを指定するには、インテント フィルタでゼロ個以上の {@code +<data>} 要素を宣言します。 +次に例を示します。

+ +
+<intent-filter>
+    <data android:mimeType="video/mpeg" android:scheme="http" ... />
+    <data android:mimeType="audio/mpeg" android:scheme="http" ... />
+    ...
+</intent-filter>
+
+ +

<data> 要素では、URI 構造とデータタイプ(MIME メディア タイプ)を指定できます。 +URI の各パートには、{@code scheme}、{@code host}、{@code port}、{@code path} の個別の属性があります。— +— + +

+ +

{@code <scheme>://<host>:<port>/<path>}

+ +

+次に例を示します。 +

+ +

{@code content://com.example.project:200/folder/subfolder/etc}

+ +

この URI では、スキームが {@code content}、ホストが {@code com.example.project}、ポートが {@code 200}、パスが {@code folder/subfolder/etc} です。 + +

+ +

これらの各属性は {@code <data>} 要素では省略可能ですが、一次従属があります。 +

+ + +

インテントの URI をフィルタの URI 仕様に比較するときは、フィルタに含まれる URI の一部でのみ比較されます。 +次に例を示します。

+ + +

注: パスの指定では、ワイルドカードのアスタリスク(*)を使ってパス名の部分一致のみを要求することもできます。 +

+ +

データのテストでは、インテントの URI と MIME タイプの両方を、フィルタで指定された URI と MIME タイプと比較します。 +規則は次のとおりです。 +

+ +
    +
  1. URI も MIME タイプも含まないインテントは、フィルタで URI や MIME タイプが指定されていない場合のみテストをパスします。 +
  2. + +
  3. URI を含んでいて MIME タイプを含んでいないインテント(明示的にも含まれておらず、URI からも推測できない)場合は、URI がフィルタの URI 形式に一致し、フィルタが MIME タイプを指定していない場合のみテストをパスします。 + +
  4. + +
  5. MIME タイプを含んでいて、URI を含んでいないインテントは、フィルタのリストに同じ MIME タイプがあり、URI 形式が指定されていない場合のみテストをパスします。 +
  6. + +
  7. URI と MINE タイプの両方を含む(明示的か、URI からの推測)インテントは、MIME タイプがフィルタのリストにあるタイプに一致した場合のみ、テストの MIME タイプのパートをパスします。 + +テストの URI のパートは、URI がフィルタの URI に一致するか、{@code content:} URI か {@code file:} URI があって URI が指定されていない場合にパスできます。つまり、フィルタにリストに MIME タイプのみがある場合、コンポーネントは {@code content:} データと {@code file:} データをサポートすると推定されます。 + + + +

  8. +
+ +

+この最後の規則(d)は、コンポーネントがファイルやコンテンツ プロバイダからのローカル データを取得できるという予測を反映しています。そのため、フィルタにはデータタイプのみをリストして、{@code content:} スキームや {@code file:} スキームを明示的に指定する必要はありません。これは一般的なケースです。 + + + +たとえば、次の {@code <data>} 要素は、コンポーネントがコンテンツ プロバイダからの画像データを取得し、それを表示できることを Android に伝えています。 + + +

+ +
+<intent-filter>
+    <data android:mimeType="image/*" />
+    ...
+</intent-filter>
+ +

+利用可能なデータはほとんどコンテンツプロバイダによって投入されることから、データタイプが指定されていて URI 指定されていないデータが最も一般的です + +

+ +

+もうひとつ一般的な設定として、スキームと データタイプを使ったフィルタがあります。たとえば、次の {@code <data>} 要素は、アクションを実行するためにコンポーネントがネットワークから動画データを取得できることを Android に伝えています。 + + + +

+ +
+<intent-filter>
+    <data android:scheme="http" android:type="video/*" />
+    ...
+</intent-filter>
+ + + +

インテントのマッチング

+ +

インテントをインテント フィルタにマッチングする目的には、アクティベートするターゲットを発見するということだけでなく、端末上のコンポーネントのセットに関連する何かを発見するという目的があります。 + +たとえば、ホーム アプリは {@link android.content.Intent#ACTION_MAIN} アクションと {@link android.content.Intent#CATEGORY_LAUNCHER} カテゴリを指定するインテント フィルタを持つすべてのアクティビティを見つけることで、アプリ ランチャーを設定します。 + + +

+ +

アプリケーションでも、同様の方法でインテントのマッチングを使用できます。{@link android.content.pm.PackageManager} には特定のインテントを受け入れることのできるすべてのコンポーネントを返す一連の {@code query...()} メソッドと、インテントに応答できる最適なコンポーネントを返す同様のシリーズの {@code resolve...()} メソッドがあります。 + + + +たとえば、{@link android.content.pm.PackageManager#queryIntentActivities +queryIntentActivities()} は引数として渡されたインテントを実行できるすべてのアクティビティの一覧を返し、{@link +android.content.pm.PackageManager#queryIntentServices +queryIntentServices()} は同様のサービスの一覧を返します。いずれのメソッドでもコンポーネントのアクティベートは行われず、応答できるものを列挙するだけです。 + + + +ブロードキャスト レシーバー用にも、同様のメソッド {@link android.content.pm.PackageManager#queryBroadcastReceivers +queryBroadcastReceivers()} があります。 + +

+ + + + diff --git a/docs/html-intl/intl/ja/guide/components/loaders.jd b/docs/html-intl/intl/ja/guide/components/loaders.jd new file mode 100644 index 0000000000000000000000000000000000000000..bc936774f721ba6af82085ea3ab9b33b768e106e --- /dev/null +++ b/docs/html-intl/intl/ja/guide/components/loaders.jd @@ -0,0 +1,494 @@ +page.title=ローダ +parent.title=アクティビティ +parent.link=activities.html +@jd:body +
+
+

本書の内容

+
    +
  1. Loader API の概要
  2. +
  3. アプリケーションでローダを使用する +
      +
    1. +
    2. ローダを開始する
    3. +
    4. ローダを再開する
    5. +
    6. LoaderManager コールバックを使用する
    7. +
    +
  4. +
  5. +
      +
    1. その他の例
    2. +
    +
  6. +
+ +

キークラス

+
    +
  1. {@link android.app.LoaderManager}
  2. +
  3. {@link android.content.Loader}
  4. + +
+ +

関連サンプル

+
    +
  1. +LoaderCursor
  2. +
  3. +LoaderThrottle
  4. +
+
+
+ +

Android 3.0 で導入されたローダによって、アクティビティやフラグメントでのデータの非同期ロードが簡単になりました。 +ローダには、次の 3 つの特徴があります。

+ + +

Loader API の概要

+ +

アプリケーションでローダを使用するのに必要になりそうなクラスやインターフェースは複数あります。 +次の表で、それらの概要をまとめました。

+ + + + + + + + + + + + + + + + + + + + + + + + + + +
クラス/インターフェース説明
{@link android.app.LoaderManager}1 つ以上の {@link +android.content.Loader} インスタンスを管理するための、{@link android.app.Activity} や {@link android.app.Fragment} に関連した抽象クラスです。 +これにより、アプリケーションは {@link android.app.Activity} や {@link android.app.Fragment} のライフサイクルと連動して長時間の操作を管理できるようになります。最も一般的なのは、{@link android.content.CursorLoader} で使用する方法ですが、アプリケーションでは他のタイプのデータのロード用に、独自のローダを自由に作成することもできます。 + + + + +
+
+ 1 つのアクティビティやフラグメントごとに、{@link android.app.LoaderManager} は 1 つだけ存在しますが、{@link android.app.LoaderManager} は複数のローダを持つことができます。 +
{@link android.app.LoaderManager.LoaderCallbacks}クライアントが {@link +android.app.LoaderManager} とやり取りするためのコールバック インターフェースです。たとえば、{@link +android.app.LoaderManager.LoaderCallbacks#onCreateLoader onCreateLoader()} コールバック メソッドを使用してと新しいローダを作成します。 +
{@link android.content.Loader}非同期のデータロードを実行する抽象クラスです。これは、ローダの基本クラスです。 +通常は {@link +android.content.CursorLoader} を使用しますが、独自のサブクラスを実装することもできます。ローダがアクティブな間は、データのソースを管理し、コンテンツが変更されたときに新しい結果を配信します。 + +
{@link android.content.AsyncTaskLoader}処理を行うための {@link android.os.AsyncTask} を提供する抽象的なローダです。
{@link android.content.CursorLoader}{@link android.content.ContentResolver} に問い合わせて {@link +android.database.Cursor} を返す{@link android.content.AsyncTaskLoader} のサブクラスです。 +これは、カーソルのクエリ用に標準的な方法で {@link +android.content.Loader} プロトコルを実装するクラスで、アプリケーションの UI をブロックせずにバックグラウンドでカーソルのクエリを実行するように、{@link android.content.AsyncTaskLoader} を基に構築されています。{@link +android.content.ContentProvider} からデータを非同期的にロードする際は、フラグメントの API やアクティビティの API 経由でマネージド クエリを実行するのではなく、このローダを使用するのが最適です。 + + + +
+ +

上の表のクラスとインターフェースは、アプリケーションでローダを実装する際に使用する必須コンポーネントです。 +作成するローダごとにこれらすべてが必要になるわけではありませんが、{@link +android.app.LoaderManager} への参照は、ローダを初期化したり {@link +android.content.CursorLoader} などの {@link android.content.Loader} を実装したりするには {@link +android.app.LoaderManager} への参照が常に必要になります。 +次のセクションでは、アプリケーションでのこれらのクラスとインターフェースの使用方法を説明します。 +

+ +

アプリケーションでローダを使用する

+

このセクションでは、Android アプリケーションのローダの使用方法について説明します。通常、ローダを使用するアプリケーションには次の内容が含まれます。 +

+ +

ローダを開始する

+ +

{@link android.app.LoaderManager} は 1 つ以上の {@link +android.content.Loader} インスタンスを {@link android.app.Activity} や {@link android.app.Fragment} 内で管理します。 +1 つのアクティビティやフラグメントごとに、{@link +android.app.LoaderManager} は 1 つだけ存在します。

+ +

通常は、アクティビティの {@link +android.app.Activity#onCreate onCreate()} メソッドか、フラグメントの {@link android.app.Fragment#onActivityCreated onActivityCreated()} メソッド内で {@link android.content.Loader} を初期化します。 + +その方法は次のとおりです。 +

+ +
// Prepare the loader.  Either re-connect with an existing one,
+// or start a new one.
+getLoaderManager().initLoader(0, null, this);
+ +

{@link android.app.LoaderManager#initLoader initLoader()} メソッドが次のパラメータを受け取ります。 +

+ +

{@link android.app.LoaderManager#initLoader initLoader()} の呼び出しによって、ローダが初期化され、アクティブになります。 +結果には次の 2 つの可能性があります。

+ +

いずれの場合でも、その {@link android.app.LoaderManager.LoaderCallbacks} の実装はローダに関連付けられ、ローダの状態が変化したときに呼び出されます。 + +この呼び出しの時点で、呼び出し側が開始された状態にあり、要求されたローダが既に存在し、データを生成済みの場合は、システムはただちに {@link +android.app.LoaderManager.LoaderCallbacks#onLoadFinished onLoadFinished()} を呼び出す({@link android.app.LoaderManager#initLoader initLoader()} の間に)ため、それに備えておく必要があります。 + + + +このコールバックの詳細については、 +onLoadFinished をご覧ください。

+ +

{@link android.app.LoaderManager#initLoader initLoader()} メソッドは作成された {@link android.content.Loader} を返しますが、そこへの参照はキャプチャしないことに注意してください。 + +{@link android.app.LoaderManager} は自動的にローダの生存状態を管理します。 +{@link android.app.LoaderManager} は必要に応じて開始と停止を行いし、ローダとそれに関連付けられたコンテンツの状態を管理します。 + +このことからもわかるように、ローダと直接やり取りすることはほとんどありません(ローダの動作を微調整するローダ メソッドの使用例については、 LoaderThrottle のサンプルをご覧ください)。 + +特定のイベントが発生したときにローディングの処理に干渉することを目的に、{@link +android.app.LoaderManager.LoaderCallbacks} を使用することがよくあります。 + +このトピックの詳細については、LoadManager コールバックを使用するをご覧ください。

+ +

ローダを再開する

+ +

上記のように {@link android.app.LoaderManager#initLoader initLoader()} を使用する場合、指定した ID があれば既存のローダを使用し、なければ新たに作成します。 + +ただし、古いデータを破棄して最初からやり直したいこともあります。 +

+ +

古いデータを破棄するには、{@link +android.app.LoaderManager#restartLoader restartLoader()} を使用します。たとえば、この {@link android.widget.SearchView.OnQueryTextListener} を実装すると、ユーザーのクエリが変化したときにローダが再開されます。 + +新しい検索フィルタを使用して新しいクエリを実行できるように、ローダは再開される必要があります。 +

+ +
+public boolean onQueryTextChanged(String newText) {
+    // Called when the action bar search text has changed.  Update
+    // the search filter, and restart the loader to do a new query
+    // with this filter.
+    mCurFilter = !TextUtils.isEmpty(newText) ? newText : null;
+    getLoaderManager().restartLoader(0, null, this);
+    return true;
+}
+ +

LoaderManager コールバックを使用する

+ +

{@link android.app.LoaderManager.LoaderCallbacks} はクライアントが {@link android.app.LoaderManager} とやり取りできるようにするコールバック インターフェースです。 +

+

特に、{@link android.content.CursorLoader} のローダでは、停止後もデータを保持しておくことが望まれます。 +これにより、アプリケーションがアクティビティやフラグメントの {@link android.app.Activity#onStop +onStop()} メソッドや {@link android.app.Activity#onStart onStart()} メソッド全体でデータを維持することができるので、ユーザーがアプリケーションに戻ったときにデータの再ロードを待つ必要がありません。 + + +新しいローダを作成するタイミングを知りたいときや、ローダのデータの使用を停止するタイミングをアプリケーションに伝えるときは、{@link android.app.LoaderManager.LoaderCallbacks} メソッドを使用します。 + +

+ +

{@link android.app.LoaderManager.LoaderCallbacks} には次のメソッドが含まれています。 +

+ + + +

これらのメソッドについては、次のセクションで詳しく説明します。

+ +

onCreateLoader

+ +

ローダにアクセスしようとしたとき(たとえば、{@link +android.app.LoaderManager#initLoader initLoader()} 経由など)、ID で指定したローダが存在するかどうかを確認されます。 +存在しない場合は、{@link +android.app.LoaderManager.LoaderCallbacks} メソッドの {@link +android.app.LoaderManager.LoaderCallbacks#onCreateLoader onCreateLoader()} をトリガーします。ここで、新しいローダを作成します。 +通常は、{@link +android.content.CursorLoader} になりますが、独自の {@link +android.content.Loader} サブクラスを実装することもできます。

+ +

この例では、{@link +android.app.LoaderManager.LoaderCallbacks#onCreateLoader onCreateLoader()} +コールバック メソッドが {@link android.content.CursorLoader} を作成します。{@link android.content.CursorLoader}は、そのコンストラクタ メソッドを使用して構築する必要があり、{@link +android.content.ContentProvider} へのクエリを実行するのに必要なすべての情報が必要になります。 + +具体的に必要な情報は次のとおりです。

+ +

次に例を示します。

+
+ // If non-null, this is the current filter the user has provided.
+String mCurFilter;
+...
+public Loader<Cursor> onCreateLoader(int id, Bundle args) {
+    // This is called when a new Loader needs to be created.  This
+    // sample only has one Loader, so we don't care about the ID.
+    // First, pick the base URI to use depending on whether we are
+    // currently filtering.
+    Uri baseUri;
+    if (mCurFilter != null) {
+        baseUri = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI,
+                  Uri.encode(mCurFilter));
+    } else {
+        baseUri = Contacts.CONTENT_URI;
+    }
+
+    // Now create and return a CursorLoader that will take care of
+    // creating a Cursor for the data being displayed.
+    String select = "((" + Contacts.DISPLAY_NAME + " NOTNULL) AND ("
+            + Contacts.HAS_PHONE_NUMBER + "=1) AND ("
+            + Contacts.DISPLAY_NAME + " != '' ))";
+    return new CursorLoader(getActivity(), baseUri,
+            CONTACTS_SUMMARY_PROJECTION, select, null,
+            Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC");
+}
+

onLoadFinished

+ +

このメソッドは、前に作成したローダがロードを完了したときに呼び出されます。このメソッドは、このローダが提供した最後のデータが解放される前に呼び出されることが保証されています。 + +この時点で、すべての古いデータを削除する必要がありますが(まもなく解放されるため)、データの所有者はローダでありローダが処理するため、自身でデータを解放しないようにしてください。 + +

+ + +

アプリケーションがもうデータを使用していないことを検知すると、ローダがデータを解放します。 +たとえば、データが {@link +android.content.CursorLoader} からのカーソルの場合は、自身で {@link +android.database.Cursor#close close()} を呼び出さないようにしてください。カーソルが {@link android.widget.CursorAdapter} に置かれている場合は、古い {@link android.database.Cursor} がクローズされないように {@link +android.widget.SimpleCursorAdapter#swapCursor swapCursor()} メソッドを使用する必要があります。 + +次に例を示します。

+ +
+// This is the Adapter being used to display the list's data.
SimpleCursorAdapter mAdapter; +... + +public void onLoadFinished(Loader<Cursor> loader, Cursor data) { + // Swap the new cursor in.  (The framework will take care of closing the + // old cursor once we return.) + mAdapter.swapCursor(data); +}
+ +

onLoaderReset

+ +

このメソッドは、前に作成されたローダがリセットされ、データが利用できなくなったときに呼び出されます。 +このコールバックにより、データが解放されるタイミングがわかり、そのデータへの参照を削除できます。 +  

+

この実装では、null の値を使用して {@link android.widget.SimpleCursorAdapter#swapCursor swapCursor()} を呼び出します。 + +

+ +
+// This is the Adapter being used to display the list's data.
+SimpleCursorAdapter mAdapter;
+...
+
+public void onLoaderReset(Loader<Cursor> loader) {
+    // This is called when the last Cursor provided to onLoadFinished()
+    // above is about to be closed.  We need to make sure we are no
+    // longer using it.
+    mAdapter.swapCursor(null);
+}
+ + +

+ +

以下は、連絡先のコンテンツ プロバイダに対するクエリの結果が含まれた {@link android.widget.ListView} を表示する {@link +android.app.Fragment} の完全な実装の例です。 +{@link +android.content.CursorLoader} を使用してプロバイダへのクエリを管理しています。

+ +

この例にあるように、アプリケーションがユーザーの連絡先にアクセスするには、マニフェストに {@link android.Manifest.permission#READ_CONTACTS READ_CONTACTS} の許可を含める必要があります。 + +

+ +
+public static class CursorLoaderListFragment extends ListFragment
+        implements OnQueryTextListener, LoaderManager.LoaderCallbacks<Cursor> {
+
+    // This is the Adapter being used to display the list's data.
+    SimpleCursorAdapter mAdapter;
+
+    // If non-null, this is the current filter the user has provided.
+    String mCurFilter;
+
+    @Override public void onActivityCreated(Bundle savedInstanceState) {
+        super.onActivityCreated(savedInstanceState);
+
+        // Give some text to display if there is no data.  In a real
+        // application this would come from a resource.
+        setEmptyText("No phone numbers");
+
+        // We have a menu item to show in action bar.
+        setHasOptionsMenu(true);
+
+        // Create an empty adapter we will use to display the loaded data.
+        mAdapter = new SimpleCursorAdapter(getActivity(),
+                android.R.layout.simple_list_item_2, null,
+                new String[] { Contacts.DISPLAY_NAME, Contacts.CONTACT_STATUS },
+                new int[] { android.R.id.text1, android.R.id.text2 }, 0);
+        setListAdapter(mAdapter);
+
+        // Prepare the loader.  Either re-connect with an existing one,
+        // or start a new one.
+        getLoaderManager().initLoader(0, null, this);
+    }
+
+    @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+        // Place an action bar item for searching.
+        MenuItem item = menu.add("Search");
+        item.setIcon(android.R.drawable.ic_menu_search);
+        item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
+        SearchView sv = new SearchView(getActivity());
+        sv.setOnQueryTextListener(this);
+        item.setActionView(sv);
+    }
+
+    public boolean onQueryTextChange(String newText) {
+        // Called when the action bar search text has changed.  Update
+        // the search filter, and restart the loader to do a new query
+        // with this filter.
+        mCurFilter = !TextUtils.isEmpty(newText) ? newText : null;
+        getLoaderManager().restartLoader(0, null, this);
+        return true;
+    }
+
+    @Override public boolean onQueryTextSubmit(String query) {
+        // Don't care about this.
+        return true;
+    }
+
+    @Override public void onListItemClick(ListView l, View v, int position, long id) {
+        // Insert desired behavior here.
+        Log.i("FragmentComplexList", "Item clicked: " + id);
+    }
+
+    // These are the Contacts rows that we will retrieve.
+    static final String[] CONTACTS_SUMMARY_PROJECTION = new String[] {
+        Contacts._ID,
+        Contacts.DISPLAY_NAME,
+        Contacts.CONTACT_STATUS,
+        Contacts.CONTACT_PRESENCE,
+        Contacts.PHOTO_ID,
+        Contacts.LOOKUP_KEY,
+    };
+    public Loader<Cursor> onCreateLoader(int id, Bundle args) {
+        // This is called when a new Loader needs to be created.  This
+        // sample only has one Loader, so we don't care about the ID.
+        // First, pick the base URI to use depending on whether we are
+        // currently filtering.
+        Uri baseUri;
+        if (mCurFilter != null) {
+            baseUri = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI,
+                    Uri.encode(mCurFilter));
+        } else {
+            baseUri = Contacts.CONTENT_URI;
+        }
+
+        // Now create and return a CursorLoader that will take care of
+        // creating a Cursor for the data being displayed.
+        String select = "((" + Contacts.DISPLAY_NAME + " NOTNULL) AND ("
+                + Contacts.HAS_PHONE_NUMBER + "=1) AND ("
+                + Contacts.DISPLAY_NAME + " != '' ))";
+        return new CursorLoader(getActivity(), baseUri,
+                CONTACTS_SUMMARY_PROJECTION, select, null,
+                Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC");
+    }
+
+    public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
+        // Swap the new cursor in.  (The framework will take care of closing the
+        // old cursor once we return.)
+        mAdapter.swapCursor(data);
+    }
+
+    public void onLoaderReset(Loader<Cursor> loader) {
+        // This is called when the last Cursor provided to onLoadFinished()
+        // above is about to be closed.  We need to make sure we are no
+        // longer using it.
+        mAdapter.swapCursor(null);
+    }
+}
+

その他の例

+ +

ApiDemos には、ローダの使用方法を示す他のサンプルがいくつか用意されています。 +

+ + +

SDK サンプルのダウンロードとインストールの詳細については、Getting the Samples をご覧ください。 +

+ diff --git a/docs/html-intl/intl/ja/guide/components/processes-and-threads.jd b/docs/html-intl/intl/ja/guide/components/processes-and-threads.jd new file mode 100644 index 0000000000000000000000000000000000000000..691a5f4718801cce755f073b570a3fd273cf6cfb --- /dev/null +++ b/docs/html-intl/intl/ja/guide/components/processes-and-threads.jd @@ -0,0 +1,411 @@ +page.title=プロセスとスレッド +page.tags=lifecycle,background + +@jd:body + + + +

アプリケーション コンポーネントが開始し、アプリケーションに他に実行中のコンポーネントがない場合、Android システムは実行用のシングル スレッドを持つアプリケーション用の新しい Linux プロセスを開始します。 + +デフォルトでは、同じアプリケーションのすべてのコンポーネントは同じプロセスとスレッド(「メイン」 スレッドと呼ばれます)で実行します。 +アプリケーション コンポーネントが開始したときに、既にそのアプリケーションのプロセスが存在する場合(アプリケーションからの他のコンポーネントが存在するため)、コンポーネントはそのプロセス内で開始し、同じ実行用のスレッドを使用します。 + +ただし、アプリケーション内の別のコンポーネントを別のプロセスで実行するよう調整でき、あらゆるプロセスに対して追加のスレッドを作成できます。 + +

+ +

このドキュメントでは、Android アプリケーションでプロセスとスレッドがどのように動作するかについて説明します。

+ + +

プロセス

+ +

デフォルトでは、同じアプリケーションのすべてのコンポーネントは同じプロセスで実行し、ほとんどのアプリケーションでこの動作を変更する必要はありません。 +ただし、特定のコンポーネントが属するプロセスを管理する必要がある場合は、マニフェスト ファイルでそれを行うことができます。 +

+ +

コンポーネント要素の各タイプのマニフェスト エントリ({@code +<activity>}{@code +<service>}{@code +<receiver>}{@code +<provider>})は、コンポーネントを実行するプロセスを指定できる {@code android:process} 属性をサポートしています。— +—この属性を設定して、各コンポーネントが独自のプロセスで実行するようにしたり、一部のコンポーネントで同じプロセスを共有し、残りのコンポーネントでは別のプロセスを使用するようにしたりできます。 +また、{@code android:process} を設定すると、異なるアプリケーションのコンポーネントを同じプロセスで実行させることもできます。この場合、アプリケーションが同じ Linux ユーザー ID を共有していて、同じ証明書で署名されている必要があります。 +— + +

+ +

{@code +<application>} 要素も {@code android:process} 属性をサポートしており、すべてのコンポーネントに適用されるデフォルトの値を設定します。 +

+ +

メモリの空きが少なくなり、早急にユーザーに提供する必要のあるプロセスが必要とする場合は、Android がプロセスをどこかの時点でシャットダウンするよう決定する場合があります。 +破棄されたプロセスで実行しているアプリケーション コンポーネントは、結果的に破棄されます。 +プロセスは、それらのコンポーネントの処理が再度発生したときに再開されます。 +

+ +

破棄するプロセスを決定する際、Android システムはユーザーへの相対的な重要度を測ります。 +たとえば、画面に見えているアクティビティをホストするプロセスよりも、もう画面に見えていないアクティビティをホストするプロセスの方が先にシャットダウンされることになります。 +そのため、プロセスを停止するかどうかは、そのプロセスで実行しているコンポーネントの状態によって決まります。 +ここから、停止するプロセスを決定する規則について詳しく説明していきます。 +

+ + +

プロセスのライフサイクル

+ +

Android システムは、可能な限り長期間アプリケーション プロセスを維持しようとしますが、新たに重要なプロセスが発生した際には、メモリを回収するために古いプロセスをいずれは削除する必要が生じます。 +どのプロセスを維持して、どのプロセスを強制終了するかを決定するため、システムはプロセスで実行しているコンポーネントとコンポーネントの状態に基づいて、各プロセスを「重要度の階層」に位置付けします。 + + +まず、重要度の最も低いプロセスが除去され、その後システム リソースを回復できるまで重要度の低い順に除去していきます。 + +

+ +

重要度の階層には 5 つのレベルがあります。次の一覧では、さまざまなプロセスのタイプを重要度の高い順に表しています(1 つ目のプロセスが最も重要度が高く最後に強制終了されます)。 + +

+ +
    +
  1. フォアグラウンド プロセス +

    ユーザーが現在行っている操作に必要なプロセスです。次の条件のいずれかにあてはまる場合、そのプロセスはフォアグラウンドにあるとみなされます。 +

    + +
      +
    • ユーザーが操作している {@link android.app.Activity} のホストになっている({@link +android.app.Activity} の{@link android.app.Activity#onResume onResume()} メソッドが呼び出された)。 +
    • + +
    • ユーザーが操作しているアクティビティにバインドされている {@link android.app.Service} のホストになっている。 +
    • + +
    • 「フォアグラウンド」 で実行中の {@link android.app.Service} のホストになっている(サービスが {@link android.app.Service#startForeground startForeground()} を呼び出した)。— + + +
    • {@link android.app.Service#onCreate onCreate()}、{@link android.app.Service#onStart +onStart()}、{@link android.app.Service#onDestroy onDestroy()} のいずれかのライフサイクル コールバックを実行している {@link android.app.Service} のホストになっている。 +
    • + +
    • {@link + android.content.BroadcastReceiver#onReceive onReceive()} メソッドを実行している {@link android.content.BroadcastReceiver} のホストになっている。
    • +
    + +

    通常は、2~3 個のフォアグラウンド プロセスが存在します。フォアグラウンド プロセスは、それらすべてを実行できなくなるほどメモリが少なくなると、最終手段として強制終了されます。 +—通常はその時点で、端末がメモリのページング状態に達しているため、ユーザー インターフェースのレスポンシブを維持するには一部のフォアグラウンド プロセスを強制終了する必要があります。 + +

  2. + +
  3. 可視プロセス +

    フォアグラウンド コンポーネントはないものの、ユーザーに対して画面上に表示される内容に影響を与える可能性のあるプロセスです。 +次の条件のいずれかにあてはまる場合、そのプロセスは可視プロセスであるとみなされます。 +

    + +
      +
    • フォアグラウンドにないが、ユーザーに表示されている {@link android.app.Activity} のホストになっている({@link android.app.Activity#onPause onPause()} メソッドが呼び出された)。 +たとえば、フォアグラウンドのアクティビティがダイアログを開始したときに、前のアクティビティがその背後に見えている場合などがあります。 + +
    • + +
    • 可視(またはフォアグラウンドの)アクティビティにバインドされている {@link android.app.Service} のホストになっている。 +
    • +
    + +

    可視プロセスは非常に重要度が高いため、フォアグラウンド プロセスの実行を維持するのに必要な場合のみ、強制終了されます。 +

    +
  4. + +
  5. サービス プロセス +

    {@link +android.content.Context#startService startService()} メソッドで開始されたサービスを実行するプロセスで、上の 2 つのカテゴリに分類されないものです。 +サービス プロセスは、ユーザーに表示される内容には直接関係ありませんが、ユーザーにとって必要な操作を実行している場合が多いため(バックグラウンドで音楽を再生したり、ネットワーク経由でデータをダウンロードしたりなど)、フォアグラウンド プロセスと可視プロセスのすべてと合わせて、それらを継続するのにメモリが不足した場合のみ強制終了されます。 + + +

    +
  6. + +
  7. バックグラウンド プロセス +

    現在ユーザーに表示されていないアクティビティを有するプロセスです(アクティビティの {@link android.app.Activity#onStop onStop()} メソッドが呼び出された)。 +ユーザーの操作性に直接影響を与えるものではなく、フォアグラウンド プロセス、可視プロセス、サービス プロセス用にメモリを回収する必要があればいつでも強制終了されます。 + + +通常はバックグラウンドで実行するプロセスは多数あるため、最近ユーザーに表示されたアクティビティのあるプロセスを最後に強制終了するよう、LRU(最小使用頻度)リストに入れられます。 + +アクティビティがライフサイクル メソッドを正確に実装し、現在の状態を保存する場合は、そのプロセスを強制終了しても、ユーザーがそのアクティビティに戻ったときに、アクティビティがすべての視覚的状態を復元するため、ユーザーの操作性に視覚的な影響はありません + + +。状態の保存と復元の詳細については、「Activities」のドキュメントをご覧ください。 +

    +
  8. + +
  9. 空のプロセス +

    アクティブなアプリケーション コンポーネントが 1 つも含まれていないプロセスです。このようなプロセスは、プロセスをキャッシュしておくことのみを目的として保持され、次回コンポーネントを実行する際の起動時間を向上させることができます。 + +システムは、プロセスのキャッシュと下層のカーネル キャッシュとの間の全体的なシステム リソースのバランスを整える目的でこれらのシステムを頻繁に強制終了します。 +

    +
  10. +
+ + +

Android では、プロセスで現在アクティブなコンポーネントの重要度に基づいて、あてはまるランクのなかで最も高いランクにプロセスを位置付けます。 +たとえば、サービス アクティビティと可視アクティビティの両方のホストとなっているプロセスは、サービス プロセスではなく、可視プロセスとして位置付けられます。 +

+ +

さらに、他のプロセスから依存されているプロセスの位置付けが上がる場合があります。他のプロセスのために動作しているプロセスは、その対象プロセスよりも下に位置付けられることはありません。 +— +たとえば、プロセス A のコンテンツ プロバイダが、プロセス B のクライアントのために動作している場合や、プロセス A のサービスがプロセス B のコンポーネントにバインドされている場合、プロセス A の重要度は常にプロセス B 以上であるとみなされます。 + +

+ +

サービスを実行するプロセスは、バックグラウンドのアクティビティを持つプロセスよりも上に位置付けされるため、長時間の操作を開始するアクティビティでは、特に、操作がアクティビティよりも長く続く場合、ワーカー スレッドを作成するよりもその操作のサービスを開始する方がよいと考えられます。たとえば、ウェブサイトに写真をアップロードするアクティビティでは、ユーザーがアクティビティから離れた後もアップロードをバックグラウンドで続行できるよう、アップロードを実行するサービスを開始することをお勧めします。サービスを使用することで、アクティビティの状況に変わらず、その操作に「サービス プロセス」以上の優先度が保証されることになります。 + + + + + +同じ理由から、ブロードキャスト レシーバーでもスレッドに長時間の処理を置くのではなく、サービスを採用するようお勧めします。 +

+ + + + +

スレッド

+ +

アプリケーション起動の際、システムは アプリケーション実行用のスレッドを作成します。これは、「メイン スレッド」と呼ばれます。 +このスレッドは、イベント(描画イベントを含む)を適切なユーザー インターフェース ウィジェットに送信する役割を担うため非常に重要です。 +また、これはアプリケーションが Android UI ツールキット({@link +android.widget} と {@link android.view} パッケージからのコンポーネント)からのコンポーネントとやり取りをするスレッドでもあります。 +そのため、メイン スレッドは UI スレッドと呼ばれることもあります。 +

+ +

コンポーネントのインスタンスごとに別のスレッドが作成されることはありません。同じプロセスで実行するすべてのコンポーネントは UI スレッドでインスタンス化され、スレッドから送られた各コンポーネントをシステムが呼び出します。 + +結果的に、システムのコールバック(ユーザー操作を報告する {@link android.view.View#onKeyDown onKeyDown()} やライフサイクル コールバック メソッドなど)に応答するメソッドは常にプロセスの UI スレッドで実行することになります。 + +

+ +

たとえば、ユーザーが画面上のボタンをタッチすると、アプリの UI スレッドがタッチ イベントをウィジェットに送信し、ウィジェットがそのタッチされた状態を設定してイベント キューに無効化の要求を投稿します。 + +UI スレッドが要求をキューから取り出し、ウィジェットに自身を描画するよう通知します。 +

+ +

アプリがユーザー操作に応答して集中的な動作を実行する場合、アプリケーションを正しく実装していない限りこのシングル スレッド モデルではパフォーマンスの低下につながる可能性があります。 +具体的には、すべてが UI スレッドで行われている場合、長ネットワークへのアクセスやデータベースへの問い合わせといった時間のかかる操作を実行すると UI 全体をブロックしてしまいます。スレッドがブロックされると、描画イベントを含むすべてのイベントを送信できなくなります。 + + +ユーザー側には、アプリケーションがハングしたように見えます。 +さらには、UI スレッドが数秒以上(現時点では 5 秒以上)ブロックされると、ユーザーに「アプリケーションが応答していません」のダイアログが表示されます。 + +ユーザーはアプリケーションを停止するか、不快な場合はアンインストールしてしまう可能性があります。 +

+ +

また、Android UI ツールキットはスレッド セーフではありません。そのため、ワーカー スレッドから UI を操作できません。すべての操作は、UI スレッドから行う必要があります。 +— +そのため、Android のシングル スレッド モデルには 2 つの明快なルールがあります。

+ +
    +
  1. UI スレッドをブロックしない +
  2. UI スレッド以外から Android UI ツールキットにアクセスしない +
+ +

ワーカー スレッド

+ +

上記で説明したシングル スレッド モデルにより、アプリケーションの UI の応答性のためにも UI スレッドをブロックしないことが不可欠です。 +即座に実行する必要のない操作の場合は、別のスレッド(「バックグラウンド」スレッドや「ワーカー」スレッド)で実行するようにする必要があります。 + +

+ +

例として、別のスレッドから画像をダウンロードして {@link android.widget.ImageView} に表示するクリック リスナのコードの一部を次に示します。 +

+ +
+public void onClick(View v) {
+    new Thread(new Runnable() {
+        public void run() {
+            Bitmap b = loadImageFromNetwork("http://example.com/image.png");
+            mImageView.setImageBitmap(b);
+        }
+    }).start();
+}
+
+ +

ここでは、ネットワークの操作を処理する新しいスレッドを作成しているため、一見問題ないように見えます。 +ただし、これはUI スレッド以外から Android UI ツールキットにアクセスしないというシングルスレッド モデルの 2 つ目のルールに違反しています。このサンプルは、UI スレッドではなくワーカー スレッドから {@link +android.widget.ImageView} を変更しています。— +結果として、未定義かつ予想外の動作を引き起こし、追跡が難しく時間のかかる作業になってしまいます。 +

+ +

この問題を修正するため、Android には UI スレッド以外からのアクセス方法がいくつか用意されています。 +使用できるメソッドは次のとおりです。

+ + + +

たとえば、上記のコードは {@link +android.view.View#post(java.lang.Runnable) View.post(Runnable)} メソッドを使用して修正できます。

+ +
+public void onClick(View v) {
+    new Thread(new Runnable() {
+        public void run() {
+            final Bitmap bitmap = loadImageFromNetwork("http://example.com/image.png");
+            mImageView.post(new Runnable() {
+                public void run() {
+                    mImageView.setImageBitmap(bitmap);
+                }
+            });
+        }
+    }).start();
+}
+
+ +

これで、この実装がスレッドセーフになりました。ネットワーク操作は別のスレッドから実行され、{@link android.widget.ImageView} は UI スレッドから操作されます。 +

+ +

ただし、操作が複雑になるにつれて、この種のコードも複雑化してメンテナンスも難しくなります。 +ワーカー スレッドとのより複雑なやり取りを処理するため、ワーカー スレッドで {@link android.os.Handler} を使うと、UI スレッドから配信されたメッセージを処理できます。 + +ただし、最善なのは{@link android.os.AsyncTask} クラスを拡張することであり、これにより UI を操作する必要のあるワーカー スレッドのタスクの実行を簡素化できます。 +

+ + +

AsyncTask を使用する

+ +

{@link android.os.AsyncTask} では、ユーザー インターフェースに非同期の処理を実行できます。 +スレッドやハンドラを自身で処理する必要なく、ワーカー スレッドの操作をブロックし、結果を UI スレッドに発行します。 +

+ +

これを使用するには、{@link android.os.AsyncTask} をサブクラス化し、バックグラウンド スレッドのプール内で実行する {@link +android.os.AsyncTask#doInBackground doInBackground()} コールバック メソッドを実装する必要があります。 +UI を更新するには、{@link +android.os.AsyncTask#onPostExecute onPostExecute()} を実装します。これは {@link +android.os.AsyncTask#doInBackground doInBackground()} からの結果を配信し、UI スレッド内で実行されるため、UI を安全に更新できます。その後、UI スレッドから {@link android.os.AsyncTask#execute execute()} を呼び出してタスクを実行できます。 + +

+ +

たとえば、次のように {@link android.os.AsyncTask} を使って前出の例を実装できます。 +

+ +
+public void onClick(View v) {
+    new DownloadImageTask().execute("http://example.com/image.png");
+}
+
+private class DownloadImageTask extends AsyncTask<String, Void, Bitmap> {
+    /** The system calls this to perform work in a worker thread and
+      * delivers it the parameters given to AsyncTask.execute() */
+    protected Bitmap doInBackground(String... urls) {
+        return loadImageFromNetwork(urls[0]);
+    }
+    
+    /** The system calls this to perform work in the UI thread and delivers
+      * the result from doInBackground() */
+    protected void onPostExecute(Bitmap result) {
+        mImageView.setImageBitmap(result);
+    }
+}
+
+ +

ワーカー スレッドで処理される作業と、UI スレッドで処理される作業が分けられたため、UI は安全に、コードはシンプルになりました。 +

+ +

このクラスの使用方法をより深く理解するには {@link android.os.AsyncTask} に目を通す必要がありますが、ここに、その仕組みについて簡単に挙げておきます。 +

+ + + +

警告: ワーカー スレッドの使用時に発生する可能性のあるもう 1 つの問題として、実行時の設定が変更された(ユーザーが画面の向きを変えた場合など)ことによってアクティビティが予期せず再起動され、ワーカー スレッドが破棄されてしまうことがあります。 + +このような再起動の間タスクを維持する方法、アクティビティが破棄されたときの正しいタスクのキャンセル方法については、Shelves のサンプル アプリケーションのソース コードをご覧ください。 + +

+ + +

スレッド セーフのメソッド

+ +

状況によっては、実装したメソッドが複数のスレッドから呼び出されることがあり、その場合はメソッドがスレッドセーフになるよう作成する必要があります。 +

+ +

主に、バインドされたサービスのメソッドなど、リモートで呼び出されるメソッドなどがこれに該当します。—{@link android.os.IBinder} に実装されたメソッドへの呼び出しが、{@link android.os.IBinder IBinder} を実行しているプロセスと同じプロセスで発生した場合、メソッドは呼び出し側のスレッドで実行されます。ただし、呼び出しが別のプロセスで起こった場合は、メソッドはシステムが {@link android.os.IBinder +IBinder} と同じプロセスに保持するスレッドのプールから選ばれたスレッドで実行されます(プロセスの UI スレッドでは実行されません)。 + + + +たとえば、サービスの {@link android.app.Service#onBind onBind()} メソッドがサービスのプロセスの UI スレッドから呼び出されるのに対して、{@link android.app.Service#onBind +onBind()} が返すオブジェクトで実装されたメソッド(RPC メソッドを実装するサブクラスなど)は、プール内のスレッドから呼び出されます。 + + +サービスは複数のクライアントを持てるため、複数のプール スレッドが同じ {@link android.os.IBinder IBinder} メソッドを同時に動かすことができます。このため、{@link android.os.IBinder +IBinder} メソッドはスレッドセーフになるよう実装する必要があります。 +

+ +

同様に、コンテンツ プロバイダは他のプロセスから送られたデータ要求を受け取ることができます。{@link android.content.ContentResolver} クラスと {@link android.content.ContentProvider} クラスによってプロセス間通信がどのように管理されているかが見えなくなりますが、それらの要求に応答する {@link +android.content.ContentProvider} メソッド({@link +android.content.ContentProvider#query query()}、 {@link android.content.ContentProvider#insert +insert()}、{@link android.content.ContentProvider#delete delete()}、{@link +android.content.ContentProvider#update update()}、{@link android.content.ContentProvider#getType +getType()})は、プロセスの UI スレッドではなく、コンテンツ プロバイダのプロセスにあるスレッドのプールから呼び出されます。 + + +——これらのメソッドは同時に複数のスレッドから呼び出される可能性があるため、先ほどと同様にスレッドセーフになるよう実装する必要があります。 +

+ + +

プロセス間通信(IPC)

+ +

Android では、リモート プロシージャ コール(RPC)を使ったプロセス間通信のメカニズムを備えており、メソッドはアクティビティや他のアプリケーション コンポーネントから呼び出された後に、リモート(別のプロセス)で実行され、結果を呼び出し側に返します。 + + +これにより、メソッドの呼び出しとそのデータをオペレーティング システムが理解できるレベルまで分解し、ローカル プロセスとアドレス空間からリモート プロセスとアドレス空間にそれを送信して、そこで呼び出しが再度組み立てて、再現します。 + +その後、戻り値が逆方向に伝達されます。 +Android ではこれらの IPC トランザクションを実行するためのすべてのコードが用意されているため、開発者は RPC のプログラミング インターフェースの定義と実装に集中できます。 +

+ +

IPC を実行するには、アプリケーションが {@link +android.content.Context#bindService bindService()} を使ってサービスにバインドされている必要があります。詳細については、デベロッパー ガイドの「サービス」をご覧ください。

+ + + diff --git a/docs/html-intl/intl/ja/guide/components/recents.jd b/docs/html-intl/intl/ja/guide/components/recents.jd new file mode 100644 index 0000000000000000000000000000000000000000..81626e1f2925adb3ce0c6c8a436da4cf5203a200 --- /dev/null +++ b/docs/html-intl/intl/ja/guide/components/recents.jd @@ -0,0 +1,256 @@ +page.title=オーバービュー画面 +page.tags="recents","overview" + +@jd:body + + + +

オーバービュー画面(別名、最近使った画面、最近使ったタスクリスト、最近使ったアプリ)は、最近アクセスしたアクティビティタスクの一覧を示すシステムレベルの UI です。 + +ユーザーはリスト内をナビゲートして再開するタスクを選択したり、スワイプしてタスクをリストから削除したりできます。 + +Android 5.0(API レベル 21)のリリースでは、異なるドキュメントを持つ同一アクティビティ内の複数のインスタンスが、1 つのタスクとしてオーバービュー画面に表示される場合があります。 +たとえば、Google ドライブには複数の Google ドキュメントごとのタスクが表示される場合があります。 +オーバービュー画面には、各ドキュメントが 1 つのタスクとして表示されます。 +

+ + +

図 1. オーバービュー画面に表示されている 3 つの Google ドライブ ドキュメントが、それぞれ別のタスクを表しています。 +

+ +

通常、オーバービュー画面にタスクとアクティビティを提示する方法はシステムが定義できるよう許可し、この動作を変更する必要はありません。 +ただし、アクティビティをいつ、どのようにオーバービュー画面に表示するかをアプリで決定することもできます。 +{@link android.app.ActivityManager.AppTask} クラスを使ってタスクを管理でき、{@link android.content.Intent} クラスのアクティビティ フラグを使うとアプリをオーバービュー画面に追加、削除するタイミングを指定できます。 + + +また、 +<activity> 属性を使ってマニフェストで動作を設定することもできます。

+ +

オーバービュー画面にタスクを追加する

+ +

{@link android.content.Intent} クラスのフラグを使用してタスクを追加すると、ドキュメントをいつ、どのようにオーバービュー画面で開いたり、再度開いたりできるかを細かく制御できます。 +<activity> 属性を使用すると、常に新しいタスクでドキュメントを開くか、ドキュメントの既存のタスクを再利用するかを選択できます。 + + +

+ +

インテント フラグを使用してタスクを追加する

+ +

アクティビティの新しいドキュメントを作成するときは、{@link android.app.ActivityManager.AppTask} クラスの {@link android.app.ActivityManager.AppTask#startActivity(android.content.Context, android.content.Intent, android.os.Bundle) startActivity()} メソッドを呼び出し、アクティビティを起動するインテントを渡します。 + + +論理的な改行を挿入して、システムがアクティビティをオーバービュー画面の新しいタスクとして扱えるようにするには、{@link android.content.Intent#FLAG_ACTIVITY_NEW_DOCUMENT} フラグを、アクティビティを起動する {@link android.content.Intent} の {@link android.content.Intent#addFlags(int) addFlags()} メソッドに渡します。 + + +

+ +

注: {@link android.content.Intent#FLAG_ACTIVITY_NEW_DOCUMENT} フラグは、Android 5.1(API レベル 21)で廃止された {@link android.content.Intent#FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET} を置き換えるものです。 + +

+ +

新しいドキュメントの作成時に {@link android.content.Intent#FLAG_ACTIVITY_MULTIPLE_TASK} フラグを設定すると、システムは常にターゲット アクティビティの新しいタスクをルートとして作成するようになります。この設定によって、同じドキュメントを複数のタスクで開くことが可能になります。 + +これをメインのアクティビティが行う方法を、次のコードで示し舞うs。 +

+ +

+DocumentCentricActivity.java

+
+public void createNewDocument(View view) {
+      final Intent newDocumentIntent = newDocumentIntent();
+      if (useMultipleTasks) {
+          newDocumentIntent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
+      }
+      startActivity(newDocumentIntent);
+  }
+
+  private Intent newDocumentIntent() {
+      boolean useMultipleTasks = mCheckbox.isChecked();
+      final Intent newDocumentIntent = new Intent(this, NewDocumentActivity.class);
+      newDocumentIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
+      newDocumentIntent.putExtra(KEY_EXTRA_NEW_DOCUMENT_COUNTER, incrementAndGet());
+      return newDocumentIntent;
+  }
+
+  private static int incrementAndGet() {
+      Log.d(TAG, "incrementAndGet(): " + mDocumentCounter);
+      return mDocumentCounter++;
+  }
+}
+
+ +

注: {@code FLAG_ACTIVITY_NEW_DOCUMENT} フラグを使って起動されたアクティビティには、マニフェスト ファイルで {@code android:launchMode="standard"} 属性の値(デフォルト)が設定されている必要があります。 + +

+ +

メイン アクティビティが新しいアクティビティを起動するとき、システムはアクティビティのインテント コンポーネント名とインテント データに一致するインテントを持つ既存のタスクを検索します。 +タスクが見つからない場合や、インテントに {@link android.content.Intent#FLAG_ACTIVITY_MULTIPLE_TASK} フラグに含まれている場合は、新しいタスクがアクティビティのルートとして作成されます。 + +タスク見つかった場合は、そのタスクをフロントに移動して、新しいインテントを {@link android.app.Activity#onNewIntent onNewIntent()} に渡します。新しいアクティビティがインテントを受け取り、次の例のようにオーバービュー画面に新しいドキュメントを作成します。 + + +

+ +

+NewDocumentActivity.java

+
+@Override
+protected void onCreate(Bundle savedInstanceState) {
+    super.onCreate(savedInstanceState);
+    setContentView(R.layout.activity_new_document);
+    mDocumentCount = getIntent()
+            .getIntExtra(DocumentCentricActivity.KEY_EXTRA_NEW_DOCUMENT_COUNTER, 0);
+    mDocumentCounterTextView = (TextView) findViewById(
+            R.id.hello_new_document_text_view);
+    setDocumentCounterText(R.string.hello_new_document_counter);
+}
+
+@Override
+protected void onNewIntent(Intent intent) {
+    super.onNewIntent(intent);
+    /* If FLAG_ACTIVITY_MULTIPLE_TASK has not been used, this activity
+    is reused to create a new document.
+     */
+    setDocumentCounterText(R.string.reusing_document_counter);
+}
+
+ + +

アクティビティの属性を使用してタスクを追加する

+ +

アクティビティのマニフェストで、<activity> の属性の{@code android:documentLaunchMode} を使ってアクティビティを常に新しいタスクで起動するよう指定することもできます。 + + +この属性には 4 つの値があり、ユーザーがアプリケーションでドキュメントを開くときに次のような効果を生みます。 +

+ +
+
「{@code intoExisting}」
+
アクティビティがそのドキュメントの既存のタスクを再利用します。これは、インテント フラグを使用してタスクを追加するで説明したように、{@link android.content.Intent#FLAG_ACTIVITY_MULTIPLE_TASK} フラグを設定せずに、{@link android.content.Intent#FLAG_ACTIVITY_NEW_DOCUMENT} フラグを設定した場合と同じです。 + + +
+ +
「{@code always}」
+
ドキュメントが既に開いている場合でも、アクティビティがドキュメントの新しいタスクを作成します。これは、{@link android.content.Intent#FLAG_ACTIVITY_NEW_DOCUMENT} フラグと {@link android.content.Intent#FLAG_ACTIVITY_MULTIPLE_TASK} フラグの両方を設定した場合と同じです。 + +
+ +
「{@code none”}」
+
アクティビティはドキュメントの新しいタスクを作成しません。オーバービュー画面はデフォルトでアクティビティがタスクを作成したかのように処理し、アプリの 1 つのタスクを表示して、ユーザーが最後に呼び出したアクティビティから再開します。 + +
+ +
「{@code never}」
+
アクティビティはドキュメントの新しいタスクを作成しません。この値を設定すると、{@link android.content.Intent#FLAG_ACTIVITY_NEW_DOCUMENT} フラグと {@link android.content.Intent#FLAG_ACTIVITY_MULTIPLE_TASK} フラグのいずれかが設定されている場合にその動作をオーバーライドし、オーバービュー画面にはアプリの 1 つのタスクが表示され、ユーザーが最後に呼び出したアクティビティから再開します。 + + + +
+
+ +

注: {@code none} と {@code never} の値以外の場合、アクティビティを {@code launchMode="standard"} を使って定義する必要があります。 +属性が指定されていない場合は、{@code documentLaunchMode="none"} が使用されます。 +

+ +

タスクを削除する

+ +

デフォルトで、アクティビティの完了時にドキュメントのタスクはオーバービュー画面から自動的に削除されます。 +この動作は、{@link android.app.ActivityManager.AppTask} クラスで {@link android.content.Intent} フラグを使うか、 +<activity> 属性を使ってオーバーライドできます。 +

+ +

<activity> 属性の {@code android:excludeFromRecents} を {@code true} に設定すると、タスクを常にオーバービュー画面から完全に除外することができます。 + + +

+ +

アプリがオーバービュー画面に表示できるタスクの最大数を設定するには、<activity> 属性の {@code android:maxRecents} に整数値を設定します。 + + +デフォルトでは 16 に設定されています。タスクの最大数に達すると、最も古いタスクがオーバービュー画面から削除されます。 +{@code android:maxRecents} の最大値は 50(低メモリの端末では 25)で、1 未満の値は無効です。 +

+ +

AppTask クラスを使用してタスクを削除する

+ +

オーバービュー画面に新しいタスクを作成するアクティビティで {@link android.app.ActivityManager.AppTask#finishAndRemoveTask() finishAndRemoveTask()} を呼び出すと、タスクを削除して関連アクティビティのすべてを終了させるタイミングを指定できます。 + +

+ +

+NewDocumentActivity.java

+
+public void onRemoveFromRecents(View view) {
+    // The document is no longer needed; remove its task.
+    finishAndRemoveTask();
+}
+
+ +

注: {@link android.app.ActivityManager.AppTask#finishAndRemoveTask() finishAndRemoveTask()} メソッドを使用すると、次のセクションで説明する {@link android.content.Intent#FLAG_ACTIVITY_RETAIN_IN_RECENTS} タグの使用がオーバーライドされます。 + + +

+ +

完了したタスクを保持する

+ +

アクティビティの終了後もオーバービュー画面にタスクを保持する場合は、{@link android.content.Intent#FLAG_ACTIVITY_RETAIN_IN_RECENTS} フラグを、アクティビティを起動するインテントの {@link android.content.Intent#addFlags(int) addFlags()} メソッドに渡します。 + +

+ +

+DocumentCentricActivity.java

+
+private Intent newDocumentIntent() {
+    final Intent newDocumentIntent = new Intent(this, NewDocumentActivity.class);
+    newDocumentIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT |
+      android.content.Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS);
+    newDocumentIntent.putExtra(KEY_EXTRA_NEW_DOCUMENT_COUNTER, incrementAndGet());
+    return newDocumentIntent;
+}
+
+ +

<activity> 属性の {@code android:autoRemoveFromRecents} を {@code false} に設定することでも同じ効果を得られます。 + + +ドキュメントのアクティビティのデフォルト値は {@code true}、通常のアクティビティでは {@code false} になります。 +この属性を使用すると、前のセクションで説明した {@link android.content.Intent#FLAG_ACTIVITY_RETAIN_IN_RECENTS} フラグがオーバーライドされます。 +

+ + + + + + + diff --git a/docs/html-intl/intl/ja/guide/components/services.jd b/docs/html-intl/intl/ja/guide/components/services.jd new file mode 100644 index 0000000000000000000000000000000000000000..fa946dbc94b2a6fa2f47f95a46320698f91f69c3 --- /dev/null +++ b/docs/html-intl/intl/ja/guide/components/services.jd @@ -0,0 +1,813 @@ +page.title=サービス +@jd:body + + + + +

{@link android.app.Service} は、バックグラウンドで長時間動作して作業を行い、ユーザー インターフェースを表示しないアプリケーション コンポーネントです。 +別のアプリケーション コンポーネントがサービスを開始し、ユーザーが他のアプリケーションに切り替えた場合でも、サービスはバックグラウンドで実行し続けることができます。 + +さらに、コンポーネントをサービスにバインドして操作したり、プロセス間通信(IPC)を実行したりすることも可能です。 +たとえば、サービスはネットワーク トランザクションの処理、音楽の再生、ファイルの I/O の実行、コンテンツ プロバイダとのやり取りなどのすべてをバックグラウンドで行うことができます。 + +

+ +

サービスには、基本的に次の 2 つの形式があります。

+ +
+
開始されたサービス
+
アプリケーション コンポーネント(アクティビティなど)が {@link android.content.Context#startService startService()} を呼び出して開始したときに、サービスが「開始された」状態になります。 +一旦開始されると、たとえ開始したコンポーネントが破棄されても、サービスは無期限に実行できます。 +通常、開始されたサービスは 1 つの操作を実行するだけで、呼び出し元に結果を返すことはありません。たとえば、サービスはネットワーク上でファイルのダウンロードやアップロードを行います。 + +操作が完了すると、サービスは自身で停止する必要があります。 +
+
バインドされたサービス
+
アプリケーション コンポーネントが {@link +android.content.Context#bindService bindService()} を呼び出してサービスにバインドすると、サービスは「バインドされた」状態になります。バインドされたサービスは、コンポーネントがサービスを操作したり、要求を送信したり、結果を取得したり、さらにはプロセス間通信(IPC)でもそれを行えるクライアントサーバー型インターフェースを提供します。 + +バインドされたサービスは、他のアプリケーション コンポーネントがそれにバインドしている限り実行し続けます。 +サービスには同時に複数のコンポーネントがバインドできますが、すべてがアンバインドされると、サービスは破棄されます。 +
+
+ +

このドキュメントではこの 2 種類のサービスそれぞれの概要について説明しますが、サービスは開始された状態(無期限に実行)、バインドされた状態のどちらの方法でも動作できます。これは、2 つのコールバック メソッドを実装するかどうかの問題で、{@link +android.app.Service#onStartCommand onStartCommand()} ではコンポーネントにサービスの開始を許可し、{@link +android.app.Service#onBind onBind()} ではバインドを許可することになります。 + +

+ +

アプリケーションが開始された、バインドされた、またはその両方かであるかどうかにかかわらず、どんなコンポーネントでもアクティビティを使用できるのと同じように、{@link android.content.Intent} を使って開始することで、どんなアプリケーション コンポーネントでもサービスを使用できます。 + +—ただし、マニフェスト ファイルでサービスを非公開として宣言して、他のアプリケーションからのアクセスをブロックすることもできます。 +詳細については、マニフェストでサービスを宣言するで説明します。 + +

+ +

警告: サービスは、そのホスト プロセスのメインスレッドで実行します。サービスが自身のスレッドを作成することはなく、別のプロセスで実行されることもありません(別の方法を指定しない限り)。 +— +つまり、サービスが CPU を集中的に使ったり、ブロック操作を行ったりするような場合(MP3 の再生やネットワーク作業)は、サービス内に新しいスレッドを作成してその作業を行う必要があります。 + +別のスレッドを使うことで、「アプリケーションが応答していません (ANR)」のエラーが発生するリスクを軽減でき、アプリケーションのメインスレッドをアクティビティのユーザー操作専用にすることができます。 + +

+ + +

基本

+ + + +

サービスを作成するには、{@link android.app.Service} のサブクラス(またはその既存のサブクラス)を作成する必要があります。 +実装時には、サービスのライフサイクルの重要側面を扱うコールバック メソッドをオーバーライドして、必要に応じてサービスにバインドするためのメカニズムをコンポーネントに提供する必要があります。 + +オーバーライドする必要のある、最も重要なコールバック メソッドは次の 2 つです。

+ +
+
{@link android.app.Service#onStartCommand onStartCommand()}
+
アクティビティなどの他のコンポーネントが、{@link android.content.Context#startService +startService()} を呼び出してサービスの開始を要求したときに、システムがこのメソッドを呼び出します。 +このメソッドが実行されるとサービスは開始され、バックグラウンドで無期限に実行します。 +これを実装すると、作業完了時に {@link android.app.Service#stopSelf stopSelf()} か {@link +android.content.Context#stopService stopService()} を呼び出して自身でサービスを停止する必要があります +(バインドのみを提供する場合は、このメソッドを実装する必要はありません)。 +
+
{@link android.app.Service#onBind onBind()}
+
{@link android.content.Context#bindService +bindService()} を呼び出して他のコンポーネントをサービスにバインドさせるとき(RPC 実行時など)に、システムがこのメソッドを呼び出します。 +このメソッドの実装時には、{@link android.os.IBinder} を返してクライアントがサービスとの通信に使うインターフェースを提供する必要があります。 +このメソッドの実装は常に必要ですが、バインドを許可しない場合は、null を返す必要があります。 +
+
{@link android.app.Service#onCreate()}
+
サービスが始めて作成されたときに、1 回限りのセットアップ処理を実行するためにシステムがこのメソッドを呼び出します({@link android.app.Service#onStartCommand onStartCommand()} か {@link android.app.Service#onBind onBind()} のいずれかを呼び出す前)。 + +サービスが既に実行中の場合、このメソッドは呼び出されません。 +
+
{@link android.app.Service#onDestroy()}
+
サービスがもう使用されておらず破棄されたときに、システムがこのメソッドを呼び出します。これは、スレッドや登録されたリスナ、レシーバーなどをクリーンアップするためにサービスに実装する必要があります。 + +これが、サービスが受け取る最後の呼び出しになります。
+
+ +

コンポーネントが {@link +android.content.Context#startService startService()} を呼び出してサービスを開始すると(結果的に {@link +android.app.Service#onStartCommand onStartCommand()} が呼び出される)、{@link android.app.Service#stopSelf()} を使ってサービス自身が停止するか、他のコンポーネントが {@link android.content.Context#stopService stopService()} を呼び出して停止するまで、サービスは実行し続けます。 + +

+ +

コンポーネントが {@link android.content.Context#bindService bindService()} を呼び出してサービスを作成した(そして {@link +android.app.Service#onStartCommand onStartCommand()} が呼び出されていない)場合、サービスはコンポーネントにバインドされている間のみ実行します。 + +すべてのクライアントからアンバインドされると、サービスはシステムによって破棄されます。 +

+ +

Android システムは メモリが少なくなって、ユーザーが使用しているアクティビティ用のシステムリソースを回復させる必要が生じた場合のみ、サービスを強制的に停止させます。 +サービスがユーザーが使用しているアクティビティにバインドされている場合は、それが強制終了される可能性は低く、フォアグラウンドで実行(後で説明)するように宣言されている場合は、強制終了されることはほとんどありません。一方で、サービスが開始されてから長時間実行している場合は、システムはバックグラウンド タスクのリストにおけるその位置付けを徐々に低くし、そのサービスが強制終了される確率が高くなります。開始されたサービスを作成する際は、システムによる再起動を円滑に処理するようデザインする必要があります。 + + + +— +システムがサービスを強制終了すると、リソースが回復次第そのサービスが再起動します(後述の {@link +android.app.Service#onStartCommand onStartCommand()} から返される値にもよります)。 +システムがサービスを破棄するタイミングについては、「Processes and Threading」のドキュメントをご覧ください。 + +

+ +

次のセクションでは、それぞれのタイプのサービスの作成方法と、他のアプリケーション コンポーネントからの使用方法について説明します。 +

+ + + +

マニフェストでサービスを宣言する

+ +

アクティビティ(や他のコンポーネント)と同様に、すべてのサービスをアプリケーションのマニフェスト ファイルで宣言する必要があります。 +

+ +

サービスを宣言するには、{@code <service>} 要素を {@code <application>} の子要素として追加します。 + +次に例を示します。

+ +
+<manifest ... >
+  ...
+  <application ... >
+      <service android:name=".ExampleService" />
+      ...
+  </application>
+</manifest>
+
+ +

マニフェストでのサービスの宣言に関する詳細については、{@code <service>} 要素のリファレンスをご覧ください。 +

+ +

{@code <service>} に含めることで、サービスやサービスを実行するプロセスの開始に必要なパーミッションなどのプロパティを定義できる他の属性がいくつかあります。 + +{@code android:name} 属性は唯一の必須属性で、サービスのクラス名を指定します。 +—アプリケーションを発行したら、この名前は変更できません。変更すると、サービスを開始したりバインドしたりする明示的インテントの依存関係によってコードを破損する可能性があります(ブログの投稿「Things That Cannot Change」をご覧ください)。 + + + + +

アプリの安全性を保つため、{@link android.app.Service}を開始したりバインドしたりするときは、常に明示的インテントを使用し、サービスでインテント フィルタを宣言しないようにしてください。 +どのサービスを開始するかについて、ある程度のあいまい静を残しておく必要がある場合は、サービスにインテント フィルタを定義でき、{@link +android.content.Intent} からコンポーネント名を除外して、その後ターゲットのサービスのあいまい性を十分に解消する {@link +android.content.Intent#setPackage setPackage()} でインテントのパッケージを設定する必要があります。 + + +

+ +

さらに、{@code android:exported} 属性を含めて {@code "false"} に設定すると、サービスを自身のアプリでしか利用できないようにできます。 + +これにより、他のアプリによるサービスの開始を効果的に回避でき、たとえ明示的インテントを使用したとしても開始できなくなります。 +

+ + + + +

開始されたサービスを作成する

+ +

開始されたサービスは、他のコンポーネントが {@link +android.content.Context#startService startService()} を呼び出すことで結果的にサービスの {@link android.app.Service#onStartCommand onStartCommand()} メソッドを呼び出して開始されたサービスです。 +

+ +

サービスが開始されると、サービスはそれを開始したコンポーネントから独立したライフサイクルを持ち、たとえ開始したコンポーネントが破棄されても、サービスは無期限に実行できます。 + +そのため、サービスはジョブが完了したら {@link android.app.Service#stopSelf stopSelf()} を呼び出して自身で停止する必要があり、他のコンポーネントが {@link android.content.Context#stopService stopService()} を呼び出して停止することもできます。 + +

+ +

アクティビティなどのアプリケーション コンポーネントは、{@link +android.content.Context#startService startService()} を呼び出して、サービスを指定し、サービスが使用するデータを含めた {@link android.content.Intent} を渡してサービスを開始できます。 +サービスはこの {@link android.content.Intent} を、{@link android.app.Service#onStartCommand +onStartCommand()} メソッドで受け取ります。 +

+ +

たとえば、オンライン データベースにデータを保存する必要のあるアクティビティがあるとします。アクティビティでサービスを開始し、{@link +android.content.Context#startService startService()} にインテントを渡して、保存するデータをサービスに配信します。 +サービスはインテントを {@link +android.app.Service#onStartCommand onStartCommand()} で受け取り、インターネットに接続してデータベースのトランザクションを実行します。 +トランザクションが完了したら、サービスは自身で停止し、破棄されます。 +

+ +

警告: デフォルトでは、アプリケーションで宣言されたサービスは、アプリケーションと同じプロセスで、そのアプリケーションのメインスレッドで実行します。 +そのため、ユーザーが同じアプリケーションのアクティビティを操作している間に、サービスが集中的な処理やブロック操作を実行すると、アクティビティのパフォーマンスが低下します。 + +アプリケーションのパフォーマンスへの影響を回避するには、サービス内で新しいスレッドを開始する必要があります。 +

+ +

従来どおり、開始されたサービスを作成するために拡張できるクラスが 2 つあります。

+
+
{@link android.app.Service}
+
これは、すべてのサービスの基本クラスです。サービスはデフォルトでアプリケーションのメインスレッドを使用し、アプリケーションが実行しているアクティビティのパフォーマンスを低下させることがあるため、このクラスを拡張するときは、サービスのすべての作業を行うための新しいスレッドを作成することが重要です。 + + +
+
{@link android.app.IntentService}
+
すべての開始要求を一件ずつ処理するワーカー スレッドを使用した {@link android.app.Service} のサブクラスです。 +サービスで同時に複数の要求を処理する必要がない場合は、これが最適です。 +{@link +android.app.IntentService#onHandleIntent onHandleIntent()} を実装するだけで、それぞれの開始要求のインテントを受け取り、バックグラウンドでの処理が可能になります。 +
+
+ +

次のセクションでは、これらいずれかのクラスを使用してサービスを実装する方法について説明します。 +

+ + +

IntentService クラスを拡張する

+ +

開始されたサービスで同時に複数の要求を処理する必要があることはほとんどないため(実際には危険なマルチスレッド シナリオになります)、{@link android.app.IntentService} クラスを使用してサービスを実装するのが最適だと考えられます。 + +

+ +

{@link android.app.IntentService} は、次の操作を行います。

+ + + +

上記すべての操作が行われることで、開発者が行う必要があるのは、クライアントから指示された内容を処理する {@link +android.app.IntentService#onHandleIntent onHandleIntent()} を実装するだけになります +(ただし、サービスの小さいコンストラクタを提供する必要はあります)。

+ +

{@link android.app.IntentService} の実装例を次に示します。

+ +
+public class HelloIntentService extends IntentService {
+
+  /**
+   * A constructor is required, and must call the super {@link android.app.IntentService#IntentService}
+   * constructor with a name for the worker thread.
+   */
+  public HelloIntentService() {
+      super("HelloIntentService");
+  }
+
+  /**
+   * The IntentService calls this method from the default worker thread with
+   * the intent that started the service. When this method returns, IntentService
+   * stops the service, as appropriate.
+   */
+  @Override
+  protected void onHandleIntent(Intent intent) {
+      // Normally we would do some work here, like download a file.
+      // For our sample, we just sleep for 5 seconds.
+      long endTime = System.currentTimeMillis() + 5*1000;
+      while (System.currentTimeMillis() < endTime) {
+          synchronized (this) {
+              try {
+                  wait(endTime - System.currentTimeMillis());
+              } catch (Exception e) {
+              }
+          }
+      }
+  }
+}
+
+ +

コンストラクタと {@link +android.app.IntentService#onHandleIntent onHandleIntent()} の実装、必要なのはこれだけです。

+ +

{@link +android.app.IntentService#onCreate onCreate()}、{@link +android.app.IntentService#onStartCommand onStartCommand()}、{@link +android.app.IntentService#onDestroy onDestroy()} などの他のコールバック メソッドもオーバーライドする場合は、{@link android.app.IntentService} がワーカー スレッドの生存状態を正しく処理できるよう、必ず super を実装するようにします。 +

+ +

たとえば、{@link android.app.IntentService#onStartCommand onStartCommand()} はデフォルト実装を返す必要があります(これによりインテントが {@link +android.app.IntentService#onHandleIntent onHandleIntent()} に配信されます)。 +

+ +
+@Override
+public int onStartCommand(Intent intent, int flags, int startId) {
+    Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show();
+    return super.onStartCommand(intent,flags,startId);
+}
+
+ +

{@link android.app.IntentService#onHandleIntent onHandleIntent()} 以外で、super クラスを呼び出す必要のないメソッドは、{@link android.app.IntentService#onBind +onBind()} です(ただし、サービスでバインドを許可している場合のみ実装が必要です)。 +

+ +

次のセクションでは、この種のサービスが、基本の {@link android.app.Service} クラスを拡張したときにどのように実装されるかを説明します。 +これにはさらに多くのコードがありますが、開始要求の同時処理が必要な場合には適しています。 +

+ + +

Service クラスを拡張する

+ +

前のセクションで説明したように、{@link android.app.IntentService} を使用すると開始されたサービスの実装が非常に簡単になります。 +ただし、サービスでマルチスレッドを実行する必要がある場合(開始要求をワークキュー経由で処理するのではなく)、{@link android.app.Service} クラスを拡張して、それぞれのインテントを処理できます。 + +

+ +

比較のために、{@link +android.app.IntentService} を使用した上記の例とまったく同じ処理を実行する {@link +android.app.Service} クラスの実装の例が次のコードです。つまり、それぞれの開始要求に対し、ワーカー スレッドを使用してジョブとプロセスを実行し、一度に 1 つの要求のみを処理します。 +

+ +
+public class HelloService extends Service {
+  private Looper mServiceLooper;
+  private ServiceHandler mServiceHandler;
+
+  // Handler that receives messages from the thread
+  private final class ServiceHandler extends Handler {
+      public ServiceHandler(Looper looper) {
+          super(looper);
+      }
+      @Override
+      public void handleMessage(Message msg) {
+          // Normally we would do some work here, like download a file.
+          // For our sample, we just sleep for 5 seconds.
+          long endTime = System.currentTimeMillis() + 5*1000;
+          while (System.currentTimeMillis() < endTime) {
+              synchronized (this) {
+                  try {
+                      wait(endTime - System.currentTimeMillis());
+                  } catch (Exception e) {
+                  }
+              }
+          }
+          // Stop the service using the startId, so that we don't stop
+          // the service in the middle of handling another job
+          stopSelf(msg.arg1);
+      }
+  }
+
+  @Override
+  public void onCreate() {
+    // Start up the thread running the service.  Note that we create a
+    // separate thread because the service normally runs in the process's
+    // main thread, which we don't want to block.  We also make it
+    // background priority so CPU-intensive work will not disrupt our UI.
+    HandlerThread thread = new HandlerThread("ServiceStartArguments",
+            Process.THREAD_PRIORITY_BACKGROUND);
+    thread.start();
+
+    // Get the HandlerThread's Looper and use it for our Handler
+    mServiceLooper = thread.getLooper();
+    mServiceHandler = new ServiceHandler(mServiceLooper);
+  }
+
+  @Override
+  public int onStartCommand(Intent intent, int flags, int startId) {
+      Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show();
+
+      // For each start request, send a message to start a job and deliver the
+      // start ID so we know which request we're stopping when we finish the job
+      Message msg = mServiceHandler.obtainMessage();
+      msg.arg1 = startId;
+      mServiceHandler.sendMessage(msg);
+
+      // If we get killed, after returning from here, restart
+      return START_STICKY;
+  }
+
+  @Override
+  public IBinder onBind(Intent intent) {
+      // We don't provide binding, so return null
+      return null;
+  }
+
+  @Override
+  public void onDestroy() {
+    Toast.makeText(this, "service done", Toast.LENGTH_SHORT).show();
+  }
+}
+
+ +

このように、{@link android.app.IntentService} を使う場合よりもかなり面倒です。

+ +

ただし、{@link android.app.Service#onStartCommand +onStartCommand()} への各呼び出しを自身で処理するため、同時に複数の要求を実行することも可能です。この例にはあてはまりませんが、自身の状況に合う場合は、要求ごとに新しいスレッドを作成し、すぐに実行することもできます(前の要求が完了するのを待つ必要がありません)。 + +

+ +

{@link android.app.Service#onStartCommand onStartCommand()} メソッドは整数を返す必要があることに注意してください。 +整数は、システムがサービスを強制終了した場合に、サービスをどのように続行させるかを記述する値です(前述したように、{@link +android.app.IntentService} のデフォルト実装が代わりにこれを処理しますが、自身で修正することもできます)。 +{@link android.app.Service#onStartCommand onStartCommand()} から返される値は、次のいずれかの定数である必要があります。 + +

+ +
+
{@link android.app.Service#START_NOT_STICKY}
+
{@link android.app.Service#onStartCommand +onStartCommand()} から戻った後でシステムがサービスを強制終了した場合は、配信が保留中のインテントがない限りシステムはサービスを再作成しません。 +これは、サービスが不必要で、アプリケーションが未完了のジョブを再開できる場合にサービスを実行してしまうのを回避できる最も安全な選択肢です。 +
+
{@link android.app.Service#START_STICKY}
+
{@link android.app.Service#onStartCommand +onStartCommand()} から戻った後でシステムがサービスを強制終了した場合は、サービスを再作成し、{@link +android.app.Service#onStartCommand onStartCommand()} を呼び出しますが、最後のインテントは再配信しません。代わりに、サービスを開始するインテントが保留になっている場合を除いて、システムは {@link android.app.Service#onStartCommand onStartCommand()} を null インテントを使って呼び出します。保留中のインテントがある場合、そのインテントは配信されます。 + + +これは、コマンドは実行しないが、無期限に実行し、ジョブを待機するメディア プレーヤー(や同様のサービス)に適しています。 +
+
{@link android.app.Service#START_REDELIVER_INTENT}
+
{@link android.app.Service#onStartCommand +onStartCommand()} から戻った後でシステムがサービスを強制終了した場合は、サービスを再作成し、サービスに最後に配信されたインテントで {@link +android.app.Service#onStartCommand onStartCommand()} を呼び出します。 +すべてのペンディング インテントが順に配信されます。これは、ファイルのダウンロードのような、活発にジョブを実行し、ただちに再開する必要のあるサービスに適しています。 +
+
+

これらの戻り値の詳細については、リンクされた各定数のリファレンス ドキュメントをご覧ください。 +

+ + + +

サービスを開始する

+ +

アクティビティや他のアプリケーション コンポーネントから {@link android.content.Intent}(開始するサービスを指定する)を {@link android.content.Context#startService startService()} に渡すことで、サービスを開始できます。 + +Android システムが、サービスの {@link +android.app.Service#onStartCommand onStartCommand()} メソッドを呼び出して、{@link +android.content.Intent} を渡します(絶対に、{@link android.app.Service#onStartCommand +onStartCommand()} を直接呼び出さないでください)。

+ +

アクティビティが前のセクションで例に挙げたサービス({@code +HelloSevice})を、{@link android.content.Context#startService +startService()} で明示的インテントを使って開始する例を次に示します。

+ +
+Intent intent = new Intent(this, HelloService.class);
+startService(intent);
+
+ +

{@link android.content.Context#startService startService()} メソッドがすぐに戻り、Android システムがサービスの {@link android.app.Service#onStartCommand +onStartCommand()} メソッドを呼び出します。 +サービスがまだ実行されていない場合、システムはまず {@link +android.app.Service#onCreate onCreate()} を呼び出してから、{@link android.app.Service#onStartCommand +onStartCommand()} を呼び出します。

+ +

サービスでバインドが提供されない場合は、{@link +android.content.Context#startService startService()} で配信されたインテントが、アプリケーション コンポーネントとサービス間の唯一の通信モードになります。 +ただし、サービスから結果を送り返す場合は、サービスを開始するクライアントがブロードキャスト用に {@link android.app.PendingIntent} を作成でき({@link android.app.PendingIntent#getBroadcast getBroadcast()} で)、それをサービスを開始する {@link android.content.Intent} のサービスに配信できます。 + + +その後、サービスはブロードキャストを使って結果を配信できます。 +

+ +

サービスを開始する要求が複数ある場合は、それに対応する複数の呼び出しがサービスの {@link android.app.Service#onStartCommand onStartCommand()} に対して発生することになります。 +ただし、サービスを停止するために必要な要求({@link android.app.Service#stopSelf stopSelf()} や {@link +android.content.Context#stopService stopService()} を使用)は、1 つのみです。 +

+ + +

サービスを停止する

+ +

開始されたサービスは、自身でライフサイクルを管理する必要があります。つまり、システムメモリを復元する必要がある場合を除き、システムがサービスを停止したり破棄したりすることはなく、サービスは {@link android.app.Service#onStartCommand onStartCommand()} から戻った後も実行し続けます。 + +そのため、サービスは {@link android.app.Service#stopSelf stopSelf()} を呼び出して自身で停止する必要があり、他のコンポーネントが {@link android.content.Context#stopService stopService()} を呼び出して停止することもできます。 + +

+ +

{@link android.app.Service#stopSelf stopSelf()} や {@link +android.content.Context#stopService stopService()} で停止が要求されたら、システムは可能な限りすぐにサービスを破棄します。 +

+ +

ただし、サービスが {@link +android.app.Service#onStartCommand onStartCommand()} への複数の要求を同時に処理している場合は、その後に新しい開始要求を受け取る可能性があることから、開始要求の処理後もサービスを停止しないでください(1 つ目の要求の最後に停止すると、2 つ目が停止されてしまいます)。 + +この問題を回避するには、{@link android.app.Service#stopSelf(int)} を使って、常に最新の開始要求に基づいてサービスの停止要求を行うようにできます。 + +具体的には、{@link +android.app.Service#stopSelf(int)} を呼び出すとき、停止要求に対応する開始要求の ID({@link android.app.Service#onStartCommand onStartCommand()} に配信された startId)を渡します。 + +その後、{@link +android.app.Service#stopSelf(int)} を呼び出す前にサービスが新しい開始要求を受け取ったとしても、結果的に ID が一致せずサービスが停止されなくなります。

+ +

警告: アプリケーションはサービスの処理が完了したら、システムリソースやバッテリ電力の節約のため、サービスを停止することが重要です。 +必要であれば、他のコンポーネントから {@link +android.content.Context#stopService stopService()} を呼び出してサービスを停止することもできます。 +サービスのバインドを有効にしている場合でも、{@link +android.app.Service#onStartCommand onStartCommand()} を受け取ったときには常に自身でサービスを停止する必要があります。 +

+ +

サービスのライフサイクルの詳細については、後半のセクションのサービスのライフサイクルを管理するをご覧ください。

+ + + +

バインドされたサービスを作成する

+ +

バインドされたサービスとは、アプリケーション コンポーネントが長時間の接続を作成するために {@link +android.content.Context#bindService bindService()} を呼び出してサービスにバインドできるようにするサービスです(通常はコンポーネントが {@link +android.content.Context#startService startService()} を呼び出すことではサービスは開始できません)。 +

+ +

バインドされたサービスは、アプリケーションのアクティビティや他のコンポーネントからのサービスとやり取りしたり、アプリケーションの一部の機能をプロセス間通信(IPC)を用いて他のアプリケーションが利用できるようにしたりする場合に作成します。 + +

+ +

バインドされたサービスを作成するには、{@link +android.app.Service#onBind onBind()} コールバック メソッドを実装して、サービスとの通信用のインターフェースを定義する {@link android.os.IBinder} を返す必要があります。 +その後、他のアプリケーション コンポーネントが {@link android.content.Context#bindService bindService()} を呼び出してインターフェースを取得し、サービスのメソッドの呼び出しを開始できます。 + +サービスは、バインドされているアプリケーション コンポーネントのためだけに存在するため、バインドされているコンポーネントがなくなると、サービスにシステムによって破棄されます(バインドされたサービスは、{@link android.app.Service#onStartCommand onStartCommand()} でサービスが開始されたときと同じ方法で停止する必要はありません)。 + + +

+ +

バインドされたサービスを作成するには、まずクライアントからサービスへの通信方法を指定するインターフェースを定義します。 +サービスとクライアント間のこのインターフェースは {@link android.os.IBinder} の実装である必要があり、サービスはこれを {@link android.app.Service#onBind +onBind()} コールバック メソッドから返す必要があります。 + +クライアントが {@link android.os.IBinder} を受け取ると、そのインターフェースを介してサービスとのやり取りを開始できます。 +

+ +

複数のクライアントが同時にサービスにバインドできます。クライアントとサービスのやり取りが終わったら、{@link android.content.Context#unbindService unbindService()} を呼び出してアンバインドします。 +サービスにバインドされているクライアントがなくなったら、システムがサービスを破棄します。 +

+ +

バインドされたサービスの実装方法はいくつかあり、その実装は開始されたサービスよりも複雑であるため、バインドされたサービスの詳細については、バインドされたサービス のドキュメントで別途説明しています。 + +

+ + + +

ユーザーに通知を送信する

+ +

一旦サービスが実行されると、トースト通知ステータスバー通知を使ってユーザーにイベントを通知できます。

+ +

トースト通知は、現在のウィンドウに表示されるメッセージで、少しの間表示され、すぐに消えます。一方、ステータスバー通知では、ステータスバーにメッセージの付いたアイコンが表示され、ユーザーはそれを選択して何らかの操作(アクティビティの開始など)を行うことができます。 + +

+ +

通常は、バックグラウンド ワークが完了して、ユーザーが実行できる操作がある場合(ファイルのダウンロードが完了した場合など)は、ステータスバーの通知が最適なテクニックです。 + +展開したビューでユーザーが通知を選択すると、通知がアクティビティ(ダウンロードしたファイルを表示するなど)を開始できるようになります。 +

+ +

詳細については、デベロッパー ガイドの「トースト通知」や「ステータスバーの通知」をご覧ください。 +

+ + + +

サービスをフォアグラウンドで実行する

+ +

フォアグラウンド サービスは、ユーザーがその存在を認識しているものであるとみなされるため、メモリ残量が少なくなった場合でも、システムによる強制終了の現候補にはなりません。 +フォアグラウンド サービスではステータスバーに通知を表示する必要があり、通知は「継続中」という見出しの下に表示されるため、サービスが停止するか、フォアグラウンドから除去しない限り通知を消すことはできないということになります。 + + +

+ +

たとえば、サービスから音楽を再生する音楽プレーヤーは、ユーザーがその操作を認識しているのは明らかであるため、フォアグラウンドで実行する必要があります。 + +ステータスバーの通知には現在再生中の曲名を表示でき、ユーザーが音楽プレーヤーを操作するためのアクティビティを起動できるようにできます。 +

+ +

サービスをフォアグラウンドで実行するよう要求するには、{@link +android.app.Service#startForeground startForeground()} を呼び出します。このメソッドは、通知を一意に識別する整数と、ステータスバーの {@link +android.app.Notification} の 2 つのパラメータを受け付けます。 +次に例を示します。

+ +
+Notification notification = new Notification(R.drawable.icon, getText(R.string.ticker_text),
+        System.currentTimeMillis());
+Intent notificationIntent = new Intent(this, ExampleActivity.class);
+PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);
+notification.setLatestEventInfo(this, getText(R.string.notification_title),
+        getText(R.string.notification_message), pendingIntent);
+startForeground(ONGOING_NOTIFICATION_ID, notification);
+
+ +

警告: {@link +android.app.Service#startForeground startForeground()} に渡す整数 ID には、0 は使用できません。

+ + +

サービスをフォアグラウンドから除去するには、{@link +android.app.Service#stopForeground stopForeground()} を呼び出します。このメソッドでは、同時にステータスバーの通知も除去するかどうかを示すブール値を受け付けます。 +このメソッドでは、サービスは停止されません。 +ただし、サービスがまだフォアグラウンドで実行中に停止した場合は、通知も除去されます。 +

+ +

通知の詳細については、「Creating Status Bar Notifications」をご覧ください。 +

+ + + +

サービスのライフサイクルを管理する

+ +

サービスのライフサイクルは、アクティビティのライフサイクルよりもはるかにシンプルです。ただし、サービスはバックグラウンドでサイレントに実行されるため、サービスがどのように作成され、破棄されるかについては、より一層の注意を払っておく必要があります。 + +

+ +

作成されてから破棄されるまでのサービスのライフサイクルには、2 つの経路があります。—— +

+ + + +

この 2 つの経路は、完全に分離しているわけではありません。つまり、{@link android.content.Context#startService startService()} で既に開始されたサービスにバインドすることも可能です。 +たとえば、{@link android.content.Context#startService +startService()} を呼び出してバックグラウンドの音楽サービスを開始し、{@link android.content.Intent} を使って再生する音楽を指定できます。 +その後、ユーザーがプレーヤーを操作したり、現在の曲に関する情報を入手したりする場合は、{@link +android.content.Context#bindService bindService()} を呼び出すことでアクティビティをサービスにバインドできます。 + +このような場合は、すべてのクライアントがアンバインドされるまで、{@link +android.content.Context#stopService stopService()} や {@link android.app.Service#stopSelf +stopSelf()} ではサービスは停止されません。

+ + +

ライフサイクル コールバックを実装する

+ +

アクティビティと同様に、サービスにもコールバック メソッドがあり、それを実装することでサービスの状態の変化を監視したり、適切なタイミングで処理を実行したりできます。 +次のスケルトン サービスは、それぞれのライフサイクル メソッドを表しています。 +

+ +
+public class ExampleService extends Service {
+    int mStartMode;       // indicates how to behave if the service is killed
+    IBinder mBinder;      // interface for clients that bind
+    boolean mAllowRebind; // indicates whether onRebind should be used
+
+    @Override
+    public void {@link android.app.Service#onCreate onCreate}() {
+        // The service is being created
+    }
+    @Override
+    public int {@link android.app.Service#onStartCommand onStartCommand}(Intent intent, int flags, int startId) {
+        // The service is starting, due to a call to {@link android.content.Context#startService startService()}
+        return mStartMode;
+    }
+    @Override
+    public IBinder {@link android.app.Service#onBind onBind}(Intent intent) {
+        // A client is binding to the service with {@link android.content.Context#bindService bindService()}
+        return mBinder;
+    }
+    @Override
+    public boolean {@link android.app.Service#onUnbind onUnbind}(Intent intent) {
+        // All clients have unbound with {@link android.content.Context#unbindService unbindService()}
+        return mAllowRebind;
+    }
+    @Override
+    public void {@link android.app.Service#onRebind onRebind}(Intent intent) {
+        // A client is binding to the service with {@link android.content.Context#bindService bindService()},
+        // after onUnbind() has already been called
+    }
+    @Override
+    public void {@link android.app.Service#onDestroy onDestroy}() {
+        // The service is no longer used and is being destroyed
+    }
+}
+
+ +

注: アクティビティのライフサイクル コールバック メソッドの場合とは異なり、これらのコールバック メソッドのスーパークラスの実装を呼び出す必要はありません。 +

+ + +

図 2. サービスのライフサイクル。左側の図は、{@link android.content.Context#startService +startService()} でサービスが作成されたときのライフサイクルを示し、右側の図は、{@link android.content.Context#bindService bindService()} でサービスが作成されたときのライフサイクルを示しています。 + +

+ +

これらのメソッドを実装すると、サービスのライフサイクル内の次の 2 つのネストされたループを監視できます。

+ + + +

注: 開始されたサービスは、{@link android.app.Service#stopSelf stopSelf()} か {@link +android.content.Context#stopService stopService()} への呼び出しで停止されますが、サービスには同様のコールバックはありません({@code onStop()} コールバックがありません)。 + +そのため、サービスがクライアントにバインドされていない限り、サービスが停止するとシステムがそれを破棄します。受け取るコールバックは {@link +android.app.Service#onDestroy onDestroy()} のみです。 +—

+ +

図 2 は、サービスの典型的なコールバック メソッドを表しています。この図では、{@link android.content.Context#startService startService()} で作成されたサービスと {@link android.content.Context#bindService bindService()} で作成されたサービスを分けていますが、開始方法にかかわらず、すべてのサービスがクライアントからバインドされる可能性があることに注意してください。つまり、当初は {@link android.app.Service#onStartCommand +onStartCommand()} で(クライアントが {@link android.content.Context#startService startService()} を呼び出した際)開始されたサービスも、{@link android.app.Service#onBind onBind()} への呼び出し(クライアントが {@link android.content.Context#bindService bindService()} を呼び出した際)を受け取ることができます。 + + + + + +

+ +

バインドを提供するサービスの作成に関する詳細は、「バインドされたサービス」のドキュメントをご覧ください。このドキュメントのバインドされたサービスのライフサイクルを管理するでは、{@link android.app.Service#onRebind onRebind()} コールバックの詳細についても説明しています。 + + +

+ + + diff --git a/docs/html-intl/intl/ja/guide/components/tasks-and-back-stack.jd b/docs/html-intl/intl/ja/guide/components/tasks-and-back-stack.jd new file mode 100644 index 0000000000000000000000000000000000000000..12f5911da22e94b98a235199ec76d902f887d58c --- /dev/null +++ b/docs/html-intl/intl/ja/guide/components/tasks-and-back-stack.jd @@ -0,0 +1,578 @@ +page.title=タスクとバックスタック +parent.title=アクティビティ +parent.link=activities.html +@jd:body + + + + +

通常、アプリケーションには複数のアクティビティが含まれています。それぞれのアクティビティは、ユーザーが実行して、他のアクティビティを開始するといった特定のアクションを中心に設計されています。 + +たとえば、メール アプリケーションに新規メッセージ一覧を表示する 1 つのアクティビティがあるとします。ユーザーがメッセージを選択すると、そのメッセージを表示するための新しいアクティビティが開きます。 +

+ +

アクティビティでは、端末上の他のアプリケーションに存在するアクティビティを開始することもできます。たとえば、アプリケーションがメールの送信を求める場合は、「送信」アクションを実行し、メールアドレスや本文などのデータを含めるインテントを定義できます。 + +その結果、この種のインテントの処理を宣言している別のアプリケーションのアクティビティが開きます。 +この場合、インテントはメールを送信することであるため、メール アプリケーションの「作成」アクティビティが開始されます(複数のアクティビティが同じインテントに対応している場合、システムはユーザーに選択を求めます)。 + +メールが送信されると、元のアクティビティが再開され、まるでメール アクティビティがそのアプリケーションの一部であるように見えます。 +アクティビティは異なるアプリケーションのものでも、Android は両方のアクティビティを同じタスク内に保つことによって、このシームレスな操作性を維持しています。 + +

+ +

タスクとは、ユーザーが特定の作業を行う時に情報のやり取りを行うアクティビティの集まりです。 +アクティビティは、各アクティビティが開かれた順にスタック(バックスタック)形式で配置されます。 +

+ + + +

ほとんどのタスクは、端末のホーム画面から開始されます。ユーザーがアプリケーション ランチャーでアイコン(またはホーム画面のショートカット)にタッチすると、アプリケーションのタスクがフォアグラウンドに移動します。 + +アプリケーションにタスクが存在しない(アプリケーションが最近使われていない)場合は、新しいタスクが作成され、そのアプリケーションの「メイン」アクティビティがスタック内のルート アクティビティとして開きます。 + +

+ +

現在のアクティビティが別のアクティビティを開始すると、新しいアクティビティがスタックの一番上にプッシュされ、アクティブになります。 +前のアクティビティはスタックに残りますが、停止されます。アクティビティが停止すると、システムはそのユーザー インターフェースの状態を維持します。 +ユーザーが [戻る] ボタンを押すと、現在のアクティビティがスタックの一番上から消え(アクティビティは破棄され)、前のアクティビティが再開します(UI は前の状態で復元されます)。 + + +スタック内のアクティビティが並べ替えられることはなく、スタック上にプッシュされるかスタックから消されるかのみです — 現在のアクティビティにより開始されるとスタック上にプッシュされ、ユーザーが [戻る] ボタンを押すと破棄されます。 + +このように、バックスタックは「後入れ先出し」オブジェクト構造となっています。 + +図 1 は、この動作を時系列に視覚化し、アクティビティとそれに伴うその時点でのバックスタックの状態を示したものです。 + +

+ + +

図 1.タスク内の新しいアクティビティがバックスタックにアイテムを追加するプロセスを示しています。 +ユーザーが [戻る] ボタンを押すと、現在のアクティビティは破棄され、前のアクティビティが再開します。 + +

+ + +

ユーザーが続けて [戻る] ボタンを押すと、スタック内のアクティビティは消えていき、前のアクティビティが表示されます。最終的に、ユーザーはホーム画面(またはタスクの開始時に実行されていたアクティビティ)に戻ります。 + + +すべてのアクティビティがスタックから削除されると、タスクはなくなります。

+ +
+

図 2. 2 つのタスク: タスク B がフォアグラウンドでユーザー操作を受け入れます。タスク A は再開されるまでバックグラウンドで待機します。 +

+
+
+

図 3. 1 つのアクティビティが複数回インスタンス化されます。

+
+ +

タスクは結束した構成単位で、ユーザーが新しいタスクを開始したり [ホーム] ボタンを使ってホーム画面に移動したりすると「バックグラウンド」に移動できます。 +バックグラウンドでは、タスク内のすべてのアクティビティが停止されますが、タスクのバックスタックは元の状態を保ちます — 図 2 に示すように、別のタスクが行われている間、タスクはフォーカスを失った状態になります。 + + +タスクはその後「フォアグラウンド」に戻り、ユーザーは操作の続きを行うことができます。 +たとえば、現在のタスク(タスク A)のスタックに 3 つのアクティビティがあるとします。現在のアクティビティの下に 2 つのアクティビティがある状態です。 +ユーザーが [ホーム] ボタンを押し、アプリケーション ランチャーから新しいアプリケーションを起動します。 + +ホーム画面が表示されると、タスク A はバックグラウンドに移動します。 +新しいアプリケーションが起動すると、システムはそのアプリケーションのタスク(タスク B)を開始します。タスク B には独自のアクティビティ スタックがあります。 +アプリケーションの操作が終了すると、ユーザーはホームに戻り、タスク A を開始した元のアプリケーションを選択します。ここでタスク A はフォアグラウンドに移動します — 3 つのアクティビティはすべて元のままで、スタックの一番上にあるアクティビティが再開します。 + + + +この時点で、ユーザーはホームに移動してタスク B を開始したアプリケーションを選択して(またはオーバービュー画面でアプリのタスクを選択して)タスク B に切り替えることもできます。これは、Android のマルチタスク操作の一例です。 + + + +

+ +

注: バックグラウンドには複数のタスクを一度に置くことができます。しかし、ユーザーが多数のバックグラウンド タスクを同時に実行すると、システムがメモリを回復するためにバックグラウンド アクティビティを破棄する場合があります。その結果、アクティビティの状態は失われます。アクティビティの状態セクションをご覧ください。 + + +

+ +

バックスタック内のアクティビティが並べ替えられることはないため、アプリケーションが複数のアクティビティからの特定のアクティビティ開始を許可すると、(アクティビティの前のインスタンスを一番上に移動させるのではなく)そのアクティビティの新しいインスタンスが作成されてスタック上にプッシュされます。 + + +これによって、図 3 に示すように、アプリケーションの 1 つのアクティビティが(別のタスクからも)複数回インスタンス化される場合があります。 +この場合は、ユーザーが [戻る] ボタンを使って移動すると、アクティビティの各インスタンスが(UI の状態はそれぞれそのままで)開いた順に表示されます。 + + +しかし、1 つのアクティビティを何度もインスタンス化したくない場合は、この動作を修正できます。 +その方法については、後述のセクションタスクを管理するで説明します。

+ + +

以下は、アクティビティとタスクのデフォルトの動作をまとめたものです。

+ + + + +
+

ナビゲーション デザイン

+

Android 上でのアプリ ナビゲーションの仕組みの詳細については、Android Design の「Navigation」ガイドをご覧ください。

+
+ + +

アクティビティの状態を保存する

+ +

上述のように、デフォルト動作では、システムはアクティビティが停止するとその状態を保持します。 +この方法では、ユーザーが前のアクティビティに戻ると、ユーザー インターフェースが前の状態のままで表示されます。 +しかし、コールバック メソッドを使って積極的にアクティビティの状態を保持することもできます。破棄されたアクティビティを再作成しなければならないことを考えると、アクティビティの状態は積極的に保持すべきです。 + +

+ +

システムが(新しいアクティビティの開始時やバックグラウンドへのタスクの移動時などに)アクティビティの 1 つを停止している時にシステム メモリの回復が必要になると、システムはそのアクティビティを完全に破棄する可能性があります。 + +これにより、アクティビティの状態に関する情報が失われてしまいます。システムは、アクティビティがバックスタックに留まっていることは認識していますが、アクティビティがスタックの一番上に置かれるとアクティビティを(再開ではなく)再作成しなければなりません。 + + +ユーザーの作業内容が失われるのを回避するには、{@link android.app.Activity#onSaveInstanceState onSaveInstanceState()} コールバック メソッドをアクティビティに実装することによって、作業状態を積極的に保持する必要があります。 + + +

+ +

アクティビティの状態の保存方法の詳細については、「Activities」ドキュメントをご覧ください。 +

+ + + +

タスクを管理する

+ +

上記のように、Android は、連続して開始されたすべてのアクティビティを同じタスクの「後入れ先出し」式スタックに置くことによって、タスクやバックスタックを管理します。この方法は、ほとんどのアプリケーションでうまく動作し、アクティビティがどのようにタスクに関連付けられ、どのようにバックスタックに置かれているのかを心配する必要はありません。 + + +しかし、通常の動作に割り込みたい場合もあります。 +アプリケーション内のアクティビティが開始された時に(現在のタスク内に置かれる代わりに)新しいタスクを開始させたり、アクティビティの開始時に(新しいインスタンスをバックスタックの一番上に作成する代わりに)アクティビティの既存インスタンスを前に持ってきたり、ユーザーがタスクを離れる時にルート アクティビティ以外のすべてのアクティビティをバックスタックからクリアする場合などが考えられます。 + + + +

+ +

{@code <activity>} マニフェスト エレメントの属性と {@link android.app.Activity#startActivity startActivity()} に渡すインテント内のフラグを使って、これらの、さらに他の動作を実現できます。 + + +

+ +

この件に関して、使用できる主な {@code <activity>} 属性には以下のものがあります。 +

+ + + +

使用できる主なインテント フラグには以下のものがあります。

+ + + +

以下のセクションでは、これらのマニフェスト属性とインテント フラグを使ってアクティビティをタスクに関連付ける方法とバックスタック内での動作を定義する方法を説明します。 +

+ +

また、タスクとアクティビティの提示方法やオーバービュー画面での管理方法に関する考慮点について別途記載しています。 +詳細については、「オーバービュー画面」をご覧ください。 +通常、オーバービュー画面にタスクとアクティビティを提示する方法はシステムが定義できるよう許可し、この動作を変更する必要はありません。 +

+ +

警告: ほとんどのアプリケーションはアクティビティとタスクに対するデフォルトの動作に割り込むべきではありません。 +アクティビティがデフォルトの動作を変更する必要があると判断した場合は、起動時と他のアクティビティやタスクからの [戻る] ボタンによる移動時のアクティビティのユーザビリティを慎重にテストする必要があります。ユーザーが想定する動作と競合する恐れのあるナビゲーション時の動作については必ずテストを行ってください。 + + +

+ + +

起動モードを定義する

+ +

起動モードでは、アクティビティの新しいインスタンスを現在のタスクに関連付ける方法を定義できます。 +次の 2 つの方法を使ってさまざまな起動モードを定義できます。

+ + +

アクティビティ A がアクティビティ B を開始する場合、アクティビティ B は自身を現在のタスクに関連付ける方法(もしあれば)をマニフェストに定義し、アクティビティ A はアクティビティ B を現在のタスクに関連付ける方法を要求できます。 + +両方のアクティビティがアクティビティ B をタスクに関連付ける方法を定義している場合は、アクティビティ B の要求よりも(インテントに定義される)アクティビティ A の要求が優先されます。 + +

+ +

注: マニフェスト ファイルで使用できる起動モードの中にはインテントのフラグとして使用できないものもあります。同様に、インテントのフラグとして使用できる起動モードの中には、マニフェストで定義できないものもあります。 + +

+ + +

マニフェスト ファイルを使用する

+ +

マニフェスト ファイルでアクティビティを宣言する際に、{@code <activity>} エレメントの {@code launchMode} 属性を使ってアクティビティをタスクに関連付ける方法を定義できます。 + + +

+ +

{@code launchMode} 属性は、アクティビティをタスク内で起動する方法についての指示を定めます。 + +launchMode 属性には、次の 4 つの起動モードを割り当てることができます。 + +

+ +
+
{@code "standard"}(デフォルトのモード)
+
デフォルトの設定です。システムは、開始されたタスクからアクティビティの新しいインスタンスをタスク内に作成し、インテントを渡します。 +アクティビティは複数回インスタンス化できます。各インスタンスは異なるタスクに所属でき、1 つのタスクは複数のインスタンスを持つことができます。 +
+
{@code "singleTop"}
+
アクティビティのインスタンスが現在のタスクの一番上に既に存在する場合は、システムはアクティビティの新しいインスタンスを作成せずに、{@link android.app.Activity#onNewIntent onNewIntent()} メソッドを呼び出して、インテントをそのインスタンスに渡します。 + + +アクティビティは複数回インスタンス化できます。各インスタンスは異なるタスクに所属でき、1 つのタスクは複数のインスタンスを持つことができます(バックスタックの一番上にあるアクティビティがそのアクティビティの既存のインスタンスでない場合のみ)。 + + +

たとえば、タスクのバックスタックがアクティビティ B、C、そしてアクティビティ D を一番上に持つルート アクティビティ A で構成されているとします(スタックは A-B-C-D で D が一番上)。 +D タイプのアクティビティにインテントが届きます。もし D の起動モードがデフォルトの {@code "standard"} である場合は、そのクラスの新しいインスタンスが起動し、スタックは A-B-C-D-D となります。しかし、D の起動モードが {@code "singleTop"} である場合は、スタックの一番上にある D の既存のインスタンスが {@link android.app.Activity#onNewIntent onNewIntent()} を介してインテントを受け取ります — この場合、スタックは A-B-C-D のままとなります。ただし、B タイプのアクティビティのインテントが届くと、その起動モードが {@code "singleTop"} であっても、B の新しいインスタンスがスタックに追加されます。 + + + + + +

+

注: アクティビティの新しいインスタンスが作成されると、ユーザーは [戻る] ボタンを押して前のアクティビティに戻ることができます。 +しかし、アクティビティの既存のインスタンスが新しいインテントに対応すると、ユーザーは、新しいインテントが {@link android.app.Activity#onNewIntent onNewIntent()} で届く前の状態に [戻る] ボタンを押して戻ることはできません。 + + + + +

+
+ +
{@code "singleTask"}
+
システムは新しいタスクを作成し、新しいタスクのルートにアクティビティをインスタンス化します。しかし、そのアクティビティのインスタンスが別のタスクに既に存在する場合は、システムは新しいインスタンスを作成せずに、{@link android.app.Activity#onNewIntent onNewIntent()} メソッドを呼び出して、インテントを既存のインスタンスに渡します。 + + +同時に存在できるアクティビティのインスタンスは 1 つだけです。 + +

注: アクティビティは新しいタスク内で開始されますが、ユーザーは [戻る] ボタンを押して前のアクティビティに戻ることができます。 +

+
{@code "singleInstance"}
+
システムが、他のアクティビティをインスタンスを保持しているタスクで起動しないことを除いて {@code "singleTask"} と同じです。 +アクティビティは、常にタスクの唯一の構成要素となります。このアクティビティによって開始されたアクティビティは別のタスクで開きます。 +
+
+ + +

たとえば、Android Browser アプリケーションは、{@code <activity>} エレメントの {@code singleTask} 起動モードを指定することによって、ウェブブラウザ アクティビティを常に自身のタスクで開くことを宣言しています。これは、開発されたアプリケーションが Android Browser を開くインテントを発行すると、そのアクティビティがアプリケーションのタスクには配置されないことを意味します。 + + + +代わりに、Android Browser の新しいタスクが開始されるか、Android Browser に既に実行中のバックグラウンド タスクがある場合は、そのタスクがフォアグラウンドに移動し、新しいインテントに対応します。 + +

+ +

アクティビティを新しいタスク内で開始するかアクティビティを起動したアクティビティと同じタスクで開始するかにかかわらず、ユーザーは [戻る] ボタンを押して前のアクティビティに戻ることができます。 +ただし、{@code singleTask} 起動モードを設定したアクティビティを開始し、そのアクティビティのインスタンスがバックグラウンド タスクに存在する場合は、そのタスク全体がフォアグラウンドに移動します。 + +この時点で、バックスタックの一番上に、フォアグラウンドに移動したタスクのすべてのアクティビティが含まれてます。 + +図 4 は、このタイプのシナリオを示しています。

+ + +

図 4.「singleTask」起動モードを持つアクティビティをバックスタックに追加する方法を示しています。 +アクティビティが自身のバックスタックを持つバックグラウンド タスクの一部である場合は、バックスタック全体がフォアグラウンドに移動し、現在のタスクの上に置かれます。 + +

+ +

マニフェスト ファイルでの起動モードの使用の詳細については、<activity> エレメントのドキュメントをご覧ください。{@code launchMode} 属性と利用できる値について詳しく説明しています。 + + +

+ +

注: 次のセクションで説明しますが、{@code launchMode} 属性を使ってアクティビティに指定する動作は、アクティビティを開始するインテントに含まれるフラグによって上書きされる場合があります。 + +

+ + + +

インテント フラグを使用する

+ +

アクティビティの開始時に、{@link android.app.Activity#startActivity startActivity()} に渡すインテントにフラグを含めることによってアクティビティとタスクのデフォルトの関連付けを変更できます。 + +以下は、デフォルトの動作を変更するために使用できるフラグです。 +

+ +

+

{@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK}
+
新しいタスクでアクティビティを開始します。開始しようとしているアクティビティに対してタスクが既に実行されている場合は、そのタスクの最新の状態が復元されてフォアグラウンドに移動し、アクティビティが {@link android.app.Activity#onNewIntent onNewIntent()} で新しいインテントを受け取ります。 + + +

この手順は、上述のセクションで説明した {@code "singleTask"} {@code launchMode} の値の動作と同じものです。 +

+
{@link android.content.Intent#FLAG_ACTIVITY_SINGLE_TOP}
+
開始されるアクティビティが(バックスタックの一番上にある)現在のアクティビティである場合は、アクティビティの新しいインスタンスを作成する代わりに、既存のインスタンスが {@link android.app.Activity#onNewIntent onNewIntent()} の呼び出しを受け取ります。 + + +

この手順は、上述のセクションで説明した {@code "singleTop"} {@code launchMode} の値の動作と同じものです。 +

+
{@link android.content.Intent#FLAG_ACTIVITY_CLEAR_TOP}
+
開始されるアクティビティが既に現在のタスクを実行している場合は、そのアクティビティの新しいインスタンスを起動する代わりに、上に置かれたその他すべてのアクティビティを破棄し、{@link android.app.Activity#onNewIntent onNewIntent()} を介してこのインテントを(一番上に移動した)アクティビティの再開されたインスタンスに渡します。 + + + +

この動作を起こす {@code launchMode} 属性には値はありません。 +

+

ほとんどのケースで {@code FLAG_ACTIVITY_CLEAR_TOP} は {@code FLAG_ACTIVITY_NEW_TASK} と併用されます。同時に使用することで、これらのフラグは、別のタスクにある既存のアクティビティを捜し出し、インテントに対応できる位置に置く手段を提供します。 + + +

+

注: 指定されたアクティビティの起動モードが {@code "standard"} である場合は、やはりスタックから削除されます。その場所に、渡されるインテントに対応する新しいインスタンスが開始されます。 + + +これは、起動モードが {@code "standard"} である場合は、新しいインテントに対して常に新しいインスタンスが作成されるためです。 +

+
+ + + + + + +

アフィニティを処理する

+ +

アフィニティは、アクティビティの所属が望ましいタスクを示します。 デフォルトでは、同じアプリケーションのすべてアクティビティに相互のアフィニティが設定されています。 +このため、同じアプリケーションのすべてのアクティビティは、デフォルトで同じタスクに属します。 +しかし、アクティビティのデフォルトのアフィニティは変更できます。 +異なるアプリケーションに定義されているアクティビティがアフィニティを共有したり、同じアプリケーションに定義されたアクティビティに別のタスクのアフィニティを割り当てたりすることができます。 + +

+ +

{@code <activity>} エレメントの {@code taskAffinity} 属性を使って、任意のアクティビティのアフィニティを変更できます。 + +

+ +

{@code taskAffinity} 属性には文字列を指定します。これは、{@code <manifest>} エレメントに宣言されたデフォルトのパッケージ名とは異なる一意のものでなければなりません。理由は、システムがアプリケーションに対するデフォルトのタスク アフィニティを識別するためにこの名前を使用するためです。 + + + + +

+ +

アフィニティは、以下の 2 つの状況で役立ちます。

+ + +

ヒント: {@code .apk} ファイルがユーザーの視点で 1 つ以上の「アプリケーション」を含んでいる場合は、各「アプリケーション」に関連付けられたアクティビティに {@code taskAffinity} 属性を使って異なるアフィニティを割り当てることをお勧めします。 + +

+ + + +

バックスタックをクリアする

+ +

ユーザーがタスクを長時間離れると、システムはルート アクティビティを除くすべてのアクティビティをタスクからクリアします。 +ユーザーが再びタスクに戻ると、ルート アクティビティのみが復元されます。システムがこの動作を行うのは、しばらく時間が経過した後は、ユーザーが前に行っていた作業を放棄した可能性が高く、新しいことを始めるためにタスクに戻ったとみなされるためです。 + +

+ +

この動作を変更するために、以下のアクティビティ属性を使用できます。

+ +
+
alwaysRetainTaskState +
+
タスクのルート アクティビティでこの属性が {@code "true"} に設定されていると、上記のデフォルトの動作は発生しません。長時間が経過しても、タスクはすべてのアクティビティをスタックに保持します。 + +
+ +
clearTaskOnLaunch
+
タスクのルート アクティビティでこの属性が {@code "true"} に設定されている場合、ユーザーがタスクを離れて戻るたびに、スタックがルート アクティビティまでクリアされます。 + +つまり、{@code alwaysRetainTaskState} の逆の動作となります。 + +ユーザーがタスクをほんの少しの間離れた場合でも、タスクは常に初期状態に戻ります。 +
+ +
finishOnTaskLaunch +
+
この属性は、{@code clearTaskOnLaunch} に似ていますが、タスク全体ではなく 1 つのアクティビティに作用します。 + +ルート アクティビティを含むあらゆるアクティビティを消すことができます。 +この属性が {@code "true"} に設定されていると、アクティビティは現在のセッションが開かれている間のみタスクの一部として留まります。 +ユーザーがタスクから離れて戻ると、アクティビティは消えています。 +
+
+ + + + +

タスクを開始する

+ +

指定するアクションとして {@code "android.intent.action.MAIN"}、指定するカテゴリとして {@code "android.intent.category.LAUNCHER"} を持つインテント フィルタを付加することによって、アクティビティをタスクのエントリ ポイントとして設定できます。 + + +次に例を示します。

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

この種類のインテント フィルタを使うことによって、アクティビティのアイコンとラベルがアプリケーション ランチャーに表示されるようになります。これにより、ユーザーは、アクティビティを起動し、アクティビティの起動後は、作成されたタスクにいつでも戻ることができるようになります。 + + +

+ +

ユーザーがタスクを離れた後に、このアクティビティ ランチャーを使って戻ってくることが可能でなければならないため、2 番目の機能が重要になります。 +アクティビティが {@link android.content.Intent#ACTION_MAIN} と {@link android.content.Intent#CATEGORY_LAUNCHER} フィルタを持つ場合のみに、アクティビティが常にタスクを開始することを示す {@code "singleTask"} と {@code "singleInstance"} の 2 つの起動モードを使用すべきなのはこのためです。 + + + +たとえば、フィルタがない場合にどうなるのかを想像してみてください。 +インテントが {@code "singleTask"} アクティビティを起動し、新しいタスクを開始し、ユーザーがそのタスクでしばらくの間作業を行います。 +その後、ユーザーは [ホーム] ボタンを押します。 +タスクはバックグラウンドに移動し、見えなくなります。タスクはアプリケーション ランチャーに表示されていないため、この状態ではユーザーがタスクに戻る手段がありません。 +

+ +

ユーザーがアクティビティに戻るのを防ぐには、<activity> エレメントの {@code finishOnTaskLaunch} を {@code "true"} に設定します(スタックをクリアするをご覧ください)。 + + + +

+ +

オーバービュー画面でのタスクとアクティビティの表示方法と管理方法の詳細については、「オーバービュー画面」をご覧ください。 + +

+ + diff --git a/docs/html-intl/intl/ja/guide/index.jd b/docs/html-intl/intl/ja/guide/index.jd new file mode 100644 index 0000000000000000000000000000000000000000..07688b63ec208288380bfb6a1e1652c3d15f5323 --- /dev/null +++ b/docs/html-intl/intl/ja/guide/index.jd @@ -0,0 +1,74 @@ +page.title=Android 入門 + +@jd:body + + + + +

Android は、モバイル端末用の画期的なアプリやゲームを Java 言語環境でビルドできる、充実したアプリケーション フレームワークを提供します。 +左側のナビゲーションに表示されているドキュメントには、さまざまな Android API を使ってアプリをビルドする詳しい方法が記載されています。 +

+ +

Android 開発の経験がない場合は、Android アプリのフレームワークについて次の基本的な概念を理解していることが重要になります。 +

+ + +
+ +
+ +

アプリには複数のエントリ ポイントがある

+ +

Android のアプリは、個別に呼び出し可能な異なるコンポーネントを組み合わせてビルドされます。 +たとえば、1 つのアクティビティが 1 つの画面でユーザー インターフェースを提供し、サービスがバックグラウンドで個別に処理を実行します。 + +

+ +

インテントを使って、1 つのコンポーネントから別のコンポーネントを開始できます。地図アプリで住所を表示するアクティビティなどのように、別のアプリでコンポーネントを開始することもできます。 +このモデルでは、1 つのアプリに複数のエントリ ポイントを提供し、あらゆるアプリを他のアプリが呼び出すアクションの「デフォルト」として動作させることができます。 + +

+ + +

詳細を見る

+ + +
+ + +
+ +

さまざまな端末に対応するアプリ

+ +

Android は、さまざまな端末の構成に独自のリソースを提供できる柔軟なアプリ フレームワークを提供します。 +たとえば、画面サイズごとに個別の XML レイアウト ファイルを作成し、システムが使用中の端末の画面サイズに基づいて適用するレイアウトを決定します。 + +

+ +

アプリの機能に特定のハードウェア(カメラなど)が必要な場合は、実行時に端末機能の使用可能状況をクエリできます。 +必要に応じて、アプリに必要な機能を宣言することもできます。これによって、Google Play ストアなどのアプリ マーケットでは、宣言した機能に対応していない端末へのインストールが許可されません。 + +

+ + +

詳細を見る

+ + +
+ +
+ + + diff --git a/docs/html-intl/intl/ja/guide/topics/manifest/manifest-intro.jd b/docs/html-intl/intl/ja/guide/topics/manifest/manifest-intro.jd new file mode 100644 index 0000000000000000000000000000000000000000..7d9e8ac588f82809a745e87ed6246137e7611c27 --- /dev/null +++ b/docs/html-intl/intl/ja/guide/topics/manifest/manifest-intro.jd @@ -0,0 +1,517 @@ +page.title=アプリ マニフェスト +@jd:body + + + +

+ すべてのアプリケーションには、そのルート ディレクトリに AndroidManifest.xml ファイル(名前はこのまま)が必要です。 + マニフェスト ファイルはアプリに関する重要な情報を Android システムに提示します。これは、システムがアプリのコードを実行する前に必要な情報です。 + + + その他、マニフェスト ファイルでは次のことを行います。 +

+ + + + +

マニフェスト ファイルの構造

+ +

+次の図は、マニフェスト ファイルの一般的な構造とファイルに含むことができるすべてのエレメントを示します。 +各エレメントとそれぞれの属性はすべて別のファイルに記述されます。 +エレメントの詳細を見るには、図中、図の下にあるアルファベット順のエレメント一覧、またはその他の説明文に表示されているエレメント名をクリックしてください。 + + + +

+ +
+<?xml version="1.0" encoding="utf-8"?>
+
+<manifest>
+
+    <uses-permission />
+    <permission />
+    <permission-tree />
+    <permission-group />
+    <instrumentation />
+    <uses-sdk />
+    <uses-configuration />  
+    <uses-feature />  
+    <supports-screens />  
+    <compatible-screens />  
+    <supports-gl-texture />  
+
+    <application>
+
+        <activity>
+            <intent-filter>
+                <action />
+                <category />
+                <data />
+            </intent-filter>
+            <meta-data />
+        </activity>
+
+        <activity-alias>
+            <intent-filter> . . . </intent-filter>
+            <meta-data />
+        </activity-alias>
+
+        <service>
+            <intent-filter> . . . </intent-filter>
+            <meta-data/>
+        </service>
+
+        <receiver>
+            <intent-filter> . . . </intent-filter>
+            <meta-data />
+        </receiver>
+
+        <provider>
+            <grant-uri-permission />
+            <meta-data />
+            <path-permission />
+        </provider>
+
+        <uses-library />
+
+    </application>
+
+</manifest>
+
+ +

+以下は、マニフェスト ファイルに含むことができるすべてのエレメントをアルファベット順に並べたものです。 +使用が許可されているのはこれらのエレメントのみです。独自のエレメントや属性は追加できません。 + +

+ +

+<action> +
<activity> +
<activity-alias> +
<application> +
<category> +
<data> +
<grant-uri-permission> +
<instrumentation> +
<intent-filter> +
<manifest> +
<meta-data> +
<permission> +
<permission-group> +
<permission-tree> +
<provider> +
<receiver> +
<service> +
<supports-screens> +
<uses-configuration> +
<uses-feature> +
<uses-library> +
<uses-permission> +
<uses-sdk> +

+ + + + +

ファイルの規則

+ +

+マニフェスト ファイルに含まれるエレメントと属性には、通常、次の規則とルールが適用されます。 + +

+ +
+
エレメント
+
必須のエレメントは<manifest><application> のみです。両方のエレメントを提示する必要があり、使用できるのはそれぞれ 1 回のみです。 + + +その他のほとんどのエレメントは、複数回使ってもまったく使わなくてもかまいませんが、意味のあるマニフェストを行うにはいくつかのエレメントが必要です。 + + + + +

+内容のあるエレメントには他のエレメントが含まれているためです。すべての値は属性によって設定され、エレメント内に文字データが設定されることはありません。 + +

+ +

+同じレベルにあるエレメントに順番はありません。たとえば、<activity><provider><service> エレメントは、どのような順で並べてもかまいません。 + + + +(<activity-alias> エレメントは例外となります。 + +エイリアスとなっている <activity> のすぐ後に記述する必要があります)。 + + +

+ +
属性
+
正式には、すべての属性はオプションです。しかし、エレメントが目的を達成するために設定しなければならない属性もあります。 +このドキュメントはガイドとしてお使いください。 +真の意味でオプションの属性には、デフォルト値または指定がない場合の動作が記述されています。 + + +

ルートの <manifest> エレメントの属性の一部を除き、すべての属性名は {@code android:} 接頭辞で始まります(例: {@code android:alwaysRetainTaskState})。 + + +この接頭辞は共通であるため、ドキュメントで属性が名前で参照されている場合は通常省略されます。 + +

+ +
クラス名の宣言
+
多くのエレメントは Java オブジェクトと一致しています。これらのエレメントには、アプリケーション自体(<application> エレメント)とその主要なコンポーネントであるアクティビティ(<activity>)、サービス(<service>)、ブロードキャスト レシーバー(<receiver>)やコンテンツ プロバイダ(<provider>)が含まれます。 + + + + + + + + + + + +

+サブクラスを定義する場合は、コンポーネント クラス({@link android.app.Activity}、{@link android.app.Service}、{@link android.content.BroadcastReceiver}、{@link android.content.ContentProvider})でいつも行うように、サブクラスは {@code name} 属性で宣言されます。 + + +この名前には、完全なパッケージ名を含む必要があります。 +たとえば、{@link android.app.Service} サブクラスの宣言は以下のようになります。 + +

+ +
<manifest . . . >
+    <application . . . >
+        <service android:name="com.example.project.SecretService" . . . >
+            . . .
+        </service>
+        . . .
+    </application>
+</manifest>
+ +

+ただし、簡素化する方法として、文字列の最初の文字がピリオドの場合、文字列はアプリケーションのパッケージ名に付加されます(<manifest> エレメントの package 属性によって指定されるため)。 + + + + +以下は、上記のものと同じ内容です。 +

+ +
<manifest package="com.example.project" . . . >
+    <application . . . >
+        <service android:name=".SecretService" . . . >
+            . . .
+        </service>
+        . . .
+    </application>
+</manifest>
+ +

+コンポーネントの開始時に、Android は名前が指定されたサブクラスのインスタンスを作成します。サブクラスが指定されていない場合は、基底クラスのインスタンスを作成します。 + +

+ +
複数の値
+
1 つ以上の値が指定されている場合は、1 つのエレメントに複数の値が記載されているのではなく、エレメントが繰り返されているケースがほとんどです。 +たとえば、インテント フィルタには複数のアクションが記載されている場合があります。 + + +
<intent-filter . . . >
+    <action android:name="android.intent.action.EDIT" />
+    <action android:name="android.intent.action.INSERT" />
+    <action android:name="android.intent.action.DELETE" />
+    . . .
+</intent-filter>
+ +
リソースの値
+
属性には、ユーザーに対して表示する値を含むものもあります。たとえば、アクティビティのラベルやアイコンです。 +これらの属性の値は、ローカライズされていなければならないため、リソースまたはテーマから設定する必要があります。 +リソースの値は、以下の形式で記述されます。 +

+ +

{@code @[package:]type:name}

+ +

+リソースがアプリケーションと同じパッケージ内にある場合は package 名を省略できます。type はリソースのタイプ(「string」や「drawable」など)、name は特定のリソースの名前です。 + +次に例を示します。 + +

+ +
<activity android:icon="@drawable/smallPic" . . . >
+ +

+テーマからの値も同様の方法で表記されますが、冒頭は '{@code @}' ではなく '{@code ?}' となります。 + +

+ +

{@code ?[package:]type:name} +

+ +
文字列の値
+
属性値が文字列で文字をエスケープする場合はダブル バックスラッシュ('{@code \\}')を使う必要があります。たとえば、改行は '{@code \\n}'、Unicode の文字は '{@code \\uxxxx}' となります。 + +
+
+ + +

ファイルの特徴

+ +

+以下のセクションでは、Android の機能がどのようにマニフェスト ファイルに反映されるのかを説明します。 + +

+ + +

インテント フィルタ

+ +

+アプリケーションのコア コンポーネント(アクティビティ、サービス、ブロードキャスト レシーバー)はインテントによって開始されます。 +インテントとは、目的とするアクションを記述している情報の束({@link android.content.Intent} オブジェクト)で、使用されるデータやアクションを実行するコンポーネントのカテゴリ、その他の関連する指示を含みます。 + + +Android は、インテントに対応する適切なコンポーネントを捜し出し、必要に応じて新しいインスタンスを起動し、それにインテント オブジェクトを渡します。 + + + +

+ +

+コンポーネントはインテント フィルタを使ってその機能(対応できるインテントの種類)を通知します。 +Android システムはコンポーネントを起動する前にコンポーネントが対応できるインテントを知っておく必要があるため、インテント フィルタはマニフェスト ファイルの <intent-filter> エレメントで指定されます。 + + + +コンポーネントは、それぞれが異なる機能を記述しているフィルタをいくつでも持つことができます。 + +

+ +

+対象となるコンポーネントの名前を明示的に指定しているインテントは、コンポーネントを起動します。この場合、フィルタは適用されません。 +対象の名前が指定されていない場合は、コンポーネントのフィルタを通過したインテントのみがコンポーネントを起動できます。 + + +

+ +

+インテント フィルタに対してインテント オブジェクトをテストする方法については、「インテントとインテント フィルタ」ドキュメントをご覧ください。 + + + +

+ + +

アイコンとラベル

+ +

+エレメントは、小さなアイコンやテキスト ラベルをユーザーに表示するために、{@code icon} と {@code label} 属性を持つ場合があります。 +画面に長い説明文を表示するための {@code description} 属性を持つエレメントもあります。 + +たとえば、<permission> エレメントはこれらすべての属性を持っています。これによって、アプリケーションがユーザーにパーミッションを付与するかどうかを尋ねる際に、ユーザーに対して、パーミッションを示すアイコン、パーミッションの名前、パーミッションについての説明文を提示できます。 + + + + + +

+ +

+どの場合においても、属性を含むエレメントに設定されたアイコンとラベルが、そのすべてのサブエレメントの {@code icon} と {@code label} のデフォルト設定となります。 +そのため、<application> エレメントに設定されたアイコンとラベルは、アプリケーションの各コンポーネントのデフォルトのアイコンとラベルとなります。 + + +同様に、コンポーネントに設定されたアイコンとラベルは、— <activity> エレメントのように — それぞれのコンポーネントの <intent-filter> エレメントのデフォルト設定となります。 + + + + +<application> エレメントにラベルを設定し、アクティビティとそのインテント フィルタに設定しない場合は、アプリケーション ラベルはアクティビティとインテント フィルタの両方のラベルとして取り扱われます。 + + + + +

+ +

+インテント フィルタに設定されるアイコンとラベルは、コンポーネントがユーザーに対して提示されるときはいつでも、そのフィルタにより通知されている機能を提供するコンポーネントを示すために使用されます。 + +たとえば、「{@code android.intent.action.MAIN}」と「{@code android.intent.category.LAUNCHER}」が設定されているフィルタは、アプリケーションを起動するアクティビティを通知しています — つまり、アプリケーション ランチャーに表示されるということです。 + + + +そのため、このフィルタに設定されているアイコンとラベルがランチャーに表示されるものとなります。 + +

+ + +

パーミッション

+ +

+パーミッションは、コードの一部や端末上のデータへのアクセスを制限する機能を提供します。 + 誤用や悪用によってユーザーの利用を妨げたり有害な作用をもたらす恐れのある重要なデータやコードを保護したりするための制限を設定します。 + +

+ +

+それぞれのパーミッションは一意のラベルで識別されています。通常、ラベルは制限されるアクションを示します。 +以下は、Android により定義されているパーミッションの例です。 + +

+ +

{@code android.permission.CALL_EMERGENCY_NUMBERS} +
{@code android.permission.READ_OWNER_DATA} +
{@code android.permission.SET_WALLPAPER} +
{@code android.permission.DEVICE_POWER}

+ +

+1 つの機能は、1 つのパーミッションでのみ保護できます。 +

+ +

+アプリケーションがパーミッションにより保護されている機能へのアクセスを必要としている場合は、<uses-permission> エレメントをマニフェスト ファイルに記述し、そのパーミッションが必要であることを宣言する必要があります。 + + +これによって、アプリケーションが端末にインストールされた際に、インストーラーは、アプリケーションの証明書に署名した関係機関を確認し、場合によってはユーザーに尋ねることで、要求されたパーミッションを付与するかどうかを決定します。 + + +パーミッションが付与されると、アプリケーションは保護された機能を使用できるようになります。 + +パーミッションが付与されなかった場合は、アプリケーションは機能にアクセスできず、ユーザーに通知が表示されることもありません。 + +

+ +

+アプリケーションは、パーミッションを使って自身のコンポーネント(アクティビティ、サービス、ブロードキャスト レシーバー、コンテンツ プロバイダ)を保護することもできます。 +Android により定義されているパーミッション({@link android.Manifest.permission android.Manifest.permission} に記載)や他のアプリケーションにより宣言されたパーミッションであればどれでも使用できます。 + + +独自のパーミッションを定義することもできます。新しいパーミッションは、<permission> エレメントを使って宣言します。 + + +たとえば、次のようにアクティビティを保護できます。 +

+ +
+<manifest . . . >
+    <permission android:name="com.example.project.DEBIT_ACCT" . . . />
+    <uses-permission android:name="com.example.project.DEBIT_ACCT" />
+    . . .
+    <application . . .>
+        <activity android:name="com.example.project.FreneticActivity"
+                  android:permission="com.example.project.DEBIT_ACCT"
+                  . . . >
+            . . .
+        </activity>
+    </application>
+</manifest>
+
+ +

+この例では、{@code DEBIT_ACCT} パーミッションは <permission> エレメントで宣言されているだけではなく、その使用が <uses-permission> エレメントで要求されている点に注意してください。 + + + + +アプリケーション自身によって保護が設定されている場合でも、アプリケーションの他のコンポーネントが保護されたアクティビティを起動するために、その使用を要求する必要があります。 + + +

+ +

+同じ例で、別の場所で宣言されたパーミッションに {@code permission} 属性が設定されている場合({@code android.permission.CALL_EMERGENCY_NUMBERS} など)は、<permission> エレメントを使って再度宣言する必要があります。 + + + + +その場合も <uses-permission> でその使用を要求する必要があります。 + +

+ +

+<permission-tree> エレメントは、コードに定義されるパーミッションの集まりに対するネームスペースを宣言します。 + + +<permission-group> エレメントは、パーミッションのセットに対するラベル(マニフェスト ファイルに <permission> エレメントを使って宣言されたもの、その他の場所で宣言されたものの両方)を定義します。 + + + +これは、ユーザーに提示される際のパーミッションのグループ分けのみに影響します。 +<permission-group> エレメントでは、パーミッションのグループへの所属は指定されません。グループ名のみが指定されます。 + + +パーミッションは、<permission> エレメントの permissionGroup 属性にグループ名を割り当てることによって、グループに配置されます。 + + + + + +

+ + +

ライブラリ

+ +

+すべてのアプリケーションはデフォルトの Android ライブラリにリンクされています。このライブラリには、Activity、Service、Intent、View、Button、Application、ContentProvider などの共通クラスを含む、アプリケーションをビルドするための基本パッケージが含まれています。 + + + +

+ +

+しかし、独自のライブラリに含まれているパッケージもあります。開発中のアプリケーションがこれらのパッケージのコードを使用している場合は、該当するライブラリにリンクするよう明示的に求める必要があります。 + +この場合は、マニフェスト ファイルに各ライブラリを指定する <uses-library> エレメントが別途含まれている必要があります。 + +(ライブラリ名は該当するパッケージのドキュメントに記載されています)。 + +

diff --git a/docs/html-intl/intl/ja/guide/topics/providers/calendar-provider.jd b/docs/html-intl/intl/ja/guide/topics/providers/calendar-provider.jd new file mode 100644 index 0000000000000000000000000000000000000000..7b97c3083341b3b82005f9215719aec2a2ff1875 --- /dev/null +++ b/docs/html-intl/intl/ja/guide/topics/providers/calendar-provider.jd @@ -0,0 +1,1184 @@ +page.title=カレンダー プロバイダ +@jd:body + + + +

カレンダー プロバイダは、ユーザーのカレンダー イベントのためのリポジトリです。カレンダー プロバイダ API を使用すると、カレンダー、イベント、参加者、リマインダーなどに対するクエリ、挿入、アップデート、削除の操作を実行できます。 + +

+ + +

カレンダー プロバイダ API は、アプリケーションや同期アダプタで使用できます。ルールは、呼び出しを実行しているプログラムのタイプによって異なります。 +本書では主に、カレンダー プロバイダ API のアプリケーションとしての使用について説明します。 +同期アダプタの場合の違いについては、同期アダプタをご覧ください。 + +

+ + +

通常、カレンダー データを読み取ったり書き込んだりするためには、アプリケーションのマニフェストにユーザー パーミッションで説明している適切なパーミッションが含まれている必要があります。 + +一般的な操作を簡単に実行できるようにするため、カレンダー プロバイダには、いくつかのインテントが用意されています。これらについては、カレンダーのインテントで説明しています。 + +これらのインテントは、ユーザーをカレンダー アプリケーションに誘導して、イベントを挿入、参照、編集できるようにします。 +ユーザーは、カレンダー アプリケーションを操作した後、元のアプリケーションに戻ります。 +これにより、アプリケーションがパーミッションを要求したり、イベントの参照や作成のためのインターフェースを提供したりする必要がなくなります。 +

+ +

基本

+ +

コンテンツ プロバイダは、データを保存してアプリケーションからアクセスできるようにします。 +Android プラットフォームによって提供されるコンテンツ プロバイダ(カレンダー プロバイダを含む)は、一般にリレーショナル データベース モデルに基づくテーブルのセットとしてデータを公開します。このモデルにおいて、各行はレコード、各列は特定のタイプと意味のデータになっています。 + +アプリケーションと同期アダプタは、ユーザーのカレンダー データを保持するデータベース テーブルの読み取りおよび書き込みアクセスを、カレンダー プロバイダ API を使用して取得できます。 + +

+ +

各コンテンツ プロバイダは、データセットを一意に指定するパブリック URI({@link android.net.Uri} オブジェクトとしてラップされています)を公開します。 + +複数のデータセット(複数のテーブル)を制御するコンテンツ プロバイダは、データセットごとに個別の URI を公開します。 +プロバイダの URI はすべて、文字列「content://」で始まります。 +これは、コンテンツ プロバイダによって管理されているデータであることを示します。 +カレンダー プロバイダは、クラス(テーブル)それぞれの URI のための定数を定義します。 +URI の形式は <class>.CONTENT_URI です。 +たとえば、{@link android.provider.CalendarContract.Events#CONTENT_URI Events.CONTENT_URI} のようになります。 +

+ +

図 1 に、カレンダー プロバイダのデータモデルを図示します。メイン テーブルとそれらを相互にリンクしているフィールドが示されています。 +

+ +Calendar Provider Data Model +

図 1. カレンダー プロバイダのデータモデル。

+ +

ユーザーは複数のカレンダーを持つことができ、カレンダーをそれぞれ異なるタイプのアカウント(Google カレンダー、Exchange など)と関連付けることができます。

+ +

{@link android.provider.CalendarContract} は、カレンダーのデータモデルとイベント関連の情報を定義します。データは、次に示すいくつかのテーブルに保存されます。

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
テーブル(クラス)説明

{@link android.provider.CalendarContract.Calendars}

このテーブルは、該当するカレンダー固有の情報を保持します。 +各行には、名前、色、同期情報など、1 つのカレンダーに関する詳細が格納されます。 +
{@link android.provider.CalendarContract.Events}このテーブルは、該当するイベント固有の情報を保持します。 +各行には、1 つのイベントに関する情報が格納されます—イベントのタイトル、場所、開始時刻、終了時刻などが該当します。 + +イベントは、単発だったり繰り返し発生したりすることが考えられます。参加者、リマインダー、拡張プロパティは、個別のテーブルに格納されます。 +これらのテーブルにはそれぞれ、Events テーブルの {@link android.provider.BaseColumns#_ID} を参照する {@link android.provider.CalendarContract.AttendeesColumns#EVENT_ID} があります。 + +
{@link android.provider.CalendarContract.Instances}このテーブルは、あるイベントの発生ごとの開始時刻と終了時刻を保持します。 +各行は、イベントの発生 1 回を表します。 +単発イベントには、イベントのインスタンスが 1 対 1 で対応します。 +繰り返しのイベントには、そのイベントの複数回の発生それぞれに対応する複数の行が自動生成されます。 +
{@link android.provider.CalendarContract.Attendees}このテーブルは、イベントの参加者(ゲスト)の情報を保持します。 +各行は、1 回のイベントの 1 人のゲストを表します。 +ゲストのタイプと、ゲストのイベントへの出欠の回答を指定します。 +
{@link android.provider.CalendarContract.Reminders}このテーブルは、アラートや通知のデータを保持します。 +各行は、1 回のイベントの 1 つのアラートを表します。1 つのイベントが複数のリマインダーを持つことができます。 +1 イベントあたりの最大リマインダー数は {@link android.provider.CalendarContract.CalendarColumns#MAX_REMINDERS} で指定され、指定されたカレンダーを所有する同期アダプタによって設定されます。 + + + +リマインダーは、イベント開始前の分単位で指定され、ユーザーに注意喚起する手段を示す情報を保持します。 +
+ +

カレンダー プロバイダ API は、柔軟で効果的な設計になっています。ですが同時に、エンドユーザーにとって使いやすくすることや、カレンダーとそのデータの整合性を保護することが重要です。 + +次に、そのために API の使用に際して留意すべきことを示します。 +

+ + + + +

ユーザー パーミッション

+ +

カレンダー データを読み込むためには、アプリケーションはマニフェスト ファイルに {@link android.Manifest.permission#READ_CALENDAR} パーミッションを含める必要があります。 +カレンダー データを削除、挿入、アップデートするためには、{@link android.Manifest.permission#WRITE_CALENDAR} パーミッションを含める必要があります。 + +

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"...>
+    <uses-sdk android:minSdkVersion="14" />
+    <uses-permission android:name="android.permission.READ_CALENDAR" />
+    <uses-permission android:name="android.permission.WRITE_CALENDAR" />
+    ...
+</manifest>
+
+ + +

Calendars テーブル

+ +

{@link android.provider.CalendarContract.Calendars} テーブルには、個々のカレンダーの詳細情報が格納されます。 +次に示す Calendars の列は、アプリケーションと同期アダプタのどちらからも書き込み可能です。 +サポートされているすべてのフィールドの一覧については、{@link android.provider.CalendarContract.Calendars} のリファレンスをご覧ください。 + +

+ + + + + + + + + + + + + + + + + + + + + + + + + +
定数説明
{@link android.provider.CalendarContract.Calendars#NAME}カレンダーの名前。
{@link android.provider.CalendarContract.Calendars#CALENDAR_DISPLAY_NAME}ユーザーに表示されるこのカレンダーの名前。
{@link android.provider.CalendarContract.Calendars#VISIBLE}カレンダーが表示対象として選択されているかどうかを示すブール値。値 0 は、このカレンダーに関連付けられているイベントを表示しないことを示します。 + +値 1 は、このカレンダーに関連付けられているイベントを表示することを示します。 +この値は、{@link android.provider.CalendarContract.Instances} テーブルの行の生成に影響を与えます。 +
{@link android.provider.CalendarContract.CalendarColumns#SYNC_EVENTS}カレンダーを同期してそのイベントを端末に格納するかどうかを示すブール値。 +値 0 は、このカレンダーを同期せず、イベントを端末に格納しないことを指定します。 +値 1 は、このカレンダーのイベントを同期し、イベントを端末に格納することを指定します。 +
+ +

カレンダーへのクエリ

+ +

次の例は、特定のユーザーが所有するカレンダーを取得する方法を示しています。 +簡潔にするため、この例のクエリ操作はユーザー インターフェース スレッド(「メインスレッド」)内に示されています。 +実際には、メインスレッドではなく非同期スレッドで実行してください。 +詳細については、「ローダ」をご覧ください。 +データを読み込むだけでなく変更する場合は、{@link android.content.AsyncQueryHandler} をご覧ください。 + +

+ + +
+// Projection array. Creating indices for this array instead of doing
+// dynamic lookups improves performance.
+public static final String[] EVENT_PROJECTION = new String[] {
+    Calendars._ID,                           // 0
+    Calendars.ACCOUNT_NAME,                  // 1
+    Calendars.CALENDAR_DISPLAY_NAME,         // 2
+    Calendars.OWNER_ACCOUNT                  // 3
+};
+  
+// The indices for the projection array above.
+private static final int PROJECTION_ID_INDEX = 0;
+private static final int PROJECTION_ACCOUNT_NAME_INDEX = 1;
+private static final int PROJECTION_DISPLAY_NAME_INDEX = 2;
+private static final int PROJECTION_OWNER_ACCOUNT_INDEX = 3;
+ + + + + +

この例の次の部分では、クエリを作成します。クエリの条件は selection に指定しています。 +この例のクエリは、ACCOUNT_NAME が「sampleuser@google.com」で、ACCOUNT_TYPE が「com.google」で、OWNER_ACCOUNT が「sampleuser@google.com」であるカレンダーを検索します。 + + + +ユーザーが所有しているだけでなく参照したことのあるすべてのカレンダーを確認するには、OWNER_ACCOUNT を省略します。このクエリは、データベース クエリが返した結果セットをトラバースするのに使用できる {@link android.database.Cursor} オブジェクトを返します。 + + + +コンテンツ プロバイダでのクエリの使用については、「コンテンツ プロバイダ」をご覧ください。 +

+ + +
// Run query
+Cursor cur = null;
+ContentResolver cr = getContentResolver();
+Uri uri = Calendars.CONTENT_URI;   
+String selection = "((" + Calendars.ACCOUNT_NAME + " = ?) AND (" 
+                        + Calendars.ACCOUNT_TYPE + " = ?) AND ("
+                        + Calendars.OWNER_ACCOUNT + " = ?))";
+String[] selectionArgs = new String[] {"sampleuser@gmail.com", "com.google",
+        "sampleuser@gmail.com"}; 
+// Submit the query and get a Cursor object back. 
+cur = cr.query(uri, EVENT_PROJECTION, selection, selectionArgs, null);
+ +

次のセクションでは、カーソルを使用して結果セットをステップ単位で移動します。例の冒頭で設定した定数を使用して各フィールドの値を返します。 + +

+ +
// Use the cursor to step through the returned records
+while (cur.moveToNext()) {
+    long calID = 0;
+    String displayName = null;
+    String accountName = null;
+    String ownerName = null;
+      
+    // Get the field values
+    calID = cur.getLong(PROJECTION_ID_INDEX);
+    displayName = cur.getString(PROJECTION_DISPLAY_NAME_INDEX);
+    accountName = cur.getString(PROJECTION_ACCOUNT_NAME_INDEX);
+    ownerName = cur.getString(PROJECTION_OWNER_ACCOUNT_INDEX);
+              
+    // Do something with the values...
+
+   ...
+}
+
+ +

カレンダーの変更

+ +

カレンダーのアップデートを実行する場合、カレンダーの {@link android.provider.BaseColumns#_ID} を、URI({@link android.content.ContentUris#withAppendedId(android.net.Uri,long) withAppendedId()})の末尾に追加された ID として、または最初の selection 項目として指定できます。 + + + + +先ほどの selection を "_id=?" で始め、最初の selectionArg をカレンダーの {@link android.provider.BaseColumns#_ID} にする必要があります。 + + +URL に含まれる ID をエンコードすることでも、アップデートを実行できます。この例では、カレンダーの表示名をこの({@link android.content.ContentUris#withAppendedId(android.net.Uri,long) withAppendedId()} を使用する)アプローチで変更しています。 + + + +

+ +
private static final String DEBUG_TAG = "MyActivity";
+...
+long calID = 2;
+ContentValues values = new ContentValues();
+// The new display name for the calendar
+values.put(Calendars.CALENDAR_DISPLAY_NAME, "Trevor's Calendar");
+Uri updateUri = ContentUris.withAppendedId(Calendars.CONTENT_URI, calID);
+int rows = getContentResolver().update(updateUri, values, null, null);
+Log.i(DEBUG_TAG, "Rows updated: " + rows);
+ +

カレンダーの挿入

+ +

カレンダーは主に同期アダプタで管理するように設計されているため、新しいカレンダーは必ず同期アダプタとして挿入してください。 +ほとんどの場合、アプリケーションはカレンダーに対し、表示名の変更のような表面的な変更しかできません。 +アプリケーションがローカル カレンダーを作成する必要がある場合は、{@link android.provider.CalendarContract.SyncColumns#ACCOUNT_TYPE} として {@link android.provider.CalendarContract#ACCOUNT_TYPE_LOCAL} を使用し、カレンダーの挿入を同期アダプタとして実行することで作成できます。{@link android.provider.CalendarContract#ACCOUNT_TYPE_LOCAL} は、端末アカウントと関連付けられていないカレンダー用の特殊なアカウント タイプです。 + + + + + + +このタイプのカレンダーは、サーバーと同期されません。同期アダプタの詳細については、同期アダプタをご覧ください。 +

+ +

Events テーブル

+ +

{@link android.provider.CalendarContract.Events} テーブルには、個々のイベントの詳細情報が格納されます。 + +イベントを追加、アップデート、削除するためには、アプリケーションはマニフェスト ファイルに {@link android.Manifest.permission#WRITE_CALENDAR} パーミッションを含める必要があります。 +

+ +

次に示す Events の列は、アプリケーションと同期アダプタのどちらからも書き込み可能です。 +サポートされているすべてのフィールドの一覧については、{@link android.provider.CalendarContract.Events} のリファレンスをご覧ください。 +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
定数説明
{@link android.provider.CalendarContract.EventsColumns#CALENDAR_ID}イベントが属するカレンダーの {@link android.provider.BaseColumns#_ID}。
{@link android.provider.CalendarContract.EventsColumns#ORGANIZER}イベントの主催者(所有者)のメールアドレス。
{@link android.provider.CalendarContract.EventsColumns#TITLE}イベントのタイトル。
{@link android.provider.CalendarContract.EventsColumns#EVENT_LOCATION}イベントが開催される場所。
{@link android.provider.CalendarContract.EventsColumns#DESCRIPTION}イベントの説明。
{@link android.provider.CalendarContract.EventsColumns#DTSTART}エポックからの UTC ミリ秒単位で表現されたイベント開始時刻。
{@link android.provider.CalendarContract.EventsColumns#DTEND}エポックからの UTC ミリ秒単位で表現されたイベント終了時刻。
{@link android.provider.CalendarContract.EventsColumns#EVENT_TIMEZONE}イベントのタイムゾーン。
{@link android.provider.CalendarContract.EventsColumns#EVENT_END_TIMEZONE}イベントの終了時刻のタイムゾーン。
{@link android.provider.CalendarContract.EventsColumns#DURATION}RFC5545 形式でのイベントの期間。たとえば、"PT1H" という値はイベントが 1 時間であること、"P2W" という値は期間が 2 週間であることを示します。 + + +
{@link android.provider.CalendarContract.EventsColumns#ALL_DAY}値 1 は、そのイベントがローカルのタイムゾーンで定義された終日を占めることを示します。 +値 0 は、1 日のどこかで始まって終わる定例のイベントであることを示します。 +
{@link android.provider.CalendarContract.EventsColumns#RRULE}イベント形式の繰り返し発生ルール。たとえば、"FREQ=WEEKLY;COUNT=10;WKST=SU" のようになります。 +その他の例については、こちらをご覧ください。 +
{@link android.provider.CalendarContract.EventsColumns#RDATE}イベントの繰り返し発生日。通常、{@link android.provider.CalendarContract.EventsColumns#RDATE} は {@link android.provider.CalendarContract.EventsColumns#RRULE} と組み合わせて、繰り返し発生全体を定義するのに使用します。 + + + +詳細については、RFC5545 の仕様をご覧ください。
{@link android.provider.CalendarContract.EventsColumns#AVAILABILITY}このイベントを埋まっている時間に数えるか、それともスケジュールのやり直しが利く空き時間とみなすか。 +
{@link android.provider.CalendarContract.EventsColumns#GUESTS_CAN_MODIFY}ゲストがイベントを変更できるかどうか。
{@link android.provider.CalendarContract.EventsColumns#GUESTS_CAN_INVITE_OTHERS}ゲストが他のゲストを招待できるかどうか。
{@link android.provider.CalendarContract.EventsColumns#GUESTS_CAN_SEE_GUESTS}ゲストが他の参加者のリストを参照できるかどうか。
+ +

イベントの追加

+ +

アプリケーションが新しいイベントを挿入する場合は、{@link android.content.Intent#ACTION_INSERT INSERT} インテントを使用することをお勧めします。詳細については、インテントを使用したイベントの挿入をご覧ください。 +ただし、必要があればイベントを直接挿入できます。 +ここではその方法について説明します。 +

+ + +

新しいイベントを挿入するためのルールは次のとおりです。

+ + +

イベントの挿入の例を次に示します。簡潔にするため、この例は UI スレッドで実行されています。 +実際には、挿入やアップデートは非同期スレッドで実行して、アクションをバックグラウンド スレッドに移してください。 +詳細については、{@link android.content.AsyncQueryHandler} をご覧ください。 +

+ + +
+long calID = 3;
+long startMillis = 0; 
+long endMillis = 0;     
+Calendar beginTime = Calendar.getInstance();
+beginTime.set(2012, 9, 14, 7, 30);
+startMillis = beginTime.getTimeInMillis();
+Calendar endTime = Calendar.getInstance();
+endTime.set(2012, 9, 14, 8, 45);
+endMillis = endTime.getTimeInMillis();
+...
+
+ContentResolver cr = getContentResolver();
+ContentValues values = new ContentValues();
+values.put(Events.DTSTART, startMillis);
+values.put(Events.DTEND, endMillis);
+values.put(Events.TITLE, "Jazzercise");
+values.put(Events.DESCRIPTION, "Group workout");
+values.put(Events.CALENDAR_ID, calID);
+values.put(Events.EVENT_TIMEZONE, "America/Los_Angeles");
+Uri uri = cr.insert(Events.CONTENT_URI, values);
+
+// get the event ID that is the last element in the Uri
+long eventID = Long.parseLong(uri.getLastPathSegment());
+// 
+// ... do something with event ID
+//
+//
+ +

注: イベントの作成後にイベント ID を取得していることに注目してください。 +これがイベント ID を取得する最も簡単な方法です。他のカレンダー操作を実行するためにイベント ID が必要になることはよくあります—イベントに対する参加者やリマインダーの追加などが該当します。 + +

+ + +

イベントのアップデート

+ +

アプリケーションでユーザーによるイベントの編集を許可する場合は、{@link android.content.Intent#ACTION_EDIT EDIT} インテントを使用することをお勧めします。詳細については、インテントを使用したイベントの挿入をご覧ください。ただし、イベントは必要に応じて直接編集できます。 + + +イベントのアップデートを実行する場合は、イベントの _ID を、URI({@link android.content.ContentUris#withAppendedId(android.net.Uri,long) withAppendedId()})の末尾に追加された ID として、または前出の selection の最初の項目として指定できます。 + + + +前出の selection を "_id=?" で始め、最初の selectionArg をカレンダーの _ID にする必要があります。 + +ID なしの selection を使用してアップデートすることもできます。次に、イベントのアップデートの例を示します。 + +ここでは、{@link android.content.ContentUris#withAppendedId(android.net.Uri,long) withAppendedId()} を使用するアプローチでイベントのタイトルを変更しています。 + +

+ + +
private static final String DEBUG_TAG = "MyActivity";
+...
+long eventID = 188;
+...
+ContentResolver cr = getContentResolver();
+ContentValues values = new ContentValues();
+Uri updateUri = null;
+// The new title for the event
+values.put(Events.TITLE, "Kickboxing"); 
+updateUri = ContentUris.withAppendedId(Events.CONTENT_URI, eventID);
+int rows = getContentResolver().update(updateUri, values, null, null);
+Log.i(DEBUG_TAG, "Rows updated: " + rows);  
+ +

イベントの削除

+ +

イベントは、目的のイベントの {@link android.provider.BaseColumns#_ID} を URI の末尾に追加する ID として使用するか、標準的な選択を使用して削除できます。 + +ID の追加で削除を実行する場合、選択はできません。削除には、アプリケーションとして実行する方法と同期アダプタとして実行する方法の 2 種類があります。 +アプリケーションとしての削除では、削除される列が 1 に設定されます。 +このフラグは同期アダプタに対して、その行が削除されたことを通知し、この削除をサーバーに通知するよう指示します。 + +同期アダプタとしての削除では、すべての関連データとともにデータベースからイベントが削除されます。 +次の例では、アプリケーションがイベントをその {@link android.provider.BaseColumns#_ID} を使用して削除しています。 +

+ + +
private static final String DEBUG_TAG = "MyActivity";
+...
+long eventID = 201;
+...
+ContentResolver cr = getContentResolver();
+ContentValues values = new ContentValues();
+Uri deleteUri = null;
+deleteUri = ContentUris.withAppendedId(Events.CONTENT_URI, eventID);
+int rows = getContentResolver().delete(deleteUri, null, null);
+Log.i(DEBUG_TAG, "Rows deleted: " + rows);  
+
+ +

Attendees テーブル

+ +

{@link android.provider.CalendarContract.Attendees} テーブルの各行は、あるイベントの 1 人の参加者ないしゲストを表します。 +{@link android.provider.CalendarContract.Reminders#query(android.content.ContentResolver, long, java.lang.String[]) query()} を呼び出すと、指定された {@link android.provider.CalendarContract.AttendeesColumns#EVENT_ID} を持つイベントの参加者リストが返されます。 + + +この {@link android.provider.CalendarContract.AttendeesColumns#EVENT_ID} は特定のイベントの {@link android.provider.BaseColumns#_ID} と一致している必要があります。 + + +

+ +

次の表に、書き込み可能なフィールドを示します。 +新しい参加者を挿入する際には、ATTENDEE_NAME を除くすべてを含める必要があります。 + +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
定数説明
{@link android.provider.CalendarContract.AttendeesColumns#EVENT_ID}イベントの ID。
{@link android.provider.CalendarContract.AttendeesColumns#ATTENDEE_NAME}参加者の名前。
{@link android.provider.CalendarContract.AttendeesColumns#ATTENDEE_EMAIL}参加者のメールアドレス。
{@link android.provider.CalendarContract.AttendeesColumns#ATTENDEE_RELATIONSHIP}

参加者のイベントに対する関係。次のどれかです。

+
    +
  • {@link android.provider.CalendarContract.AttendeesColumns#RELATIONSHIP_ATTENDEE}
  • +
  • {@link android.provider.CalendarContract.AttendeesColumns#RELATIONSHIP_NONE}
  • +
  • {@link android.provider.CalendarContract.AttendeesColumns#RELATIONSHIP_ORGANIZER}
  • +
  • {@link android.provider.CalendarContract.AttendeesColumns#RELATIONSHIP_PERFORMER}
  • +
  • {@link android.provider.CalendarContract.AttendeesColumns#RELATIONSHIP_SPEAKER}
  • +
+
{@link android.provider.CalendarContract.AttendeesColumns#ATTENDEE_TYPE}

参加者のタイプ。次のどれかです。

+
    +
  • {@link android.provider.CalendarContract.AttendeesColumns#TYPE_REQUIRED}
  • +
  • {@link android.provider.CalendarContract.AttendeesColumns#TYPE_OPTIONAL}
  • +
{@link android.provider.CalendarContract.AttendeesColumns#ATTENDEE_STATUS}

参加者の出席ステータス。次のどれかです。

+
    +
  • {@link android.provider.CalendarContract.AttendeesColumns#ATTENDEE_STATUS_ACCEPTED}
  • +
  • {@link android.provider.CalendarContract.AttendeesColumns#ATTENDEE_STATUS_DECLINED}
  • +
  • {@link android.provider.CalendarContract.AttendeesColumns#ATTENDEE_STATUS_INVITED}
  • +
  • {@link android.provider.CalendarContract.AttendeesColumns#ATTENDEE_STATUS_NONE}
  • +
  • {@link android.provider.CalendarContract.AttendeesColumns#ATTENDEE_STATUS_TENTATIVE}
  • +
+ +

参加者の追加

+ +

次の例では、あるイベントに 1 人の参加者を挿入しています。{@link android.provider.CalendarContract.AttendeesColumns#EVENT_ID} は必須です。 + +

+ +
+long eventID = 202;
+...
+ContentResolver cr = getContentResolver();
+ContentValues values = new ContentValues();
+values.put(Attendees.ATTENDEE_NAME, "Trevor");
+values.put(Attendees.ATTENDEE_EMAIL, "trevor@example.com");
+values.put(Attendees.ATTENDEE_RELATIONSHIP, Attendees.RELATIONSHIP_ATTENDEE);
+values.put(Attendees.ATTENDEE_TYPE, Attendees.TYPE_OPTIONAL);
+values.put(Attendees.ATTENDEE_STATUS, Attendees.ATTENDEE_STATUS_INVITED);
+values.put(Attendees.EVENT_ID, eventID);
+Uri uri = cr.insert(Attendees.CONTENT_URI, values);
+
+ +

Reminders テーブル

+ +

{@link android.provider.CalendarContract.Reminders} テーブルの各行は、あるイベントの 1 つのリマインダーを表します。 +{@link android.provider.CalendarContract.Reminders#query(android.content.ContentResolver, long, java.lang.String[]) query()} を呼び出すと、指定された {@link android.provider.CalendarContract.AttendeesColumns#EVENT_ID} を持つイベントのリマインダー リストが返されます。 + + +

+ + +

次の表に、リマインダーに関する書き込み可能なフィールドを示します。新しいリマインダーを挿入する際には、これらをすべて含める必要があります。 +同期アダプタは、サポートするリマインダーのタイプを {@link android.provider.CalendarContract.Calendars} テーブルで指定しています。 + +詳細については、{@link android.provider.CalendarContract.CalendarColumns#ALLOWED_REMINDERS} をご覧ください。 + +

+ + + + + + + + + + + + + + + + + + + +
定数説明
{@link android.provider.CalendarContract.RemindersColumns#EVENT_ID}イベントの ID。
{@link android.provider.CalendarContract.RemindersColumns#MINUTES}リマインダーが通知されるまでの分単位の時間。
{@link android.provider.CalendarContract.RemindersColumns#METHOD}

アラーム手段。サーバーに設定されます。次のどれかです。

+
    +
  • {@link android.provider.CalendarContract.RemindersColumns#METHOD_ALERT}
  • +
  • {@link android.provider.CalendarContract.RemindersColumns#METHOD_DEFAULT}
  • +
  • {@link android.provider.CalendarContract.RemindersColumns#METHOD_EMAIL}
  • +
  • {@link android.provider.CalendarContract.RemindersColumns#METHOD_SMS}
  • +
+ +

リマインダーの追加

+ +

この例では、イベントにリマインダーを追加します。このリマインダーは、イベントの 15 分前に通知されます。 +

+
+long eventID = 221;
+...
+ContentResolver cr = getContentResolver();
+ContentValues values = new ContentValues();
+values.put(Reminders.MINUTES, 15);
+values.put(Reminders.EVENT_ID, eventID);
+values.put(Reminders.METHOD, Reminders.METHOD_ALERT);
+Uri uri = cr.insert(Reminders.CONTENT_URI, values);
+ +

Instances テーブル

+ +

{@link android.provider.CalendarContract.Instances} テーブルは、あるイベントの発生の開始時刻と終了時刻を保持します。 + +各行は、イベントの発生 1 回を表します。 +Instances テーブルは書き込みできません。イベントの発生をクエリする手段を提供するためだけのものです。 +

+ +

次の表に、インスタンスに関してクエリできるフィールドの一部を示します。タイムゾーンは {@link android.provider.CalendarContract.CalendarCache#KEY_TIMEZONE_TYPE} と {@link android.provider.CalendarContract.CalendarCache#KEY_TIMEZONE_INSTANCES} で定義されます。 + + + +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
定数説明
{@link android.provider.CalendarContract.Instances#BEGIN}インスタンスの開始時刻(UTC ミリ秒単位)。
{@link android.provider.CalendarContract.Instances#END}インスタンスの終了時刻(UTC ミリ秒単位)。
{@link android.provider.CalendarContract.Instances#END_DAY}インスタンスの、カレンダーのタイムゾーンにおけるユリウス暦での終了日。 + + +
{@link android.provider.CalendarContract.Instances#END_MINUTE}カレンダーのタイムゾーンにおける午前零時を基準にしたインスタンスの終了分。 +
{@link android.provider.CalendarContract.Instances#EVENT_ID}このインスタンスのイベントの _ID
{@link android.provider.CalendarContract.Instances#START_DAY}インスタンスの、カレンダーのタイムゾーンにおけるユリウス暦での開始日。 +
{@link android.provider.CalendarContract.Instances#START_MINUTE}カレンダーのタイムゾーンにおける午前零時を基準にしたインスタンスの開始分。 + +
+ +

Instances テーブルへのクエリ

+ +

Instances テーブルにクエリするには、クエリの範囲を示す時刻を URI に指定する必要があります。この例で、{@link android.provider.CalendarContract.Instances} は {@link android.provider.CalendarContract.EventsColumns} インターフェースの実装を介して {@link android.provider.CalendarContract.EventsColumns#TITLE} フィールドへのアクセスを取得します。 + + + +言い換えると、{@link android.provider.CalendarContract.EventsColumns#TITLE} はデータベースの参照に対して返されているのであって、{@link android.provider.CalendarContract.Instances} テーブルそのものへのクエリに対して返されているのではありません。 + + + +

+ +
+private static final String DEBUG_TAG = "MyActivity";
+public static final String[] INSTANCE_PROJECTION = new String[] {
+    Instances.EVENT_ID,      // 0
+    Instances.BEGIN,         // 1
+    Instances.TITLE          // 2
+  };
+  
+// The indices for the projection array above.
+private static final int PROJECTION_ID_INDEX = 0;
+private static final int PROJECTION_BEGIN_INDEX = 1;
+private static final int PROJECTION_TITLE_INDEX = 2;
+...
+
+// Specify the date range you want to search for recurring
+// event instances
+Calendar beginTime = Calendar.getInstance();
+beginTime.set(2011, 9, 23, 8, 0);
+long startMillis = beginTime.getTimeInMillis();
+Calendar endTime = Calendar.getInstance();
+endTime.set(2011, 10, 24, 8, 0);
+long endMillis = endTime.getTimeInMillis();
+  
+Cursor cur = null;
+ContentResolver cr = getContentResolver();
+
+// The ID of the recurring event whose instances you are searching
+// for in the Instances table
+String selection = Instances.EVENT_ID + " = ?";
+String[] selectionArgs = new String[] {"207"};
+
+// Construct the query with the desired date range.
+Uri.Builder builder = Instances.CONTENT_URI.buildUpon();
+ContentUris.appendId(builder, startMillis);
+ContentUris.appendId(builder, endMillis);
+
+// Submit the query
+cur =  cr.query(builder.build(), 
+    INSTANCE_PROJECTION, 
+    selection, 
+    selectionArgs, 
+    null);
+   
+while (cur.moveToNext()) {
+    String title = null;
+    long eventID = 0;
+    long beginVal = 0;    
+    
+    // Get the field values
+    eventID = cur.getLong(PROJECTION_ID_INDEX);
+    beginVal = cur.getLong(PROJECTION_BEGIN_INDEX);
+    title = cur.getString(PROJECTION_TITLE_INDEX);
+              
+    // Do something with the values. 
+    Log.i(DEBUG_TAG, "Event:  " + title); 
+    Calendar calendar = Calendar.getInstance();
+    calendar.setTimeInMillis(beginVal);  
+    DateFormat formatter = new SimpleDateFormat("MM/dd/yyyy");
+    Log.i(DEBUG_TAG, "Date: " + formatter.format(calendar.getTime()));    
+    }
+ }
+ +

カレンダーのインテント

+

アプリケーションがカレンダー データの読み取りや書き込みを実行するのにパーミッションは要りません。その代わり、Android のカレンダー アプリケーションがサポートするインテントを使用し、読み取りと書き込みの操作をそのアプリケーションに引き渡します。次の表に、カレンダー プロバイダによってサポートされているインテントを示します。

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
アクションURI説明エクストラ

+ {@link android.content.Intent#ACTION_VIEW VIEW}

content://com.android.calendar/time/<ms_since_epoch>

+ URI は、{@link android.provider.CalendarContract#CONTENT_URI CalendarContract.CONTENT_URI} を使用しても参照できます。 +このインテントの使用例については、インテントを使用したカレンダー データの参照をご覧ください。 + + +
<ms_since_epoch> に指定された時刻でカレンダーを開きます。なし

{@link android.content.Intent#ACTION_VIEW VIEW}

+ +

content://com.android.calendar/events/<event_id>

+ + URI は、{@link android.provider.CalendarContract.Events#CONTENT_URI Events.CONTENT_URI} を使用しても参照できます。 +このインテントの使用例については、インテントを使用したカレンダー データの参照をご覧ください。 + + +
<event_id> で指定されたイベントを参照します。{@link android.provider.CalendarContract#EXTRA_EVENT_BEGIN_TIME CalendarContract.EXTRA_EVENT_BEGIN_TIME}
+
+
+ {@link android.provider.CalendarContract#EXTRA_EVENT_END_TIME CalendarContract.EXTRA_EVENT_END_TIME}
{@link android.content.Intent#ACTION_EDIT EDIT}

content://com.android.calendar/events/<event_id>

+ + URI は、{@link android.provider.CalendarContract.Events#CONTENT_URI Events.CONTENT_URI} を使用しても参照できます。 +このインテントの使用例については、インテントを使用したカレンダー データの編集をご覧ください。 + + + +
<event_id> で指定されたイベントを編集します。{@link android.provider.CalendarContract#EXTRA_EVENT_BEGIN_TIME CalendarContract.EXTRA_EVENT_BEGIN_TIME}
+
+
+ {@link android.provider.CalendarContract#EXTRA_EVENT_END_TIME CalendarContract.EXTRA_EVENT_END_TIME}
{@link android.content.Intent#ACTION_EDIT EDIT}
+
+ {@link android.content.Intent#ACTION_INSERT INSERT}

content://com.android.calendar/events

+ + URI は、{@link android.provider.CalendarContract.Events#CONTENT_URI Events.CONTENT_URI} を使用しても参照できます。 +このインテントの使用例については、インテントを使用したカレンダー データの挿入をご覧ください。 + + +
イベントを作成します。次の表に示された任意のエクストラ。
+ +

次の表に、カレンダー プロバイダによってサポートされているインテント エクストラを示します。 +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
インテント エクストラ説明
{@link android.provider.CalendarContract.EventsColumns#TITLE Events.TITLE}イベントの名前。
{@link android.provider.CalendarContract#EXTRA_EVENT_BEGIN_TIME CalendarContract.EXTRA_EVENT_BEGIN_TIME} +イベントの開始時刻(エポックからのミリ秒単位)。
{@link android.provider.CalendarContract#EXTRA_EVENT_END_TIME CalendarContract.EXTRA_EVENT_END_TIME} +イベントの終了時刻(エポックからのミリ秒単位)。
{@link android.provider.CalendarContract#EXTRA_EVENT_ALL_DAY CalendarContract.EXTRA_EVENT_ALL_DAY} +イベントが終日かどうかを示すブール値。使用できる値は true または false です。 +
{@link android.provider.CalendarContract.EventsColumns#EVENT_LOCATION Events.EVENT_LOCATION} +イベントの場所。
{@link android.provider.CalendarContract.EventsColumns#DESCRIPTION Events.DESCRIPTION} +イベントの説明。
+ {@link android.content.Intent#EXTRA_EMAIL Intent.EXTRA_EMAIL}招待者のメールアドレス(コンマ区切りリスト形式)。
+ {@link android.provider.CalendarContract.EventsColumns#RRULE Events.RRULE}イベントの反復ルール。
+ {@link android.provider.CalendarContract.EventsColumns#ACCESS_LEVEL Events.ACCESS_LEVEL} +イベントが公開か非公開か。
{@link android.provider.CalendarContract.EventsColumns#AVAILABILITY Events.AVAILABILITY} +このイベントを埋まっている時間に数えるか、それともスケジュールのやり直しが利く空き時間とみなすか。
+

次のセクションでは、これらのインテント使用方法を説明します。

+ + +

インテントを使用したイベントの挿入

+ +

{@link android.content.Intent#ACTION_INSERT INSERT} インテントを使用すると、アプリケーションはイベント挿入タスクをカレンダーそのものに引き渡すことができます。このアプローチでは、アプリケーションは {@link android.Manifest.permission#WRITE_CALENDAR} パーミッションをマニフェスト ファイルに含める必要さえなくなります。 + + +

+ + +

このアプローチのアプリケーションをユーザーが実行すると、アプリケーションによってユーザーがカレンダーに誘導されイベントを追加できるようになります。 +{@link android.content.Intent#ACTION_INSERT INSERT} インテントは、追加フィールドを使用して、カレンダーに格納されている詳細情報をフォームに自動入力します。 + +それに対して、ユーザーはイベントのキャンセル、必要に応じたイベントの編集、イベントのカレンダーへの保存ができます。 + +

+ + + +

次に示すのは、2012 年 1 月 19 日の午前 7:30 から 午前 8:30 までのイベントをスケジュールするコード スニペットです。 +このコード スニペットの以下の点に注目してください。

+ + +
+Calendar beginTime = Calendar.getInstance();
+beginTime.set(2012, 0, 19, 7, 30);
+Calendar endTime = Calendar.getInstance();
+endTime.set(2012, 0, 19, 8, 30);
+Intent intent = new Intent(Intent.ACTION_INSERT)
+        .setData(Events.CONTENT_URI)
+        .putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, beginTime.getTimeInMillis())
+        .putExtra(CalendarContract.EXTRA_EVENT_END_TIME, endTime.getTimeInMillis())
+        .putExtra(Events.TITLE, "Yoga")
+        .putExtra(Events.DESCRIPTION, "Group class")
+        .putExtra(Events.EVENT_LOCATION, "The gym")
+        .putExtra(Events.AVAILABILITY, Events.AVAILABILITY_BUSY)
+        .putExtra(Intent.EXTRA_EMAIL, "rowan@example.com,trevor@example.com");
+startActivity(intent);
+
+ +

インテントを使用したイベントの編集

+ +

イベントは、イベントのアップデートで説明したように、直接アップデートできます。ただし、{@link android.content.Intent#ACTION_EDIT EDIT} インテントを使用すれば、パーミッションのないアプリケーションが、カレンダー アプリケーションにイベント編集を引き渡すことができます。カレンダーでのイベント編集を終えたユーザーは、元のアプリケーションに戻ります。 + + + +

次の例のインテントは、指定されたイベントの新しいタイトルを設定して、ユーザーがそのイベントをカレンダーで編集できるようにしています。 +

+ + +
long eventID = 208;
+Uri uri = ContentUris.withAppendedId(Events.CONTENT_URI, eventID);
+Intent intent = new Intent(Intent.ACTION_EDIT)
+    .setData(uri)
+    .putExtra(Events.TITLE, "My New Title");
+startActivity(intent);
+ +

インテントを使用したカレンダー データの参照

+

カレンダー プロバイダには、{@link android.content.Intent#ACTION_VIEW VIEW} インテントを使用する次の 2 つの方法が用意されています。

+ +

次の例は、特定の日付のカレンダーを開く方法を示しています。

+
// A date-time specified in milliseconds since the epoch.
+long startMillis;
+...
+Uri.Builder builder = CalendarContract.CONTENT_URI.buildUpon();
+builder.appendPath("time");
+ContentUris.appendId(builder, startMillis);
+Intent intent = new Intent(Intent.ACTION_VIEW)
+    .setData(builder.build());
+startActivity(intent);
+ +

次の例は、参照するためにイベントを開く方法を示しています。

+
long eventID = 208;
+...
+Uri uri = ContentUris.withAppendedId(Events.CONTENT_URI, eventID);
+Intent intent = new Intent(Intent.ACTION_VIEW)
+   .setData(uri);
+startActivity(intent);
+
+ + +

同期アダプタ

+ + +

アプリケーションと同期アダプタとの間で、カレンダー プロバイダにアクセスする方法の違いは、次のいくつかだけです。 +

+ + + +

次に示すヘルパー メソッドを使用すると、同期アダプタで使用するための URI が返されます。

+
 static Uri asSyncAdapter(Uri uri, String account, String accountType) {
+    return uri.buildUpon()
+        .appendQueryParameter(android.provider.CalendarContract.CALLER_IS_SYNCADAPTER,"true")
+        .appendQueryParameter(Calendars.ACCOUNT_NAME, account)
+        .appendQueryParameter(Calendars.ACCOUNT_TYPE, accountType).build();
+ }
+
+

同期アダプタの実装例(カレンダーに特化されたものではありません)については、SampleSyncAdapter をご覧ください。 + diff --git a/docs/html-intl/intl/ja/guide/topics/providers/contacts-provider.jd b/docs/html-intl/intl/ja/guide/topics/providers/contacts-provider.jd new file mode 100644 index 0000000000000000000000000000000000000000..e12b62f222c535c2c9cb4b0aecd4d6c7999eafda --- /dev/null +++ b/docs/html-intl/intl/ja/guide/topics/providers/contacts-provider.jd @@ -0,0 +1,2356 @@ +page.title=連絡先プロバイダ +@jd:body +

+
+

クイックビュー

+
    +
  • 人の情報に関する Android のリポジトリ。
  • +
  • + ウェブとの同期。 +
  • +
  • + ソーシャル ストリーム データの統合。 +
  • +
+

本書の内容

+
    +
  1. + 連絡先プロバイダの構成 +
  2. +
  3. + 未加工連絡先 +
  4. +
  5. + データ +
  6. +
  7. + 連絡先 +
  8. +
  9. + 同期アダプタからのデータ +
  10. +
  11. + 必要なパーミッション +
  12. +
  13. + ユーザー プロファイル +
  14. +
  15. + 連絡先プロバイダのメタデータ +
  16. +
  17. + 連絡先プロバイダへのアクセス +
  18. +
  19. +
  20. + 連絡先プロバイダの同期アダプタ +
  21. +
  22. + ソーシャル ストリーム データ +
  23. +
  24. + 連絡先プロバイダの追加機能 +
  25. +
+

キークラス

+
    +
  1. {@link android.provider.ContactsContract.Contacts}
  2. +
  3. {@link android.provider.ContactsContract.RawContacts}
  4. +
  5. {@link android.provider.ContactsContract.Data}
  6. +
  7. {@code android.provider.ContactsContract.StreamItems}
  8. +
+

関連サンプル

+
    +
  1. + Contact Manager + + +
  2. +
  3. + サンプル同期アダプタ + +
  4. +
+

関連ドキュメント

+
    +
  1. + コンテンツ プロバイダの基本 + + +
  2. +
+
+
+

+ 連絡先プロバイダは、人のデータに関する端末の中央リポジトリを管理する、柔軟で効果的な Android コンポーネントです。 +連絡先プロバイダは、端末の連絡先アプリケーションに表示されるデータのソースであり、さらに独自アプリケーションで連絡先プロバイダのデータにアクセスし、端末とオンライン サービスとの間でデータを転送することもできます。 + +このプロバイダは幅広いデータソースに対応しており、各人に関してできるだけ多くのデータを管理しようとするため、複雑な構造をしています。 + +そのため、連絡先プロバイダの API には、データの取得と変更をどちらもやりやすくするクラスやインターフェースが多数用意されています。 + + +

+

+ このガイドでは、以下について説明します。 +

+ +

+ このガイドは、Android のコンテンツ プロバイダの基礎知識がある読者を対象としています。Android のコンテンツ プロバイダについて詳しくは、「コンテンツ プロバイダの基本」をご覧ください。 + + +サンプル同期アダプタ サンプルアプリは、同期アダプタを使用して連絡先プロバイダと Google ウェブ サービスでホストされているサンプル アプリケーションとの間でデータを転送する例です。 + + + +

+

連絡先プロバイダの構成

+

+ 連絡先プロバイダは、Android のコンテンツ プロバイダ コンポーネントです。人に関する 3 種類のデータを保持しており、それぞれがコンテンツ プロバイダによって提供される 1 つのテーブルに対応しています。この構成を図 1 に示します。 + + +

+ +

+ 図 1. 連絡先プロバイダのテーブル構造。 +

+

+ この 3 つのテーブルは、一般にそれぞれのコントラクト クラス名で呼ばれます。各クラスは、テーブルによって使用されるコンテンツ URI、列名、列の値のための定数を定義しています。 + +

+
+
+ {@link android.provider.ContactsContract.Contacts} テーブル +
+
+ 各行は、未加工連絡先の行の集約に基づいて異なる人を表します。 +
+
+ {@link android.provider.ContactsContract.RawContacts} テーブル +
+
+ 各行には、ユーザーのアカウントとタイプに固有の、人に関するデータの概要が格納されています。 +
+
+ {@link android.provider.ContactsContract.Data} テーブル +
+
+ 各行には、メールアドレスや電話番号など、未加工連絡先の詳細情報が格納されています。 +
+
+

+ {@link android.provider.ContactsContract} に含まれるコントラクト クラスによって表現される他のテーブルは補助テーブルであり、連絡先プロバイダがその操作を管理したり、端末の連絡先アプリや電話アプリの特定機能をサポートしたりするために使用します。 + + +

+

未加工連絡先

+

+ 未加工連絡先は、アカウント タイプとアカウント名の 1 つのペアを使用して得られる人に関するデータです。 +連絡先プロバイダでは、1 人に関するデータのソースとして複数のオンライン サーバーを使用できるため、同じ個人に対して複数の未加工連絡先が許されます。 + + 未加工連絡先が複数ある場合、ユーザーは同じアカウント タイプの複数のアカウントから取得したその人のデータを組み合わせることができます。 + +

+

+ 未加工連絡先データの大半は、{@link android.provider.ContactsContract.RawContacts} テーブルには格納されません。 +その代わり、{@link android.provider.ContactsContract.Data} テーブルの 1 つまたは複数の行に格納されています。 +各データ行には列 {@link android.provider.ContactsContract.DataColumns#RAW_CONTACT_ID Data.RAW_CONTACT_ID} があり、親 {@link android.provider.ContactsContract.RawContacts} 行の {@code android.provider.BaseColumns#_ID RawContacts._ID} 値が格納されています。 + + + +

+

重要な未加工連絡先列

+

+ 表 1 に、{@link android.provider.ContactsContract.RawContacts} の重要な列を示します。 +表の後の注もお読みください。 +

+

+ 表 1. 重要な未加工連絡先列。 +

+ + + + + + + + + + + + + + + + + + + + +
列名用途
+ {@link android.provider.ContactsContract.SyncColumns#ACCOUNT_NAME} + + この未加工連絡先のソースであるアカウント タイプにおけるアカウント名。 + たとえば、Google アカウントのアカウント名は、デバイス オーナーの Gmail アドレスのどれかです。 +詳しくは、次の {@link android.provider.ContactsContract.SyncColumns#ACCOUNT_TYPE} の項目をご覧ください。 + + + + この名前の形式は、アカウント タイプによって異なります。必ずしもメールアドレスとは限りません。 + +
+ {@link android.provider.ContactsContract.SyncColumns#ACCOUNT_TYPE} + + この未加工連絡先のソースであるアカウント タイプ。たとえば、Google アカウントのアカウント タイプは com.google です。 +アカウント タイプは必ず、所有または管理しているドメインのドメイン ID で修飾してください。 +そうすることで、アカウント タイプが確実に一意になります。 + + + 連絡先データを提供するアカウント タイプには、通常、連絡先プロバイダと同期する関連同期アダプタが用意されています。 + +
+ {@link android.provider.ContactsContract.RawContactsColumns#DELETED} + + 未加工連絡先の「削除済み」フラグ。 + + 連絡先プロバイダは、同期アダプタが該当行をサーバーから削除し、最終的にリポジトリから削除するまで、このフラグを基に行を内部的に管理します。 + + +
+

+

+ 次に、{@link android.provider.ContactsContract.RawContacts} テーブルに関する重要な注を示します。 + +

+ +

未加工連絡先データのソース

+

+ 未加工連絡先の働きを理解するために、「Emily Dickinson」というユーザーについて考えてみましょう。彼女は自分の端末に次の 3 つのアカウントを定義しています。 + +

+ +

+ このユーザーは、[アカウント] の設定でこの 3 つのアカウントすべてについて [連絡先を同期] を有効にしています。 + +

+

+ Emily Dickinson がブラウザのウィンドウを開き、Gmail に emily.dickinson@gmail.com としてログインし、[連絡先] を開いて「Thomas Higginson」を追加したとしましょう。 + +後日、彼女が Gmail に emilyd@gmail.com としてログインし、メールを「Thomas Higginson」宛てに送信すると、彼は自動的に連絡先として追加されます。 + +彼女はまた、Twitter で「colonel_tom」(Thomas Higginson の Twitter ID)をフォローしています。 + +

+

+ 連絡先プロバイダは、この操作の結果として次の 3 行の未加工連絡先を作成します。 +

+
    +
  1. + 「Thomas Higginson」の、emily.dickinson@gmail.com に関連付けられた未加工連絡先。 + ユーザー アカウントのタイプは Google です。 +
  2. +
  3. + 「Thomas Higginson」の、emilyd@gmail.com に関連付けられた新たな未加工連絡先。 + ユーザー アカウントのタイプはやはり Google です。名前が前の行とまったく同じなのに新たな未加工連絡先が作成されたのは、この個人が異なるユーザー アカウントに追加されたからです。 + + +
  4. +
  5. + 「Thomas Higginson」の、「belle_of_amherst」に関連付けられた未加工連絡先。ユーザー アカウントのタイプは Twitter です。 + +
  6. +
+

データ

+

+ 前にも説明したように、未加工連絡先のデータは未加工連絡先の _ID 値にリンクされている {@link android.provider.ContactsContract.Data} 行に格納されます。 + +これにより、1 つの未加工連絡先が同じタイプのデータ(メールアドレスや電話番号など)のインスタンスを複数持つことができます。 +たとえば、{@code emilyd@gmail.com} に対する「Thomas Higginson」(Google アカウント emilyd@gmail.com に関連付けられた、Thomas Higginson の未加工連絡先の行)には、thigg@gmail.com という個人用アドレスと thomas.higginson@gmail.com という仕事用アドレスがあり、連絡先プロバイダはこの 2 つのメールアドレスの行を格納し、両方とも同じ未加工連絡先にリンクします。 + + + + + +

+

+ 異なるタイプのデータが同じテーブルに格納されることに注目してください。表示名、電話番号、メール、住所、写真、ウェブサイトに関する詳細行はすべて、{@link android.provider.ContactsContract.Data} テーブルにあります。 + +これを管理しやすくするため、{@link android.provider.ContactsContract.Data} テーブルの一部の行には説明的な名前が、それ以外には一般的な名前がそれぞれ付いています。 + +説明的な名前が付いた列の内容は、行データのタイプによらず意味が同じなのに対し、一般的な名前が付いた列の内容は、データのタイプによって意味が異なります。 + + +

+

説明的な名前の列

+

+ 説明的な名前が付いた列の例をいくつか示します。 +

+
+
+ {@link android.provider.ContactsContract.Data#RAW_CONTACT_ID} +
+
+ このデータの、未加工連絡先の _ID 列の値。 +
+
+ {@link android.provider.ContactsContract.Data#MIMETYPE} +
+
+ カスタム MIME タイプで表現した、この行に格納されているデータのタイプ。連絡先プロバイダは、{@link android.provider.ContactsContract.CommonDataKinds} のサブクラスに定義されている MIME タイプを使用します。 + +これらの MIME タイプはオープンソースで、連絡先プロバイダと連携する任意のアプリケーションまたは同期アダプタで使用できます。 + +
+
+ {@link android.provider.ContactsContract.DataColumns#IS_PRIMARY} +
+
+ このタイプのデータ行が 1 つの未加工連絡先に対して複数発生する場合、{@link android.provider.ContactsContract.DataColumns#IS_PRIMARY} 列はそのタイプに対するプライマリ データを含むデータ行を示します。 + +たとえば、ユーザーがある連絡先の電話番号を長押しし、[デフォルトに設定] を選択すると、その番号を含む {@link android.provider.ContactsContract.Data} 行の {@link android.provider.ContactsContract.DataColumns#IS_PRIMARY} 列にゼロでない値が設定されます。 + + + + +
+
+

汎用名の列

+

+ 自由に使用できる DATA1DATA15 という 15 個の汎用列の他に、同期アダプタしか使用してはいけない SYNC1SYNC4 という 4 個の列があります。 + + +汎用名列の定数は、行に指定されているデータのタイプによらず、常に機能します。 + +

+

+ DATA1 列はインデックス付きです。連絡先プロバイダは常に、この列を、最も頻繁にクエリの対象となるとプロバイダが予想するデータ用の列として使用します。 +たとえば、メールの行では、この列には実際のメールアドレスが格納されます。 + +

+

+ DATA15 は慣例として、写真サムネイルのようなバイナリ ラージ オブジェクト(BLOB)データ用に予約されています。 + +

+

タイプ固有の列名

+

+ 特定タイプの行に含まれる列に対する作業をやりやすくするため、連絡先プロバイダではタイプ固有の列名定数もあり、たとえば {@link android.provider.ContactsContract.CommonDataKinds} のサブクラスに定義されています。 + +こうした定数は、同じ列名に異なる定数名を充てて、特定タイプの行に含まれるデータにアクセスしやすくしているだけのものです。 + + +

+

+ たとえば、{@link android.provider.ContactsContract.CommonDataKinds.Email} クラスには、MIME タイプが {@link android.provider.ContactsContract.CommonDataKinds.Email#CONTENT_ITEM_TYPE Email.CONTENT_ITEM_TYPE} である{@link android.provider.ContactsContract.Data} 行向けにタイプ固有の列名定数が定義されています。 + + + +このクラスには、メールアドレス 列用に {@link android.provider.ContactsContract.CommonDataKinds.Email#ADDRESS} という定数が含まれています。 + +{@link android.provider.ContactsContract.CommonDataKinds.Email#ADDRESS} の実際の値は「data1」で、これはこの列の汎用名と同じです。 + + +

+

+ 警告: 連絡先プロバイダにあらかじめ定義されている MIME タイプのどれかである行を使用している {@link android.provider.ContactsContract.Data} テーブルには、独自のカスタムデータを追加しないでください。 + +追加すると、データが失われたり、連絡先プロバイダが誤動作したりすることがあります。 +たとえば、メールアドレスではなくユーザー名が格納されている MIME タイプ {@link android.provider.ContactsContract.CommonDataKinds.Email#CONTENT_ITEM_TYPE Email.CONTENT_ITEM_TYPE} の行は、列 DATA1 には追加しないでください。 + + +一方、行に独自のカスタム MIME タイプを使用している場合は、独自のタイプ固有名を定義して列を自由に使用してかまいません。 + +

+

+ 図 2 に、説明的な名前の列とデータ列で {@link android.provider.ContactsContract.Data} 行がどう見えるか、そしてタイプ固有の列名が汎用列名をどう「オーバーレイ」するかを示します。 + + +

+How type-specific column names map to generic column names +

+ 図 2. タイプ固有の列名と汎用列名 +

+

タイプ固有列名が使用されるクラス

+

+ 表 2 に、最もよく用いられるタイプ固有列名クラスを示します。 +

+

+ 表 2. タイプ固有列名が使用されるクラス

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
対応クラスデータのタイプ
{@link android.provider.ContactsContract.CommonDataKinds.StructuredName}このデータ行に関連付けられている未加工連絡先の名前データ。1 つの未加工連絡先は、この行を 1 行だけ持ちます。
{@link android.provider.ContactsContract.CommonDataKinds.Photo}このデータ行に関連付けられている未加工連絡先のメインの写真。1 つの未加工連絡先は、この行を 1 行だけ持ちます。
{@link android.provider.ContactsContract.CommonDataKinds.Email}このデータ行に関連付けられている未加工連絡先のメールレアドレス。1 つの未加工連絡先は複数のメールアドレスを持つことができます。
{@link android.provider.ContactsContract.CommonDataKinds.StructuredPostal}このデータ行に関連付けられている未加工連絡先の住所。1 つの未加工連絡先は複数の住所を持つことができます。
{@link android.provider.ContactsContract.CommonDataKinds.GroupMembership}その未加工連絡先を連絡先プロバイダのグループのどれかにリンクする識別子。 + グループは、アカウント タイプとアカウント名のオプション機能です。詳しくは、連絡先グループをご覧ください。 + +
+

連絡先

+

+ 連絡先プロバイダは、すべてのアカウント タイプとアカウント名にわたって未加工連絡先の行を結び付けて 1 つの連絡先を形成します。 +これにより、1 人の人についてユーザーが集めた全データを表示したり変更したりしやすくなります。 +連絡先プロバイダは、新しい連絡先の行の作成と、既存の連絡先の行を使用した未加工連絡先の集約とを管理します。 +アプリケーションと同期アダプタは連絡先の追加はできず、連絡先の行の一部の列は読み取り専用です。 + +

+

+ 注: 連絡先を連絡先プロバイダに {@link android.content.ContentResolver#insert(Uri,ContentValues) insert()} を使用して追加しようとすると、{@link java.lang.UnsupportedOperationException} 例外が発生します。 + +「読み取り専用」になっている列をアップデートしようとしても、そのアップデートは無視されます。 + +

+

+ 連絡先プロバイダは、既存の連絡先と一致しない新しい未加工連絡先が追加されると、それに対して新しい連絡先を作成します。 +また、既存の未加工連絡先のデータが変更され、それまで関連付けられていた連絡先と一致しなくなった場合にも、同じ処理がなされます。 + +アプリケーションまたは同期アダプタが、既存の連絡先と一致する新しい未加工連絡先を作成すると、その新しい未加工連絡先は既存の連絡先に集約されます。 + + +

+

+ 連絡先プロバイダは、連絡先の行を未加工連絡先とリンクするのに、{@link android.provider.ContactsContract.Contacts Contacts} テーブルの _ID 列を使用します。 + +未加工連絡先テーブル {@link android.provider.ContactsContract.RawContacts} の CONTACT_ID 列には、未加工連絡先の各行に関連付けられている連絡先の行の _ID 値が格納されます。 + + +

+

+ {@link android.provider.ContactsContract.Contacts} テーブルには列 {@code android.provider.ContactsContract.ContactsColumns#LOOKUP_KEY} もあり、こちらは連絡先の行への「永久」リンクです。 + +連絡先プロバイダは連絡先を自動的に管理するため、集約や同期が行われると、それに応じて連絡先の行の {@code android.provider.BaseColumns#_ID} 値が変更される場合があります。 + +この処理が行われても、コンテンツ URI {@link android.provider.ContactsContract.Contacts#CONTENT_LOOKUP_URI} と連絡先の {@code android.provider.ContactsContract.ContactsColumns#LOOKUP_KEY} の組み合わせは、引き続きその連絡先の行を指すため、{@code android.provider.ContactsContract.ContactsColumns#LOOKUP_KEY} を使用して「お気に入り」の連絡先へのリンクを管理するなどができます。 + + + + +この列には、{@code android.provider.BaseColumns#_ID} 列の形式と関係のない独自の形式があります。 + +

+

+ 図 3 に、3 つのテーブルの相互関係を示します。 +

+Contacts provider main tables +

+ 図 3. Contacts、RawContacts、Data の各テーブル間の関係。 +

+

同期アダプタからのデータ

+

+ ユーザーは端末に連絡先データを直接入力しますが、データはウェブサービスから同期アダプタを経由して連絡先プロバイダにも流れます。同期アダプタは、端末とサービスの間のデータ転送を自動化します。 + +同期アダフタはシステムの管理下でバックグラウンドで実行され、{@link android.content.ContentResolver} のメソッドを呼び出してデータを管理します。 + + +

+

+ Android では、同期アダプタと連携するウェブサービスをアカウント タイプで識別します。 + 各同期アダプタが扱うアカウント タイプは 1 つですが、そのタイプのアカウント名を複数サポートできます。 +アカウント名とアカウント タイプについては、未加工連絡先データのソースで簡単に説明しています。 +次に、アカウント タイプとアカウント名が同期アダプタやサービスとどのような関係にあるかを詳しく説明します。 + +

+
+
+ アカウント タイプ +
+
+ ユーザーがデータを格納しているサービスを示します。ほとんどの場合、ユーザーにはサービスに対する認証が求められます。 +たとえば、Google Contacts はアカウント タイプの 1 つで、コード google.com で識別されます。 +この値は、{@link android.accounts.AccountManager} によって使用されるアカウント タイプに対応します。 + +
+
+ アカウント名 +
+
+ あるアカウント タイプで使用する特定のアカウントまたはログインを示します。Google Contacts アカウントは Google アカウントと同じで、アカウント名としてメールアドレスを使用します。 + + 他のサービスでは、1 語のユーザー名や数字の ID が使われていることもあります。 +
+
+

+ アカウント タイプは、一意である必要はありません。1 人のユーザーが複数の Google Contacts アカウントを設定し、それぞれのデータを連絡先プロバイダにダウンロードする、ということが可能です。このような使い方は、そのユーザーが個人用のアカウント名で私用の連絡先を、仕事用のアカウント名で仕事用の連絡先を管理している場合にありえます。 + +アカウント名は、普通は一意です。 +この 2 つを組み合わせて、連絡先プロバイダと外部サービスとの間のある決まったデータフローを識別します。 + +

+

+ 独自サービスのデータを連絡先プロバイダに転送する場合は、独自の同期アダプタを作成する必要があります。 +詳しくは、連絡先プロバイダの同期アダプタをご覧ください。 + +

+

+ 図 4 に、人に関するデータの流れにおける連絡先プロバイダの位置付けを示します。 +右から 2 列目の各ボックス内のアダプタには、そのアダプタのアカウント タイプが示されています。 +

+Flow of data about people +

+ 図 4. 連絡先プロバイダに絡んだデータフロー。 +

+

必要なパーミッション

+

+ 連絡先プロバイダにアクセスするアプリケーションは、次のパーミッションを要求する必要があります。 + +

+
+
1 つ以上のテーブルに対する読み取りアクセス
+
+ {@link android.Manifest.permission#READ_CONTACTS}。AndroidManifest.xml + <uses-permission> 要素を使用して <uses-permission android:name="android.permission.READ_CONTACTS"> のように指定します。 + + + +
+
1 つ以上のテーブルに対する書き込みアクセス
+
+ {@link android.Manifest.permission#WRITE_CONTACTS}。AndroidManifest.xml + <uses-permission> 要素を使用して <uses-permission android:name="android.permission.WRITE_CONTACTS"> のように指定します。 + + + +
+
+

+ これらのパーミッションは、ユーザー プロファイル データにまでは拡張されません。ユーザー プロファイルとそれに必要なパーミッションについては、次のセクションであるユーザー プロファイルで説明しています。 + + +

+

+ ユーザーの連絡先データは個人的で秘密性の高い情報であることを再度ご確認ください。ユーザーはプライバシーに敏感であり、アプリケーションが自分や自分の連絡先に関するデータを集めることを望みません。 + + 連絡先データにアクセスするためのパーミッションが必要な理由が明らかでないと、ユーザーがアプリケーションを低く評価したりインストールを拒否したりすることがあります。 + +

+

ユーザー プロファイル

+

+ {@link android.provider.ContactsContract.Contacts} テーブルには、その端末のユーザーのプロファイル データを格納している行が 1 行あります。 +このデータが記述しているのは端末の user であって、そのユーザーの連絡先ではありません。 +プロファイルの連絡先の行は、プロファイルを使用する各システムの未加工連絡先の行にリンクされています。 + + プロファイルの未加工連絡先の各行は、複数のデータ行を持つことができます。ユーザー プロファイルにアクセスするための定数は、{@link android.provider.ContactsContract.Profile} クラスに用意されています。 + +

+

+ ユーザー プロファイルにアクセスするには、特別なパーミッションが必要です。読み取りと書き込みに必要な {@link android.Manifest.permission#READ_CONTACTS} パーミッションと {@link android.Manifest.permission#WRITE_CONTACTS} パーミッションの他に、ユーザー プロファイルに対する読み取りと書き込みのために {@code android.Manifest.permission#READ_PROFILE} パーミッションと {@code android.Manifest.permission#WRITE_PROFILE} パーミッションがそれぞれ必要です。 + + + + + +

+

+ ユーザーのプロファイルは秘密性の高い情報であることを再度ご確認ください。パーミッション {@code android.Manifest.permission#READ_PROFILE} を使用すると、端末ユーザーの個人識別データにアクセスできます。 + +アプリケーションの説明には、ユーザー プロファイルへのアクセス パーミッションが必要な理由を必ず記載してください。 + +

+

+ ユーザーのプロファイルが格納された連絡先の行を取得するには、{@link android.content.ContentResolver#query(Uri,String[], String, String[], String) ContentResolver.query()} を呼び出します。 + +コンテンツ URI を {@link android.provider.ContactsContract.Profile#CONTENT_URI} に設定し、選択条件は何も指定しません。 + +このコンテンツ URI は、そのプロファイルの未加工連絡先やデータを取得するためのベース URI としても使用できます。 +たとえば、次のスニペットは指定されたプロファイルのデータを取得します。 +

+
+// Sets the columns to retrieve for the user profile
+mProjection = new String[]
+    {
+        Profile._ID,
+        Profile.DISPLAY_NAME_PRIMARY,
+        Profile.LOOKUP_KEY,
+        Profile.PHOTO_THUMBNAIL_URI
+    };
+
+// Retrieves the profile from the Contacts Provider
+mProfileCursor =
+        getContentResolver().query(
+                Profile.CONTENT_URI,
+                mProjection ,
+                null,
+                null,
+                null);
+
+

+ 注: 連絡先の行を複数取得する場合、そのなかの 1 つがユーザー プロファイルかどうかを確認するには、行の {@link android.provider.ContactsContract.ContactsColumns#IS_USER_PROFILE} 列をテストします。 + +その連絡先がユーザー プロファイルであれば、この列は「1」に設定されています。 + +

+

連絡先プロバイダのメタデータ

+

+ 連絡先プロバイダは、連絡先データの状態を継続的に追跡するためのデータをリポジトリで管理します。 +リポジトリに関するこのメタデータは、RawContacts、Data、Contacts の各テーブルの行、{@link android.provider.ContactsContract.Settings} テーブル、{@link android.provider.ContactsContract.SyncState} テーブルなど、さまざまな場所に格納されています。 + + +次の表に、各メタデータの効果を示します。 + +

+

+ 表 3. 連絡先プロバイダに用意されているメタデータ

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
テーブル意味
{@link android.provider.ContactsContract.RawContacts}{@link android.provider.ContactsContract.SyncColumns#DIRTY}「0」 - 前回の同期以降、変更はありません。 + 端末上で変更があり、サーバーと同期する必要がある未加工連絡先をマークします。 +Android アプリケーションが行をアップデートすると、連絡先プロバイダによって値が自動的に設定されます。 + +

+ 未加工連絡先やデータのテーブルを変更する同期アダプタは、使用するコンテンツ URI の末尾に文字列 {@link android.provider.ContactsContract#CALLER_IS_SYNCADAPTER} を必ず追加してください。 + +これにより、プロバイダが行をダーティとマークするのを防ぐことができます。 + こうしないと、同期アダプタによる変更がローカルな変更と認識され、変更のソースがサーバーであるにもかかわらず、その変更がサーバーに送信されます。 + +

+
「1」 - 前回の同期以降に変更があり、サーバーへの同期が必要です。
{@link android.provider.ContactsContract.RawContacts}{@link android.provider.ContactsContract.SyncColumns#VERSION}この行のバージョン番号。 + この行またはその関連データが変更されるたび、連絡先プロバイダがこの値を自動的にインクリメントします。 + +
{@link android.provider.ContactsContract.Data}{@link android.provider.ContactsContract.DataColumns#DATA_VERSION}この行のバージョン番号。 + このデータ行が変更されるたび、連絡先プロバイダがこの値を自動的にインクリメントします。 + +
{@link android.provider.ContactsContract.RawContacts}{@link android.provider.ContactsContract.SyncColumns#SOURCE_ID} + この未加工連絡先をそれが作成されたアカウントと一意に結び付ける文字列値。 + + + 同期アダプタが新しい未加工連絡先を作成するたび、この列はその未加工連絡先に対するサーバーの一意の ID に設定される必要があります。 +Android アプリケーションが新しい未加工連絡先を作成した場合、そのアプリケーションはこの列を空欄のままにする必要があります。 +同期アダプタはこれを確認して、サーバー上に新しい未加工連絡先を作成し、{@link android.provider.ContactsContract.SyncColumns#SOURCE_ID} の値を取得します。 + + +

+ 特に、ソース ID はアカウント タイプごとに一意で、同期中に安定している必要があります。 + +

+
    +
  • + Unique: アカウントの各未加工連絡先には独自のソース ID が必要です。これを強制しないと、連絡先アプリケーションに問題を引き起こすことになります。 + + 同じアカウント タイプに対する 2 つの未加工連絡先が、同じソース ID を持つことがありえます。 +たとえば、アカウント {@code emily.dickinson@gmail.com} の未加工連絡先「Thomas Higginson」は、アカウント {@code emilyd@gmail.com} の未加工連絡先「Thomas Higginson」と同じソース ID を持つことができます。 + + + +
  • +
  • + Stable: ソース ID は、未加工連絡先のオンライン サービス データにおいて変わらない部分です。 +たとえば、ユーザーが [アプリ] 設定から [連絡先ストレージ] を消去し、再同期したとしても、復元された未加工連絡先のソース ID は以前と同じになります。 + +これを強制しないと、ショートカットが機能しなくなります。 + +
  • +
+
{@link android.provider.ContactsContract.Groups}{@link android.provider.ContactsContract.GroupsColumns#GROUP_VISIBLE}「0」 - このグループに属する連絡先が Android アプリケーションの UI に表示されなくなります。 + この列は、ユーザーが特定のグループに属する連絡先を非表示にすることを許可するサーバーに対して互換性を確保するために用意されています。 + +
「1」 - このグループに属する連絡先を Android アプリケーションの UI に表示できます。
{@link android.provider.ContactsContract.Settings} + {@link android.provider.ContactsContract.SettingsColumns#UNGROUPED_VISIBLE} + 「0」 - このアカウントとアカウント タイプについて、グループに属さない連絡先は Android アプリケーションの UI に表示されなくなります。 + + + デフォルトでは、グループに属する未加工連絡先がないなら連絡先は表示されません(未加工連絡先のグループ メンバーシップは、{@link android.provider.ContactsContract.Data} テーブルの 1 つないし複数の {@link android.provider.ContactsContract.CommonDataKinds.GroupMembership} 行によって示されます)。 + + + + あるアカウントとアカウント タイプについて、{@link android.provider.ContactsContract.Settings} テーブルの行でこのフラグを設定すると、グループのない連絡先を強制的に表示できます。 + + このフラグの用途の 1 つとして、グループを使用しないサーバーから連絡先を取得して表示することが考えられます。 +
+ 「0」 - このアカウントとアカウント タイプについて、グループに属さない連絡先が Android アプリケーションの UI に表示されます。 + +
{@link android.provider.ContactsContract.SyncState}(すべて) + このテーブルを使用して、同期アダプタのメタデータを格納します。 + + このテーブルを使用すると、同期状態などの同期関連データを永続的に端末上に格納できます。 + +
+

連絡先プロバイダへのアクセス

+

+ このセクションでは、連絡先プロバイダからのデータにアクセスするためのガイドラインについて、以下に注目して説明します。 + +

+ +

+ 同期アダプタからの変更については、連絡先プロバイダの同期アダプタでも詳しく説明しています。 + +

+

エンティティのクエリ

+

+ 連絡先プロバイダのテーブルは階層構造になっており、ある行とその「子」の行すべてを取得すると便利なことがよくあります。 +たとえば、ある人に関するすべての情報を表示するために、{@link android.provider.ContactsContract.Contacts} 行 1 行に対するすべての {@link android.provider.ContactsContract.RawContacts} 行や、{@link android.provider.ContactsContract.RawContacts} 行 1 行に対するすべての {@link android.provider.ContactsContract.CommonDataKinds.Email} 行を取得することが考えられます。 + + + + +こうした処理をやりやすくするため、連絡先プロバイダにはエンティティ構造が用意されています。これは、テーブル間でのデータベースの和集合のように機能します。 + + +

+

+ 1 つのエンティティは、ある親テーブルとその子テーブルから選ばれた列からなるテーブルのようなものです。 + エンティティをクエリする場合は、そのエンティティで使用できる列に基づいてプロジェクションと検索の条件を指定します。 +その結果が {@link android.database.Cursor} で、取得された各子テーブル行につき 1 行を含みます。 +たとえば、ある連絡先名と、その名前のすべての未加工連絡先の {@link android.provider.ContactsContract.CommonDataKinds.Email} 行について、{@link android.provider.ContactsContract.Contacts.Entity} をクエリすると、各 {@link android.provider.ContactsContract.CommonDataKinds.Email} 行につき 1 行を含む {@link android.database.Cursor} が得られます。 + + + + +

+

+ エンティティにより、クエリが簡素化されます。エンティティを使用することで、ある連絡先または未加工連絡先の連絡先データをすべて取得できるため、まず親テーブルにクエリして ID を取得し、次にその ID 使用して子テーブルにクエリする必要がありません。また、連絡先プロバイダは、エンティティに対するクエリを 1 回のトランザクションで処理することから、取得されたデータの内部的な整合性が保証されます。 + + + + +

+

+ 注: エンティティには通常、親テーブルと子テーブルのすべての列が含まれるわけではありません。 +そのエンティティの列名定数のリストにない列名に対して作業しようとすると、{@link java.lang.Exception} が発生します。 + +

+

+ 次のスニペットは、ある連絡先のすべての未加工連絡先を取得する方法を示しています。このスニペットは、「メイン」と「詳細」という 2 つのアクティビティを持つ、もっと大きなアプリケーションの一部です。 +メイン アクティビティは、連絡先の行の一覧を示します。ユーザーが 1 つを選択すると、このアクティビティはその ID を詳細アクティビティに送ります。 + +詳細アクティビティは {@link android.provider.ContactsContract.Contacts.Entity} を使用して、選択した連絡先と関連付けられているすべての未加工連絡先から取得したすべてのデータ行を示します。 + + +

+

+ このスニペットは「詳細」アクティビティからの抜粋です。 +

+
+...
+    /*
+     * Appends the entity path to the URI. In the case of the Contacts Provider, the
+     * expected URI is content://com.google.contacts/#/entity (# is the ID value).
+     */
+    mContactUri = Uri.withAppendedPath(
+            mContactUri,
+            ContactsContract.Contacts.Entity.CONTENT_DIRECTORY);
+
+    // Initializes the loader identified by LOADER_ID.
+    getLoaderManager().initLoader(
+            LOADER_ID,  // The identifier of the loader to initialize
+            null,       // Arguments for the loader (in this case, none)
+            this);      // The context of the activity
+
+    // Creates a new cursor adapter to attach to the list view
+    mCursorAdapter = new SimpleCursorAdapter(
+            this,                        // the context of the activity
+            R.layout.detail_list_item,   // the view item containing the detail widgets
+            mCursor,                     // the backing cursor
+            mFromColumns,                // the columns in the cursor that provide the data
+            mToViews,                    // the views in the view item that display the data
+            0);                          // flags
+
+    // Sets the ListView's backing adapter.
+    mRawContactList.setAdapter(mCursorAdapter);
+...
+@Override
+public Loader<Cursor> onCreateLoader(int id, Bundle args) {
+
+    /*
+     * Sets the columns to retrieve.
+     * RAW_CONTACT_ID is included to identify the raw contact associated with the data row.
+     * DATA1 contains the first column in the data row (usually the most important one).
+     * MIMETYPE indicates the type of data in the data row.
+     */
+    String[] projection =
+        {
+            ContactsContract.Contacts.Entity.RAW_CONTACT_ID,
+            ContactsContract.Contacts.Entity.DATA1,
+            ContactsContract.Contacts.Entity.MIMETYPE
+        };
+
+    /*
+     * Sorts the retrieved cursor by raw contact id, to keep all data rows for a single raw
+     * contact collated together.
+     */
+    String sortOrder =
+            ContactsContract.Contacts.Entity.RAW_CONTACT_ID +
+            " ASC";
+
+    /*
+     * Returns a new CursorLoader. The arguments are similar to
+     * ContentResolver.query(), except for the Context argument, which supplies the location of
+     * the ContentResolver to use.
+     */
+    return new CursorLoader(
+            getApplicationContext(),  // The activity's context
+            mContactUri,              // The entity content URI for a single contact
+            projection,               // The columns to retrieve
+            null,                     // Retrieve all the raw contacts and their data rows.
+            null,                     //
+            sortOrder);               // Sort by the raw contact ID.
+}
+
+

+ 読み込みが完了すると、{@link android.app.LoaderManager} は {@link android.app.LoaderManager.LoaderCallbacks#onLoadFinished(Loader, D) onLoadFinished()} に対するコールバックを起動します。 + +このメソッドへの入力引数の 1 つは、クエリの結果を含む {@link android.database.Cursor} です。 +独自アプリでは、データをこの {@link android.database.Cursor} から取得して表示したりさらに処理したりできます。 + +

+

バッチ変更

+

+ 可能であれば必ず、連絡先プロバイダのデータを「バッチモード」で挿入、アップデート、削除してください。そのためには、{@link android.content.ContentProviderOperation} オブジェクトの {@link java.util.ArrayList} を作成し、{@link android.content.ContentResolver#applyBatch(String, ArrayList) applyBatch()} を呼び出します。 + + +連絡先プロバイダはすべての操作を 1 つのトランザクションの 1 つの {@link android.content.ContentResolver#applyBatch(String, ArrayList) applyBatch()} で行うため、変更内容が連絡先リポジトリで不整合のままになることは決してありません。 + + + +また、バッチ変更では、未加工連絡先とその詳細データを同時に挿入しやすくなっています。 + +

+

+ 注: 1 つの未加工連絡先を変更する場合は、変更を独自アプリ内で処理するのではなく、インテントを端末の連絡先アプリケーションに送信することを検討してください。この方法については、インテントを使用した取得と変更で詳しく説明しています。 + + + +

+

明け渡し点

+

+ 多数の操作を伴うバッチ変更は、他のプロセスをブロックしてユーザーにとっての全体的な使用感を悪化させかねません。 +意図したすべての変更をできるだけ少ない個別リストに整理するとともに、それらがシステムをブロックしないようにするために、1 つまたは複数の操作に明け渡し点を設定してください。 + + + 明け渡し点の実体は、{@link android.content.ContentProviderOperation#isYieldAllowed()} の値が true に設定された {@link android.content.ContentProviderOperation} オブジェクトです。 + +連絡先プロバイダが明け渡し点に遭遇すると、作業を一時中断して他のプロセスを実行させ、現在のトランザクションをクローズします。 +作業を再開した連絡先プロバイダは、{@link java.util.ArrayList} に含まれている次の操作を行い、新しいトランザクションを始めます。 + + +

+

+ 明け渡し点により、{@link android.content.ContentResolver#applyBatch(String, ArrayList) applyBatch()} の呼び出し 1 回につき複数のトランザクションが発生します。 +そのため、明け渡し点は 1 組の関連する行への最後の操作に設定してください。 + + たとえば、未加工連絡先の行とその関連データ行を追加する一連の操作の最後に、あるいは 1 人の連絡先に関連する 1 組の行に対する最後の操作に、明け渡し点を設定します。 + + +

+

+ 明け渡し点は、アトミック操作の単位でもあります。2 つの明け渡し点間のすべてのアクセスは、1 つのまとまりとして成功または失敗します。 +明け渡し点を設定しない場合、最小のアトミック操作は操作のバッチ全体になります。 +明け渡し点を使用すると、操作がシステムのパフォーマンスを低下させるのを防ぐと同時に、操作の一部分が確実にアトミックになります。 + + +

+

変更の後方参照

+

+ 新しい未加工連絡先の行とその関連データの行を 1 組の {@link android.content.ContentProviderOperation} オブジェクトとして挿入している場合は、データの行を未加工連絡先の行にリンクする必要があります。そのためには、未加工連絡先の {@code android.provider.BaseColumns#_ID} 値を {@link android.provider.ContactsContract.DataColumns#RAW_CONTACT_ID} 値として挿入します。 + + + +ただし、この値は、データの行のために{@link android.content.ContentProviderOperation} を作成している間は使用できません。未加工連絡先に {@link android.content.ContentProviderOperation} 操作がまだ適用されていないからです。 + + +これを回避するため、{@link android.content.ContentProviderOperation.Builder} クラスにはメソッド {@link android.content.ContentProviderOperation.Builder#withValueBackReference(String, int) withValueBackReference()} が用意されています。 + + + このメソッドを使用すると、前の操作の結果を使用して列を挿入または変更できます。 + +

+

+ {@link android.content.ContentProviderOperation.Builder#withValueBackReference(String, int) withValueBackReference()} メソッドには引数が 2 つあります。 + +

+
+
+ key +
+
+ キーと値のペアのキーです。この引数の値は、変更中のテーブルに含まれる列であることが必要です。 + +
+
+ previousResult +
+
+ {@link android.content.ContentResolver#applyBatch(String, ArrayList) applyBatch()} から取得した {@link android.content.ContentProviderResult} オブジェクトの配列に含まれる値の、0 ベースのインデックスです。 + +バッチ操作が適用されると、各操作の結果は結果用の中間配列に格納されます。 + +previousResult 値はそうした結果の 1 つのインデックスで、key 値を使用して取得され、格納されます。 + +これにより、新しい未加工連絡先レコードを挿入してその {@code android.provider.BaseColumns#_ID} 値に戻り、次に {@link android.provider.ContactsContract.Data} 行を追加するときにその値を「後方参照」できます。 + + +

+ {@link android.content.ContentResolver#applyBatch(String, ArrayList) applyBatch()} を初めて呼び出すと、指定した {@link android.content.ContentProviderOperation} オブジェクトの {@link java.util.ArrayList} と同じサイズを使用して、結果配列全体が作成されます。 + + +ただし、結果配列に含まれるすべての要素は null に設定され、そのためまだ適用されていない操作から結果を後方参照しようとすると {@link android.content.ContentProviderOperation.Builder#withValueBackReference(String, int) withValueBackReference()} が {@link java.lang.Exception} をスローします。 + + + + + +

+
+
+

+ 次のスニペットは、新しい未加工連絡先とデータをバッチで挿入する方法を示しています。その中には、明け渡し点を指定し、後方参照を使用するコードが含まれています。 +これらのスニペットは、 + Contact Manager サンプル アプリケーションの ContactAdder クラスの一部である createContacEntry() メソッドの拡張版です。 + + + +

+

+ 最初のスニペットは、連絡先データを UI から取得します。この時点で、ユーザーは新しい未加工連絡先の追加先となるアカウントを既に選択してあります。 + +

+
+// Creates a contact entry from the current UI values, using the currently-selected account.
+protected void createContactEntry() {
+    /*
+     * Gets values from the UI
+     */
+    String name = mContactNameEditText.getText().toString();
+    String phone = mContactPhoneEditText.getText().toString();
+    String email = mContactEmailEditText.getText().toString();
+
+    int phoneType = mContactPhoneTypes.get(
+            mContactPhoneTypeSpinner.getSelectedItemPosition());
+
+    int emailType = mContactEmailTypes.get(
+            mContactEmailTypeSpinner.getSelectedItemPosition());
+
+

+ 次のスニペットは、未加工連絡先の行を {@link android.provider.ContactsContract.RawContacts} テーブルに挿入する操作を作成します。 + +

+
+    /*
+     * Prepares the batch operation for inserting a new raw contact and its data. Even if
+     * the Contacts Provider does not have any data for this person, you can't add a Contact,
+     * only a raw contact. The Contacts Provider will then add a Contact automatically.
+     */
+
+     // Creates a new array of ContentProviderOperation objects.
+    ArrayList<ContentProviderOperation> ops =
+            new ArrayList<ContentProviderOperation>();
+
+    /*
+     * Creates a new raw contact with its account type (server type) and account name
+     * (user's account). Remember that the display name is not stored in this row, but in a
+     * StructuredName data row. No other data is required.
+     */
+    ContentProviderOperation.Builder op =
+            ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI)
+            .withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, mSelectedAccount.getType())
+            .withValue(ContactsContract.RawContacts.ACCOUNT_NAME, mSelectedAccount.getName());
+
+    // Builds the operation and adds it to the array of operations
+    ops.add(op.build());
+
+

+ 次に、表示名、電話、メールのデータ行を作成します。 +

+

+ 操作の各ビルダー オブジェクトは、{@link android.content.ContentProviderOperation.Builder#withValueBackReference(String, int) withValueBackReference()} を使用して {@link android.provider.ContactsContract.DataColumns#RAW_CONTACT_ID} を取得します。 + + +この参照が最初の操作からの {@link android.content.ContentProviderResult} オブジェクトを後方参照しており、それが未加工連絡先の行を追加し、新しい {@code android.provider.BaseColumns#_ID} 値を返します。 + + +その結果、各データ行はその {@link android.provider.ContactsContract.DataColumns#RAW_CONTACT_ID} によって、属する新しい {@link android.provider.ContactsContract.RawContacts} 行に自動的にリンクされます。 + + +

+

+ メール行を追加する {@link android.content.ContentProviderOperation.Builder} オブジェクトは、明け渡し点を設定する {@link android.content.ContentProviderOperation.Builder#withYieldAllowed(boolean) withYieldAllowed()} でフラグ付けされます。 + + +

+
+    // Creates the display name for the new raw contact, as a StructuredName data row.
+    op =
+            ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
+            /*
+             * withValueBackReference sets the value of the first argument to the value of
+             * the ContentProviderResult indexed by the second argument. In this particular
+             * call, the raw contact ID column of the StructuredName data row is set to the
+             * value of the result returned by the first operation, which is the one that
+             * actually adds the raw contact row.
+             */
+            .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
+
+            // Sets the data row's MIME type to StructuredName
+            .withValue(ContactsContract.Data.MIMETYPE,
+                    ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE)
+
+            // Sets the data row's display name to the name in the UI.
+            .withValue(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, name);
+
+    // Builds the operation and adds it to the array of operations
+    ops.add(op.build());
+
+    // Inserts the specified phone number and type as a Phone data row
+    op =
+            ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
+            /*
+             * Sets the value of the raw contact id column to the new raw contact ID returned
+             * by the first operation in the batch.
+             */
+            .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
+
+            // Sets the data row's MIME type to Phone
+            .withValue(ContactsContract.Data.MIMETYPE,
+                    ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE)
+
+            // Sets the phone number and type
+            .withValue(ContactsContract.CommonDataKinds.Phone.NUMBER, phone)
+            .withValue(ContactsContract.CommonDataKinds.Phone.TYPE, phoneType);
+
+    // Builds the operation and adds it to the array of operations
+    ops.add(op.build());
+
+    // Inserts the specified email and type as a Phone data row
+    op =
+            ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
+            /*
+             * Sets the value of the raw contact id column to the new raw contact ID returned
+             * by the first operation in the batch.
+             */
+            .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
+
+            // Sets the data row's MIME type to Email
+            .withValue(ContactsContract.Data.MIMETYPE,
+                    ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE)
+
+            // Sets the email address and type
+            .withValue(ContactsContract.CommonDataKinds.Email.ADDRESS, email)
+            .withValue(ContactsContract.CommonDataKinds.Email.TYPE, emailType);
+
+    /*
+     * Demonstrates a yield point. At the end of this insert, the batch operation's thread
+     * will yield priority to other threads. Use after every set of operations that affect a
+     * single contact, to avoid degrading performance.
+     */
+    op.withYieldAllowed(true);
+
+    // Builds the operation and adds it to the array of operations
+    ops.add(op.build());
+
+

+ 最後のスニペットは、新しい未加工連絡先とデータ行を挿入する {@link android.content.ContentResolver#applyBatch(String, ArrayList) applyBatch()} を呼び出しています。 + + +

+
+    // Ask the Contacts Provider to create a new contact
+    Log.d(TAG,"Selected account: " + mSelectedAccount.getName() + " (" +
+            mSelectedAccount.getType() + ")");
+    Log.d(TAG,"Creating contact: " + name);
+
+    /*
+     * Applies the array of ContentProviderOperation objects in batch. The results are
+     * discarded.
+     */
+    try {
+
+            getContentResolver().applyBatch(ContactsContract.AUTHORITY, ops);
+    } catch (Exception e) {
+
+            // Display a warning
+            Context ctx = getApplicationContext();
+
+            CharSequence txt = getString(R.string.contactCreationFailure);
+            int duration = Toast.LENGTH_SHORT;
+            Toast toast = Toast.makeText(ctx, txt, duration);
+            toast.show();
+
+            // Log exception
+            Log.e(TAG, "Exception encountered while inserting contact: " + e);
+    }
+}
+
+

+ バッチ処理を使用すると、楽観的並行性制御を実装できます。これは、背後のリポジトリをロックする必要なく変更トランザクションを適用する手法です。 + + この手法を使用するには、トランザクションを適用してから、同時に行うことのできる可能性のある他の変更をチェックします。 +一貫性のない変更が発生していることがわかった場合は、トランザクションをロールバックしてやり直します。 + +

+

+ 楽観的並行性制御は、一度に使用するユーザーが 1 人でデータリポジトリへの同時アクセスがまれなモバイル端末に便利です。 +ロックが使用されないため、ロックの設定や他のトランザクションによるロックの解放待ちで時間を無駄にしません。 + +

+

+ {@link android.provider.ContactsContract.RawContacts} 行を 1 行更新している間に楽観的並行性制御を使用する方法は次のとおりです。 + +

+
    +
  1. + 取得する他のデータとともに、未加工連絡先の {@link android.provider.ContactsContract.SyncColumns#VERSION} 列を取得します。 + +
  2. +
  3. + 制約を強制するのに適した {@link android.content.ContentProviderOperation.Builder} オブジェクトを、メソッド {@link android.content.ContentProviderOperation#newAssertQuery(Uri)} を使用して作成します。 + +コンテンツ URI に、未加工連絡先の {@code android.provider.BaseColumns#_ID} が末尾に追加された {@link android.provider.ContactsContract.RawContacts#CONTENT_URI RawContacts.CONTENT_URI} を使用します。 + + + +
  4. +
  5. + {@link android.content.ContentProviderOperation.Builder} オブジェクトに対し、{@link android.content.ContentProviderOperation.Builder#withValue(String, Object) withValue()} を呼び出して、{@link android.provider.ContactsContract.SyncColumns#VERSION} 列と取得したばかりのバージョン番号を比較します。 + + + +
  6. +
  7. + 同じ {@link android.content.ContentProviderOperation.Builder} に対し、{@link android.content.ContentProviderOperation.Builder#withExpectedCount(int) withExpectedCount()} を呼び出して、このアサーションで 1 行だけがテストされるようにします。 + + +
  8. +
  9. + {@link android.content.ContentProviderOperation.Builder#build()} を呼び出して {@link android.content.ContentProviderOperation} オブジェクトを作成し、次のこのオブジェクトを {@link android.content.ContentResolver#applyBatch(String, ArrayList) applyBatch()} に渡す {@link java.util.ArrayList} に先頭オブジェクトとして追加します。 + + + +
  10. +
  11. + バッチトランザクションを適用します。 +
  12. +
+

+ 目的の未加工連絡先の行が、行の読み込みからその変更の試行までの間に別の操作によってアップデートされた場合、{@link android.content.ContentProviderOperation} の「アサート」が失敗し、操作のバッチ全体がバックアウトされます。 + +バックアウトが終わったら、バッチをやり直すこと、または他のアクションを行うことができます。 + +

+

+ 次のスニペットは、{@link android.content.CursorLoader} を使用して未加工連絡先を 1 つクエリした後に、{@link android.content.ContentProviderOperation} を「アサート」します。 + + +

+
+/*
+ * The application uses CursorLoader to query the raw contacts table. The system calls this method
+ * when the load is finished.
+ */
+public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
+
+    // Gets the raw contact's _ID and VERSION values
+    mRawContactID = cursor.getLong(cursor.getColumnIndex(BaseColumns._ID));
+    mVersion = cursor.getInt(cursor.getColumnIndex(SyncColumns.VERSION));
+}
+
+...
+
+// Sets up a Uri for the assert operation
+Uri rawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, mRawContactID);
+
+// Creates a builder for the assert operation
+ContentProviderOperation.Builder assertOp = ContentProviderOperation.netAssertQuery(rawContactUri);
+
+// Adds the assertions to the assert operation: checks the version and count of rows tested
+assertOp.withValue(SyncColumns.VERSION, mVersion);
+assertOp.withExpectedCount(1);
+
+// Creates an ArrayList to hold the ContentProviderOperation objects
+ArrayList ops = new ArrayList<ContentProviderOperationg>;
+
+ops.add(assertOp.build());
+
+// You would add the rest of your batch operations to "ops" here
+
+...
+
+// Applies the batch. If the assert fails, an Exception is thrown
+try
+    {
+        ContentProviderResult[] results =
+                getContentResolver().applyBatch(AUTHORITY, ops);
+
+    } catch (OperationApplicationException e) {
+
+        // Actions you want to take if the assert operation fails go here
+    }
+
+

インテントを使用した取得と変更

+

+ インテントを端末の連絡先アプリケーションに送信すると、連絡先プロバイダに間接的にアクセスできます。 +インテントが端末の連絡先アプリケーション UI を起動し、ユーザーはそこで連絡先関連の作業を行えます。 +このタイプのアクセスで、ユーザーは次の作業ができます。 +

+

+ ユーザーがデータを挿入またはアップデートしている場合は、まずデータを収集し、次にそれをインテントの一部として送信できます。 + +

+

+ インテントを使用して端末の連絡先アプリケーション経由で連絡先プロバイダにアクセスする場合、連絡先プロバイダにアクセスするための独自の UI やコードを作成する必要はありません。 +また、連絡先プロバイダに対する読み取りや書き込みのパーミッションを要求する必要もありません。 +端末の連絡先アプリケーションは、連絡先に対する読み取りパーミッションをデリゲートできます。また、連絡先プロバイダへの変更を他のアプリケーション経由で行っていることから、書き込みパーミッションは不要です。 + + +

+

+ プロバイダにアクセスするためのインテントを送信する汎用プロセスについては、コンテンツ プロバイダの基本のセクション「インテント経由のデータアクセス」で詳しく説明しています。 + +表 4 に、利用可能なタスクで使用できるアクション、MIME タイプ、データ値をまとめます。{@link android.content.Intent#putExtra(String, String) putExtra()} で使用できるエクストラ値については、{@link android.provider.ContactsContract.Intents.Insert} のリファレンス ドキュメントに一覧があります。 + + + + +

+

+ 表 4. 連絡先プロバイダのインテント +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
タスクアクションデータMIME タイプ
連絡先をリストから選ぶ{@link android.content.Intent#ACTION_PICK} + 次のどれかです。 +
    +
  • +{@link android.provider.ContactsContract.Contacts#CONTENT_URI Contacts.CONTENT_URI}。連絡先のリストを表示します。 + +
  • +
  • +{@link android.provider.ContactsContract.CommonDataKinds.Phone#CONTENT_URI Phone.CONTENT_URI}。未加工連絡先の電話番号のリストを表示します。 + +
  • +
  • +{@link android.provider.ContactsContract.CommonDataKinds.StructuredPostal#CONTENT_URI StructuredPostal.CONTENT_URI}。未加工連絡先の住所のリストを表示します。 + + +
  • +
  • +{@link android.provider.ContactsContract.CommonDataKinds.Email#CONTENT_URI Email.CONTENT_URI}。未加工連絡先のメールアドレスのリストを表示します。 + +
  • +
+
+ 使用せず + + 指定したコンテンツ URI のタイプに応じて、未加工連絡先のリストか、未加工連絡先から取得されたデータのリストを表示します。 + +

+ {@link android.app.Activity#startActivityForResult(Intent, int) startActivityForResult()} を呼び出してください。それにより、選択した行のコンテンツ URI が返されます。 + +URI の形式は、テーブルのコンテンツ URI の末尾にその行の LOOKUP_ID が追加されたものです。 + + 端末の連絡先アプリは、このアクティビティの実行中を通して、読み取りと書き込みのパーミッションをこのコンテンツ URI にデリゲートします。 +詳しくは、「コンテンツ プロバイダの基本」をご覧ください。 + + +

+
新しい未加工連絡先を挿入する{@link android.provider.ContactsContract.Intents.Insert#ACTION Insert.ACTION}該当せず + {@link android.provider.ContactsContract.RawContacts#CONTENT_TYPE RawContacts.CONTENT_TYPE}。未加工連絡先のセットに対する MIME タイプです。 + + + 端末の連絡先アプリケーションの [連絡先の追加] 画面を表示します。インテントに追加したエクストラ値が表示されます。 +{@link android.app.Activity#startActivityForResult(Intent, int) startActivityForResult()} を使用して送信すると、新たに追加された未加工連絡先の URI が、アクティビティの {@link android.app.Activity#onActivityResult(int, int, Intent) onActivityResult()} コールバック メソッドの {@link android.content.Intent} 引数の「data」フィールドに格納されて戻ってきます。 + + + + +この値を取得するには、{@link android.content.Intent#getData()} を呼び出します。 +
連絡先を編集する{@link android.content.Intent#ACTION_EDIT} + 連絡先の {@link android.provider.ContactsContract.Contacts#CONTENT_LOOKUP_URI}。 +エディタ アクティビティを使用すると、ユーザーがその連絡先に関連付けられている任意のデータを編集できます。 + + + {@link android.provider.ContactsContract.Contacts#CONTENT_ITEM_TYPE Contacts.CONTENT_ITEM_TYPE}。1 つの連絡先です。 + + 連絡先アプリケーションに連絡先の編集画面を表示します。インテントに追加したエクストラ値が表示されます。 +ユーザーが [完了] をタップして編集内容を保存すると、アクティビティがフォアグラウンドに戻ります。 + +
ピッカー(やはりデータを追加できる)を表示する。{@link android.content.Intent#ACTION_INSERT_OR_EDIT} + 該当せず + + {@link android.provider.ContactsContract.Contacts#CONTENT_ITEM_TYPE} + + このインテントは、連絡先アプリのピッカー画面を常に表示します。ユーザーは、編集する連絡先を選ぶか、新しい連絡先を追加できます。 +ユーザーの選択に応じて編集画面か追加画面が開き、インテントに含めて渡したエクストラ データが表示されます。 + +メールアドレスや電話番号などの連絡先データを独自アプリに表示する場合は、このインテントを使用すると、ユーザーが既存の連絡先にデータを追加できます。 + + +

+ 注: このインテントのエクストラで名前の値を送信する必要はありません。ユーザーは必ず既存の名前を選ぶか新しい名前を追加するからです。 +そのうえ、アプリが名前を送信し、ユーザーが編集することを選んだ場合、連絡先アプリは送信した名前を表示し、以前の値を上書きします。 + +ユーザーがこのことに気付かず、編集内容を保存すると、以前の値が失われます。 + +

+
+

+ 端末の連絡先アプリは、インテントを使用して未加工連絡先や任意のデータを削除することを許可しません。 +そのため、未加工連絡先を削除するには {@link android.content.ContentResolver#delete(Uri, String, String[]) ContentResolver.delete()} または {@link android.content.ContentProviderOperation#newDelete(Uri) ContentProviderOperation.newDelete()} を使用してください。 + + + +

+

+ 次のスニペットは、新しい未加工連絡先とデータをバッチで挿入するインテントをコンストラクトして送信する方法を示しています。 + +

+
+// Gets values from the UI
+String name = mContactNameEditText.getText().toString();
+String phone = mContactPhoneEditText.getText().toString();
+String email = mContactEmailEditText.getText().toString();
+
+String company = mCompanyName.getText().toString();
+String jobtitle = mJobTitle.getText().toString();
+
+// Creates a new intent for sending to the device's contacts application
+Intent insertIntent = new Intent(ContactsContract.Intents.Insert.ACTION);
+
+// Sets the MIME type to the one expected by the insertion activity
+insertIntent.setType(ContactsContract.RawContacts.CONTENT_TYPE);
+
+// Sets the new contact name
+insertIntent.putExtra(ContactsContract.Intents.Insert.NAME, name);
+
+// Sets the new company and job title
+insertIntent.putExtra(ContactsContract.Intents.Insert.COMPANY, company);
+insertIntent.putExtra(ContactsContract.Intents.Insert.JOB_TITLE, jobtitle);
+
+/*
+ * Demonstrates adding data rows as an array list associated with the DATA key
+ */
+
+// Defines an array list to contain the ContentValues objects for each row
+ArrayList<ContentValues> contactData = new ArrayList<ContentValues>();
+
+
+/*
+ * Defines the raw contact row
+ */
+
+// Sets up the row as a ContentValues object
+ContentValues rawContactRow = new ContentValues();
+
+// Adds the account type and name to the row
+rawContactRow.put(ContactsContract.RawContacts.ACCOUNT_TYPE, mSelectedAccount.getType());
+rawContactRow.put(ContactsContract.RawContacts.ACCOUNT_NAME, mSelectedAccount.getName());
+
+// Adds the row to the array
+contactData.add(rawContactRow);
+
+/*
+ * Sets up the phone number data row
+ */
+
+// Sets up the row as a ContentValues object
+ContentValues phoneRow = new ContentValues();
+
+// Specifies the MIME type for this data row (all data rows must be marked by their type)
+phoneRow.put(
+        ContactsContract.Data.MIMETYPE,
+        ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE
+);
+
+// Adds the phone number and its type to the row
+phoneRow.put(ContactsContract.CommonDataKinds.Phone.NUMBER, phone);
+
+// Adds the row to the array
+contactData.add(phoneRow);
+
+/*
+ * Sets up the email data row
+ */
+
+// Sets up the row as a ContentValues object
+ContentValues emailRow = new ContentValues();
+
+// Specifies the MIME type for this data row (all data rows must be marked by their type)
+emailRow.put(
+        ContactsContract.Data.MIMETYPE,
+        ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE
+);
+
+// Adds the email address and its type to the row
+emailRow.put(ContactsContract.CommonDataKinds.Email.ADDRESS, email);
+
+// Adds the row to the array
+contactData.add(emailRow);
+
+/*
+ * Adds the array to the intent's extras. It must be a parcelable object in order to
+ * travel between processes. The device's contacts app expects its key to be
+ * Intents.Insert.DATA
+ */
+insertIntent.putParcelableArrayListExtra(ContactsContract.Intents.Insert.DATA, contactData);
+
+// Send out the intent to start the device's contacts app in its add contact activity.
+startActivity(insertIntent);
+
+

データの整合性

+

+ 連絡先リポジトリにはユーザーによる修正や更新が予想される重要で秘密性の高いデータが格納されるため、連絡先プロバイダにはデータの整合性に関して明確に定義されたルールがあります。 +連絡先データを変更する際にこれらのルールを守ることは、開発側の責任です。 +ここでは、重要なルールを示します。 + +

+
+
+ {@link android.provider.ContactsContract.CommonDataKinds.StructuredName} 行を、追加する {@link android.provider.ContactsContract.RawContacts} 行ごとに必ず追加してください。 + +
+
+ {@link android.provider.ContactsContract.Data} テーブルに {@link android.provider.ContactsContract.CommonDataKinds.StructuredName} 行のない {@link android.provider.ContactsContract.RawContacts} 行は、集約中に問題を引き起こすことがあります。 + + + +
+
+ 新しい {@link android.provider.ContactsContract.Data} 行を、その親 {@link android.provider.ContactsContract.RawContacts} 行に必ずリンクしてください。 + +
+
+ {@link android.provider.ContactsContract.RawContacts} にリンクされていない {@link android.provider.ContactsContract.Data} 行は端末の連絡先アプリケーションで可視にならず、同期アダプタで問題になることがあります。 + + +
+
+ 変更するのは、所有する未加工連絡先のデータに限ってください。 +
+
+ 通常、連絡先プロバイダは複数の異なるアカウント タイプやオンライン サービスから取得したデータを管理しています。 +そのため、アプリケーションが所有しているデータのみ変更または削除されるようにし、アプリケーションが管理しているアカウント タイプとアカウント名のデータのみ挿入するようにすることが必要です。 + + +
+
+ 権限、コンテンツ URI、URI パス、列名、MIME タイプ、{@link android.provider.ContactsContract.CommonDataKinds.CommonColumns#TYPE} 値については、{@link android.provider.ContactsContract} とそのサブクラスに定義されている定数を必ず使用してください。 + + +
+
+ そのような定数を使用することがエラーの予防に役立ちます。また、使われなくなった定数があれば、コンパイラーから警告として通知されます。 + +
+
+

カスタムデータ行

+

+ 独自のカスタム MIME タイプを作成して使用すると、{@link android.provider.ContactsContract.Data} テーブルで独自データ行を挿入、編集、削除、取得できます。 +独自行で使用できるのは {@link android.provider.ContactsContract.DataColumns} に定義された列だけですが、独自タイプに固有の列名をデフォルトの列名にマッピングできます。 + + +端末の連絡先アプリケーションで、独自行のデータは表示されますが、編集または削除はできず、ユーザーはデータの追加ができません。 + +ユーザーがカスタムデータ行を変更できるようにするには、独自のアプリケーションでエディタ アクティビティを提供する必要があります。 + +

+

+ カスタムデータを表示するには、1 つの <ContactsAccountType> 要素と 1 つまたは複数の <ContactsDataKind> 子要素を含む contacts.xml ファイルを提供します。 + +詳しくは、<ContactsDataKind> elementに関するセクションで説明しています。 + +

+

+ カスタム MIME タイプについて詳しくは、「コンテンツ プロバイダの作成」をご覧ください。 + + +

+

連絡先プロバイダの同期アダプタ

+

+ 連絡先プロバイダは、特に端末とオンライン サービスとの間における連絡先データの同期を処理するために設計されています。 +これを使用することで、ユーザーは既存のデータを新しい端末にダウンロードしたり、既存のデータを新しいアカウントにアップロードしたりできています。 + + また、同期によって、追加や変更のソースに関係なく、ユーザーは最新データを入手できます。 +同期の利点としては他にも、端末がネットワークに接続されていなくても連絡先データを使用できるようになることが挙げられます。 + +

+

+ 同期はさまざまな手法で実装できますが、Android システムでは次のタスクを自動化するプラグイン同期フレームワークを提供しています。 + +

+

+ このフレームワークを使用するには、同期アダプタ プラグインを提供する必要があります。各同期アダプタはサービスと連絡先プロバイダに対して一意ですが、同じサービスに対して複数のアカウント名を処理できます。 +また、このフレームワークでは同じサービスとプロバイダに対して複数の同期アダプタが可能です。 + +

+

同期アダプタのクラスとファイル

+

+ 同期アダプタは、{@link android.content.AbstractThreadedSyncAdapter} のサブクラスとして実装し、Android アプリケーションの一部としてインストールします。 + +システムは同期アダプタに関する知識を、アプリケーション マニフェストの要素からと、マニフェストが指す専用の XML ファイルから取得します。 +この XML ファイルは、オンライン サービスのアカウント タイプと連絡先プロバイダに絡む権限を定義しており、この組み合わせでアダプタを一意に識別します。 + +同期アダプタは、ユーザーが同期アダプタのアカウント タイプに対してアカウントを追加し、同期アダプタの同期対象である連絡先プロバイダで同期を有効にすることで、アクティブになります。 + +この時点で、システムはアダプタの管理を開始し、連絡先プロバイダとサーバーとの同期が必要になるとそれを呼び出します。 + +

+

+ 注: アカウント タイプを同期アダプタの識別の一環として使用することにより、システムは同じ組織から異なるサービスにアクセスする同期アダプタを検出してグループ化できます。 + +たとえば、Google オンライン サービス用の同期アダプタでは、すべて同じアカウント タイプ com.google です。 +ユーザーが Google アカウントを端末に追加すると、Google サービス用にインストールされているすべての同期アダプタがひとまとめにリストされ、リストされている各同期アダプタは端末上の異なる連絡先プロバイダと同期します。 + + +

+

+ データにアクセスするたび、ほとんどのサービスがユーザーに ID の確認を要求するため、Android システムでは、同期アダプタ フレームワークと類似した認証フレームワークを提供しており、往々にして同期アダプタ フレームワークと組み合わせて使用します。 + +この認証フレームワークでは、{@link android.accounts.AbstractAccountAuthenticator} のサブクラスであるプラグイン認証システムを使用します。 + +認証システムは、次の手順でユーザーの身元を検証します。 + +

    +
  1. + ユーザー名とパスワードか、それに準ずる情報(ユーザーの資格情報)を収集します。 + +
  2. +
  3. + 収集した資格情報をサービスに送信します。 +
  4. +
  5. + サービスの返答を検証します。 +
  6. +
+

+ サービスが資格情報を受け入れた場合、認証システムはその資格情報を後での使用のために格納します。 +{@link android.accounts.AccountManager} はプラグイン認証フレームワークのおかげで、OAuth2 認証トークンなど、認証システムがサポートして公開することを選択している任意の認証トークンへのアクセスを提供できます。 + + +

+

+ 認証は必須ではありませんが、たいていの連絡先サービスが使用しています。 + ただし、認証に Android 認証フレームワークを使用する必要はありません。 +

+

同期アダプタの実装

+

+ 連絡先プロバイダ用の同期アダプタを実装するには、まず以下を備えた Android アプリケーションを作成します。 + +

+
+
+ システムからの同期アダプタとのバインド要求に対処する {@link android.app.Service} コンポーネント。 + +
+
+ システムは、同期しようとするとき、サービスの {@link android.app.Service#onBind(Intent) onBind()} メソッドを呼び出して、同期アダプタの {@link android.os.IBinder} を取得します。 + +これにより、システムはアダプタのメソッドに対するプロセス間呼び出しを実行できます。 + +

+ サンプル同期アダプタのサンプルアプリで、このサービスのクラス名は com.example.android.samplesync.syncadapter.SyncService です。 + + +

+
+
+ {@link android.content.AbstractThreadedSyncAdapter} の具象サブクラスとして実装された、実際の同期アダプタ。 + +
+
+ このクラスが、サーバーからのデータのダウンロード、端末からのデータのアップロード、競合の解消といった作業を実施します。 +アダフタの主な作業は、メソッド {@link android.content.AbstractThreadedSyncAdapter#onPerformSync( Account, Bundle, String, ContentProviderClient, SyncResult) onPerformSync()} で実行されます。 + + +このクラスは、シングルトンとしてインスタンス化する必要があります。 +

+ サンプル同期アダプタのサンプルアプリで、同期アダプタはクラス com.example.android.samplesync.syncadapter.SyncAdapter に定義されています。 + + +

+
+
+ {@link android.app.Application} のサブクラス。 +
+
+ このクラスは同期アダプタ シングルトンのファクトリとして機能します。同期アダプタのインスタンス化には {@link android.app.Application#onCreate()} メソッドを使用し、同期アダプタ シングルトンを同期アダプタのサービスの {@link android.app.Service#onBind(Intent) onBind()} メソッドに返すための静的な「getter」メソッドを提供します。 + + + + +
+
+ 省略可能: システムからのユーザー認証要求に対処する {@link android.app.Service} コンポーネント。 + +
+
+ {@link android.accounts.AccountManager} は、このサービスを開始することによって認証プロセスを始めます。 +サービスの {@link android.app.Service#onCreate()} メソッドは、認証システム オブジェクトをインスタンス化します。 +システムは、アプリケーションの同期アダプタのためにユーザー アカウントを認証しようとするとき、サービスの {@link android.app.Service#onBind(Intent) onBind()} メソッドを呼び出して、認証システムの {@link android.os.IBinder} を取得します。 + + +これにより、システムは認証システムのメソッドに対するプロセス間呼び出しを実行できます。 + +

+ サンプル同期アダプタのサンプルアプリで、このサービスのクラス名は com.example.android.samplesync.authenticator.AuthenticationService です。 + + +

+
+
+ 省略可能: 認証の要求を処理する {@link android.accounts.AbstractAccountAuthenticator} の具象サブクラス。 + + +
+
+ このクラスは、ユーザーの資格情報をサーバーに対して認証するために {@link android.accounts.AccountManager} が呼び出すメソッドを提供します。認証プロセスの詳細は、使用されているサーバー技術に応じて多岐にわたります。 + +認証について詳しくは、使用するサーバー ソフトウェアのドキュメントをご覧ください。 + +

+ サンプル同期アダプタのサンプルアプリで、認証はクラス com.example.android.samplesync.authenticator.Authenticator に定義されています。 + + +

+
+
+ システムに対する同期アダプタと認証システムを定義する XML ファイル。 +
+
+ 前述の同期アダプタと認証システム サービスのコンポーネントは、アプリケーション マニフェストの <service> 要素に定義されます。 + + +これらの要素には、特定のデータをシステムに提供する次のような <meta-data> 子要素が含まれます。 + + + + +
    +
  • + 同期アダプタ サービス用の <meta-data> 要素は、XML ファイル res/xml/syncadapter.xml を指します。 + + +このファイルが、連絡先プロバイダと同期されるウェブサービスの URI と、そのウェブサービスで使用するアカウント タイプを指定します。 + + +
  • +
  • + 省略可能: 認証システム用の <meta-data> 要素は、XML ファイル res/xml/authenticator.xml を指します。 + + +このファイルが、この認証システムがサポートするアカウント タイプと、認証プロセスの最中に表示される UI リソースを指定します。 + +この要素に指定されるアカウント タイプは、同期アダプタ用に指定されるアカウント タイプと同じであることが必要です。 + + +
  • +
+
+
+

ソーシャル ストリーム データ

+

+ {@code android.provider.ContactsContract.StreamItems} テーブルと {@code android.provider.ContactsContract.StreamItemPhotos} テーブルは、ソーシャル ネットワークからの受信データを管理します。 + +独自ネットワークからのストリーム データをこれらのテーブルに追加する同期アダプタを作成したり、ストリーム データをこれらのテーブルから読み込んで独自アプリケーションに表示したり、この両方を行ったりできます。 + +このような機能があると、ソーシャル ネットワーキングのサービスやアプリケーションを Android のソーシャル ネットワーキング機能に統合できます。 + +

+

ソーシャル ストリーム テキスト

+

+ ストリーム アイテムは、必ず未加工連絡先に関連付けられます。{@code android.provider.ContactsContract.StreamItemsColumns#RAW_CONTACT_ID} は、未加工連絡先の _ID 値にリンクされます。 + +未加工連絡先のアカウント タイプとアカウント名は、ストリーム アイテム行にも格納されます。 + +

+

+ ストリームからのデータを次の列に格納します。 +

+
+
+ {@code android.provider.ContactsContract.StreamItemsColumns#ACCOUNT_TYPE} +
+
+ 必須。このストリーム アイテムに関連付けられている未加工連絡先の、ユーザーのアカウント タイプ。 +ストリーム アイテムを挿入する際には、この値を忘れずに設定してください。 +
+
+ {@code android.provider.ContactsContract.StreamItemsColumns#ACCOUNT_NAME} +
+
+ 必須。このストリーム アイテムに関連付けられている未加工連絡先の、ユーザーのアカウント名。 +ストリーム アイテムを挿入する際には、この値を忘れずに設定してください。 +
+
+ 識別用列 +
+
+ 必須。ストリーム アイテムを挿入する際には、次の識別用列を挿入する必要があります。 + +
    +
  • + {@code android.provider.ContactsContract.StreamItemsColumns#CONTACT_ID}:このストリーム アイテムが関連付けられている連絡先の {@code android.provider.BaseColumns#_ID} 値。 + + +
  • +
  • + {@code android.provider.ContactsContract.StreamItemsColumns#CONTACT_LOOKUP_KEY}: このストリーム アイテムが関連付けられている連絡先の {@code android.provider.ContactsContract.ContactsColumns#LOOKUP_KEY} 値。 + + +
  • +
  • + {@code android.provider.ContactsContract.StreamItemsColumns#RAW_CONTACT_ID}:このストリーム アイテムが関連付けられている未加工連絡先の {@code android.provider.BaseColumns#_ID} 値。 + + +
  • +
+
+
+ {@code android.provider.ContactsContract.StreamItemsColumns#COMMENTS} +
+
+ 省略可能。ストリーム アイテムの冒頭に表示できる概要情報を格納します。 +
+
+ {@code android.provider.ContactsContract.StreamItemsColumns#TEXT} +
+
+ ストリーム アイテムのテキスト。アイテムのソースによって投稿されたコンテンツか、そのストリーム アイテムを生成した何らかのアクションに関する説明です。 +この列には、{@link android.text.Html#fromHtml(String) fromHtml()} でレンダリングできる任意の書式や埋め込みリソース画像を含めることができます。 + +プロバイダは、長いコンテンツを切り捨てたり省いたりすることがありますが、タグは壊さないようにします。 + +
+
+ {@code android.provider.ContactsContract.StreamItemsColumns#TIMESTAMP} +
+
+ ストリーム アイテムが挿入またはアップデートされた時刻を含むテキスト。エポックからのミリ秒の形式です。 +ストリーム アイテムを挿入またはアップデートするアプリケーションは、この列の管理を担当します。この列は、連絡先プロバイダによって自動的に管理されるわけではありません。 + + +
+
+

+ ストリーム アイテムの識別情報を表示するには、{@code android.provider.ContactsContract.StreamItemsColumns#RES_ICON}、{@code android.provider.ContactsContract.StreamItemsColumns#RES_LABEL}、{@code android.provider.ContactsContract.StreamItemsColumns#RES_PACKAGE} を使用してアプリケーション内のリソースにリンクします。 + + + + +

+

+ {@code android.provider.ContactsContract.StreamItems} テーブルには、同期アダプタ専用に列 {@code android.provider.ContactsContract.StreamItemsColumns#SYNC1} ~ {@code android.provider.ContactsContract.StreamItemsColumns#SYNC4} も格納されます。 + + + +

+

ソーシャル ストリーム フォト

+

+ {@code android.provider.ContactsContract.StreamItemPhotos} テーブルには、ストリーム アイテムに関連付けられた写真が格納されます。 +このテーブルの {@code android.provider.ContactsContract.StreamItemPhotosColumns#STREAM_ITEM_ID} 列は、{@code android.provider.ContactsContract.StreamItems} テーブルの {@code android.provider.BaseColumns#_ID} 列の値にリンクされます。 + + +写真の参照は、テーブルの次の列に格納されます。 + +

+
+
+ {@code android.provider.ContactsContract.StreamItemPhotos#PHOTO} 列(BLOB)。 +
+
+ 写真のバイナリ表現。格納や表示のためにプロバイダによってサイズが変更されます。 + この列は、写真を格納するために使用されていた連絡先プロバイダの従来のバージョンとの下方互換性のために用意されています。 +ただし、現在のバージョンでは、写真の格納にこの列を使用しないでください。 +代わりに、{@code android.provider.ContactsContract.StreamItemPhotosColumns#PHOTO_FILE_ID} または {@code android.provider.ContactsContract.StreamItemPhotosColumns#PHOTO_URI}(どちらについても次の項目で説明します)を使用して、写真をファイルに保存します。 + + +現状では、この列には写真のサムネイルが格納されており、読み出し可能です。 + +
+
+ {@code android.provider.ContactsContract.StreamItemPhotosColumns#PHOTO_FILE_ID} +
+
+ 未加工連絡先の写真の数値 ID。この値を定数 {@link android.provider.ContactsContract.DisplayPhoto#CONTENT_URI DisplayPhoto.CONTENT_URI} の末尾に追加して、1 つの写真ファイルを指すコンテンツ URI を取得し、{@link android.content.ContentResolver#openAssetFileDescriptor(Uri, String) openAssetFileDescriptor()} を呼び出して、その写真ファイルのハンドルを取得します。 + + + + +
+
+ {@code android.provider.ContactsContract.StreamItemPhotosColumns#PHOTO_URI} +
+
+ この行によって表されている写真の写真ファイルを直接指すコンテンツ URI。 + この URI を使用して {@link android.content.ContentResolver#openAssetFileDescriptor(Uri, String) openAssetFileDescriptor()} を呼び出して、写真ファイルのハンドルを取得します。 + +
+
+

ソーシャル ストリーム テーブルの使用

+

+ これらのテーブルは、連絡先プロバイダの他の主なテーブルと同じように機能しますが、次のような例外があります。 +

+ + +

+ クラス {@code android.provider.ContactsContract.StreamItems.StreamItemPhotos} は、1 つのストリーム アイテムの写真行を含む {@code android.provider.ContactsContract.StreamItemPhotos} のサブテーブルを定義します。 + + +

+

ソーシャル ストリーム操作

+

+ 連絡先プロバイダによって端末の連絡先アプリケーションと連携して管理されるソーシャル ストリーム データは、ソーシャル ネットワーキング システムと既存の連絡先を接続するためのとても便利な手段を用意しています。 + +利用できる機能は次のとおりです。 +

+ +

+ 連絡先プロバイダを使用したストリーム アイテムの定期的な同期は、他の同期と同じです。 +同期について詳しくは、連絡先プロバイダの同期アダプタをご覧ください。 +通知の登録と連絡先の招待については、次の 2 つのセクションで説明します。 + +

+

ソーシャル ネットワーキング表示を処理するための登録

+

+ 同期アダプタを登録し、同期アダプタによって管理されている連絡先をユーザーが表示したときに通知されるようにする方法は、次のとおりです。 + +

+
    +
  1. + contacts.xml という名前のファイルをプロジェクトの res/xml/ ディレクトリに作成します。 +このファイルが既に存在する場合は、この手順を省略できます。 +
  2. +
  3. + このファイルに要素 <ContactsAccountType xmlns:android="http://schemas.android.com/apk/res/android"> を追加します。 + + この要素が既に存在する場合は、この手順を省略できます。 +
  4. +
  5. + ユーザーが端末の連絡先アプリケーションで連絡先の詳細ページを開いたときに通知されるサービスを登録するには、属性 viewContactNotifyService="serviceclass" を要素に追加します。serviceclass は、端末の連絡先アプリケーションからインテントを受け取ることになるサービスの完全修飾クラス名です。 + + + +通知側サービスのために、{@link android.app.IntentService} を機能拡張したクラスを使用して、そのサービスがインテントを受け取れるようにします。 + +受け取るインテントに含まれるデータには、ユーザーがクリックした未加工連絡先のコンテンツ URI が格納されます。 +通知側サービスから、同期アダプタをバインドして呼び出し、未加工連絡先のデータをアップデートできます。 + +
  6. +
+

+ ユーザーがストリーム アイテムかストリーム フォトかその両方をクリックしたときに呼び出されるアクティビティを登録する方法は次のとおりです。 +

+
    +
  1. + contacts.xml という名前のファイルをプロジェクトの res/xml/ ディレクトリに作成します。 +このファイルが既に存在する場合は、この手順を省略できます。 +
  2. +
  3. + このファイルに要素 <ContactsAccountType xmlns:android="http://schemas.android.com/apk/res/android"> を追加します。 + + この要素が既に存在する場合は、この手順を省略できます。 +
  4. +
  5. + アクティビティのどれかを登録して、ユーザーが端末の連絡先アプリケーションでストリーム アイテムをタップしたことに対処するには、属性 viewStreamItemActivity="activityclass" を要素に追加します。activityclass は、端末の連絡先アプリケーションからインテントを受け取ることになるアクティビティの完全修飾クラス名です。 + + + + +
  6. +
  7. + アクティビティのどれかを登録して、ユーザーが端末の連絡先アプリケーションでストリーム フォトをタップしたことに対処するには、属性 viewStreamItemPhotoActivity="activityclass" を要素に追加します。activityclass は、端末の連絡先アプリケーションからインテントを受け取ることになるアクティビティの完全修飾クラス名です。 + + + + +
  8. +
+

+ <ContactsAccountType> 要素については、<ContactsAccountType> 要素で詳しく説明しています。 + +

+

+ 受け取るインテントには、ユーザーがクリックしたアイテムまたは写真のコンテンツ URI が格納されます。 + テキスト アイテムと写真とで異なるアクティビティを使用するには、同じファイルで両方の属性を使用します。 +

+

ソーシャル ネットワーキング サービスの操作

+

+ ユーザーは、連絡先をソーシャル ネットワーキング サービスに招待するのに、端末の連絡先アプリケーションを離れる必要はありません。 +その代わりとして、連絡先をアクティビティのどれかに招待するインテントを端末の連絡先アプリケーションに送信させることができます。 +これをセットアップする方法は次のとおりです。 +

+
    +
  1. + contacts.xml という名前のファイルをプロジェクトの res/xml/ ディレクトリに作成します。 +このファイルが既に存在する場合は、この手順を省略できます。 +
  2. +
  3. + このファイルに要素 <ContactsAccountType xmlns:android="http://schemas.android.com/apk/res/android"> を追加します。 + + この要素が既に存在する場合は、この手順を省略できます。 +
  4. +
  5. + 次の属性を追加します。 +
      +
    • inviteContactActivity="activityclass"
    • +
    • + inviteContactActionLabel="@string/invite_action_label" +
    • +
    + activityclass 値は、インテントを受信するアクティビティの完全修飾クラス名です。 +invite_action_label 値は、端末の連絡先アプリケーションの [Add Connection] メニューに表示される文字列です。 + + +
  6. +
+

+ 注: ContactsSource は廃止されたタグ名で、ContactsAccountType に置き換わっています。 + +

+

contacts.xml リファレンス

+

+ ファイル contacts.xml には、同期アダプタやアプリケーションと連絡先アプリケーションや連絡先プロバイダとのやり取りを管理する XML 要素が含まれています。 +これらの要素について、以降のセクションで説明します。 + +

+

<ContactsAccountType> 要素

+

+ <ContactsAccountType> 要素は、アプリケーションと連絡先アプリケーションとのやり取りを管理します。 +構文は次のとおりです。 +

+
+<ContactsAccountType
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        inviteContactActivity="activity_name"
+        inviteContactActionLabel="invite_command_text"
+        viewContactNotifyService="view_notify_service"
+        viewGroupActivity="group_view_activity"
+        viewGroupActionLabel="group_action_text"
+        viewStreamItemActivity="viewstream_activity_name"
+        viewStreamItemPhotoActivity="viewphotostream_activity_name">
+
+

+ 含まれているファイル: +

+

+ res/xml/contacts.xml +

+

+ 含めることのできる要素: +

+

+ <ContactsDataKind> +

+

+ 説明: +

+

+ ユーザーが連絡先の 1 人をソーシャル ネットワーキングに招待できるようにしたり、ソーシャル ネットワーキング ストリームのどれかがアップデートされたらユーザーに通知したりするための、Android コンポーネントや UI ラベルを宣言します。 + + +

+

+ 属性プレフィックス android: は、<ContactsAccountType> の属性に必須ではないことに注目してください。 + +

+

+ 属性: +

+
+
{@code inviteContactActivity}
+
+ ユーザーが端末の連絡先アプリケーションで [Add Connection] を選択したときに起動する、アプリケーション内のアクティビティの完全修飾クラス名。 + + +
+
{@code inviteContactActionLabel}
+
+ [Add Connection] メニューで、{@code inviteContactActivity} に指定されたアクティビティ用に表示されるテキスト。 + + たとえば、文字列「Follow in my network」を指定できます。このラベルには文字列リソース ID を使用できます。 + +
+
{@code viewContactNotifyService}
+
+ ユーザーが連絡先を表示したときに通知を受け取ることになる、独自アプリケーション内のサービスの完全修飾クラス名。 +この通知は端末の連絡先アプリケーションによって送信されます。これを使用することで、アプリケーションはデータ処理の多い操作を必要になるまで延期できます。 + +たとえば、アプリケーションはこの通知への対応として、連絡先の高解像度写真と直近のソーシャル ストリーム アイテムを読み込んで表示できます。 + +この機能について詳しくは、ソーシャル ストリーム操作で説明しています。 +NotifierService.java ファイルに指定された通知サービスの例は、SampleSyncAdapter サンプル アプリケーションにあります。 + + + +
+
{@code viewGroupActivity}
+
+ グループ情報を表示できる、独自アプリケーション内のアクティビティの完全修飾クラス名。 +ユーザーが端末の連絡先アプリケーションでグループラベルをタップすると、このアクティビティの UI が表示されます。 + +
+
{@code viewGroupActionLabel}
+
+ ユーザーがアプリケーションでグループを表示できるようにする UI コントロールに対し、連絡先アプリケーションが表示するラベル。 + +

+ たとえば、端末に Google+ アプリケーションをインストールし、Google+ を連絡先アプリケーションと同期すると、Google+ のサークルが連絡先アプリケーションの [グループ] タブにグループとして表示されます。 + +Google+ サークルのどれかをクリックすると、そのサークルに所属する人が「グループ」として表示されます。 +表示の最上部に、Google+ アイコンが表示されます。それをクリックすると、制御が Google+ アプリに移ります。連絡先アプリケーションはこれを {@code viewGroupActivity} を使用して行い、Google+ アイコンを {@code viewGroupActionLabel} の値として使用します。 + + + + +

+

+ この属性には、文字列リソース ID を使用できます。 +

+
+
{@code viewStreamItemActivity}
+
+ ユーザーが未加工連絡先のストリーム アイテムをタップしたときに端末の連絡先アプリケーションが起動する、独自アプリケーション内のアクティビティの完全修飾クラス名。 + +
+
{@code viewStreamItemPhotoActivity}
+
+ ユーザーが未加工連絡先のストリーム アイテムで写真をタップしたときに端末の連絡先アプリケーションが起動する、独自アプリケーション内のアクティビティの完全修飾クラス名。 + + +
+
+

<ContactsDataKind> 要素

+

+ <ContactsDataKind> 要素は、連絡先アプリケーションにおける独自アプリケーションのカスタムデータ行の表示を管理します。構文は次のとおりです。 + +

+
+<ContactsDataKind
+        android:mimeType="MIMEtype"
+        android:icon="icon_resources"
+        android:summaryColumn="column_name"
+        android:detailColumn="column_name">
+
+

+ 含まれているファイル: +

+<ContactsAccountType> +

+ 説明: +

+

+ この要素は、カスタムデータ行のコンテンツを未加工連絡先の詳細の一部として連絡先アプリケーションに表示させるために使用します。 +<ContactsAccountType> の各 <ContactsDataKind> 子要素は、同期アダプタが {@link android.provider.ContactsContract.Data} テーブルに追加するカスタムデータ行のタイプを表します。 + +使用するカスタム MIME タイプごとに <ContactsDataKind> 要素を 1 つ追加します。 +データを表示しないカスタムデータ行がある場合は、この要素を追加する必要はありません。 + +

+

+ 属性: +

+
+
{@code android:mimeType}
+
+ {@link android.provider.ContactsContract.Data} テーブルに含まれるカスタムデータ行のどれかに定義したカスタム MIME タイプ。 +たとえば、値 vnd.android.cursor.item/vnd.example.locationstatus は、連絡先の最新の場所情報を記録するデータ行のためのカスタム MIME タイプということが考えられます。 + + +
+
{@code android:icon}
+
+ 連絡先アプリケーションがデータの隣に表示する Android ドローアブル リソース。 + +そのデータが独自サービスからのものであることを示すのに使用します。 + +
+
{@code android:summaryColumn}
+
+ データ行から取得された 2 つの値で最初の列名。この値は、このデータ行のエントリの先頭行として表示されます。 +この先頭行は、データの要約として使われることを狙ったものですが、省略可能です。 +android:detailColumn もご覧ください。 + +
+
{@code android:detailColumn}
+
+ データ行から取得された 2 つの値で 2 番目の列名。この値は、このデータ行のエントリの 2 行目として表示されます。 +{@code android:summaryColumn} もご覧ください。 + +
+
+

連絡先プロバイダの追加機能

+

+ ここまでのセクションで説明した主な機能の他に、連絡先プロバイダには連絡先データの作業用として次のような便利な機能が用意されています。 + +

+ +

連絡先グループ

+

+ 連絡先プロバイダでは、オプションで、関連連絡先のコレクションをグループ データでラベル付けできます。 +あるユーザー アカウントに関連付けられているサーバーがグループを管理する場合、そのアカウントのアカウント タイプ用の同期アダプタが、連絡先プロバイダとサーバーとの間でグループデータを転送する必要があります。 + +ユーザーが新しい連絡先をサーバーに追加し、その連絡先を新しいグループに入れた場合、同期アダプタはその新しいグループを {@link android.provider.ContactsContract.Groups} テーブルに追加する必要があります。 + +ある未加工連絡先が属するグループ(複数の場合あり)は、{@link android.provider.ContactsContract.Data} テーブルに、{@link android.provider.ContactsContract.CommonDataKinds.GroupMembership} MIME タイプを使用して格納されます。 + + +

+

+ サーバーからの未加工連絡先データを連絡先プロバイダに追加する同期アダプタを設計している場合、グループを使用しないのであれば、連絡先プロバイダに対してデータが可視であることを通知する必要があります。 + +ユーザーがアカウントを端末に追加したときに実行されるコード内で、連絡先プロバイダがそのアカウントに対して追加する {@link android.provider.ContactsContract.Settings} 行をアップデートします。 + +この行で、{@link android.provider.ContactsContract.SettingsColumns#UNGROUPED_VISIBLE Settings.UNGROUPED_VISIBLE} 列の値を 1 に設定します。 + +こうすると、連絡先プロバイダは、グループを使用していない場合でも、独自の連絡先データを可視にします。 + +

+

連絡先の写真

+

+ {@link android.provider.ContactsContract.Data} テーブルは、写真を MIME タイプ {@link android.provider.ContactsContract.CommonDataKinds.Photo#CONTENT_ITEM_TYPE Photo.CONTENT_ITEM_TYPE} の行として格納します。 + +この行の {@link android.provider.ContactsContract.RawContactsColumns#CONTACT_ID} 列は、それが属する未加工連絡先の {@code android.provider.BaseColumns#_ID} 列にリンクされます。 + + + クラス {@link android.provider.ContactsContract.Contacts.Photo} は、連絡先のプライマリ フォトの写真情報を格納する {@link android.provider.ContactsContract.Contacts} のサブテーブルを定義します。ここでプライマリ フォトとは、連絡先のプライマリ未加工連絡先のプライマリ フォトのことを示します。 + +同様に、クラス {@link android.provider.ContactsContract.RawContacts.DisplayPhoto} は、未加工連絡先のプライマリ フォトに関する情報を含む {@link android.provider.ContactsContract.RawContacts} のサブテーブルを定義します。 + + + +

+

+ {@link android.provider.ContactsContract.Contacts.Photo} と {@link android.provider.ContactsContract.RawContacts.DisplayPhoto} の参照ドキュメントには、写真情報の取得例が含まれています。 + +未加工連絡先のプライマリ サムネイルを取得するための便利なクラスはありませんが、{@link android.provider.ContactsContract.Data} テーブルにクエリを送信して、未加工連絡先の {@code android.provider.BaseColumns#_ID}、{@link android.provider.ContactsContract.CommonDataKinds.Photo#CONTENT_ITEM_TYPE Photo.CONTENT_ITEM_TYPE}、{@link android.provider.ContactsContract.Data#IS_PRIMARY} 列を選んでその未加工連絡先のプライマリ フォト行を探すことができます。 + + + + + + +

+

+ 人のソーシャル ストリーム データにも写真が含まれていることがあります。その場合は {@code android.provider.ContactsContract.StreamItemPhotos} テーブルに格納されています。このテーブルについては、ソーシャル ストリーム フォトで詳しく説明しています + + +

diff --git a/docs/html-intl/intl/ja/guide/topics/providers/content-provider-basics.jd b/docs/html-intl/intl/ja/guide/topics/providers/content-provider-basics.jd new file mode 100644 index 0000000000000000000000000000000000000000..9eef5d6eed4604e5a3f9d334bda93ee23dc8ecdb --- /dev/null +++ b/docs/html-intl/intl/ja/guide/topics/providers/content-provider-basics.jd @@ -0,0 +1,1196 @@ +page.title=コンテンツ プロバイダの基本 +@jd:body + + + +

+ コンテンツ プロバイダは、データの中央リポジトリへのアクセスを管理します。プロバイダは Android アプリケーションの一部であり、通常、データを処理するための独自の UI を備えています。 + +ただし、コンテンツ プロバイダは主に他のアプリケーションでの使用を意図したものです。アプリケーションはプロバイダ クライアント オブジェクトを使用してプロバイダにアクセスします。 +さらに、プロバイダとプロバイダ クライアントにはデータを扱うための一貫性のある標準のインターフェースが備わっており、プロセス間の通信や安全なデータ アクセスを処理します。 + + +

+

+ このトピックでは、次の項目に関する基本的な内容を説明します。 +

+ + + +

概要

+

+ 外部アプリケーションでは、コンテンツ プロバイダのデータは、リレーショナル データベースで使用する表と同様に、1 つ以上の表として表示されます。 +行はプロバイダが収集するデータの何らかのタイプのインスタンスを表しており、行内のそれぞれの列はインスタンスに対して収集した個々のデータを表しています。 + + +

+

+ たとえば、Android プラットフォームに組み込まれているプロバイダの 1 つに単語リストがありますが、ここにはユーザーが保存しておく標準以外の単語のスペリングが格納されます +表 1 は、このプロバイダの表にデータがどのように格納されているのかを表しています。 + +

+

+ 表 1: サンプルの単語リスト表。 +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
wordapp idfrequencylocale_ID
mapreduceuser1100en_US1
precompileruser14200fr_FR2
appletuser2225fr_CA3
constuser1255pt_BR4
intuser5100en_UK5
+

+ 表 1 の各行は、標準の辞書には含まれていない単語のインスタンスを表しています。 +各列は、該当する単語のデータの一部(その単語が最初に見つかったロケールなど)を表しています。 +列の見出しは、プロバイダに格納される列の名前です。 +行のロケールを調べるには、locale 列を参照します。このプロバイダの場合、_ID 列が「プライマリキー」の列の役割を果たし、プロバイダはこの列を自動的に保持します。 + + +

+

+ 注: プロバイダはプライマリキーを持つ必要がなく、プライマリキーがある場合は _ID をプライマリキーの列名として使用する必要はありません。 +ただし、プロバイダのデータを {@link android.widget.ListView} にバインドする場合は、いずれかの列の名前を _ID とする必要があります。 + +この要件についての詳細は、クエリ結果を表示するセクションをご覧ください。 + +

+

プロバイダにアクセスする

+

+ アプリケーションは、{@link android.content.ContentResolver} クライアント オブジェクトを使用して、コンテンツ プロバイダのデータにアクセスします。 +このオブジェクトには、プロバイダ オブジェクトの同名のメソッドを呼び出すメソッドが備わっています。これは、{@link android.content.ContentProvider} の具体的なサブクラスのインスタンスのいずれかになります。 + +{@link android.content.ContentResolver} メソッドには、永続ストレージの基本的な「CRUD」(作成、取得、更新、削除)機能が備わっています。 + + +

+

+ クライアント アプリケーションのプロセスにおける {@link android.content.ContentResolver} オブジェクトと、プロバイダを所有するアプリケーションの {@link android.content.ContentProvider} オブジェクトが、プロセス間の通信を自動的に処理します。さらに、{@link android.content.ContentProvider} は、データのリポジトリと、外部に表形式で表示されるデータの間の抽象化レイヤーとして機能します。 + + + + +

+

+ 注: 通常、アプリケーションがプロバイダにアクセスする場合、そのマニフェスト ファイルで特定のパーミッションを要求する必要があります。 +詳細は、コンテンツ プロバイダ パーミッションセクションをご覧ください。 + +

+

+ たとえば、単語リスト プロバイダから単語とそのロケールの一覧を取得するには、{@link android.content.ContentResolver#query ContentResolver.query()} を呼び出します。 + + {@link android.content.ContentResolver#query query()} メソッドによって、単語リスト プロバイダが定義する{@link android.content.ContentProvider#query ContentProvider.query()} メソッドが呼び出されます。 + +コードの次の行は、{@link android.content.ContentResolver#query ContentResolver.query()} 呼び出しを表しています。 + +

+

+// Queries the user dictionary and returns results
+mCursor = getContentResolver().query(
+    UserDictionary.Words.CONTENT_URI,   // The content URI of the words table
+    mProjection,                        // The columns to return for each row
+    mSelectionClause                    // Selection criteria
+    mSelectionArgs,                     // Selection criteria
+    mSortOrder);                        // The sort order for the returned rows
+
+

+ 表 2 は、query(Uri,projection,selection,selectionArgs,sortOrder)} の引数が SQL SELECT 文にどのように一致しているかを示しています。 + + +

+

+ 表 2: SQL クエリと比較した Query()。 +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
query() 引数SELECT キーワード / パラメータ
UriFROM table_nameUri は、table_name という名前のプロバイダの表にマッピングされます。
projectioncol,col,col,... + projection は、取得するそれぞれの行に含まれる列の配列です。 + +
selectionWHERE col = valueselection は、行を選択する際の基準を指定します。
selectionArgs + (正確に一致するものはありません。選択句では、? プレースホルダーが選択引数に置き換わります) + +
sortOrderORDER BY col,col,... + sortOrder は、返される {@link android.database.Cursor} 内で行が表示される順番を指定します。 + +
+

コンテンツ URI

+

+ コンテンツ URI は、プロバイダのデータを特定する URI です。コンテンツ URI には、プロバイダ全体の識別名(認証局)と表をポイントする名前(パス)が含まれます。 + +プロバイダの表にアクセスするためのクライアント メソッドを呼び出す場合、その引数の 1 つがコンテンツ URI になります。 + + +

+

+ コードの前半の行では、定数 {@link android.provider.UserDictionary.Words#CONTENT_URI} に、単語リストの「words」表のコンテンツ URI が入ります。 + +{@link android.content.ContentResolver} オブジェクトは URI の認証局をパースし、認証局を既知のプロバイダのシステム表と比較して、プロバイダを「解決」します。 + +その後、{@link android.content.ContentResolver} は、クエリ引数を正しいプロバイダに送信できます。 + + +

+

+ {@link android.content.ContentProvider} は、アクセスする表を選択するのに、コンテンツ URI のパス部分を使用します。 +通常、プロバイダは公開する各表のパスを持ちます。 +

+

+ コードの前半の行では、「words」表の完全な URI は次のようになります。 +

+
+content://user_dictionary/words
+
+

+ ここで、user_dictionary 文字列はプロバイダの認証局になり、words 文字列は表のパスになります。 +文字列 content://スキーム)は常に存在し、これがコンテンツ URI であることを示します。 + + +

+

+ 多くのプログラムでは、URI の末尾に ID 値を付加することで、表内の 1 つの行にアクセスできます。たとえば、_ID4 の行を単語リストから取得するには、次のコンテンツ URI を使用します。 + + +

+
+Uri singleUri = ContentUris.withAppendedId(UserDictionary.Words.CONTENT_URI,4);
+
+

+ 通常は、一連の行を取得してからいずれかの行を更新、削除するような場合に ID 値を使用します。 + +

+

+ 注: {@link android.net.Uri} クラスと {@link android.net.Uri.Builder} クラスには、正しい形式の URI オブジェクトを文字列から作成するための、便利なメソッドが用意されています。 +{@link android.content.ContentUris} には、URI に ID 値を付加するための便利なメソッドが用意されています。前述のスニペットは {@link android.content.ContentUris#withAppendedId + withAppendedId()} を使用して、UserDictionary コンテンツ URI に ID を付加しています。 + + +

+ + + +

プロバイダからデータを取得する

+

+ このセクションでは、単語リスト プロバイダの例を使い、プロバイダからデータを取得する方法を説明します。 + +

+

+ わかりやすくするために、このセクションのコード スニペットは「UI スレッド」の {@link android.content.ContentResolver#query ContentResolver.query()} を呼び出しています。 +ただし、実際のコードでは、個別のスレッドで非同期にクエリを実行する必要があります。 +この操作は、{@link android.content.CursorLoader} クラスを使用して行うこともできます。このクラスについての詳細は、「ローダ」に関するガイドをご覧ください。 + + +さらに、コードの行はスニペットのみであり、完全なアプリケーションにはなっていません。 + +

+

+ プロバイダからデータを取得するには、次の基本的な手順に従います。 +

+
    +
  1. + プロバイダの読み取りアクセスを要求します。 +
  2. +
  3. + プロバイダにクエリを送信するコードを定義します。 +
  4. +
+

読み取りアクセス パーミッションを要求する

+

+ プロバイダからデータを取得するには、アプリケーションからプロバイダの読み取りアクセスを要求します。 +実行時にはこのパーミッションを要求できません。その代わりに、<uses-permission> 要素とプロバイダで定義した正確なパーミッション名を使用して、このパーミッションを必要としていることをマニフェストで指定する必要があります。 + + + +この要素をマニフェストで指定すると、実際に、このパーミッションをアプリケーションに「要求」することになります。 +ユーザーがアプリケーションをインストールすると、この要求が暗黙的に付与されることになります。 + +

+

+ 使用する読み取りアクセス パーミッションの正確な名前と、プロバイダが使用するその他のアクセス パーミッションの名前については、プロバイダのドキュメントをご覧ください。 + + +

+

+ プロバイダにアクセスする際のパーミッションのロールの詳細は、コンテンツ プロバイダ パーミッションセクションをご覧ください。 + +

+

+ 単語リスト プロバイダはパーミッション android.permission.READ_USER_DICTIONARY をマニフェスト ファイルで定義するため、プロバイダからの読み取りを行うアプリケーションはこのパーミッションを要求する必要があります。 + + +

+ +

クエリを作成する

+

+ プロバイダからのデータの取得の次の手順では、クエリを作成します。最初のスニペットで、単語リスト プロバイダにアクセスするための変数をいくつか定義します。 + +

+
+
+// A "projection" defines the columns that will be returned for each row
+String[] mProjection =
+{
+    UserDictionary.Words._ID,    // Contract class constant for the _ID column name
+    UserDictionary.Words.WORD,   // Contract class constant for the word column name
+    UserDictionary.Words.LOCALE  // Contract class constant for the locale column name
+};
+
+// Defines a string to contain the selection clause
+String mSelectionClause = null;
+
+// Initializes an array to contain selection arguments
+String[] mSelectionArgs = {""};
+
+
+

+ 次のスニペットでは、単語リスト プロバイダの例を使って、{@link android.content.ContentResolver#query ContentResolver.query()} を使用する方法を示しています。 + +プロバイダ クライアント クエリは SQL クエリに似たものであり、戻り値となる一連の列、一連の選択基準、並べ替えの順番を含みます。 + +

+

+ クエリの戻り値となる一連の列は、投影(変数 mProjection)と呼ばれます。 + +

+

+ 取得する行を指定する式は、選択句と選択引数に分割されます。 +選択句は論理式やブール式、列名、値(変数 mSelectionClause)の組み合わせになります。 +値の代わりに置き換え可能パラメータ ? を指定すると、クエリ メソッドによって、選択引数の配列(変数 mSelectionArgs)から値が取得されます。 + + +

+

+ 次のスニペットでは、ユーザーが単語を入力しない場合、選択句が null に設定され、クエリによってプロバイダのすべての単語が返されます。 +ユーザーが単語を入力すると、選択句が UserDictionary.Words.WORD + " = ?" に設定され、選択引数の配列の最初の要素が、ユーザーが入力した単語に設定されます。 + + +

+
+/*
+ * This defines a one-element String array to contain the selection argument.
+ */
+String[] mSelectionArgs = {""};
+
+// Gets a word from the UI
+mSearchString = mSearchWord.getText().toString();
+
+// Remember to insert code here to check for invalid or malicious input.
+
+// If the word is the empty string, gets everything
+if (TextUtils.isEmpty(mSearchString)) {
+    // Setting the selection clause to null will return all words
+    mSelectionClause = null;
+    mSelectionArgs[0] = "";
+
+} else {
+    // Constructs a selection clause that matches the word that the user entered.
+    mSelectionClause = UserDictionary.Words.WORD + " = ?";
+
+    // Moves the user's input string to the selection arguments.
+    mSelectionArgs[0] = mSearchString;
+
+}
+
+// Does a query against the table and returns a Cursor object
+mCursor = getContentResolver().query(
+    UserDictionary.Words.CONTENT_URI,  // The content URI of the words table
+    mProjection,                       // The columns to return for each row
+    mSelectionClause                   // Either null, or the word the user entered
+    mSelectionArgs,                    // Either empty, or the string the user entered
+    mSortOrder);                       // The sort order for the returned rows
+
+// Some providers return null if an error occurs, others throw an exception
+if (null == mCursor) {
+    /*
+     * Insert code here to handle the error. Be sure not to use the cursor! You may want to
+     * call android.util.Log.e() to log this error.
+     *
+     */
+// If the Cursor is empty, the provider found no matches
+} else if (mCursor.getCount() < 1) {
+
+    /*
+     * Insert code here to notify the user that the search was unsuccessful. This isn't necessarily
+     * an error. You may want to offer the user the option to insert a new row, or re-type the
+     * search term.
+     */
+
+} else {
+    // Insert code here to do something with the results
+
+}
+
+

+ このクエリは、 SQL 文に似ています。 +

+
+SELECT _ID, word, locale FROM words WHERE word = <userinput> ORDER BY word ASC;
+
+

+ この SQL 文では、コントラクト クラスの定数ではなく、実際の列の名前が使用されます。 +

+

悪意のある入力から保護する

+

+ コンテンツ プロバイダが管理するデータが SQL データベース内にある場合、外部の信頼されていないデータを未処理の SQL 文に含めると SQL インジェクションが発生することがあります。 + +

+

+ 次のような選択句を考えてみます。 +

+
+// Constructs a selection clause by concatenating the user's input to the column name
+String mSelectionClause =  "var = " + mUserInput;
+
+

+ このようにすれば、ユーザーが悪意のある SQL を SQL 文に連結できるようになります。 + たとえば、ユーザーが mUserInput に「nothing; DROP TABLE *;」と入力すると、選択句は var = nothing; DROP TABLE *; となります。 +選択句は SQL 文として処理されるため、この場合、基本的な SQLite データベースのすべての表が消去されることがあります(プロバイダに SQL インジェクションの試みの検出が設定されていない場合)。 + + + +

+

+ この問題を回避するには、? を置き換え可能パラメータとして使う選択句と、選択引数の個別の配列を使用します。 +そうすることで、ユーザー入力は、SQL 文の一部として解釈されずに、クエリに直接バインドされます。 + + SQL として扱われないため、ユーザー入力によって悪意のある SQL が挿入されることはありません。ユーザー入力を含める際に連結を使用しないで、次の選択句を使用します。 + +

+
+// Constructs a selection clause with a replaceable parameter
+String mSelectionClause =  "var = ?";
+
+

+ 選択引数の配列を次のように設定します。 +

+
+// Defines an array to contain the selection arguments
+String[] selectionArgs = {""};
+
+

+ 選択引数の配列に次のように値を格納します。 +

+
+// Sets the selection argument to the user's input
+selectionArgs[0] = mUserInput;
+
+

+ プロバイダが SQL データベースに基づいたものではない場合でも、選択内容を指定する場合は、? を置き換え可能パラメータとして使う選択句と、選択引数の配列を使用することをお勧めします。 + + +

+ +

クエリ結果を表示する

+

+ {@link android.content.ContentResolver#query ContentResolver.query()} クライアント メソッドは、常に {@link android.database.Cursor} を返します。これには、クエリの選択基準に一致する、行へのクエリの投影によって指定される列が含まれます。 + +{@link android.database.Cursor} オブジェクトは、含まれる行と列へのランダム読み取りアクセスを提供します。 + +{@link android.database.Cursor} メソッドを使用すると、結果の行の繰り返し、各列のデータタイプの識別、列からのデータの取得、結果のその他のプロパティの確認といった操作が可能となります。 + +一部の {@link android.database.Cursor} を実装すると、プロバイダのデータが変更された場合にオブジェクトが自動的に送信されたり、{@link android.database.Cursor} が変更された場合にオブザーバ オブジェクトのメソッドがトリガーされたり、その両方が実行されたりします。 + + +

+

+ 注: クエリを作成するオブジェクトの特性に基づいて、プロバイダによって列へのアクセスが制限されることがあります。 +たとえば、連絡先プロバイダにより同期アダプタは一部の列へのアクセスが制限されるため、その場合はアクティビティやサービスを返しません。 + +

+

+ 選択基準に一致する行がない場合は、{@link android.database.Cursor#getCount Cursor.getCount()} が 0(空のカーソル)の {@link android.database.Cursor} オブジェクトを返します。 + + +

+

+ 内部エラーが発生した場合、クエリの結果はプロバイダによって異なります。null が返されることもあれば、{@link java.lang.Exception} がスローされることもあります。 + +

+

+ {@link android.database.Cursor} は行の「一覧」であることから、{@link android.database.Cursor} のコンテンツを表示する場合は、{@link android.widget.SimpleCursorAdapter} 経由で {@link android.widget.ListView} にリンクすることをお勧めします。 + + +

+

+ 次のスニペットは前のスニペットのコードの続きです。クエリによって取得する {@link android.database.Cursor} を含む {@link android.widget.SimpleCursorAdapter} オブジェクトを作成し、このオブジェクトを {@link android.widget.ListView} のアダプタに設定します。 + + + +

+
+// Defines a list of columns to retrieve from the Cursor and load into an output row
+String[] mWordListColumns =
+{
+    UserDictionary.Words.WORD,   // Contract class constant containing the word column name
+    UserDictionary.Words.LOCALE  // Contract class constant containing the locale column name
+};
+
+// Defines a list of View IDs that will receive the Cursor columns for each row
+int[] mWordListItems = { R.id.dictWord, R.id.locale};
+
+// Creates a new SimpleCursorAdapter
+mCursorAdapter = new SimpleCursorAdapter(
+    getApplicationContext(),               // The application's Context object
+    R.layout.wordlistrow,                  // A layout in XML for one row in the ListView
+    mCursor,                               // The result from the query
+    mWordListColumns,                      // A string array of column names in the cursor
+    mWordListItems,                        // An integer array of view IDs in the row layout
+    0);                                    // Flags (usually none are needed)
+
+// Sets the adapter for the ListView
+mWordList.setAdapter(mCursorAdapter);
+
+

+ 注: {@link android.database.Cursor} によって {@link android.widget.ListView} を戻すには、カーソルに _ID という名前の列を含める必要があります。 + + そのため、{@link android.widget.ListView} には表示されまませんが、前述のクエリは「words」表に _ID 列を取得します。 + + また、このような制限があることから、大部分のプロバイダがそれぞれの表に _ID 列を持ちます。 + +

+ + +

クエリ結果を取得する

+

+ クエリ結果を単に表示するだけでなく、他のタスクに使用することもできます。たとえば、単語リストからスペリングを取得して、それを他のプロバイダで検索するといった操作も可能です。 + +そのためには、次のように {@link android.database.Cursor} の行に操作を繰り返します。 +

+
+
+// Determine the column index of the column named "word"
+int index = mCursor.getColumnIndex(UserDictionary.Words.WORD);
+
+/*
+ * Only executes if the cursor is valid. The User Dictionary Provider returns null if
+ * an internal error occurs. Other providers may throw an Exception instead of returning null.
+ */
+
+if (mCursor != null) {
+    /*
+     * Moves to the next row in the cursor. Before the first movement in the cursor, the
+     * "row pointer" is -1, and if you try to retrieve data at that position you will get an
+     * exception.
+     */
+    while (mCursor.moveToNext()) {
+
+        // Gets the value from the column.
+        newWord = mCursor.getString(index);
+
+        // Insert code here to process the retrieved word.
+
+        ...
+
+        // end of while loop
+    }
+} else {
+
+    // Insert code here to report an error if the cursor is null or the provider threw an exception.
+}
+
+

+ {@link android.database.Cursor} の実装には「取得」メソッドがいくつか備わっており、オブジェクトからさまざまなタイプのデータを取得できます。 +たとえば、前述のスニペットは {@link android.database.Cursor#getString getString()} を使用しています。 +さらに、列のデータタイプを示す値を返す {@link android.database.Cursor#getType getType()} メソッドも使用しています。 + + +

+ + + +

コンテンツ プロバイダ パーミッション

+

+ プロバイダのアプリケーションでは、他のアプリケーションがプロバイダのデータにアクセスするのに必要なパーミッションを指定できます。 +これらのパーミッションを設定しておけば、ユーザーはアプリケーションがアクセスしようとしているデータの種類を把握できます。 +他のアプリケーションは、プロバイダの要件に基づき、そのプロバイダにアクセスするのに必要なパーミッションを要求します。 +エンドユーザーがアプリケーションをインストールする際には、要求されたパーミッションを確認できます。 + +

+

+ プロバイダのアプリケーションでパーミッションを指定していない場合は、他のアプリケーションはプロバイダのデータにアクセスできません。 +ただし、指定したパーミッションに関係なく、プロバイダのアプリケーションのコンポーネントには完全な読み取り権限と書き込み権限を常に付与する必要があります。 + +

+

+ 前述のとおり、単語リスト プロバイダには、データを取得するための android.permission.READ_USER_DICTIONARY パーミッションが必要です。 + + プロバイダには、データの挿入、更新、削除に対してそれぞれ個別の android.permission.WRITE_USER_DICTIONARY パーミッションがあります。 + +

+

+ プロバイダにアクセスするのに必要なパーミッションを取得するには、アプリケーションのマニフェスト ファイルの <uses-permission> 要素を使用してパーミッションを要求する必要があります。 + +Android Package Manager でアプリケーションをインストールする場合は、ユーザーがアプリケーションが要求するすべてのパーミッションを承認する必要があります。 +ユーザーがすべてのパーミッションを承認すると、Package Manager がインストールを続行します。ユーザーが承認しない場合は、Package Manager がインストールを中止します。 + + +

+

+ 次の <uses-permission> 要素は、単語リスト プロバイダへの読み取りアクセスを要求します。 + + +

+
+    <uses-permission android:name="android.permission.READ_USER_DICTIONARY">
+
+

+ プロバイダ アクセスへのパーミッションの影響については、セキュリティとパーミッションに関するガイドをご覧ください。 + +

+ + + +

データを挿入、更新、削除する

+

+ データを収集するには、プロバイダからデータを取得するのと同様に、プロバイダ クライアントとプロバイダの {@link android.content.ContentProvider} 間で操作を行います。 + + 対応する {@link android.content.ContentProvider} のメソッドに渡した引数を使用して、{@link android.content.ContentResolver} のメソッドを呼び出します。 +プロバイダとプロバイダ クライアントが、セキュリティとプロセス間の通信を自動的に処理します。 + +

+

データを挿入する

+

+ プロバイダにデータを挿入するには、{@link android.content.ContentResolver#insert ContentResolver.insert()} メソッドを呼び出します。 + +このメソッドはプロバイダに新しい行を挿入し、その行のコンテンツ URI を返します。 + このスニペットは、単語リスト プロバイダに新しい単語を挿入する方法を示しています。 +

+
+// Defines a new Uri object that receives the result of the insertion
+Uri mNewUri;
+
+...
+
+// Defines an object to contain the new values to insert
+ContentValues mNewValues = new ContentValues();
+
+/*
+ * Sets the values of each column and inserts the word. The arguments to the "put"
+ * method are "column name" and "value"
+ */
+mNewValues.put(UserDictionary.Words.APP_ID, "example.user");
+mNewValues.put(UserDictionary.Words.LOCALE, "en_US");
+mNewValues.put(UserDictionary.Words.WORD, "insert");
+mNewValues.put(UserDictionary.Words.FREQUENCY, "100");
+
+mNewUri = getContentResolver().insert(
+    UserDictionary.Word.CONTENT_URI,   // the user dictionary content URI
+    mNewValues                          // the values to insert
+);
+
+

+ 新しい行のデータは 1 つの {@link android.content.ContentValues} オブジェクトに入ります。これは 1 行カーソルの形式に似ています。 +このオブジェクト内の列は同じデータタイプを持つ必要はなく、値をまったく指定しない場合は、{@link android.content.ContentValues#putNull ContentValues.putNull()} を使用して列を null に設定できます。 + + +

+

+ この列は自動的に保持されるため、スニペットは _ID 列を追加しません。 +プロバイダは、追加するそれぞれの行に、_ID の一意の値を割り当てます。 +通常、プロバイダはこの値を表のプライマリキーとして使用します。 +

+

+ newUri で返されるコンテンツ URI は、新たに追加された行を特定するものであり、次の形式となります。 + +

+
+content://user_dictionary/words/<id_value>
+
+

+ <id_value> は、新しい行の _ID のコンテンツです。 + ほとんどのプロバイダではコンテンツ URI のこの形式を自動的に検出し、特定の行で要求された操作を実行します。 + +

+

+ 返された {@link android.net.Uri} から _ID の値を取得するには、{@link android.content.ContentUris#parseId ContentUris.parseId()} を呼び出します。 + +

+

データを更新する

+

+ 行を更新するには、挿入操作と同じように、値を更新した {@link android.content.ContentValues} オブジェクトと、クエリ操作のときと同じように選択基準を使用します。 + + 使用するクライアント メソッドは {@link android.content.ContentResolver#update ContentResolver.update()} です。 +更新する列の {@link android.content.ContentValues} オブジェクトに値を追加する操作のみが必要になります。 +列のコンテンツを消去する場合は、値を null に設定します。 + +

+

+ 次のスニペットは、ロケール言語が「en」となっているすべての行のロケールを null に変更します。 +戻り値は、更新された行の数となります。 +

+
+// Defines an object to contain the updated values
+ContentValues mUpdateValues = new ContentValues();
+
+// Defines selection criteria for the rows you want to update
+String mSelectionClause = UserDictionary.Words.LOCALE +  "LIKE ?";
+String[] mSelectionArgs = {"en_%"};
+
+// Defines a variable to contain the number of updated rows
+int mRowsUpdated = 0;
+
+...
+
+/*
+ * Sets the updated value and updates the selected words.
+ */
+mUpdateValues.putNull(UserDictionary.Words.LOCALE);
+
+mRowsUpdated = getContentResolver().update(
+    UserDictionary.Words.CONTENT_URI,   // the user dictionary content URI
+    mUpdateValues                       // the columns to update
+    mSelectionClause                    // the column to select on
+    mSelectionArgs                      // the value to compare to
+);
+
+

+ また、{@link android.content.ContentResolver#update ContentResolver.update()} を呼び出すときには、ユーザー入力をサニタイズする必要があります。 +詳細は、悪意のある入力から保護するセクションをご覧ください。 + +

+

データを削除する

+

+ 行の削除は行データの取得と似た操作になります。削除する行の選択基準を指定することで、クライアント メソッドが削除した行の数を返します。 + + 次のスニペットは appid が「user」となっている行を削除します。メソッドによって削除された行の数が返されます。 + +

+
+
+// Defines selection criteria for the rows you want to delete
+String mSelectionClause = UserDictionary.Words.APP_ID + " LIKE ?";
+String[] mSelectionArgs = {"user"};
+
+// Defines a variable to contain the number of rows deleted
+int mRowsDeleted = 0;
+
+...
+
+// Deletes the words that match the selection criteria
+mRowsDeleted = getContentResolver().delete(
+    UserDictionary.Words.CONTENT_URI,   // the user dictionary content URI
+    mSelectionClause                    // the column to select on
+    mSelectionArgs                      // the value to compare to
+);
+
+

+ また、{@link android.content.ContentResolver#delete ContentResolver.delete()} を呼び出すときには、ユーザー入力をサニタイズする必要があります。 +詳細は、悪意のある入力から保護するセクションをご覧ください。 + +

+ +

プロバイダ データタイプ

+

+ コンテンツ プロバイダではさまざまなデータタイプを利用できます。単語リスト プロバイダで提供できるのはテキストのみですが、次のような形式を提供できます。 + +

+ +

+ それ以外にも、プロバイダでは 64KB バイト配列として実装されるバイナリ ラージ オブジェクト(BLOB)もよく使用されます。 +利用可能なデータタイプについては、{@link android.database.Cursor} クラスの「get」メソッドをご覧ください。 + +

+

+ プロバイダの各列のデータタイプは、通常、そのドキュメントに一覧が記載されています。 + 単語リスト プロバイダのデータタイプは、コントラクト クラス {@link android.provider.UserDictionary.Words} のリファレンスに一覧が記載されています(コントラクト クラスの詳細は、コントラクト クラスセクションをご覧ください)。 + + + さらに、{@link android.database.Cursor#getType + Cursor.getType()} を呼び出すことで、データタイプを判断することもできます。 +

+

+ プロバイダは、プロバイダによって定義される各コンテンツ URI の MIME データタイプも保持します。MIME タイプ情報を使用すると、プロバイダが提供するデータをアプリケーションが処理できるかを判断したり、MIME タイプに基づいて処理のタイプを選択したりできます。 + +通常は、複雑なデータ構造やファイルを持つプロバイダで作業をする際に MIME タイプが必要になります。 + +たとえば、連絡先プロバイダの {@link android.provider.ContactsContract.Data} 表は MIME タイプを使用して、それぞれの行に格納される連絡先データのタイプをラベル付けします。 + +コンテンツ URI に対応する MIME タイプを取得するには、{@link android.content.ContentResolver#getType ContentResolver.getType()} を呼び出します。 + +

+

+ 「MIME タイプ リファレンス」セクションでは、標準とカスタムの両方の MIME タイプについて説明しています。 + +

+ + + +

プロバイダ アクセスの別の形式

+

+ アプリケーションの開発では、次に示すプロバイダ アクセスの別の 3 つの形式が重要になります。 +

+ +

+ バッチアクセスとインテント経由の修正については、次のセクションで説明しています。 +

+

バッチアクセス

+

+ 多数の行を挿入する場合や、同じメソッド呼び出しで複数の表に行を挿入する場合は、プロバイダへのバッチアクセスを使用すると便利です。また、一般的には、境界をまたいでプロセス全体にトランザクションとして一連の操作を実行する場合にもバッチアクセスが便利です。 + + +

+

+ 「バッチモード」でプロバイダにアクセスするには、{@link android.content.ContentProviderOperation} オブジェクトの配列を作成してから、{@link android.content.ContentResolver#applyBatch ContentResolver.applyBatch()} を使用してそれらのオブジェクトをコンテンツ プロバイダに送信します。 + + +このメソッドには、特定のコンテンツ URI ではなく、コンテンツ プロバイダの認証局を渡します。そうすることで、配列内のそれぞれの {@link android.content.ContentProviderOperation} オブジェクトを別々の表に対して機能するようになります。 + + +{@link android.content.ContentResolver#applyBatch + ContentResolver.applyBatch()} への呼び出しでは、結果の配列が返されます。 +

+

+ {@link android.provider.ContactsContract.RawContacts} コントラクト クラスの説明には、バッチ挿入を示したコード スニペットが記載されています。 +Contact Manager のサンプル アプリケーションでは、ContactAdder.java ソースファイルでのバッチアクセスの例が紹介されています。 + + + +

+ +

インテント経由のデータアクセス

+

+ インテントを使用すれば、コンテンツ プロバイダに間接的にアクセスできます。アプリケーションにアクセス パーミッションがない場合でも、パーミッションを持つアプリケーションから結果のインテントを取得したり、パーミッションを持つアプリケーションをアクティベートしてそのアプリケーションをユーザーに使用させたりすることで、ユーザーがプロバイダのデータにアクセスできるようになります。 + + + +

+

一時的なパーミッションによりアクセスを取得する

+

+ 適切なアクセス パーミッションがない場合でも、パーミッションを持つアプリケーションにインテントを送信し、「URI」パーミッションを含む結果のインテントを受け取ることで、コンテンツ プロバイダのデータにアクセスできます。 + + + これらのパーミッションは特定のコンテンツ URI のパーミッションであり、パーミッションを受け取ったアクティビティが終了するまで効力を持ちます。 +永続的なパーミッションを持つアプリケーションは、次のように結果のインテントにフラグを設定し、一時的なパーミッションを付与します。 + +

+ +

+ 注: これらのフラグは、認証局がコンテンツ URI に含まれるプロバイダへの全般的な読み取りと書き込みアクセスを付与するものではありません。アクセスは URI 自身に限定されます。 + +

+

+ プロバイダは、<provider> 要素の android:grantUriPermission 属性や、<provider> 要素の <grant-uri-permission> 子要素を使用して、コンテンツ URI の URI パーミッションを自身のマニフェストで定義します。 + + + + + + + +URI パーミッションのメカニズムについての詳細は、URI パーミッション セクションのセキュリティとパーミッションに関するガイドをご覧ください。 + + +

+

+ たとえば、{@link android.Manifest.permission#READ_CONTACTS} パーミッションを持たない場合でも、連絡先プロバイダの連絡先のデータを取得できます。 +連絡先の誕生日にグリーティング カードをオンラインで送信するアプリケーションなどにこのメカニズムを利用できます。 +ユーザーのすべての連絡先やそのすべての情報へのアクセス件を付与する {@link android.Manifest.permission#READ_CONTACTS} を要求する代わりに、アプリケーションで使用する連絡先をユーザーが制御できるように設定することもできます。 + + +そのためには、次の手順を使用します。 +

+
    +
  1. + アプリケーションが、メソッド {@link android.app.Activity#startActivityForResult + startActivityForResult()} を使用して、アクション {@link android.content.Intent#ACTION_PICK} と「連絡先」MIME タイプ {@link android.provider.ContactsContract.RawContacts#CONTENT_ITEM_TYPE} を含むインテントを送信します。 + + + +
  2. +
  3. + このインテントは連絡帳アプリの「selection」アクティビティのインテント フィルタに一致するため、アクティビティがフォアグラウンドに移動します。 + +
  4. +
  5. + selection アクティビティでは、更新する連絡先をユーザーが選択します。 +この操作が行われると、selection が {@link android.app.Activity#setResult setResult(resultcode, intent)} を呼び出し、アプリケーションに戻すインテントを設定します。 + +インテントには、ユーザーが選択した連絡先のコンテンツ URI と、「エクストラ」フラグ {@link android.content.Intent#FLAG_GRANT_READ_URI_PERMISSION} が含まれます。 + +これらのフラグがあることで、コンテンツ URI がポイントする連絡先のデータを読み取るための URI パーミッションがアプリに付与されます。その後、selection アクティビティによって、アプリケーションに制御を返すための {@link android.app.Activity#finish()} が呼び出されます。 + + + +
  6. +
  7. + アクティビティがフォアグラウンドに戻り、システムがアクティビティの {@link android.app.Activity#onActivityResult onActivityResult()} メソッドを呼び出します。 + +このメソッドは、連絡帳アプリの selection アクティビティによって作成された結果のインテントを受け取ります。 + +
  8. +
  9. + 結果のインテントのコンテンツ URI を使用すれば、マニフェストで永続的なアクセス パーミッションをプロバイダに要求していなくても、連絡先プロバイダから連絡先のデータを読み取ることができます。 + +その後、連絡先の誕生日情報やメールアドレスを取得し、グリーティング カードをオンラインで送信できます。 + +
  10. +
+

別のアプリケーションを使用する

+

+ パーミッションを持つアプリケーションをアクティベートし、そのアプリケーションをユーザーが使用すれば、アクセス パーミッションを持たないデータをユーザーが修正できるようになります。 + +

+

+ たとえば、アプリケーションの挿入 UI をアクティベートできる {@link android.content.Intent#ACTION_INSERT} インテントをカレンダー アプリケーションが受け入れるとします。このインテントに「エクストラ」データを渡すことで、アプリケーションがそのデータを使用して、事前に入力された UI を作成します。繰り返し発生するイベントは複雑な構文を持つため、カレンダー プロバイダにイベントを挿入する場合は、{@link android.content.Intent#ACTION_INSERT} でカレンダー アプリをアクティベートしてから、ユーザーにイベントを挿入してもらうことをお勧めします。 + + + + + +

+ +

コントラクト クラス

+

+ コントラクト クラスは、アプリケーションがコンテンツ URI、列名、インテント アクション、コンテンツのその他の機能で作業する際に役立つ定数を定義します。 +プロバイダでコントラクト クラスが自動的に含まれることはありません。プロバイダのデベロッパーはコントラクト クラスを定義して、その他のデベロッパーが使用できるようにする必要があります。 + +Android プラットフォームに含まれる多くのプロバイダは、パッケージ {@link android.provider} 内に対応するコントラクト クラスを持ちます。 + +

+

+ たとえば、単語リスト プロバイダは、コンテンツ URI と列名の定数を含む、コントラクト クラス {@link android.provider.UserDictionary} を持つとします。 +「words」表のコンテンツ URI は、定数 {@link android.provider.UserDictionary.Words#CONTENT_URI UserDictionary.Words.CONTENT_URI} で定義されます。 + + + さらに、{@link android.provider.UserDictionary.Words} クラスは、このガイドのサンプルのスニペットで使用される、列名の定数を持ちます。 +たとえば、クエリの投影は次のように定義できます。 + +

+
+String[] mProjection =
+{
+    UserDictionary.Words._ID,
+    UserDictionary.Words.WORD,
+    UserDictionary.Words.LOCALE
+};
+
+

+ もう一方のコントラクト クラスは、連絡先プロバイダのクラス {@link android.provider.ContactsContract} です。 + このクラスのリファレンスには、サンプルのコード スニペットが記載されています。サブクラスの 1 つである {@link android.provider.ContactsContract.Intents.Insert} は、インテントとインテント データの定数を含むコントラクト クラスです。 + + +

+ + + +

MIME タイプ リファレンス

+

+ コンテンツ プロバイダは、標準の MIME メディア タイプかカスタムの MIME タイプ文字列、またはその両方を返すことができます。 +

+

+ MIME タイプは次の形式となります +

+
+type/subtype
+
+

+ たとえば、よく利用される MIME タイプ text/html は、text タイプと html サブタイプを持ちます。 +プロバイダから URI に対してこのタイプが返される場合、その URI を使用するクエリは HTML タグを含むテキストを返すことになります。 + +

+

+ 「ベンダー固有の」MIME タイプとも呼ばれるカスタムの MIME タイプ文字列には、より複雑な typesubtype の値が含まれます。 +複数行の場合、type 値は常に +

+
+vnd.android.cursor.dir
+
+

+ となり、1 つの行の場合は +

+
+vnd.android.cursor.item
+
+

+ となります。 +

+

+ subtype はプロバイダ固有の値になります。通常、Android 組み込みプロバイダは単純な subtype を使用します。 +たとえば、連絡先アプリケーションが電話番号の行を作成すると、行には次のように MIME タイプが設定されます。 + +

+
+vnd.android.cursor.item/phone_v2
+
+

+ subtype の値は単純に phone_v2 となっていることがわかります。 +

+

+ 他のプロバイダ デベロッパーは、プロバイダの認証局と表明に基づいて独自のパターンの subtype を作成できます。 +たとえば、列車の時刻表を含むプロバイダについて考えてみます。 + プロバイダの認証局は com.example.trains であり、表 Line1、Line2、Line3 を持ちます。 +表 Line1 のコンテンツ URI +

+

+

+content://com.example.trains/Line1
+
+

+ に対しては、プロバイダは次の MIME タイプを返します +

+
+vnd.android.cursor.dir/vnd.example.line1
+
+

+ 表 Line2 の行 5 のコンテンツ URI +

+
+content://com.example.trains/Line2/5
+
+

+ に対しては、プロバイダは次の MIME タイプを返します +

+
+vnd.android.cursor.item/vnd.example.line2
+
+

+ ほとんどのコンテンツ プロバイダが、使用する MIME タイプに対してコントラクト クラス定数を定義します。たとえば、連絡先プロバイダのコントラクト クラス {@link android.provider.ContactsContract.RawContacts} は、1 つの未加工連絡先行の MIME タイプに、定数 {@link android.provider.ContactsContract.RawContacts#CONTENT_ITEM_TYPE} を定義します。 + + + + +

+

+ 1 つの行のコンテンツ URI については、コンテンツ URI セクションをご覧ください。 + +

diff --git a/docs/html-intl/intl/ja/guide/topics/providers/content-provider-creating.jd b/docs/html-intl/intl/ja/guide/topics/providers/content-provider-creating.jd new file mode 100644 index 0000000000000000000000000000000000000000..af2181482d33925bfd7bb69e0eade3525b1fee51 --- /dev/null +++ b/docs/html-intl/intl/ja/guide/topics/providers/content-provider-creating.jd @@ -0,0 +1,1214 @@ +page.title=コンテンツ プロバイダの作成 +@jd:body + + + +

+ コンテンツ プロバイダは、データの中央リポジトリへのアクセスを管理します。Android アプリケーションでは 1 つ以上のクラスとしてプロバイダを実装し、要素をマニフェスト ファイルで実装します。 + +いずれか 1 つのクラスがサブラス {@link android.content.ContentProvider} を実装します。これは、プロバイダと他のアプリケーションとの間のインターフェースになります。 + +コンテンツ プロバイダは他のアプリケーションへのデータの提供を意図したものですが、ユーザーがプロバイダに管理されるデータを照会、修正するアクティビティをアプリケーション内に設定することもできます。 + + +

+

+ このトピックの残りの部分では、コンテンツ プロバイダと使用する API のリストをビルドするための基本的な手順を挙げていきます。 + +

+ + + +

ビルドを開始する前に

+

+ ビルドを開始する前に、次の操作を行います。 +

+
    +
  1. + コンテンツ プロバイダが必要かどうかを決定します。次のような機能を 1 つ以上提供する場合に、コンテンツ プロバイダをビルドする必要があります。 + +
      +
    • 他のアプリケーションに複雑なデータやファイルを提供する。
    • +
    • アプリから他のアプリへの複雑なデータのコピーをユーザーに許可する。
    • +
    • 検索フレームワークを使用してカスタムの検索候補を提供する。
    • +
    +

    + 完全に独自アプリケーション内だけで使用する場合は、SQLite データベースを使用するプロバイダは必要はありません。 + +

    +
  2. +
  3. + 必要かどうかを決定していない場合は、プロバイダの詳細については「コンテンツ プロバイダの基本」のトピックをご覧ください。 + + +
  4. +
+

+ 次に、以下の手順に従ってプロバイダをビルドします。 +

+
    +
  1. + データの未処理のストレージを設計します。コンテンツ プロバイダは次の 2 つの方法でデータを提供します。 +
    +
    + ファイルデータ +
    +
    + 通常、写真、オーディオ、ビデオなどのファイルの形式となるデータです。 +ファイルはアプリケーションのプライベート スペースに格納します。 +ファイルに対する別のアプリケーションからの要求に応じて、プロバイダからファイルへのハンドルが提供されます。 + +
    +
    + 「構造化」データ +
    +
    + 通常、データベース、配列、類似の構造の形式となるデータです。 + テーブルの行と列に互換性を持つ形式でデータが格納されます。行は、担当者や在庫にあるアイテムなどのエンティティを表します。 +列は、担当者の名前やアイテムの価格などの、エンティティの一部のデータを表します。 +一般的にこのタイプのデータは SQLite データベースに格納しますが、任意のタイプの永続ストレージを使用できます。 + +Android システムで使用できるストレージ タイプの詳細は、データ ストレージを設計するをご覧ください。 + + +
    +
    +
  2. +
  3. + {@link android.content.ContentProvider} クラスの具体的な実装とそれに必要なメソッドを定義します。 +このクラスは、データと Android システムのそれ以外の部分とのインターフェースとなります。 +このクラスの詳細は、ContentProvider クラスを実装するセクションをご覧ください。 + +
  4. +
  5. + プロバイダの認証局の文字列、コンテンツ URI、列名を定義します。プロバイダのアプリケーションでインテントを処理する場合は、インテント アクション、エクストラ データ、フラグも定義します。 + +さらに、データにアクセスするアプリケーションに必要なパーミッションも定義します。 +これらの値はすべて定数として個別のコントラクト クラスに定義するようにします。そうしておくと、後で他のデベロッパーに対してこのクラスを公開できます。 +コンテンツ URI の詳細は、コンテンツ URI を設計するセクションをご覧ください。 + + + インテントの使用に関する詳細については、インテントとデータアクセスセクションをご覧ください。 + +
  6. +
  7. + サンプルデータや、プロバイダとクラウドベースのデータの間でデータを同期する {@link android.content.AbstractThreadedSyncAdapter} の実装などの、その他のオプション部分を追加します。 + + +
  8. +
+ + + +

データ ストレージを設計する

+

+ コンテンツ プロバイダは、構造化された形式で保存されているデータへのインターフェースです。インターフェースを作成する前に、データの格納方法を決定しておく必要があります。 +データは任意の形式で保存でき、必要に応じてデータの読み取りと書き込みを行うためのインターフェースを設計できます。 + +

+

+ Android には、次のように、いくつかのデータ格納テクノロジーがあります。 +

+ +

+ データ設計上の考慮事項 +

+

+ 次に、プロバイダのデータ構造を設計する際のヒントを示します。 +

+ + +

コンテンツ URI を設計する

+

+ コンテンツ URI は、プロバイダのデータを特定する URI です。コンテンツ URI には、プロバイダ全体の識別名(認証局)とテーブルやファイルをポイントする名前(パス)が含まれます。 + +オプションの ID 部分は、テーブル内の個々の行を指します。 +{@link android.content.ContentProvider} のそれぞれのデータアクセス メソッドは引数としてコンテンツ URI を使用します。これにより、アクセスするテーブル、行、ファイルを決定できます。 + + +

+

+ コンテンツ URI の基本については、「コンテンツ プロバイダの基本」トピックをご覧ください。 + + +

+

認証局を設計する

+

+ 通常、プロバイダは、Android 内部の名前として使用される認証局を 1 つ持ちます。他のプロバイダとの競合を避けるためには、(逆に)プロバイダの認証局の基礎としてインターネット ドメインの所有権を使用する必要があります。 + +この推奨事項は Android パッケージ名にもあてはまるため、プロバイダを含むパッケージの名前の拡張子として、プロバイダの認証局を定義できます。 + +たとえば、Android パッケージ名が com.example.<appname> の場合、プロバイダに認証局 com.example.<appname>.provider を付与する必要があります。 + + +

+

パス構造を設計する

+

+ デベロッパーは通常、個々のテーブルを指すパスを末尾に追加して認証局からコンテンツ URI を作成します。 +たとえば、table1table2 の 2 つのテーブルがある場合、前の例の認証局を組み合わせてコンテンツ URI com.example.<appname>.provider/table1com.example.<appname>.provider/table2 を作成します。 + + + +パスは 1 つのセグメントに限定されず、パスの各レベルにテーブルを作成する必要はありません。 + +

+

コンテンツ URI ID を処理する

+

+ 慣例として、URI の末尾にある行の ID 値を持つコンテンツ URI を受け取ることで、プロバイダはテーブル内の 1 つの行へのアクセスを提供します。さらに、慣例として、プロバイダは ID 値をテーブルの _ID 列とマッチングし、一致する行に対して要求されたアクセスを実行します。 + + + +

+

+ この慣例により、プロバイダにアクセスするアプリの一般的なパターンを簡単に設計できます。アプリはプロバイダにクエリを実行し、{@link android.widget.CursorAdapter} を使用して、取得した {@link android.database.Cursor} を {@link android.widget.ListView} に表示します。 + + + {@link android.widget.CursorAdapter} の定義には、{@link android.database.Cursor} のいずれかの列を _ID に設定する必要があります + +

+

+ その後、ユーザーはデータの確認や修正のために、表示された行のいずれかを UI から選択します。 +アプリは {@link android.widget.ListView} を返す {@link android.database.Cursor} から対応する行を取得し、この行の _ID 値を取得し、その値をコンテンツ URI の末尾に追加して、プロバイダにアクセス要求を送ります。 + +その後、プロバイダは、ユーザーが選択した行にクエリや修正を実行します。 + +

+

コンテンツ URI パターン

+

+ 受け取ったコンテンツ URI に対するアクションを簡単に選択できるように、プロバイダ API には {@link android.content.UriMatcher} という便利なクラスが用意されています。このクラスはコンテンツ URI 「パターン」を正数値にマッピングします。 + +この正数値を switch 文で使用することで、特定のパターンに一致する 1 つ以上のコンテンツ URI に目的のアクションを選択できます。 + +

+

+ コンテンツ URI パターンは、次のワイルドカード文字を使用するコンテンツ URI に一致します。 +

+ +

+ コンテンツ URI 処理の設計とコーディングの例として、テーブルを指す次のコンテンツ URI を認識する認証局 com.example.app.provider を持つプロバイダを考えてみます。 + + +

+ +

+ さらに、行 ID が末尾に追加されていると、プロバイダではこれらのコンテンツ URI が認識されます。たとえば、table31 で特定される行は content://com.example.app.provider/table3/1 になります。 + + +

+

+ 次のようなコンテンツ URI パターンを使用できます。 +

+
+
+ content://com.example.app.provider/* +
+
+ プロバイダの任意のコンテンツ URI に一致します。 +
+
+ content://com.example.app.provider/table2/*: +
+
+ テーブル dataset1dataset2 のコンテンツ URI に一致しますが、table1table3 のコンテンツ URI には一致しません。 + + +
+
+ content://com.example.app.provider/table3/#: table3 の 1 つの行のコンテンツ URI に一致します。6 で特定される行は content://com.example.app.provider/table3/6 になります。 + + + +
+
+

+ 次のコード スニペットでは、{@link android.content.UriMatcher} のメソッドが機能する仕組みを示します。 + このコードでは、テーブルにコンテンツ URI パターン content://<authority>/<path> を使用し、1 つの行に content://<authority>/<path>/<id> を使用することで、表全体の URI と 1 つの行の URI を異なる方法で処理します。 + + + +

+

+ メソッド {@link android.content.UriMatcher#addURI(String, String, int) addURI()} は認証局とパスを正数値にマッピングします。 +メソッド {@link android.content.UriMatcher#match(Uri) + match()} は URI に正数値を返します。次のように、switch 文によって、クエリをテーブル全体に実行するか、1 つのレコードに実行するかが決まります。 + +

+
+public class ExampleProvider extends ContentProvider {
+...
+    // Creates a UriMatcher object.
+    private static final UriMatcher sUriMatcher;
+...
+    /*
+     * The calls to addURI() go here, for all of the content URI patterns that the provider
+     * should recognize. 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
+     */
+    sUriMatcher.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.
+     */
+    sUriMatcher.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 (sUriMatcher.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 is not recognized, you should do some error handling here.
+        }
+        // call the code to actually do the query
+    }
+
+

+ 別の {@link android.content.ContentUris} には、コンテンツ URI の一部である id で作業するための便利なメソッドが備わっています。 +クラス {@link android.net.Uri} と {@link android.net.Uri.Builder} には、既存の {@link android.net.Uri} オブジェクトを解析して新しいオブジェクトをビルドするための便利なメソッドがあります。 + + +

+ + +

ContentProvider クラスを実装する

+

+ {@link android.content.ContentProvider} インスタンスは、他のアプリケーションからの要求を処理することで、一連の構造化されたデータへのアクセスを管理します。 +どのような形式でアクセスする場合も、最終的には {@link android.content.ContentResolver} が呼び出されます。その後 {@link android.content.ContentProvider} の具象メソッドが呼び出され、アクセスを取得します。 + + +

+

必須メソッド

+

+ 抽象クラス {@link android.content.ContentProvider} は 6 つの抽象メソッドを定義しますが、これらのメソッドは独自の具象サブクラスの一部として実装する必要があります。 +次のように、{@link android.content.ContentProvider#onCreate() onCreate()} を除くこれらのメソッドは、コンテンツ プロバイダへのアクセスを試みるクライアント アプリケーションによって呼び出されます。 + + +

+
+
+ {@link android.content.ContentProvider#query(Uri, String[], String, String[], String) + query()} +
+
+ プロバイダからデータを取得します。引数を使って、クエリを実行するテーブル、返す行と列、結果の並び順を選択します。 + + データは {@link android.database.Cursor} オブジェクトとして返されます。 +
+
+ {@link android.content.ContentProvider#insert(Uri, ContentValues) insert()} +
+
+ プロバイダに新しい行を挿入します。引数を使って、挿入先のテーブルを選択し、使用する列の値を取得します。 +新たに挿入した行のコンテンツ URI が返されます。 + +
+
+ {@link android.content.ContentProvider#update(Uri, ContentValues, String, String[]) + update()} +
+
+ プロバイダ内の既存の行を更新します。引数を使って、更新するテーブルと行を選び、更新された列の値を取得します。 +更新された行の数が返されます。 +
+
+ {@link android.content.ContentProvider#delete(Uri, String, String[]) delete()} +
+
+ プロバイダから行を削除します。引数を使って、削除するテーブルと行を選びます。 +削除された行の数が返されます。 +
+
+ {@link android.content.ContentProvider#getType(Uri) getType()} +
+
+ コンテンツ URI に対応する MIME タイプを返します。このメソッドの詳細は、コンテンツ プロバイダ MIME を実装するセクションをご覧ください。 + +
+
+ {@link android.content.ContentProvider#onCreate() onCreate()} +
+
+ プロバイダを初期化します。プロバイダが作成されると、Android システムがこのメソッドを即座に呼び出します。 +{@link android.content.ContentResolver} オブジェクトがアクセスを試みるまでは、プロバイダは作成されません。 + +
+
+

+ これらのメソッドが持つ署名は、同じ名前の {@link android.content.ContentResolver} メソッドの署名と同じになります。 + +

+

+ これらのメソッドを実装する場合は、次の点を考慮する必要があります。 +

+ +

query() メソッドを実装する

+

+ {@link android.content.ContentProvider#query(Uri, String[], String, String[], String) + ContentProvider.query()} メソッドは {@link android.database.Cursor} オブジェクトを返す必要があります。失敗した場合は、{@link java.lang.Exception} をスローします。 + +SQLite データベースをデータストレージとして使用する場合は、{@link android.database.sqlite.SQLiteDatabase} クラスのいずれかの query() メソッドが返す {@link android.database.Cursor} を返します。 + + + クエリがどの行にも一致しない場合は、{@link android.database.Cursor#getCount()} メソッドが 0 を返す {@link android.database.Cursor} インスタンスを返す必要があります。 + + クエリプロセス中にエラーが発生した場合にのみ null を返します。 +

+

+ SQLite データベースをデータストレージとして使用しない場合は、{@link android.database.Cursor} クラスのいずれかの具象サブクラスを使用します。 +たとえば、{@link android.database.MatrixCursor} クラスは、各行が {@link java.lang.Object} の配列となるカーソルを実装します。 +このクラスでは、{@link android.database.MatrixCursor#addRow(Object[]) addRow()} を使って新しい行を追加します。 + +

+

+ Android システムは、プロセスの境界をまたいで {@link java.lang.Exception} を送信できるように設定する必要があることにご注意ください。 +Android では、クエリエラーを処理するのに便利な、次の例外を送信できます。 + +

+ +

insert() メソッドを実装する

+

+ {@link android.content.ContentProvider#insert(Uri, ContentValues) insert()} メソッドは、{@link android.content.ContentValues} 引数の値を使用して、適切なテーブルに新しい行を追加します。 + +列名が {@link android.content.ContentValues} 引数にない場合は、プロバイダ コードがデータベース スキーマのいずれかで、デフォルト値を設定できます。 + + +

+

+ このメソッドは新しい行のコンテンツ URI を返します。作成するには、{@link android.content.ContentUris#withAppendedId(Uri, long) withAppendedId()} を使用して、新しい行の _ID(または他のプライマリキー)の値をテーブルのコンテンツ URI の末尾に追加します。 + + +

+

delete() メソッドを実装する

+

+ {@link android.content.ContentProvider#delete(Uri, String, String[]) delete()} メソッドでは、データストレージから行を物理的に削除する必要はありません。 +プロバイダで同期アダプタを使用する場合は、行全体を削除するのではなく、削除された行に「削除」フラグを付けるようにします。 + +同期アダプタは削除された行を探して、プロバイダから削除される前に、サーバーから行を削除できます。 + +

+

update() メソッドを実装する

+

+ {@link android.content.ContentProvider#update(Uri, ContentValues, String, String[]) + update()} メソッドは {@link android.content.ContentProvider#insert(Uri, ContentValues) insert()} が使用するのと同じ {@link android.content.ContentValues} 引数、{@link android.content.ContentProvider#delete(Uri, String, String[]) delete()} と {@link android.content.ContentProvider#query(Uri, String[], String, String[], String) + ContentProvider.query()} が使用するのと同じ selectionselectionArgs 引数を使用します。 + + + +これにより、これらのメソッド間でコードを再利用できます。 +

+

onCreate() メソッドを実装する

+

+ Android システムはプロバイダを起動する際に {@link android.content.ContentProvider#onCreate() + onCreate()} を呼び出します。このメソッドでは迅速に実行できる初期化タスクのみを実行するようにし、プロバイダが実際にデータの要求を受け取るまでは、データベースの作成とデータロードを保留します。 + +{@link android.content.ContentProvider#onCreate() onCreate()} で冗長なタスクを行う場合は、プロバイダの起動が遅くなります。 + +同様に、プロバイダから他のアプリケーションへの応答も遅くなります。 + +

+

+ たとえば、SQLite データベースを使用すると、{@link android.content.ContentProvider#onCreate() ContentProvider.onCreate()} に {@link android.database.sqlite.SQLiteOpenHelper} オブジェクトを作成し、データベースを初めて開くときに SQL テーブルを作成できます。 + + +この操作を簡単にするために、{@link android.database.sqlite.SQLiteOpenHelper#getWritableDatabase + getWritableDatabase()} を初めて呼び出すときには、{@link android.database.sqlite.SQLiteOpenHelper#onCreate(SQLiteDatabase) + SQLiteOpenHelper.onCreate()} メソッドが自動的に呼び出されます。 + + +

+

+ 次の 2 つのスニペットは、{@link android.content.ContentProvider#onCreate() ContentProvider.onCreate()} と {@link android.database.sqlite.SQLiteOpenHelper#onCreate(SQLiteDatabase) + SQLiteOpenHelper.onCreate()} との間のやり取りを表しています。 + +最初のスニペットは {@link android.content.ContentProvider#onCreate() ContentProvider.onCreate()} の実装です。 + +

+
+public class ExampleProvider extends ContentProvider
+
+    /*
+     * Defines a handle to the database helper object. The MainDatabaseHelper class is defined
+     * in a following snippet.
+     */
+    private MainDatabaseHelper mOpenHelper;
+
+    // Defines the database name
+    private static final String DBNAME = "mydb";
+
+    // Holds the database object
+    private SQLiteDatabase db;
+
+    public boolean onCreate() {
+
+        /*
+         * Creates a new helper object. This method always returns quickly.
+         * Notice that the database itself isn't created or opened
+         * until SQLiteOpenHelper.getWritableDatabase is called
+         */
+        mOpenHelper = new MainDatabaseHelper(
+            getContext(),        // the application context
+            DBNAME,              // the name of the database)
+            null,                // uses the default SQLite cursor
+            1                    // the version number
+        );
+
+        return true;
+    }
+
+    ...
+
+    // Implements the provider's insert method
+    public Cursor insert(Uri uri, ContentValues values) {
+        // Insert code here to determine which table to open, handle error-checking, and so forth
+
+        ...
+
+        /*
+         * Gets a writeable database. This will trigger its creation if it doesn't already exist.
+         *
+         */
+        db = mOpenHelper.getWritableDatabase();
+    }
+}
+
+

+ 次のスニペットは {@link android.database.sqlite.SQLiteOpenHelper#onCreate(SQLiteDatabase) + SQLiteOpenHelper.onCreate()} の実装であり、ヘルパークラスを含みます。 + +

+
+...
+// A string that defines the SQL statement for creating a table
+private static final String SQL_CREATE_MAIN = "CREATE TABLE " +
+    "main " +                       // Table's name
+    "(" +                           // The columns in the table
+    " _ID INTEGER PRIMARY KEY, " +
+    " WORD TEXT"
+    " FREQUENCY INTEGER " +
+    " LOCALE TEXT )";
+...
+/**
+ * Helper class that actually creates and manages the provider's underlying data repository.
+ */
+protected static final class MainDatabaseHelper extends SQLiteOpenHelper {
+
+    /*
+     * Instantiates an open helper for the provider's SQLite data repository
+     * Do not do database creation and upgrade here.
+     */
+    MainDatabaseHelper(Context context) {
+        super(context, DBNAME, null, 1);
+    }
+
+    /*
+     * Creates the data repository. This is called when the provider attempts to open the
+     * repository and SQLite reports that it doesn't exist.
+     */
+    public void onCreate(SQLiteDatabase db) {
+
+        // Creates the main table
+        db.execSQL(SQL_CREATE_MAIN);
+    }
+}
+
+ + + +

ContentProvider MIME タイプを実装する

+

+ {@link android.content.ContentProvider} クラスには、MIME タイプを返すための次の 2 つのクラスがあります。 +

+
+
+ {@link android.content.ContentProvider#getType(Uri) getType()} +
+
+ 任意のプロバイダに実装する必要がある必須メソッドの 1 つです。 +
+
+ {@link android.content.ContentProvider#getStreamTypes(Uri, String) getStreamTypes()} +
+
+ ファイルを提供するプロバイダの場合に、実装が考えられるメソッドです。 +
+
+

テーブルの MIME タイプ

+

+ {@link android.content.ContentProvider#getType(Uri) getType()} メソッドは、MIME 形式で {@link java.lang.String} を返します。これは、コンテンツ URI 引数によって返されるデータのタイプを表します。 + +{@link android.net.Uri} 引数には特定の URI ではなくパターンを指定することもできます。この場合、パターンに一致するコンテンツ URI に関連付けられているデータのタイプを返します。 + + +

+

+ テキスト、HTML、JPEG といった一般的なタイプのデータの場合、{@link android.content.ContentProvider#getType(Uri) getType()} では該当するデータの標準的な MIME タイプを返す必要があります。 + +利用できるこれらの標準的なタイプの詳細なリストは、「IANA MIME Media Types」のウェブサイトをご覧ください。 + + +

+

+ テーブル データの 1 つ以上の行を指すコンテンツ URI の場合、{@link android.content.ContentProvider#getType(Uri) getType()} は、Android のベンダー固有の MIME 形式で MIME タイプを返す必要があります。 + + +

+ +

+ たとえば、プロバイダの認証局が com.example.app.provider の場合、テーブル名は table1 となり、table1 の複数行の MIME タイプは次のようになります。 + + +

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

+ table1 の 1 つの行の場合は、MIME タイプは次のようになります。 +

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

ファイルの MIME タイプ

+

+ ファイルを提供するプロバイダの場合は、{@link android.content.ContentProvider#getStreamTypes(Uri, String) getStreamTypes()} を実装します。 + + このメソッドは、プロバイダが特定のコンテンツ URI に対して返すことができるファイルの MIME タイプの {@link java.lang.String} 配列を返します。クライアントで処理する MIME タイプのみを返すには、MIME タイプフィルタ引数によって、提供する MIME タイプをフィルタする必要があります。 + + +

+

+ たとえば、写真画像を .jpg.png.gif 形式で提供するプロバイダについて考えてみます。 + + アプリケーションが {@link android.content.ContentResolver#getStreamTypes(Uri, String) + ContentResolver.getStreamTypes()} をフィルタ文字列 image/* (何らかの形式の「画像」)で呼び出す場合、{@link android.content.ContentProvider#getStreamTypes(Uri, String) + ContentProvider.getStreamTypes()} メソッドは次のような配列を返します。 + + +

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

+ アプリの対象が .jpg ファイルのみであり、アプリが {@link android.content.ContentResolver#getStreamTypes(Uri, String) + ContentResolver.getStreamTypes()} をフィルタ文字列 *\/jpeg で呼び出す場合、{@link android.content.ContentProvider#getStreamTypes(Uri, String) + ContentProvider.getStreamTypes()} は次の項目を返します。 + + +

+{"image/jpeg"}
+
+

+ プロバイダが、フィルタ文字列で要求した MIME タイプを提供していない場合、{@link android.content.ContentProvider#getStreamTypes(Uri, String) getStreamTypes()} は null を返します。 + + +

+ + + +

コントラクト クラスを実装する

+

+ コントラクト クラスは public final クラスであり、URI、列名、MIME タイプ、プロバイダに関連するその他のメタデータを含みます。 +このクラスは、URI や列名などの実際の値が変更された場合にも、プロバイダに正しくアクセスできることを保証することで、プロバイダとその他のアプリケーションとの間にコントラクトを構築します。 + + + +

+

+ さらに、通常、コントラクト クラスではその定数にニーモニック名が設定されているため、デベロッパーが列名や URI に誤った値を使用しにくいようになっています。 +クラスであるため、Javadoc ドキュメントを含めることができます。 +Eclipse などの統合型開発環境では、コントラクト クラスから定数名が自動的に設定され、定数の Javadoc が表示されます。 + + +

+

+ デベロッパーはアプリケーションからコントラクト クラスのクラスファイルにアクセスできませんが、提供する .jar ファイルからクラスファイルをアプリケーションに静的にコンパイルできます。 + +

+

+ {@link android.provider.ContactsContract} クラスとそのネストされたクラスは、コントラクト クラスの例です。 + +

+

コンテンツ プロバイダ パーミッションを実装する

+

+ Android システムのあらゆる領域に対するパーミッションとクラスについては、「セキュリティとパーミッション」トピックをご覧ください。 + + また、トピック「Data Storage」にも、ストレージのさまざまなタイプに有効なセキュリティとパーミッションについて記載されています。 + + 重要な点をまとめると次のようになります。 +

+ +

+ コンテンツ プロバイダ パーミッションを使用してデータへのアクセスを制御する場合は、内部ファイル、SQLite データベース、「クラウド」(リモート サーバーなど)にデータを格納し、ファイルやデータベースを自分のアプリケーションにのみ公開するように設定します。 + + +

+

パーミッションを実装する

+

+ デフォルトではプロバイダにはパーミッションが設定されていないため、基礎となるデータがプライベートに設定されている場合でも、すべてのアプリケーションは自分のプロバイダからの読み取りとプロバイダへの書き込みを実行できます。 +これを変更するには、 + <provider> 要素の属性や子要素を使用して、マニフェスト ファイルでプロバイダのパーミッションを設定します。 + +プロバイダ全体に適用するパーミッションを設定することもできますし、特定のテーブル、特定のレコード、これら 3 つすべてに適用するパーミッションを設定することもできます。 + +

+

+ マニフェスト ファイルで 1 つ以上の + <permission> 要素を使用して、プロバイダのパーミッションを定義します。 +自分のプロバイダに独自のパーミッションを作成するには、 + android:name 属性に Java スタイルのスコーピングを使用します + +たとえば、読み取りパーミッションの名前を com.example.app.provider.permission.READ_PROVIDER とします。 + + +

+

+ 次のリストは、プロバイダ パーミッションの範囲を示しています。プロバイダ全体に適用するパーミッションから始まり、より詳細な範囲のものになっています。 + + より広い範囲を持つパーミッションよりも、範囲が限定されるパーミッションの方が優先されます。 +

+
+
+ プロバイダ レベルで 1 つの読み取りと書き込みのパーミッション +
+
+ 1 つのパーミッションでプロバイダ全体の読み取りと書き込みのアクセスの両方を制御します。 + <provider> 要素の + android:permission 属性で指定します。 + + +
+
+ プロバイダ レベルで別々の読み取りと書き込みパーミッション +
+
+ プロバイダ全体の読み取りパーミッションと書き込みパーミッションです。これらのパーミッションは + <provider> 要素の + android:readPermission 属性と + android:writePermission 属性で指定します。 + + +これらは、 + android:permission で要求するパーミッションよりも優先されます。 + +
+
+ パスレベルのパーミッション +
+
+ プロバイダのコンテンツ URI の読み取り、書き込み、読み取り / 書き込みパーミッションです。 + <provider> 要素の + <path-permission> 子要素を使用して、制御対象の各 URI を指定します。 + + +指定する各コンテンツ URI に対して、読み取り / 書き込みパーミッション、読み取りパーミッション、書き込みパーミッション、3 つすべてを指定できます。 +読み取りパーミッションと書き込みパーミッションは、読み取り / 書き込みパーミッションに優先します。 +また、パスレベルのパーミッションはプロバイダ レベルのパーミッションに優先します。 + +
+
+ 一時的なパーミッション +
+
+ アプリケーションへの一時的なアクセスを付与するパーミッション レベルです。アプリケーションに通常必要とするパーミッションが付与されていない場合でも付与できます。 +一時的なアクセス機能により、アプリケーションのマニフェストに必要なパーミッションの数を減らせます。 + +一時的なパーミッションを有効にすると、プロバイダへの「永続的な」パーミッションを必要とするアプリケーションのみが、継続的にすべてのデータにアクセスできます。 + + +

+ プロバイダからの写真の添付ファイルを外部の画像ビューワ アプリケーションで表示する場合に、メール プロバイダとアプリに実装するパーミッションを考えてみます。 + +パーミッションを要求しなくても、画像ビューワに必要なアクセスを付与するには、写真のコンテンツ URI に一時的なパーミッションを設定します。 +ユーザーが写真を表示しようとしたときに、アプリから写真のコンテンツ URI とパーミッション フラグを含むインテントを画像ビューワに送信するようにメール アプリを設計します。 + +ビューワにプロバイダの通常の読み取りパーミッションが付与されていない場合でも、画像ビューワはメール プロバイダにクエリを実行して写真を取得します。 + + +

+

+ 一時的なパーミッションを有効にするには、 + <provider> 要素の + android:grantUriPermissions 属性を設定するか、1 つ以上の + <grant-uri-permission> 子要素を + <provider> 要素に追加します。 + + + +一時的なパーミッションを使用する場合、プロバイダからのコンテンツ URI のサポートを削除したときは常に {@link android.content.Context#revokeUriPermission(Uri, int) + Context.revokeUriPermission()} を呼び出す必要があります。そうすると、コンテンツ URI が一時的なパーミッションに関連付けられます。 + + +

+

+ 属性の値によって、プロバイダへのアクセスレベルが決まります。 + 属性を true に設定すると、システムによってプロバイダ全体への一時的なパーミッションが付与されます。このパーミッションは、プロバイダ レベルやパスレベルのパーミッションで必要になるその他のパーミッションよりも優先されます。 + + +

+

+ このフラグを false に設定する場合は、 + <grant-uri-permission> 子要素を + <provider> 要素に追加する必要があります。 + +それぞれの子要素は、一時的なアクセスが付与される 1 つ以上のコンテンツ URI を指定します。 + +

+

+ 一時的なアクセスをアプリケーションに委任するには、インテントに {@link android.content.Intent#FLAG_GRANT_READ_URI_PERMISSION} フラグか {@link android.content.Intent#FLAG_GRANT_WRITE_URI_PERMISSION} フラグ、またはその両方を含める必要があります。 + +これらは、{@link android.content.Intent#setFlags(int) setFlags()} メソッドで設定します。 + +

+

+ + android:grantUriPermissions 属性がない場合は、false であると仮定されます。 + +

+
+
+ + + + +

<provider> 要素

+

+ {@link android.app.Activity} コンポーネントや {@link android.app.Service} コンポーネントと同様に、 + <provider> 要素を使用して {@link android.content.ContentProvider} のサブクラスをアプリケーションのマニフェスト ファイルに定義する必要があります。 + + +Android システムは要素から次の情報を取得します。 + +

+
+ 認証局({@code + android:authorities}) + +
+
+ システム内のプロバイダ全体を特定する識別名です。この属性の詳細は、コンテンツ URI を設計するセクションをご覧ください。 + + +
+
+ プロバイダ クラス名( +android:name + ) + +
+
+ {@link android.content.ContentProvider} を実装するクラスです。このクラスの詳細は、ContentProvider クラスを実装するセクションをご覧ください。 + + +
+
+ パーミッション +
+
+ 他のアプリケーションがプロバイダのデータにアクセスするのに必要なパーミッションを指定する属性です。 + + +

+ パーミッションとそれに対応する属性の詳細は、コンテンツ プロバイダ パーミッションを実装するセクションをご覧ください。 + + +

+
+
+ 起動と制御の属性 +
+
+ これらの属性は、Android システムの起動方法とそのタイミング、プロバイダのプロセスの特性、その他の実行時の設定を決定します。 + +
    +
  • + + android:enabled: システムにプロバイダの起動を許可するフラグです。 +
  • +
  • + + android:exported: 他のアプリケーションにこのプロバイダの使用を許可するフラグです。 +
  • +
  • + + android:initOrder: 同じプロセス内の他のプロバイダに対して、このプロバイダを起動する順番です。 + +
  • +
  • + + android:multiProcess: システムに呼び出しクライアントと同じプロセスでのプロバイダの開始を許可するフラグです。 + +
  • +
  • + + android:process: プロバイダを実行するプロセスの名前です。 + +
  • +
  • + + android:syncable: プロバイダのデータをサーバー上のデータと同期することを示すフラグです。 + +
  • +
+

+ 属性に関する詳細は、開発ガイドの + <provider> 要素に関するトピックに記載されています。 + + +

+
+
+ 情報に関する属性 +
+
+ プロバイダのオプションのアイコンとラベルです。 +
    +
  • + + android:icon: プロバイダのアイコンを含むドローアブル リソースです。 + [設定] > [アプリ] > [すべて] にあるアプリのリストのプロバイダラベルの横に表示されるアイコンです。 + +
  • +
  • + + android:label: プロバイダ、データ、その両方を説明する、情報ラベルです。 +[設定] > [アプリ] > [すべて] にあるアプリのリストのプロバイダラベルの横に表示されるアイコンです。 + +
  • +
+

+ 属性に関する詳細は、開発ガイドの + <provider> 要素に関するトピックに記載されています。 + +

+
+
+ + +

インテントとデータアクセス

+

+ {@link android.content.Intent} を使用すると、アプリケーションはコンテンツ プロバイダに間接的にアクセスできます。 + アプリケーションは、{@link android.content.ContentResolver} や {@link android.content.ContentProvider} のメソッドを呼び出しません。 +その代わりに、アクティビティを起動するインテントを送信します。これは通常、プロバイダ独自のアプリケーションの一部となっています。 +対象のアクティビティは UI のデータの取得と表示を担当します。インテントのアクションに応じて、対象のアクティビティにより、ユーザーにプロバイダのデータの修正を求めるメッセージが表示されることがあります。 + + + さらに、インテントには、対象のアクティビティが UI に表示する「エクストラ」データが含まれることがあります。ユーザーは使用前に、このデータを変更してプロバイダのデータを修正することもできます。 + + +

+

+ +

+

+ インテント アクセスを使用して、データの整合性を保証できます。プロバイダのデータの挿入、更新、削除は、厳格に定義されたビジネス ロジックによって規定されることがあります。 +この場合、他のアプリケーションにデータを直接修正できるように許可すると、無効なデータが発生することがあります。 + +デベロッパーがインテント アクセスを使用する場合は、すべての操作を完全に文書化します。 + コードを使用してデータを修正するよりも、独自のアプリケーションの UI を使用したインテント アクセスの方が優れている理由を説明します。 + +

+

+ プロバイダのデータを修正するための受信インテントの処理は、その他のインテントの処理と同じです。 +インテントの使用についての詳細は、「インテントとインテント フィルタ」をご覧ください。 + +

diff --git a/docs/html-intl/intl/ja/guide/topics/providers/content-providers.jd b/docs/html-intl/intl/ja/guide/topics/providers/content-providers.jd new file mode 100644 index 0000000000000000000000000000000000000000..918426ad1480571da839b34520ad203ca320a0c6 --- /dev/null +++ b/docs/html-intl/intl/ja/guide/topics/providers/content-providers.jd @@ -0,0 +1,108 @@ +page.title=コンテンツ プロバイダ +@jd:body + +

+ コンテンツ プロバイダは、一連の構造化されたデータへのアクセスを管理します。データをカプセル化し、データ セキュリティの定義のためのメカニズムを提供します。 +コンテンツ プロバイダは、1 つのプロセス内のデータを別のプロセスで実行されているコードにつなげるための標準的なインターフェースです。 + +

+

+ コンテンツ プロバイダのデータにアクセスする場合は、アプリケーションの {@link android.content.Context} の {@link android.content.ContentResolver} オブジェクトを使用し、クライアントとしてプロバイダとやり取りします。 + + + {@link android.content.ContentResolver} オブジェクトは、{@link android.content.ContentProvider} を実装するクラスのインスタンスである、プロバイダ オブジェクトとやり取りします。 +プロバイダ オブジェクトはクライアントからのデータ要求を受け取り、要求されたアクションを実行し、結果を返します。 + + +

+

+ データを他のアプリケーションと共有しない場合は、独自のプロバイダを開発する必要はありません。 +ただし、独自のアプリケーションでカスタムの検索候補を提供する場合は、独自のプロバイダが必要になります。 +また、自分のアプリケーションから別のアプリケーションに複雑なデータやファイルをコピーして貼り付ける場合も、独自のプロバイダが必要になります。 + +

+

+ Android 自身には、オーディオ、ビデオ、画像、個人的な連絡先情報などのデータを管理するコンテンツ プロバイダが用意されています。 +一部のプロバイダについては、android.provider + パッケージのリファレンスに一覧が記載されています。 + +いくつかの制限もありますが、これらのプロバイダにはすべての Android アプリケーションからアクセスできます。 + +

+ コンテンツ プロバイダの詳細は、次のトピックをご覧ください。 +

+
+
+ コンテンツ プロバイダの基本 + +
+
+ データが表形式にまとめられている場合の、コンテンツ プロバイダでのデータへのアクセス方法。 +
+
+ コンテンツ プロバイダの作成 + +
+
+ 独自のコンテンツ プロバイダの作成方法。 +
+
+ カレンダー プロバイダ + +
+
+ Android プラットフォームの一部であるカレンダー プロバイダへのアクセス方法。 +
+
+ 連絡先プロバイダ + +
+
+ Android プラットフォームの一部である連絡先プロバイダへのアクセス方法。 +
+
diff --git a/docs/html-intl/intl/ja/guide/topics/providers/document-provider.jd b/docs/html-intl/intl/ja/guide/topics/providers/document-provider.jd new file mode 100644 index 0000000000000000000000000000000000000000..b4342dcdd75026079a3a7652a93012d2bf11523f --- /dev/null +++ b/docs/html-intl/intl/ja/guide/topics/providers/document-provider.jd @@ -0,0 +1,916 @@ +page.title=ストレージ アクセス フレームワーク +@jd:body + + + +

Android 4.4 (API レベル 19)は、ストレージ アクセス フレームワーク(SAF)を採用しています。SAF を利用することで、ユーザーは設定したドキュメント ストレージ プロバイダ全体から簡単にドキュメント、画像、その他のファイルを参照して開くことができます。 + +標準の使いやすい UI により、アプリやプロバイダを通じて一貫性のある方法でファイルを参照したり、最近使用したファイルにアクセスしたりできます。 +

+ +

サービスをカプセル化する {@link android.provider.DocumentsProvider} を実装することで、クラウドやローカル ストレージ サービスをエコシステムに参加させることができます。 +プロバイダのドキュメントへのアクセスが必要なクライアントも、数行のコードだけで SAF と統合できます。 + +

+ +

SAF には次の項目が含まれます。

+ + + +

SAF は次のような機能も提供します。

+ + +

概要

+ +

SAF は、{@link android.provider.DocumentsProvider} クラスのサブクラスであるコンテンツ プロバイダを中心に展開します。 +ドキュメント プロバイダでは、データは下図のように従来のファイル階層で構造化されます。 +

+

data model

+

図 1. ドキュメント プロバイダのデータモデル。ルートポイントが 1 つのドキュメントを指し、そこからツリー全体が広がります。 +

+ +

注:

+ + +

コントロール フロー

+

前述のように、ドキュメント プロバイダのデータモデルは従来のファイル階層を基本とします。 +ただし、{@link android.provider.DocumentsProvider} API でアクセスできるのであれば、任意の方法でデータを物理的に格納できます。たとえば、データにタグベースのクラウド ストレージを使用できます。 + +

+ +

図 2 の例は、写真アプリが格納されたデータに SAF を使ってアクセスする様子を表しています。 +

+

app

+ +

図 2. ストレージ アクセス フレームワーク フロー

+ +

注:

+ + +

図 3 は、Google Drive アカウントを選択したユーザーが画像を検索するピッカーを表しています。 +

+ +

picker

+ +

図 3. ピッカー

+ +

ユーザーが Google Drive を選択すると、図 4 のように画像が表示されます。 +この時点から、ユーザーはプロバイダとクライアント アプリがサポートするすべての操作方法を使って、画像を操作できるようになります。 + + +

picker

+ +

図 4.画像

+ +

クライアント アプリを作成する

+ +

Android 4.3 以前では、別のアプリからファイルを取得する場合、{@link android.content.Intent#ACTION_PICK} や {@link android.content.Intent#ACTION_GET_CONTENT} といったインテントを呼び出す必要があります。 + +その後ユーザーは、ファイルを選択するためのアプリを 1 つ選びます。選択されたアプリはユーザーが使用可能なファイルを参照して選択するためのユーザー インターフェースを提供する必要があります。 + +

+ +

Android 4.4 以降であれば、{@link android.content.Intent#ACTION_OPEN_DOCUMENT} インテントを使用するという選択肢もあります。このインテントではシステム制御のピッカー UI が表示され、ユーザーはそこから他のアプリで利用可能なすべてのファイル参照できます。 + + +ユーザーは、この 1 つの UI から、サポートされるすべてのアプリのファイルを選択できます。 +

+ +

{@link android.content.Intent#ACTION_OPEN_DOCUMENT} は、{@link android.content.Intent#ACTION_GET_CONTENT} との置き換えを意図したものではありません。どちらを使用するかは、アプリのニーズによって異なります。 + +

+ + + + +

ここでは、{@link android.content.Intent#ACTION_OPEN_DOCUMENT} と {@link android.content.Intent#ACTION_CREATE_DOCUMENT} インテントに基づいたクライアント アプリの作成方法について説明します。 + +

+ + + + +

+次のスニペットでは {@link android.content.Intent#ACTION_OPEN_DOCUMENT} を使って、画像ファイルがあるドキュメント プロバイダを検索します。 + +

+ +
private static final int READ_REQUEST_CODE = 42;
+...
+/**
+ * Fires an intent to spin up the "file chooser" UI and select an image.
+ */
+public void performFileSearch() {
+
+    // ACTION_OPEN_DOCUMENT is the intent to choose a file via the system's file
+    // browser.
+    Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
+
+    // Filter to only show results that can be "opened", such as a
+    // file (as opposed to a list of contacts or timezones)
+    intent.addCategory(Intent.CATEGORY_OPENABLE);
+
+    // Filter to show only images, using the image MIME data type.
+    // If one wanted to search for ogg vorbis files, the type would be "audio/ogg".
+    // To search for all documents available via installed storage providers,
+    // it would be "*/*".
+    intent.setType("image/*");
+
+    startActivityForResult(intent, READ_REQUEST_CODE);
+}
+ +

注:

+ + +

プロセスの結果

+ +

ユーザーがピッカーでドキュメントを選択すると、{@link android.app.Activity#onActivityResult onActivityResult()} が呼び出されます。選択したドキュメントを指す URI は {@code resultData} パラメータに含まれます。 + + +{@link android.content.Intent#getData getData()} を使って URI を抽出します。URI を抽出したら、その URI を使ってユーザーが希望するドキュメントを取得できます。 +次に例を示します。 +

+ +
@Override
+public void onActivityResult(int requestCode, int resultCode,
+        Intent resultData) {
+
+    // The ACTION_OPEN_DOCUMENT intent was sent with the request code
+    // READ_REQUEST_CODE. If the request code seen here doesn't match, it's the
+    // response to some other intent, and the code below shouldn't run at all.
+
+    if (requestCode == READ_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
+        // The document selected by the user won't be returned in the intent.
+        // Instead, a URI to that document will be contained in the return intent
+        // provided to this method as a parameter.
+        // Pull that URI using resultData.getData().
+        Uri uri = null;
+        if (resultData != null) {
+            uri = resultData.getData();
+            Log.i(TAG, "Uri: " + uri.toString());
+            showImage(uri);
+        }
+    }
+}
+
+ +

ドキュメント メタデータを確認する

+ +

ドキュメントのURI を取得したら、そのメタデータにアクセスできます。次のスニペットは、URI で指定したドキュメントのメタデータを取得して、ログに記録します。 +

+ +
public void dumpImageMetaData(Uri uri) {
+
+    // The query, since it only applies to a single document, will only return
+    // one row. There's no need to filter, sort, or select fields, since we want
+    // all fields for one document.
+    Cursor cursor = getActivity().getContentResolver()
+            .query(uri, null, null, null, null, null);
+
+    try {
+    // moveToFirst() returns false if the cursor has 0 rows.  Very handy for
+    // "if there's anything to look at, look at it" conditionals.
+        if (cursor != null && cursor.moveToFirst()) {
+
+            // Note it's called "Display Name".  This is
+            // provider-specific, and might not necessarily be the file name.
+            String displayName = cursor.getString(
+                    cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME));
+            Log.i(TAG, "Display Name: " + displayName);
+
+            int sizeIndex = cursor.getColumnIndex(OpenableColumns.SIZE);
+            // If the size is unknown, the value stored is null.  But since an
+            // int can't be null in Java, the behavior is implementation-specific,
+            // which is just a fancy term for "unpredictable".  So as
+            // a rule, check if it's null before assigning to an int.  This will
+            // happen often:  The storage API allows for remote files, whose
+            // size might not be locally known.
+            String size = null;
+            if (!cursor.isNull(sizeIndex)) {
+                // Technically the column stores an int, but cursor.getString()
+                // will do the conversion automatically.
+                size = cursor.getString(sizeIndex);
+            } else {
+                size = "Unknown";
+            }
+            Log.i(TAG, "Size: " + size);
+        }
+    } finally {
+        cursor.close();
+    }
+}
+
+ +

ドキュメントを開く

+ +

ドキュメントのURI を取得したら、そのドキュメントを開いてさまざまな操作を行うことができます。 +

+ +

ビットマップ

+ +

{@link android.graphics.Bitmap} を開く方法の例を次に示します。

+ +
private Bitmap getBitmapFromUri(Uri uri) throws IOException {
+    ParcelFileDescriptor parcelFileDescriptor =
+            getContentResolver().openFileDescriptor(uri, "r");
+    FileDescriptor fileDescriptor = parcelFileDescriptor.getFileDescriptor();
+    Bitmap image = BitmapFactory.decodeFileDescriptor(fileDescriptor);
+    parcelFileDescriptor.close();
+    return image;
+}
+
+ +

この操作を UI スレッドで実行しないでください。{@link android.os.AsyncTask} を使ってバックグラウンドで実行します。 +ビットマップを開いたら、{@link android.widget.ImageView} で表示できます。 + +

+ +

InputStream を取得する

+ +

URI から {@link java.io.InputStream} を取得する方法の例を次に示します。このスニペットでは、ファイルの行が文字列に読み込まれます。 +

+ +
private String readTextFromUri(Uri uri) throws IOException {
+    InputStream inputStream = getContentResolver().openInputStream(uri);
+    BufferedReader reader = new BufferedReader(new InputStreamReader(
+            inputStream));
+    StringBuilder stringBuilder = new StringBuilder();
+    String line;
+    while ((line = reader.readLine()) != null) {
+        stringBuilder.append(line);
+    }
+    fileInputStream.close();
+    parcelFileDescriptor.close();
+    return stringBuilder.toString();
+}
+
+ +

新しいドキュメントを作成する

+ +

{@link android.content.Intent#ACTION_CREATE_DOCUMENT} インテントを使用すると、アプリでドキュメント プロバイダに新しいドキュメントを作成できます。 + +ファイルを作成するには、インテントに MIME タイプとファイル名を渡し、一意の要求コードを使ってインテントを起動します。 +後は自分で設定します。

+ + +
+// Here are some examples of how you might call this method.
+// The first parameter is the MIME type, and the second parameter is the name
+// of the file you are creating:
+//
+// createFile("text/plain", "foobar.txt");
+// createFile("image/png", "mypicture.png");
+
+// Unique request code.
+private static final int WRITE_REQUEST_CODE = 43;
+...
+private void createFile(String mimeType, String fileName) {
+    Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
+
+    // Filter to only show results that can be "opened", such as
+    // a file (as opposed to a list of contacts or timezones).
+    intent.addCategory(Intent.CATEGORY_OPENABLE);
+
+    // Create a file with the requested MIME type.
+    intent.setType(mimeType);
+    intent.putExtra(Intent.EXTRA_TITLE, fileName);
+    startActivityForResult(intent, WRITE_REQUEST_CODE);
+}
+
+ +

新しいドキュメントを作成したら、{@link android.app.Activity#onActivityResult onActivityResult()} で URI を取得して、ドキュメントに書き込み操作を実行できます。 + +

+ +

ドキュメントを削除する

+ +

ドキュメントの URI と、{@link android.provider.DocumentsContract.Document#FLAG_SUPPORTS_DELETE SUPPORTS_DELETE} を含むドキュメントの {@link android.provider.DocumentsContract.Document#COLUMN_FLAGS Document.COLUMN_FLAGS} を取得すると、ドキュメントを削除できます。 + + + +次に例を示します。

+ +
+DocumentsContract.deleteDocument(getContentResolver(), uri);
+
+ +

ドキュメントを編集する

+ +

SAF を使用するとテキスト ドキュメントを直接編集できます。このスニペットは {@link android.content.Intent#ACTION_OPEN_DOCUMENT} インテントを起動し、カテゴリ {@link android.content.Intent#CATEGORY_OPENABLE} を使って、開くことができるドキュメントのみを表示します。 + + + +次のように、テキスト ファイルのみを表示するようにフィルタリングします。

+ +
+private static final int EDIT_REQUEST_CODE = 44;
+/**
+ * Open a file for writing and append some text to it.
+ */
+ private void editDocument() {
+    // ACTION_OPEN_DOCUMENT is the intent to choose a file via the system's
+    // file browser.
+    Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
+
+    // Filter to only show results that can be "opened", such as a
+    // file (as opposed to a list of contacts or timezones).
+    intent.addCategory(Intent.CATEGORY_OPENABLE);
+
+    // Filter to show only text files.
+    intent.setType("text/plain");
+
+    startActivityForResult(intent, EDIT_REQUEST_CODE);
+}
+
+ +

次に、{@link android.app.Activity#onActivityResult onActivityResult()}(「プロセスの結果」をご覧ください)からコードを呼び出して、編集を行います。次のスニペットは {@link android.content.ContentResolver} から {@link java.io.FileOutputStream} を取得します。 + + +デフォルトでは、「書き込み」モードを使用します。必要最小限のアクセスのみを要求することをお勧めします。書き込みのみが必要な場合に読み込みと書き込みを要求しないでください。 + +

+ +
private void alterDocument(Uri uri) {
+    try {
+        ParcelFileDescriptor pfd = getActivity().getContentResolver().
+                openFileDescriptor(uri, "w");
+        FileOutputStream fileOutputStream =
+                new FileOutputStream(pfd.getFileDescriptor());
+        fileOutputStream.write(("Overwritten by MyCloud at " +
+                System.currentTimeMillis() + "\n").getBytes());
+        // Let the document provider know you're done by closing the stream.
+        fileOutputStream.close();
+        pfd.close();
+    } catch (FileNotFoundException e) {
+        e.printStackTrace();
+    } catch (IOException e) {
+        e.printStackTrace();
+    }
+}
+ +

パーミッションを固定する

+ +

アプリで読み込みや書き込み用にファイルを開くと、システムからアプリに対して、該当するファイルの URI パーミッションが付与されます。 +このパーミッションはユーザーが端末を再起動するまで有効です。ただし、画像アプリでユーザーが最近編集した 5 つの画像にアプリから直接アクセスできるようにするにはどうすればいいでしょう。ユーザーの端末が再起動されたら、ファイルを検索するためにユーザーをシステム ピッカーに戻す必要がありますが、これは明らかにいい方法とは言えません。 + + + +

+ +

このような状況を避けるために、システムがアプリに付与するパーミッションを固定します。実際は、システムが提供する固定可能な URI パーミッションをアプリで「取得」します。 + +これにより、端末を再起動した場合でも、ユーザーはアプリからファイルに継続的にアクセスできます。 +

+ + +
final int takeFlags = intent.getFlags()
+            & (Intent.FLAG_GRANT_READ_URI_PERMISSION
+            | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+// Check for the freshest data.
+getContentResolver().takePersistableUriPermission(uri, takeFlags);
+ +

最後にもう 1 つ手順があります。アプリが最近アクセスした URI は、保存されていても既に無効になっている場合があります。—別のアプリによってドキュメントが削除されたり、修正されたりすることが考えられます。 + +そこで、最新のデータを確認するには、常に {@code getContentResolver().takePersistableUriPermission()} を呼び出す必要があります。 + +

+ +

カスタム ドキュメント プロバイダを作成する

+ +

+ファイル用のストレージ サービスを提供するアプリ(クラウド保存サービスなど)を開発する場合、カスタム ドキュメント プロバイダを作成すれば、SAF を介してファイルを利用可能にできます。 + +ここではその方法について説明します。 +

+ + +

マニフェスト

+ +

カスタム ドキュメント プロバイダを実装するには、アプリケーションのマニフェストに次のものを追加します。 +

+ +

プロバイダを含むサンプル マニフェストの抜粋を次に示します。

+ +
<manifest... >
+    ...
+    <uses-sdk
+        android:minSdkVersion="19"
+        android:targetSdkVersion="19" />
+        ....
+        <provider
+            android:name="com.example.android.storageprovider.MyCloudProvider"
+            android:authorities="com.example.android.storageprovider.documents"
+            android:grantUriPermissions="true"
+            android:exported="true"
+            android:permission="android.permission.MANAGE_DOCUMENTS"
+            android:enabled="@bool/atLeastKitKat">
+            <intent-filter>
+                <action android:name="android.content.action.DOCUMENTS_PROVIDER" />
+            </intent-filter>
+        </provider>
+    </application>
+
+</manifest>
+ +

Android 4.3 以前を実行する端末をサポートする

+ +

{@link android.content.Intent#ACTION_OPEN_DOCUMENT} インテントは、Android 4.4 以降を実行する端末でのみ利用可能です。Android 4.3 以前を実行する端末に対応するためにアプリケーションで {@link android.content.Intent#ACTION_GET_CONTENT} をサポートするには、Android 4.4 以降を実行する端末のマニフェストで {@link android.content.Intent#ACTION_GET_CONTENT} インテント フィルタを無効にする必要があります。 + + + + + +ドキュメント プロバイダと {@link android.content.Intent#ACTION_GET_CONTENT} は互いに排他的な関係にあると考える必要があります。 + +両方を同時にサポートする場合、アプリはシステム ピッカー UI に 2 度表示され、格納されたデータへの 2 つの異なる方法を提供します。 + +これではユーザーも操作に迷ってしまいます。

+ +

ここでは、Android バージョン 4.4 以降を実行する端末で {@link android.content.Intent#ACTION_GET_CONTENT} インテント フィルタを無効にする際に推奨される方法を紹介します。 + +

+ +
    +
  1. {@code res/values/} にある {@code bool.xml} リソース ファイルに、次の行を追加します。 +
    <bool name="atMostJellyBeanMR2">true</bool>
  2. + +
  3. {@code res/values-v19/} にある {@code bool.xml} リソース ファイルに、次の行を追加します。 +
    <bool name="atMostJellyBeanMR2">false</bool>
  4. + +
  5. 4.4 (API レベル 19)以降のバージョンに対して {@link android.content.Intent#ACTION_GET_CONTENT} インテント フィルタを無効にするには、アクティビティ エイリアスを追加します。 + + +次に例を示します。 + +
    +<!-- This activity alias is added so that GET_CONTENT intent-filter
    +     can be disabled for builds on API level 19 and higher. -->
    +<activity-alias android:name="com.android.example.app.MyPicker"
    +        android:targetActivity="com.android.example.app.MyActivity"
    +        ...
    +        android:enabled="@bool/atMostJellyBeanMR2">
    +    <intent-filter>
    +        <action android:name="android.intent.action.GET_CONTENT" />
    +        <category android:name="android.intent.category.OPENABLE" />
    +        <category android:name="android.intent.category.DEFAULT" />
    +        <data android:mimeType="image/*" />
    +        <data android:mimeType="video/*" />
    +    </intent-filter>
    +</activity-alias>
    +
    +
  6. +
+

コントラクト

+ +

通常、カスタム コンテンツ プロバイダを作成するときには、コンテンツ プロバイダのデベロッパー ガイドに記載があるように、コントラクト クラスを実装するタスクが必要になります。 + + +コントラクト クラスは {@code public final} クラスであり、URI、列名、MIME タイプ、プロバイダに関連するその他のメタデータを含みます。 + +コントラクト クラスは SAF によって提供されるため、独自に作成する必要はありません。 + +

+ + + +

たとえば、ドキュメント プロバイダでドキュメントやルートを照会したときには、カーソルに次のような列が返されることがあります。 +

+ +
private static final String[] DEFAULT_ROOT_PROJECTION =
+        new String[]{Root.COLUMN_ROOT_ID, Root.COLUMN_MIME_TYPES,
+        Root.COLUMN_FLAGS, Root.COLUMN_ICON, Root.COLUMN_TITLE,
+        Root.COLUMN_SUMMARY, Root.COLUMN_DOCUMENT_ID,
+        Root.COLUMN_AVAILABLE_BYTES,};
+private static final String[] DEFAULT_DOCUMENT_PROJECTION = new
+        String[]{Document.COLUMN_DOCUMENT_ID, Document.COLUMN_MIME_TYPE,
+        Document.COLUMN_DISPLAY_NAME, Document.COLUMN_LAST_MODIFIED,
+        Document.COLUMN_FLAGS, Document.COLUMN_SIZE,};
+
+ +

サブクラス DocumentsProvider

+ +

カスタム ドキュメント プロバイダの作成の次の手順では、抽象クラス {@link android.provider.DocumentsProvider} をサブクラス化します。 +少なくとも、次のメソッドを実装する必要があります。 +

+ + + +

これらは実装する必要があるメソッドですが、これ以外にも多くのメソッドを実装できます。 +詳細については、{@link android.provider.DocumentsProvider} をご覧ください。 +

+ +

queryRoots を実装する

+ +

{@link android.provider.DocumentsProvider#queryRoots +queryRoots()} を実装すると、{@link android.provider.DocumentsContract.Root} で定義される列を使って、ドキュメント プロバイダのすべてのルート ディレクトリを指す {@link android.database.Cursor} が返されます。 + +

+ +

次のスニペットの {@code projection} パラメータは、呼び出し側が取得する特定のフィールドを表します。 +スニペットは新しいカーソルを作成し、それに行を 1 つ追加します。—[ダウンロード] や [画像] と同じように、1 つのルートに対して最上位のディレクトリが 1 つになります。 + +ほとんどのプロバイダでルートは 1 つになります。複数のユーザー アカウントを使用するような場合は、1 つ以上のルートを設定できます。 +その場合、カーソルに 2 つ目の行を追加します。 +

+ +
+@Override
+public Cursor queryRoots(String[] projection) throws FileNotFoundException {
+
+    // Create a cursor with either the requested fields, or the default
+    // projection if "projection" is null.
+    final MatrixCursor result =
+            new MatrixCursor(resolveRootProjection(projection));
+
+    // If user is not logged in, return an empty root cursor.  This removes our
+    // provider from the list entirely.
+    if (!isUserLoggedIn()) {
+        return result;
+    }
+
+    // It's possible to have multiple roots (e.g. for multiple accounts in the
+    // same app) -- just add multiple cursor rows.
+    // Construct one row for a root called "MyCloud".
+    final MatrixCursor.RowBuilder row = result.newRow();
+    row.add(Root.COLUMN_ROOT_ID, ROOT);
+    row.add(Root.COLUMN_SUMMARY, getContext().getString(R.string.root_summary));
+
+    // FLAG_SUPPORTS_CREATE means at least one directory under the root supports
+    // creating documents. FLAG_SUPPORTS_RECENTS means your application's most
+    // recently used documents will show up in the "Recents" category.
+    // FLAG_SUPPORTS_SEARCH allows users to search all documents the application
+    // shares.
+    row.add(Root.COLUMN_FLAGS, Root.FLAG_SUPPORTS_CREATE |
+            Root.FLAG_SUPPORTS_RECENTS |
+            Root.FLAG_SUPPORTS_SEARCH);
+
+    // COLUMN_TITLE is the root title (e.g. Gallery, Drive).
+    row.add(Root.COLUMN_TITLE, getContext().getString(R.string.title));
+
+    // This document id cannot change once it's shared.
+    row.add(Root.COLUMN_DOCUMENT_ID, getDocIdForFile(mBaseDir));
+
+    // The child MIME types are used to filter the roots and only present to the
+    //  user roots that contain the desired type somewhere in their file hierarchy.
+    row.add(Root.COLUMN_MIME_TYPES, getChildMimeTypes(mBaseDir));
+    row.add(Root.COLUMN_AVAILABLE_BYTES, mBaseDir.getFreeSpace());
+    row.add(Root.COLUMN_ICON, R.drawable.ic_launcher);
+
+    return result;
+}
+ +

queryChildDocuments を実装する

+ +

{@link android.provider.DocumentsProvider#queryChildDocuments queryChildDocuments()} を実装すると、{@link android.provider.DocumentsContract.Document} で定義される列を使って、指定したディレクトリ内のすべてのファイルを指す {@link android.database.Cursor} が返されます。 + + + +

+ +

ピッカー UI でアプリケーション ルートを選択すると、このメソッドが呼び出されます。ルートの下のディレクトリの子ドキュメントを取得します。 +ルートだけでなく、ファイル階層内の任意のレベルで呼び出せます。 +このスニペットでは、要求した列を持つ新しいカーソルが作成され、親ディレクトリのすぐ下のそれぞれの子の情報がカーソルに追加されます。子は、画像、別のディレクトリなど—任意のファイルとなります。 + + +

+ +
@Override
+public Cursor queryChildDocuments(String parentDocumentId, String[] projection,
+                              String sortOrder) throws FileNotFoundException {
+
+    final MatrixCursor result = new
+            MatrixCursor(resolveDocumentProjection(projection));
+    final File parent = getFileForDocId(parentDocumentId);
+    for (File file : parent.listFiles()) {
+        // Adds the file's display name, MIME type, size, and so on.
+        includeFile(result, null, file);
+    }
+    return result;
+}
+
+ +

queryDocument を実装する

+ +

{@link android.provider.DocumentsProvider#queryDocument queryDocument()} を実装すると、{@link android.provider.DocumentsContract.Document} で定義される列を使って、指定したファイルを指す {@link android.database.Cursor} が返されます。 + + + +

+ +

{@link android.provider.DocumentsProvider#queryDocument queryDocument()} メソッドでは、特定のファイルの情報ではなく、{@link android.provider.DocumentsProvider#queryChildDocuments queryChildDocuments()} に渡したのと同じ情報が返されます。 + + +

+ + +
@Override
+public Cursor queryDocument(String documentId, String[] projection) throws
+        FileNotFoundException {
+
+    // Create a cursor with the requested projection, or the default projection.
+    final MatrixCursor result = new
+            MatrixCursor(resolveDocumentProjection(projection));
+    includeFile(result, documentId, null);
+    return result;
+}
+
+ +

openDocument を実装する

+ +

指定したファイルを表す {@link android.os.ParcelFileDescriptor} を返すには、{@link android.provider.DocumentsProvider#openDocument +openDocument()} を実装する必要があります。 +他のアプリは、返された {@link android.os.ParcelFileDescriptor} を使ってデータをストリーミングします。 +ユーザーがファイルを選択するとシステムがこのメソッドを呼び出し、クライアント アプリは {@link android.content.ContentResolver#openFileDescriptor openFileDescriptor()} を呼び出してファイルへのアクセスを要求します。次に例を示します。 + + +

+ +
@Override
+public ParcelFileDescriptor openDocument(final String documentId,
+                                         final String mode,
+                                         CancellationSignal signal) throws
+        FileNotFoundException {
+    Log.v(TAG, "openDocument, mode: " + mode);
+    // It's OK to do network operations in this method to download the document,
+    // as long as you periodically check the CancellationSignal. If you have an
+    // extremely large file to transfer from the network, a better solution may
+    // be pipes or sockets (see ParcelFileDescriptor for helper methods).
+
+    final File file = getFileForDocId(documentId);
+
+    final boolean isWrite = (mode.indexOf('w') != -1);
+    if(isWrite) {
+        // Attach a close listener if the document is opened in write mode.
+        try {
+            Handler handler = new Handler(getContext().getMainLooper());
+            return ParcelFileDescriptor.open(file, accessMode, handler,
+                        new ParcelFileDescriptor.OnCloseListener() {
+                @Override
+                public void onClose(IOException e) {
+
+                    // Update the file with the cloud server. The client is done
+                    // writing.
+                    Log.i(TAG, "A file with id " +
+                    documentId + " has been closed!
+                    Time to " +
+                    "update the server.");
+                }
+
+            });
+        } catch (IOException e) {
+            throw new FileNotFoundException("Failed to open document with id "
+            + documentId + " and mode " + mode);
+        }
+    } else {
+        return ParcelFileDescriptor.open(file, accessMode);
+    }
+}
+
+ +

セキュリティ

+ +

たとえば、ドキュメント プロバイダがパスワードで保護されたクラウド ストレージ サービスであり、ファイル共有を開始する前にユーザーがログインしているかどうかを確認するとします。ユーザーがログインしていない場合、アプリはどのような動作を行う必要があるでしょうか。 + +このような場合、{@link android.provider.DocumentsProvider#queryRoots +queryRoots()} の実装のゼロルートを返すようにします。 +つまり、空のルートカーソルになります。

+ +
+public Cursor queryRoots(String[] projection) throws FileNotFoundException {
+...
+    // If user is not logged in, return an empty root cursor.  This removes our
+    // provider from the list entirely.
+    if (!isUserLoggedIn()) {
+        return result;
+}
+
+ +

その他に {@code getContentResolver().notifyChange()} を呼び出す手順もあります。{@link android.provider.DocumentsContract} については既に説明してあります。 +これを使ってこの URI を作成します。次のスニペットは、ユーザーのログイン ステータスが変わった場合に、ドキュメント プロバイダのルートを照会するようシステムに指示します。 + +ユーザーがログインしてない場合は、上述のように、{@link android.provider.DocumentsProvider#queryRoots queryRoots()} を呼び出すと空のカーソルが返されます。 + +こうすることで、ユーザーがプロバイダにログインしている場合にのみ、プロバイダのドキュメントを利用できます。 +

+ +
private void onLoginButtonClick() {
+    loginOrLogout();
+    getContentResolver().notifyChange(DocumentsContract
+            .buildRootsUri(AUTHORITY), null);
+}
+
\ No newline at end of file diff --git a/docs/html-intl/intl/ja/guide/topics/resources/accessing-resources.jd b/docs/html-intl/intl/ja/guide/topics/resources/accessing-resources.jd new file mode 100644 index 0000000000000000000000000000000000000000..af8d005dac428d96aa1f427d4d3f6007f167b91f --- /dev/null +++ b/docs/html-intl/intl/ja/guide/topics/resources/accessing-resources.jd @@ -0,0 +1,337 @@ +page.title=リソースへのアクセス +parent.title=アプリケーション リソース +parent.link=index.html +@jd:body + +
+
+

クイックビュー

+
    +
  • リソースは、{@code R.java} の整数を使うコード({@code R.drawable.myimage} など)を使用して参照できます。 +
  • +
  • リソースは、特殊な XML 構文({@code +@drawable/myimage} など)を使用して参照できます。
  • +
  • さらに、{@link android.content.res.Resources} のメソッドを使用すると、アプリ リソースにアクセスできます。 +
  • +
+ +

キークラス

+
    +
  1. {@link android.content.res.Resources}
  2. +
+ +

本書の内容

+
    +
  1. コードからリソースにアクセスする
  2. +
  3. XML からリソースにアクセスする +
      +
    1. スタイル属性を参照する
    2. +
    +
  4. +
  5. プラットフォーム リソースにアクセスする
  6. +
+ +

関連ドキュメント

+
    +
  1. リソースの提供
  2. +
  3. Resource Types
  4. +
+
+
+ + + + +

アプリケーションでリソースを提供すると(詳細は「リソースの提供」)、リソース ID を参照することで適用できるようになります。リソース ID はすべてプロジェクトの {@code R} クラスで定義します。これは {@code aapt} ツールが自動的に生成します。 + +

+ +

アプリケーションをコンパイルすると、{@code aapt} が {@code R} クラスを生成します。このクラスには、{@code +res/} ディレクトリに含まれるの全リソースのリソース ID が入ります。 +リソースのそれぞれのタイプに対して、{@code R} サブクラス(例: ドローアブル リソースの場合はすべて {@code R.drawable})があり、そのタイプのそれぞれのリソースには、静的整数(例: {@code R.drawable.icon})があります。 + +この整数がリソース ID であり、リソースを取得するのに使用できます。 +

+ +

{@code R} クラスではリソース ID を指定されていますが、リソース ID を探す必要はありません。リソース ID は常に次のように構成されます。 +

+ + +

リソースにアクセスするには次の 2 つの方法があります。

+ + + + +

コードでリソースにアクセスする

+ +

リソース ID をメソッド パラメータとして渡すことで、コード内でリソースを使用できます。たとえば、{@link android.widget.ImageView#setImageResource(int) setImageResource()} を使って、{@link android.widget.ImageView} が {@code res/drawable/myimage.png} を使用するように設定できます。 + +

+
+ImageView imageView = (ImageView) findViewById(R.id.myimageview);
+imageView.setImageResource(R.drawable.myimage);
+
+ +

さらに、{@link +android.content.res.Resources} のメソッドを使用して、個々のリソースを取得することもできます。{@link android.content.Context#getResources()} を使用すると、インスタンスを取得できます。 +

+ + + + +

構文

+ +

次の構文を使用して、コード内のリソースを参照します。

+ +
+[<package_name>.]R.<resource_type>.<resource_name>
+
+ + +

リソースタイプの詳細やそれらの査証方法については、「Resource Types」をご覧ください。 +

+ + +

使用例

+ +

多くのメソッドでリソース ID パラメータを使うことができ、{@link android.content.res.Resources} のメソッドを使用すればリソースを取得できます。 +{@link +android.content.res.Resources} のインスタンスは、{@link android.content.Context#getResources +Context.getResources()} により取得できます。

+ + +

次は、コードのリソースにアクセスする例です。

+ +
+// Load a background for the current screen from a drawable resource
+{@link android.app.Activity#getWindow()}.{@link
+android.view.Window#setBackgroundDrawableResource(int)
+setBackgroundDrawableResource}(R.drawable.my_background_image) ;
+
+// Set the Activity title by getting a string from the Resources object, because
+//  this method requires a CharSequence rather than a resource ID
+{@link android.app.Activity#getWindow()}.{@link android.view.Window#setTitle(CharSequence)
+setTitle}(getResources().{@link android.content.res.Resources#getText(int)
+getText}(R.string.main_title));
+
+// Load a custom layout for the current screen
+{@link android.app.Activity#setContentView(int)
+setContentView}(R.layout.main_screen);
+
+// Set a slide in animation by getting an Animation from the Resources object
+mFlipper.{@link android.widget.ViewAnimator#setInAnimation(Animation)
+setInAnimation}(AnimationUtils.loadAnimation(this,
+        R.anim.hyperspace_in));
+
+// Set the text on a TextView object using a resource ID
+TextView msgTextView = (TextView) findViewById(R.id.msg);
+msgTextView.{@link android.widget.TextView#setText(int)
+setText}(R.string.hello_message);
+
+ + +

警告: {@code +R.java} ファイルを手動で修正しないでください。—プロジェクトがコンパイルされると、{@code aapt} ツールによって生成されます。 +変更した場合は、次回のコンパイルの際に上書きされます。

+ + + +

XML からリソースにアクセスする

+ +

一部の XML 属性や要素の値は、既存のリソースへの参照を使用して定義できます。 +通常は、ウィジェットに文字列や画像を提供するレイアウト ファイルを作成する場合に、この方法を使用します。 +

+ +

たとえば、レイアウトに {@link android.widget.Button} を追加する場合は、次のように、ボタンテキストの文字列リソースを使用します。 +

+ +
+<Button
+    android:layout_width="fill_parent"
+    android:layout_height="wrap_content"
+    android:text="@string/submit" />
+
+ + +

構文

+ +

次の構文を使用して、XML リソース内のリソースを参照します。

+ +
+@[<package_name>:]<resource_type>/<resource_name>
+
+ + + +

リソースタイプの詳細やそれらの査証方法については、「Resource Types」をご覧ください。 +

+ + +

使用例

+ +

場合によっては、XML の値にリソースを使用する必要がありますが(ウィジェットにドローアブル画像を適用するような場合など)、単純な値を受け入れる XML の任意の場所にリソースを使用できます。 +たとえば、カラーリソース文字列リソースを持つ、次のようなリソースがあるとします。 +

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+   <color name="opaque_red">#f00</color>
+   <string name="hello">Hello!</string>
+</resources>
+
+ +

これらのリソースを次のようなレイアウト ファイルに使用して、テキストカラーとテキストの文字列を設定できます。 +

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<EditText xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    android:textColor="@color/opaque_red"
+    android:text="@string/hello" />
+
+ +

この場合、リソースは独自のパッケージ内のものであることから、リソースの参照でパッケージ名を指定する必要はありません。 +システム リソースを参照するには、パッケージ名を含める必要があります。 +次に例を示します。

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<EditText xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    android:textColor="@android:color/secondary_text_dark"
+    android:text="@string/hello" />
+
+ +

注: アプリケーションを他の言語にローカライズできるように、常に文字列リソースを使用するようにします。 +代替リソース(ローカライズされた文字列など)の作成方法についての詳細は、代替リソースを提供するをご覧ください。 + + +アプリケーションを他の言語にローカライズするための詳細なガイドは、「Localization」をご覧ください。 +

+ +

XML でリソースを使用すれば、エイリアスを作成することもできます。たとえば、別のドローアブル リソースのエイリアスとなる、次のようなドローアブル リソースを作成できます。 +

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
+    android:src="@drawable/other_drawable" />
+
+ +

これは冗長のように思われますが、代替リソースを使用する場合に大変便利です。詳細は、エイリアス リソースを作成するをご覧ください。 +

+ + + +

スタイル属性を参照する

+ +

スタイル属性リソースを使用すると、現在適用されているテーマの属性の値を参照できます。 +スタイル属性を参照すれば、ハードコードした値を指定しなくても、現在のテーマで与えられる標準的なバリエーションに合わせてスタイルを設定して、UI 要素の外観をカスタマイズできます。 + +本質的に、スタイル属性を参照するというのは、「現在のテーマのこの属性で定義されるスタイルを使用する」という意味になります。 +

+ +

属性スタイルを参照する場合、名前構文は通常のリソース形式とほぼ同じですが、アットマーク({@code @})の代わりに疑問符({@code ?})を使用します。リソースタイプの部分は省略可能です。 + +例:

+ +
+?[<package_name>:][<resource_type>/]<resource_name>
+
+ +

たとえば、次の例では、属性を参照して、テキスト カラーをシステム テーマの「プライマリ」テキスト カラーに合わせて設定します。 +

+ +
+<EditText id="text"
+    android:layout_width="fill_parent"
+    android:layout_height="wrap_content"
+    android:textColor="?android:textColorSecondary"
+    android:text="@string/hello_world" />
+
+ +

ここでは、{@code android:textColor} 属性が、現在のテーマのスタイル属性の名前を指定しています。 +Android は、{@code android:textColorSecondary} スタイル属性に適用された値を、このウィジェットの {@code android:textColor} の値として使用できるようになりました。 +このコンテキストで属性リソースが必要なことはシステム リソース ツールで認識されているため、タイプ(ここでは ?android:attr/textColorSecondary)を明示的に指定する必要はありません — {@code attr} タイプを除外できます。 + + +

+ + + + +

プラットフォーム リソースにアクセスする

+ +

Android には、スタイル、テーマ、レイアウトといった多数の標準的なリソースが用意されています。これらのリソースにアクセスするには、android パッケージ名でリソース参照を修飾します。 + +たとえば、Android には、{@link android.widget.ListAdapter} のリストアイテムに使用できるレイアウト リソースが用意されています。 +

+ +
+{@link android.app.ListActivity#setListAdapter(ListAdapter)
+setListAdapter}(new {@link
+android.widget.ArrayAdapter}<String>(this, android.R.layout.simple_list_item_1, myarray));
+
+ +

この例では、{@link android.R.layout#simple_list_item_1} は、プラットフォームによって {@link android.widget.ListView} のアイテム向けに定義されるレイアウト リソースです。 +リストアイテムに独自のレイアウトを作成する代わりに、このレイアウトを使用できます。 +詳細については、「List View」のデベロッパー ガイドをご覧ください。 +

+ diff --git a/docs/html-intl/intl/ja/guide/topics/resources/overview.jd b/docs/html-intl/intl/ja/guide/topics/resources/overview.jd new file mode 100644 index 0000000000000000000000000000000000000000..28ffa94970c0e7f0cfea1ea0d3c16eae4e263d8e --- /dev/null +++ b/docs/html-intl/intl/ja/guide/topics/resources/overview.jd @@ -0,0 +1,103 @@ +page.title=リソースの概要 +@jd:body + + + + +

画像や文字列といったリソースは、常にアプリケーション コードの外部に置くようにすることで、独立して保持できるようになります。 +さらに、リソースを外部化することで、言語や画面サイズが異なるような特定の端末構成をサポートする代替リソースを提供できるようになります。異なる構成を持つ Android ベースの端末が増えていることから、この外部化がますます重要になってきています。 + + +異なる構成に互換性を持たせるには、リソースをタイプや構成ごとにグループ化するさまざまなサブディレクトリを使用して、プロジェクトの {@code res/} ディレクトリ内にリソースを整理する必要があります。 + + +

+ +
+ +

+図 1. デフォルト レイアウトを使用する 2 つの異なる端末(アプリによる代替レイアウトの提供なし)。 +

+
+ +
+ +

+図 2. 画面サイズが違うレイアウトを使用する 2 つの異なる端末 +

+
+ +

すべてのリソースタイプに対して、アプリケーションのデフォルト レイアウトと複数の代替リソースを指定できます。 +

+ + +

たとえば、デフォルトの UI レイアウトが {@code res/layout/} ディレクトリに保存してある場合、{@code res/layout-land/} ディレクトリにレイアウトを保存しておくことで、横向きの画面を使用する際の別のレイアウトを指定できます。 + + +Android は、現在の端末の構成とリソースのディレクトリ名をマッチングさせ、適切なリソースを自動的に適用します。 +

+ +

図 1 は、代替リソースがない場合に、システムが 2 つの異なる端末に同じレイアウトを適用する様子を表しています。 +図 2 は、大きい画面の代替レイアウト リソースを追加した場合の、レイアウトの適用の様子を表しています。 +

+ +

次のドキュメントには、アプリケーション リソースの整理、代替リソースの指定、アプリケーションでのアクセスなどに関する情報が詳細に記載されています。 +

+ +
+
リソースの提供
+
アプリで提供可能なリソースの種類、保存場所、特定の端末構成用の代替リソースの作成方法。 +
+
リソースへのアクセス
+
アプリケーション コードまたは他の XML リソースからの参照による、提供済みリソースの使用方法。 +
+
実行時の変更の処理
+
アクティビティの実行中に生じた構成の変更の管理方法。
+
Localization
+
代替リソースを使用したアプリケーションのローカライズのためのボトムアップ ガイド。代替リソースの特定の使用方法を 1 つだけ解説していますが、複数のユーザーが利用する場合には非常に重要になります。 + +
+
Resource Types
+
提供可能な各種リソースタイプのリファレンス。XML 要素、属性、構文について説明しています。 +たとえば、このリファレンスには、アプリケーション メニュー、ドローアブル、アニメーションなどの作成方法が記載されています。 +
+
+ + diff --git a/docs/html-intl/intl/ja/guide/topics/resources/providing-resources.jd b/docs/html-intl/intl/ja/guide/topics/resources/providing-resources.jd new file mode 100644 index 0000000000000000000000000000000000000000..6729e8b6ee8a0f5a698bd21af230a51db958427c --- /dev/null +++ b/docs/html-intl/intl/ja/guide/topics/resources/providing-resources.jd @@ -0,0 +1,1094 @@ +page.title=リソースの提供 +parent.title=アプリケーション リソース +parent.link=index.html +@jd:body + +
+
+

クイックビュー

+
    +
  • {@code res/} のそれぞれのサブディレクトリにはさまざまなタイプのリソースがある
  • +
  • 代替リソースは設定固有のリソース ファイルを提供する
  • +
  • 特定の端末設定に依存しないように、デフォルトのリソースを常に入れておく +
  • +
+

本書の内容

+
    +
  1. リソースタイプをグループ化する
  2. +
  3. 代替リソースを提供する +
      +
    1. 修飾子の名前のルール
    2. +
    3. エイリアス リソースを作成する
    4. +
    +
  4. +
  5. リソースとの最適な端末の互換性を提供する
  6. +
  7. Android が最適なリソースを見つける仕組み
  8. +
+ +

関連ドキュメント

+
    +
  1. リソースへのアクセス
  2. +
  3. Resource Types
  4. +
  5. 複数のスクリーンをサポートする +
  6. +
+
+
+ +

画像や文字列といったアプリケーション リソースは、常にコードの外部に置くようにすることで、独立して保持できるようになります。 +さらに、特別に名前を設定したリソース ディレクトリにグループ化することで、特定の端末設定に代替リソースを提供する必要があります。 +実行時には、現在の設定に基づいて、Android は適切なリソースを使用します。 +たとえば、画面サイズに応じて異なる UI レイアウトを提供したり、言語設定に応じて異なる文字列を提供したりできます。 + +

+ +

アプリケーション リソースを外部化したら、プロジェクトの {@code R} クラスで生成されるリソース ID を使用してそれらのリソースにアクセスできます。 +アプリケーションでのリソースの使用方法については、「リソースへのアクセス」をご覧ください。 + +このドキュメントでは、Android プロジェクトでリソースをグループ化する方法と、特定の端末設定に代替リソースを提供する方法を説明します。 +

+ + +

リソースタイプをグループ化する

+ +

それぞれのタイプのリソースを、プロジェクトの {@code res/} ディレクトリの特定のサブディレクトリに配置する必要があります。 +次は、単純なプロジェクトのファイル階層の例です。

+ +
+MyProject/
+    src/  
+        MyActivity.java  
+    res/
+        drawable/  
+            graphic.png  
+        layout/  
+            main.xml
+            info.xml
+        mipmap/  
+            icon.png 
+        values/  
+            strings.xml  
+
+ +

この例を見てわかるように、{@code res/} ディレクトリには、画像リソース、2 つのレイアウト リソース、ランチャー アイコンの {@code mipmap/} ディレクトリ、文字列リソース ファイルといった、すべてのリソース(サブディレクトリ内)が入ります。 + +リソース ディレクトリ名は重要です。表 1 に説明があります。 +

+ +

注: Mipmap フォルダの使用方法については、「Managing Projects Overview」をご覧ください。 +

+ +

表 1. プロジェクト {@code res/} ディレクトリ内でサポートされているリソース ディレクトリ。 +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ディレクトリリソースタイプ
animator/プロパティ アニメーションを定義する XML ファイル。 +
anim/トゥイーン アニメーションを定義する XML ファイル。 +(プロパティ アニメーションをこのディレクトリに保存することもできますが、2 つのタイプを区別するために、プロパティ アニメーションは {@code animator/} ディレクトリに保存することをお勧めします)。 + +
color/色の状態リストを定義する XML ファイル。「Color State List Resource」をご覧ください。 +
drawable/

ビットマップ ファイル({@code .png}、{@code .9.png}、{@code .jpg}、{@code .gif})または次のドローアブル リソース サブタイプにコンパイルされる XML ファイル: +

+
    +
  • ビットマップ ファイル
  • +
  • Nine-Patche(リサイズ可能なビットマップ)
  • +
  • 状態リスト
  • +
  • 形状
  • +
  • アニメーション ドローアブル
  • +
  • その他のドローアブル
  • +
+

Drawable Resources」をご覧ください。

+
mipmap/さまざまなランチャー アイコン密度のドローアブル ファイル。{@code mipmap/} フォルダによるランチャー アイコンの管理方法については、「Managing Projects Overview」をご覧ください。 + +
layout/ユーザー インターフェースのレイアウトを定義する XML ファイル。 + 「Layout Resource」をご覧ください。
menu/オプション メニュー、コンテキスト メニュー、サブ メニューといった、アプリケーション メニューを定義する XML ファイル。 +「Menu Resource」をご覧ください。
raw/

未加工の形式で保存する任意のファイル。これらのリソースを未加工の {@link java.io.InputStream} で開くには、{@link android.content.res.Resources#openRawResource(int) +Resources.openRawResource()} をリソース ID({@code R.raw.ファイル名})とともに呼び出します。 +

+

ただし、元のファイル名とファイル階層にアクセスする必要がある場合は、一部のリソースを {@code +assets/} ディレクトリ({@code res/raw/} の代わりに)に保存することを検討します。 +{@code assets/} のファイルにはリソース ID が付与されていないため、読み込むには {@link android.content.res.AssetManager} が必要です。 +

values/

文字列、整数、色など、単純な値を含む XML ファイル。

+

その他の {@code res/} サブディレクトリの XML リソース ファイルは XML ファイル名に基づいて 1 つのリソースを定義しますが、{@code values/} ディレクトリのファイルは複数のリソースを表します。このディレクトリのファイルの場合、{@code <resources>} 要素のそれぞれの子が 1 つのリソースを定義します。 + + +たとえば、{@code <string>} 要素は {@code R.string} リソースを作成し、{@code <color>} 要素は {@code R.color} リソースを作成します。 + +

+

各リソースの定義には XML 要素を使用するため、ファイルには任意の名前を設定でき、1 つのファイル内にさまざまなリソースを配置できます。 +ただし、わかりやすくするために、一意のリソースタイプをそれぞれのファイルに配置することもできます。 +次は、このディレクトリで作成できるリソースのファイル名の変換例です。 +

+ +

String Resources」、「Style Resource」、「More Resource Types」をご覧ください。 + +

+
xml/{@link +android.content.res.Resources#getXml(int) Resources.getXML()} を呼び出すことによって、実行時に読み込むことができる任意の XML ファイル。ここには、検索可能な設定など、各種の XML 設定ファイルが保存されます。 + +
+ +

警告: リソース ファイルを {@code res/} ディレクトリに直接保存しないでください。—コンパイラー エラーの原因になります。 +

+ +

リソースの特定のタイプの詳細は、「Resource Types」のドキュメントをご覧ください。

+ +

表 1 で定義したサブディレクトリに保存するリソースは、「デフォルト」のリソースとなります。 +つまり、これらのリソースはアプリケーションのデフォルトのデザインとコンテンツを定義します。ただし、Android が搭載された端末では別のタイプのリソースが呼び出されることがあります。たとえば、端末の画面サイズが通常のものよりも大きな場合、追加の画面スペースを利用する別のレイアウト リソースを提供する必要があります。 + + +また、端末の言語設定が異なる場合、使用するユーザー インターフェースのテキストを翻訳する、別の文字列リソースを提供します。 + +さまざまな端末設定にこれらの異なるリソースを提供するには、デフォルトのリソースに加えて、代替リソースを提供する必要があります。 + +

+ + +

代替リソースを提供する

+ + +
+ +

+図 1. 別のレイアウト リソースを使用する、2 つの異なる端末。

+
+ +

ほぼすべてのアプリケーションが、特定の端末設定をサポートするための代替リソースを提供します。 +たとえば、画面密度が異なる場合は代替ドローアブル リソースを含め、言語が異ならう場合は代替文字列リソースを含めます。 +実行時には、Android が現在の端末設定を検出し、アプリケーションに合ったリソースを読み込みます。 + +

+ +

一連のリソースに設定固有の代替リソースを指定するには:

+
    +
  1. {@code +<resources_name>-<config_qualifier>} の形式で名前を付けた {@code res/} に新しいディレクトリを作成します。 +
      +
    • {@code <resources_name>} は、対応するディレクトリ リソースのディレクトリ名です(表 1 で定義)。 +
    • +
    • {@code <qualifier>} は、リソースを使用する個々の設定を指定する名前です(表 2 で定義)。 +
    • +
    +

    1 つ以上の {@code <qualifier>} を末尾に追加できます。それぞれの修飾子はダッシュを使用して区切ります。 +

    +

    警告: 複数の修飾子を末尾に追加する場合は、表 2 に記載されているのと同じ順番で配置する必要があります。 +修飾子の順番に誤りがあると、リソースが無視されます。 +

    +
  2. +
  3. それぞれの代替リソースをこの新しいディレクトリに保存します。リソース ファイルの名前は、デフォルトのリソース ファイルと同じものにする必要があります。 +
  4. +
+ +

次に、デフォルト リソースと代替リソースの例を示します。

+ +
+res/
+    drawable/   
+        icon.png
+        background.png    
+    drawable-hdpi/  
+        icon.png
+        background.png  
+
+ +

{@code hdpi} 修飾子は、ディレクトリ内のリソースが高密度画面を持つ端末用であることを表します。 +これらのドローアブル ディレクトリの画像は特定の画面密度に合わせたサイズになっていますが、ファイル名は完全に同じになります。 + +このように、{@code icon.png} や {@code +background.png} 画像を参照するのに使用するリソース ID は常に同じになりますが、Android はリソース ディレクトリ名の修飾子により端末設定情報を比較して、各リソースの現在の端末に最適なバージョンを選択します。 + +

+ +

Android では複数の設定修飾子をサポートしており、それぞれの修飾子をダッシュで区切ることで、1 つのディレクトリ名に複数の修飾子を追加できます。 +表 2 には、有効な設定修飾子が優先度順に記載されています。—参照ディレクトリに複数の修飾子を使用する場合、表に記載されている順番にディレクトリ名に追加する必要があります。 + + +

+ + +

表 2. 設定修飾子の名前。 +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
設定修飾子の値説明
MCC と MNC例:
+ mcc310
+ mcc310-mnc004
+ mcc208-mnc00
+ など +
+

モバイル カントリーコード(MCC)です。端末の SIM カードにあるモバイル ネットワーク コード(MNC)が続くことがあります。 +たとえば、mcc310 は米国の任意の携帯通信会社、mcc310-mnc004 は米国の Verizon、mcc208-mnc00 はフランスの Orange となります。 + +

+

端末が無線通信接続(GSM 電話)を使用する場合、MCC と MNC の値は SIM カードの値となります。 +

+

さらに、MCC のみを使用することもできます(たとえば、アプリケーションに国固有の法に関するリソースを含める場合)。 +言語のみに基づいて指定する場合は、代わりに言語と地域の修飾子(次のセクションで解説)を使用します。 +MCC と MNC 修飾子を使用する場合は、慎重に設定し、予測のとおりに機能することをテストします。 +

+

さらに、設定フィールド {@link +android.content.res.Configuration#mcc}、{@link +android.content.res.Configuration#mnc} もご覧ください。これらは、それぞれ、現在のモバイル カントリーコードとモバイル ネットワーク コードを表します。 +

+
言語と地域例:
+ en
+ fr
+ en-rUS
+ fr-rFR
+ fr-rCA
+ など +

言語は 2 文字の ISO 639-1 言語コードで定義し、オプションで、2 文字の ISO 3166-1-alpha-2 地域コードを後ろに追加します(先頭に小文字の「{@code r}」が付きます)。 + + + +

+ コードでは大文字と小文字が区別されません。地域を区別するには、{@code r} 接頭辞を使用します。 + + 地域のみは指定できません。

+

ユーザーがシステム設定で言語を変更すると、アプリケーションの実行中にこの値が変更されます。 +実行時のアプリケーションに与える影響については、実行時の変更の処理をご覧ください。 +

+

アプリケーションを他の言語にローカライズするための詳細なガイドは、「Localization」をご覧ください。 +

+

さらに、{@link android.content.res.Configuration#locale} 設定フィールドもご覧ください。これは、現在のロケールを表します。 +

+
レイアウトの方向ldrtl
+ ldltr
+

アプリケーションのレイアウトの方向です。{@code ldrtl} は、「レイアウト方向は右から左」という意味になります。{@code ldltr} は、「レイアウト方向は左から右」という意味になります。この設定が暗黙的にデフォルト値となります。 + +

+

この設定は、レイアウト、ドローアブル、値など、任意のリソースに適用できます。 +

+

たとえば、アラビア語に何らかの特定なレイアウトを提供し、その他の「右から左」の言語(ペルシャ語やヘブライ語)に汎用的なレイアウトを提供する場合、次のように設定します。 + +

+
+res/
+    layout/   
+        main.xml  (Default layout)
+    layout-ar/  
+        main.xml  (Specific layout for Arabic)
+    layout-ldrtl/  
+        main.xml  (Any "right-to-left" language, except
+                  for Arabic, because the "ar" language qualifier
+                  has a higher precedence.)
+
+

注: アプリで右から左のレイアウト機能を有効にするには、{@code + supportsRtl} を {@code "true"} に設定し、{@code targetSdkVersion} を 17 以上に設定します。 +

+

API レベル 17 で追加。

+
smallestWidthsw<N>dp

+ 例:
+ sw320dp
+ sw600dp
+ sw720dp
+ など +
+

使用可能な画面領域の最小寸法で指定する、画面の基本サイズ。 +具体的には、端末の smallestWidth は画面に使用できる高さと幅の最小サイズとなります(画面の「使用できる最小幅」と考えることもできます)。 +画面の現在の方向に関係なく、この修飾子を使用することで、アプリケーションの UI に少なくとも {@code <N>} dps の幅を使用できます。 + +

+

たとえば、画面領域のレイアウトの最小寸法を常に 600 dp とする必要がある場合、この修飾子を使用してレイアウト リソース {@code +res/layout-sw600dp/} を作成できます。 +システムは、600dp の側が縦になるか横になるか関係なく、使用可能な画面の最小寸法が 600dp 以上の場合にのみこれらのリソースを使用します。 + +smallestWidth は端末の固定された画面サイズ特性です。画面の向きが変わっても、端末の smallestWidth は変更されません。 +

+

端末の smallestWidth では画面の装飾とシステム UI が考慮されます。たとえば、端末の画面に smallestWidth の軸に沿ったスペースを考慮した固定 UI 要素がある場合、これらの画面ピクセルは UI に使用できないことから、システムは実際の画面サイズよりも小さな smallestWidth を宣言します。つまり、使用する値は、レイアウトが必要とする実際の最小寸法とする必要があります(通常は、この値は、画面の現在の向きに関係なく、レイアウトがサポートする「最小幅」となります)。 + + + + +

+

次に、一般的な画面サイズに使用する値を示します。

+
    +
  • 320。次のような画面設定を持つ端末。 +
      +
    • 240x320 ldpi(QVGA ハンドセット)
    • +
    • 320x480 mdpi(ハンドセット)
    • +
    • 480x800 hdpi(高密度ハンドセット)
    • +
    +
  • +
  • 480。480x800 mdpi(タブレットやハンドセット)などの画面。
  • +
  • 600。600x1024 mdpi(7 インチタブレット)などの画面。
  • +
  • 720。720x1280 mdpi(10 インチタブレット)などの画面。
  • +
+

アプリケーションで smallestWidth 修飾子の値が異なる複数のリソース ディレクトリを提供する場合、システムは端末の smallestWidth に最も近い(かつ超過しない)ものを使用します。 + +

+

API レベル 13 で追加。

+

さらに、{@code +android:requiresSmallestWidthDp} 属性もご覧ください。これは、アプリケーションが互換性を持つ最小の smallestWidth と、端末の smallestWidth 値を保持する {@link +android.content.res.Configuration#smallestScreenWidthDp} 設定フィールドを宣言します。 + +

+

さまざまな画面の設計とこの修飾子の使用方法についての詳細は、「Supporting Multiple Screens」のデベロッパー ガイドをご覧ください。 + +

+
使用可能な幅w<N>dp

+ 例:
+ w720dp
+ w1024dp
+ など +
+

リソースを使用する、使用可能な最小の画面幅を {@code dp} 単位で指定します。—<N> の値で定義します。 +画面の向きの縦と横を変更すると、現在の実際の幅に合わせて、この設定値が変更されます。 + +

+

アプリケーションでこの設定が異なる複数のリソース ディレクトリを提供する場合、システムは端末の現在の画面の幅に最も近い(かつ超過しない)ものを使用します。 + +この値は画面の装飾を考慮します。つまり、端末のディスプレイの左や右に何らかの固定 UI 要素がある場合、これらの UI 要素を考慮し、アプリケーションの使用可能なスペースを減らして、実際の画面サイズよりも小さな幅を使用します。 + + + +

+

API レベル 13 で追加。

+

さらに、{@link android.content.res.Configuration#screenWidthDp} 設定フィールドもご覧ください。これは現在の画面の幅を保持します。 +

+

さまざまな画面の設計とこの修飾子の使用方法についての詳細は、「Supporting Multiple Screens」のデベロッパー ガイドをご覧ください。 + +

+
使用可能な高さh<N>dp

+ 例:
+ h720dp
+ h1024dp
+ など +
+

リソースを使用する、使用可能な最小の画面幅を「dp」単位で指定します。—<N> の値で定義します。 +画面の向きの縦と横を変更すると、現在の実際の高さに合わせて、この設定値が変更されます。 + +

+

アプリケーションでこの設定が異なる複数のリソース ディレクトリを提供する場合、システムは端末の現在の画面の高さに最も近い(かつ超過しない)ものを使用します。 + +この値は画面の装飾を考慮します。つまり、端末のディスプレイの上や下に何らかの固定 UI 要素がある場合、これらの UI 要素を考慮し、アプリケーションの使用可能なスペースを減らして、実際の画面サイズよりも小さな高さを使用します。 + + + +固定されていない画面の装飾(全画面にした場合に非表示になる携帯電話のステータスバーなど)や、タイトルバーやアクションバーなどのウィンドウの装飾は、ここでは考慮されません。そのため、アプリケーションでは指定したスペースよりもやや小さなサイズに対処できるように準備をし置く必要があります。 + + + + +

API レベル 13 で追加。

+

さらに、{@link android.content.res.Configuration#screenHeightDp} 設定フィールドもご覧ください。これは現在の画面の幅を保持します。 +

+

さまざまな画面の設計とこの修飾子の使用方法についての詳細は、「Supporting Multiple Screens」のデベロッパー ガイドをご覧ください。 + +

+
画面サイズ + small
+ normal
+ large
+ xlarge +
+
    +
  • {@code small}: 低密度 QVGA 画面と同等のサイズを持つ画面です。 +小さい画面の最小レイアウト サイズは約 320x426 dp 単位です。 +たとえば、QVGA 低密度や VGA 高密度です。 +
  • +
  • {@code normal}: 中密度 HVGA 画面と同等のサイズを持つ画面です。 +通常の画面の最小レイアウト サイズは約 320x470 dp 単位です。 +たとえば、WQVGA 低密度、HVGA 中密度、WVGA 高密度といった画面になります。 + +
  • +
  • {@code large}: 中密度 VGA 画面と同等のサイズを持つ画面です。 + + 大きな画面の最小レイアウト サイズは約 480x640 dp 単位です。 + たとえば、VGA や WVGA の中密度といった画面です。
  • +
  • {@code xlarge}: 従来の中密度 HVGA 画面よりもはるかに大きなサイズの画面です。 +特大の画面の最小レイアウト サイズは約 720x960 dp 単位です。 +ほとんどの場合、特大の画面を持つ端末はポケットに入れて持ち運ぶことができないほど大きく、タブレット スタイルの端末になります。 + +API レベル 9 で追加。
  • +
+

注: サイズ識別子を持つ場合でも、リソースがそのサイズの画面以外には対応しないということではありません。 +現在の端末に最適な識別子を持つ代替リソースを提供していない場合でも、システムによって最適なリソースが使用されることがあります。 + +

+

警告: すべてのリソースが現在の画面よりも大きなサイズ識別子を使用している場合、システムはそれらのリソースを使用せず、アプリケーションが実行時にクラッシュしてしまいます(たとえば、すべてのリソースに {@code +xlarge} 識別子のタグが付いているが、端末は通常サイズの画面である場合)。 + +

+

API レベル 4 で追加。

+ +

詳細については、「Supporting Multiple Screens」をご覧ください。 +

+

さらに、{@link android.content.res.Configuration#screenLayout} 設定フィールドもご覧ください。これは、画面のサイズが小、中、大のいずれかであるかを表します。 + +

+
画面アスペクト + long
+ notlong +
+
    +
  • {@code long}: WQVGA、WVGA、FWVGA のような長い画面
  • +
  • {@code notlong}: QVGA、HVGA、VGA のように長くない画面
  • +
+

API レベル 4 で追加。

+

これは純粋に画面のアスペクト比を基準とします(「長い」画面は幅広の画面になります)。これは画面の向きには関係ありません。 +

+

さらに、{@link android.content.res.Configuration#screenLayout} 設定フィールドもご覧ください。これは、画面のサイズが長いかどうかを表します。 +

+
画面の向き + port
+ land +
+
    +
  • {@code port}: 端末は縦向き(垂直)になっています
  • +
  • {@code land}: 端末は横向き(水平)になっています
  • + +
+

ユーザーが画面を回転すると、アプリケーションの実行中にこの値が変更されます。 +実行時のアプリケーションに与える影響については、「実行時の変更の処理」をご覧ください。 +

+

さらに、{@link android.content.res.Configuration#orientation} 設定フィールドもご覧ください。これは、現在の端末の画面の向きを表します。 +

+
UI モード + car
+ desk
+ television
+ appliance + watch +
+
    +
  • {@code car}: 端末は車載ドックで表示されています
  • +
  • {@code desk}: 端末はデスクドックで表示されています
  • +
  • {@code television}: 端末はテレビで表示されており、「10 フィート」離れた位置からの操作が可能です。UI はユーザーから離れた場所にある大きな画面に表示され、主に十字キーやポインタ以外のやり取りで操作します。 + + +
  • +
  • {@code appliance}: 端末をアプライアンスとして使用します。ディスプレイはありません +
  • +
  • {@code watch}: 端末にはディスプレイがあり、手首に装着して使用します
  • +
+

API レベル 8 で追加。television は API 13 で追加。watch は API 20 で追加。

+

端末をホルダーに装着したり、取り外したりしたときのアプリの反応については、「Determining and Monitoring the Docking State and Type」をご覧ください。 + +

+

ユーザーが端末をドックに装着すると、アプリケーションの実行中にこの値が変更されます。 +{@link +android.app.UiModeManager} を使用すると、これらのモードの一部を有効または無効にできます。実行時のアプリケーションに与える影響については、「実行時の変更の処理」をご覧ください。 +

+
ナイトモード + night
+ notnight +
+
    +
  • {@code night}: 夜間
  • +
  • {@code notnight}: 昼間
  • +
+

API レベル 8 で追加。

+

ナイトモードを自動モード(デフォルト)のままにしておくと、時間を基準にしてモードが変更された場合に、アプリケーションの実行中にこの値が変更されます。 +{@link android.app.UiModeManager} を使用すると、このモードを有効または無効にできます。 +実行時のアプリケーションに与える影響については、「実行時の変更の処理」をご覧ください。 +

+
画面ピクセル密度(dpi) + ldpi
+ mdpi
+ hdpi
+ xhdpi
+ xxhdpi
+ xxxhdpi
+ nodpi
+ tvdpi +
+
    +
  • {@code ldpi}: 低密度の画面。約 120dpi です。
  • +
  • {@code mdpi}: 中密度(従来の HVGA)の画面。約 160dpi です。 +
  • +
  • {@code hdpi}: 高密度の画面。約 240dpi です。
  • +
  • {@code xhdpi}: 超高密度の画面。約 320dpi です。API レベル 8 で追加。 +
  • +
  • {@code xxhdpi}: 超超高密度の画面。約 480dpi です。API レベル 16 で追加。 +
  • +
  • {@code xxxhdpi}: 超超高密度が使用(ランチャー アイコンのみ。「Supporting Multiple Screens」のをご覧ください)。約 640dpi です。 + +API レベル 18 で追加。 +
  • +
  • {@code nodpi}: 端末の密度に合わせてサイズを変更しないビットマップ リソースに使用します。 +
  • +
  • {@code tvdpi}: 密度が mdpi と hdpi の間の画面。約 213dpi です。これは「プライマリ」の密度グループとしては認識されません。 +ほとんどの場合、テレビ向けのものであり、大部分のアプリは必要としません。—通常、アプリの場合は mdpi と hdpi リソースを提供すれば十分であり、システムが必要に応じてサイズを変更します。 + +この識別子は API レベル 13 で導入されました。
  • +
+

6 つのプライマリ密度のサイズの比率は 3:4:6:8:12:16 です(tvdpi 密度を除く)。 +そのため、ldpi では 9x9 のビットマップ、mdpi では 12x12 のビットマップ、hdpi では 18x18 のビットマップ、xhdpi では 24x24 のビットマップとなります。 +

+

画像リソースがテレビや他の特定の端末で正常に表示されず、tvdpi リソースを試す場合、拡張係数は 1.33*mdpi になります。 +たとえば、画面向けの 100px x 100px 画像は、tvdpi 向けにすると 133px x 133px となります。 +

+

注: 密度識別子を持つ場合でも、リソースがその密度の画面以外には対応しないということではありません。 +現在の端末に最適な識別子を持つ代替リソースを提供していない場合でも、システムによって最適なリソースが使用されることがあります。 + +

+

異なる画面密度を処理する方法や、Android が現在の密度に合わせてビットマップのサイズを変更する方法については、「Supporting Multiple Screens」をご覧ください。 + +

+
タッチスクリーン タイプ + notouch
+ finger +
+
    +
  • {@code notouch}: 端末にはタッチスクリーンが搭載されていません。
  • +
  • {@code finger}: 端末には、ユーザーの指先による指示操作で使用するためのタッチスクリーンが搭載されています。 +
  • +
+

{@link android.content.res.Configuration#touchscreen} 設定フィールドもご覧ください。これは、端末上のタッチスクリーンのタイプを表します。 +

+
キーボードの使用可能状況 + keysexposed
+ keyshidden
+ keyssoft +
+
    +
  • {@code keysexposed}: 端末ではキーボードを使用できます。端末でソフトウェア キーボードが有効な(と想定される)場合、端末にハードウェア キーボードが接続されておらず、ハードウェア キーボードがユーザーに提示されていない場合でも、この値が使用されることがあります。 + +ソフトウェア キーボードが提供されていない、または無効になっている場合は、ハードウェア キーボードが認識された場合にのみ使用されます。 + +
  • +
  • {@code keyshidden}: 端末には使用可能なハードウェア キーボードがありますが非表示になっています。また、端末ではソフトウェア キーボードが有効になっていません。 +
  • +
  • {@code keyssoft}: 表示されているかどうかに関係なく、端末ではソフトウェア キーボードが有効になっています。 +
  • +
+

keysexposed リソースを提供するが、keyssoft リソースを提供しない場合、システムのソフトウェア キーボードが有効になっている場合は、キーボードが表示されているかに関係なく、システムは keysexposed リソースを使用します。 + +

+

ユーザーがハードウェア キーボードを開くと、アプリケーションの実行中にこの値が変更されます。 +実行時のアプリケーションに与える影響については、「実行時の変更の処理」をご覧ください。 +

+

さらに、設定フィールド {@link +android.content.res.Configuration#hardKeyboardHidden} と {@link +android.content.res.Configuration#keyboardHidden} もご覧ください。これは、ハードウェア キーボードの可視性、任意のキーボード(ソフトウェアを含む)の可視性、それぞれの可視性を表します。 +

+
主なテキストの入力方法 + nokeys
+ qwerty
+ 12key +
+
    +
  • {@code nokeys}: 端末にはテキスト入力用のハードウェア キーはありません。
  • +
  • {@code qwerty}: ユーザーに表示されているかどうかに関係なく、端末にはハードウェア クワーティ キーボードが搭載されています。 + +
  • +
  • {@code 12key}: ユーザーに表示されているかどうかに関係なく、端末にはハードウェア 12key キーボードが搭載されています。 +
  • +
+

さらに、{@link android.content.res.Configuration#keyboard} 設定フィールドもご覧ください。これは、使用可能な主なテキストの入力方法を表します。 +

+
プラットフォーム バージョン(API レベル)例:
+ v3
+ v4
+ v7
+ など
+

端末がサポートする API レベル。たとえば、v1 は API レベル 1(Android 1.0 以降の端末)、v4 は API レベル 4(Android 1.6 以降の端末)となります。 + +これらの値の詳細については、「Android API levels」のドキュメントをご覧ください。 +

+
+ + +

注: Android 1.0 以降、いくつかの設定識別子が追加されているため、すべてのバージョンの Android がすべての識別子をサポートしているわけではありません。 +新しい識別子を使用すると、旧式の端末がその識別子を無視できるように、プラットフォーム バージョンの識別子が暗黙的に追加されます。 +たとえば、available-width の識別子は API レベル 13 で新たに追加されたため、w600dp 識別子を使用すると、自動的に v13 識別子が追加されます。 + +問題を回避するために、常に一連のデフォルト リソースを含めるようにします(識別子のない一連のリソース)。 +詳細は、リソースとの最適な端末の互換性を提供するセクションをご覧ください。 + +

+ + + +

修飾子の名前のルール

+ +

ここでは、設定修飾子の名前の使用方法に関するルールを説明します。

+ + + +

これらの修飾子を名前に持つディレクトリに代替リソースを保存すると、現在の端末設定に基づいて、Android がアプリケーションにリソースを自動的に適用します。 + +リソースが要求されるたびに、Android は要求されたリソース ファイルを持つ代替リソース ディレクトリを探し、最適なリソースを見つけます(後述)。 + +特定の端末設定に合う代替リソースがない場合、Android は対応するデフォルト リソース(設定識別子を含まない特定のリソースタイプ用の一連のリソース)を使用します。 + + +

+ + + +

エイリアス リソースを作成する

+ +

複数の端末で使用するリソースがある場合(ただし、デフォルト リソースとして提供しない場合)、同じリソースを複数の代替リソース ディレクトリに配置する必要はありません。 + +その代わりに、(場合によっては)デフォルト リソースのディレクトリに保存したリソースのエイリアスとして機能する代替リソースを作成できます。 + +

+ +

注: すべてのリソースに、別のリソースへのエイリアスを作成できるメカニズムが備わっているわけではありません。 +特に、アニメーション、メニュー、未加工、{@code xml/} で指定されていないその他のリソースにはこの機能を使用できません。 +

+ +

たとえば、アプリケーション アイコン {@code icon.png} について、ロケールごとに一意のバージョンが必要になるという状況を考えてみます。 +ただし、英語カナダとフランス語カナダの 2 つのロケールでは同じバージョンを使用する必要があります。 +英語カナダとフランス語カナダの両方のリソース ディレクトリに同じ画像をコピーする必要は実際にはありません。 + +代わりに、両方に使用する画像を {@code icon_ca.png} という名前({@code icon.png} 以外の名前)で保存し、デフォルトの {@code res/drawable/} ディレクトリに配置します。 + +次に {@code <bitmap>} 要素を使用して {@code icon_ca.png} リソースを参照する {@code icon.xml} ファイルを {@code +res/drawable-en-rCA/} と {@code res/drawable-fr-rCA/} に作成します。 +これにより、PNG ファイルを 1 つだけ作成し、それを指す小さな XML ファイルを 2 つ作成するだけで済みます +(次に、XML ファイルの例を示します)。

+ + +

ドローアブル

+ +

既存のドローアブルのエイリアスを作成するには、{@code <bitmap>} 要素を使用します。次に例を示します。 +

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
+    android:src="@drawable/icon_ca" />
+
+ +

このファイルを {@code icon.xml} として({@code res/drawable-en-rCA/} などの代替リソース ディレクトリに)保存すると、{@code R.drawable.icon} として参照可能なリソースにコンパイルされますが、実際のところ、これは({@code res/drawable/} に保存されている){@code +R.drawable.icon_ca} リソースのエイリアスです。 + +

+ + +

レイアウト

+ +

既存のレイアウトのエイリアスを作成するには、{@code <merge>} にラップされる {@code <include>} 要素を使用します。 +次に例を示します。

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<merge>
+    <include layout="@layout/main_ltr"/>
+</merge>
+
+ +

このファイルを {@code main.xml} として保存すると、{@code R.layout.main} として参照可能なリソースにコンパイルされますが、実際のところ、これは {@code R.layout.main_ltr} リソースのエイリアスです。 + +

+ + +

文字列とその他の単純な値

+ +

既存の文字列のエイリアスを作成するには、単に、目的の文字列のリソース ID を新しい文字列の値として使用します。 +次に例を示します。

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <string name="hello">Hello</string>
+    <string name="hi">@string/hello</string>
+</resources>
+
+ +

{@code R.string.hi} リソースは {@code R.string.hello} のエイリアスになりました。

+ +

その他の単純な値も同様に機能します。 +次に色の例を示します。

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <color name="yellow">#f00</color>
+    <color name="highlight">@color/red</color>
+</resources>
+
+ + + + +

リソースとの最適な端末の互換性を提供する

+ +

アプリケーションで複数の端末設定をサポートするには、アプリケーションが使用するそれぞれのタイプのリソースに常にデフォルト リソースを提供することが重要です。 +

+ +

たとえば、アプリケーションが複数の言語をサポートする場合は、言語と地域の修飾子持たない {@code +values/} ディレクトリ(文字列の保存先)を常に用意します。すべての文字列ファイルをすべて言語と地域の修飾子を持つディレクトリに配置してしまうと、自分の文字列がサポートしていない言語に設定された端末でアプリケーションを実行すると、アプリケーションがクラッシュしてしまいます。 + +ただし、デフォルトの {@code values/} リソースを提供しておけば、アプリケーションは適切に実行されます(ユーザーがその言語を理解できない場合でも。—クラッシュは避けられます)。 + +

+ +

同様に、画面の向きに基づいた異なるレイアウト リソースを提供する場合も、いずれかの方向をデフォルトに設定する必要があります。 +たとえば、横向きの {@code +layout-land/} と縦向きの {@code layout-port/} にレイアウト リソースを提供する代わりに、横向きの {@code layout/} と縦向きの {@code layout-port/} のようなリソースをデフォルトとして保存しておきます。 +

+ +

予測しなかった設定でアプリケーションが実行されるだけでなく、新しいバージョンの Android では古いバージョンではサポートされない設定修飾子が追加されることもあるため、デフォルト リソースの提供が重要になります。 + +新しいリソース修飾子を使用するが、古いバージョンの Android とのコードの互換性を保持する場合、古いバージョンの Android でアプリケーションを実行するときにデフォルト リソースが提供されていないと、古いバージョンの Android では新しい修飾子の付いたリソースを使用できないために、アプリケーションがクラッシュします。 + + +たとえば、{@code +minSdkVersion} が 4 に設定されている場合に、ナイトモード(API レベル 8 で追加された {@code night} または {@code notnight})を使用してすべてのドローアブル リソースの修飾子を設定すると、API レベル 4 端末はドローアブル リソースにアクセスできず、クラッシュします。 +この場合、{@code notnight} をデフォルト リソースにする場合、その修飾子を除外することになるため、ドローアブル リソースは {@code drawable/} または {@code drawable-night/} のいずれかになります。 + +

+ +

そこで、端末の最適な互換性を提供するには、アプリケーションを適切に実行するのに必要なリソースに常にデフォルト リソースを提供するようにします。 +次に、設定修飾子を使用して、特定の端末設定向けの代替リソースを作成します。 +

+ +

このルールには次の例外があります。アプリケーションの {@code minSdkVersion} が 4 以上の場合、画像密度修飾子で代替のドローアブル リソースを提供するときには、デフォルトのドローアブル リソースを提供する必要はありません。 + +デフォルトのドローアブル リソースがない場合でも、Android は代替の画面密度のなかで最適なものを見つけて、必要に応じてビットマップのサイズを変更できます。 + +ただし、すべてのタイプに最適な操作性を提供するには、密度の 3 つのタイプすべてに代替ドローアブルを提供する必要があります。 +

+ + + +

Android が最適なリソースを見つける仕組み

+ +

代替を提供するリソースを要求すると、現在の端末設定に応じて Android が実行時に使用する代替リソースを選択します。 +Android が代替リソースを選択する方法説明するために、次のドローアブル ディレクトリを想定します。それぞれに同じ画像の異なるバージョンが入っています。 + +

+ +
+drawable/
+drawable-en/
+drawable-fr-rCA/
+drawable-en-port/
+drawable-en-notouch-12key/
+drawable-port-ldpi/
+drawable-port-notouch-12key/
+
+ +

さらに、次のような端末設定を想定します。

+ +

+ロケール = en-GB
+画面の向き = port
+画面ピクセル密度 = hdpi
+タッチスクリーン タイプ = notouch
+主なテキストの入力方法 = 12key +

+ +

端末設定を使用可能な代替リソースに比較して、Android は {@code drawable-en-port} からドローアブルを選択します。 +

+ +

システムは、次のロジックを使用して、使用するリソースを決定します。 +

+ + +
+ +

図 2. Android が最適なリソースを見つける仕組みを示したフローチャート。 +

+
+ + +
    +
  1. 端末設定に矛盾するリソース ファイルを排除します。 +

    en-GB ロケールに矛盾するため、drawable-fr-rCA/ ディレクトリが排除されます。 +

    +
    +drawable/
    +drawable-en/
    +drawable-fr-rCA/
    +drawable-en-port/
    +drawable-en-notouch-12key/
    +drawable-port-ldpi/
    +drawable-port-notouch-12key/
    +
    +

    例外: 画面ピクセル密度は、矛盾により排除されない修飾子の 1 つです。 +それぞれの画面密度はその時点で最適であると判断されたものであるため、端末の画面密度が hdpi の場合でも、drawable-port-ldpi/ は排除されません。 + +詳細は、「Supporting Multiple Screens」のドキュメントをご覧ください。 +

  2. + +
  3. リスト(表 2)にある(次に)優先される修飾子を選択します(MCC から順に下がっていきます)。 +
  4. +
  5. この修飾子を含むリソース ディレクトリがあるかどうかを確認します。
  6. +
      +
    • ない場合は、ステップ 2 に戻り、次の修飾子を調べます(この例では、言語識別子になるまですべて「いいえ」になります)。 +
    • +
    • 「はい」の場合は、ステップ 4 に進みます。
    • +
    + + +
  7. この修飾子を持たないリソース ディレクトリを排除します。この例では、システムによって言語修飾子を含まないすべてのディレクトリが排除されます。 +
  8. +
    +drawable/
    +drawable-en/
    +drawable-en-port/
    +drawable-en-notouch-12key/
    +drawable-port-ldpi/
    +drawable-port-notouch-12key/
    +
    +

    例外: 対象となる修飾子が画面ピクセル密度の場合、Android は端末の画像密度に最も近いオプションを選択します。一般的に、Android では小さな元画像を拡大するよりも、大きな元画像を縮小する方法が使用されます。 + + +「Supporting Multiple Screens」をご覧ください。 +

    + + +
  9. 残るディレクトリが 1 つになるまで、元に戻ってステップ 2、3、4 を繰り返します。このレイでは、次にマッチングするのが画面の向きの修飾子です。そのため、画面の向きを指定しないリソースは排除されます。 + + +
    +drawable-en/
    +drawable-en-port/
    +drawable-en-notouch-12key/
    +
    +

    {@code drawable-en-port} ディレクトリが残ります。

    +
  10. +
+ +

この手順は要求した各リソースに対して実行されますが、システムはさらにいくつかの最適化を行います。 +たとえば、端末の設定が判明すると、マッチングしない代替リソースが排除されたりします。 +たとえば、設定言語が英語(「en」)の場合、言語修飾子が英語以外に設定されているリソース ディレクトリはチェック対象のリソースのプールに入ることはありません(言語修飾子のないリソース ディレクトリはそのままプールに入ります)。 + + +

+ +

画面サイズ修飾子に基づいてリソースを選択する場合、最適なリソースがない場合、システムは現在の画面よりも小さな画面向けのリソースを使用します(たとえば、必要に応じて大きなサイズの画面が通常サイズの画面のリソースを使用します)。 + +ただし、使用できるリソースが現在の画面よりも大きなサイズのものしかない場合は、システムはそれらのリソースを使用せず、端末設定にあるその他のリソースがないときは、アプリケーションがクラッシュします(たとえば、すべてのリソースに {@code xlarge} 識別子のタグが付いているが、端末は通常サイズの画面である場合) + + + +

+ +

注:表 2 の)上位にある修飾子の方が、端末に正確に一致する修飾子の数よりも重要になります。 +たとえば、上のステップ 4 では、drawable-en には一致するパラメータが 1 つしかありませんが(言語)、リストの最後の選択肢には、端末に正確に一致する修飾子が 3 つあります(画面の向き、タッチスクリーン タイプ、入力方法)。 + + +ただし、言語はこれらの他の修飾子よりも優先されるため、drawable-port-notouch-12key は排除されます。 +

+ +

アプリケーションでのリソースの使用方法については、「リソースへのアクセス」をご覧ください。

diff --git a/docs/html-intl/intl/ja/guide/topics/resources/runtime-changes.jd b/docs/html-intl/intl/ja/guide/topics/resources/runtime-changes.jd new file mode 100644 index 0000000000000000000000000000000000000000..9bce95aee1152852999eca36e99a5bf3b20b6ac1 --- /dev/null +++ b/docs/html-intl/intl/ja/guide/topics/resources/runtime-changes.jd @@ -0,0 +1,281 @@ +page.title=実行時の変更の処理 +page.tags=アクティビティ,ライフサイクル +@jd:body + + + +

端末の構成の中には、実行時に変化するものがあります(画面の向き、キーボードの可用性、言語など)。 +そのような変更が発生すると、Android は実行中の {@link android.app.Activity} を再起動します({@link android.app.Activity#onDestroy()} が呼び出され、その後に{@link +android.app.Activity#onCreate(Bundle) onCreate()} が呼び出されます)。 + +再起動の動作は、新しい端末構成に一致する代替リソースを使用してアプリケーションを自動的にリロードすることで、アプリケーションを新しい構成に適応させることを目的としています。 + +

+ +

再起動を適切に処理するには、アクティビティが通常のアクティビティのライフサイクルを通じて事前の状態を格納することが重要です。その場合、アプリケーションの状態に関するデータを保存できるように、Android はアクティビティを破棄する前に {@link android.app.Activity#onSaveInstanceState(Bundle) onSaveInstanceState()} を呼び出します。 + + + +その後、{@link android.app.Activity#onCreate(Bundle) onCreate()} か {@link +android.app.Activity#onRestoreInstanceState(Bundle) onRestoreInstanceState()} の際に状態を格納できます。 +

+ +

アプリケーション自体がそのままの状態で再起動することをテストするには、アプリケーションでさまざまなタスクを実行中に、構成の変更(画面の向きの変更など)を呼び出す必要があります。 + +構成の変更を処理したり、ユーザーが電話を着信し、アプリケーション プロセスが破棄されるほど時間が経過してからアプリケーションに戻ったりするような場合には、ユーザーデータや状態を失うことなくいつでもアプリケーションを再起動できるようにする必要があります。 + + +アクティビティの状態を格納する方法については、「アクティビティのライフサイクル」をご覧ください。

+ +

ただし、場合によっては、アプリケーションを再起動すると、大量のデータの復元にコストがかかり、操作性が悪くなることがあります。 +そのような場合、次の 2 つの方法で処理できます。 +

+ +
    +
  1. 構成の変更中にオブジェクトを保持する +

    構成の変更時にアクティビティを再起動できますが、ステートフル オブジェクトがアクティビティの新しいインスタンスに移動します。 +

    + +
  2. +
  3. 構成の変更を自分で処理する +

    特定の構成変更の際に、システムがアクティビティを再起動しないようにしますが、必要に応じてアクティビティをアップデートできるように、構成が変更された場合には、コールバックを受け取ります。 + +

    +
  4. +
+ + +

構成の変更中にオブジェクトを保持する

+ +

アクティビティの再起動で大量のデータの復元、ネットワーク接続の再構築、他の負荷のかかる操作の実行が必要になる場合、構成変更のために完全な再起動を実行すると、操作性が悪くなってしまうことがあります。 + +さらに、システムの {@link +android.app.Activity#onSaveInstanceState(Bundle) onSaveInstanceState()} コールバックによって保存される {@link android.os.Bundle} を使用して、アクティビティの状態を完全に復元できない場合もあります。—大きなオブジェクト(ビットマップなど)を扱うためのものではなく、内部のデータをシリアル化してから逆シリアル化を行うため、多くのメモリを消費することになり、構成の変更に時間がかかってしまいます。 + + +そのような場合、構成の変更によるアクティビティの再起動の際に、{@link +android.app.Fragment} を保持することで、再初期化の負担を軽減できます。 +このフラグメントには、保持しておきたいステートフル オブジェクトへの参照を含めることができます。 +

+ +

構成変更により Android システムがアクティビティをシャットダウンするとき、保持するためのマークを設定しておくと、アクティビティのフラグメントが破棄されません。 +ステートフル オブジェクトを保持するために、アクティビティにこのようなフラグメントを追加しておくことができます。 +

+ +

実行時の構成変更の際に、フラグメントにステートフル オブジェクトを保持するには:

+ +
    +
  1. {@link android.app.Fragment} クラスを拡張し、ステートフル オブジェクトへの参照を宣言します。 +
  2. +
  3. フラグメントを作成するときに、{@link android.app.Fragment#setRetainInstance(boolean)} を呼び出します。 +
  4. +
  5. フラグメントをアクティビティに追加します。
  6. +
  7. アクティビティの再起動時にフラグメントを取得するには、{@link android.app.FragmentManager} を使用します。 +
  8. +
+ +

次は、フラグメントの定義の例です。

+ +
+public class RetainedFragment extends Fragment {
+
+    // data object we want to retain
+    private MyDataObject data;
+
+    // this method is only called once for this fragment
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        // retain this fragment
+        setRetainInstance(true);
+    }
+
+    public void setData(MyDataObject data) {
+        this.data = data;
+    }
+
+    public MyDataObject getData() {
+        return data;
+    }
+}
+
+ +

警告: オブジェクトの格納が可能な間は、{@link +android.graphics.drawable.Drawable}、{@link android.widget.Adapter}、{@link android.view.View}、さらには {@link android.content.Context} に関連付けられているその他のオブジェクトのように、{@link android.app.Activity} に結び付いたオブジェクトを渡さないようにしてください。 + +オブジェクトを渡すと、元のアクティビティ インスタンスのビューやリソースがすべて漏えいしてしまいます +(リソースが漏えいすると、アプリケーションはリソースを保持しているがガーベジコレクションを実行できず、大量のメモリが失われてしまうことがあります)。 + +

+ +

次に、{@link android.app.FragmentManager} を使用して、フラグメントをアクティビティに追加します。実行時に構成を変更する際にアクティビティを再び起動すると、フラグメントからデータ オブジェクトを取得できます。 + +次は、アクティビティの定義の例です。

+ +
+public class MyActivity extends Activity {
+
+    private RetainedFragment dataFragment;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.main);
+
+        // find the retained fragment on activity restarts
+        FragmentManager fm = getFragmentManager();
+        dataFragment = (DataFragment) fm.findFragmentByTag(“data”);
+
+        // create the fragment and data the first time
+        if (dataFragment == null) {
+            // add the fragment
+            dataFragment = new DataFragment();
+            fm.beginTransaction().add(dataFragment, “data”).commit();
+            // load the data from the web
+            dataFragment.setData(loadMyData());
+        }
+
+        // the data is available in dataFragment.getData()
+        ...
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+        // store the data in the fragment
+        dataFragment.setData(collectMyLoadedData());
+    }
+}
+
+ +

この例では、{@link android.app.Activity#onCreate(Bundle) onCreate()} がフラグメントを追加するか、フラグメントへの参照を復元します。さらに、{@link android.app.Activity#onCreate(Bundle) onCreate()} がステートフル オブジェクトをフラグメント インスタンスの内部に格納し、{@link android.app.Activity#onDestroy() onDestroy()} が保持しているフラグメント インスタンス内部のステートフル オブジェクトを更新します。 + + + +

+ + + + + +

構成の変更を自分で処理する

+ +

アプリケーションに特定の構成の変更の際にリソースを更新する必要がなく、パフォーマンスの制限によりアクティビティの再起動を回避する必要がある場合は、構成の変更をアクティビティ自身が処理することを宣言します。そうすることで、システムによってアクティビティが再起動されなくなります。 + + +

+ +

注: 構成の変更を自分で処理すると、変更がシステムによって自動的に適用されることがないため、代替リソースの使用が非常に難しくなります。 + +この手法は、構成の変更による再起動を回避する際の最後の手段として検討する手法です。ほとんどの場合、アプリケーションでの使用は推奨されません。 +

+ +

アクティビティで構成の変更を処理することを宣言するには、マニフェスト ファイルの該当する {@code <activity>} 要素を編集し、処理する構成を表す値とともに {@code +android:configChanges} 属性を配置します。 + +使用可能な値は、{@code +android:configChanges} 属性のドキュメントに一覧が記載されています(一般的には、画面の向きを変更した場合の再起動を回避するには {@code "orientation"} の値を、キーボードの可用性を変更した場合の再起動を回避するには {@code "keyboardHidden"} の値を使用します)。 + +パイプ記号の {@code |} 文字を使用して区切ることで、属性内に複数の構成値を宣言できます。 +

+ +

たとえば、次のマニフェスト コードでは、画面の向きとキーボードの可用性の変更の両方を処理するアクティビティを宣言しています。 +

+ +
+<activity android:name=".MyActivity"
+          android:configChanges="orientation|keyboardHidden"
+          android:label="@string/app_name">
+
+ +

これで、これらのいずれかの構成が変更された場合でも、{@code MyActivity} が再起動することはなくなりました。代わりに、{@code MyActivity} が {@link +android.app.Activity#onConfigurationChanged(Configuration) onConfigurationChanged()} への呼び出しを取得します。 +このメソッドには、新しい端末構成を指定する {@link android.content.res.Configuration} オブジェクトが渡されます。 + +{@link android.content.res.Configuration} のフィールドを読み込むことで新しい構成を判断し、インターフェースに使用するリソースを更新して適切に変更できます。 + +このメソッドが呼び出されると、アクティビティの {@link android.content.res.Resources} オブジェクトが更新され、新しい構成に基づいたリソースを返します。それにより、システムがアクティビティを再起動しなくても、UI の要素を簡単にリセットできます。 + + +

+ +

警告: Android 3.2(API レベル 13)以降では、端末の画面の向きを縦から横に切り替えると、「画面サイズ」も変更されます。 + +そのため、API レベル 13 以降で開発を行う場合({@code minSdkVersion} 属性と {@code targetSdkVersion} 属性により宣言)、画面の向きによる実行時の再起動を回避するには、{@code "screenSize"} 値と一緒に {@code +"orientation"} 値を使用する必要があります。 + +つまり、{@code +android:configChanges="orientation|screenSize"} を宣言する必要があります。ただし、アプリケーションのターゲットが API レベル 12 以前の場合は、常にアクティビティ自身がこの構成の変更を処理します(Android 3.2 以降の端末で実行している場合でも、この構成の変更によりアクティビティが再起動されることはありません)。 + +

+ +

たとえば、次の {@link +android.app.Activity#onConfigurationChanged(Configuration) onConfigurationChanged()} の実装により現在の端末の画面の向きがチェックされます。 +

+ +
+@Override
+public void onConfigurationChanged(Configuration newConfig) {
+    super.onConfigurationChanged(newConfig);
+
+    // Checks the orientation of the screen
+    if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
+        Toast.makeText(this, "landscape", Toast.LENGTH_SHORT).show();
+    } else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT){
+        Toast.makeText(this, "portrait", Toast.LENGTH_SHORT).show();
+    }
+}
+
+ +

{@link android.content.res.Configuration} オブジェクトは、変更された構成を除く、現在のすべての構成を表します。 +ほとんどの場合、構成の変更内容を正確に把握する必要はなく、代替の構成を提供するすべてのリソースを処理中の構成に割り当て直します。 + +たとえば、現在は {@link +android.content.res.Resources} オブジェクトが更新されたため、任意の {@link android.widget.ImageView} を {@link android.widget.ImageView#setImageResource(int) +setImageResource()} でリセットでき、新しい構成には最適なリソースが使用されます(「リソースの提供」をご覧ください)。 + +

+ +

{@link +android.content.res.Configuration} フィールドの値は、{@link android.content.res.Configuration} クラスの特定の定数に一致する整数であることにご注意ください。 +それぞれのフィールドで使用する定数に関するドキュメントについては、{@link +android.content.res.Configuration} リファレンスの該当するフィールドをご覧ください。 +

+ +

メモ: 構成の変更をアクティビティで処理することを宣言すると、代替を提供するすべての要素をリセットする操作が必要になります。 +画面の向きの変更を処理するアクティビティを宣言しており、縦向きと横向きで切り替わる画像がある場合、{@link +android.app.Activity#onConfigurationChanged(Configuration) onConfigurationChanged()} の際にそれぞれの要素に各リソースを割り当て直す必要があります。 + +

+ +

これらの構成の変更に基づいてアプリケーションを更新する必要がない場合は、代わりに {@link +android.app.Activity#onConfigurationChanged(Configuration) onConfigurationChanged()} を実装できません。 +このような場合、構成の変更前に使用していたすべてのリソースがそのまま使用され、アクティビティの再起動を回避した状態となります。 + +ただし、アプリケーションはいつでもシャットダウンして以前のままの状態で再起動できる状態にしておく必要があるため、通常のアクティビティのライフサイクルでは、状態を保持しない手段としてこの手法を使用しないようにします。 + +それは、アプリケーションの再起動を回避できない他の構成の変更があるだけでなく、ユーザーがアプリケーションを離れ、ユーザーがアプリケーションに戻る前に破棄されてしまうようなイベントを処理する必要があるためです。 + + +

+ +

アクティビティで処理できる構成の変更についての詳細は、{@code +android:configChanges} のドキュメントと{@link android.content.res.Configuration} クラスをご覧ください。 +

diff --git a/docs/html-intl/intl/ja/guide/topics/ui/controls.jd b/docs/html-intl/intl/ja/guide/topics/ui/controls.jd new file mode 100644 index 0000000000000000000000000000000000000000..0bc2063764bb65397992166cad2a9d4343ae2591 --- /dev/null +++ b/docs/html-intl/intl/ja/guide/topics/ui/controls.jd @@ -0,0 +1,90 @@ +page.title=入力コントロール +parent.title=ユーザー インターフェース +parent.link=index.html +@jd:body + +
+ +
+ +

入力コントロールは、アプリのユーザー インターフェースのインタラクティブなコンポーネントです。Android では、ボタン、テキスト フィールド、シークバー、チェックボックス、ズームボタン、トグルボタンなど UI で使用できるさまざまなコントロールが提供されています。 + +

+ +

UI に入力コントロールを追加することは、XML レイアウトに XML 要素を追加するのと同じくらい簡単です。テキスト フィールドとボタンを含むレイアウトの例を次に示します。 +

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    android:orientation="horizontal">
+    <EditText android:id="@+id/edit_message"
+        android:layout_weight="1"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:hint="@string/edit_message" />
+    <Button android:id="@+id/button_send"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/button_send"
+        android:onClick="sendMessage" />
+</LinearLayout>
+
+ +

各入力コントロールでは、特定の一連の入力イベントがサポートされているため、ユーザーがテキストを入力したり、ボタンをタップしたりするときなどに、イベントを処理できます。 +

+ + +

コモン コントロール

+

アプリで使用できるコモン コントロールには、次のようなものがあります。それぞれの使い方の詳細については、各リンクをご覧ください。 +

+ +

注: Android では、ここにリストされている以外にもいくつかコントロールが提供されています。 +他のコントロールについては、{@link android.widget} パッケージをご確認ください。アプリで、特定の種類の入力コントロールを必要とする場合、独自の カスタム コンポーネント をビルドできます。 +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
コントロール タイプ説明関連クラス
ボタンユーザーがアクションを実行するために、押したり、クリックしたりできるプッシュボタン。{@link android.widget.Button Button}
テキスト フィールド編集できるテキスト フィールド。AutoCompleteTextView ウィジェットを使って、オートコンプリート候補を表示するテキスト入力ウィジェットを作成できます。{@link android.widget.EditText EditText}、{@link android.widget.AutoCompleteTextView}
チェックボックスユーザーが切り替えることができる、オン、オフスイッチ。相互に排他的ではない選択可能なオプションのグループをユーザーに表示するときは、チェックボックスを使ってください。{@link android.widget.CheckBox CheckBox}
ラジオボタングループで選択できるオプションは 1 つのみであることを除き、チェックボックスと同様です。{@link android.widget.RadioGroup RadioGroup} +
{@link android.widget.RadioButton RadioButton}
トグルボタンライト インジケーター付きの、オン、オフボタン。{@link android.widget.ToggleButton ToggleButton}
スピナーユーザーが一連の値から 1 つを選択できるドロップダウン リスト。{@link android.widget.Spinner Spinner}
ピッカー上下のボタンを使うか、スワイプして、1 つの値を選択するためのダイアログ。日付(月、日、年)の値を入力するには DatePickercode> ウィジェットを使い、時刻(時間、分、午前または午後)の値を入力するには TimePicker ウィジェットを使います。これにより、ユーザーのロケールが自動的に書式設定されます。{@link android.widget.DatePicker}、{@link android.widget.TimePicker}
diff --git a/docs/html-intl/intl/ja/guide/topics/ui/declaring-layout.jd b/docs/html-intl/intl/ja/guide/topics/ui/declaring-layout.jd new file mode 100644 index 0000000000000000000000000000000000000000..b0e9c03111fe8f50e2b6769098cb5a38a2ec5a9d --- /dev/null +++ b/docs/html-intl/intl/ja/guide/topics/ui/declaring-layout.jd @@ -0,0 +1,492 @@ +page.title=レイアウト +page.tags=view,viewgroup +@jd:body + + + +

レイアウトでは、アクティビティアプリ ウィジェットの UI のような、ユーザー インターフェースの視覚的構造が定義されます。次の 2 つの方法でレイアウトを宣言できます。 +

+ + +

Android フレームワークでは、アプリケーション UI の宣言と管理に、これらのいずれかのメソッドまたは両方のメソッドを柔軟に使うことができます。たとえば、レイアウトに表示されるスクリーン要素やそのプロパティを含む、アプリケーションのデフォルト レイアウトを XML で宣言できます。スクリーン オブジェクトの状態を変更するコードをアプリケーション内で追加できます(実行時の XML での宣言を含む)。

+ + + +

XML で UI を宣言する利点は、動作を制御するコードとアプリケーションの表示をうまく切り離すことができる点です。UI の記述は、アプリケーション コード外にあるため、ソースコードの変更や再コンパイルをすることなく、記述を修正したり改良したりできます。たとえば、異なる画面の向き、端末の画面サイズ、言語に対して XML レイアウトを作成できます。XML でレイアウトを宣言すると、UI の構造を簡単に視覚化できるため、問題のデバッグも簡単です。そのため、本書では XML でレイアウトを宣言する方法を中心に説明しています。実行時に View オブジェクトのインスタンスを作成することに関心がある場合は、{@link android.view.ViewGroup} と {@link android.view.View} クラス参照をご覧ください。 + +

+ +

通常、UI 要素を宣言する XML ボキャブラリは、クラスとメソッドの構造と命名に厳密に従っており、要素名はクラス名に、属性名はメソッドに対応しています。実際、ほとんどの場合、直接的に対応しているため、クラスメソッドに対応する XML 属性を推測したり、与えられた XML 要素に対応するクラスを推測したりできます。ただし、すべてのボキャブラリが同一とは限りません。場合によっては、命名が若干異なります。たとえば、EditText 要素には、EditText.setText() に対応する text 属性があります。 + +

+ +

ヒント: 異なるレイアウト タイプの詳細については、共通レイアウト オブジェクトをご覧ください。 +また、Hello Views のチュートリアル ガイドには、さまざまなレイアウトのビルドに関するチュートリアルがあります。 +

+ +

XML の記述

+ +

Android の XML ボキャブラリを使って、HTML でウェブページを作成するのと同じ方法で(ネストした一連の要素を使って)、UI レイアウトとそれに含まれる画面要素を素早く設計できます。

+ +

各レイアウト ファイルには、必ず 1 つのルート要素が含まれていて、そのルート要素は View または ViewGroup オブジェクトでなくてはなりません。ルート要素を定義すれば、追加のレイアウト オブジェクトやウィジェットを子の要素として追加して、レイアウトを定義するビュー階層を少しずつビルドできます。{@link android.widget.TextView} と {@link android.widget.Button} を保持するために、縦方向の {@link android.widget.LinearLayout} を使用する XML レイアウトの例を次に示します。 +

+
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:layout_width="match_parent"
+              android:layout_height="match_parent"
+              android:orientation="vertical" >
+    <TextView android:id="@+id/text"
+              android:layout_width="wrap_content"
+              android:layout_height="wrap_content"
+              android:text="Hello, I am a TextView" />
+    <Button android:id="@+id/button"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Hello, I am a Button" />
+</LinearLayout>
+
+ +

XML でレイアウトを宣言した後、そのファイルを Android プロジェクトの res/layout/ ディレクトリ内で .xml 拡張子を付けて保存して、正しくコンパイルされるようにします。 +

+ +

レイアウト XML ファイルの構文の詳細については、「Layout Resources」のドキュメントをご覧ください。

+ +

XML リソースの読み込み

+ +

アプリケーションをコンパイルすると、各 XML レイアウト ファイルは {@link android.view.View} リソースにコンパイルされます。 +{@link android.app.Activity#onCreate(android.os.Bundle) Activity.onCreate()} コールバックの実装で、アプリケーション コードからレイアウト リソースを読み込んでください。これを行うには、{@link android.app.Activity#setContentView(int) setContentView()} を呼び出し、R.layout.layout_file_name の形式でレイアウト リソースへの参照を渡します。たとえば、XML レイアウトが main_layout.xml として保存される場合、次のようにしてアクティビティに読み込みます。 + + + + + +

+
+public void onCreate(Bundle savedInstanceState) {
+    super.onCreate(savedInstanceState);
+    setContentView(R.layout.main_layout);
+}
+
+ +

アクティビティの onCreate() コールバック メソッドは、アクティビティが起動されるときに、Android フレームワークによって呼び出されます(ライフサイクルに関する説明については、「アクティビティ」のドキュメントをご覧ください)。 + + +

+ + +

属性

+ +

すべての View と ViewGroup オブジェクトでは、独自のさまざまな XML 属性がサポートされています。一部の属性は、View オブジェクト特有のものですが(たとえば TextView では textSize 属性がサポートされています)、これらの属性はこのクラスを拡張可能な View オブジェクトによっても継承されます。一部は、ルート View クラスから継承されるため、すべての View オブジェクトに共通します(id 属性のような)。 + + + +その他の属性は「レイアウト パラメータ」として考慮され、オブジェクトの親である ViewGroup オブジェクトとして定義された、View オブジェクトの特定のレイアウト方向を記述する属性となります。 + +

+ +

ID

+ +

ツリー内で View を一意に識別するために、すべての View オブジェクトには、それに関連付けられた整数の ID があることがあります。アプリケーションがコンパイルされると、この ID は整数として参照されますが、一般的にその ID は id 属性で文字列としてレイアウト XML ファイルに割り当てられます。これは、すべての View オブジェクトに共通の XML 属性で({@link android.view.View} クラスで定義)、非常に頻繁に使用されます。XML タグ内の ID の構文を次に示します。 + + + + +

+
android:id="@+id/my_button"
+ +

文字列の先頭にあるアットマーク(@)は、その XML パーサーが ID の残りの文字列をパースして展開し、それを ID リソースとして識別する必要があることを示します。 +プラス記号(+)は、それが新しいリソース名で、作成してリソースに追加する(R.java ファイル内で)必要があることを意味しています。 +Android フレームワークによって提供されるその他のさまざまな ID リソースがあります。 +Android リソース ID を参照するときは、プラス記号を使う必要はありませんが、次のように android パッケージ名前空間を追加する必要があります。 +

+
android:id="@android:id/empty"
+

android パッケージ名前空間付きの場合、ローカルのリソースクラスからではなく android.R リソースクラスから ID を参照するようになります。 +

+ +

ビューを作成してアプリケーションからそれを参照するには、次のような共通のパターンがあります。

+
    +
  1. レイアウト ファイルでビューとウィジェットを定義して、一意の ID を割り当てる。 +
    +<Button android:id="@+id/my_button"
    +        android:layout_width="wrap_content"
    +        android:layout_height="wrap_content"
    +        android:text="@string/my_button_text"/>
    +
    +
  2. +
  3. 次に、View オブジェクトのインスタンスを作成し、それを次のようにレイアウトから取得する(通常は、{@link android.app.Activity#onCreate(Bundle) onCreate()} メソッド内で)。 + +
    +Button myButton = (Button) findViewById(R.id.my_button);
    +
    +
  4. +
+

View オブジェクトの ID を定義することは、{@link android.widget.RelativeLayout} を作成するときに重要になります。相対レイアウトでは、兄弟ビューは一意の ID で参照される別の兄弟ビューに相対するレイアウトを定義できます。 + +

+

ツリー全体で ID が一意である必要はありませんが、探しているツリーの一部では、一意である必要があります。ほとんどの場合、それはツリー全体になりますが、可能な時に完全に一意になるようにすることが最善です。 + +

+ + +

レイアウト パラメータ

+ +

layout_something という名前の XML レイアウト属性では、含まれている ViewGroup に適した View に対するレイアウト パラメータが定義されます。 +

+ +

すべての ViewGroup クラスでは、{@link +android.view.ViewGroup.LayoutParams} を拡張するネストされたクラスが実装されます。このサブクラスには、各子ビューのサイズと位置を ViewGroup に合うように定義するプロパティ タイプが含まれています。 + +図 1 にあるように、親 ViewGroup によって、子 ViewGroup を含む、各子ビューのレイアウト パラメータが定義されます。 +

+ + +

図 1. 各ビューに関連付けられたレイアウトのパラメータを含むビュー階層の視覚化。 +

+ +

すべての LayoutParams サブクラスには、設定値に独自の構文があります。 +それぞれの子の要素では、その親に適した LayoutParams が定義される必要がありますが、自分の子に対して異なる LayoutParams を定義することもできます。 +

+ +

すべての ViewGroup には、幅と高さ(layout_widthlayout_height)が含まれていて、各ビューでそれが定義されている必要があります。 +多くの LayoutParams には、省略可能なマージンと境界線も含まれています。 +

+ +

必要になる頻度は少ない可能性はありますが、幅と高さを正確なサイズで指定することもできます。 +ほとんどの場合は、次の定数のいずれかを使って幅や高さを設定します。 +

+ + + +

通常、ピクセルなどの絶対単位でレイアウトの幅と高さを指定することは推奨されません。 + +密度非依存ピクセル単位(dp)、 wrap_content、 +match_parentなどの相対測定を使う方が賢明です。そうすることで、さまざまな端末の画面サイズでアプリケーションが正しく表示されるようになります。使用可能な測定タイプは、「使用可能なリソース」のドキュメントで定義されています。 + + + +

+ + +

レイアウトの位置

+

+ ビューの形状は、矩形です。ビューには、の座標ペアで表される位置と、幅と高さで表される 2 次元があります。 + +位置と寸法の単位はピクセルです。 + +

+ +

+ {@link android.view.View#getLeft()} と {@link android.view.View#getTop()} メソッドを呼び出してビューの位置を取得できます。 +前者では、左またはビューを表す矩形の X 座標が返されます。 +後者では、上またはビューを表す矩形の Y 座標が返されます。 +これらのメソッドでは、両方とも親に相対するビューの位置が返されます。 +たとえば、getLeft() で 20 が返される場合、そのビューはその直属の親の左端から右に 20 ピクセルの位置にあることを意味します。 + + +

+ +

+ 他にも、{@link android.view.View#getRight()} や {@link android.view.View#getBottom()} といった不要な計算を回避するさまざまな便利なメソッドが提供されています。 + + これらのメソッドでは、ビューを表す矩形の右端と下端の座標が返されます。 +たとえば、{@link android.view.View#getRight()} を呼び出すことは、getLeft() + getWidth() の計算と同様です。 + +

+ + +

サイズ、パディング、マージン

+

+ ビューのサイズは、幅と高さで示します。実際に、ビューには幅と高さ 2 つの値がペアとして保持されています。 + +

+ +

+ 最初のペアは、測定された幅測定された高さと呼ばれます。 +これらの寸法で、その親内でのビューの大きさが定義されます。 +測定された寸法は、{@link android.view.View#getMeasuredWidth()} と {@link android.view.View#getMeasuredHeight()} を呼び出して取得できます。 + + +

+ +

+ 2 番目のペアは、単に高さと呼ばれたり、描画する幅描画する高さと呼ばれたりします。 +これらの寸法は、描画時とレイアウト後に、画面上のビューの実サイズを定義するものです。 + +これらの値は、測定された幅や高さと異なる値にすることもできますが、必須ではありません。 +幅と高さは、{@link android.view.View#getWidth()} と {@link android.view.View#getHeight()} を呼び出して取得できます。 + +

+ +

+ その寸法を測るために、ビューではパディングも考慮されます。パディングは、ビューの上下左右に対し、ピクセルで記述されます。 + + パディングを使って、特定のピクセル値でビュー コンテンツのオフセットを指定できます。 +たとえば、左側のパディングが 2 の場合は、左端から 2 ピクセル右にビューのコンテンツが寄せられます。 +パディングは {@link android.view.View#setPadding(int, int, int, int)} メソッドを使って設定でき、{@link android.view.View#getPaddingLeft()}、{@link android.view.View#getPaddingTop()}、{@link android.view.View#getPaddingRight()}、{@link android.view.View#getPaddingBottom()} を呼び出してクエリできます。 + + + +

+ +

+ ビューではパディングを定義できますが、マージンについてはサポートされていません。 +ただし、ViewGroup ではそのようなサポートが提供されています。詳細については、{@link android.view.ViewGroup} と {@link android.view.ViewGroup.MarginLayoutParams} をご覧ください。 + + +

+ +

寸法の詳細については、寸法の値をご覧ください。 + +

+ + + + + + + + + + + +

共通レイアウト

+ +

{@link android.view.ViewGroup} クラスの各サブクラスでは、ビュー内でネストするビューを表示する一意の方法が提供されます。 +Android プラットフォームにビルドされる共通のレイアウト タイプの一部を以下に示します。 +

+ +

注: 別のレイアウト内で 1 つ以上のレイアウトをネストして UI を設計できますが、レイアウトの階層はできる限り浅くしておくようにしてください。 + +ネストが浅いレイアウトの場合、レイアウトの描画がより速くなります。深いビュー階層より、ワイドなビュー階層の方がより良いと言えます。 +

+ + + + +
+

線形レイアウト

+ +

子を横方向または縦方向の 1 行にまとめるレイアウト。ウィンドウの長さが画面の長さを超える場合は、スクロールバーが作成されます。 +

+
+ +
+

相対レイアウト

+ +

たとえば子 A を子 B の左になど、それぞれの子オブジェクトの位置を相対して指定したり、親の上に揃えてなど、親に相対して指定したりできます。 +

+
+ +
+

ウェブビュー

+ +

ウェブページを表示します。

+
+ + + + +

アダプタを使ったレイアウトをビルドする

+ +

レイアウトのコンテンツが動的または事前設定されていないとき、{@link android.widget.AdapterView} のサブクラスのレイアウトを使って、実行時にビューでレイアウトを設定できます。 +{@link android.widget.AdapterView} クラスのサブクラスでは、{@link android.widget.Adapter} を使ってそのレイアウトにデータがバインドされます。 + +{@link android.widget.Adapter} はデータソースと {@link android.widget.AdapterView} レイアウト間の仲介として動作します。{@link android.widget.Adapter} によって、配列やデータベース クエリのようなソースからデータが取得され、各エントリが {@link android.widget.AdapterView} レイアウトに追加できるビューに変換されます。 + + +

+ +

アダプタでサポートされている共通レイアウトには次が含まれます。

+ +
+

リストビュー

+ +

スクロール可能な 1 列のリストが表示されます。

+
+ +
+

グリッドビュー

+ +

スクロール可能な列と行のグリッドが表示されます。

+
+ + + +

データを使ったアダプタビューを書き込む

+ +

{@link android.widget.AdapterView} インスタンスを {@link android.widget.Adapter} にバインドして、{@link android.widget.ListView} や {@link android.widget.GridView} などの {@link android.widget.AdapterView} を入力できます。外部ソースのデータが取得され、各データエントリを表す {@link +android.view.View} が作成されます。 + +

+ +

Android ではさまざまな種類のデータの取得と {@link android.widget.AdapterView} のビューのビルドに役立つ {@link android.widget.Adapter} のサブクラスがいくつか提供されます。 +最も一般的なアダプタは次の 2 つです。 +

+ +
+
{@link android.widget.ArrayAdapter}
+
データソースが配列の場合に、このアダプタを使います。デフォルトでは、{@link +android.widget.ArrayAdapter} によって各アイテムで {@link +java.lang.Object#toString()} が呼び出され、そのコンテンツが {@link +android.widget.TextView} に配置されることで、各配列アイテムのビューが作成されます。 +

たとえば、{@link +android.widget.ListView} に表示する文字列の配列がある場合、コンストラクタを使って新しい {@link android.widget.ArrayAdapter} を初期化して、各文字列と文字列配列のレイアウトを指定します。 +

+
+ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
+        android.R.layout.simple_list_item_1, myStringArray);
+
+

このコンストラクタの引数を次に示します。

+
    +
  • アプリの {@link android.content.Context}
  • +
  • 配列の各文字列に対して {@link android.widget.TextView} を含むレイアウト
  • +
  • 文字列配列
  • +
+

次に、{@link android.widget.ListView} で {@link android.widget.ListView#setAdapter setAdapter()} を呼び出します。 +

+
+ListView listView = (ListView) findViewById(R.id.listview);
+listView.setAdapter(adapter);
+
+ +

各アイテムの概観をカスタマイズするには、自分の配列内のオブジェクトに {@link +java.lang.Object#toString()} メソッドをオーバーライドします。または、たとえば各配列アイテムに {@link android.widget.ImageView} が必要な場合など、{@link android.widget.TextView} 以外の各アイテムの表示を作成するには、{@link +android.widget.ArrayAdapter} クラスを拡張し、{@link android.widget.ArrayAdapter#getView +getView()} をオーバーライドして、各アイテムに必要なビュータイプが返されるようにします。 + +

+ +
+ +
{@link android.widget.SimpleCursorAdapter}
+
{@link android.database.Cursor} から取得されたデータの場合は、このアダプタを使います。{@link android.widget.SimpleCursorAdapter} を使う場合は、{@link android.database.Cursor} で各行に使うレイアウトを指定して、{@link android.database.Cursor} のどの列をレイアウトのビューに挿入するのかを指定してください。 + + +たとえば、人の名前と電話番号のリストを作成する場合は、それぞれの人の行と、その名前と番号の列を含む {@link +android.database.Cursor} を返すクエリを実行します。 + +次に、それぞれの結果に対して {@link +android.database.Cursor} のどの列をレイアウトに含めるかを指定する文字列配列と、各列が配置されるべき対応するビューを指定する整数配列を作成します。 +

+
+String[] fromColumns = {ContactsContract.Data.DISPLAY_NAME,
+                        ContactsContract.CommonDataKinds.Phone.NUMBER};
+int[] toViews = {R.id.display_name, R.id.phone_number};
+
+

{@link android.widget.SimpleCursorAdapter} のインスタンスを作成するとき、各結果に使うレイアウト、結果を含む {@link android.database.Cursor}、次の 2 つの配列を渡します。 +

+
+SimpleCursorAdapter adapter = new SimpleCursorAdapter(this,
+        R.layout.person_name_and_number, cursor, fromColumns, toViews, 0);
+ListView listView = getListView();
+listView.setAdapter(adapter);
+
+

次に、{@link android.widget.SimpleCursorAdapter} によって、対応する {@code toViews} ビューに各 {@code +fromColumns} アイテムを挿入して提供されたレイアウトを使って {@link android.database.Cursor} で各行のビューが作成されます。 +

.
+
+ + +

アプリケーションのライフサイクル中に、アダプタによって読み取られる基礎となるデータを変更する場合は、{@link android.widget.ArrayAdapter#notifyDataSetChanged()} を呼び出してください。 +これによって、データが変更されたアタッチされたビューが通知され、それ自体を更新する必要があることが通知されます。 +

+ + + +

クリック イベントを処理する

+ +

{@link android.widget.AdapterView.OnItemClickListener} インターフェースを実装して {@link android.widget.AdapterView} の各アイテムでのクリック イベントに応答できます。 +次に例を示します。

+ +
+// Create a message handling object as an anonymous class.
+private OnItemClickListener mMessageClickedHandler = new OnItemClickListener() {
+    public void onItemClick(AdapterView parent, View v, int position, long id) {
+        // Do something in response to the click
+    }
+};
+
+listView.setOnItemClickListener(mMessageClickedHandler);
+
+ + + diff --git a/docs/html-intl/intl/ja/guide/topics/ui/dialogs.jd b/docs/html-intl/intl/ja/guide/topics/ui/dialogs.jd new file mode 100644 index 0000000000000000000000000000000000000000..358fc304f60ab861614ec8ea969f918a8a90ec66 --- /dev/null +++ b/docs/html-intl/intl/ja/guide/topics/ui/dialogs.jd @@ -0,0 +1,798 @@ +page.title=ダイアログ +page.tags=alertdialog,dialogfragment + +@jd:body + + + + + +

ダイアログは、ユーザーによる意思決定や追加情報の入力用に表示される小さなウィンドウです。 +ダイアログは全画面に表示されることはなく、通常はユーザーが処理を続ける前にアクションを起こす必要があるモーダル イベントに使用されます。 +

+ +
+

ダイアログ デザイン

+

ダイアログをデザインする方法について(言語に対する推奨を含む)は、ダイアログ デザインのガイドをお読みください。 +

+
+ + + +

{@link android.app.Dialog} クラスは、ダイアログの基本クラスですが、{@link android.app.Dialog} ディレクトリのインスタンスを作成することは避けてください。代わりに、次のいずれかのサブクラスを使用します。 + +

+
+
{@link android.app.AlertDialog}
+
タイトル、最大 3 つのボタン、選択可能なアイテムやカスタム レイアウトのリストを表示できるダイアログ。 +
+
{@link android.app.DatePickerDialog} または {@link android.app.TimePickerDialog}
+
ユーザーが日付または時刻を選択できるようにあらかじめ定義された UI を含むダイアログ。
+
+ + + +

これらのクラスでは、ダイアログのスタイルと構造が定義されますが、ダイアログのコンテナとして {@link android.support.v4.app.DialogFragment} を使用してください。{@link android.support.v4.app.DialogFragment} クラスでは、{@link android.app.Dialog} オブジェクトでメソッドを呼び出す代わりに、ダイアログの作成と表示の管理に必要なすべてのコントロールが提供されます。 + + + +

+ +

{@link android.support.v4.app.DialogFragment} を使ってダイアログを管理すると、ライフサイクル イベント([戻る] ボタンを押したときや画面を回転したときなど)が正しく処理されます。 + +{@link +android.support.v4.app.DialogFragment} クラスを使用すると、従来の {@link +android.support.v4.app.Fragment} のように、大きな UI で埋め込み可能なコンポーネントとしてダイアログの UI を再利用することもできます(ダイアログ UI を大小の画面で異なって表示させる場合など)。 + +

+ +

このガイドの次のセクションでは、{@link android.app.AlertDialog} オブジェクトと組み合わせて {@link +android.support.v4.app.DialogFragment} を使用する方法について説明します。 +日付や時刻ピッカーを作成する場合は、「Pickers」のガイドをご覧ください。 +

+ +

注: {@link android.app.DialogFragment} クラスは 元々 Android 3.0(API レベル 11)で追加されたため、このドキュメントではサポート ライブラリと一緒に提供される {@link +android.support.v4.app.DialogFragment} クラスの使用方法について説明します。 + +アプリにこのライブラリを追加すると、Android 1.6 以降を実行する端末で、{@link android.support.v4.app.DialogFragment} とその他のさまざまな API を使うことができます。 + +アプリの最小バージョンで API レベル 11 以降がサポートされている場合、{@link +android.app.DialogFragment} のフレームワーク バージョンを使用できますが、このドキュメントのリンクはサポート ライブラリ API 向けであることにご注意ください。 + +サポート ライブラリを使用するときは、android.app.DialogFragment ではなくandroid.support.v4.app.DialogFragment クラス をインポートしてください。 + +

+ + +

Dialog Fragment を作成する

+ +

{@link android.support.v4.app.DialogFragment} を拡張して {@link android.support.v4.app.DialogFragment#onCreateDialog +onCreateDialog()} コールバック メソッドで {@link android.app.AlertDialog} を作成することで、さまざまなダイアログ デザイン(カスタム レイアウトやダイアログのデザインガイドで説明されているものを含む)を実現できます。 + + + +

+ +

{@link android.support.v4.app.DialogFragment} で管理される基本的な {@link android.app.AlertDialog} を次に示します。 +

+ +
+public class FireMissilesDialogFragment extends DialogFragment {
+    @Override
+    public Dialog onCreateDialog(Bundle savedInstanceState) {
+        // Use the Builder class for convenient dialog construction
+        AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+        builder.setMessage(R.string.dialog_fire_missiles)
+               .setPositiveButton(R.string.fire, new DialogInterface.OnClickListener() {
+                   public void onClick(DialogInterface dialog, int id) {
+                       // FIRE ZE MISSILES!
+                   }
+               })
+               .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
+                   public void onClick(DialogInterface dialog, int id) {
+                       // User cancelled the dialog
+                   }
+               });
+        // Create the AlertDialog object and return it
+        return builder.create();
+    }
+}
+
+ +
+ +

図 1 +メッセージと 2 つのアクション ボタンを含むダイアログ

+
+ +

このクラスのインスタンスを作成してオブジェクトで {@link +android.support.v4.app.DialogFragment#show show()} を呼び出すと、図 1 のようなダイアログが表示されます。 +

+ +

次のセクションでは、{@link android.app.AlertDialog.Builder} API を使ったダイアログの作成について詳細を説明します。 +

+ +

ダイアログの複雑さに応じて、{@link android.support.v4.app.DialogFragment} ですべての基本的なフラグメントのライフサイクル メソッドを含む、他のさまざまなコールバック メソッドを実装できます。 + + + + + + + +

アラート ダイアログをビルドする

+ + +

{@link android.app.AlertDialog} クラスを使って、さまざまなダイアログ デザインをビルドできます。ほとんどの場合、必要なダイアログ クラスはこれだけです。図 2 のように、アラート ダイアログには 3 つの領域があります。 + +

+ +
+ +

図 2. ダイアログのレイアウト。

+
+ +
    +
  1. タイトル +

    この領域は省略可能で、コンテンツ エリアが詳細メッセージ、リスト、カスタム レイアウトで占有されている場合にのみ使う必要があります。 +単純なメッセージや質問(図 1 にあるダイアログなど)を記述する場合は、タイトルは必要ありません。 +

  2. +
  3. コンテンツ エリア +

    メッセージ、リスト、その他のカスタム レイアウトを表示できます。

  4. +
  5. アクション ボタン +

    1 つのダイアログ内に置くアクション ボタンは、3 つ以内にする必要があります。

  6. +
+ +

{@link android.app.AlertDialog.Builder} クラスでは、カスタム レイアウトなど、これらの種類のコンテンツを含む {@link android.app.AlertDialog} を作成できます。 + +

+ +

{@link android.app.AlertDialog} をビルドするには:

+ +
+// 1. Instantiate an {@link android.app.AlertDialog.Builder} with its constructor
+AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+
+// 2. Chain together various setter methods to set the dialog characteristics
+builder.setMessage(R.string.dialog_message)
+       .setTitle(R.string.dialog_title);
+
+// 3. Get the {@link android.app.AlertDialog} from {@link android.app.AlertDialog.Builder#create()}
+AlertDialog dialog = builder.create();
+
+ +

次のトピックでは、{@link android.app.AlertDialog.Builder} クラスを使ってさまざまなダイアログの属性を定義する方法を示します。 +

+ + + + +

ボタンを追加する

+ +

図 2 のようなアクション ボタンを追加するには、{@link android.app.AlertDialog.Builder#setPositiveButton setPositiveButton()} と {@link android.app.AlertDialog.Builder#setNegativeButton setNegativeButton()} メソッドを呼び出します。 + +

+ +
+AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+// Add the buttons
+builder.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
+           public void onClick(DialogInterface dialog, int id) {
+               // User clicked OK button
+           }
+       });
+builder.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
+           public void onClick(DialogInterface dialog, int id) {
+               // User cancelled the dialog
+           }
+       });
+// Set other dialog properties
+...
+
+// Create the AlertDialog
+AlertDialog dialog = builder.create();
+
+ +

set...Button() メソッドには、ボタンのタイトル(文字列リソースで指定)と、ユーザーがボタンを押したときに実行するアクションを定義する {@link android.content.DialogInterface.OnClickListener} が必要です。 + + +

+ +

追加できるアクション ボタンは、次の 3 つです。

+
+
Positive
+
アクションを受け入れて続ける場合に使います(「OK」アクション)。
+
Negative
+
アクションをキャンセルする場合に使います。
+
Neutral
+
ユーザーがアクションを続けたくない可能性があり、キャンセルしたいとは限らない場合に使います。 +ポジティブ ボタンとネガティブ ボタンの間に表示されます。 +たとえば、「後で通知する」のようなアクションの場合です。
+
+ +

各ボタンタイプのいずれか 1 つのみを {@link +android.app.AlertDialog} に追加できます。つまり、2 つ以上の「ポジティブ」ボタンを置くことはできません。

+ + + +
+ +

図 3 +タイトルとリストを含むダイアログ

+
+ +

リストを追加する

+ +

{@link android.app.AlertDialog} API で使用できるリストは次の 3 種類です。

+ + +

図 3 のように、排他的選択リストを作成するには、{@link android.app.AlertDialog.Builder#setItems setItems()} メソッドを使います。 +

+ +
+@Override
+public Dialog onCreateDialog(Bundle savedInstanceState) {
+    AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+    builder.setTitle(R.string.pick_color)
+           .setItems(R.array.colors_array, new DialogInterface.OnClickListener() {
+               public void onClick(DialogInterface dialog, int which) {
+               // The 'which' argument contains the index position
+               // of the selected item
+           }
+    });
+    return builder.create();
+}
+
+ +

リストは、ダイアログのコンテンツ エリアに表示されるため、ダイアログにはメッセージとリストの両方は表示できません。{@link android.app.AlertDialog.Builder#setTitle setTitle()} でダイアログのタイトルを設定してください。 + +リストのアイテムを指定するには、{@link +android.app.AlertDialog.Builder#setItems setItems()} を呼び出して配列を渡します。{@link +android.app.AlertDialog.Builder#setAdapter setAdapter()} を使ってリストを指定することもできます。 + +こうすることで、{@link android.widget.ListAdapter} を使って、データベースからなど、ダイナミック データを含むリストを返すことができます。 +

+ +

{@link android.widget.ListAdapter} を使ってリストを返すことを選択する場合は、必ず {@link android.support.v4.content.Loader} を使ってコンテンツが非同期で読み込まれるようにします。 + +この詳細については、「Building Layouts with an Adapter」と「ローダ」のガイドをご覧ください。 + + +

+ +

注: デフォルトでは、次の固定選択リストのいずれかを使っていない場合、リストアイテムをタップするとダイアログが閉じられます。 +

+ +
+ +

図 4.複数選択アイテムのリスト。 +

+
+ + +

固定の複数選択または排他的選択リストを追加する

+ +

複数選択アイテム(チェックボックス)または排他的選択アイテム(ラジオボタン)のリストを追加するには、{@link android.app.AlertDialog.Builder#setMultiChoiceItems(Cursor,String,String, +DialogInterface.OnMultiChoiceClickListener) setMultiChoiceItems()} または {@link android.app.AlertDialog.Builder#setSingleChoiceItems(int,int,DialogInterface.OnClickListener) +setSingleChoiceItems()} メソッドをそれぞれ使用します。 + + +

+ +

{@link java.util.ArrayList} で選択されたアイテムを保存する、図 4 にあるような複数選択リストを作成する方法を次に示します。 + +

+ +
+@Override
+public Dialog onCreateDialog(Bundle savedInstanceState) {
+    mSelectedItems = new ArrayList();  // Where we track the selected items
+    AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+    // Set the dialog title
+    builder.setTitle(R.string.pick_toppings)
+    // Specify the list array, the items to be selected by default (null for none),
+    // and the listener through which to receive callbacks when items are selected
+           .setMultiChoiceItems(R.array.toppings, null,
+                      new DialogInterface.OnMultiChoiceClickListener() {
+               @Override
+               public void onClick(DialogInterface dialog, int which,
+                       boolean isChecked) {
+                   if (isChecked) {
+                       // If the user checked the item, add it to the selected items
+                       mSelectedItems.add(which);
+                   } else if (mSelectedItems.contains(which)) {
+                       // Else, if the item is already in the array, remove it 
+                       mSelectedItems.remove(Integer.valueOf(which));
+                   }
+               }
+           })
+    // Set the action buttons
+           .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
+               @Override
+               public void onClick(DialogInterface dialog, int id) {
+                   // User clicked OK, so save the mSelectedItems results somewhere
+                   // or return them to the component that opened the dialog
+                   ...
+               }
+           })
+           .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
+               @Override
+               public void onClick(DialogInterface dialog, int id) {
+                   ...
+               }
+           });
+
+    return builder.create();
+}
+
+ +

従来のリストとラジオボタンを含むリストでは、「排他的選択」アクションが提供されますが、ユーザーの選択を固定させたい場合は、{@link +android.app.AlertDialog.Builder#setSingleChoiceItems(int,int,DialogInterface.OnClickListener) +setSingleChoiceItems()} を使用してください。つまり、ダイアログを後でもう一度開く場合は、ユーザーの現在の選択を表示し、ラジオボタンを含むリストを作成します。 + + +

+ + + + + +

カスタム レイアウトを作成する

+ +
+ +

図 5. カスタム ダイアログのレイアウト。

+
+ +

ダイアログでカスタム レイアウトが必要な場合は、レイアウトを作成し、{@link +android.app.AlertDialog.Builder} オブジェクトで {@link +android.app.AlertDialog.Builder#setView setView()} を呼び出して {@link android.app.AlertDialog} にそのレイアウトを追加します。 +

+ +

デフォルトでは、カスタム レイアウトは、ダイアログ ウィンドウ全体に表示されますが、{@link android.app.AlertDialog.Builder} メソッドを使ってボタンとタイトルを追加できます。 +

+ +

以下は、図 5 にあるダイアログのレイアウト ファイルです。

+ +

res/layout/dialog_signin.xml

+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content">
+    <ImageView
+        android:src="@drawable/header_logo"
+        android:layout_width="match_parent"
+        android:layout_height="64dp"
+        android:scaleType="center"
+        android:background="#FFFFBB33"
+        android:contentDescription="@string/app_name" />
+    <EditText
+        android:id="@+id/username"
+        android:inputType="textEmailAddress"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="16dp"
+        android:layout_marginLeft="4dp"
+        android:layout_marginRight="4dp"
+        android:layout_marginBottom="4dp"
+        android:hint="@string/username" />
+    <EditText
+        android:id="@+id/password"
+        android:inputType="textPassword"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="4dp"
+        android:layout_marginLeft="4dp"
+        android:layout_marginRight="4dp"
+        android:layout_marginBottom="16dp"
+        android:fontFamily="sans-serif"
+        android:hint="@string/password"/>
+</LinearLayout>
+
+ +

ヒント: デフォルトでは、{@code "textPassword"} 入力タイプを使うために、{@link android.widget.EditText} 要素を設定すると、フォント ファミリーが monospace に設定されるため、フォント ファミリーを {@code "sans-serif"} に変えて、両方のテキスト フィールドで同じフォント スタイルが使用されるようにしてください。 + + +

+ +

{@link android.support.v4.app.DialogFragment} でレイアウトをインフレートするには、{@link android.app.Activity#getLayoutInflater()} で {@link android.view.LayoutInflater} を取得して {@link android.view.LayoutInflater#inflate inflate()} を呼び出します。最初のパラメータは、レイアウト リソース ID で、2 番目のパラメータはレイアウトの親ビューです。その後、{@link android.app.AlertDialog#setView setView()} を呼び出してダイアログのレイアウトを配置できます。 + + + + + +

+ +
+@Override
+public Dialog onCreateDialog(Bundle savedInstanceState) {
+    AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+    // Get the layout inflater
+    LayoutInflater inflater = getActivity().getLayoutInflater();
+
+    // Inflate and set the layout for the dialog
+    // Pass null as the parent view because its going in the dialog layout
+    builder.setView(inflater.inflate(R.layout.dialog_signin, null))
+    // Add action buttons
+           .setPositiveButton(R.string.signin, new DialogInterface.OnClickListener() {
+               @Override
+               public void onClick(DialogInterface dialog, int id) {
+                   // sign in the user ...
+               }
+           })
+           .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
+               public void onClick(DialogInterface dialog, int id) {
+                   LoginDialogFragment.this.getDialog().cancel();
+               }
+           });      
+    return builder.create();
+}
+
+ +
+

ヒント: カスタム ダイアログが必要な場合は、{@link android.app.Dialog} API を使う代わりに、{@link android.app.Activity} をダイアログとして表示できます。 + +アクティビティを作り、{@code +<activity>} マニフェスト要素でそのテーマを {@link android.R.style#Theme_Holo_Dialog Theme.Holo.Dialog} に設定します。 + +

+ +
+<activity android:theme="@android:style/Theme.Holo.Dialog" >
+
+

これだけです。これで、アクティビティは全画面でなく、ダイアログ ウィンドウに表示されるようになります。

+
+ + + +

ダイアログのホストにイベントを渡す

+ +

ユーザーがダイアログのアクション ボタンのいずれかをタップするか、そのリストからアイテムを選択すると、{@link android.support.v4.app.DialogFragment} によって必要なアクションが実行される場合がありますが、ダイアログを開くアクティビティやフラグメントにイベントを配信したい場合もよくあります。 + + +これを行うには、クリック イベントの各タイプのメソッドでインターフェースを定義します。次に、ダイアログからアクション イベントを受け取るホスト コンポーネントでインターフェースを実装します。 + +

+ +

ホスト アクティビティにイベントを配信するインターフェースを定義する {@link android.support.v4.app.DialogFragment} を次に示します。 +

+ +
+public class NoticeDialogFragment extends DialogFragment {
+    
+    /* The activity that creates an instance of this dialog fragment must
+     * implement this interface in order to receive event callbacks.
+     * Each method passes the DialogFragment in case the host needs to query it. */
+    public interface NoticeDialogListener {
+        public void onDialogPositiveClick(DialogFragment dialog);
+        public void onDialogNegativeClick(DialogFragment dialog);
+    }
+    
+    // Use this instance of the interface to deliver action events
+    NoticeDialogListener mListener;
+    
+    // Override the Fragment.onAttach() method to instantiate the NoticeDialogListener
+    @Override
+    public void onAttach(Activity activity) {
+        super.onAttach(activity);
+        // Verify that the host activity implements the callback interface
+        try {
+            // Instantiate the NoticeDialogListener so we can send events to the host
+            mListener = (NoticeDialogListener) activity;
+        } catch (ClassCastException e) {
+            // The activity doesn't implement the interface, throw exception
+            throw new ClassCastException(activity.toString()
+                    + " must implement NoticeDialogListener");
+        }
+    }
+    ...
+}
+
+ +

ダイアログをホスティングするアクティビティによって、ダイアログ フラグメントのコンストラクタを使ってダイアログのインスタンスが作成され、{@code NoticeDialogListener} インターフェースの実装によってダイアログのイベントが受信されます。 + +

+ +
+public class MainActivity extends FragmentActivity
+                          implements NoticeDialogFragment.NoticeDialogListener{
+    ...
+    
+    public void showNoticeDialog() {
+        // Create an instance of the dialog fragment and show it
+        DialogFragment dialog = new NoticeDialogFragment();
+        dialog.show(getSupportFragmentManager(), "NoticeDialogFragment");
+    }
+
+    // The dialog fragment receives a reference to this Activity through the
+    // Fragment.onAttach() callback, which it uses to call the following methods
+    // defined by the NoticeDialogFragment.NoticeDialogListener interface
+    @Override
+    public void onDialogPositiveClick(DialogFragment dialog) {
+        // User touched the dialog's positive button
+        ...
+    }
+
+    @Override
+    public void onDialogNegativeClick(DialogFragment dialog) {
+        // User touched the dialog's negative button
+        ...
+    }
+}
+
+ +

ホスト アクティビティによって、上記の {@link android.support.v4.app.Fragment#onAttach onAttach()} コールバック メソッドで適用される {@code NoticeDialogListener} が実装されるため、ダイアログ フラグメントではインターフェース コールバック メソッドを使ってアクティビティにクリック イベントを配信できます。 + + +

+ +
+public class NoticeDialogFragment extends DialogFragment {
+    ...
+
+    @Override
+    public Dialog onCreateDialog(Bundle savedInstanceState) {
+        // Build the dialog and set up the button click handlers
+        AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+        builder.setMessage(R.string.dialog_fire_missiles)
+               .setPositiveButton(R.string.fire, new DialogInterface.OnClickListener() {
+                   public void onClick(DialogInterface dialog, int id) {
+                       // Send the positive button event back to the host activity
+                       mListener.onDialogPositiveClick(NoticeDialogFragment.this);
+                   }
+               })
+               .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
+                   public void onClick(DialogInterface dialog, int id) {
+                       // Send the negative button event back to the host activity
+                       mListener.onDialogNegativeClick(NoticeDialogFragment.this);
+                   }
+               });
+        return builder.create();
+    }
+}
+
+ + + +

ダイアログを表示する

+ +

ダイアログを表示する場合、{@link +android.support.v4.app.DialogFragment} のインスタンスを作成して {@link android.support.v4.app.DialogFragment#show +show()} を呼び出し、{@link android.support.v4.app.FragmentManager} とダイアログ フラグメントのタグ名を渡します。 +

+ +

{@link android.support.v4.app.FragmentActivity} から {@link android.support.v4.app.FragmentActivity#getSupportFragmentManager()} を、または {@link +android.support.v4.app.Fragment} から {@link +android.support.v4.app.Fragment#getFragmentManager()} を呼び出して {@link android.support.v4.app.FragmentManager} を取得できます。 + +次に例を示します。

+ +
+public void confirmFireMissiles() {
+    DialogFragment newFragment = new FireMissilesDialogFragment();
+    newFragment.show(getSupportFragmentManager(), "missiles");
+}
+
+ +

2 番目の引数 {@code "missiles"} は、固有のタグ名で、システムはこれを使って必要な時にフラグメントの状態を保存して復元します。 +そのタグを使って、{@link android.support.v4.app.FragmentManager#findFragmentByTag +findFragmentByTag()} を呼び出してフラグメントを操作することもできます。 +

+ + + + +

全画面でまたは埋め込まれたフラグメントとしてダイアログを表示する

+ +

場合によっては、UI の一部をダイアログとして表示させ、それ以外の場合には、たとえば端末の画面の大小に応じて、全画面や埋め込まれたフラグメントとして表示させるよう UI を設計できます。 + +{@link android.support.v4.app.DialogFragment} クラスは、埋め込み可能な {@link +android.support.v4.app.Fragment} として動作できるため、この柔軟性を実現できます。 +

+ +

ただし、この場合は、{@link android.app.AlertDialog.Builder AlertDialog.Builder} やその他の {@link android.app.Dialog} オブジェクトを使ってダイアログをビルドできません。 +{@link android.support.v4.app.DialogFragment} を埋め込み可能にする場合、レイアウトでダイアログの UI を定義し、{@link android.support.v4.app.DialogFragment#onCreateView +onCreateView()} コールバックでレイアウトを読み込んでください。 + + +

+ +

ダイアログまたは埋め込み可能なフラグメントのいずれかとして(purchase_items.xml という名前のレイアウトを使って)表示できる {@link android.support.v4.app.DialogFragment} の例を次に示します。 +

+ +
+public class CustomDialogFragment extends DialogFragment {
+    /** The system calls this to get the DialogFragment's layout, regardless
+        of whether it's being displayed as a dialog or an embedded fragment. */
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+            Bundle savedInstanceState) {
+        // Inflate the layout to use as dialog or embedded fragment
+        return inflater.inflate(R.layout.purchase_items, container, false);
+    }
+  
+    /** The system calls this only when creating the layout in a dialog. */
+    @Override
+    public Dialog onCreateDialog(Bundle savedInstanceState) {
+        // The only reason you might override this method when using onCreateView() is
+        // to modify any dialog characteristics. For example, the dialog includes a
+        // title by default, but your custom layout might not need it. So here you can
+        // remove the dialog title, but you must call the superclass to get the Dialog.
+        Dialog dialog = super.onCreateDialog(savedInstanceState);
+        dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
+        return dialog;
+    }
+}
+
+ +

画面サイズに基づいて、フラグメントをダイアログとしてまたは全画面の UI として表示するかどうかを決めるコードの一例も示します。 +

+ +
+public void showDialog() {
+    FragmentManager fragmentManager = getSupportFragmentManager();
+    CustomDialogFragment newFragment = new CustomDialogFragment();
+    
+    if (mIsLargeLayout) {
+        // The device is using a large layout, so show the fragment as a dialog
+        newFragment.show(fragmentManager, "dialog");
+    } else {
+        // The device is smaller, so show the fragment fullscreen
+        FragmentTransaction transaction = fragmentManager.beginTransaction();
+        // For a little polish, specify a transition animation
+        transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
+        // To make it fullscreen, use the 'content' root view as the container
+        // for the fragment, which is always the root view for the activity
+        transaction.add(android.R.id.content, newFragment)
+                   .addToBackStack(null).commit();
+    }
+}
+
+ +

フラグメントのトランザクション実行の詳細については、「フラグメント」のガイドをご覧ください。 +

+ +

この例では、mIsLargeLayout ブール値によって、現在の端末でアプリの大きなレイアウト デザインを使う(その結果、全画面でなく、このフラグメントをダイアログとして表示する)かどうかが指定されます。 + +この種のブール値を設定する最良の方法は、異なる画面サイズに対して別のリソース値ブールリソース値を宣言することです。 + +次に、異なる画面サイズのブールリソースを 2 種類示します。 +

+ +

res/values/bools.xml

+
+<!-- Default boolean values -->
+<resources>
+    <bool name="large_layout">false</bool>
+</resources>
+
+ +

res/values-large/bools.xml

+
+<!-- Large screen boolean values -->
+<resources>
+    <bool name="large_layout">true</bool>
+</resources>
+
+ +

アクティビティの {@link android.app.Activity#onCreate onCreate()} メソッド中に、{@code mIsLargeLayout} 値を初期化できます。 +

+ +
+boolean mIsLargeLayout;
+
+@Override
+public void onCreate(Bundle savedInstanceState) {
+    super.onCreate(savedInstanceState);
+    setContentView(R.layout.activity_main);
+
+    mIsLargeLayout = getResources().getBoolean(R.bool.large_layout);
+}
+
+ + + +

大画面でアクティビティをダイアログとして表示する

+ +

小画面のときにダイアログを全画面の UI として表示するのではなく、大画面のときに {@link android.app.Activity} をダイアログとして表示することで、同じ結果を得ることができます。 + +どちらの方法を選択するかはアプリのデザインによって異なりますが、アプリが小画面で設計されていて、存在期間が短いアクティビティをダイアログとして示すことでタブレットでの使用感を改善するときには、ほとんどの場合、アクティビティをダイアログとして表示する方法が役立ちます。 + + +

+ +

大画面のときにのみ、アクティビティをダイアログとして表示するには、{@link android.R.style#Theme_Holo_DialogWhenLarge Theme.Holo.DialogWhenLarge} テーマを {@code +<activity>} マニフェスト要素に適用します。 + +

+ +
+<activity android:theme="@android:style/Theme.Holo.DialogWhenLarge" >
+
+ +

テーマを使ったアクティビティのスタイル指定についての詳細は、「Styles and Themes」をご覧ください。

+ + + +

ダイアログを閉じる

+ +

{@link android.app.AlertDialog.Builder} で作成したアクション ボタンのいずれかがタップされると、システムはダイアログを閉じます。 +

+ +

また、リストでラジオボタンやチェックボックスが使われている場合を除き、ダイアログ リストでアイテムがタップされると、ダイアログが閉じます。 +それ以外の場合は、{@link +android.support.v4.app.DialogFragment} で {@link android.support.v4.app.DialogFragment#dismiss()} を呼び出してダイアログを手動で閉じることができます。 +

+ +

ダイアログが閉じるときに、特定のアクションを実行する必要がある場合は、{@link +android.support.v4.app.DialogFragment} で {@link +android.support.v4.app.DialogFragment#onDismiss onDismiss()} メソッドを実装できます。 +

+ +

ダイアログをキャンセルすることもできます。これは、ユーザーがタスクを完了せずに、明示的にダイアログを離れたことを示す特別なイベントです。 +ユーザーが [戻る] ボタンを押す、ダイアログ領域外の画面をタップする、または開発者が {@link +android.app.Dialog} で明示的に {@link android.app.Dialog#cancel()} を呼び出す(ダイアログの [キャンセル] ボタンに応じてなど)場合に実行されます。 + +

+ +

上記の例のように、{@link +android.support.v4.app.DialogFragment} クラスで {@link android.support.v4.app.DialogFragment#onCancel onCancel()} を実装してキャンセル イベントに応答できます。 +

+ +

注: システムによって、{@link android.support.v4.app.DialogFragment#onCancel onCancel()} コールバックを呼び出す各イベントで {@link android.support.v4.app.DialogFragment#onDismiss onDismiss()} が呼び出されます。 + +ただし、{@link android.app.Dialog#dismiss Dialog.dismiss()} や {@link +android.support.v4.app.DialogFragment#dismiss DialogFragment.dismiss()} を呼び出す場合、システムによって {@link android.support.v4.app.DialogFragment#onDismiss onDismiss()} が呼び出されますが、{@link android.support.v4.app.DialogFragment#onCancel onCancel()} は呼び出されません。 + + +通常は、ユーザーがダイアログのポジティブボタンを押すときに、{@link android.support.v4.app.DialogFragment#dismiss dismiss()} を呼び出して、ビューからダイアログが削除されるようにしてください。 + +

+ + diff --git a/docs/html-intl/intl/ja/guide/topics/ui/menus.jd b/docs/html-intl/intl/ja/guide/topics/ui/menus.jd new file mode 100644 index 0000000000000000000000000000000000000000..7d8090e7a0ad616101036c53a2f87b5548df786f --- /dev/null +++ b/docs/html-intl/intl/ja/guide/topics/ui/menus.jd @@ -0,0 +1,1031 @@ +page.title=メニュー +parent.title=ユーザー インターフェース +parent.link=index.html +@jd:body + + + +

メニューは、さまざまなタイプのアプリケーションで共通するユーザー インターフェースです。使い慣れた一貫した操作感を提供するには、{@link android.view.Menu} API を使ってアクティビティでユーザーのアクションとその他のオプションを表示する必要があります。 + +

+ +

Android 3.0(API レベル 11)からは、Android 搭載端末では専用の Menu ボタンを表示する必要はなくなりました。 +この変更により、Android アプリでは従来の 6 アイテムのメニューパネルの依存関係から離れて、共通のユーザー アクションを表示するアクションバーを提供する必要があります。 + +

+ +

一部のメニュー アイテムのデザインと操作感は変わりましたが、一連のアクションとオプションを定義する意味論は、引き続き {@link android.view.Menu} API に基づきます。 +このガイドでは、すべてのバージョンの Android で表示されるメニューやアクションの基本的な 3 タイプを作成する方法について説明します。 + +

+ +
+
オプション メニューとアクションバー
+
オプション メニューは、アクティビティの主なメニュー アイテムのコレクションです。 +ここには、「検索」、「メールの作成」、「設定」のような、アプリにグローバルな影響があるアクションを配置する必要があります。 + +

Android 2.3 以前のバージョン向けに開発している場合は、ユーザーは Menu ボタンを押してオプション メニューパネルを表示できます。 +

+

Android 3.0 以降の場合、オプション メニューのアイテムは、画面上のアクション アイテムとオーバーフロー オプションの組み合わせでアクションバーに表示されます。 +Android 3.0 からは、Menu ボタンが廃止されたため(一部の端末にはこのボタンがありません)、アクションバーを使ってアクションや他のオプションにアクセスできるよう移行する必要があります。 + + +

+

オプション メニューの作成のセクションをご覧ください。

+
+ +
コンテキスト メニューとコンテキスト アクション モード
+ +
コンテキスト メニューは、ユーザーが要素を長押しクリックするときに表示されるフローティング メニューです。 +ここでは、選択したコンテンツやコンテキスト フレームに影響するアクションが提供されます。 + +

Android 3.0 以降向けに開発している場合は、コンテキスト アクション モードを使って選択されたコンテンツでのアクションを有効にする必要があります。このモードでは、画面最上部にあるバーで選択されたコンテンツに影響するアクション アイテムが表示され、ユーザーは複数のアイテムを選択できます。 + +

+

コンテキスト メニューの作成のセクションをご覧ください。

+
+ +
ポップアップ メニュー
+
ポップアップ メニューでは、メニューを呼び出すビューに固定された縦方向のリストでアイテムが表示されます。 +特定のコンテンツに関連するアクションの概要を表示したり、コマンドの 2 番目の部分のオプションを表示したりする場合に適しています。 +ポップアップ メニューのアクションは対応するコンテンツに直接影響を与えないようにしてください(そのためにコンテキスト アクションがあります)。 + +ポップアップ メニューは、アクティビティのコンテンツ領域に関連する拡張されたアクション用です。 + +

ポップアップ メニューの作成のセクションをご覧ください。

+
+
+ + + +

XML でのメニューの定義

+ +

Android では、すべてのメニュータイプに、メニュー アイテムを定義するための標準の XML 形式が提供されます。 +アクティビティのコードでメニューをビルドするのではなく、メニューとそのすべてのアイテムを XML メニュー リソースで定義してください。 +その際、アクティビティやフラグメントでメニュー リソースをインフレートできます({@link android.view.Menu} オブジェクトとして読み込む)。 + +

+ +

メニュー リソースを使うことは、次のような理由で優れた方法と言えます。

+ + +

メニューを定義するには、プロジェクトの res/menu/ ディレクトリ内で XML ファイルを作成し、次の要素を含むメニューをビルドします。 +

+
+
<menu>
+
メニュー アイテムのコンテナである {@link android.view.Menu} を定義します。<menu> 要素は、ファイルのルートノードである必要があります。また、1 つ以上の <item><group> 要素を持つことができます。 + +
+ +
<item>
+
メニューで 1 つのアイテムを表示する {@link android.view.MenuItem} を作成します。この要素には、サブメニューを作成するために、ネストされた <menu> 要素を含めることができます。 +
+ +
<group>
+
省略可能な {@code <item>} 要素の非表示コンテナ。メニュー アイテムでアクティブ状態や可視性のようなプロパティを共有できるよう、メニュー アイテムを分類できます。 +詳細については、メニュー グループの作成のセクションをご覧ください。 +
+
+ + +

game_menu.xml という名前のメニュー例を次に示します。

+
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:id="@+id/new_game"
+          android:icon="@drawable/ic_new_game"
+          android:title="@string/new_game"
+          android:showAsAction="ifRoom"/>
+    <item android:id="@+id/help"
+          android:icon="@drawable/ic_help"
+          android:title="@string/help" />
+</menu>
+
+ +

<item> 要素では、アイテムの概観と動作を定義するために使用できるいくつかの属性がサポートされています。 +上記メニューのアイテムには、次の属性が含まれます。

+ +
+
{@code android:id}
+
ユーザーがアイテムを選択するときに、アプリケーションがそのアイテムを認識できるようにする、アイテム固有のリソース ID。 +
+
{@code android:icon}
+
アイテムのアイコンとして使用するための、ドローアブルへの参照。
+
{@code android:title}
+
アイテムのタイトルとして使用するための、文字列への参照。
+
{@code android:showAsAction}
+
このアイテムがアクションバーのアクション アイテムとして、いつ、どのように表示される必要があるかを指定。
+
+ +

これらの属性は使用する必要のある最も重要なものですが、他にもさまざまな属性があります。サポートされているすべての属性については、「Menu Resource」のドキュメントをご覧ください。 +

+ +

サブメニューを除くすべてのメニューで、{@code <item>} の子として {@code <menu>} 要素を追加すると、アイテムにサブメニューを追加できます。 +PC アプリケーションのメニューバー(ファイル、編集、表示など)にあるアイテムのように、アプリケーションにトピックで分類できる多くの機能がある場合は、サブメニューが役立ちます。 + +次に例を示します。

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:id="@+id/file"
+          android:title="@string/file" >
+        <!-- "file" submenu -->
+        <menu>
+            <item android:id="@+id/create_new"
+                  android:title="@string/create_new" />
+            <item android:id="@+id/open"
+                  android:title="@string/open" />
+        </menu>
+    </item>
+</menu>
+
+ +

アクティビティでメニューを使うには、{@link android.view.MenuInflater#inflate(int,Menu) +MenuInflater.inflate()} を使って、メニュー リソースをインフレートする(XML リソースをプログラム可能なオブジェクトに変換する)必要があります。 +次のセクションでは、各メニュー アイテムのメニューをインフレートする方法を説明します。 +

+ + + +

オプション メニューの作成

+ +
+ +

図 1. Android 2.3 のブラウザでのオプション メニュー。 +

+
+ +

オプション メニューでは、「検索」、「メールの作成」、「設定」のような、現在のアクティビティ コンテキストに関連するアクションとその他のオプションを含める必要があります。 +

+ +

オプション メニューのアイテムが画面上のどこに表示されるかは、開発対象のアプリケーションのバージョンによって異なります。 +

+ + + + +

図 2. ナビゲーション タブ、カメラ アクション アイテム、アクション オーバーフロー ボタンが表示されている Honeycomb Gallery アプリのアクションバー。 +

+ +

{@link android.app.Activity} サブクラスや {@link android.app.Fragment} サブクラスのいずれかからオプション メニューのアイテムを宣言できます。 +アクティビティとフラグメントの両方でオプション メニューのアイテムを宣言する場合、それらは UI に統合されます。まず、アクティビティのアイテムが表示され、次にアクティビティに各フラグメントが追加される順序で各フラグメントのアイテムが表示されます。 + + +必要に応じて、移動する必要のある各 {@code <item>} で {@code android:orderInCategory} 属性を使ってメニュー アイテムの順序を並べ替えることができます。 +

+ +

アクティビティのオプション メニューを指定するには、{@link +android.app.Activity#onCreateOptionsMenu(Menu) onCreateOptionsMenu()} をオーバーライドします(フラグメントは独自の {@link android.app.Fragment#onCreateOptionsMenu onCreateOptionsMenu()} コールバックを提供)。 +このメソッドでは、メニュー リソース(XML で定義)をコールバックで提供される {@link +android.view.Menu} にインフレートできます。 +次に例を示します。

+ +
+@Override
+public boolean onCreateOptionsMenu(Menu menu) {
+    MenuInflater inflater = {@link android.app.Activity#getMenuInflater()};
+    inflater.inflate(R.menu.game_menu, menu);
+    return true;
+}
+
+ +

{@link android.view.Menu#add(int,int,int,int) +add()} を使ってメニュー アイテムを追加し、{@link android.view.MenuItem} API でそのプロパティを修正するために、{@link android.view.Menu#findItem findItem()} でアイテムを取得することもできます。 +

+ +

Android 2.3.x 以前向けにアプリケーションを開発した場合、システムは {@link +android.app.Activity#onCreateOptionsMenu(Menu) onCreateOptionsMenu()} を呼び出し、ユーザーが初めてそのメニューを開いたときにオプション メニューが作成されます。 +Android 3.0 以降向けに開発した場合、アクティビティの開始時にシステムが {@link android.app.Activity#onCreateOptionsMenu(Menu) onCreateOptionsMenu()} を呼び出し、アクションバーにアイテムが表示されるようにします。 + +

+ + + +

クリック イベントを処理する

+ +

ユーザーがオプション メニューからアイテムを選択すると(アクションバーのアクション アイテムを含む)、アクティビティの {@link android.app.Activity#onOptionsItemSelected(MenuItem) +onOptionsItemSelected()} メソッドが呼び出されます。 +このメソッドでは、選択された {@link android.view.MenuItem} が渡されます。{@link android.view.MenuItem#getItemId()} を呼び出してアイテムを識別できます。これにより、メニュー アイテムに対して一意の ID が返されます(メニュー リソースで {@code android:id} 属性を定義するか、{@link android.view.Menu#add(int,int,int,int) add()} メソッドに指定された整数で)。 + + +この ID を既知のメニュー アイテムと突き合わせて適切なアクションを実行できます。 +次に例を示します。

+ +
+@Override
+public boolean onOptionsItemSelected(MenuItem item) {
+    // Handle item selection
+    switch (item.getItemId()) {
+        case R.id.new_game:
+            newGame();
+            return true;
+        case R.id.help:
+            showHelp();
+            return true;
+        default:
+            return super.onOptionsItemSelected(item);
+    }
+}
+
+ +

メニュー アイテムを正常に処理する場合、{@code true} を返します。メニュー アイテムを処理しない場合は、{@link +android.app.Activity#onOptionsItemSelected(MenuItem) onOptionsItemSelected()} のスーパークラスの実装を呼び出す必要があります(デフォルトの実装では fause が返されます)。 + +

+ +

アクティビティにフラグメントが含まれる場合は、システムはまずアクティビティに対して {@link +android.app.Activity#onOptionsItemSelected(MenuItem) onOptionsItemSelected()} を呼び出し、次に {@code true} が返されるまで、またはすべてのフラグメントが呼び出されるまで、各フラグメントが追加された順序で、各フラグメントに対して呼び出します。 + +

+ +

ヒント: Android 3.0 では、{@code android:onClick} 属性を使って、メニュー アイテムのクリックでの動作を XML で定義できます。 +その属性値は、メニューを使ってアクティビティによって定義されるメソッド名である必要があります。 +そのメソッドは、パブリックであり、1 つの {@link android.view.MenuItem} パラメータを使用できる必要があります。システムがこのメソッドを呼び出すと、選択したメニュー アイテムが渡されます。 + +詳細と例については、「Menu Resource」のドキュメントをご覧ください。

+ +

ヒント: アプリケーションに複数のアクティビティが含まれていて、その一部で同じオプション メニューが提供されている場合、{@link android.app.Activity#onCreateOptionsMenu(Menu) +onCreateOptionsMenu()} と {@link android.app.Activity#onOptionsItemSelected(MenuItem) +onOptionsItemSelected()} メソッドのみを実装するアクティビティを作成することを検討します。 + +その後、同じオプション メニューを共有する必要のある各アクティビティのこのクラスを拡張します。 +この方法で、メニューの動作を継承するメニュー アクションとそれぞれの子クラスを処理するためのコードを 1 セットで管理できます。子孫アクティビティの 1 つにメニュー アイテムを追加する場合は、そのアクティビティの {@link android.app.Activity#onCreateOptionsMenu(Menu) +onCreateOptionsMenu()} をオーバーライドします。 + + +元のメニュー アイテムが作成されるように、{@code super.onCreateOptionsMenu(menu)} を呼び出し、{@link +android.view.Menu#add(int,int,int,int) menu.add()} で新しいメニュー アイテムを追加します。 +各メニュー アイテムのスーパークラスの行動をオーバーライドすることもできます。 +

+ + +

実行時におけるメニュー アイテムの変更

+ +

システムが {@link android.app.Activity#onCreateOptionsMenu(Menu) +onCreateOptionsMenu()} を呼び出した後、入力する {@link android.view.Menu} のインスタンスは残り、何らかの理由でメニューが無効にならない限り、{@link android.app.Activity#onCreateOptionsMenu(Menu) onCreateOptionsMenu()} をもう一度呼び出すことはありません。 + +ただし、初期のメニュー状態を作成し、アクティビティのライフサイクル中に変更しないという目的の場合に限って、{@link +android.app.Activity#onCreateOptionsMenu(Menu) onCreateOptionsMenu()} を使用する必要があります。 +

+ +

アクティビティのライフサイクル中に発生するイベントに基づいてオプション メニューを変更する場合は、{@link android.app.Activity#onPrepareOptionsMenu(Menu) onPrepareOptionsMenu()} メソッドでそれを実行できます。 + +このメソッドは、現在存在している {@link android.view.Menu} オブジェクトを渡し、それを編集(アイテムの追加、削除、無効化など)できるようにします。 + +(フラグメントでも {@link +android.app.Fragment#onPrepareOptionsMenu onPrepareOptionsMenu()} コールバックが提供されます)。

+ +

Android 2.3.x 以前では、ユーザーが Menu ボタンを押してオプション メニューを開くたびに、システムによって {@link +android.app.Activity#onPrepareOptionsMenu(Menu) +onPrepareOptionsMenu()} が呼び出されます。 +

+ +

Android 3.0 以降では、メニュー アイテムがアクションバーに表示されるときに、オプション メニューが常に開かれるとみなされます。 +イベントが発生し、メニューをアップデートするときは、{@link android.app.Activity#invalidateOptionsMenu invalidateOptionsMenu()} を呼び出して、システムが {@link android.app.Activity#onPrepareOptionsMenu(Menu) onPrepareOptionsMenu()} を呼び出すようリクエストする必要があります。 + +

+ +

注: 現在フォーカスされている {@link android.view.View} に基づいてオプション メニューでアイテムを変更しないでください。 + +タッチモードの場合(ユーザーがトラックボールやリモコンの矢印ボタンを使うとき)、ビューはフォーカスを取得できないため、オプション メニューのアイテム変更のベースとしてフォーカスを使用しないでください。 + +{@link +android.view.View} に状況依存のメニュー アイテムを提供する場合は、コンテキスト メニューを使います。

+ + + + +

コンテキスト メニューの作成

+ +
+ +

図 3. フローティング コンテキスト メニュー(左)とコンテキスト アクションバー(右)のスクリーンショット +

+
+ +

コンテキスト メニューでは、UI の特定のアイテムやコンテキスト フレームに影響するアクションが提供されます。どのビューでもコンテキスト メニューを提供できますが、ほとんどの場合は、{@link +android.widget.ListView}、{@link android.widget.GridView}、各アイテムでユーザーが直接実行できるその他のビュー コレクションのアイテムに使用されます。 + +

+ +

コンテキスト アクションを提供するには次の 2 つの方法があります。

+ + +

注: コンテキスト アクション モードは、Android 3.0(API レベル 11)以降で使用可能で、使用可能な場合にコンテキスト アクションを表示するのに適した方法です。 + +アプリで 3.0 以前のバージョンをサポートする場合、その端末ではフローティング コンテキスト メニューを使う必要があります。 +

+ + +

フローティング コンテキスト メニューの作成

+ +

フローティング コンテキスト メニューを提供するには:

+
    +
  1. {@link android.app.Activity#registerForContextMenu(View) registerForContextMenu()} を呼び出し、{@link android.view.View} を渡して、コンテキスト メニューに関連付ける必要のある {@link android.view.View} を登録します。 + + +

    アクティビティで {@link android.widget.ListView} や {@link android.widget.GridView} が使用され、各アイテムに同じコンテキスト メニューを提供する場合、{@link android.widget.ListView} や {@link android.widget.GridView} を {@link +android.app.Activity#registerForContextMenu(View) registerForContextMenu()} に渡してコンテキスト メニューにすべてのアイテムを登録します。 + +

    +
  2. + +
  3. {@link android.app.Activity} や {@link android.app.Fragment} に {@link +android.view.View.OnCreateContextMenuListener#onCreateContextMenu onCreateContextMenu()} メソッドを実装します。 + +

    登録されたビューが長押しクリック イベントを受け取ると、システムは {@link +android.view.View.OnCreateContextMenuListener#onCreateContextMenu onCreateContextMenu()} メソッドを呼び出します。 +通常、ここでメニュー リソースをインフレートして、メニュー アイテムを定義します。次に例を示します。 +

    +
    +@Override
    +public void onCreateContextMenu(ContextMenu menu, View v,
    +                                ContextMenuInfo menuInfo) {
    +    super.onCreateContextMenu(menu, v, menuInfo);
    +    MenuInflater inflater = getMenuInflater();
    +    inflater.inflate(R.menu.context_menu, menu);
    +}
    +
    + +

    {@link android.view.MenuInflater} では、メニュー リソースからコンテキスト メニューをインフレートできます。そのコールバック メソッド パラメータには、ユーザーが選択した {@link android.view.View} と選択されたアイテムに関する追加情報を提供する {@link android.view.ContextMenu.ContextMenuInfo} オブジェクトが含まれます。 + + +アクティビティに、それぞれ別のコンテキスト メニューを提供する複数のビューがある場合、これらのパラメータを使ってインフレートするコンテキスト メニューを決定できます。 + +

    +
  4. + +
  5. {@link android.app.Activity#onContextItemSelected(MenuItem) +onContextItemSelected()} を実装します。 +

    ユーザーがメニュー アイテムを選択すると、システムによってこのメソッドが呼び出され、適切なアクションを実行できるようになります。 +次に例を示します。

    + +
    +@Override
    +public boolean onContextItemSelected(MenuItem item) {
    +    AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo();
    +    switch (item.getItemId()) {
    +        case R.id.edit:
    +            editNote(info.id);
    +            return true;
    +        case R.id.delete:
    +            deleteNote(info.id);
    +            return true;
    +        default:
    +            return super.onContextItemSelected(item);
    +    }
    +}
    +
    + +

    {@link android.view.MenuItem#getItemId()} メソッドは、選択されたメニュー アイテムの ID を照会します。XML でメニューを定義するのセクションで説明されているように、{@code +android:id} 属性を使って XML で各メニュー アイテムを割り当てる必要があります。 + +

    + +

    メニュー アイテムを正常に処理する場合、{@code true} を返します。メニュー アイテムを処理しない場合は、スーパークラスの実装にメニュー アイテムを渡す必要があります。 +アクティビティにフラグメントが含まれる場合、そのアクティビティは最初にこのコールバックを受け取ります。 +未処理のときにスーパークラスを呼び出すと、システムは {@code true} や {@code false} が返されるまで、各フラグメントのそれぞれのコールバック メソッドに 1 つずつ、各フラグメントが追加された順序でイベントを渡します。 + +{@link android.app.Activity} と {@code android.app.Fragment} のデフォルトの実装では {@code +false} が返されるため、未処理のときは常にスーパークラスを呼び出す必要があります。 +

    +
  6. +
+ + +

コンテキスト アクション モードの使用

+ +

コンテキスト アクション モードは、{@link android.view.ActionMode} のシステム実装で、ユーザーによるコンテキスト アクション実行のための操作に焦点が置かれています。 +ユーザーがアイテムを選択してこのモードを有効にすると、画面の最上部にコンテキスト アクションバーが表示され、現在選択中のアイテムで実行できるアクションが表示されます。 + +このモードが有効な間は、ユーザーは複数のアイテムを選択したり(許可されている場合)、アイテムを選択解除したり、アクティビティ内を移動し続けたり(許可されている範囲内で)することができます。 + +ユーザーがすべてのアイテムの選択を解除したり、Back ボタンをしたり、またはバーの左端で Done アクションを選択したりすると、このアクション モードは無効になり、コンテキスト アクションバーは表示されなくなります。 + +

+ +

注: コンテキスト アクションバーをアクションバーと関連付ける必要はありません。 +コンテキスト アクションバーが、視覚的にアクションバーの位置にかかる場合でも、個別に操作できます。 + +

+ +

Android 3.0(API レベル 11)以降向けに開発中の場合、通常はフローティング コンテキスト メニューではなく、コンテキスト アクション モードを使ってコンテキスト アクションを表示します。 +

+ +

コンテキスト アクションを提供するビューでは、通常は次の 2 つのイベントのいずれかまたは両方で、コンテキスト アクションを呼び出す必要があります。 +

+ + +

アプリケーションがどのようにコンテキスト アクション モードを呼び出して各アクションの動作を定義するかは、デザインによって異なります。 +基本的に次の 2 つのデザインがあります。

+ + +

次のセクションでは、各シナリオに必要な設定について説明します。

+ + +

個別のビューに対してコンテキスト アクション モードを有効にする

+ +

ユーザーが特定のビューを選択するときにのみ、コンテキスト アクション モードを呼び出すには、次のことを行う必要があります。 +

+
    +
  1. {@link android.view.ActionMode.Callback} インターフェースを実装します。このコールバック メソッドでは、コンテキスト アクションバーに対してアクションを指定して、アクション アイテムでのイベント クリックに応答し、アクション モードのその他のライフサイクル イベントを処理できます。 + +
  2. +
  3. ユーザーがビューを長押しクリックしたときなど、バーを表示するときに、{@link android.app.Activity#startActionMode startActionMode()} を呼び出します。 +
  4. +
+ +

次に例を示します。

+ +
    +
  1. {@link android.view.ActionMode.Callback ActionMode.Callback} インターフェースを実装します。 +
    +private ActionMode.Callback mActionModeCallback = new ActionMode.Callback() {
    +
    +    // Called when the action mode is created; startActionMode() was called
    +    @Override
    +    public boolean onCreateActionMode(ActionMode mode, Menu menu) {
    +        // Inflate a menu resource providing context menu items
    +        MenuInflater inflater = mode.getMenuInflater();
    +        inflater.inflate(R.menu.context_menu, menu);
    +        return true;
    +    }
    +
    +    // Called each time the action mode is shown. Always called after onCreateActionMode, but
    +    // may be called multiple times if the mode is invalidated.
    +    @Override
    +    public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
    +        return false; // Return false if nothing is done
    +    }
    +
    +    // Called when the user selects a contextual menu item
    +    @Override
    +    public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
    +        switch (item.getItemId()) {
    +            case R.id.menu_share:
    +                shareCurrentItem();
    +                mode.finish(); // Action picked, so close the CAB
    +                return true;
    +            default:
    +                return false;
    +        }
    +    }
    +
    +    // Called when the user exits the action mode
    +    @Override
    +    public void onDestroyActionMode(ActionMode mode) {
    +        mActionMode = null;
    +    }
    +};
    +
    + +

    これらのイベント コールバックは、これらのそれぞれもイベントに関連する {@link +android.view.ActionMode} オブジェクトを渡すこと以外は、オプション メニューのコールバックとほぼ同じです。{@link +android.view.ActionMode} API を使って、{@link android.view.ActionMode#setTitle setTitle()} と {@link +android.view.ActionMode#setSubtitle setSubtitle()}(選択されているアイテム数を表示するのに役立つ)でタイトルとサブタイトルを変更するなど、CAB にさまざまな変更を行うことができます。 + +

    + +

    また、上記のサンプルでは、アクション モードが破棄されるときに {@code mActionMode} 変数が null に設定されます。 +次のステップでは、それがどのように初期化され、アクティビティやフラグメントでどのようにメンバー変数を保存するのが役立つかについて説明します。 +

    +
  2. + +
  3. {@link android.app.Activity#startActionMode startActionMode()} を呼び出して、{@link +android.view.View} で長押しクリックに応答するときなど、適切な時にコンテキスト アクション モードを有効にします。 +

    + +
    +someView.setOnLongClickListener(new View.OnLongClickListener() {
    +    // Called when the user long-clicks on someView
    +    public boolean onLongClick(View view) {
    +        if (mActionMode != null) {
    +            return false;
    +        }
    +
    +        // Start the CAB using the ActionMode.Callback defined above
    +        mActionMode = getActivity().startActionMode(mActionModeCallback);
    +        view.setSelected(true);
    +        return true;
    +    }
    +});
    +
    + +

    {@link android.app.Activity#startActionMode startActionMode()} を呼び出すと、システムは作成された {@link android.view.ActionMode} を返します。 +メンバー変数でこれを保存すると、その他のイベントに応じてコンテキスト アクションバーを変更できます。 +上記の例では、{@link android.view.ActionMode} を使って、{@link android.view.ActionMode} インスタンスがアクティブな状態である場合に、アクション モードを開始する前にメンバーが null であるかどうかを確認して、そのインスタンスが再作成されないようにしています。 + + +

    +
  4. +
+ + + +

バッチ コンテキスト アクションを ListView または GridView で有効にする

+ +

{@link android.widget.ListView} や {@link +android.widget.GridView}(または {@link android.widget.AbsListView} の別の拡張)にアイテムのコレクションがあり、ユーザーがバッチ アクションを実行できるようにする場合は、次のことを行う必要があります。 +

+ + + +

次に例を示します。

+ +
+ListView listView = getListView();
+listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL);
+listView.setMultiChoiceModeListener(new MultiChoiceModeListener() {
+
+    @Override
+    public void onItemCheckedStateChanged(ActionMode mode, int position,
+                                          long id, boolean checked) {
+        // Here you can do something when items are selected/de-selected,
+        // such as update the title in the CAB
+    }
+
+    @Override
+    public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
+        // Respond to clicks on the actions in the CAB
+        switch (item.getItemId()) {
+            case R.id.menu_delete:
+                deleteSelectedItems();
+                mode.finish(); // Action picked, so close the CAB
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    @Override
+    public boolean onCreateActionMode(ActionMode mode, Menu menu) {
+        // Inflate the menu for the CAB
+        MenuInflater inflater = mode.getMenuInflater();
+        inflater.inflate(R.menu.context, menu);
+        return true;
+    }
+
+    @Override
+    public void onDestroyActionMode(ActionMode mode) {
+        // Here you can make any necessary updates to the activity when
+        // the CAB is removed. By default, selected items are deselected/unchecked.
+    }
+
+    @Override
+    public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
+        // Here you can perform updates to the CAB due to
+        // an {@link android.view.ActionMode#invalidate} request
+        return false;
+    }
+});
+
+ +

これだけです。これで、ユーザーが長押しクリックでアイテムを選択すると、システムは {@link +android.widget.AbsListView.MultiChoiceModeListener#onCreateActionMode onCreateActionMode()} メソッドを呼び出して、特定のアクションでコンテキスト アクションバーを表示するようになります。 +コンテキスト アクションバーが表示されている間は、追加のアイテムを選択できます。 +

+ +

コンテキスト アクションによって一般的なアクション アイテムが提供されるとき、ユーザーが長押しクリックの動作に気付かない可能性があることを考慮して、アイテムを選択できるようにチェックボックスや同様の UI 要素を追加したい場合もあります。 + +ユーザーがチェックボックスをオンにするとき、{@link android.widget.AbsListView#setItemChecked setItemChecked()} でオンにされた状態にそれぞれのリストアイテムを設定して、コンテキスト アクション モードを呼び出すことができます。 + +

+ + + + +

ポップアップ メニューの作成

+ +
+ +

図 4.右上のオーバーフローボタンに固定された Gmail アプリのポップアップ メニュー。 +

+
+ +

{@link android.widget.PopupMenu} は {@link android.view.View} に固定されるモーダル メニューです。スペースがある場合にはアンカービューの下に、スペースがない場合はビューの上に表示されます。 +次の場合に役立ちます。

+ + + +

注: {@link android.widget.PopupMenu} は API レベル 11 以降で使用できます。 +

+ +

XML でメニューを定義する場合の、ポップアップ メニューの表示方法について、次に示します。

+
    +
  1. そのコンストラクタを使って {@link android.widget.PopupMenu} のインスタンスを作成します。これにより、メニューが固定される必要のある、現在のアプリケーションの {@link android.content.Context} と {@link android.view.View} が取得されます。 + +
  2. +
  3. {@link android.view.MenuInflater} を使って、{@link +android.widget.PopupMenu#getMenu() PopupMenu.getMenu()} によって返される {@link +android.view.Menu} オブジェクトにメニュー リソースをインフレートします。API レベル 14 以降では、代わりに {@link android.widget.PopupMenu#inflate PopupMenu.inflate()} を使うことができます。 +
  4. +
  5. {@link android.widget.PopupMenu#show() PopupMenu.show()} を呼び出します。
  6. +
+ +

ポップアップ メニューを表示する {@link android.R.attr#onClick android:onClick} 属性を含むボタンの一例を以下に示します。 +

+ +
+<ImageButton
+    android:layout_width="wrap_content" 
+    android:layout_height="wrap_content" 
+    android:src="@drawable/ic_overflow_holo_dark"
+    android:contentDescription="@string/descr_overflow_button"
+    android:onClick="showPopup" />
+
+ +

アクティビティでは、次のようにポップアップ メニューが表示されます。

+ +
+public void showPopup(View v) {
+    PopupMenu popup = new PopupMenu(this, v);
+    MenuInflater inflater = popup.getMenuInflater();
+    inflater.inflate(R.menu.actions, popup.getMenu());
+    popup.show();
+}
+
+ +

API レベル 14 以降では、{@link +android.widget.PopupMenu#inflate PopupMenu.inflate()} でメニューをインフレートする 2 行を組み合わせることができます。

+ +

ユーザーがアイテムを選択するか、メニュー領域外をタップすると、メニューが閉じます。 +{@link +android.widget.PopupMenu.OnDismissListener} を使って dismiss イベントをリッスンできます。

+ +

クリック イベントを処理する

+ +

ユーザーがアイテム メニューを選択するときにアクションを実行するには、{@link +android.widget.PopupMenu.OnMenuItemClickListener} インターフェースを実装し、{@link android.widget.PopupMenu#setOnMenuItemClickListener +setOnMenuItemclickListener()} を呼び出して {@link +android.widget.PopupMenu} でそれを登録する必要があります。 +ユーザーがアイテムを選択すると、インターフェースで {@link +android.widget.PopupMenu.OnMenuItemClickListener#onMenuItemClick onMenuItemClick()} コールバックが呼び出されます。 +

+ +

次に例を示します。

+ +
+public void showMenu(View v) {
+    PopupMenu popup = new PopupMenu(this, v);
+
+    // This activity implements OnMenuItemClickListener
+    popup.setOnMenuItemClickListener(this);
+    popup.inflate(R.menu.actions);
+    popup.show();
+}
+
+@Override
+public boolean onMenuItemClick(MenuItem item) {
+    switch (item.getItemId()) {
+        case R.id.archive:
+            archive(item);
+            return true;
+        case R.id.delete:
+            delete(item);
+            return true;
+        default:
+            return false;
+    }
+}
+
+ + +

メニュー グループの作成

+ +

メニュー グループは、特定の特徴を共有するメニュー アイテムのコレクションです。グループを使って次のことを実行できます。 +

+ + +

メニュー リソースの {@code <group>} 要素内で {@code <item>} 要素をネストするか、{@link +android.view.Menu#add(int,int,int,int) add()} メソッドでグループ ID を指定して、グループを作成できます。 +

+ +

グループを含むメニュー リソースの一例を次に示します。

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:id="@+id/menu_save"
+          android:icon="@drawable/menu_save"
+          android:title="@string/menu_save" />
+    <!-- menu group -->
+    <group android:id="@+id/group_delete">
+        <item android:id="@+id/menu_archive"
+              android:title="@string/menu_archive" />
+        <item android:id="@+id/menu_delete"
+              android:title="@string/menu_delete" />
+    </group>
+</menu>
+
+ +

グループ内のアイテムは、最初のアイテムと同じレベルで表示されます(メニューの 3 つのアイテムすべてが兄弟)。 +ただし、グループ ID を参照するか、上記のメソッドを使って、グループ内の 2 つのアイテムの特徴を変更できます。 +システムが、グループ化されたアイテムを分けることもありません。 +たとえば、各アイテムに {@code +android:showAsAction="ifRoom"} を宣言する場合、その両方がアクションバーまたはアクション オーバーフローに表示されます。 +

+ + +

オンにできるメニュー アイテムの使用

+ +
+ +

図 5. オンにできるアイテムを含むサブメニューのスクリーンショット。 +

+
+ +

メニューは、オプションのオンとオフを切り替えるインターフェースとして役立ちます。スタンドアロンのオプションにはチェックボックスを、相互に排他的なオプションのグループにはラジオボタンを使います。 + +図 5 に、ラジオボタン付きのオンにできるアイテムを含むサブメニューを示します。 +

+ +

注: アイコン メニューのメニュー アイテム(オプション メニューから)ではチェックボックスやラジオボタンを表示できません。 +アイコン メニューのアイテムをオンにできるようにする場合、状態が変わるごとにアイコンやテキストを入れ替えて、オンにされた状態を手動で示す必要があります。 + +

+ +

個々のメニュー アイテムには {@code <item>} 要素の {@code +android:checkable} 属性を、グループ全体には {@code <group>} 要素の {@code android:checkableBehavior} 属性を使って、オンにできる動作を定義できます。 +たとえば、このメニュー グループのすべてのアイテムはラジオボタンでオンにできます。 +

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+    <group android:checkableBehavior="single">
+        <item android:id="@+id/red"
+              android:title="@string/red" />
+        <item android:id="@+id/blue"
+              android:title="@string/blue" />
+    </group>
+</menu>
+
+ +

{@code android:checkableBehavior} 属性では、次のいずれかが許容されます。 +

+
{@code single}
+
グループから 1 つのアイテムのみをオンにできる(ラジオボタン)
+
{@code all}
+
すべてのアイテムをオンにできる(チェックボックス)
+
{@code none}
+
どのアイテムもオンにできない
+
+ +

{@code <item>} 要素の {@code android:checked} 属性を使って、デフォルトのオンにされた状態をアイテムに適用でき、{@link +android.view.MenuItem#setChecked(boolean) setChecked()} メソッドを使ってコード内でそれを変更できます。 +

+ +

オンにできるアイテムが選択されると、システムは {@link android.app.Activity#onOptionsItemSelected(MenuItem) onOptionsItemSelected()} のような、それぞれの item-selected コールバック メソッドを呼び出します。 +チェックボックスやラジオボタンによって自動的にその状態が変わることはないため、ここでチェックボックスの状態を設定する必要があります。 + +{@link android.view.MenuItem#isChecked()} で、アイテムの現在の状態(ユーザーが選択する前の状態)を照会できます。その後、{@link android.view.MenuItem#setChecked(boolean) setChecked()} でオンにされた状態を設定します。 + +次に例を示します。

+ +
+@Override
+public boolean onOptionsItemSelected(MenuItem item) {
+    switch (item.getItemId()) {
+        case R.id.vibrate:
+        case R.id.dont_vibrate:
+            if (item.isChecked()) item.setChecked(false);
+            else item.setChecked(true);
+            return true;
+        default:
+            return super.onOptionsItemSelected(item);
+    }
+}
+
+ +

この方法でオンにされた状態を設定しない場合には、アイテム(チェックボックスまたはラジオボタン)の表示状態はユーザーがオンにしたときに変更されません。 + +状態を設定すると、アクティビティはアイテムのオンにされた状態を維持して、ユーザーが後でメニューを開いたときに、開発者が設定したオンにされた状態が表示されるようになります。 + +

+ +

注: オンにできるメニュー アイテムは、セッション単位ベースのみでの使用を意図したもので、アプリケーションが破棄された後は、保存されません。 + +ユーザー用に保存するアプリケーション設定がある場合は、共有のプリファレンスを使ってデータを保存してください。 +

+ + + +

インテントに基づくメニュー アイテムの追加

+ +

{@link android.content.Intent} を使ってメニュー アイテムでアクティビティが起動されるようにしたい場合もあります(自分のアプリケーションのアクティビティであるか、別のアプリケーションのアクティビティであるかにかかわらず)。 +使用するインテントがわかっていて、インテントを開始する必要のある特定のメニュー アイテムがある場合は、適切な on-item-selected コールバック メソッド({@link +android.app.Activity#onOptionsItemSelected(MenuItem) onOptionsItemSelected()} のような)中に、{@link android.app.Activity#startActivity(Intent) startActivity()} でインテントを実行できます。 + + +

+ +

ただし、ユーザーの端末にインテントを処理するアプリケーションが含まれているかどうかが不明な場合、それを呼び出すメニュー アイテムを追加すると、そのインテントによってアクティビティが解決されないためにメニュー アイテムが機能しなくなることがあります。 + + +これを解決するために、Android では、インテントを処理する端末で Android によってアクティビティが検出されるときに、メニュー アイテムがメニューに動的に追加されるようにします。 +

+ +

インテントを受け入れる使用可能なアクティビティに基づいてメニュー アイテムを追加するには:

+
    +
  1. {@link android.content.Intent#CATEGORY_ALTERNATIVE} や {@link android.content.Intent#CATEGORY_SELECTED_ALTERNATIVE} カテゴリでインテントを定義します。その他の要件も必要です。 + +
  2. +
  3. {@link +android.view.Menu#addIntentOptions(int,int,int,ComponentName,Intent[],Intent,int,MenuItem[]) +Menu.addIntentOptions()} を呼び出します。その際、Android によって、インテントを実行できるアプリケーションが検索され、メニューにそのアプリケーションが追加されます。 +
  4. +
+ +

インテントを満たすアプリケーションがインストールされていない場合、メニュー アイテムは追加されません。 +

+ +

注: {@link android.content.Intent#CATEGORY_SELECTED_ALTERNATIVE} は、現在画面で選択されている要素の処理に使われます。 + +このため、{@link +android.app.Activity#onCreateContextMenu(ContextMenu,View,ContextMenuInfo) +onCreateContextMenu()} でメニューを作成するときにのみ、それが使われる必要があります。

+ +

次に例を示します。

+ +
+@Override
+public boolean onCreateOptionsMenu(Menu menu){
+    super.onCreateOptionsMenu(menu);
+
+    // Create an Intent that describes the requirements to fulfill, to be included
+    // in our menu. The offering app must include a category value of Intent.CATEGORY_ALTERNATIVE.
+    Intent intent = new Intent(null, dataUri);
+    intent.addCategory(Intent.CATEGORY_ALTERNATIVE);
+
+    // Search and populate the menu with acceptable offering applications.
+    menu.addIntentOptions(
+         R.id.intent_group,  // Menu group to which new items will be added
+         0,      // Unique item ID (none)
+         0,      // Order for the items (none)
+         this.getComponentName(),   // The current activity name
+         null,   // Specific items to place first (none)
+         intent, // Intent created above that describes our requirements
+         0,      // Additional flags to control items (none)
+         null);  // Array of MenuItems that correlate to specific items (none)
+
+    return true;
+}
+ +

定義されたインテントに一致するインテント フィルタが提供されていることを見つけた各アクティビティに対して、メニュー アイテムのタイトルにインテント フィルタの android:label の値を、メニュー アイテムのアイコンにアプリケーション アイコンを使って、メニュー アイテムが追加されます。 + +{@link android.view.Menu#addIntentOptions(int,int,int,ComponentName,Intent[],Intent,int,MenuItem[]) +addIntentOptions()} メソッドによって、追加されたメニュー アイテム数が返されます。 +

+ +

注: {@link +android.view.Menu#addIntentOptions(int,int,int,ComponentName,Intent[],Intent,int,MenuItem[]) +addIntentOptions()} を呼び出すとき、最初の引数で指定されたメニュー グループによって、すべてのメニュー アイテムがオーバーライドされます。 +

+ + +

アクティビティを他のメニューに追加できるようにする

+ +

他のアプリケーションにアクティビティのサービスを提供して、アプリケーションを他のメニューに含めることができるようにすることもできます(前述の役割を逆にする)。 +

+ +

他のアプリケーションのメニューに含まれるようにするには、通常どおりインテント フィルタを定義する必要がありますが、インテント フィルタのカテゴリに、{@link android.content.Intent#CATEGORY_ALTERNATIVE} や {@link android.content.Intent#CATEGORY_SELECTED_ALTERNATIVE} 値を含めるようにしてください。 + + +次に例を示します。

+
+<intent-filter label="@string/resize_image">
+    ...
+    <category android:name="android.intent.category.ALTERNATIVE" />
+    <category android:name="android.intent.category.SELECTED_ALTERNATIVE" />
+    ...
+</intent-filter>
+
+ +

インテント フィルタの記述の詳細については、「インテントとインテント フィルタ」をご覧ください。 +

+ +

この方法を使ったサンプル アプリケーションについては、Note Pad のサンプルコードをご覧ください。 + +

diff --git a/docs/html-intl/intl/ja/guide/topics/ui/notifiers/notifications.jd b/docs/html-intl/intl/ja/guide/topics/ui/notifiers/notifications.jd new file mode 100644 index 0000000000000000000000000000000000000000..f341256a3225be22b136bff83747f143f65165a6 --- /dev/null +++ b/docs/html-intl/intl/ja/guide/topics/ui/notifiers/notifications.jd @@ -0,0 +1,979 @@ +page.title=通知 +@jd:body + + +

+ 通知は、アプリケーションの通常の UI 以外で、ユーザーに表示できるメッセージです。システムが通知を発行通知すると、通知はまず通知エリアにアイコンで表示されます。 + +通知の詳細を確認するには、ユーザーが通知ドロワーを開く必要があります。 +ドロワー通知エリアと通知ドロワーはどちらも、システムによって制御されているエリアであり、ユーザーはいつでも見ることができます。 + +

+ +

+ 図 1. 通知エリアの通知。 +

+ +

+ 図 2. 通知ドロワーの通知。 +

+ +

注: 別途記載がある場合を除き、このガイドでは、バージョン 4 の サポート ライブラリの {@link android.support.v4.app.NotificationCompat.Builder NotificationCompat.Builder} クラスについて記述しています。クラス {@link android.app.Notification.Builder Notification.Builder} は、Android 3.0(API レベル 11)で追加されました。 + + + +

+ +

設計上の考慮事項

+ +

通知は、Android ユーザー インターフェースの重要なパーツであり、独自の設計ガイドラインが設けられています。Android 5.0 (API レベル 21)で導入されたマテリアル デザインの変更は特に重要です。詳細については、「マテリアル デザイン」をご覧ください。 + + +通知とその操作の設計方法については、通知設計ガイドをご覧ください。 +

+ +

通知を作成する

+ +

通知のための UI 情報とアクションを、{@link android.support.v4.app.NotificationCompat.Builder NotificationCompat.Builder} オブジェクトに指定します。通知自体を作成するには、{@link android.support.v4.app.NotificationCompat.Builder#build NotificationCompat.Builder.build()} を呼び出します。これにより、指定された UI 情報とアクションを含む {@link android.app.Notification} オブジェクトが返されます。 + + + +通知を発行するには、{@link android.app.NotificationManager#notify NotificationManager.notify()} を呼び出して、この {@link android.app.Notification} オブジェクトをシステムに渡します。 + +

+ +

必須通知コンテンツ

+

+ {@link android.app.Notification} オブジェクトの必須コンテンツは次のとおりです。 +

+ +

省略可能な通知コンテンツと設定

+

+ 上記以外のすべての通知設定とコンテンツは省略可能です。省略可能な通知設定とコンテンツについては、{@link android.support.v4.app.NotificationCompat.Builder} のリファレンスをご覧ください。 + +

+ +

通知アクション

+

+ 通知アクションは省略可能ですが、通知には、少なくとも 1 つのアクションを追加する必要があります。 + アクションは、ユーザーが通知からアプリケーションの {@link android.app.Activity} に直接移動することを可能にします。ユーザーは、移動先で、イベントを確認したりさらに作業を行ったりすることができます。 + + +

+

+ 1 つの通知が複数のアクションを提供することもあります。そのため、ユーザーが通知をクリックした時にトリガーされるアクションを必ず定義してください。通常、このアクションは、アプリケーション内で {@link android.app.Activity} を開きます。 + +また、アラームのスヌーズやテキスト メッセージへの即時返信などの追加のアクションを実行するボタンを、通知に追加することもできます。この機能は、Android 4.1 から利用できるようになりました。 + +追加のアクション ボタンを使用する場合、それらのボタンの機能をアプリの {@link android.app.Activity} で利用できるようにする必要があります。詳細については、互換性の確保についてのセクションをご覧ください。 + + +

+

+ {@link android.app.Notification} 内部では、アクションは、アプリケーションで {@link android.app.Activity} を開始する {@link android.content.Intent} が含まれる {@link android.app.PendingIntent} によって定義されます。 + + +{@link android.app.PendingIntent} を操作と関連付けるには、{@link android.support.v4.app.NotificationCompat.Builder} の該当するメソッドを呼び出します。 + +たとえば、ユーザーが通知ドロワーで通知のテキストをクリックしたときに {@link android.app.Activity} を開始する場合、{@link android.support.v4.app.NotificationCompat.Builder#setContentIntent setContentIntent()} を呼び出して {@link android.app.PendingIntent} を追加します。 + + + +

+

+ ユーザーが通知をクリックしたときに {@link android.app.Activity} を開始することは、最も一般的なアクション シナリオです。 +ユーザーが通知を閉じた場合に {@link android.app.Activity} を開始することもできます。 +Android 4.1 以降では、アクション ボタンから {@link android.app.Activity} を開始できます。 +詳細については、{@link android.support.v4.app.NotificationCompat.Builder} のリファレンスをご覧ください。 + +

+ +

通知の優先度

+

+ 必要に応じて、通知の優先度を設定できます。通知の優先度は、通知の表示方法についての端末 UI へのヒントの役割を果たします。 + + 通知の優先度を設定するには、{@link android.support.v4.app.NotificationCompat.Builder#setPriority(int) otificationCompat.Builder.setPriority()} を呼び出し、{@link android.support.v4.app.NotificationCompat} 優先度定数の 1 つを渡します。 + + +優先度レベルには、{@link android.support.v4.app.NotificationCompat#PRIORITY_MIN} (-2)から {@link android.support.v4.app.NotificationCompat#PRIORITY_MAX} (2)までの 5 段階あります。優先度レベルが設定されていない場合、優先度はデフォルト値の {@link android.support.v4.app.NotificationCompat#PRIORITY_DEFAULT} (0)になります。 + + + + + +

+

適切な優先順位の設定方法については、通知設計ガイドの「Correctly set and manage notification priority」をご覧ください。 + + +

+ +

簡単な通知を作成する

+

+ 次のスニペットでは、ユーザーに通知がクリックされたときに起動するアクティビティを指定する簡単な通知を作成しています。 +このコードでは {@link android.support.v4.app.TaskStackBuilder} オブジェクトを作成し、そのオブジェクトを使用して、アクションのための {@link android.app.PendingIntent} を作成していることにご注意ください。 + +詳細は、アクティビティの開始時のナビゲーションの維持セクションで説明しています。 + + +

+
+NotificationCompat.Builder mBuilder =
+        new NotificationCompat.Builder(this)
+        .setSmallIcon(R.drawable.notification_icon)
+        .setContentTitle("My notification")
+        .setContentText("Hello World!");
+// Creates an explicit intent for an Activity in your app
+Intent resultIntent = new Intent(this, ResultActivity.class);
+
+// The stack builder object will contain an artificial back stack for the
+// started Activity.
+// This ensures that navigating backward from the Activity leads out of
+// your application to the Home screen.
+TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);
+// Adds the back stack for the Intent (but not the Intent itself)
+stackBuilder.addParentStack(ResultActivity.class);
+// Adds the Intent that starts the Activity to the top of the stack
+stackBuilder.addNextIntent(resultIntent);
+PendingIntent resultPendingIntent =
+        stackBuilder.getPendingIntent(
+            0,
+            PendingIntent.FLAG_UPDATE_CURRENT
+        );
+mBuilder.setContentIntent(resultPendingIntent);
+NotificationManager mNotificationManager =
+    (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+// mId allows you to update the notification later on.
+mNotificationManager.notify(mId, mBuilder.build());
+
+

これで、ユーザーに通知が行われました。

+ +

通知に拡張レイアウトを適用する

+

+ 通知を拡張ビューに表示するには、まず {@link android.support.v4.app.NotificationCompat.Builder} オブジェクトを任意の標準ビュー オプションで作成します。 + +次に、{@link android.support.v4.app.NotificationCompat.Builder#setStyle Builder.setStyle()} を拡張レイアウト オブジェクトを引数に指定して呼び出します。 + +

+

+ 通知の拡張機能は、Android 4.1 より前のバージョンでは利用できないことにご注意ください。Android 4.1 とそれ以前のプラットフォームでの通知の処理方法については、互換性の確保についてのセクションをご覧ください。 + + +

+

+ たとえば、次のコード スニペットでは、先ほどのスニペットで作成した通知を変更して、拡張レイアウトを使用するようにしています。 + +

+
+NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(this)
+    .setSmallIcon(R.drawable.notification_icon)
+    .setContentTitle("Event tracker")
+    .setContentText("Events received")
+NotificationCompat.InboxStyle inboxStyle =
+        new NotificationCompat.InboxStyle();
+String[] events = new String[6];
+// Sets a title for the Inbox in expanded layout
+inboxStyle.setBigContentTitle("Event tracker details:");
+...
+// Moves events into the expanded layout
+for (int i=0; i < events.length; i++) {
+
+    inboxStyle.addLine(events[i]);
+}
+// Moves the expanded layout object into the notification object.
+mBuilder.setStyle(inBoxStyle);
+...
+// Issue the notification here.
+
+ +

互換性を確保する

+ +

+ 通知機能をセットするメソッドはサポート ライブラリのクラス {@link android.support.v4.app.NotificationCompat.Builder NotificationCompat.Builder} に登録されていますが、特定のバージョンですべての通知機能が利用できるわけではありません。 + + + たとえば、アクション ボタンは拡張通知の機能ですが、拡張通知自体が Android 4.1 以上でしか利用できないため、Android 4.1 以上でのみ表示されます。 + + +

+

+ 可能な限り互換性を確保するには、{@link android.support.v4.app.NotificationCompat NotificationCompat} とそのサブクラス、特に {@link android.support.v4.app.NotificationCompat.Builder NotificationCompat.Builder} を使用して通知を作成します。 + + +さらに、通知の実装時に次の処理を行ってください。 +

+
    +
  1. + ユーザーが利用しているバージョンにかかわらず、通知機能のすべてをすべてのユーザーに提供します。 +それには、アプリの {@link android.app.Activity} からすべての機能が利用できるようにする必要があります。このために、新しい {@link android.app.Activity} を追加した方がよい場合もあります。 + + +

    + たとえば、{@link android.support.v4.app.NotificationCompat.Builder#addAction addAction()} を使用してメディア再生の開始と停止を行うコントロールを提供する場合、まずこのコントロールをアプリの {@link android.app.Activity} に実装します。 + + + +

    +
  2. +
  3. + ユーザーが通知をクリックしたときにその {@link android.app.Activity} が起動するようにして、すべてのユーザーがその機能にアクセスできるようにします。 +それには、{@link android.app.Activity} のための {@link android.app.PendingIntent} を作成する必要があります。 + +{@link android.support.v4.app.NotificationCompat.Builder#setContentIntent setContentIntent()} を呼び出し、{@link android.app.PendingIntent} を通知に追加してください。 + + +
  4. +
  5. + 利用したい拡張通知機能を通知に追加します。追加した機能は、ユーザーが通知をクリックしたときに開始する {@link android.app.Activity} でも利用できることにご注意ください。 + + +
  6. +
+ + + + +

通知を管理する

+

+ 同じタイプのイベントのために通知を複数回発行する場合、そのたびに新しい通知を作成することは避ける必要があります。 +新しい通知を作成する代わりに、以前の通知を更新して一部の値を変更することやいくつかの値を追加することを検討してください。 + +

+

+ たとえば、Gmail は新しいメールが届いたことを、未読メッセージの数を増やし通知に各メールの概要を追加することで、ユーザーに通知します。 +これは、通知の「スタッキング」と呼ばれています。詳細については、通知設計ガイドをご覧ください。 + + +

+

+ 注: この Gmail 機能には、「受信トレイ」の拡張レイアウトが必要です。これは、Android 4.1 以降で利用可能な拡張通知機能の 1 つです。 + +

+

+ 次のセクションでは、通知の更新方法と通知の削除方法を説明します。 +

+

通知を更新する

+

+ 後で更新できるように通知をセットアップするには、{@link android.app.NotificationManager#notify(int, android.app.Notification) NotificationManager.notify()} を呼び出し、通知 ID を指定して通知を発行します。 + + 発行後に通知を更新するには、{@link android.support.v4.app.NotificationCompat.Builder} オブジェクトを更新または作成し、そのオブジェクトから {@link android.app.Notification} オブジェクトをビルドし、以前使用した ID と同じ ID で {@link android.app.Notification} を発行します。 + + +以前の通知がそのまま表示されている場合は、{@link android.app.Notification} オブジェクトのコンテンツから、その通知が更新されます。 + +以前の通知が閉じられている場合は、代わりに新しい通知が作成されます。 + +

+

+ 次のスニペットでは、発生したイベントの数を反映するために通知が更新されています。 +このスニペットは通知をスタックし、概要を表示します。 +

+
+mNotificationManager =
+        (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+// Sets an ID for the notification, so it can be updated
+int notifyID = 1;
+mNotifyBuilder = new NotificationCompat.Builder(this)
+    .setContentTitle("New Message")
+    .setContentText("You've received new messages.")
+    .setSmallIcon(R.drawable.ic_notify_status)
+numMessages = 0;
+// Start of a loop that processes data and then notifies the user
+...
+    mNotifyBuilder.setContentText(currentText)
+        .setNumber(++numMessages);
+    // Because the ID remains unchanged, the existing notification is
+    // updated.
+    mNotificationManager.notify(
+            notifyID,
+            mNotifyBuilder.build());
+...
+
+ + +

通知を削除する

+

+ 次のいずれかが発生するまで、通知は表示され続けます。 +

+ + + +

アクティビティの開始時にナビゲーションを維持する

+

+ 通知から {@link android.app.Activity} を開始する場合、ユーザーが期待するナビゲーション操作を変えないようにする必要があります。 +たとえば、 [戻る] がクリックされた場合、アプリケーションの標準的なワークフローでは、ホーム画面に戻る必要があります。また、 + [最近使ったアプリ] がクリックされた場合、{@link android.app.Activity} を別のタスクとして表示する必要があります。 +このナビゲーション操作を変えないようにするには、新たなタスクで {@link android.app.Activity} を開始する必要があります。 +{@link android.app.PendingIntent} を設定して新たなタスクを生成する方法は、開始する {@link android.app.Activity} の性質によって異なります。 + +通常は、次の 2 つの場合があります。 +

+
+
+ 通常のアクティビティ +
+
+ アプリケーションの標準的なワークフローの一部である {@link android.app.Activity} を開始します。 +この場合、{@link android.app.PendingIntent} を設定して新たなタスクを開始し、{@link android.app.PendingIntent} にバックスタックを提供します。これにより、アプリケーションの標準的な + + 「戻る」 動作を再現します。 +

+ Gmail アプリからの通知は、このタイプのアクティビティの一例です。1 つの電子メール メッセージの通知をクリックすると、メッセージそれ自体が表示されます。 +[戻る] をタップすると、通知から移動してきたのでなくホーム画面から Gmail に移動してきたかのように、Gmail からホーム画面に戻ります。 + + +

+

+ これは、通知のタップ時に使用していたアプリケーションに関係なく発生します。 +たとえば、Gmail でメッセージを作成しているときに、1 つのメールの通知をクリックすると、すぐにそのメールに移動します。 +その場合、 [戻る] + をタップすると、作成中のメッセージに戻るのではなく、受信トレイ、ホーム画面の順に移動します。 + +

+
+
+ 特殊なアクティビティ +
+
+ ユーザーは、この {@link android.app.Activity} を、通知から開始した場合のみ見ることができます。 + ある意味では、通知自体に表示するのは難しい情報を提供することで、この {@link android.app.Activity} が通知を拡張しているということができます。 +このタイプのアクティビティでは、{@link android.app.PendingIntent} を設定して新たなタスクを開始します。 +ただし、開始した {@link android.app.Activity} はアプリケーションのアクティビティ フローには含まれていないので、バックスタックを作成する必要はありません。 + +たとえば、 [戻る] をクリックすると、ユーザーはホーム画面に移動します。 + +
+
+ +

通常のアクティビティの PendingIntent を設定する

+

+ ダイレクト エントリの {@link android.app.Activity} を開始する {@link android.app.PendingIntent} を設定するには、次の手順に従います。 + +

+
    +
  1. + マニフェストに、アプリケーションの {@link android.app.Activity} の階層を定義します。 +
      +
    1. + Android 4.0.3 以前へのサポートを追加します。これには、<meta-data> 要素を <activity> の子として追加して、開始する {@link android.app.Activity} の親を指定します。 + + + + +

      + この要素に、android:name="android.support.PARENT_ACTIVITY" を設定します。 + + <parent_activity_name> が親 <activity> 要素の android:name の値の場合は、android:value="<parent_activity_name>" を設定します。 + + + + + +例については、以下の XML をご覧ください。 +

      +
    2. +
    3. + また、Android 4.1 以降のサポートを追加します。これには、開始する {@link android.app.Activity} の <activity> 要素に、android:parentActivityName 属性を追加します。 + + + + +
    4. +
    +

    + 最終的な XML は、次のようになります。 +

    +
    +<activity
    +    android:name=".MainActivity"
    +    android:label="@string/app_name" >
    +    <intent-filter>
    +        <action android:name="android.intent.action.MAIN" />
    +        <category android:name="android.intent.category.LAUNCHER" />
    +    </intent-filter>
    +</activity>
    +<activity
    +    android:name=".ResultActivity"
    +    android:parentActivityName=".MainActivity">
    +    <meta-data
    +        android:name="android.support.PARENT_ACTIVITY"
    +        android:value=".MainActivity"/>
    +</activity>
    +
    +
  2. +
  3. + {@link android.app.Activity} を開始する {@link android.content.Intent} に基づくバックスタックを作成します。 + +
      +
    1. + {@link android.app.Activity} を開始する {@link android.content.Intent} を作成します。 +
    2. +
    3. + {@link android.app.TaskStackBuilder#create TaskStackBuilder.create()} を呼び出して、スタック ビルダーを作成します。 + +
    4. +
    5. + {@link android.support.v4.app.TaskStackBuilder#addParentStack addParentStack()} を呼び出し、バックスタックをスタック ビルダーに追加します。 + + マニフェストに定義した階層のそれぞれの {@link android.app.Activity} ごとに、バックスタックに、{@link android.app.Activity} を開始する {@link android.content.Intent} が含まれます。 + +このメソッドは、新たなタスクでスタックを開始するためのフラグも追加します。 + +

      + 注: {@link android.support.v4.app.TaskStackBuilder#addParentStack addParentStack()} の引数は開始した {@link android.app.Activity} への参照ですが、このメソッドの呼び出しによって、{@link android.app.Activity} を開始する {@link android.content.Intent} が追加されることはありません。 + + + +追加は、次の d. で行われます。 +

      +
    6. +
    7. + {@link android.support.v4.app.TaskStackBuilder#addNextIntent addNextIntent()} を呼び出して、通知から {@link android.app.Activity} を開始する {@link android.content.Intent} を追加します。 + + + a. で作成した {@link android.content.Intent} を、引数として {@link android.support.v4.app.TaskStackBuilder#addNextIntent addNextIntent()} に渡します。 + + +
    8. +
    9. + 必要に応じて、{@link android.support.v4.app.TaskStackBuilder#editIntentAt TaskStackBuilder.editIntentAt()} を呼び出し、スタック上の {@link android.content.Intent} オブジェクトに引数を追加します。 + +これは、場合によっては、ターゲット {@link android.app.Activity} に、ユーザーが + + [戻る] を使って移動したときに、適切なデータが表示されるようにするために必要です。 +
    10. +
    11. + {@link android.support.v4.app.TaskStackBuilder#getPendingIntent getPendingIntent()} を呼び出し、このバックスタックのための {@link android.app.PendingIntent} を取得します。 + + この {@link android.app.PendingIntent} は、{@link android.support.v4.app.NotificationCompat.Builder#setContentIntent setContentIntent()} の引数として使用できます。 + + +
    12. +
    +
  4. +
+

+ 次のコード スニペットでは、上記の処理を行っています。 +

+
+...
+Intent resultIntent = new Intent(this, ResultActivity.class);
+TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);
+// Adds the back stack
+stackBuilder.addParentStack(ResultActivity.class);
+// Adds the Intent to the top of the stack
+stackBuilder.addNextIntent(resultIntent);
+// Gets a PendingIntent containing the entire back stack
+PendingIntent resultPendingIntent =
+        stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
+...
+NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
+builder.setContentIntent(resultPendingIntent);
+NotificationManager mNotificationManager =
+    (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+mNotificationManager.notify(id, builder.build());
+
+ +

特殊なアクティビティの PendingIntent を設定する

+

+ 次のセクションでは、特殊なアクティビティのための {@link android.app.PendingIntent} の設定方法を説明します。 + +

+

+ 特殊な {@link android.app.Activity} はバックスタックを必要としません。そのため、マニフェストにその {@link android.app.Activity} の階層を定義する必要はありません。また、バックスタックを作成するために {@link android.support.v4.app.TaskStackBuilder#addParentStack addParentStack()} を呼び出す必要もありません。 + + + +代わりに、マニフェストを利用して {@link android.app.Activity} のタスク オプションを設定し、{@link android.app.PendingIntent#getActivity getActivity()} を呼び出して {@link android.app.PendingIntent} を作成します。 + + +

+
    +
  1. + マニフェストで、{@link android.app.Activity} の <activity> 要素に、次の属性を追加します。 + + +
    +
    +android:name="activityclass" +
    +
    + アクティビティの完全修飾クラス名。 +
    +
    +android:taskAffinity="" +
    +
    + コードにセットした {@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK FLAG_ACTIVITY_NEW_TASK} フラグと組み合わせて、{@link android.app.Activity} がアプリケーションのデフォルトのタスクに入ることがないようにします。 + + +アプリケーションのアフィニティがデフォルトのままの既存タスクは影響を受けません。 + +
    +
    +android:excludeFromRecents="true" +
    +
    + 新しいタスクを、 [最近使ったアプリ] から除外し、ユーザーが新しいタスクに間違って戻ることがないようにします。 + +
    +
    +

    + 次のスニペットは、この要素を示しています。 +

    +
    +<activity
    +    android:name=".ResultActivity"
    +...
    +    android:launchMode="singleTask"
    +    android:taskAffinity=""
    +    android:excludeFromRecents="true">
    +</activity>
    +...
    +
    +
  2. +
  3. + 通知をビルドし発行します。 +
      +
    1. + {@link android.app.Activity} を開始する {@link android.content.Intent} を作成します。 + +
    2. +
    3. + フラグ {@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK FLAG_ACTIVITY_NEW_TASK} と {@link android.content.Intent#FLAG_ACTIVITY_CLEAR_TASK FLAG_ACTIVITY_CLEAR_TASK} を指定して {@link android.content.Intent#setFlags setFlags()} を呼び出し、{@link android.app.Activity} を新しい空のタスクで開始するように設定します。 + + + + +
    4. +
    5. + {@link android.content.Intent} に、必要に応じてオプションを設定します。 +
    6. +
    7. + {@link android.app.PendingIntent#getActivity getActivity()} を呼び出し、{@link android.content.Intent} から {@link android.app.PendingIntent} を作成します。 + + この {@link android.app.PendingIntent} は、{@link android.support.v4.app.NotificationCompat.Builder#setContentIntent setContentIntent()} の引数として使用できます。 + + +
    8. +
    +

    + 次のコード スニペットでは、上記の処理を行っています。 +

    +
    +// Instantiate a Builder object.
    +NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
    +// Creates an Intent for the Activity
    +Intent notifyIntent =
    +        new Intent(this, ResultActivity.class);
    +// Sets the Activity to start in a new, empty task
    +notifyIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
    +                        | Intent.FLAG_ACTIVITY_CLEAR_TASK);
    +// Creates the PendingIntent
    +PendingIntent notifyPendingIntent =
    +        PendingIntent.getActivity(
    +        this,
    +        0,
    +        notifyIntent,
    +        PendingIntent.FLAG_UPDATE_CURRENT
    +);
    +
    +// Puts the PendingIntent into the notification builder
    +builder.setContentIntent(notifyPendingIntent);
    +// Notifications are issued by sending them to the
    +// NotificationManager system service.
    +NotificationManager mNotificationManager =
    +    (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
    +// Builds an anonymous Notification object from the builder, and
    +// passes it to the NotificationManager
    +mNotificationManager.notify(id, builder.build());
    +
    +
  4. +
+ + +

通知に進捗状況を表示する

+

+ 通知には、進行中の処理の状況をユーザーに示すアニメーション表示の進捗インジケーターを表示できます。 +その処理にどれくらいの時間がかかるかと、ある時点でその処理がどれだけ完了しているかを見積もることができる場合、「確定(determinate)」タイプのインジケーター(プログレスバー)を使用します。 + +処理にかかる時間を見積もることができない場合は、「不確定(indeterminate)」タイプのインジケーター(アクティビティ インジケーター)を使用します。 + +

+

+ 進捗インジケーターは、{@link android.widget.ProgressBar} クラスのプラットフォーム実装で表示されます。 + +

+

+ Android 4.0 以降のプラットフォーム上で進捗インジケーターを使用するには、{@link android.support.v4.app.NotificationCompat.Builder#setProgress setProgress()} を呼び出します。 +それ以前のバージョンでは、{@link android.widget.ProgressBar} ビューを含むカスタム通知レイアウトを作成する必要があります。 + + +

+

+ 次のセクションでは、{@link android.support.v4.app.NotificationCompat.Builder#setProgress setProgress()} を使用して、通知に進捗状況を表示する方法を説明します。 + +

+ +

範囲固定の進捗インジケーターを表示する

+

+ 確定プログレスバーを表示するには、{@link android.support.v4.app.NotificationCompat.Builder#setProgress setProgress(max, progress, false)} を呼び出して通知にバーを追加してから、その通知を発行します。 + +処理の進行に合わせて、progress の値を増やし、通知を更新します。 +処理の最後には、progressmax と等しくなります。 +{@link android.support.v4.app.NotificationCompat.Builder#setProgress setProgress()} を呼び出す一般的な方法は、max に 100 を設定して、処理の「進捗度」の値である progress の値を増やすことです。 + + + +

+

+ 処理の完了時には、プログレスバーを表示したままにすることも、削除することもできます。いずれの場合でも、通知のテキストを更新して、処理が完了したことを示すことを忘れないでください。 + + プログレスバーを削除するには、{@link android.support.v4.app.NotificationCompat.Builder#setProgress setProgress(0, 0, false)} を呼び出します。 + +次に例を示します。 +

+
+...
+mNotifyManager =
+        (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+mBuilder = new NotificationCompat.Builder(this);
+mBuilder.setContentTitle("Picture Download")
+    .setContentText("Download in progress")
+    .setSmallIcon(R.drawable.ic_notification);
+// Start a lengthy operation in a background thread
+new Thread(
+    new Runnable() {
+        @Override
+        public void run() {
+            int incr;
+            // Do the "lengthy" operation 20 times
+            for (incr = 0; incr <= 100; incr+=5) {
+                    // Sets the progress indicator to a max value, the
+                    // current completion percentage, and "determinate"
+                    // state
+                    mBuilder.setProgress(100, incr, false);
+                    // Displays the progress bar for the first time.
+                    mNotifyManager.notify(0, mBuilder.build());
+                        // Sleeps the thread, simulating an operation
+                        // that takes time
+                        try {
+                            // Sleep for 5 seconds
+                            Thread.sleep(5*1000);
+                        } catch (InterruptedException e) {
+                            Log.d(TAG, "sleep failure");
+                        }
+            }
+            // When the loop is finished, updates the notification
+            mBuilder.setContentText("Download complete")
+            // Removes the progress bar
+                    .setProgress(0,0,false);
+            mNotifyManager.notify(ID, mBuilder.build());
+        }
+    }
+// Starts the thread by calling the run() method in its Runnable
+).start();
+
+ + +

進行中アクティビティ インジケーターを表示する

+

+ 不確定アクティビティ インジケーターを表示するには、{@link android.support.v4.app.NotificationCompat.Builder#setProgress setProgress(0, 0, true)}(最初の 2 つの引数は無視されます)を使用して通知に追加し、通知を発行します。 + +このインジケーターの外見は、アニメーションが常時表示されていること以外はプログレスバーと同じです。 + +

+

+ 処理の開始時に通知を発行します。通知を変更するまで、アニメーションは表示され続けます。 +処理が完了したら、{@link android.support.v4.app.NotificationCompat.Builder#setProgress setProgress(0, 0, false)} を呼び出し、次に通知を更新してアクティビティ インジケーターを削除します。 + + + この操作を行わないと、処理が完了してもアニメーションが表示され続けることになります。また、通知のテキストを更新して、処理が完了したことを示すことを忘れないでください。 + +

+

+ アクティビティ インジケーターの仕組みについては、上記のスニペットをご覧ください。次のコード行を探します。 +

+
+// Sets the progress indicator to a max value, the current completion
+// percentage, and "determinate" state
+mBuilder.setProgress(100, incr, false);
+// Issues the notification
+mNotifyManager.notify(0, mBuilder.build());
+
+

+ 見つけたコードを次のコードに置き換えます。 +

+
+ // Sets an activity indicator for an operation of indeterminate length
+mBuilder.setProgress(0, 0, true);
+// Issues the notification
+mNotifyManager.notify(0, mBuilder.build());
+
+ +

通知メタデータ

+ +

通知は、次の {@link android.support.v4.app.NotificationCompat.Builder} メソッドを使用して割り当てるメタデータによって、ソートされることがあります。 +

+ + + +
+ +

+ 図 3. ヘッドアップ通知を表示する全画面アクティビティ +

+
+ +

ヘッドアップ通知

+ +

Android 5.0(API レベル 21)では、端末がアクティブな場合(端末がロックされておらず、画面がオンになっている場合)に、小さなフローティング ウィンドウ(ヘッドアップ通知とも呼ばれます)に通知を表示することができます。 + +これらの通知は、ヘッドアップ通知がアクション ボタンも表示すること以外は、通知をコンパクトにしたものと同じように表示されます。 + +使用中のアプリから移動しなくても、ユーザーは、ヘッドアップ通知に反応したりヘッドアップ通知を閉じたりすることができます。 +

+ +

ヘッドアップ通知のトリガーとなる条件には、たとえば以下のようなものがあります。

+ + + +

ロック画面通知

+ +

Android 5.0(API レベル 21)では、通知をロック画面に表示できるようになりました。 +アプリは、この機能を利用してメディア再生コントロールやその他の一般的なアクションを提供できます。 +ユーザーは設定でロック画面に通知を表示するかどうか選ぶことができます。また、開発者も、アプリからの通知をロック画面に表示するかどうか指定できます。 +

+ +

可視性を設定する

+ +

アプリでは、保護されたロック画面に表示される通知の表示の詳細レベルをコントロールできます。 +{@link android.support.v4.app.NotificationCompat.Builder#setVisibility(int) setVisibility()} を呼び出し、次の値のいずれかを指定してください。 +

+ + + +

{@link android.support.v4.app.NotificationCompat#VISIBILITY_PRIVATE} を設定すると、特定の内容を非表示にした代替バージョンの通知コンテンツを提供できます。 +たとえば、SMS アプリで 3 件の新しいテキスト メッセージがあることを示す通知を表示する場合に、メッセージ コンテンツと送信者を非表示にできます。 + +この代替通知を提供するには、{@link android.support.v4.app.NotificationCompat.Builder} を使用してまず置換用の通知を作成し、 +プライベート通知オブジェクトを作成したら、その置換用の通知をプライベート通知オブジェクトに {@link android.support.v4.app.NotificationCompat.Builder#setPublicVersion(android.app.Notification) setPublicVersion()} メソッドを使用してアタッチしてください。 + + +

+ +

ロック画面でのメディア再生をコントロールする

+ +

Android 5.0(API レベル 21)では、{@link android.media.RemoteControlClient} を使用したメディア コントロールは、ロック画面に表示されません。このクラスは廃止済みです。 +代わりに、{@link android.app.Notification.MediaStyle} テンプレートと {@link android.app.Notification.Builder#addAction(android.app.Notification.Action) addAction()} メソッドを使用します。このメソッドは、アクションをクリックできるアイコンに変換します。 + + +

+ +

注: このテンプレートと {@link android.app.Notification.Builder#addAction(android.app.Notification.Action) addAction()} メソッドはサポート ライブラリには含まれません。そのため、これらの機能は Android 5.0 以降でのみ動作します。 + +

+ +

Android 5.0 でロック画面にメディア再生コントロールを表示するには、可視性を上記の{@link android.support.v4.app.NotificationCompat#VISIBILITY_PUBLIC} に設定します。 +その後、次のサンプルコードに従って、アクションを追加し {@link android.app.Notification.MediaStyle} テンプレートを設定します。 + +

+ +
+Notification notification = new Notification.Builder(context)
+    // Show controls on lock screen even when user hides sensitive content.
+    .setVisibility(Notification.VISIBILITY_PUBLIC)
+    .setSmallIcon(R.drawable.ic_stat_player)
+    // Add media control buttons that invoke intents in your media service
+    .addAction(R.drawable.ic_prev, "Previous", prevPendingIntent) // #0
+    .addAction(R.drawable.ic_pause, "Pause", pausePendingIntent)  // #1
+    .addAction(R.drawable.ic_next, "Next", nextPendingIntent)     // #2
+    // Apply the media style template
+    .setStyle(new Notification.MediaStyle()
+    .setShowActionsInCompactView(1 /* #1: pause button */)
+    .setMediaSession(mMediaSession.getSessionToken())
+    .setContentTitle("Wonderful music")
+    .setContentText("My Awesome Band")
+    .setLargeIcon(albumArtBitmap)
+    .build();
+
+ +

注: {@link android.media.RemoteControlClient} の廃止は、他の点でもメディアのコントロールに影響を与えています。 +新しい API でのメディア セッションの管理と再生のコントロールの詳細については、メディア再生コントロールをご覧ください。 + +

+ + + +

カスタム通知レイアウト

+

+ 通知フレームワークでは、カスタム通知レイアウトを定義することができます。カスタム通知レイアウトは、{@link android.widget.RemoteViews} オブジェクトに通知の外観を定義します。 + + カスタム レイアウトの通知は通常の通知と似ていますが、XML レイアウト ファイルに定義された {@link android.widget.RemoteViews} が使用されています。 + +

+

+ カスタム通知レイアウトで使用できる高さは、通知ビューによって異なります。標準ビュー レイアウトでは 64 dp までで、拡張ビュー レイアウトでは 256 dp までです。 + +

+

+ カスタム通知レイアウトを定義するには、まず、XML レイアウト ファイルをインフレートする {@link android.widget.RemoteViews} オブジェクトのインスタンスを作成します。 +次に、{@link android.support.v4.app.NotificationCompat.Builder#setContentTitle setContentTitle()} などのメソッドを呼び出す代わりに、{@link android.support.v4.app.NotificationCompat.Builder#setContent setContent()} を呼び出します。 + + +コンテンツの詳細をカスタム通知に設定するには、{@link android.widget.RemoteViews} のメソッドを使用してビューの子の値を設定します。 + + +

+
    +
  1. + 別ファイルに通知の XML レイアウトを作成します。ファイル名はどのような名前でもかまいませんが、拡張子は必ず .xml にします。 + +
  2. +
  3. + アプリで、{@link android.widget.RemoteViews} メソッドを使用して、通知のアイコンとテキストを定義します。 +{@link android.support.v4.app.NotificationCompat.Builder#setContent setContent()} を呼び出して、この {@link android.widget.RemoteViews} オブジェクトを {@link android.support.v4.app.NotificationCompat.Builder} にセットします。 + +{@link android.widget.RemoteViews} オブジェクトに背景 {@link android.graphics.drawable.Drawable} を設定することは避けてください。文字が読めなくなる場合があります。 + + +
  4. +
+

+ {@link android.widget.RemoteViews} クラスには、通知のレイアウトに {@link android.widget.Chronometer} や {@link android.widget.ProgressBar} を簡単に追加できるメソッドも含まれています。 + +通知のカスタム レイアウトの作成についての詳細は、{@link android.widget.RemoteViews} のリファレンスをご覧ください。 + +

+

+ 警告: カスタム通知レイアウトを使用する場合、そのカスタム通知レイアウトがさまざまな画面の向きと解像度で適切に表示されるかどうか、十分に注意してください。 +すべてのビュー レイアウトでさまざまな画面の向きと解像度への対応に注意する必要がありますが、通知ドロワーのスペースが限られているため、通知では特に注意する必要があります。 + +カスタム レイアウトは複雑にし過ぎないようにしてください。また、必ずさまざまな構成でテストするようにしてください。 + +

+ +

カスタム通知のテキストにスタイル リソースを使用する

+

+ カスタム通知のテキストには、必ずスタイル リソースを使用してください。通知の背景色は端末やバージョンによって異なりますが、スタイル リソースを使用することで、この問題に対処できます。 + +Android 2.3 以降では、標準の通知レイアウトのテキストのスタイルは、システムによって定義されています。 +Android 2.3 以降を対象とするアプリケーションで同じスタイルを使用する場合は、表示される背景でテキストを読むことができるかご確認ください。 + +

diff --git a/docs/html-intl/intl/ja/guide/topics/ui/overview.jd b/docs/html-intl/intl/ja/guide/topics/ui/overview.jd new file mode 100644 index 0000000000000000000000000000000000000000..08d93560cb4e0bb0cce656da5fdaafece6adce5b --- /dev/null +++ b/docs/html-intl/intl/ja/guide/topics/ui/overview.jd @@ -0,0 +1,71 @@ +page.title=UI の概要 +@jd:body + + +

Android アプリのすべてのユーザー インターフェース エレメントは、{@link android.view.View} と {@link android.view.ViewGroup} オブジェクトを使ってビルドされています。 +{@link android.view.View} は、ユーザーが相互操作できる、画面上で何かを描画するオブジェクトです。 +{@link android.view.ViewGroup} は、インターフェースのレイアウトを定義するために、その他の {@link android.view.View}(と {@link android.view.ViewGroup})オブジェクトを保持するオブジェクトです。 + +

+ +

Android では、共通の入力コントロール(ボタンやテキスト フィールドなど)とさまざまなレイアウトモデル(線形レイアウトや相対レイアウトなど)を提供する {@link android.view.View} と {@link android.view.ViewGroup} サブクラスの両方が提供されています。 + +

+ + +

ユーザー インターフェースのレイアウト

+ +

アプリの各コンポーネントのユーザー インターフェースは、図 1 のように、{@link +android.view.View} と {@link android.view.ViewGroup} オブジェクトの階層を使って定義されます。各ビューグループは、子ビューをまとめる非表示のコンテナであり、子ビューは UI の一部を描画する入力コントロールまたはその他のウィジェットです。この階層ツリーは、必要に応じて単純にまたは複雑にすることができますが、パフォーマンスの点では単純な階層ツリーが最適です。 + + + +

+ + +

図 1. UI レイアウトを定義するビュー階層の図。 +

+ +

レイアウトを宣言するには、コードで {@link android.view.View} オブジェクトのインスタンスを作成してツリーのビルドを始めることができますが、レイアウトを定義する最も簡単で効率的な方法は XML ファイルを使用することです。XML では HTML のように、人が見える構造でレイアウトが提供されます。 + +

+ +

ビューの XML 要素名は、それが表す Android クラスによって決まります。<TextView> 要素は UI で {@link android.widget.TextView} ウィジェットを、<LinearLayout> 要素は {@link android.widget.LinearLayout} ビューグループを作成します。 + + +

+ +

たとえば、テキストビューとボタンを含む単純な縦レイアウトは次のようになります。

+
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:layout_width="fill_parent" 
+              android:layout_height="fill_parent"
+              android:orientation="vertical" >
+    <TextView android:id="@+id/text"
+              android:layout_width="wrap_content"
+              android:layout_height="wrap_content"
+              android:text="I am a TextView" />
+    <Button android:id="@+id/button"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="I am a Button" />
+</LinearLayout>
+
+ +

アプリでレイアウト リソースを読み込むとき、Android はレイアウトの各ノードを初期化して、他の動作の定義、オブジェクト状態の照会、レイアウトの変更をするのに使用できるランタイム オブジェクトにします。 + +

+ +

UI レイアウト作成のガイドについては、XML レイアウトをご覧ください。 + + + +

ユーザー インターフェース コンポーネント

+ +

{@link android.view.View} と {@link android.view.ViewGroup} オブジェクトを使用してすべての UI をビルドする必要はありません。 +Android では、コンテンツの定義に必要な標準的な UI レイアウトを提供する複数のアプリ コンポーネントが提供されます。 +これらの各 UI コンポーネントには、それぞれのドキュメントで説明されている固有の API(アクションバーダイアログ状態通知など)があります。 +

+ + diff --git a/docs/html-intl/intl/ja/guide/topics/ui/settings.jd b/docs/html-intl/intl/ja/guide/topics/ui/settings.jd new file mode 100644 index 0000000000000000000000000000000000000000..9e6bb9d04169c0bc5b6d0de07450bee32f2e154e --- /dev/null +++ b/docs/html-intl/intl/ja/guide/topics/ui/settings.jd @@ -0,0 +1,1202 @@ +page.title=設定 +page.tags=preference,preferenceactivity,preferencefragment + +@jd:body + + + + + + + +

多くの場合、アプリケーションには、ユーザーがアプリの機能や動作を変更できる設定が含まれています。たとえば、一部のアプリでは、通知を有効にするかどうかや、アプリケーションがクラウドとデータを同期する頻度を、ユーザーが指定できます。 + +

+ +

アプリに設定機能を提供するには、Android の {@link android.preference.Preference} API を使用して、他の Android アプリ(システム設定を含む)の操作と整合性のあるインターフェースを構築する必要があります。 + +このドキュメントでは、{@link android.preference.Preference} API を使用して、アプリの設定機能を構築する方法について説明します。 +

+ +
+

設定の設計

+

設定の設計方法については、設定のデザインガイドをご覧ください。

+
+ + + +

図 1. Android SMS アプリの設定のスクリーンショット +{@link android.preference.Preference} で定義されたアイテムを選択すると、設定を変更するためのインターフェースが開きます。 +

+ + + + +

概要

+ +

設定は、{@link android.view.View} オブジェクトを使用してユーザー インターフェースを作成する方法ではなく、XML ファイルで宣言された {@link android.preference.Preference} クラスの各種のサブクラスを使用する方法で作成されます。 + +

+ +

1 つの {@link android.preference.Preference} オブジェクトが、1 つの設定の構成要素になります。 +各 {@link android.preference.Preference} はアイテムとしてリストに表示され、ユーザーが設定を変更するための適切な UI を提供します。 +たとえば、{@link android.preference.CheckBoxPreference} はチェックボックスを表示するリストアイテムを作成し、{@link android.preference.ListPreference} は、選択リスト付きのダイアログを開くアイテムを作成します。 + +

+ +

追加した各 {@link android.preference.Preference} は、アプリの設定のためのデフォルトの {@link android.content.SharedPreferences} ファイルに設定を保存するためにシステムが使用する対応するキーと値のペアを持ちます。 + +ユーザーが設定を変更する場合、システムが {@link android.content.SharedPreferences} ファイルの対応する値を更新します。 +関連する {@link android.content.SharedPreferences} ファイルを直接操作することが必要なのは、ユーザーの設定に基づいてアプリの動作を決定するために値を読み込むことが必要な場合のみです。 + +

+ +

各設定の {@link android.content.SharedPreferences} に保存される値のデータ型は、次のいずれかにすることができます。 +

+ + + +

アプリの設定の UI は {@link android.view.View} オブジェクトではなく {@link android.preference.Preference} で作成されているため、リストの設定を表示するには、専用の {@link android.app.Activity} サブクラスまたは {@link android.app.Fragment} サブクラスを使用する必要があります。 + + +

+ + + +

{@link android.preference.PreferenceActivity} と {@link android.preference.PreferenceFragment} のインスタンスの設定方法は、プリファレンス アクティビティを作成するプリファレンス フラグメントを使用するセクションをご覧ください。 + +

+ + +

プリファレンス

+ +

アプリの各設定は、{@link android.preference.Preference} クラスの個々のサブクラスに相当します。 +各サブクラスには、設定のタイトルやデフォルト値などを指定できる一連の核となるプロパティが含まれています。 +また、各サブクラスは、専用のプロパティとユーザー インターフェースを提供しています。 +たとえば、図 1. は、SMS アプリの設定のスクリーンショットです。 +設定画面の各リスト アイテムは、それぞれ異なる {@link android.preference.Preference} オブジェクトに基づいています。 +

+ +

以下は、最も一般的なプリファレンスの一部です。

+ +
+
{@link android.preference.CheckBoxPreference}
+
有効または無効にする設定のチェックボックス付きのアイテムを表示します。保存される値は、Boolean です(オンの場合、true)。 +
+ +
{@link android.preference.ListPreference}
+
ラジオボタンのリスト付きのダイアログを開きます。保存される値は、サポートされるデータ型(上記参照)であればどのデータ型にもできます。 +
+ +
{@link android.preference.EditTextPreference}
+
{@link android.widget.EditText} ウィジェット付きのダイアログを開きます。保存される値は、{@link java.lang.String} です。 +
+
+ +

その他のサブクラスと対応するプロパティについては、{@link android.preference.Preference} クラスをご覧ください。 +

+ +

もちろん、組み込みのクラスがすべてのニーズを満たすわけではなく、アプリケーションがより特殊な機能を必要とする可能性もあります。 +たとえば、プラットフォームは、現時点では、数字や日付を選択するための {@link android.preference.Preference} クラスを提供していません。 +そのため、独自の {@link android.preference.Preference} サブクラスを定義することが必要になる場合もあります。 +詳細については、カスタム プリファレンスを作成するセクションをご覧ください。

+ + + +

XML にプリファレンスを定義する

+ +

実行時に新しい {@link android.preference.Preference} オブジェクトのインスタンスを作成することもできますが、{@link android.preference.Preference} オブジェクトの階層で XML に設定のリストを定義する必要があります。 + +XML ファイルは更新が容易な簡単に読むことができる構造を持つため XML ファイルを使用して設定のコレクションを定義することをお勧めします。 +また、アプリの設定は通常、事前設定されていますが、設定のコレクションを実行時に変更することもできます。 +

+ +

各 {@link android.preference.Preference} サブクラスは、{@code <CheckBoxPreference>} などのクラス名と一致する XML 要素で定義できます。 +

+ +

この XML ファイルは、{@code res/xml/} ディレクトリに保存する必要があります。この XML ファイルには好きな名前を付けることができますが、一般的には、{@code preferences.xml} という名前が使用されています。 +階層の分岐(この分岐がそれ自身の設定のリストを開きます)が {@link android.preference.PreferenceScreen} のネストされたインスタンスを使用して宣言されているため、必要なファイルは通常 1 ファイルのみです。 + +

+ +

注: 複数ペイン レイアウトの設定を作成する場合は、フラグメントごとに別々の XML ファイルが必要です。 +

+ +

XML ファイルのルートノードは、{@link android.preference.PreferenceScreen <PreferenceScreen>} 要素にする必要があります。 +この要素内に、各 {@link android.preference.Preference} を追加します。 +{@link android.preference.PreferenceScreen <PreferenceScreen>} 要素内に追加したそれぞれの子は、設定のリストで 1 つのアイテムとして表示されます。 + +

+ +

次に例を示します。

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <CheckBoxPreference
+        android:key="pref_sync"
+        android:title="@string/pref_sync"
+        android:summary="@string/pref_sync_summ"
+        android:defaultValue="true" />
+    <ListPreference
+        android:dependency="pref_sync"
+        android:key="pref_syncConnectionType"
+        android:title="@string/pref_syncConnectionType"
+        android:dialogTitle="@string/pref_syncConnectionType"
+        android:entries="@array/pref_syncConnectionTypes_entries"
+        android:entryValues="@array/pref_syncConnectionTypes_values"
+        android:defaultValue="@string/pref_syncConnectionTypes_default" />
+</PreferenceScreen>
+
+ +

この例には、{@link android.preference.CheckBoxPreference} と {@link android.preference.ListPreference} が含まれています。 +どちらのアイテムにも次の 3 つの属性が含まれています。

+ +
+
{@code android:key}
+
この属性は、データ値を保持するプリファレンスで必要です。設定の値を {@link android.content.SharedPreferences} に保存するときにシステムが使用する一意のキー(文字列)を指定します。 + + +

プリファレンスが {@link android.preference.PreferenceCategory} または{@link android.preference.PreferenceScreen} の場合、またはプリファレンスが {@link android.content.Intent} の呼び出しを指定している場合({@code <intent>} 要素を使用)、または {@link android.app.Fragment} の表示を指定している場合({@code android:fragment} 属性を使用)のみ、インスタンスでこの属性は必要ありません。 + + +

+
+
{@code android:title}
+
この属性は、ユーザーに表示される設定の名前です。
+
{@code android:defaultValue}
+
この属性は、システムが {@link android.content.SharedPreferences} ファイルに設定する必要がある初期値を指定します。 +すべての設定のデフォルト値を指定する必要があります。 +
+
+ +

その他のサポートされている属性については、{@link android.preference.Preference}(と対応するサブクラス)のドキュメントをご覧ください。 +

+ + +
+ +

図 2. カテゴリを設定しタイトルを付ける +
1.カテゴリは、{@link android.preference.PreferenceCategory <PreferenceCategory>} 要素で指定します。 +
2.タイトルは、{@code android:title} 属性で指定します。 +

+
+ + +

設定のリストが 10 アイテムを超える場合は、タイトルを追加して設定のグループを定義するか、それらのグループを別の画面に表示することをお勧めします。 + +詳細については、次のセクションで説明します。

+ + +

設定グループを作成する

+ +

10 以上の設定のリストがある場合、ユーザーが目を通して把握し処理することが難しくなる場合があります。 +この問題を解決するには、設定の一部またはすべてをグループに分割し、1 つの長いリストを複数の短いリストに変えます。 + +関連設定のグループは、次の 2 つの方法のいずれかで表示できます。

+ + + +

これらのグループ化方法の 1 つまたは両方を利用して、アプリの設定を整理できます。使用する方法と設定の分割方法を決定する際は、Android Design のSettings ガイドのガイドラインに従ってください。 + +

+ + +

タイトルを使用する

+ +

設定のグループの間に見出しを入れる場合(図 2. 参照)、{@link android.preference.Preference} オブジェクトをグループごとに 1 つの {@link android.preference.PreferenceCategory} 内にセットしてください。 + +

+ +

次に例を示します。

+ +
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <PreferenceCategory 
+        android:title="@string/pref_sms_storage_title"
+        android:key="pref_key_storage_settings">
+        <CheckBoxPreference
+            android:key="pref_key_auto_delete"
+            android:summary="@string/pref_summary_auto_delete"
+            android:title="@string/pref_title_auto_delete"
+            android:defaultValue="false"... />
+        <Preference 
+            android:key="pref_key_sms_delete_limit"
+            android:dependency="pref_key_auto_delete"
+            android:summary="@string/pref_summary_delete_limit"
+            android:title="@string/pref_title_sms_delete"... />
+        <Preference 
+            android:key="pref_key_mms_delete_limit"
+            android:dependency="pref_key_auto_delete"
+            android:summary="@string/pref_summary_delete_limit"
+            android:title="@string/pref_title_mms_delete" ... />
+    </PreferenceCategory>
+    ...
+</PreferenceScreen>
+
+ + +

サブ画面を使用する

+ +

設定のグループをサブ画面に配置する場合(図 3. 参照)、{@link android.preference.Preference} オブジェクトのグループを {@link android.preference.PreferenceScreen} 内にセットしてください。 + +

+ + +

図 3. 子画面を設定する。{@code <PreferenceScreen>} は、選択されると、ネストされた設定を表示するための個別のリストを開くアイテムを作成します。 + +

+ +

次に例を示します。

+ +
+<PreferenceScreen  xmlns:android="http://schemas.android.com/apk/res/android">
+    <!-- opens a subscreen of settings -->
+    <PreferenceScreen
+        android:key="button_voicemail_category_key"
+        android:title="@string/voicemail"
+        android:persistent="false">
+        <ListPreference
+            android:key="button_voicemail_provider_key"
+            android:title="@string/voicemail_provider" ... />
+        <!-- opens another nested subscreen -->
+        <PreferenceScreen
+            android:key="button_voicemail_setting_key"
+            android:title="@string/voicemail_settings"
+            android:persistent="false">
+            ...
+        </PreferenceScreen>
+        <RingtonePreference
+            android:key="button_voicemail_ringtone_key"
+            android:title="@string/voicemail_ringtone_title"
+            android:ringtoneType="notification" ... />
+        ...
+    </PreferenceScreen>
+    ...
+</PreferenceScreen>
+
+ + +

インテントを使用する

+ +

設定画面ではなく、ウェブページを表示するためのウェブブラウザなどの別のアクティビティを開くプリファレンス アイテムが必要になることもあります。 +ユーザーがプリファレンス アイテムを選択したときに {@link android.content.Intent} が呼び出されるようにするには、対応する {@code <Preference>} 要素の子として {@code <intent>} 要素を追加します。 + +

+ +

たとえば、次の方法で、プリファレンス アイテムを使用してウェブページを開くことができます。

+ +
+<Preference android:title="@string/prefs_web_page" >
+    <intent android:action="android.intent.action.VIEW"
+            android:data="http://www.example.com" />
+</Preference>
+
+ +

次の属性を使用して、明示的なインテントと黙示的なインテントの両方を作成できます。

+ +
+
{@code android:action}
+
{@link android.content.Intent#setAction setAction()} メソッドで割り当てるアクション。 +
+
{@code android:data}
+
{@link android.content.Intent#setData setData()} メソッドで割り当てるデータ。
+
{@code android:mimeType}
+
{@link android.content.Intent#setType setType()} メソッドで割り当てる MIME タイプ。 +
+
{@code android:targetClass}
+
{@link android.content.Intent#setComponent setComponent()} メソッドでのコンポーネント名のクラス部分。 +
+
{@code android:targetPackage}
+
{@link android.content.Intent#setComponent setComponent()} メソッドでのコンポーネント名のパッケージ部分。 +
+
+ + + +

プリファレンス アクティビティを作成する

+ +

アクティビティに設定を表示するには、{@link android.preference.PreferenceActivity} クラスを継承します。 +このクラスは、{@link android.preference.Preference} オブジェクトの階層に基づいて設定のリストを表示する従来の {@link android.app.Activity} クラスを継承したものです。 + +{@link android.preference.PreferenceActivity} は、ユーザーが変更を行ったときに、各 {@link android.preference.Preference} に対応する設定を自動的に保存します。 + +

+ +

注: Android 3.0 以降向けにアプリケーションを開発する場合は、代わりに {@link android.preference.PreferenceFragment} を使用する必要があります。 +プリファレンス フラグメントの使用についての詳細は、次のセグメントをご覧ください。 +

+ +

注意する必要があるのは、{@link android.preference.PreferenceActivity#onCreate onCreate()} のコールバック時に、ビューのレイアウトをロードしてはならないことを忘れないでください。 +代わりに {@link android.preference.PreferenceActivity#addPreferencesFromResource addPreferencesFromResource()} を呼び出し、XML ファイルで宣言済みのプリファレンスをアクティビティに追加する必要があります。 + +以下は、{@link android.preference.PreferenceActivity} を機能させるために最小限必要なコードです。 +

+ +
+public class SettingsActivity extends PreferenceActivity {
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        addPreferencesFromResource(R.xml.preferences);
+    }
+}
+
+ +

ユーザーがプリファレンスを変更するとすぐに、ユーザーの設定のチェックが必要なときに他のアプリケーション コンポーネントが読み取ることができるデフォルトの {@link android.content.SharedPreferences} ファイルにシステムによって変更が保存されるため、実際一部のアプリではこのコードで十分です。 + +ただし、多くのアプリでは、プリファレンスに発生する変化をリッスンするために、もう少し多くのコードが必要になります。{@link android.content.SharedPreferences} ファイルの変更のリッスンについての詳細は、プリファレンスの読み取りについてのセクションをご覧ください。 + + +

+ + + + +

プリファレンス フラグメントを使用する

+ +

Android 3.0(API レベル 11)以降向けに開発を行っている場合は、{@link android.preference.PreferenceFragment} を使用して {@link android.preference.Preference} オブジェクトのリストを表示する必要があります。 + +{@link android.preference.PreferenceFragment} はどのアクティビティにでも追加できます — {@link android.preference.PreferenceActivity} を使用する必要はありません。 +

+ +

作成するアクティビティの種類にかかわらず、フラグメントを使用すると、アクティビティを単体で使用する場合と比べて、柔軟なアーキテクチャを持つアプリケーションを作成できます。 + +そのため、可能な限り {@link android.preference.PreferenceActivity} ではなく、{@link android.preference.PreferenceFragment} を使用して設定の表示を管理することをお勧めします。 + +

+ +

{@link android.preference.PreferenceFragment} の実装は、{@link android.preference.PreferenceFragment#onCreate onCreate()} メソッドを {@link android.preference.PreferenceFragment#addPreferencesFromResource addPreferencesFromResource()} を使用してプリファレンス ファイルをロードするように定義するだけと簡単です。 + + +次に例を示します。

+ +
+public static class SettingsFragment extends PreferenceFragment {
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        // Load the preferences from an XML resource
+        addPreferencesFromResource(R.xml.preferences);
+    }
+    ...
+}
+
+ +

他の {@link android.app.Fragment} と同様に、このフラグメントは {@link android.app.Activity} に追加できます。 +次に例を示します。

+ +
+public class SettingsActivity extends Activity {
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        // Display the fragment as the main content.
+        getFragmentManager().beginTransaction()
+                .replace(android.R.id.content, new SettingsFragment())
+                .commit();
+    }
+}
+
+ +

注: {@link android.preference.PreferenceFragment} は、独自の {@link android.content.Context} オブジェクトを持ちません。 +{@link android.content.Context} オブジェクトが必要な場合は、{@link android.app.Fragment#getActivity()} を呼び出すことができます。 +ただし、{@link android.app.Fragment#getActivity()} はフラグメントがアクティビティにアタッチされている場合にのみ呼び出すようにしてください。 +フラグメントがまだアタッチされていない場合や、ライフサイクルの終了時にデタッチされた場合は、{@link android.app.Fragment#getActivity()} は null を返します。 + +

+ + +

デフォルト値を設定する

+ +

作成するプリファレンスは、多くの場合、アプリケーションにとって重要ないくつかの動作を定義します。そのため、ユーザーが最初にプリケーションを開いたときに、各 {@link android.preference.Preference} のデフォルト値で、関連する {@link android.content.SharedPreferences} ファイルを初期化する必要があります。 + + +

+ +

まず、{@code android:defaultValue} 属性を使用して、XML ファイルの各 {@link android.preference.Preference} オブジェクトにデフォルト値を指定してください。 + +指定する値は、対応する {@link android.preference.Preference} オブジェクトで使用できるデータ型であればどのようなデータ型でもかまいません。 +次に例を示します。 +

+ +
+<!-- default value is a boolean -->
+<CheckBoxPreference
+    android:defaultValue="true"
+    ... />
+
+<!-- default value is a string -->
+<ListPreference
+    android:defaultValue="@string/pref_syncConnectionTypes_default"
+    ... />
+
+ +

次に、アプリケーションのメイン アクティビティ、およびユーザーがアプリケーションを最初に開いたときに表示されるその他のアクティビティの {@link android.app.Activity#onCreate onCreate()} メソッドから、{@link android.preference.PreferenceManager#setDefaultValues setDefaultValues()} を呼び出します。 + + +

+ +
+PreferenceManager.setDefaultValues(this, R.xml.advanced_preferences, false);
+
+ +

{@link android.app.Activity#onCreate onCreate()} 時に、このメソッドを呼び出すと、アプリケーションがデフォルト設定で適切に初期化されます。アプリケーションには、動作(セルラー ネットワーク上でデータをダウンロードするかどうかなど)を決定するために、デフォルト設定を読み込むことが必要な場合があります。 + + +

+ +

このメソッドは次の 3 つの引数を取ります。

+ + +

この 3 番目の引数を false に設定している限り、アクティビティが開始するたびに、ユーザーが保存したプリファレンスをデフォルト値にリセットして上書きすることなく、安全にこのメソッドを呼び出すことができます。 + +この引数を true に設定した場合は、以前の値がすべてデフォルト値で上書きされます。 +

+ + + +

プリファレンス ヘッダーを使用する

+ +

まれに、最初の画面がサブ画面のリストのみを表示するように設定を設計した方がよい場合もあります(図 4. と図. 5 のシステム設定アプリなど)。 + +このような設計を Android 3.0 以降で開発する場合、ネストした {@link android.preference.PreferenceScreen} 要素でサブ画面を作成するのではなく、Android 3.0 の新しい「ヘッダー」機能を使用する必要があります。 + +

+ +

ヘッダーを使用して設定を作成するには、次の準備が必要です。

+
    +
  1. 設定をグループごとに {@link android.preference.PreferenceFragment} の別々のインスタンスに分けます。 +この場合、設定のグループごとに、個別の XML ファイルが必要になります。 +
  2. +
  3. 各設定グループをリストアップし対応する設定のリストを含むのがどのフラグメントかを宣言する XML ヘッダー ファイルを作成します。 +
  4. +
  5. {@link android.preference.PreferenceActivity} クラスを継承し、設定をホストします。
  6. +
  7. {@link android.preference.PreferenceActivity#onBuildHeaders onBuildHeaders()} コールバックを実装して、ヘッダー ファイルを指定します。 + +
  8. +
+ +

この設計を使用する大きなメリットは、大きな画面で実行する場合に、{@link android.preference.PreferenceActivity} によって自動的に図. 4 の 2 ペイン レイアウトで表示されることです。 +

+ +

アプリケーションが 3.0 より前のバージョンの Android をサポートしている場合でも、3.0 より前の端末で従来の多画面階層をサポートしながら、{@link android.preference.PreferenceFragment} を使用して 3.0 以降の端末で 2 ペイン表示を行うことができます(詳細については、旧バージョンでのプリファレンス ヘッダーのサポートについてのセクションをご覧ください)。 + + + +

+ + +

図 4.ヘッダー付きの 2 ペイン レイアウト。
1.ヘッダーは、XML ヘッダー ファイルで定義します。 +
2.設定の各グループは、XML ヘッダー ファイルの {@code <header>} 要素で指定された {@link android.preference.PreferenceFragment} ファイルで定義します。 + +

+ + +

図 5. ヘッダーが設定されたモバイル端末。アイテムが選択されると、対応する {@link android.preference.PreferenceFragment} がヘッダーを置き換えます。 + +

+ + +

ヘッダー ファイルを作成する

+ +

ヘッダーのリストの設定の各グループは、ルート {@code <preference-headers>} 要素内の単独の {@code <header>} 要素によって指定されます。 +次に例を示します。

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<preference-headers xmlns:android="http://schemas.android.com/apk/res/android">
+    <header 
+        android:fragment="com.example.prefs.SettingsActivity$SettingsFragmentOne"
+        android:title="@string/prefs_category_one"
+        android:summary="@string/prefs_summ_category_one" />
+    <header 
+        android:fragment="com.example.prefs.SettingsActivity$SettingsFragmentTwo"
+        android:title="@string/prefs_category_two"
+        android:summary="@string/prefs_summ_category_two" >
+        <!-- key/value pairs can be included as arguments for the fragment. -->
+        <extra android:name="someKey" android:value="someHeaderValue" />
+    </header>
+</preference-headers>
+
+ +

{@code android:fragment} 属性を使用して、各ヘッダーは、ユーザーがヘッダーを選択したときに開く {@link android.preference.PreferenceFragment} のインスタンスを宣言します。 +

+ +

{@code <extras>} 要素を使用すると、キーと値のペアを {@link android.os.Bundle} のフラグメントに渡すことができます。 +{@link android.app.Fragment#getArguments()} を呼び出すことで、フラグメントは引数を取得できます。 +さまざまな理由でフラグメントに引数を渡す場合がありますが、各グループの {@link android.preference.PreferenceFragment} の同じサブクラスを再利用し、引数を使用してフラグメントがロードするプリファレンス XML ファイルを指定することは、効果的な引数の使い方の 1 つです。 + + +

+ +

たとえば、次のフラグメントは、各ヘッダーが {@code "settings"} キーを使用して {@code <extra>} 引数を定義している場合、複数の設定グループで再利用できます。 +

+ +
+public static class SettingsFragment extends PreferenceFragment {
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        String settings = getArguments().getString("settings");
+        if ("notifications".equals(settings)) {
+            addPreferencesFromResource(R.xml.settings_wifi);
+        } else if ("sync".equals(settings)) {
+            addPreferencesFromResource(R.xml.settings_sync);
+        }
+    }
+}
+
+ + + +

ヘッダーを表示する

+ +

プリファレンス ヘッダーを表示するには、{@link android.preference.PreferenceActivity#onBuildHeaders onBuildHeaders()} コールバックメソッドを実装し、{@link android.preference.PreferenceActivity#loadHeadersFromResource loadHeadersFromResource()} を呼び出す必要があります。 + + +次に例を示します。

+ +
+public class SettingsActivity extends PreferenceActivity {
+    @Override
+    public void onBuildHeaders(List<Header> target) {
+        loadHeadersFromResource(R.xml.preference_headers, target);
+    }
+}
+
+ +

ユーザーがヘッダーのリストからアイテムを選択した場合、システムが対応する {@link android.preference.PreferenceFragment} を開きます。 +

+ +

注: プリファレンス ヘッダーを使用する場合、アクティビティで必要なタスクはヘッダーのロードのみであるため、{@link android.preference.PreferenceActivity} のサブクラスが {@link android.preference.PreferenceActivity#onCreate onCreate()} メソッドを実装する必要はありません。 + + +

+ + +

旧バージョンでプリファレンス ヘッダーをサポートする

+ +

アプリケーションが Android 3.0 よりも前のバージョンをサポートしている場合でも、3.0 以降で実行するときに、ヘッダーを使用して 2 ペイン レイアウトを提供できます。 +必要なことは、3.0 よりも前のバージョンの Android で使用するために、ヘッダー アイテムのように動作する基本的な {@link android.preference.Preference <Preference>} 要素を使用する追加のプリファレンス XML ファイルを作成することだけです。 + + +

+ +

新しい {@link android.preference.PreferenceScreen} を開く代わりに、各 {@link android.preference.Preference <Preference>} 要素が、ロードするプリファレンス XML ファイルが指定されている {@link android.preference.PreferenceActivity} に {@link android.content.Intent} を送ります。 + + +

+ +

たとえば、以下は Android 3.0 以降で使用されるプリファレンス ヘッダーの XML ファイル({@code res/xml/preference_headers.xml})です。 +

+ +
+<preference-headers xmlns:android="http://schemas.android.com/apk/res/android">
+    <header 
+        android:fragment="com.example.prefs.SettingsFragmentOne"
+        android:title="@string/prefs_category_one"
+        android:summary="@string/prefs_summ_category_one" />
+    <header 
+        android:fragment="com.example.prefs.SettingsFragmentTwo"
+        android:title="@string/prefs_category_two"
+        android:summary="@string/prefs_summ_category_two" />
+</preference-headers>
+
+ +

また、以下は Android 3.0 よりも前のバージョンに同じヘッダーを提供するプリファレンス ファイル({@code res/xml/preference_headers_legacy.xml})です。 +

+ +
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <Preference 
+        android:title="@string/prefs_category_one"
+        android:summary="@string/prefs_summ_category_one"  >
+        <intent 
+            android:targetPackage="com.example.prefs"
+            android:targetClass="com.example.prefs.SettingsActivity"
+            android:action="com.example.prefs.PREFS_ONE" />
+    </Preference>
+    <Preference 
+        android:title="@string/prefs_category_two"
+        android:summary="@string/prefs_summ_category_two" >
+        <intent 
+            android:targetPackage="com.example.prefs"
+            android:targetClass="com.example.prefs.SettingsActivity"
+            android:action="com.example.prefs.PREFS_TWO" />
+    </Preference>
+</PreferenceScreen>
+
+ +

{@code <preference-headers>} のサポートは Android 3.0 で追加されたため、Android 3.0 以降で実行されている場合のみ、システムは {@link android.preference.PreferenceActivity} の {@link android.preference.PreferenceActivity#onBuildHeaders onBuildHeaders()} を呼び出します。 + +「レガシー」ヘッダー ファイル({@code preference_headers_legacy.xml})をロードするには、Android のバージョンを確認し、Android 3.0({@link android.os.Build.VERSION_CODES#HONEYCOMB})よりも前のバージョンの場合、{@link android.preference.PreferenceActivity#addPreferencesFromResource addPreferencesFromResource()} を呼び出す必要があります。 + + + + +次に例を示します。

+ +
+@Override
+public void onCreate(Bundle savedInstanceState) {
+    super.onCreate(savedInstanceState);
+    ...
+
+    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
+        // Load the legacy preferences headers
+        addPreferencesFromResource(R.xml.preference_headers_legacy);
+    }
+}
+
+// Called only on Honeycomb and later
+@Override
+public void onBuildHeaders(List<Header> target) {
+   loadHeadersFromResource(R.xml.preference_headers, target);
+}
+
+ +

残りの手順は、アクティビティに渡される {@link android.content.Intent} を処理して、ロードするプリファレンス ファイルを指定するだけです。 +それには、インテントのアクションを取得し、プリファレンス XML の {@code <intent>} タグで使用した既知のアクション文字列と比較します。 +

+ +
+final static String ACTION_PREFS_ONE = "com.example.prefs.PREFS_ONE";
+...
+
+@Override
+public void onCreate(Bundle savedInstanceState) {
+    super.onCreate(savedInstanceState);
+
+    String action = getIntent().getAction();
+    if (action != null && action.equals(ACTION_PREFS_ONE)) {
+        addPreferencesFromResource(R.xml.preferences);
+    }
+    ...
+
+    else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
+        // Load the legacy preferences headers
+        addPreferencesFromResource(R.xml.preference_headers_legacy);
+    }
+}
+
+ +

{@link android.preference.PreferenceActivity#addPreferencesFromResource addPreferencesFromResource()} の連続呼び出しは、すべてのプリファレンスを 1 つのリストにスタックすることに注意してください。そのため、else if ステートメントを使用して条件を適切に記述して、1 度のみ呼び出されるようにしてください。 + + +

+ + + + + +

プリファレンスを読み込む

+ +

デフォルトでは、アプリのプリファレンスは、静的メソッド {@link android.preference.PreferenceManager#getDefaultSharedPreferences PreferenceManager.getDefaultSharedPreferences()} を呼び出すとアプリケーション内のどこからでもアクセス可能なファイルに保存されます。 + + +このメソッドは、{@link android.preference.PreferenceActivity} で使用される {@link android.preference.Preference} オブジェクトに関連するすべてのキーと値のペアを含む {@link android.content.SharedPreferences} を返します。 + + +

+ +

たとえば、次の方法で、アプリケーションのその他のアクティビティからプリファレンスの値の 1 つを読み込むことができます。 +

+ +
+SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this);
+String syncConnPref = sharedPref.getString(SettingsActivity.KEY_PREF_SYNC_CONN, "");
+
+ + + +

プリファレンスの変化をリッスンする

+ +

さまざまな理由で、ユーザーがプリファレンスを変更してすぐに通知を受け取ることが必要になることがあります。 +プリファレンスの 1 つに変化が発生したときにコールバックを受け取るには、 {@link android.content.SharedPreferences.OnSharedPreferenceChangeListener SharedPreference.OnSharedPreferenceChangeListener} インターフェースを実装し、{@link android.content.SharedPreferences#registerOnSharedPreferenceChangeListener registerOnSharedPreferenceChangeListener()} を呼び出して {@link android.content.SharedPreferences} オブジェクトにリスナを登録します。 + + + + +

+ +

このインターフェースには、コールバック メソッドが {@link android.content.SharedPreferences.OnSharedPreferenceChangeListener#onSharedPreferenceChanged onSharedPreferenceChanged()} 1 つのみしか含まれておらず、アクティビティの一部として簡単に実装できます。 + + +次に例を示します。

+ +
+public class SettingsActivity extends PreferenceActivity
+                              implements OnSharedPreferenceChangeListener {
+    public static final String KEY_PREF_SYNC_CONN = "pref_syncConnectionType";
+    ...
+
+    public void onSharedPreferenceChanged(SharedPreferences sharedPreferences,
+        String key) {
+        if (key.equals(KEY_PREF_SYNC_CONN)) {
+            Preference connectionPref = findPreference(key);
+            // Set summary to be the user-description for the selected value
+            connectionPref.setSummary(sharedPreferences.getString(key, ""));
+        }
+    }
+}
+
+ +

この例では、メソッドが、変更された設定が、既知のプリファレンス キーの設定かどうかチェックしています。メソッドは {@link android.preference.PreferenceActivity#findPreference findPreference()} を呼び出して、変更された {@link android.preference.Preference} オブジェクトを取得しています。これにより、メソッドは、ユーザー選択の説明として表示されるアイテムの概要を変更できます。 + + +つまり、その設定が {@link android.preference.ListPreference} またはその他の複数選択可の設定の場合、設定が変更されたときに {@link android.preference.Preference#setSummary setSummary()} を呼び出して現在のステータス(図 5. のスリープ設定など)を表示する必要があります。 + + +

+ +

注: Android Design の Settings に説明されているように、ユーザーがプリファレンスを変更するごとに、{@link android.preference.ListPreference} の概要を更新して最新の設定を表示することをお勧めします。 + +

+ +

アクティビティでの適切なライフサイクル管理のために、{@link android.app.Activity#onResume} と {@link android.app.Activity#onPause} のそれぞれのコールバック時に、{@link android.content.SharedPreferences.OnSharedPreferenceChangeListener} の登録と登録解除を行うことをお勧めします。 + +

+ +
+@Override
+protected void onResume() {
+    super.onResume();
+    getPreferenceScreen().getSharedPreferences()
+            .registerOnSharedPreferenceChangeListener(this);
+}
+
+@Override
+protected void onPause() {
+    super.onPause();
+    getPreferenceScreen().getSharedPreferences()
+            .unregisterOnSharedPreferenceChangeListener(this);
+}
+
+ +

警告: {@link android.content.SharedPreferences#registerOnSharedPreferenceChangeListener registerOnSharedPreferenceChangeListener()} を呼び出す場合、現時点では、プリファレンス マネージャーにはリスナへの強い参照を格納できません。 + + +リスナへの強い参照を格納しない場合は、リスナがガベージ コレクションの影響を受けやすくなります。 +リスナが必要な間存在し続けるオブジェクトのインスタンス データ内に、リスナへの参照を保持することをお勧めします。 + +

+ +

たとえば、以下のコードでは、呼び出し元は、リスナへの参照を保持していません。 +結果として、リスナはガベージ コレクションの影響を受けることになり、その後のいずれかの時点でエラーになります。 +

+ +
+prefs.registerOnSharedPreferenceChangeListener(
+  // Bad! The listener is subject to garbage collection!
+  new SharedPreferences.OnSharedPreferenceChangeListener() {
+  public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
+    // listener implementation
+  }
+});
+
+ +

そのため、リスナが必要な間存在し続けるオブジェクトのインスタンス データ フィールドに、リスナへの参照を格納してください。 +

+ +
+SharedPreferences.OnSharedPreferenceChangeListener listener =
+    new SharedPreferences.OnSharedPreferenceChangeListener() {
+  public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
+    // listener implementation
+  }
+};
+prefs.registerOnSharedPreferenceChangeListener(listener);
+
+ +

ネットワークの使用を管理する

+ + +

Android 4.0 以降では、フォアグラウンドとバックグラウンドでのアプリケーションのネットワーク データの使用量を、システムの設定アプリケーションでユーザーが確認できます。 +また、ユーザーは、個々のアプリのバックグラウンド データの使用を無効にできます。 +アプリのバックグラウンドからのデータへのアクセスをユーザーが無効にすることを防ぐには、データ接続を効率的に使用することと、アプリケーションの設定でアプリのデータの利用方法をユーザーが調整できるようにすることが必要です。 + +

+ +

たとえば、データを同期する頻度や、Wi-Fi 上でのみアップロードやダウンロードを実行するようにするかどうか、ローミング時にデータを使用するかどうかなどを、ユーザーが設定できるようにすることができます。 +これらをユーザーが管理できる場合、システム設定に設定した限度にデータ使用量が近づいたときに、データへのアプリのアクセスをユーザーが無効にする可能性は減少します。ユーザーはアプリのアクセスを無効にする代わりに、アプリのデータの使用量を厳密に管理できます。 + + +

+ +

アプリのデータ使用を管理するために {@link android.preference.PreferenceActivity} に必要なプリファレンスを追加したら、マニフェスト ファイルに {@link android.content.Intent#ACTION_MANAGE_NETWORK_USAGE} のインテント フィルタを追加する必要があります。 + +次に例を示します。

+ +
+<activity android:name="SettingsActivity" ... >
+    <intent-filter>
+       <action android:name="android.intent.action.MANAGE_NETWORK_USAGE" />
+       <category android:name="android.intent.category.DEFAULT" />
+    </intent-filter>
+</activity>
+
+ +

このインテント フィルタは、これがアプリケーションのデータ使用をコントロールしているアクティビティであるとシステムに示します。 +これにより、ユーザーがシステム設定アプリからアプリのデータ使用量を調べるときに、[View application settings] ボタンを使用できるようになります。このボタンを押すと、{@link android.preference.PreferenceActivity} が開始し、アプリのデータ使用量を調整できるようになります。 + + +

+ + + + + + + +

カスタム プリファレンスを作成する

+ +

Android フレームワークには、異なる設定タイプの UI を作成できるさまざまな {@link android.preference.Preference} サブクラスが含まれています。ただし、番号ピッカーや日付ピッカーなど、組み込みソリューションがない場合に必要な設定が存在する可能性もあります。 + + +このような場合、{@link android.preference.Preference} クラスまたは他のサブクラスの 1 つを継承して、カスタム プリファレンスを作成する必要があります。 +

+ +

{@link android.preference.Preference} クラスを継承する場合、次のことが必要です。 +

+ + + +

以下の各セクションでは、上記の各事項を実現する方法を説明します。

+ + + +

ユーザー インターフェースを指定する

+ +

{@link android.preference.Preference} クラスを直接継承する場合、{@link android.preference.Preference#onClick()} を実装して、ユーザーがアイテムを選択したときに発生するアクションを定義する必要があります。 + +ただし、大部分のカスタム設定では、手順が簡単な {@link android.preference.DialogPreference} を継承してダイアログを表示する方法が利用されています。 +{@link android.preference.DialogPreference} を継承する場合、クラス コンストラクタで {@link android.preference.DialogPreference#setDialogLayoutResource setDialogLayoutResourcs()} を呼び出してダイアログのレイアウトを指定する必要があります。 + + +

+ +

たとえば、以下のカスタム {@link android.preference.DialogPreference} のコンストラクタでは、レイアウトを宣言しデフォルトのポジティブとネガティブのダイアログ ボタンのテキストを指定しています。 + +

+ +
+public class NumberPickerPreference extends DialogPreference {
+    public NumberPickerPreference(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        
+        setDialogLayoutResource(R.layout.numberpicker_dialog);
+        setPositiveButtonText(android.R.string.ok);
+        setNegativeButtonText(android.R.string.cancel);
+        
+        setDialogIcon(null);
+    }
+    ...
+}
+
+ + + +

設定の値を保存する

+ +

設定の値が整数の場合、またはブール値を保存する{@link android.preference.Preference#persistBoolean persistBoolean()} の場合、{@link android.preference.Preference#persistInt persistInt()} など、{@link android.preference.Preference} クラスの {@code persist*()} メソッドのいずれかを呼び出すことで、設定の値をいつでも保存できます。 + + +

+ +

注: 各 {@link android.preference.Preference} には、1 つのデータ型のデータのみ保存できます。そのため、カスタム {@link android.preference.Preference} で使用されているデータ型に対応する {@code persist*()} メソッドを使用する必要があります。 + +

+ +

設定をいつ保存したらよいかは、継承する {@link android.preference.Preference} クラスによって異なります。 +{@link android.preference.DialogPreference} を継承する場合、ポジティブな結果でダイアログが閉じられたとき(ユーザーが [OK] ボタンを選択したとき)のみ、値を保存する必要があります。 + +

+ +

{@link android.preference.DialogPreference} が閉じられたときには、システムが {@link android.preference.DialogPreference#onDialogClosed onDialogClosed()} メソッドを呼び出します。 +このメソッドには、ユーザーの結果が「Positive」かどうかを指定するブール値の引数が含まれています — この値が true の場合はユーザーがポジティブ ボタンを選択しているということであり、新しい値を保存する必要があります。 + +次に例を示します。 +

+ +
+@Override
+protected void onDialogClosed(boolean positiveResult) {
+    // When the user selects "OK", persist the new value
+    if (positiveResult) {
+        persistInt(mNewValue);
+    }
+}
+
+ +

この例では、mNewValue は、設定の現在の値を保持するクラスの 1 つです。 +{@link android.preference.Preference#persistInt persistInt()} を呼び出すと、値が {@link android.content.SharedPreferences} ファイルに保存されます(この {@link android.preference.Preference} の XML ファイルに指定されたキーを自動的に使用して保存されます)。 + +

+ + +

現在の値を初期化する

+ +

システムは {@link android.preference.Preference} を画面に追加すると、{@link android.preference.Preference#onSetInitialValue onSetInitialValue()} を呼び出し、その設定用に保存されている値がある場合は通知します。 + +保存されている値がない場合、この呼び出しによりデフォルト値が設定されます。 +

+ +

この {@link android.preference.Preference#onSetInitialValue onSetInitialValue()} メソッドは、ブール値の restorePersistedValue を、その設定用に値が保存済みかどうか示すために渡します。 + +true の場合、{@link android.preference.Preference} クラスの {@code getPersisted*()} メソッド(整数用の {@link android.preference.Preference#getPersistedInt getPersistedInt()} など)のいずれかを呼び出して保存されている値を取得する必要があります。 + + +通常は保存されている値を取得するのは、UI を更新して以前保存した値を反映させるためです。 + +

+ +

restorePersistedValuefalse の場合、2 番目の引数で渡されたデフォルト値を使用する必要があります。 +

+ +
+@Override
+protected void onSetInitialValue(boolean restorePersistedValue, Object defaultValue) {
+    if (restorePersistedValue) {
+        // Restore existing state
+        mCurrentValue = this.getPersistedInt(DEFAULT_VALUE);
+    } else {
+        // Set default state from the XML attribute
+        mCurrentValue = (Integer) defaultValue;
+        persistInt(mCurrentValue);
+    }
+}
+
+ +

各 {@code getPersisted*()} メソッドは、値が保存されていない場合またはキーが存在しない場合に使用するデフォルト値を指定する引数を取ります。 +上記の例では、{@link android.preference.Preference#getPersistedInt getPersistedInt()} が保存されている値を返すことができない場合に備えて、ローカル定数がデフォルト値を指定するために使用されています。 + +

+ +

警告: defaultValue は、restorePersistedValuetrue の場合、常に null になるため、{@code getPersisted*()} メソッドでデフォルト値として使用できません。 + +

+ + +

デフォルト値を提供する

+ +

{@link android.preference.Preference} クラスのインスタンスがデフォルト値を指定している場合({@code android:defaultValue} 属性を使用)、システムは、オブジェクトのインスタンスを作成するときに {@link android.preference.Preference#onGetDefaultValue onGetDefaultValue()} を呼び出してデフォルト値を取得します。 + + +システムでデフォルト値を {@link android.content.SharedPreferences} に保存するには、このメソッドを実装する必要があります。 + +次に例を示します。

+ +
+@Override
+protected Object onGetDefaultValue(TypedArray a, int index) {
+    return a.getInteger(index, DEFAULT_VALUE);
+}
+
+ +

このメソッドは、属性の配列と取得する必要がある {@code android:defaultValue} のインデックス位置を提供しており、これらはデフォルト値を保存するために必要な情報のすべてです。 +属性からデフォルト値を抽出するためにこのメソッドの実装が必要なのは、デフォルト値が定義されていないときのために属性のローカル デフォルト値を指定する必要があるためです。 + +

+ + + +

プリファレンスの状態を保存し復元する

+ +

レイアウトの {@link android.view.View} と同じく、{@link android.preference.Preference} サブクラスは、アクティビティやフラグメントが再開される場合(ユーザーが画面を回転させた場合など)に状態の保存と復元を担当します。 + +{@link android.preference.Preference} クラスの状態を適切に保存し復元するには、ライフサイクル コールバック メソッドの {@link android.preference.Preference#onSaveInstanceState onSaveInstanceState()} と {@link android.preference.Preference#onRestoreInstanceState onRestoreInstanceState()} を実装する必要があります。 + + + +

+ +

{@link android.preference.Preference} の状態は、{@link android.os.Parcelable} インターフェースを実装するオブジェクトによって定義されます。 +Android フレームワークでは、このようなオブジェクトである{@link android.preference.Preference.BaseSavedState} クラスが状態オブジェクトを定義するための起点として提供されています。 + +

+ +

{@link android.preference.Preference} クラスが状態を保存する方法を定義するには、{@link android.preference.Preference.BaseSavedState} クラスを継承する必要があります。 +いくつかのメソッドをオーバーライドして {@link android.preference.Preference.BaseSavedState#CREATOR} オブジェクトを定義してください。 + +

+ +

大部分のアプリでは、次の実装をコピーして、{@link android.preference.Preference} サブクラスが整数以外のデータ型のデータを保存している場合は、{@code value} を処理している行を変更するだけで使用できます。 + +

+ +
+private static class SavedState extends BaseSavedState {
+    // Member that holds the setting's value
+    // Change this data type to match the type saved by your Preference
+    int value;
+
+    public SavedState(Parcelable superState) {
+        super(superState);
+    }
+
+    public SavedState(Parcel source) {
+        super(source);
+        // Get the current preference's value
+        value = source.readInt();  // Change this to read the appropriate data type
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        super.writeToParcel(dest, flags);
+        // Write the preference's value
+        dest.writeInt(value);  // Change this to write the appropriate data type
+    }
+
+    // Standard creator object using an instance of this class
+    public static final Parcelable.Creator<SavedState> CREATOR =
+            new Parcelable.Creator<SavedState>() {
+
+        public SavedState createFromParcel(Parcel in) {
+            return new SavedState(in);
+        }
+
+        public SavedState[] newArray(int size) {
+            return new SavedState[size];
+        }
+    };
+}
+
+ +

{@link android.preference.Preference.BaseSavedState} の上記の実装をアプリに追加(通常、{@link android.preference.Preference} サブクラスのサブクラスとして追加)するとともに、{@link android.preference.Preference} サブクラスの {@link android.preference.Preference#onSaveInstanceState onSaveInstanceState()} と {@link android.preference.Preference#onRestoreInstanceState onRestoreInstanceState()} を実装する必要があります。 + + + + +

+ +

次に例を示します。

+ +
+@Override
+protected Parcelable onSaveInstanceState() {
+    final Parcelable superState = super.onSaveInstanceState();
+    // Check whether this Preference is persistent (continually saved)
+    if (isPersistent()) {
+        // No need to save instance state since it's persistent,
+        // use superclass state
+        return superState;
+    }
+
+    // Create instance of custom BaseSavedState
+    final SavedState myState = new SavedState(superState);
+    // Set the state's value with the class member that holds current
+    // setting value
+    myState.value = mNewValue;
+    return myState;
+}
+
+@Override
+protected void onRestoreInstanceState(Parcelable state) {
+    // Check whether we saved the state in onSaveInstanceState
+    if (state == null || !state.getClass().equals(SavedState.class)) {
+        // Didn't save the state, so call superclass
+        super.onRestoreInstanceState(state);
+        return;
+    }
+
+    // Cast state to custom BaseSavedState and pass to superclass
+    SavedState myState = (SavedState) state;
+    super.onRestoreInstanceState(myState.getSuperState());
+    
+    // Set this Preference's widget to reflect the restored state
+    mNumberPicker.setValue(myState.value);
+}
+
+ diff --git a/docs/html-intl/intl/ja/guide/topics/ui/ui-events.jd b/docs/html-intl/intl/ja/guide/topics/ui/ui-events.jd new file mode 100644 index 0000000000000000000000000000000000000000..1cff3f62d0229041aa5008bd48c540c97edd8c3d --- /dev/null +++ b/docs/html-intl/intl/ja/guide/topics/ui/ui-events.jd @@ -0,0 +1,291 @@ +page.title=入力イベント +parent.title=ユーザー インターフェース +parent.link=index.html +@jd:body + + + +

Android では、アプリケーションでのユーザーの操作からイベントをインターセプトする方法が複数用意されていますが、ユーザー インターフェース内のイベントをインターセプトする場合は、ユーザーが操作する個々の View オブジェクトからイベントを取得することになります。 + +View クラスは、このための手段を提供しています。

+ +

レイアウトを構成するために使用する各種の View クラスには、UI イベントの処理に役に立ついくつかのパブリック コールバック メソッドが含まれています。 +これらのパブリック コールバック メソッドは、オブジェクトで対応するアクションが発生したときに Android フレームワークによって呼び出されます。 +たとえば、ビュー(ボタンなど)がタップされたときには、そのオブジェクト上で onTouchEvent() メソッドが呼び出されます。 +このようなイベントをインターセプトするには、クラスを継承しメソッドをオーバーライドする必要があります。 +ただし、このようなイベントを処理するたびに View オブジェクトを継承することは、実用的とは言えません。 +そのため、View クラスには、簡単に定義できるコールバックを持つネストされたインターフェースのコレクションも含まれています。 +これらのインターフェースは、イベントリスナと呼ばれ、UI へのユーザーの操作を取得するために使用されます。 +

+ +

通常は、イベントリスナを使用して、ユーザーの操作をリッスンすることになりますが、カスタム コンポーネントを作成するために View クラスを継承する場合や、 +処理を工夫するために {@link android.widget.Button} クラスを継承することもできます。 + +その場合は、クラスのイベント ハンドラを使用して、クラスにデフォルトのイベント動作を定義できます。 +

+ + +

イベントリスナ

+ +

イベントリスナは、コールバック メソッド 1 つを含む {@link android.view.View} クラスのインターフェースです。 +以下のメソッドは、イベントリスナが登録されているビューが UI アイテムへのユーザーの操作によってトリガーされたときに、Android フレームワークによって呼び出されます。 +

+ +

イベントリスナ インターフェースに含まれているのは、以下のコールバック メソッドです。

+ +
+
onClick()
+
{@link android.view.View.OnClickListener} のメソッド。ユーザーがアイテムをタップしたとき(タッチモードの場合)、またはナビゲーション キーまたはトラックボールでアイテムにフォーカスを合わせ、適切な「エンター」キーまたはトラックボールを押したときに、呼び出されます。 + + +
+
onLongClick()
+
{@link android.view.View.OnLongClickListener} のメソッド。ユーザーがアイテムをタップしてホールドしたとき(タッチモードの場合)、またはナビゲーション キーまたはトラックボールでアイテムにフォーカスを合わせ、適切な「エンター」キーまたはトラックボールを長押し(1 秒間)したときに、呼び出されます。 + + +
+
onFocusChange()
+
{@link android.view.View.OnFocusChangeListener} のメソッド。ナビゲーション キーやトラックボールを使用してユーザーがアイテムに移動してきたときまたはアイテムから離れるときに、呼び出されます。 +
+
onKey()
+
{@link android.view.View.OnKeyListener} のメソッド。ユーザーがアイテムにフォーカスを合わせて端末のハードウェア キーを押したとき、または離したときに、呼び出されます。 +
+
onTouch()
+
{@link android.view.View.OnTouchListener} のメソッド。画面(アイテムの境界線内)での押す、離す、移動操作などのタップイベントと認定されるアクションをユーザーが実行した場合に呼び出されます。 + +
+
onCreateContextMenu()
+
{@link android.view.View.OnCreateContextMenuListener} のメソッド。コンテキスト メニュー(「長押しクリック」し続けると作成されます)の作成中に呼び出されます。 +コンテキスト メニューについての詳細は、「メニュー」のデベロッパー ガイドをご覧ください。 + +
+
+ +

これらのメソッドは、それぞれのインターフェースにおける唯一のメソッドです。これらのいずれかのメソッドを定義しイベントを処理するには、ネストされたインターフェースをアクティビティに実装するか、ネストされたインターフェースを匿名クラスとして定義します。次に、その実装のインスタンスを対応する View.set...Listener() メソッドに渡します。 + + +(例: {@link android.view.View#setOnClickListener(View.OnClickListener) setOnClickListener()} を呼び出し、{@link android.view.View.OnClickListener OnClickListener} の実装を渡します)。 + +

+ +

次の例は、ボタンへの on-click リスナの登録方法を示しています。

+ +
+// Create an anonymous implementation of OnClickListener
+private OnClickListener mCorkyListener = new OnClickListener() {
+    public void onClick(View v) {
+      // do something when the button is clicked
+    }
+};
+
+protected void onCreate(Bundle savedValues) {
+    ...
+    // Capture our button from layout
+    Button button = (Button)findViewById(R.id.corky);
+    // Register the onClick listener with the implementation above
+    button.setOnClickListener(mCorkyListener);
+    ...
+}
+
+ +

OnClickListener をアクティビティの一部として実装すると、余分なクラスのロードとオブジェクトの割り当てを避けることができます。 +次に例を示します。

+
+public class ExampleActivity extends Activity implements OnClickListener {
+    protected void onCreate(Bundle savedValues) {
+        ...
+        Button button = (Button)findViewById(R.id.corky);
+        button.setOnClickListener(this);
+    }
+
+    // Implement the OnClickListener callback
+    public void onClick(View v) {
+      // do something when the button is clicked
+    }
+    ...
+}
+
+ +

上の例の onClick() コールバックには戻り値がありませんが、イベントリスナ メソッドの中には、ブール値を返すことが必要なものもあることに注意してください。 +ブール値が示す事柄はイベントによって異なります。 +以下はその例です。

+ + +

ハードウェア キー イベントは常に、その時点でフォーカスがあるビューに届けられることに注意してください。ハードウェア・キー・イベントは、ビュー階層の一番上から下に適切な対象に到達するまでディスパッチされます。 +ビュー(またはビューの子)にフォーカスがある場合、イベントが {@link android.view.View#dispatchKeyEvent(KeyEvent) +dispatchKeyEvent()} メソッドを介して伝わっていると判断できます。 +ビューを通じてキーイベントを取得する別の方法としては、{@link android.app.Activity#onKeyDown(int,KeyEvent) onKeyDown()}{@link android.app.Activity#onKeyUp(int,KeyEvent) onKeyUp()} を使用してアクティビティ内のすべてのイベントを受け取る方法もあります。 + +

+ +

また、アプリケーションのテキスト入力について検討する際は、多くの端末にソフトウェア入力メソッドしかないことに注意してください。 +ソフトウェア入力メソッドでは、キーの利用は必要ありません。音声入力や手書き入力などを利用できるものもあります。入力メソッドがキーボードに似たインターフェースを提供していたとしても、そのインターフェースは通常、{@link android.app.Activity#onKeyDown(int,KeyEvent) onKeyDown()} ファミリーのイベントをトリガーしません。 + +アプリケーションの使用をハードウェア キーボード付きの端末に限定するのではない限り、制御するために特定のキーを押すことが必要な UI は作成しないでください。 + +特に、ユーザーのリターンキー押下時に入力を検証するような処理は行わないようにし、代わりに {@link android.view.inputmethod.EditorInfo#IME_ACTION_DONE} などのアクションを使用して入力メソッドにアプリケーションが求める反応を伝えてください。そうすることで、入力メソッドが UI を意味のあるものにします。 + +ソフトウェア入力メソッドの動作を推測し、その推測に基づいて、書式設定済みのテキストをアプリケーションに提供することは避けてください。 +

+ +

注: Android では、まずイベント ハンドラが呼び出され、次にクラス定義から適切なデフォルト ハンドラが呼び出されます。 +そのため、これらのイベントリスナから true が返された場合、イベントの他のイベントリスナへの伝播は止まり、ビューのデフォルトのイベント ハンドラへのコールバックもブロックされます。 + +そのため、イベントを終了させたいことが確実な場合のみ、true を返すようにしてください。

+ + +

イベント ハンドラ

+ +

ビューからカスタム コンポーネントを作成する場合、デフォルトのイベント ハンドラとして使用される複数のコールバック メソッドを定義できます。イベント ハンドリングに使用される次のような一般的なコールバックの一部については、詳細をカスタム コンポーネントについてのドキュメントでご覧いただけます。 + + + +

+ +

View クラスに含まれていないメソッドの中にも、イベントの処理の方法に直接関係するメソッドがいくつかあります。 +そのため、レイアウト内の複雑なイベントを管理する場合は、以下のメソッドの使用を検討してください。 +

+ + +

タッチモード

+

+ユーザーが方向キーまたはトラックボールを使用してユーザー インターフェースを操作する場合、入力を受け付けるアイテムがどのアイテムなのかユーザーが知ることができるようにアクション可能なアイテム(ボタンなど)にフォーカスを与えることが必要になります。 + +ただし、端末にタッチ機能がある場合は、ユーザーはタップでインターフェースの操作を開始するため、アイテムをハイライトすることや、特定のビューにフォーカスを与えることは必要ではなくなりました。 + +そのため、「タッチモード」と名付けられた操作モードが存在します。 + +

+

+タッチ可能な端末では、ユーザーが画面をタップすると、端末がタッチモードになります。 +端末がタッチモードの場合、テキスト編集ウィジェットなどの {@link android.view.View#isFocusableInTouchMode} が true のビューのみがフォーカス可能であり、ボタンなどのタップ可能なその他のビューは、タップされたときにフォーカスされることはありません。それらのビューは、押されたときに、on-click リスナを起動します。 + + + +

+

+ユーザーが方向キーを押すか、またはトラックボールをスクロールすると、端末はタッチモードから抜け、ビューがフォーカスを取得します。 +これにより、ユーザーは、画面をタップすることなく、ユーザー インターフェースの操作を再開できます。 + +

+

+タッチモード状態は、システム全体(すべてのウィンドウとアクティビティ)で維持されます。端末が現在タッチモードかどうか確認するには、{@link android.view.View#isInTouchMode} を呼び出します。 + + +

+ + +

フォーカスの処理

+ +

フレームワークは、ユーザーに入力に応じて、通常のフォーカス移動を処理します。フレームワークが処理するフォーカス移動には、ビューが削除されたり非表示になったり、新しいビューが利用可能になったりしたことによるフォーカスの変化も含まれます。 + +ビューは、{@link android.view.View#isFocusable()} メソッドで、フォーカスが取得可能なことを示します。 +ビューのフォーカスの取得可否を変更するには、{@link android.view.View#setFocusable(boolean) setFocusable()} を呼び出します。 +タッチモードでは、{@link android.view.View#isFocusableInTouchMode()} でビューがフォーカスを取得可能かどうか確認できます。ビューのフォーカスの取得可否は、{@link android.view.View#setFocusableInTouchMode(boolean) setFocusableInTouchMode()} で変更できます。 + + +

+ +

フォーカスの移動は、所定の方向で最も近くにあるアイテムを見つけるアルゴリズムに基づきますが、 +まれに、デフォルトのアルゴリズムが開発者の意図する動作と一致しない場合もあります。 +この場合、レイアウト ファイルの + +nextFocusDownnextFocusLeftnextFocusRight、 +nextFocusUp の各 XML 属性を明示的にオーバーライドできます。これらの属性のいずれかを、フォーカスの移動のビューに追加します。 +フォーカスの移動のビューの ID になる属性の値を定義します。 +次に例を示します。

+
+<LinearLayout
+    android:orientation="vertical"
+    ... >
+  <Button android:id="@+id/top"
+          android:nextFocusUp="@+id/bottom"
+          ... />
+  <Button android:id="@+id/bottom"
+          android:nextFocusDown="@+id/top"
+          ... />
+</LinearLayout>
+
+ +

本来、上記の縦方向のレイアウトでは、1 番上のボタンから上に移動しようとしても、2 番目のボタンから下に移動しようしても、どこにもフォーカスが移動しないはずでしたが、上記の処理により、 +1 番上のボタンによって 1 番下のボタンが + nextFocusUp として定義され(逆の場合も同様)、フォーカスが、上から下と下から上に順番に移動するようになります。 +

+ +

UI でビューをフォーカス可能にする場合は(従来は、ビューはフォーカス可能ではありません)、レイアウトの宣言で android:focusable XML 属性をビューに追加し、値を + + true に設定します。また、android:focusableInTouchMode を使用して、タッチモードのときにビューをフォーカス可能にすることもできます。 +

+

特定のビューへのフォーカスをリクエストするには、{@link android.view.View#requestFocus()} を呼び出します。

+

フォーカス イベントをリッスンするには(ビューがフォーカスを受け取ったときまたは失ったときに通知を受け取るには)、上記のイベントリスナのセクションの説明に従って {@link android.view.View.OnFocusChangeListener#onFocusChange(View,boolean) onFocusChange()} を使用します。 + +

+ + + + diff --git a/docs/html-intl/intl/ja/sdk/index.jd b/docs/html-intl/intl/ja/sdk/index.jd new file mode 100644 index 0000000000000000000000000000000000000000..a2d1865e6965f4c2521149c66988432bd458ac81 --- /dev/null +++ b/docs/html-intl/intl/ja/sdk/index.jd @@ -0,0 +1,487 @@ +page.title=Android Studio と SDK Tools のダウンロード +page.tags=sdk, android studio +page.template=sdk +header.hide=1 +page.metaDescription=公式 Android IDE とデベロッパー ツールをダウンロードして、Android 版のモバイル端末、タブレット、ウェアラブル端末、TV などの端末向けのアプリをビルドする。 + +studio.version=1.1.0 + +studio.linux_bundle_download=android-studio-ide-135.1740770-linux.zip +studio.linux_bundle_bytes=259336386 +studio.linux_bundle_checksum=e8d166559c50a484f83ebfec6731cc0e3f259208 + +studio.mac_bundle_download=android-studio-ide-135.1740770-mac.dmg +studio.mac_bundle_bytes=261303345 +studio.mac_bundle_checksum=f9745d0fec1eefd498f6160a2d6a1b5247d4cda3 + +studio.win_bundle_exe_download=android-studio-bundle-135.1740770-windows.exe +studio.win_bundle_exe_bytes=856233768 +studio.win_bundle_exe_checksum=7484b9989d2914e1de30995fbaa97a271a514b3f + +studio.win_notools_exe_download=android-studio-ide-135.1740770-windows.exe +studio.win_notools_exe_bytes=242135128 +studio.win_notools_exe_checksum=5ea77661cd2300cea09e8e34f4a2219a0813911f + +studio.win_bundle_download=android-studio-ide-135.1740770-windows.zip +studio.win_bundle_bytes=261667054 +studio.win_bundle_checksum=e903f17cc6a57c7e3d460c4555386e3e65c6b4eb + + + +sdk.linux_download=android-sdk_r24.1.2-linux.tgz +sdk.linux_bytes=168121693 +sdk.linux_checksum=68980e4a26cca0182abb1032abffbb72a1240c51 + +sdk.mac_download=android-sdk_r24.1.2-macosx.zip +sdk.mac_bytes=89151287 +sdk.mac_checksum=00e43ff1557e8cba7da53e4f64f3a34498048256 + +sdk.win_download=android-sdk_r24.1.2-windows.zip +sdk.win_bytes=159778618 +sdk.win_checksum=704f6c874373b98e061fe2e7eb34f9fcb907a341 + +sdk.win_installer=installer_r24.1.2-windows.exe +sdk.win_installer_bytes=111364285 +sdk.win_installer_checksum=e0ec864efa0e7449db2d7ed069c03b1f4d36f0cd + + + + + + +@jd:body + + + + + + + +
+ + + + + + + + + +
+ +
 
+ + + +
+ +

Android Studio

+ +

公式 Android IDE

+ +
    +
  • Android Studio IDE
  • +
  • Android SDK Tools
  • +
  • Android 5.0(Lollipop)プラットフォーム
  • +
  • Google API を使用した Android 5.0 エミュレータのシステム イメージ
  • +
+ + +Android Studio をダウンロード + + +

+Android Studio または スタンドアロンの SDK Tools を入手するには、developer.android.com/sdk/ へアクセスしてください。 +

+ + + +
+ + + + +

インテリジェント コード エディタ

+ +
+ +
+ +
+

Android Studio の中核であるインテリジェント コード エディタは、高度なコード補完機能、リファクタリング機能、解析機能を備えています。 +

+

このパワフルなコード エディタは、Android アプリ デベロッパーの生産性を高めます。

+
+ + + + + +

コード テンプレートと GitHub との統合

+ +
+ +
+ +
+

新しいプロジェクト ウィザードでは、これまで以上に簡単に新規プロジェクトを開始できます。

+ +

ナビゲーション ドロワーやビュー ページャーなどのパターンにテンプレート コードを使用してプロジェクトを開始できます。加えて GitHub から Google のコード サンプルをインポートできます。 +

+
+ + + + +

マルチスクリーン アプリの開発

+ +
+ +
+ +
+

Android 携帯電話、タブレット、Android Wear、Android TV、Android Auto、Google Glass 向けのアプリをビルドできます。 +

+

Android Studio の新しい Android プロジェクト ビューとモジュールのサポートによって、アプリのプロジェクト管理やリソース管理がさらに簡単になります。 + +

+ + + + +

あらゆる形状の端末をサポートする仮想デバイス

+ +
+ +
+ +
+

Android Studio には最適化されたエミュレータ イメージが事前構成されています。

+

アップデートされ合理化された仮想デバイス マネージャーには、一般的な Android 端末向けにあらかじめ定義されたプロファイルが用意されています。 +

+
+ + + + +

+Gradle で進化する Android のビルド

+ +
+ +
+ +
+

1 つのプロジェクトで、Android アプリのための、異なる機能を持つ複数の APK を作成できます。

+

Maven を使用してアプリの依存関係を管理します。

+

APK は Android Studio でもコマンドラインでもビルドできます。

+
+ + + + +

Android Studio のその他の特徴

+
+ +Android Studio をダウンロード + +
    +
  • 人気の高い JetBrains の Java IDE である IntelliJ IDEA Community Edition が基盤
  • +
  • Gradle ベースの柔軟なビルドシステム
  • +
  • ビルド バリアントと複数の APK 生成機能
  • +
  • Google サービスやさまざまな端末向けのテンプレート サポートの拡充
  • +
  • テーマの編集が可能なレイアウト エディタ
  • +
  • パフォーマンス、ユーザビリティ、バージョン互換性、その他の問題を検出する Lint ツール
  • +
  • ProGuard とアプリの署名機能
  • +
  • Google Cloud Messaging や App Engine との統合をサポートする Google Cloud プラットフォームの組み込みサポート +
  • +
+ +

+Android Studio の機能の詳細については、「Android Studio の基礎」をご覧ください。 +

+
+ + +

Eclipse で ADT を使用している場合、Android Studio が Android の公式 IDE になったため、Android Studio に移行して IDE の最新アップデートを入手する必要があります。 + +プロジェクトの移行については、「Migrating to Android Studio」をご覧ください。 + +

+ + + + + + + +

システム要件

+ +

Windows の場合

+ +
    +
  • Microsoft® Windows® 8/7/Vista/2003(32-bit 版または 64-bit 版)
  • +
  • 2 GB 以上のシステム メモリ(RAM)、4 GB 以上を推奨
  • +
  • 400 MB の空き容量のあるハードディスク
  • +
  • Android SDK、エミュレータのシステム イメージ、キャッシュ用には少なくとも 1 GB 以上
  • +
  • 1280 x 800 以上の画面解像度
  • +
  • Java Development Kit (JDK) 7
  • +
  • エミュレータ アクセラレータ向け(任意): Intel® VT-x、Intel® EM64T(Intel® 64)、Execute Disable(XD)ビット機能対応の Intel® プロセッサ +
  • +
+ + +

Mac OS X の場合

+ +
    +
  • Mac ®OS X® 10.8.5 以降、10.9(Mavericks)まで
  • +
  • 2 GB 以上のシステム メモリ(RAM)、4 GB 以上を推奨
  • +
  • 400 MB の空き容量のあるハードディスク
  • +
  • Android SDK、エミュレータのシステム イメージ、キャッシュ用には少なくとも 1 GB 以上
  • +
  • 1280 x 800 以上の画面解像度
  • +
  • Java Runtime Environment (JRE) 6
  • +
  • Java Development Kit (JDK) 7
  • +
  • エミュレータ アクセラレータ向け(任意): Intel® VT-x、Intel® EM64T(Intel® 64)、Execute Disable(XD)ビット機能対応の Intel® プロセッサ +
  • +
+ +

Mac OSで最適化されたフォントのレンダリングを使用するには、Android Studio を Java Runtime Environment (JRE) 6 と共に実行する必要があります。 +その後、Java Development Kit(JDK)6 または JDK 7 を使用するようプロジェクトを構成できます。

+ + + +

Linux の場合

+ +
    +
  • GNOME または KDE デスクトップ
  • +
  • GNU C Library (glibc) 2.15 以降
  • +
  • 2 GB 以上のシステム メモリ(RAM)、4 GB 以上を推奨
  • +
  • 400 MB の空き容量のあるハードディスク
  • +
  • Android SDK、エミュレータのシステム イメージ、キャッシュ用には少なくとも 1 GB 以上
  • +
  • 1280 x 800 以上の画面解像度
  • +
  • Oracle® Java Development Kit (JDK) 7
  • +
+

Ubuntu® 14.04、Trusty Tahr(32-bit 版アプリケーションを実行可能な 64-bit 版)でテスト済み。 +

+ + + + +

他のダウンロード オプション

+ + diff --git a/docs/html-intl/intl/ja/sdk/installing/adding-packages.jd b/docs/html-intl/intl/ja/sdk/installing/adding-packages.jd new file mode 100644 index 0000000000000000000000000000000000000000..7715eed4a340c1708874e9aedf1e2b0fe9a4d9e4 --- /dev/null +++ b/docs/html-intl/intl/ja/sdk/installing/adding-packages.jd @@ -0,0 +1,227 @@ +page.title=SDK パッケージの追加 + +page.tags=sdk manager +helpoutsWidget=true + +@jd:body + + + + +

+Android SDK では、デフォルトの状態で、開発の開始に必要なすべてのものが用意されているわけではありません。 +Android SDK では、ツール、プラットフォーム、その他のコンポーネントがパッケージに分けられており、Android SDK Manager を使って必要に応じてダウンロードできます。 + + +そのため、開始前に、Android SDK にいくつかのパッケージを追加する必要があります。

+ +

パッケージの追加を開始するには、次のいずれかの方法で Android SDK Manager を起動します。

+
    +
  • Android Studio で、ツールバーの [SDK Manager] をクリックします。 +
  • +
  • Android Studio を使用しない場合: +
      +
    • Windows の場合: Android SDK のルート ディレクトリにある SDK Manager.exe ファイルをダブルクリックします。 +
    • +
    • Mac または Linux の場合: ターミナルを開いて Android SDK がインストールされているロケーションの tools/ ディレクトリに移動し、android sdk を実行します。 +
    • +
    +
  • +
+ +

SDK Manager を初めて開く場合、いくつかのパッケージがデフォルトで選択されています。 +この選択はそのままにしますが、開始に必要なすべてのものが揃っているか、次の手順でご確認ください。 +

+ + +
    +
  1. +

    最新の SDK Tools を入手する

    + + + +

    Android SDK をセットアップする場合、少なくとも最新の SDK Tools と Android プラットフォームのダウンロードが必要です。 +

    +
      +
    1. [Tools] ディレクトリを開き、以下のツールを選択します。 +
        +
      • Android SDK Tools
      • +
      • Android SDK Platform-tools
      • +
      • Android SDK Build-tools(最新バージョンのもの)
      • +
      +
    2. +
    3. 1 番上の [Android X.X] フォルダ(最新バージョンのフォルダ)を開き、以下を選択します。 +
        +
      • SDK Platform
      • +
      • ARM EABI v7a System Image」などのエミュレータ用のシステム イメージ
        +
      • +
      +
    4. +
    +
  2. + +
  3. +

    追加 API 用のサポート ライブラリを取得する

    + + + +

    Android サポート ライブラリ は、大部分のバージョンの Android に対応する API の拡張セットを提供しています。 +

    + +

    [Extras] ディレクトリを開き、以下を選択します。

    +
      +
    • Android Support Repository
    • +
    • Android Support Library
    • +
    + +

     

    +

     

    + +
  4. + + +
  5. +

    その他の API 用の Google Play サービスを入手する

    + + + +

    Google API を利用して開発を行うには、Google Play サービス パッケージが必要です。

    +

    [Extras] ディレクトリを開き、以下を選択します。

    +
      +
    • Google Repository
    • +
    • Google Play サービス
    • +
    + +

    注: Google Play services API は、Android が搭載されたすべての端末で利用できるわけではありませんが、Google Play ストアを使用するとすべての端末で利用できます。 +これらの API を Android エミュレータで使用するには、SDK Manager の最新の [Android X.X] ディレクトリから Google API のシステム イメージもインストールする必要があります。 + +

    +
  6. + + +
  7. +

    パッケージをインストールする

    +

    必要なパッケージをすべて選択したら、インストールを続行します。

    +
      +
    1. [Install X packages] をクリックします。
    2. +
    3. 次のウィンドウで、左側のパッケージ名を 1 つずつダブルクリックし、各パッケージの使用許諾契約に同意します。 +
    4. +
    5. [Install] をクリックします。
    6. +
    +

    SDK Manager のウィンドウの 1 番下に、ダウンロードの進捗状況が表示されます。 + SDK Manager を終了しないでください。SDK Manager を終了すると、ダウンロードはキャンセルされます。

    +
  8. + +
  9. +

    アプリを作成する

    + +

    Android SDK にインストールしたパッケージを使用して、アプリを作成できるようになりました。 +新しいツールとその他の API を入手できるようになった場合は、SDK Manager を起動して SDK に新しいパッケージをダウンロードしてください。 +

    + +

    必要に応じて以下を参考にしてください。

    + +
    +
    +

    スタートガイド

    +

    Android 開発が初めての場合は、Android アプリの基本を初めてのアプリ作成に関するガイドに従って学習してください。 +

    + +
    +
    +

    ウェアラブル端末向けにアプリを作成する

    +

    Android ウェアラブル端末向けにアプリを開発するには、「Android Wear アプリの作成」のガイドをご覧ください。 +

    + +
    +
    +

    Google API を使用する

    +

    マップや Play ゲーム サービスなどの Google API の使用を開始するには、「Setting Up Google Play Services」のガイドをご覧ください。 + + +

    + +
    +
    + + +
  10. + +
+ + diff --git a/docs/html-intl/intl/ko/guide/components/activities.jd b/docs/html-intl/intl/ko/guide/components/activities.jd new file mode 100644 index 0000000000000000000000000000000000000000..001982f6f63c5f9fe437615fd0fc82774471ec32 --- /dev/null +++ b/docs/html-intl/intl/ko/guide/components/activities.jd @@ -0,0 +1,756 @@ +page.title=작업 +page.tags=activity,intent +@jd:body + + + + + +

{@link android.app.Activity}는 일종의 애플리케이션 구성 요소로서, +사용자와 상호작용할 수 있는 화면을 제공하여 전화 걸기, 사진 찍기, 이메일 보내기 또는 지도 보기 등의 일을 할 수 +있습니다. 액티비티마다 창이 하나씩 주어져 이곳에 사용자 인터페이스를 끌어올 수 있습니다. 이 창은 +일반적으로 화면을 가득 채우지만, 작은 창으로 만들어 다른 창 위에 띄울 수도 +있습니다.

+ +

하나의 애플리케이션은 보통 여러 개의 액티비티가 느슨하게 서로 묶여 있는 형태로 +구성됩니다. 통상 한 애플리케이션 내에서 하나의 액티비티가 "주요" 액티비티로 지정되며, +사용자가 이 애플리케이션을 처음 실행할 때 이 액티비티가 사용자에게 표시됩니다. 그런 후 +각각의 액티비티는 여러 가지 작업을 수행하기 위해 또 다른 액티비티를 시작할 수 있습니다. 새로운 +액티비티가 시작될 때마다 이전 액티비티는 중단되지만 시스템은 해당 액티비티를 +스택("백 스택")에 보존합니다. 새로운 액티비티가 시작되면, 이것이 백 스택 위로 밀려와 사용자의 +시선을 끕니다. 백 스택은 기본적인 "후입선출" 방식을 지키므로, +사용자가 현재 액티비티를 끝내고 뒤로 버튼을 누르면 해당 액티비티가 +스택에서 튀어나와(소멸되고) 이전 액티비티를 재개합니다 (백 스택은 +작업 +및 백 스택 문서에서 상세하게 논의합니다).

+ +

한 액티비티가 새로운 액티비티의 시작으로 인해 중단된 경우, 이 상태 변경은 +액티비티의 수명 주기 콜백 메서드를 통해 알려집니다. +액티비티가 시스템 액티비티를 생성, 중단, 재시작, 제거하는 등의 상태 변화로 인해 받을 수 있는 콜백 메서드는 여러 가지가 있습니다. + + 각 콜백은 상태 변화에 알맞은 특정 작업을 수행할 기회를 제공합니다. + 예를 들어 액티비티가 중단되면 네트워크 또는 데이터베이스 연결과 같이 +큰 개체는 모두 해제해야 합니다. 액티비티가 재개되면, 필요한 리소스를 +다시 획득하여 중단된 작업을 재개할 수 있습니다. 이러한 상태 전환은 +모두 액티비티 수명 주기의 일부입니다.

+ +

이 문서의 남은 부분에서는 다양한 액티비티 상태 사이의 전환을 적절히 관리할 수 있도록 액티비티 수명 주기가 작동하는 방식을 자세히 논하는 등, +액티비티를 구축하고 사용하는 기본적 방법을 +설명합니다.

+ + + +

액티비티 생성하기

+ +

액티비티를 생성하려면 {@link android.app.Activity}의 하위 클래스(또는 이의 +기존 하위 클래스)를 생성해야 합니다. 하위 클래스에서는 +액티비티 생성, 중단, 재개, 소멸 시기 등과 같은 +수명 주기의 다양한 상태 간 액티비티가 전환될 때 시스템이 호출하는 콜백 메서드를 구현해야 합니다. 가장 중요한 두 가지 콜백 메서드는 +다음과 같습니다.

+ +
+
{@link android.app.Activity#onCreate onCreate()}
+
이 메서드는 반드시 구현해야 합니다. 시스템은 액티비티를 생성할 때 이것을 호출합니다. + 구현하는 중에 액티비티의 필수 구성 요소를 초기화해야 +합니다. + 무엇보다도 중요한 점은, 바로 여기서 {@link android.app.Activity#setContentView + setContentView()}를 호출해야 액티비티의 사용자 인터페이스 레이아웃을 정의할 수 있다는 점입니다.
+
{@link android.app.Activity#onPause onPause()}
+
시스템이 이 메서드를 호출하는 것은 사용자가 액티비티를 떠난다는 +첫 번째 신호입니다(다만 이것이 항상 액티비티가 소멸 중이라는 뜻은 아닙니다). 현재 사용자 세션을 넘어서 +지속되어야 하는 변경 사항을 커밋하려면 보통 이곳에서 해아 합니다(사용자가 +돌아오지 않을 수 있기 때문입니다).
+
+ +

여러 액티비티 사이에서 원활한 사용자 경험을 제공하고, 액티비티 중단이나 심지어 +소멸을 초래할 수도 있는 예상치 못한 간섭을 처리하기 위해 사용해야 하는 다른 수명 주기 +콜백 메서드도 여러 가지 있습니다. 모든 수명 주기 콜백 메서드는 나중에 +액티비티 수명 주기 관리 섹션에서 자세히 논의할 것입니다.

+ + + +

사용자 인터페이스 구현하기

+ +

한 액티비티에 대한 사용자 인터페이스는 보기 계층—즉, +{@link android.view.View} 클래스에서 파생된 개체가 제공합니다. 각 보기는 액티비티 창 안의 특정한 직사각형 공간을 + 제어하며 사용자 상호 작용에 대응할 수 있습니다. 예를 들어, 어떤 보기는 사용자가 터치하면 +작업을 시작하는 버튼일 수 있습니다.

+ +

Android는 레이아웃을 디자인하고 정리하는 데 사용할 수 있도록 여러 가지 보기를 미리 만들어 +제공합니다. "위젯"이란 화면에 시각적(이자 대화형) 요소를 제공하는 보기입니다. 예를 들어 +버튼, 텍스트 필드, 확인란이나 그저 하나의 이미지일 수도 있습니다. "레이아웃"은 선형 레이아웃, 격자형 레이아웃, 상대적 레이아웃과 같이 하위 레이아웃에 대해 독특한 레이아웃 모델을 제공하는 {@link +android.view.ViewGroup}에서 파생된 +보기입니다. 또한, {@link android.view.View}와 +{@link android.view.ViewGroup} 클래스(또는 기존 하위 클래스)의 아래로 내려가서 위젯과 레이아웃을 생성하고 +이를 액티비티 레이아웃에 적용할 수 있습니다.

+ +

보기를 사용하여 레이아웃을 정의하는 가장 보편적인 방식은 애플리케이션 리소스에 저장된 +XML 레이아웃 파일을 사용하는 것입니다. 이렇게 하면 액티비티의 동작을 정의하는 +소스 코드와 별개로 사용자 인터페이스 디자인을 유지할 수 있습니다. +{@link android.app.Activity#setContentView(int) setContentView()}로 액티비티의 UI로서 레이아웃을 설정하고, +해당 레이아웃의 리소스 ID를 전달할 수 있습니다. 그러나 액티비티 코드에 새로운 {@link android.view.View}를 생성하고 + 새로운 {@link +android.view.View}를 {@link android.view.ViewGroup}에 삽입하여 보기 계층을 구축한 뒤 루트 +{@link android.view.ViewGroup}을 {@link android.app.Activity#setContentView(View) +setContentView()}에 전달해도 해당 레이아웃을 사용할 수 있습니다.

+ +

사용자 인터페이스 생성에 관한 정보는 사용자 인터페이스 문서를 참조하십시오.

+ + + +

매니페스트에서 액티비티 선언하기

+ +

시스템에서 액티비티에 액세스할 수 있게 하려면 이를 매니페스트 파일에서 +선언해야만 합니다. 액티비티를 선언하려면 매니페스트 파일을 열고 {@code <activity>} 요소를 + {@code <application>} +요소의 하위 항목으로 추가합니다. 예:

+ +
+<manifest ... >
+  <application ... >
+      <activity android:name=".ExampleActivity" />
+      ...
+  </application ... >
+  ...
+</manifest >
+
+ +

액티비티 레이블, 액티비티 아이콘 또는 액티비티 UI 스타일용 테마와 같이 +이 요소에 포함할 수 있는 속성이 여러 가지 있습니다. + {@code android:name} + 속성이 유일한 필수 속성입니다. 이것이 액티비티의 클래스 이름을 지정합니다. 일단 + 애플리케이션을 게시하고 나면 이 이름을 변경해서는 안 됩니다. 이름을 변경하면 +애플리케이션 바로 가기와 같은 일부 기능이 고장 날 수 있습니다(블로그 게시물의 바꿀 수 없는 +항목을 참조하십시오).

+ +

매니페스트에서 액티비티를 선언하는 것에 관한 자세한 정보는 {@code <activity>} 요소 + 참고 자료를 참조하십시오.

+ + +

인텐트 필터 사용하기

+ +

{@code +<activity>} 요소 또한 여러 가지 인텐트 필터를 지정할 수 있습니다. 다른 애플리케이션 구성 요소가 이를 활성화하는 방법을 선언하기 위해 {@code +<intent-filter>}를 사용하는 +것입니다.

+ +

Android SDK 도구를 사용하여 새 애플리케이션을 생성하는 경우, 개발자를 위해 +생성되어 있는 스텁 액티비티에 자동으로 인텐트 필터가 포함되어 있어 "주요" 동작에 +응답하는 액티비티를 선언하며, 이는 "시작 관리자" 범주에 배치해야 합니다. 인텐트 필터는 다음과 +같은 형태를 띱니다.

+ +
+<activity android:name=".ExampleActivity" android:icon="@drawable/app_icon">
+    <intent-filter>
+        <action android:name="android.intent.action.MAIN" />
+        <category android:name="android.intent.category.LAUNCHER" />
+    </intent-filter>
+</activity>
+
+ +

{@code +<action>} 요소는 이것이 애플리케이션으로 가는 "주요" 진입 지점이라는 것을 나타냅니다. {@code +<category>} 요소는 이 액티비티가 시스템의 +애플리케이션 시작 관리자에 목록으로 나열되어야 한다는 것을 나타냅니다(사용자가 이 액티비티를 시작할 수 있도록 해줌).

+ +

애플리케이션이 자체 포함 방식이기를 원하고 다른 애플리케이션이 이 +애플리케이션의 액티비티를 활성화하도록 허용하지 않고자 하면, 달리 인텐트 필터가 더 필요하지 않습니다. "주요" 동작과 "시작 관리자" 범주가 있는 +액티비티는 하나뿐이어야 합니다(이전 예시 참조). 다른 애플리케이션에서 +사용할 수 없게 하고자 하는 액티비티에는 인텐트 필터가 있으면 안 됩니다. +이러한 액티비티는 명시적인 인텐트를 사용해 직접 시작할 수 있습니다(이 내용은 다음 섹션에서 논의).

+ +

그러나, 액티비티가 다른 애플리케이션(및 본인의 애플리케이션)에서 +전달된 암시적 인텐트에 응답하도록 하려면 액티비티에 추가로 인텐트 필터를 정의해야만 +합니다. 응답하게 하고자 하는 각 인텐트 유형별로 +{@code +<action>} 요소를 포함하는 {@code +<intent-filter>}를 하나씩 포함시켜야 하며, 선택 사항으로 {@code +<category>} 요소 및/또는 {@code +<data>} 요소를 포함시킬 수 있습니다. 이와 같은 요소는 액티비티가 응답할 수 있는 인텐트의 유형을 +나타냅니다.

+ +

액티비티가 인텐트에 응답하는 방식에 관한 자세한 정보는 인텐트 및 인텐트 필터 +문서를 참조하십시오.

+ + + +

액티비티 시작하기

+ +

다른 액티비티를 시작하려면 {@link android.app.Activity#startActivity + startActivity()}를 호출한 다음 이에 시작하고자 하는 액티비티를 설명하는 {@link android.content.Intent}를 +전달하면 됩니다. 인텐트는 시작하고자 하는 액티비티를 정확히 나타내거나, 수행하고자 하는 작업의 +유형을 설명하는 것입니다(시스템이 적절한 액티비티를 선택하며, +이는 + 다른 애플리케이션에서 가져온 것일 수도 있습니다). 인텐트는 소량의 데이터를 운반하여 시작된 액티비티에서 + 사용할 수 있습니다.

+ +

본인의 애플리케이션 안에서 작업하는 경우에는, 알려진 액티비티를 시작하기만 하면 되는 경우가 잦습니다. + 이렇게 하려면 시작하고자 하는 액티비티를 명시적으로 정의하는 인텐트를 클래스 이름을 +사용하여 생성하면 됩니다. 예를 들어, 다음은 한 액티비티가 {@code +SignInActivity}라는 이름의 다른 액티비티를 시작하는 방법입니다.

+ +
+Intent intent = new Intent(this, SignInActivity.class);
+startActivity(intent);
+
+ +

그러나, 애플리케이션이 다른 몇 가지 동작을 수행하고자 할 수도 있습니다. 예를 들어 이메일 보내기, 문자 +메시지 보내기 또는 상태 업데이트 등을 액티비티의 데이터를 사용하여 수행하는 것입니다. 이 경우, 본인의 애플리케이션에 +그러한 동작을 수행할 자체 액티비티가 없을 수도 있습니다. 따라서 기기에 있는 다른 애플리케이션이 +제공하는 액티비티를 대신 활용하여 동작을 수행하도록 할 수 있습니다. 바로 이 부분에서 +인텐트의 진가가 발휘됩니다. 수행하고자 하는 동작을 설명하는 인텐트를 생성하면 +시스템이 적절한 액티비티를 + 다른 애플리케이션에서 시작하는 것입니다. 해당 인텐트를 + 처리할 수 있는 액티비티가 여러 개 있는 경우, 사용자가 어느 것을 사용할지 선택합니다. 예를 + 들어 사용자가 이메일 메시지를 보낼 수 있게 하려면, 다음과 같은 인텐트를 +생성하면 됩니다.

+ +
+Intent intent = new Intent(Intent.ACTION_SEND);
+intent.putExtra(Intent.EXTRA_EMAIL, recipientArray);
+startActivity(intent);
+
+ +

인텐트에 추가된 {@link android.content.Intent#EXTRA_EMAIL} 추가 사항은 이메일이 전송되어야 할 이메일 주소의 + 문자열 배열입니다. 이메일 애플리케이션이 이 인텐트에 +응답하면, 애플리케이션은 추가 사항이 제공한 문자열 배열을 읽어낸 다음 이를 이메일 작성 양식의 + "수신" 필드에 배치합니다. 이 상황에서 이메일 애플리케이션의 액티비티가 시작되고 사용자가 + 작업을 끝내면 본인의 액티비티가 재개되는 것입니다.

+ + + + +

결과에 대한 액티비티 시작하기

+ +

때로는 시작한 액티비티에서 결과를 받고 싶을 수도 있습니다. 그런 경우, + ({@link android.app.Activity#startActivity + startActivity()} 대신) {@link android.app.Activity#startActivityForResult + startActivityForResult()}를 호출해서 액티비티를 시작합니다. 그런 다음 후속 액티비티에서 결과를 +받으려면, {@link android.app.Activity#onActivityResult onActivityResult()} 콜백 + 메서드를 구현합니다. 해당 후속 액티비티가 완료되면, 이것이 {@link +android.content.Intent} 형식으로 결과를 {@link android.app.Activity#onActivityResult onActivityResult()} + 메서드에 반환합니다.

+ +

예를 들어 사용자가 연락처 중에서 하나를 고를 수 있도록 하고 싶을 수 있습니다. +즉 여러분의 액티비티가 해당 연락처의 정보로 무언가 할 수 있도록 하는 것입니다. 그와 같은 인텐트를 생성하고 결과를 처리하려면 +다음과 같이 하면 됩니다.

+ +
+private void pickContact() {
+    // Create an intent to "pick" a contact, as defined by the content provider URI
+    Intent intent = new Intent(Intent.ACTION_PICK, Contacts.CONTENT_URI);
+    startActivityForResult(intent, PICK_CONTACT_REQUEST);
+}
+
+@Override
+protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+    // If the request went well (OK) and the request was PICK_CONTACT_REQUEST
+    if (resultCode == Activity.RESULT_OK && requestCode == PICK_CONTACT_REQUEST) {
+        // Perform a query to the contact's content provider for the contact's name
+        Cursor cursor = getContentResolver().query(data.getData(),
+        new String[] {Contacts.DISPLAY_NAME}, null, null, null);
+        if (cursor.moveToFirst()) { // True if the cursor is not empty
+            int columnIndex = cursor.getColumnIndex(Contacts.DISPLAY_NAME);
+            String name = cursor.getString(columnIndex);
+            // Do something with the selected contact's name...
+        }
+    }
+}
+
+ +

이 예시는 액티비티 결과를 처리하기 위해 {@link +android.app.Activity#onActivityResult onActivityResult()} 메서드에서 사용해야 할 기본 논리를 + 나타낸 것입니다. 첫 번째 조건은 요청이 성공적인지 확인합니다. 요청에 성공했다면 +{@code resultCode}가 {@link android.app.Activity#RESULT_OK}가 됩니다. 또한, 이 결과가 응답하는 요청이 +알려져 있는지도 확인합니다. 이 경우에는 {@code requestCode}가 +{@link android.app.Activity#startActivityForResult +startActivityForResult()}와 함께 전송된 두 번째 매개변수와 일치합니다. 여기서부터 코드가 +{@link android.content.Intent}({@code data} 매개변수)로 반환된 데이터를 쿼리하여 액티비티 결과를 처리합니다.

+ +

그러면 {@link +android.content.ContentResolver}가 콘텐츠 제공자에 대한 쿼리를 수행하고, 콘텐츠 제공자는 쿼리된 데이터를 읽을 수 있게 허용하는 +{@link android.database.Cursor}를 반환합니다. 자세한 내용은 +콘텐츠 제공자 문서를 참조하십시오.

+ +

인텐트 사용에 관한 자세한 정보는 인텐트 및 인텐트 + 필터문서를 참조하십시오.

+ + +

액티비티 종료하기

+ +

액티비티를 종료하려면 해당 액티비티의 {@link android.app.Activity#finish +finish()} 메서드를 호출하면 됩니다. +{@link android.app.Activity#finishActivity finishActivity()}를 호출하여 이전에 시작한 별도의 액티비티를 종료할 수도 있습니다.

+ +

참고: 대부분의 경우, 이와 같은 메서드를 사용하여 액티비티를 명시적으로 +종료해서는 안 됩니다. 다음 섹션에서 액티비티 수명 주기에 관해 논의한 바와 같이, +Android 시스템이 액티비티의 수명을 대신 관리해주므로 직접 액티비티를 종료하지 +않아도 됩니다. 이와 같은 메서드를 호출하면 예상되는 사용자 +환경에 부정적인 영향을 미칠 수 있으며, 따라서 사용자가 액티비티의 이 인스턴스에 돌아오는 것을 절대 바라지 않는 경우에만 +사용해야 합니다.

+ + +

액티비티 수명 주기 관리하기

+ +

콜백 메서드를 구현하여 액티비티의 수명 주기를 관리하는 것은 +강력하고 유연한 애플리케이션 개발에 +대단히 중요한 역할을 합니다. 액티비티의 수명 주기는 다른 액티비티와의 관계, +액티비티의 작업과 백 스택 등에 직접적으로 영향을 받습니다.

+ +

액티비티는 기본적으로 세 가지 상태로 존재할 수 있습니다.

+ +
+
재개됨
+
액티비티가 화면 전경에 있으며 사용자의 초점이 맞춰져 있습니다 (이 상태를 때로는 +"실행 중"이라고 일컫기도 합니다).
+ +
일시정지됨
+
다른 액티비티가 전경에 나와 있고 사용자의 시선을 집중시키고 있지만, 이 액티비티도 여전히 표시되어 있습니다. 다시 말해, +다른 액티비티가 이 액티비티 위에 표시되어 있으며 해당 액티비티는 부분적으로 투명하거나 +전체 화면을 덮지 않는 상태라는 뜻입니다. 일시정지된 액티비티는 완전히 살아있지만({@link android.app.Activity} +개체가 메모리에 보관되어 있고, 모든 상태 및 구성원 정보를 유지하며, +창 관리자에 붙어 있는 상태로 유지됨), 메모리가 극히 부족한 경우 시스템이 중단시킬 수 있습니다.
+ +
정지됨
+
이 액티비티가 다른 액티비티에 완전히 가려진 상태입니다(액티비티가 이제 +"배경"에 위치함). 중단된 액티비티도 여전히 살아 있기는 마찬가지입니다({@link android.app.Activity} +개체가 메모리에 보관되어 있고, 모든 상태와 구성원 정보를 유지하시만 창 관리자에 붙어 있지 않음 +). 그러나, 이는 더 이상 사용자에게 표시되지 않으며 +다른 곳에 메모리가 필요하면 시스템이 종료시킬 수 있습니다.
+
+ +

액티비티가 일시정지 또는 중단된 상태이면, 시스템이 이를 메모리에서 삭제할 수 있습니다. 이러기 위해서는 +종료를 요청하거나({@link android.app.Activity#finish finish()} 메서드를 호출) 단순히 이 액티비티의 프로세스를 중단시키면 +됩니다. 액티비티가 다시 열릴 때에는(종료 또는 중단된 후에) 처음부터 다시 생성해야 +합니다.

+ + + +

수명 주기 콜백 구현하기

+ +

위에서 설명한 바와 같이 액티비티가 여러 가지 상태를 오가며 전환되면, 이와 같은 내용이 +여러 가지 콜백 메서드를 통해 통지됩니다. 콜백 메서드는 모두 후크로서, +액티비티 상태가 변경될 때 적절한 작업을 하기 위해 이를 재정의할 수 있습니다. 다음의 골격 +액티비티에는 기본 수명 주기 메서드가 각각 하나씩 포함되어 있습니다.

+ + +
+public class ExampleActivity extends Activity {
+    @Override
+    public void {@link android.app.Activity#onCreate onCreate}(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        // The activity is being created.
+    }
+    @Override
+    protected void {@link android.app.Activity#onStart onStart()} {
+        super.onStart();
+        // The activity is about to become visible.
+    }
+    @Override
+    protected void {@link android.app.Activity#onResume onResume()} {
+        super.onResume();
+        // The activity has become visible (it is now "resumed").
+    }
+    @Override
+    protected void {@link android.app.Activity#onPause onPause()} {
+        super.onPause();
+        // Another activity is taking focus (this activity is about to be "paused").
+    }
+    @Override
+    protected void {@link android.app.Activity#onStop onStop()} {
+        super.onStop();
+        // The activity is no longer visible (it is now "stopped")
+    }
+    @Override
+    protected void {@link android.app.Activity#onDestroy onDestroy()} {
+        super.onDestroy();
+        // The activity is about to be destroyed.
+    }
+}
+
+ +

참고: 이와 같은 수명 주기 메서드를 구현하려면, 항상 + 슈퍼클래스 구현을 호출한 다음에만 다른 작업을 시작할 수 있습니다(위의 예시 참조).

+ +

이와 같은 메서드를 모두 합쳐 한 액티비티의 수명 주기 전체를 정의합니다. 이러한 메서드를 구현함으로써 +액티비티 수명 주기 내의 세 가지 중첩된 루프를 모니터링할 수 있습니다.

+ +
    +
  • 한 액티비티의 전체 수명은 {@link +android.app.Activity#onCreate onCreate()} 호출과 {@link +android.app.Activity#onDestroy} 호출 사이를 말합니다. 액티비티는 {@link android.app.Activity#onCreate onCreate()}에서 +"전체" 상태(레이아웃 정의 등)의 설정을 수행한 다음 나머지 리소스를 +모두 {@link android.app.Activity#onDestroy}에 해제해야 합니다. 예를 들어, +액티비티에 네트워크에서 데이터를 다운로드하기 위해 배경에서 실행 중인 스레드가 있는 경우, 이는 +해당 스레드를 {@link android.app.Activity#onCreate onCreate()}에서 생성한 다음 {@link +android.app.Activity#onDestroy}에서 그 스레드를 중단할 수 있습니다.
  • + +
  • 액티비티의 가시적 수명은 {@link +android.app.Activity#onStart onStart()} 호출에서 {@link +android.app.Activity#onStop onStop()} 호출 사이를 말합니다. 이 기간 중에는 사용자가 액티비티를 화면에서 보고 이와 +상호작용할 수 있습니다. 예컨대 {@link android.app.Activity#onStop onStop()}이 호출되어 + 새 액티비티가 시작되면 이 액티비티는 더 이상 표시되지 않게 됩니다. 이와 같은 두 가지 메서드 중에서 +사용자에게 액티비티를 표시하는 데 필요한 리소스를 유지하면 됩니다. 예를 들어, +{@link +android.app.Activity#onStart onStart()}에서 {@link android.content.BroadcastReceiver}를 등록하고 UI에 영향을 미치는 변화를 모니터링하고 +{@link android.app.Activity#onStop onStop()}에서 등록을 해제하면 사용자는 여러분이 무엇을 표시하고 있는지 더 이상 볼 수 +없게 됩니다. 시스템은 액티비티의 전체 수명 내내 {@link android.app.Activity#onStart onStart()} 및 {@link +android.app.Activity#onStop onStop()}을 여러 번 호출할 수 있으며, 이때 +액티비티는 사용자에게 표시되었다 숨겨지는 상태를 오가게 됩니다.

  • + +
  • 액티비티의 전경 수명은 {@link +android.app.Activity#onResume onResume()} 호출에서 {@link android.app.Activity#onPause +onPause()} 호출 사이를 말합니다. 이 기간 중에는 이 액티비티가 화면에서 다른 모든 액티비티 앞에 표시되며 사용자 입력도 +여기에 집중됩니다. 액티비티는 전경에 나타났다 숨겨지는 전환을 자주 반복할 수 있습니다. 예를 들어 +, 기기가 절전 모드에 들어가거나 대화 상자가 +나타나면 {@link android.app.Activity#onPause onPause()}가 호출됩니다. 이 상태는 자주 전환될 수 있으므로, 이 두 가지 메서드의 코드는 +상당히 가벼워야 합니다. 그래야 전환이 느려 사용자를 기다리게 하는 일을 피할 수 있습니다.

  • +
+ +

그림 1은 액티비티가 상태 사이에서 취할 수 있는 이와 같은 루프와 경로를 나타낸 것입니다. +직사각형이 액티비티가 여러 상태 사이를 전환할 때 작업을 수행하도록 +구현할 수 있는 콜백 메서드를 나타냅니다.

+ + +

그림 1. 액티비티 수명 주기입니다.

+ +

같은 수명 주기 콜백 메서드가 표 1에 나열되어 있으며, 이 표는 각 콜백 +메서드를 더욱 상세하게 설명하며 액티비티의 전반적인 +수명 주기 내에서 각 메서드의 위치를 나타내기도 합니다. 여기에는 콜백 메서드가 완료된 다음 +시스템이 액티비티를 중단시킬 수 있는지 여부도 포함되어 있습니다.

+ +

표 1. 액티비티 수명 주기 +콜백 메서드의 요약입니다.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
메서드 설명 완료 후 중단 가능? 다음
{@link android.app.Activity#onCreate onCreate()}액티비티가 처음 생성되었을 때 호출됩니다. + 이곳에서 일반적인 정적 설정을 모두 수행해야 합니다. +즉 보기 생성, 목록에 데이터 바인딩하기 등을 말합니다. 이 메서드에는 +액티비티의 이전 상태가 캡처된 경우 해당 상태를 포함한 +번들 개체가 전달됩니다(이 문서 나중에 나오는 액티비티 상태 저장하기를 참조하십시오 +). +

이 뒤에는 항상 {@code onStart()}가 따라옵니다.

아니요{@code onStart()}
    {@link android.app.Activity#onRestart +onRestart()}액티비티가 중단되었다가 다시 시작되기 직전에 +호출됩니다. +

이 뒤에는 항상 {@code onStart()}가 따라옵니다.

아니요{@code onStart()}
{@link android.app.Activity#onStart onStart()}액티비티가 사용자에게 표시되기 직전에 호출됩니다. +

액티비티가 전경으로 나오면 {@code onResume()}이 뒤에 따라오고, +액티비티가 숨겨지면 {@code onStop()}이 뒤에 따라옵니다.

아니요{@code onResume()}
또는
{@code onStop()}
    {@link android.app.Activity#onResume onResume()}액티비티가 시작되고 사용자와 상호 작용하기 직전에 +호출됩니다. 이 시점에서 액티비티는 액티비티 +스택의 맨 위에 있으며, 사용자 입력이 입력되고 있습니다. +

이 뒤에는 항상 {@code onPause()}가 따라옵니다.

아니요{@code onPause()}
{@link android.app.Activity#onPause onPause()}시스템이 다른 액티비티를 재개하기 직전에 +호출됩니다. 이 메서드는 일반적으로 데이터를 유지하기 위해 저장되지 않은 변경 사항을 +커밋하는 데, 애니메이션을 비롯하여 CPU를 소모하는 기타 작업을 중단하는 등 +여러 가지 용도에 사용됩니다. 이 메서드는 무슨 일을 하든 매우 빨리 끝내야 합니다. +이것이 반환될 때까지 다음 액티비티가 재개되지 않기 때문입니다. +

액티비티가 다시 전경으로 돌아오면 {@code onResume()}이 뒤에 따라오고 +액티비티가 사용자에게 보이지 않게 되면{@code onStop()}이 뒤에 따라옵니다. +

{@code onResume()}
또는
{@code onStop()}
{@link android.app.Activity#onStop onStop()}액티비티가 더 이상 사용자에게 표시되지 않게 되면 호출됩니다. 이것은 + 액티비티가 소멸되고 있기 때문에 일어날 수도 있고, 다른 액티비티 +(기존 것이든 새로운 것이든)가 재개되어 이것을 덮고 있기 때문일 수도 있습니다. +

액티비티가 다시 사용자와 상호 작용하면 +{@code onRestart()}가 뒤에 따라오고 액티비티가 사라지면 +{@code onDestroy()}가 뒤에 따라옵니다.

{@code onRestart()}
또는
{@code onDestroy()}
{@link android.app.Activity#onDestroy +onDestroy()}액티비티가 소멸되기 전에 호출됩니다. 이것이 액티비티가 받는 +마지막 호출입니다. 이것이 호출될 수 있는 경우는 액티비티가 +완료되는 중이라서(누군가가 여기에 {@link android.app.Activity#finish + finish()}를 호출해서)일 수도 있고, 시스템이 공간을 절약하기 위해 액티비티의 이 인스턴스를 일시적으로 소멸시키는 +중이기 때문일 수도 있습니다. 이와 같은 +두 가지 시나리오는 {@link + android.app.Activity#isFinishing isFinishing()} 메서드로 구분할 수 있습니다.없음
+ +

"완료 후 중단 가능?"이라는 레이블이 붙은 열은 + 시스템이 메서드가 반환된 후 액티비티 코드의 다른 줄을 실행하지 않고도 +언제든 이 액티비티를 호스팅하는 프로세스를 중단시킬 수 있는지 여부를 나타냅니다. 다음 세 가지 메서드가 "예"로 표시되어 있습니다({@link +android.app.Activity#onPause +onPause()}, {@link android.app.Activity#onStop onStop()} 및 {@link android.app.Activity#onDestroy +onDestroy()}). {@link android.app.Activity#onPause onPause()}가 세 가지 메서드 중에서 +첫 번째이므로, 액티비티가 생성되면 {@link android.app.Activity#onPause onPause()}는 프로세스가 지워지기 전에 +반드시 호출되는 마지막 메서드입니다. +시스템이 비상 시에 메모리를 복구해야 할 경우, {@link +android.app.Activity#onStop onStop()}와 {@link android.app.Activity#onDestroy onDestroy()}는 +호출되지 않을 수도 있습니다. 따라서, 중요한 영구적 데이터(사용자 편집 등)를 보관하기 위해 작성하는 경우에는 {@link android.app.Activity#onPause onPause()}를 사용해야 +합니다. 그러나, {@link android.app.Activity#onPause onPause()} 중에 +어느 정보를 유지해야 할지는 조심해서 선택해야 합니다. 이 메서드에 차단 절차가 있으면 +다음 액티비티로의 전환을 차단하고 사용자 경험을 느려지게 할 수 있기 +때문입니다.

+ +

중단 가능한 열에 "아니요"로 표시된 메서드는 액티비티를 호스팅하는 프로세스를 +보호하여 호출된 즉시 중단되지 않도록 방지합니다. 따라서 액티비티는 +{@link android.app.Activity#onPause onPause()}가 반환되는 시기부터 +{@link android.app.Activity#onResume onResume()}이 호출되는 시기 사이에 중단시킬 수 있습니다. 다시 중단시킬 수 있는 상태가 되려면 +{@link android.app.Activity#onPause onPause()}가 다시 호출되어 반환되어야 합니다.

+ +

참고: 표 1에 나타난 이런 정의에 +따르면 엄밀히 말해 "중단 가능한" 것이 아닌 액티비티라도 시스템이 중단시킬 수는 있습니다. 다만 이것은 다른 리소스가 없는 +극단적인 상황에서만 발생합니다. 액티비티가 중단될 수 있는 시기가 +언제인지는 프로세스 및 + 스레딩 문서에서 보다 자세히 논의합니다.

+ + +

액티비티 상태 저장하기

+ +

액티비티 수명 주기 관리하기 도입부에서는 액티비티가 +일시중지되거나 +중단되었더라도 액티비티의 상태는 그대로 유지된다고 잠시 언급한 바 있습니다. 이것은 +{@link android.app.Activity} 개체가 일시중지되거나 중단된 경우에도 +메모리 안에 그대로 보관되었기 때문에 가능합니다. 즉 이 액티비티의 구성원과 현재 상태에 대한 모든 정보가 아직 살아 있다는 뜻입니다. 따라서, 사용자가 +액티비티 내에서 변경한 모든 내용도 그대로 유지되어 액티비티가 전경으로 +돌아갈 때("재개"될 때) 그와 같은 변경 사항도 그대로 존재하게 됩니다.

+ +

그러나 시스템이 메모리를 복구하기 위해 액티비티를 소멸시키는 경우에는 {@link +android.app.Activity} 개체가 소멸되므로 시스템이 액티비티의 상태를 온전히 유지한 채로 간단하게 재개할 수 없게 +됩니다. 대신, 사용자가 다시 이 액티비티로 이동해 오면 시스템이 {@link android.app.Activity} 개체를 +다시 생성해야 합니다. 하지만, 사용자는 시스템이 +해당 액티비티를 소멸시켰다가 다시 생성했다는 것을 모릅니다. +따라서 액티비티가 예전과 똑같을 것이라고 예상할 것입니다. 이런 상황에서는 +액티비티 상태에 관한 정보를 저장할 수 있는 추가 콜백 메서드 +{@link +android.app.Activity#onSaveInstanceState onSaveInstanceState()}를 구현하여 액티비티 상태에 관한 중요한 정보를 보존할 수 있습니다.

+ +

시스템이 {@link android.app.Activity#onSaveInstanceState onSaveInstanceState()} +를 호출한 다음에 액티비티를 소멸되기 쉽게 만듭니다. 시스템은 {@link +android.os.Bundle#putString putString()}와 {@link +android.os.Bundle#putInt putInt()} 같은 메서드를 사용하여, 이 메서드에 +액티비티에 관한 정보를 이름-값 쌍으로 저장할 수 있는 {@link android.os.Bundle}을 전달합니다. + 그리고 시스템이 애플리케이션 프로세스를 지우고 +사용자가 액티비티로 다시 돌아오면, 시스템이 액티비티를 다시 생성하고 +{@link android.os.Bundle}을 {@link android.app.Activity#onCreate onCreate()}와 {@link +android.app.Activity#onRestoreInstanceState onRestoreInstanceState()}에게 전달합니다. 이들 메서드 중 +하나를 사용하여 {@link android.os.Bundle}에서 저장된 상태를 추출하고 액티비티 상태 +를 복원할 수 있습니다. 복구할 상태 정보가 없는 경우, 여러분에게 전달되는 {@link +android.os.Bundle}은 null입니다(액티비티가 처음 생성되었을 때 이런 경우가 +발생합니다).

+ + +

그림 2. 액티비티의 상태가 온전한 채로 사용자의 +초점에 다시 돌아오는 데에는 두 가지 방식이 있습니다. 하나는 액티비티가 소멸되었다가 다시 생성되어 액티비티가 +이전에 저장된 상태를 복구해야 하는 경우, 다른 하나는 액티비티가 중단되었다가 재개되었으며 +액티비티 상태가 온전히 유지된 경우입니다.

+ +

참고: 상태를 저장할 필요가 없는 경우도 있으므로 액티비티가 소멸되기 전에 {@link +android.app.Activity#onSaveInstanceState onSaveInstanceState()}가 호출된다는 보장은 없습니다 + +(예컨대 사용자가 +명시적으로 +액티비티를 닫기 위해 뒤로 버튼을 눌러서 액티비티를 떠날 때가 이에 해당합니다). 시스템이 {@link android.app.Activity#onSaveInstanceState +onSaveInstanceState()}를 호출하는 경우, {@link +android.app.Activity#onStop onStop()} 전에 호출하는 것이 일반적이며 {@link android.app.Activity#onPause +onPause()} 전에 호출할 가능성도 높습니다.

+ +

그러나 아무것도 하지 않고 {@link +android.app.Activity#onSaveInstanceState onSaveInstanceState()}를 구현하지 않더라도, +{@link android.app.Activity} 클래스의 기본 구현 {@link +android.app.Activity#onSaveInstanceState onSaveInstanceState()}가 일부 액티비티 상태를 복구합니다. 특히, 기본 +구현은 레이아웃에서 {@link +android.view.View}가 나올 때마다 해당하는 {@link +android.view.View#onSaveInstanceState onSaveInstanceState()} 메서드를 호출하고, 이 때문에 각 보기가 저장해야 하는 자체 관련 정보를 제공할 수 +있게 해줍니다. Android 프레임워크를 사용하는 위젯은 거의 대부분 이 메서드를 +필요에 따라 구현하므로, UI에 눈에 보이는 변경이 있으면 모두 자동으로 저장되며 액티비티를 다시 +생성하면 복구됩니다. 예를 들어, {@link android.widget.EditText} 위젯은 사용자가 입력한 모든 텍스트 +를 저장하고 {@link android.widget.CheckBox} 위젯은 확인 여부를 저장합니다. + 여러분이 해야 할 유일한 작업은 상태를 저장하고자 하는 각 위젯에 고유 ID({@code android:id} +속성 포함)를 제공하는 것입니다. 위젯에 ID가 없으면 시스템이 그 위젯의 상태를 +저장할 수 없습니다.

+ + + +

{@link +android.app.Activity#onSaveInstanceState onSaveInstanceState()}의 기본 구현이 액티비티 UI의 유용한 정보를 저장하지만 +추가 정보를 저장하려면 이를 재설정해야 할 수도 있습니다. +예를 들어, 액티비티 수명에서 변경된 구성원 값을 변경해야 할 수도 있습니다( +UI에서 복구된 값과 상관관계가 있을 수 있지만 이런 UI 값을 보유한 구성원은 기본적으로 복구되지 않습니다 +).

+ +

{@link +android.app.Activity#onSaveInstanceState onSaveInstanceState()}의 기본 구현이 UI 상태를 저장하는 데 도움이 되기 때문에, +추가 상태 정보를 저장하기 위해 이 메서드를 재정의하려면 +작업을 하기 전에 항상{@link android.app.Activity#onSaveInstanceState onSaveInstanceState()}의 슈퍼클래스 구현 +을 호출해야 합니다. 이와 마찬가지로 {@link +android.app.Activity#onRestoreInstanceState onRestoreInstanceState()}를 재정의하는 경우, 이것의 슈퍼클래스 구현을 호출해야 하기도 합니다. +이렇게 해야 기본 구현이 보기 상태를 복구할 수 있습니다.

+ +

참고: {@link android.app.Activity#onSaveInstanceState +onSaveInstanceState()}의 호출이 보장되지 않으므로 + 이것은 액티비티의 일시적 상태(UI의 상태 +)를 기록하는 데에만 사용하고, 영구 데이터를 보관하는 데 사용해서는 안 됩니다. 대신, 사용자가 액티비티를 떠날 때 영구적인 데이터(데이터베이스에 저장되어야 +하는 데이터 등)를 저장하려면 {@link +android.app.Activity#onPause onPause()}를 사용해야 합니다.

+ +

애플리케이션의 상태 저장 기능을 시험하는 좋은 방법은 기기를 회전해보고 화면 방향이 +바뀌는지 확인하는 것입니다. 화면 방향이 바뀌면 시스템은 액티비티를 +소멸시켰다가 다시 생성하여 새 화면 구성에서 이용할 수 있을지 모르는 대체 +리소스를 적용합니다. 이 이유 하나만으로도 액티비티가 다시 생성되었을 때 상태를 +완전히 복구할 수 있어야 한다는 점이 대단히 중요합니다. 사용자는 애플리케이션을 사용하면서 화면을 +자주 돌리기 때문입니다.

+ + +

구성 변경 처리하기

+ +

몇몇 기기 구성은 런타임 중에 변경될 수 있습니다(예: 화면 방향, 키보드 가용성 + 및 언어 등). 이러한 변경이 발생하면 Android는 실행 중인 액티비티를 다시 생성합니다 +(시스템이 {@link android.app.Activity#onDestroy}를 호출하고 즉시 {@link +android.app.Activity#onCreate onCreate()}를 호출합니다). 이런 동작은 +여러분이 제공한 대체 리소스로 애플리케이션을 자동으로 다시 로딩함으로써 새로운 구성에 애플리케이션이 적응하는 것을 돕도록 +설계되었습니다 +(예: 다양한 화면 방향과 크기에 대한 다양한 레이아웃).

+ +

액티비티를 적절히 설계하여 화면 방향 변경으로 인한 재시작을 감당할 수 있으며 +위에 설명한 것처럼 액티비티 상태를 복구할 수 있도록 하면, 애플리케이션이 액티비티 수명 주기에서 +예기치 못한 이벤트가 일어나도 더욱 탄력적으로 복구될 수 있습니다.

+ +

이러한 재시작을 처리하는 가장 좋은 방법은 이전 섹션에서 논의한 바와 같이 +{@link + android.app.Activity#onSaveInstanceState onSaveInstanceState()}와 {@link +android.app.Activity#onRestoreInstanceState onRestoreInstanceState()}(또는 {@link +android.app.Activity#onCreate onCreate()})를 사용하여 액티비티 상태를 저장하고 복구하는 것입니다.

+ +

런타임에 발생하는 구성 변경과 그 처리 방법에 대한 자세한 정보는 +런타임 변경 +처리하기에 대한 가이드를 읽어보십시오.

+ + + +

액티비티 조정하기

+ +

한 액티비티가 다른 액티비티를 시작하면, 둘 모두 수명 주기 전환을 겪습니다. 첫 번째 액티비티는 +일시중지하고 중단되는 반면(배경에서 계속 보이는 경우에는 중단되지 않습니다만), 다른 액티비티는 +생성되는 것입니다. 이와 같은 액티비티가 디스크 또는 다른 속에 저장된 데이터를 공유하는 경우, +첫 번째 액티비티는 두 번째 액티비티가 생성되기 전에 완전히 중단되지 않는다는 점을 이해하는 것이 중요합니다. +그렇다기보다는, 두 번째 액티비티의 시작 과정이 첫 번째 액티비티 중단 과정과 겹쳐 일어납니다. +

+ +

수명 주기 콜백은 분명히 정의된 순서가 있으며 특히 두 개의 액티비티가 +같은 프로세스 안에 있으면서 하나가 다른 하나를 시작하는 경우 순서가 더욱 확실합니다. 다음은 액티비티 A가 +액티비티 B를 시작할 때 발생하는 작업 순서입니다.

+ +
    +
  1. 액티비티 A의 {@link android.app.Activity#onPause onPause()} 메서드가 실행됩니다.
  2. + +
  3. 액티비티 B의 {@link android.app.Activity#onCreate onCreate()}, {@link +android.app.Activity#onStart onStart()}, {@link android.app.Activity#onResume onResume()} +메서드가 순차적으로 실행됩니다 (이제 사용자는 액티비티 B에 시선을 집중합니다).
  4. + +
  5. 그런 다음, 액티비티 A가 더 이상 화면에 표시되지 않는 경우 이 액티비티의 {@link +android.app.Activity#onStop onStop()} 메서드가 실행됩니다.
  6. +
+ +

이처럼 수명 주기 콜백의 순서를 예측할 수 있기 때문에 한 액티비티에서 다른 액티비티로 전환되는 + 정보를 관리할 수 있습니다. 예를 들어 첫 번째 액티비티가 중단될 때 데이터베이스에 +내용을 작성해서 다음 액티비티가 그 내용을 읽을 수 있도록 하려면, 데이터베이스에는 +{@link android.app.Activity#onPause onPause()} 중에({@link +android.app.Activity#onStop onStop()} 중이 아니라) 쓰기 작업을 해야 합니다.

+ + diff --git a/docs/html-intl/intl/ko/guide/components/bound-services.jd b/docs/html-intl/intl/ko/guide/components/bound-services.jd new file mode 100644 index 0000000000000000000000000000000000000000..bf97b260a7dad1c70405dd18841529086a47707f --- /dev/null +++ b/docs/html-intl/intl/ko/guide/components/bound-services.jd @@ -0,0 +1,658 @@ +page.title=바인딩된 서비스 +parent.title=서비스 +parent.link=services.html +@jd:body + + +
+
    +

    이 문서의 내용

    +
      +
    1. 기본 정보
    2. +
    3. 바인딩된 서비스 생성 +
        +
      1. 바인더 클래스 확장
      2. +
      3. 메신저 사용
      4. +
      +
    4. +
    5. 서비스에 바인딩
    6. +
    7. 바인딩된 서비스 수명 주기 관리
    8. +
    + +

    Key 클래스

    +
      +
    1. {@link android.app.Service}
    2. +
    3. {@link android.content.ServiceConnection}
    4. +
    5. {@link android.os.IBinder}
    6. +
    + +

    샘플

    +
      +
    1. {@code + RemoteService}
    2. +
    3. {@code + LocalService}
    4. +
    + +

    참고 항목

    +
      +
    1. 서비스
    2. +
    +
+ + +

바인딩된 서비스란 클라이언트 서버 인터페이스 안의 서버를 말합니다. 바인딩된 서비스를 사용하면 구성 요소(활동 등)를 +서비스에 바인딩되게 하거나, 요청을 보내고 응답을 수신하며 심지어는 +프로세스간 통신(IPC)까지 수행할 수 있게 됩니다. 일반적으로 바인딩된 서비스는 다른 애플리케이션 +구성 요소를 도울 때까지만 살고 배경에서 무한히 실행되지 않습니다.

+ +

이 문서는 다른 애플리케이션 구성 요소의 +서비스에 바인딩하는 방법을 포함하여 바인딩된 서비스를 만드는 방법을 보여줍니다. 하지만 일반적인 서비스에 관한 정보도 알아두는 것이 좋습니다. +서비스에서 알림을 전달하는 방법이나 서비스를 전경에서 실행되도록 설정하는 방법 등 여러 가지 +추가 정보를 알아보려면 서비스 문서를 참조하십시오.

+ + +

기본 정보

+ +

바인딩된 서비스란 일종의 {@link android.app.Service} 클래스 구현으로, +이를 통해 다른 애플리케이션이 이 서비스에 바인딩하여 상호 작용할 수 있도록 해주는 것입니다. 한 서비스에 대한 바인딩을 제공하려면, +{@link android.app.Service#onBind onBind()} 콜백 메서드를 구현해야 합니다. +이 메서드는 클라이언트가 서비스와 상호 작용하는 데 사용하는 프로그래밍 인터페이스를 정의하는 {@link android.os.IBinder} 개체를 +반환합니다.

+ + + +

클라이언트가 서비스에 바인딩하려면 {@link android.content.Context#bindService +bindService()}를 호출하면 됩니다. 이 때, 반드시 {@link +android.content.ServiceConnection}의 구현을 제공해야 하며 이것이 서비스와의 연결을 모니터링합니다. {@link +android.content.Context#bindService bindService()} 메서드는 값 없이 즉시 반환됩니다. +그러나 Android 시스템이 클라이언트와 서비스 사이의 +연결을 만드는 경우, 시스템은 {@link +android.content.ServiceConnection#onServiceConnected onServiceConnected()}를 {@link +android.content.ServiceConnection}에서 호출하여 클라이언트가 서비스와 통신하는 데 사용할 수 있도록 {@link android.os.IBinder}를 +전달하게 됩니다.

+ +

여러 클라이언트가 한 번에 서비스에 연결될 수 있습니다. 그러나, 시스템이 서비스의 +{@link android.app.Service#onBind onBind()} 메서드를 호출하여 {@link android.os.IBinder}를 검색하는 경우는 첫 번째 클라이언트가 +바인딩되는 경우뿐입니다. 시스템은 그 후 같은 {@link android.os.IBinder}를 바인딩되는 추가 +클라이언트 모두에 전달하며 이때는 {@link android.app.Service#onBind onBind()}를 다시 호출하지 않습니다.

+ +

마지막 클라이언트가 서비스에서 바인딩을 해제하면 시스템은 서비스를 소멸시킵니다( +{@link android.content.Context#startService startService()}가 서비스를 시작했을 경우 제외).

+ +

바인딩된 서비스를 구현할 때 가장 중요한 부분은 +{@link android.app.Service#onBind onBind()} 콜백 메서드가 반환하는 인터페이스를 정의하는 것입니다. +서비스의 {@link android.os.IBinder} 인터페이스를 정의하는 방법에는 몇 가지가 있고, 다음 +섹션에서는 각 기법에 관해 논의합니다.

+ + + +

바인딩된 서비스 생성

+ +

바인딩을 제공하는 서비스를 생성할 때는 클라이언트가 서비스와 상호 작용하는 데 사용할 수 있는 프로그래밍 인터페이스를 제공하는 {@link android.os.IBinder} +를 제공해야 합니다. +인터페이스를 정의하는 방법은 세 가지가 있습니다.

+ +
+
바인더 클래스 확장
+
서비스가 본인의 애플리케이션 전용이며 클라이언트와 같은 과정으로 실행되는 +경우(이런 경우가 흔함), 인터페이스를 생성할 때 {@link android.os.Binder} 클래스를 + 확장하고 그 인스턴스를 +{@link android.app.Service#onBind onBind()}에서 반환하는 방식을 사용해야 합니다. 클라이언트가 {@link android.os.Binder}를 받으며, +이를 사용하여 {@link android.os.Binder} 구현 또는 심지어 {@link android.app.Service}에서 +사용할 수 있는 공개 메서드에 직접 액세스할 수 있습니다. +

이것은 서비스가 본인의 애플리케이션을 위해 단순히 배경에서 작동하는 요소에 그치는 경우 +선호되는 기법입니다. 인터페이스를 생성할 때 이 방식을 사용하지 않는 유일한 이유는 +서비스를 다른 애플리케이션에서나 별도의 프로세스에 걸쳐 사용하고 있는 경우뿐입니다.

+ +
메신저 사용
+
인터페이스를 여러 프로세스에 걸쳐 적용되도록 해야 하는 경우, 서비스에 대한 +인터페이스를 {@link android.os.Messenger}로 생성할 수 있습니다. +이 방식을 사용하면 서비스가 여러 가지 유형의 {@link +android.os.Message} 개체에 응답하는 {@link android.os.Handler}를 정의합니다. 이 {@link android.os.Handler} +가 {@link android.os.Messenger}의 기초이며, 이를 통해 클라이언트와 함께 {@link android.os.IBinder} +를 공유할 수 있게 되어 클라이언트가 {@link +android.os.Message} 개체를 사용해 서비스에 명령을 보낼 수 있게 해줍니다. 이외에도, 클라이언트가 자체적으로 {@link android.os.Messenger}를 +정의하여 서비스가 메시지를 돌려보낼 수 있도록 할 수도 있습니다. +

이것이 프로세스간 통신(IPC)을 수행하는 가장 간단한 방법입니다. {@link +android.os.Messenger}가 모든 요청을 단일 스레드에 대기하게 해서, 서비스를 스레드로부터 안전하게 +설계하지 않아도 되기 때문입니다.

+
+ +
AIDL 사용하기
+
AIDL(Android Interface Definition Language)은 개체를 운영 체제가 이해할 수 있는 +원시 데이터로 구성 해제한 다음 여러 프로세스에 걸쳐 집결하여 IPC를 수행하기 위해 +필요한 모든 작업을 수행합니다. 이전 기법은 {@link android.os.Messenger}를 사용했는데, +사실 그 기본 구조가 AIDL을 기반으로 하고 있는 것입니다. 위에서 언급한 바와 같이 {@link android.os.Messenger}는 단일 스레드에 모든 클라이언트 요청 +대기열을 생성하므로 서비스는 한 번에 요청을 하나씩 수신합니다. 그러나, +서비스가 동시에 여러 요청을 처리하도록 하고 싶은 경우에는 AIDL을 직접 사용해도 +됩니다. 이 경우, 서비스가 다중 스레딩을 할 수 있어야 하며 스레드로부터 안전하게 구축되었어야 합니다. +

AIDL을 직접 사용하려면 +프로그래밍 인터페이스를 정의하는 {@code .aidl} 파일을 생성해야 합니다. Android SDK 도구는 +이 파일을 사용하여 인터페이스를 구현하고 IPC를 처리하는 추상 클래스를 생성하고, +그러면 개발자가 직접 이것을 서비스 내에서 확장하면 됩니다.

+
+
+ +

참고: 대부분의 애플리케이션의 경우, +바인딩된 서비스를 생성하기 위해 AIDL를 사용해서는 안 됩니다. +그러려면 다중 스레딩 기능이 필요할 수 있고, 따라서 더 복잡한 구현을 초래할 수 있기 때문입니다. 따라서 AIDL은 +대부분의 애플리케이션에 적합하지 않으므로 이 문서에서는 여러분의 서비스에 이를 이용하는 방법에 대해 다루지 않습니다. AIDL을 직접 사용해야 한다는 확신이 드는 경우, +AIDL 문서를 참조하십시오. +

+ + + + +

바인더 클래스 확장

+ +

서비스를 사용하는 것이 로컬 애플리케이션뿐이고 여러 프로세스에 걸쳐 작동할 필요가 없는 경우, +나름의 {@link android.os.Binder} 클래스를 구현하여 +클라이언트로 하여금 서비스 내의 공개 메서드에 직접 액세스할 수 있도록 할 수도 있습니다.

+ +

참고: 이것은 클라이언트와 서비스가 +같은 애플리케이션 및 프로세스에 있는 경우에만 통하며, 이 경우가 가장 보편적입니다. 이 방식이 잘 통하는 경우를 예로 들면, +음악 애플리케이션에서 자체 서비스에 액티비티를 바인딩하여 배경에서 음악을 재생하도록 해야 하는 +경우가 있습니다.

+ +

이렇게 설정하는 방법은 다음과 같습니다.

+
    +
  1. 서비스에서 다음 중 한 가지에 해당하는 {@link android.os.Binder}의 인스턴스를 생성합니다. +
      +
    • 클라이언트가 호출할 수 있는 공개 메서드 포함
    • +
    • 클라이언트가 호출할 수 있는 공개 메서드가 있는 현재{@link android.app.Service} +인스턴스를 반환
    • +
    • 클라이언트가 호출할 수 있는 공개 메서드가 포함된 서비스가 호스팅하는 다른 클래스의 인스턴스를 반환 +
    • +
    +
  2. {@link +android.app.Service#onBind onBind()} 콜백 메서드에서 이 {@link android.os.Binder}의 인스턴스를 반환합니다.
  3. +
  4. 클라이언트의 경우, {@link android.os.Binder}를 {@link +android.content.ServiceConnection#onServiceConnected onServiceConnected()} +콜백 메서드에서 받아 제공된 메서드를 사용해 서비스를 바인딩하기 위해 호출합니다.
  5. +
+ +

참고: 서비스와 클라이언트가 같은 애플리케이션에 +있어야 하는 것은 그래야만 클라이언트가 반환된 개체를 캐스팅하여 해당 API를 적절하게 호출할 수 있기 때문입니다. 또한 +서비스와 클라이언트는 같은 프로세스에 있어야 하기도 합니다. 이 기법에서는 +여러 프로세스에 걸친 집결 작업을 전혀 수행하지 않기 때문입니다.

+ +

예컨대, 어떤 서비스가 클라이언트에게 {@link android.os.Binder} 구현을 통해 서비스 내의 +메서드에 액세스할 수 있도록 한다고 합시다.

+ +
+public class LocalService extends Service {
+    // Binder given to clients
+    private final IBinder mBinder = new LocalBinder();
+    // Random number generator
+    private final Random mGenerator = new Random();
+
+    /**
+     * Class used for the client Binder.  Because we know this service always
+     * runs in the same process as its clients, we don't need to deal with IPC.
+     */
+    public class LocalBinder extends Binder {
+        LocalService getService() {
+            // Return this instance of LocalService so clients can call public methods
+            return LocalService.this;
+        }
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return mBinder;
+    }
+
+    /** method for clients */
+    public int getRandomNumber() {
+      return mGenerator.nextInt(100);
+    }
+}
+
+ +

{@code LocalBinder}는 클라이언트에게 {@code LocalService}의 현재 인스턴스를 검색하기 위한 {@code getService()} 메서드 +를 제공합니다. 이렇게 하면 클라이언트가 서비스 내의 공개 메서드를 호출할 수 있습니다. + 클라이언트는 예컨대 서비스에서 {@code getRandomNumber()}를 호출할 수 있습니다.

+ +

다음은 버튼을 클릭했을 때 {@code LocalService}에 바인딩되며 {@code getRandomNumber()} +를 호출하는 액티비티를 나타낸 것입니다.

+ +
+public class BindingActivity extends Activity {
+    LocalService mService;
+    boolean mBound = false;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.main);
+    }
+
+    @Override
+    protected void onStart() {
+        super.onStart();
+        // Bind to LocalService
+        Intent intent = new Intent(this, LocalService.class);
+        bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
+    }
+
+    @Override
+    protected void onStop() {
+        super.onStop();
+        // Unbind from the service
+        if (mBound) {
+            unbindService(mConnection);
+            mBound = false;
+        }
+    }
+
+    /** Called when a button is clicked (the button in the layout file attaches to
+      * this method with the android:onClick attribute) */
+    public void onButtonClick(View v) {
+        if (mBound) {
+            // Call a method from the LocalService.
+            // However, if this call were something that might hang, then this request should
+            // occur in a separate thread to avoid slowing down the activity performance.
+            int num = mService.getRandomNumber();
+            Toast.makeText(this, "number: " + num, Toast.LENGTH_SHORT).show();
+        }
+    }
+
+    /** Defines callbacks for service binding, passed to bindService() */
+    private ServiceConnection mConnection = new ServiceConnection() {
+
+        @Override
+        public void onServiceConnected(ComponentName className,
+                IBinder service) {
+            // We've bound to LocalService, cast the IBinder and get LocalService instance
+            LocalBinder binder = (LocalBinder) service;
+            mService = binder.getService();
+            mBound = true;
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName arg0) {
+            mBound = false;
+        }
+    };
+}
+
+ +

위 예시는 클라이언트가 +{@link android.content.ServiceConnection} 구현과 {@link +android.content.ServiceConnection#onServiceConnected onServiceConnected()} 콜백을 사용하여 서비스에 바인딩하는 방법을 보여줍니다. 다음 +섹션에서는 서비스에 바인딩하는 이러한 과정에 대해 좀 더 자세한 정보를 제공합니다.

+ +

참고: 위 예시에서는 서비스에서 분명히 바인딩을 해제하지는 않습니다. +그러나 모든 클라이언트는 적절한 시점에 바인딩을 해제해야 합니다(액티비티가 일시 중지될 때 등).

+ +

더 많은 샘플 코드를 보려면 ApiDemos에서 {@code +LocalService.java} 클래스와 {@code +LocalServiceActivities.java} 클래스를 참조하십시오.

+ + + + + +

메신저 사용

+ + + +

서비스가 원격 프로세스와 통신해야 한다면 서비스에 인터페이스를 제공하는 데 +{@link android.os.Messenger}를 사용하면 됩니다. 이 기법을 사용하면 +AIDL을 쓰지 않고도 프로세스간 통신(IPC)을 수행할 수 있게 해줍니다.

+ +

다음은 {@link android.os.Messenger} 사용 방법을 간략하게 요약한 것입니다.

+ +
    +
  • 서비스가 클라이언트로부터 각 호출에 대해 콜백을 받는 {@link android.os.Handler}를 +구현합니다.
  • +
  • {@link android.os.Handler}를 사용하여 {@link android.os.Messenger} 개체를 생성합니다 +(이것은 {@link android.os.Handler}에 대한 참조입니다).
  • +
  • {@link android.os.Messenger}가 {@link android.os.IBinder}를 생성하여 서비스가 +{@link android.app.Service#onBind onBind()}로부터 클라이언트에게 반환하도록 합니다.
  • +
  • 클라이언트는 {@link android.os.IBinder}를 사용하여 {@link android.os.Messenger} +(서비스의 {@link android.os.Handler}를 참조)를 인스턴트화하고, 이를 이용하여 +{@link android.os.Message} 개체를 서비스에 전송합니다.
  • +
  • 서비스가 각 {@link android.os.Message}를 {@link +android.os.Handler}로 수신합니다. 구체적으로는 {@link android.os.Handler#handleMessage +handleMessage()} 메서드를 사용합니다.
  • +
+ + +

이렇게 하면, 클라이언트가 서비스에서 호출할 "메서드"가 없습니다. 대신 클라이언트는 +"메시지"({@link android.os.Message} 개체)를 전달하여 서비스가 +{@link android.os.Handler}로 받을 수 있도록 합니다.

+ +

다음은 {@link android.os.Messenger} 인터페이스를 사용하는 간단한 예시 서비스입니다.

+ +
+public class MessengerService extends Service {
+    /** Command to the service to display a message */
+    static final int MSG_SAY_HELLO = 1;
+
+    /**
+     * Handler of incoming messages from clients.
+     */
+    class IncomingHandler extends Handler {
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_SAY_HELLO:
+                    Toast.makeText(getApplicationContext(), "hello!", Toast.LENGTH_SHORT).show();
+                    break;
+                default:
+                    super.handleMessage(msg);
+            }
+        }
+    }
+
+    /**
+     * Target we publish for clients to send messages to IncomingHandler.
+     */
+    final Messenger mMessenger = new Messenger(new IncomingHandler());
+
+    /**
+     * When binding to the service, we return an interface to our messenger
+     * for sending messages to the service.
+     */
+    @Override
+    public IBinder onBind(Intent intent) {
+        Toast.makeText(getApplicationContext(), "binding", Toast.LENGTH_SHORT).show();
+        return mMessenger.getBinder();
+    }
+}
+
+ +

{@link android.os.Handler}의 {@link android.os.Handler#handleMessage handleMessage()} 메서드에서 +서비스가 수신되는 {@link android.os.Message}를 받고 +{@link android.os.Message#what} 구성원에 기초하여 무엇을 할지 결정한다는 점을 눈여겨 보십시오.

+ +

클라이언트는 서비스가 반환한 {@link +android.os.IBinder}에 기초하여 {@link android.os.Messenger}를 생성하고 {@link +android.os.Messenger#send send()}로 메시지를 전송하기만 하면 됩니다. 예를 들어, 다음은 +서비스에 바인딩하여 {@code MSG_SAY_HELLO} 메시지를 서비스에 전달하는 간단한 액티비티입니다.

+ +
+public class ActivityMessenger extends Activity {
+    /** Messenger for communicating with the service. */
+    Messenger mService = null;
+
+    /** Flag indicating whether we have called bind on the service. */
+    boolean mBound;
+
+    /**
+     * Class for interacting with the main interface of the service.
+     */
+    private ServiceConnection mConnection = new ServiceConnection() {
+        public void onServiceConnected(ComponentName className, IBinder service) {
+            // This is called when the connection with the service has been
+            // established, giving us the object we can use to
+            // interact with the service.  We are communicating with the
+            // service using a Messenger, so here we get a client-side
+            // representation of that from the raw IBinder object.
+            mService = new Messenger(service);
+            mBound = true;
+        }
+
+        public void onServiceDisconnected(ComponentName className) {
+            // This is called when the connection with the service has been
+            // unexpectedly disconnected -- that is, its process crashed.
+            mService = null;
+            mBound = false;
+        }
+    };
+
+    public void sayHello(View v) {
+        if (!mBound) return;
+        // Create and send a message to the service, using a supported 'what' value
+        Message msg = Message.obtain(null, MessengerService.MSG_SAY_HELLO, 0, 0);
+        try {
+            mService.send(msg);
+        } catch (RemoteException e) {
+            e.printStackTrace();
+        }
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.main);
+    }
+
+    @Override
+    protected void onStart() {
+        super.onStart();
+        // Bind to the service
+        bindService(new Intent(this, MessengerService.class), mConnection,
+            Context.BIND_AUTO_CREATE);
+    }
+
+    @Override
+    protected void onStop() {
+        super.onStop();
+        // Unbind from the service
+        if (mBound) {
+            unbindService(mConnection);
+            mBound = false;
+        }
+    }
+}
+
+ +

이 예시에는 서비스가 클라이언트에 응답하는 방식이 나타나 있지 않다는 것을 유념하십시오. 서비스가 응답하게 하려면 + 클라이언트에도 {@link android.os.Messenger}를 생성해야 합니다. +클라이언트가 {@link android.content.ServiceConnection#onServiceConnected +onServiceConnected()} 콜백을 받으면 {@link android.os.Messenger#send send()}메서드의 {@link android.os.Message#replyTo} 매개변수에서 클라이언트의 {@link android.os.Messenger}를 포함하는 {@link android.os.Message}를 +서비스에 전송합니다. +

+ +

양방향 메시지를 제공하는 방법의 예시는 {@code +MessengerService.java}(서비스) 및 {@code +MessengerServiceActivities.java}(클라이언트) 예시를 참조하십시오.

+ + + + + +

서비스에 바인딩

+ +

애플리케이션 구성 요소(클라이언트)를 서비스에 바인딩하려면 +{@link android.content.Context#bindService bindService()}를 호출하면 됩니다. 그러면 Android +system이 서비스의 {@link android.app.Service#onBind +onBind()} 메서드를 호출하고, 이는 서비스와의 상호 작용을 위해 {@link android.os.IBinder}를 반환합니다.

+ +

바인딩은 비동기식입니다. {@link android.content.Context#bindService +bindService()}는 즉시 반환하고 클라이언트에게 {@link android.os.IBinder}를 반환하지 않습니다. + {@link android.os.IBinder}를 수신하려면 클라이언트는 {@link +android.content.ServiceConnection}의 인스턴스를 생성하여 이를 {@link android.content.Context#bindService +bindService()}에 전달해야 합니다. {@link android.content.ServiceConnection}에는 +{@link android.os.IBinder}를 전달하기 위해 시스템이 호출하는 콜백 메서드가 포함됩니다.

+ +

참고: 서비스에 바인딩할 수 있는 것은 액티비티, 서비스 및 +콘텐츠 제공자뿐입니다. 브로드캐스트 수신자로부터는 서비스에 바인딩할 수 없습니다.

+ +

따라서, 클라이언트로부터 서비스에 바인딩하려면 다음과 같이 해야 합니다.

+
    +
  1. {@link android.content.ServiceConnection}을 구현합니다. +

    이 구현으로 두 가지 콜백 메서드를 재정의해야 합니다.

    +
    +
    {@link android.content.ServiceConnection#onServiceConnected onServiceConnected()}
    +
    시스템이 이것을 호출하여 서비스의 +{@link android.app.Service#onBind onBind()} 메서드가 반환한 {@link android.os.IBinder}를 전달합니다.
    +
    {@link android.content.ServiceConnection#onServiceDisconnected +onServiceDisconnected()}
    +
    Android 시스템이 이것을 호출하는 경우는 서비스로의 연결이 +예기치 못하게 끊어졌을 때, 즉 서비스가 충돌했거나 중단되었을 때 등입니다. +클라이언트가 바인딩을 해제한다고 이것이 호출되지는 않습니다.
    +
    +
  2. +
  3. {@link +android.content.Context#bindService bindService()}를 호출하고 {@link +android.content.ServiceConnection} 구현을 전달합니다.
  4. +
  5. 시스템이 {@link android.content.ServiceConnection#onServiceConnected +onServiceConnected()} 콜백 메서드를 호출하면, 인터페이스가 정의한 메서드를 사용하여 +서비스에 호출을 시작해도 됩니다.
  6. +
  7. 서비스로부터 연결을 해제하려면 {@link +android.content.Context#unbindService unbindService()}를 호출합니다. +

    클라이언트가 소멸되면 이는 서비스에서 바인딩을 해제하게 되지만, +서비스와 상호 작용을 마쳤을 때 또는 액티비티가 일시 중지되었을 때에는 항상 바인딩을 해제해야 합니다. +이렇게 해야 서비스가 사용 중이 아닐 때에는 중지할 수 있습니다 +(바인딩과 바인딩 해제의 적절한 시기는 아래에서 좀 더 논의합니다).

    +
  8. +
+ +

예를 들어, 다음 코드 조각은 위와 같이 +바인더 클래스를 확장해서 생성한 서비스와 클라이언트를 연결합니다. 그러므로 이것이 해야 하는 일은 반환된 +{@link android.os.IBinder}를 {@code LocalService} 클래스에 캐스팅하고 {@code +LocalService} 인스턴스를 요청하는 것뿐입니다.

+ +
+LocalService mService;
+private ServiceConnection mConnection = new ServiceConnection() {
+    // Called when the connection with the service is established
+    public void onServiceConnected(ComponentName className, IBinder service) {
+        // Because we have bound to an explicit
+        // service that is running in our own process, we can
+        // cast its IBinder to a concrete class and directly access it.
+        LocalBinder binder = (LocalBinder) service;
+        mService = binder.getService();
+        mBound = true;
+    }
+
+    // Called when the connection with the service disconnects unexpectedly
+    public void onServiceDisconnected(ComponentName className) {
+        Log.e(TAG, "onServiceDisconnected");
+        mBound = false;
+    }
+};
+
+ +

이 {@link android.content.ServiceConnection}이 있으면 클라이언트는 +이것을 {@link android.content.Context#bindService bindService()}에 전달하여 서비스에 바인딩할 수 있습니다. 예:

+ +
+Intent intent = new Intent(this, LocalService.class);
+bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
+
+ +
    +
  • {@link android.content.Context#bindService bindService()}의 첫 번째 매개변수는 바인딩할 서비스를 명시적으로 명명하는 +{@link android.content.Intent}입니다(인텐트는 +암시적일 수 있음).
  • +
  • 두 번째 매개변수는 {@link android.content.ServiceConnection} 개체입니다.
  • +
  • 세 번째 매개변수는 바인딩 옵션을 나타내는 플래그입니다. 서비스를 생성하기 위해 보통은 {@link +android.content.Context#BIND_AUTO_CREATE}를 사용합니다(이미 살아 있는 상태가 아닌 경우). +가능한 기타 값은 {@link android.content.Context#BIND_DEBUG_UNBIND} + 및 {@link android.content.Context#BIND_NOT_FOREGROUND}, 또는 값이 없는 경우 {@code 0}입니다.
  • +
+ + +

추가 참고 사항

+ +

다음은 서비스에 바인딩하는 데 관한 몇 가지 중요한 참고 사항입니다.

+
    +
  • 항상 {@link android.os.DeadObjectException} 예외를 트래핑해야 합니다. +이것은 연결이 끊어지면 발생합니다. 원격 메서드에 의해 발생하는 예외는 이것뿐입니다.
  • +
  • 개체는 여러 프로세스에 걸쳐 감안된 참조입니다.
  • +
  • 일반적으로, 클라이언트의 수명 주기를 +결합하고 분해하는 순간을 일치시키면서 바인딩과 바인딩 해제를 페어링해야 합니다. 예: +
      +
    • 액티비티가 눈에 보이는 동안에만 서비스와 상호 작용해야 한다면 +{@link android.app.Activity#onStart onStart()}에는 바인딩하고 {@link +android.app.Activity#onStop onStop()}에는 바인딩을 해제해야 합니다.
    • +
    • +배경에서 중단되었을 때도 액티비티가 응답을 받게 하고 싶다면 {@link android.app.Activity#onCreate onCreate()}에는 바인딩하고 +{@link android.app.Activity#onDestroy onDestroy()} 중에는 바인딩을 해제합니다. +이때, 사용자의 액티비티가 서비스가 실행되는 시간 전체에서(배경에서라도) 서비스를 사용한다는 것을 유념해야 합니다. +서비스가 다른 프로세스에 있을 경우, 사용자가 프로세스의 가중치를 높이면 시스템이 +이를 중단할 가능성이 높아집니다.
    • +
    +

    참고: 일반적으로는, 액티비티의 {@link android.app.Activity#onResume onResume()}와 {@link +android.app.Activity#onPause onPause()}에는 바인딩하거나 바인딩을 해제하지 말아야 합니다. 이러한 콜백은 모든 수명 주기 전환에서 발생하고 +이런 전환에서 발생하는 처리는 +최소한으로 유지해야 하기 때문입니다. 또한, +사용자 애플리케이션의 여러 액티비티가 동일한 서비스에 바인딩되었고 +두 액티비티 사이에 전환이 있을 경우, 현재 액티비티의 바인딩이 해제된 후(일시중지 중) 다음 액티비티가 바인딩하기 전(재개 중)에 +서비스가 제거되었다가 다시 생성될 수 있습니다 (수명 주기를 조절하기 위한 이러한 액티비티 전환은 +액티비티 +문서에 설명되어 있습니다).

    +
+ +

더 많은 샘플 코드를 보려면 ApiDemos{@code +RemoteService.java} 클래스를 참조하십시오.

+ + + + + +

바인딩된 서비스 수명 주기 관리

+ +

서비스가 모든 클라이언트로부터 바인딩 해제되면, Android 시스템이 이를 소멸시킵니다(다만 +{@link android.app.Service#onStartCommand onStartCommand()}와도 함께 시작된 경우는 예외). +따라서, 서비스가 순전히 바인딩된 서비스일 경우에는 해당 서비스의 수명 주기를 관리하지 않아도 됩니다. +클라이언트에 바인딩되었는지를 근거로 Android 시스템이 대신 관리해주기 때문입니다.

+ +

그러나 {@link android.app.Service#onStartCommand +onStartCommand()} 콜백 메서드를 구현하기로 선택하는 경우라면 서비스를 확실히 중지해야 합니다. +서비스가 현재 시작된 것으로 간주되기 때문입니다. 이런 경우, 서비스는 클라이언트에 바인딩되었는지 여부와 관계없이 +{@link android.app.Service#stopSelf()}와 함께 스스로 중단되거나 다른 구성 요소가{@link +android.content.Context#stopService stopService()}를 호출할 때까지 실행됩니다. +

+ +

또한, 서비스가 시작되고 바인딩을 허용할 경우 시스템이 +{@link android.app.Service#onUnbind onUnbind()} 메서드를 호출하면 +{@code true}를 선택적으로 반환할 수 있습니다. 다음에 클라이언트가 서비스에 바인딩할 때({@link +android.app.Service#onBind onBind()}에 대한 호출을 수신하지 않고) {@link android.app.Service#onRebind +onRebind()}에 대한 호출을 받을지 여부에 따라 결정됩니다. {@link android.app.Service#onRebind +onRebind()}가 void를 반환하였지만 클라이언트가 +{@link android.content.ServiceConnection#onServiceConnected onServiceConnected()} 콜백에서 {@link android.os.IBinder}를 여전히 수신합니다. +아래 그림 1은 이런 수명 주기의 논리를 나타냅니다.

+ + + +

그림 1. 시작되었으며 바인딩도 허용하는 서비스의 수명 주기입니다. +

+ + +

시작된 서비스의 수명 주기에 관한 자세한 정보는 서비스 문서를 참조하십시오.

+ + + + diff --git a/docs/html-intl/intl/ko/guide/components/fragments.jd b/docs/html-intl/intl/ko/guide/components/fragments.jd new file mode 100644 index 0000000000000000000000000000000000000000..a41250c38d9ffd2104331a936ad78fa29069a498 --- /dev/null +++ b/docs/html-intl/intl/ko/guide/components/fragments.jd @@ -0,0 +1,812 @@ +page.title=프래그먼트 +parent.title=액티비티 +parent.link=activities.html +@jd:body + + + +

{@link android.app.Fragment}는 동작 또는 +{@link android.app.Activity} 내에서 사용자 인터페이스의 일부를 나타냅니다. 여러 개의 프래그먼트를 하나의 액티비티에 +조합하여 창이 여러 개인 UI를 구축할 수 있으며, 하나의 프래그먼트를 여러 액티비티에서 재사용할 수 있습니다. 프래그먼트는 자체 수명 주기를 가지고, 자체 입력 이벤트를 받으며, +액티비티 실행 중에 추가 및 제거가 가능한 액티비티의 모듈식 섹션이라고 +생각하면 됩니다(다른 액티비티에 +재사용할 수 있는 "하위 액티비티"와 같은 개념).

+ +

프래그먼트는 항상 액티비티 내에 포함되어 있어야 하며 해당 프래그먼트의 수명 주기는 +호스트 액티비티의 수명 주기에 직접적으로 영향을 받습니다. 예를 들어 액티비티가 일시정지되는 경우, 그 안의 모든 프래그먼트도 +일시정지되며 액티비티가 소멸되면 모든 프래그먼트도 마찬가지로 소멸됩니다. 그러나 액티비티가 실행 중인 +동안에는(재개됨 수명 주기 상태에 있을 때를 말합니다) +각 프래그먼트를 추가 또는 제거하는 등 개별적으로 조작할 수 있습니다. 그와 같은 프래그먼트 트랜잭션을 +수행할 때에는 이를 액티비티가 관리하는 백 스택에도 +추가할 수 있습니다. 각 백 스택 항목이 발생한 프래그먼트 트랜잭션의 +기록이 됩니다. 이 백 스택을 사용하면 사용자가 프래그먼트 트랜잭션을 거꾸로 돌릴 수 있습니다(뒤로 이동). +이때 뒤로 버튼을 누르면 됩니다.

+ +

프래그먼트를 액티비티 레이아웃의 일부로 추가하는 경우, 이는 액티비티의 보기 계층 내부의 {@link +android.view.ViewGroup} 안에 살며, 해당 프래그먼트가 자신의 보기 +레이아웃을 정의합니다. +프래그먼트를 액티비티 레이아웃에 삽입하려면 해당 프래그먼트를 +액티비티의 레이아웃 파일에서 {@code <fragment>} 요소로 선언하거나, 애플리케이션 코드에서 이를 +기존의 {@link android.view.ViewGroup}에 추가하면 됩니다. 그러나 프래그먼트가 +액티비티 레이아웃의 일부분이어야만 하는 것은 아닙니다. 나름의 UI가 없는 프래그먼트도 액티비티를 위한 +보이지 않는 작업자로 사용할 수 있습니다.

+ +

이 문서에서는 프래그먼트를 사용하도록 애플리케이션을 구축하는 법을 +설명합니다. 그중에는 프래그먼트를 액티비티의 백 스택에 추가했을 때 프래그먼트가 자신의 상태를 유지하는 방법, +액티비티 및 액티비티 내의 다른 프래그먼트와 이벤트를 공유하는 방법과 액티비티의 +작업 모음에 참가하는 법 등등 여러 가지가 포함됩니다.

+ + +

디자인 철학

+ +

Android가 프래그먼트를 처음 도입한 것은 Android 3.0(API 레벨 11)부터입니다. 기본적으로 +태블릿과 같은 큰 화면에서 보다 역동적이고 유연한 UI 디자인을 지원하는 것이 목적이었습니다. 태블릿의 화면은 +핸드셋 화면보다 훨씬 크기 때문에 UI 구성 요소를 조합하고 상호 교환할 공간이 +더 많습니다. 프래그먼트는 개발자가 보기 계층에 복잡한 변경 내용을 관리하지 않아도 +그러한 디자인을 사용할 수 있도록 해주는 것입니다. 한 액티비티의 레이아웃을 여러 프래그먼트로 나누면 +런타임에 액티비티의 외관을 수정할 수도 있고 그러한 변경 내용을 해당 액티비티가 관리하는 +백 스택에 보존할 수도 있습니다.

+ +

예를 들어 뉴스 애플리케이션이라면 하나의 프래그먼트를 사용하여 +왼쪽에 기사 목록을 표시하도록 하고 또 다른 프래그먼트로 오른쪽에 기사 내용을 표시하도록 할 수 있습니다. 두 프래그먼트 모두 +한 액티비티에서 양쪽으로 나란히 나타나며, 각 프래그먼트에 나름의 수명 주기 콜백 메서드가 있고 +각자 사용자 입력 이벤트를 따로 처리하게 됩니다. 따라서, 사용자는 기사를 선택하는 데 한 액티비티를 쓰고 +기사를 읽는 데 또 다른 액티비티를 선택하는 대신에 같은 액티비티 안에서 기사를 선택하고 읽는 과정을 +모두 끝낼 수 있는 것입니다. 이것은 그림 1에 나타낸 태블릿 레이아웃과 같습니다.

+ +

프래그먼트를 디자인할 때에는 각 프래그먼트를 모듈식이며 재사용 가능한 액티비티 구성 요소로 만들어야 합니다. +다시 말해, 각 프래그먼트가 나름의 레이아웃을 따로 정의하고 자기만의 수명 주기 콜백으로 자기 나름의 동작을 정의하기 때문에 +한 프래그먼트를 여러 액티비티에 포함시킬 수 있습니다. 그러므로 재사용을 염두에 두고 디자인하며 +한 프래그먼트를 또 다른 프래그먼트로부터 직접 조작하는 것은 삼가야 합니다. 이것은 특히 모듈식 프래그먼트를 사용하면 +프래그먼트 조합을 여러 가지 화면 크기에 맞춰 변경할 수 있도록 해주기 때문에 중요한 요점입니다. 태블릿과 핸드셋을 모두 지원하는 +애플리케이션을 디자인하는 경우, 사용 가능한 화면 공간을 토대로 사용자 경험을 최적화하도록 프래그먼트를 +여러 레이아웃 구성에 재사용할 수 있습니다. 예를 들어 핸드셋에서의 경우 +프래그먼트를 분리해서 단일 창 UI를 제공하도록 해야할 수 있습니다. 같은 액티비티 안에 하나 이상이 들어가지 않을 수 +있기 때문입니다.

+ + +

그림 1. 프래그먼트가 정의한 두 가지 UI 모듈이 +태블릿 디자인에서는 하나의 액티비티로 조합될 수 있는 반면 핸드셋 디자인에서는 분리될 수 있다는 것을 +예시로 나타낸 것입니다.

+ +

예를 들어—뉴스 애플리케이션 예시를 계속 사용하겠습니다—이 애플리케이션을 태블릿 크기의 기기에서 실행하는 경우, +애플리케이션 내의 액티비티 A 안에 두 개의 프래그먼트를 포함시킬 수 있습니다. 그러나 +핸드셋 크기의 화면에서라면 두 프래그먼트를 모두 쓸 만큼 공간이 충분치 않습니다. +따라서 액티비티 A에는 기사 목록에 해당되는 프래그먼트만 포함하고, 사용자가 기사를 하나 선택하면 이것이 +액티비티 B를 시작합니다. 여기에 기사를 읽을 두 번째 프래그먼트가 포함되어 있습니다. 따라서 이 애플리케이션은 +서로 다른 조합으로 프래그먼트를 재사용함으로써 태블릿과 핸드셋을 둘 다 지원하는 +것입니다(그림 1 참조).

+ +

여러 가지 화면 구성에 맞게 여러 가지 프래그먼트 조합으로 애플리케이션을 디자인하는 법에 대한 자세한 정보는 +태블릿 및 핸드셋 지원에 대한 가이드를 참조하십시오.

+ + + +

프래그먼트 생성

+ +
+ +

그림 2. 프래그먼트의 수명 주기( +소속 액티비티가 실행 중일 때).

+
+ +

프래그먼트를 생성하려면 {@link android.app.Fragment}의 하위 클래스(또는 이의 기존 +하위 클래스)를 생성해야 합니다. {@link android.app.Fragment} 클래스에는 +{@link android.app.Activity}와 아주 유사해 보이는 코드가 있습니다. 여기에는 액티비티와 비슷한 콜백 메서드가 들어 있습니다. +예를 들어 {@link android.app.Fragment#onCreate onCreate()}, {@link android.app.Fragment#onStart onStart()}, +{@link android.app.Fragment#onPause onPause()} 및 {@link android.app.Fragment#onStop onStop()} 등입니다. 사실, +기존 Android 애플리케이션을 변환하여 프래그먼트를 사용하도록 하려면 그저 +액티비티의 콜백 메서드에서 프래그먼트에 해당되는 콜백 메서드로 코드를 옮기기만 하면 +될 수도 있습니다.

+ +

보통은 최소한 다음과 같은 수명 주기 메서드를 구현해야 합니다.

+ +
+
{@link android.app.Fragment#onCreate onCreate()}
+
시스템은 프래그먼트를 생성할 때 이것을 호출합니다. 구현 내에서 프래그먼트의 기본 구성 요소 중 +프래그먼트가 일시정지되거나 중단되었다가 재개되었을 때 유지하고자 하는 것을 +초기화해야 합니다.
+
{@link android.app.Fragment#onCreateView onCreateView()}
+
시스템은 프래그먼트가 자신의 사용자 인터페이스를 처음으로 그릴 시간이 되면 +이것을 호출합니다. 프래그먼트에 맞는 UI를 그리려면 메서드에서 {@link android.view.View}를 반환해야 합니다. +이 메서드는 프래그먼트 레이아웃의 루트입니다. 프래그먼트가 UI를 제공하지 않는 경우 null을 반환하면 +됩니다.
+
{@link android.app.Activity#onPause onPause()}
+
시스템이 이 메서드를 호출하는 것은 사용자가 프래그먼트를 떠난다는 +첫 번째 신호입니다(다만 이것이 항상 프래그먼트가 소멸 중이라는 뜻은 아닙니다). 현재 사용자 세션을 넘어서 +지속되어야 하는 변경 사항을 커밋하려면 보통 이곳에서 해아 합니다(사용자가 +돌아오지 않을 수 있기 때문입니다).
+
+ +

대부분의 애플리케이션은 각각의 프래그먼트에 이와 같은 메서드를 최소한 세 개씩 +구현해야 하지만, 프래그먼트 수명 주기의 여러 단계를 처리하려면 사용해야 하는 다른 콜백 메서드도 +많이 있습니다. 모든 수명 주기 콜백 메서드는 나중에 +프래그먼트 수명 주기 처리 섹션에서 더욱 상세히 논의할 것입니다.

+ + +

이외에도, 기본적인 {@link +android.app.Fragment} 클래스 대신 확장하고자 하는 하위 클래스도 몇 개 있을 수 있습니다.

+ +
+
{@link android.app.DialogFragment}
+
부동 대화 창을 표시합니다. 이 클래스를 사용하여 대화를 생성하면 +{@link android.app.Activity} 클래스의 대화 도우미 메서드를 사용하는 것의 +좋은 대안책이 됩니다. 이렇게 하면 프래그먼트 대화를 액티비티가 관리하는 프래그먼트의 백 스택에 통합시킬 수 있어, +사용자가 무시된 프래그먼트를 반환할 수 있도록 해주기 때문입니다.
+ +
{@link android.app.ListFragment}
+
어댑터가 관리하는 항목의 목록(예: {@link +android.widget.SimpleCursorAdapter})을 표시하며, {@link android.app.ListActivity}와 비슷합니다. +이것은 목록 보기를 관리하는 데 쓰는 몇 가지 메서드를 제공합니다. 예를 들어 {@link +android.app.ListFragment#onListItemClick(ListView,View,int,long) onListItemClick()} 콜백을 +제공하여 클릭 이벤트를 처리하는 것 등입니다.
+ +
{@link android.preference.PreferenceFragment}
+
{@link android.preference.Preference} 객체의 계층을 목록으로 표시하며, +{@link android.preference.PreferenceActivity}와 비슷합니다. 이것은 +애플리케이션에 대한 "설정" 액티비티를 생성할 때 유용합니다.
+
+ + +

사용자 인터페이스 추가하기

+ +

프래그먼트는 일반적으로 액티비티에 속한 사용자 인터페이스의 일부분으로 사용되며 +자기 나름의 레이아웃으로 액티비티에 참가합니다.

+ +

프래그먼트에 대해 레이아웃을 제공하려면 반드시 {@link +android.app.Fragment#onCreateView onCreateView()} 콜백 메서드를 구현해야 합니다. +이것은 프래그먼트가 자신의 레이아웃을 그릴 때가 되면 Android 시스템이 호출하는 것입니다. 이 메서드의 구현은 반드시 +{@link android.view.View}를 반환해야 합니다. 이것은 프래그먼트 레이아웃의 루트입니다.

+ +

참고: 프래그먼트가 {@link +android.app.ListFragment}의 하위 클래스인 경우, 기본 구현이 +{@link android.app.Fragment#onCreateView onCreateView()}로부터 {@link android.widget.ListView}를 반환하므로 이를 구현하지 않아도 됩니다.

+ +

{@link +android.app.Fragment#onCreateView onCreateView()}로부터 레이아웃을 반환하려면 이를 XML에서 정의된 레이아웃 리소스로부터 팽창시키면 됩니다. 이를 돕기 위해 +{@link android.app.Fragment#onCreateView onCreateView()}가 +{@link android.view.LayoutInflater} 객체를 제공합니다.

+ +

예를 들어 다음은 {@link android.app.Fragment}의 하위 클래스입니다. 이것이 +{@code example_fragment.xml} 파일로부터 레이아웃을 로딩합니다.

+ +
+public static class ExampleFragment extends Fragment {
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+                             Bundle savedInstanceState) {
+        // Inflate the layout for this fragment
+        return inflater.inflate(R.layout.example_fragment, container, false);
+    }
+}
+
+ + + +

{@link android.app.Fragment#onCreateView +onCreateView()}로 전달된 {@code container} 매개변수가 상위 {@link android.view.ViewGroup}이며(액티비티의 레이아웃으로부터), +이 안에 프래그먼트 레이아웃이 삽입됩니다. + {@code savedInstanceState} 매개변수는 일종의 {@link android.os.Bundle}로, +이것은 프래그먼트가 재개되는 중인 경우 프래그먼트의 이전 인스턴스에 대한 데이터를 +제공합니다(상태를 복원하는 것은 프래그먼트 수명 주기 +처리에서 좀 더 논의합니다).

+ +

{@link android.view.LayoutInflater#inflate(int,ViewGroup,boolean) inflate()} 메서드는 +다음과 같은 세 개의 인수를 취합니다.

+
    +
  • 팽창시키고자 하는 레이아웃의 리소스 ID.
  • +
  • 팽창된 레이아웃의 상위가 될 {@link android.view.ViewGroup}. {@code +container}를 전달해야 시스템이 레이아웃 매개변수를 팽창된 레이아웃의 루트 보기에 실행 중인 상위 보기에서 지정한 것과 같이 +적용할 수 있으므로 이는 중요한 부분입니다.
  • +
  • 팽창된 레이아웃이 팽창 중에 {@link +android.view.ViewGroup}(두 번째 매개변수)에 첨부되어야 하는지를 나타내는 부울 값 (이 경우, +이것은 거짓입니다. 시스템이 이미 팽창된 레이아웃을 {@code +container} 안에 삽입하고 있기 때문입니다. 참을 전달하면 최종 레이아웃에 중복된 보기 그룹을 생성하게 됩니다).
  • +
+ +

이제 레이아웃을 제공하는 프래그먼트 생성하는 법을 알게 되셨습니다. 다음은 프래그먼트를 +액티비티에 추가해야 합니다.

+ + + +

액티비티에 프래그먼트 추가

+ +

프래그먼트는 보통 UI의 일부분으로 호스트 액티비티에 참가합니다. 이는 해당 액티비티의 +전반적인 보기 계층의 일부분으로 포함되게 됩니다. 프래그먼트를 액티비티 레이아웃에 추가하는 데에는 두 가지 방법이 +있습니다.

+ +
    +
  • 프래그먼트를 액티비티의 레이아웃 파일 안에서 선언합니다. +

    이 경우, 프래그먼트에 대한 레이아웃 속성을 마치 +보기인 것처럼 나타낼 수 있습니다. 예를 들어 다음은 프래그먼트가 두 개 있는 +한 액티비티에 대한 레이아웃 파일을 나타낸 것입니다.

    +
    +<?xml version="1.0" encoding="utf-8"?>
    +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    +    android:orientation="horizontal"
    +    android:layout_width="match_parent"
    +    android:layout_height="match_parent">
    +    <fragment android:name="com.example.news.ArticleListFragment"
    +            android:id="@+id/list"
    +            android:layout_weight="1"
    +            android:layout_width="0dp"
    +            android:layout_height="match_parent" />
    +    <fragment android:name="com.example.news.ArticleReaderFragment"
    +            android:id="@+id/viewer"
    +            android:layout_weight="2"
    +            android:layout_width="0dp"
    +            android:layout_height="match_parent" />
    +</LinearLayout>
    +
    +

    {@code <fragment>} 안의 {@code android:name} 속성이 레이아웃 안에서 인스턴트화할 {@link +android.app.Fragment} 클래스를 나타냅니다.

    + +

    시스템은 이 액티비티 레이아웃을 생성할 때 레이아웃에서 지정된 각 프래그먼트를 인스턴트화하며 각각에 대해 +{@link android.app.Fragment#onCreateView onCreateView()} 메서드를 +호출하여 각 프래그먼트의 레이아웃을 검색합니다. 시스템은 프래그먼트가 반환한 {@link android.view.View}를 +{@code <fragment>} 요소 자리에 곧바로 삽입합니다.

    + +
    +

    참고: 각 프래그먼트에는 액티비티가 재시작되는 경우 +프래그먼트를 복구하기 위해 시스템이 사용할 수 있는 고유한 식별자가 필요합니다(그리고, 개발자는 이것을 사용하여 프래그먼트를 캡처해 +이를 제거하는 등 여러 가지 트랜잭션을 수행할 수 있습니다). 프래그먼트에 ID를 제공하는 데에는 +다음과 같은 세 가지 방법이 있습니다.

    +
      +
    • 고유한 ID와 함께 {@code android:id} 속성을 제공합니다.
    • +
    • 고유한 문자열과 함께 {@code android:tag} 속성을 제공합니다.
    • +
    • 위의 두 가지 중 어느 것도 제공하지 않으면, 시스템은 컨테이너 보기의 ID를 +사용합니다.
    • +
    +
    +
  • + +
  • 또는, 프로그래밍 방식으로 프래그먼트를 기존의 {@link android.view.ViewGroup}에 추가합니다. +

    액티비티가 실행 중인 동안에는 언제든 액티비티 레이아웃에 프래그먼트를 추가할 수 있습니다. 그저 프래그먼트를 배치할 +{@link +android.view.ViewGroup}를 지정하기만 하면 됩니다.

    +

    액티비티 내에서 프래그먼트 트랜잭션을 수행하려면(프래그먼트 추가, 제거 또는 +교체 등), {@link android.app.FragmentTransaction}에서 가져온 API를 사용해야 합니다. +{@link android.app.FragmentTransaction}의 인스턴스를 {@link android.app.Activity}에서 가져오는 방법은 다음과 같습니다.

    + +
    +FragmentManager fragmentManager = {@link android.app.Activity#getFragmentManager()}
    +FragmentTransaction fragmentTransaction = fragmentManager.{@link android.app.FragmentManager#beginTransaction()};
    +
    + +

    그런 다음 {@link +android.app.FragmentTransaction#add(int,Fragment) add()} 메서드를 사용하여 프래그먼트를 추가하고, 추가할 프래그먼트와 이를 삽입할 +보기를 지정하면 됩니다. 예:

    + +
    +ExampleFragment fragment = new ExampleFragment();
    +fragmentTransaction.add(R.id.fragment_container, fragment);
    +fragmentTransaction.commit();
    +
    + +

    {@link android.app.FragmentTransaction#add(int,Fragment) add()}에 +전달되는 첫 인수가 {@link android.view.ViewGroup}입니다. +여기에 프래그먼트가 리소스 ID가 지정한 바와 같이 배치되어야 하며, 두 번째 매개변수는 추가할 프래그먼트입니다.

    +

    +{@link android.app.FragmentTransaction}을 변경하고 나면, 반드시 +{@link android.app.FragmentTransaction#commit}을 호출해야 변경 내용이 적용됩니다.

    +
  • +
+ + +

UI 없이 프래그먼트 추가

+ +

위의 예시에서는 UI를 제공하기 위해 프래그먼트를 액티비티에 추가하는 방법을 보여드렸습니다. 하지만 +추가로 UI를 제시하지 않고 액티비티에 대한 배경 동작을 제공하는 데에도 프래그먼트를 사용할 수 +있습니다.

+ +

UI 없이 프래그먼트를 추가하려면 액티비티로부터 가져온 프래그먼트를 {@link +android.app.FragmentTransaction#add(Fragment,String)}을 사용하여 추가합니다(이때, 프래그먼트에 대해 +보기 ID보다는 고유한 문자열 "태그"를 제공합니다). 이렇게 하면 프래그먼트가 추가되지만, +이것은 액티비티 레이아웃 안에 있는 보기와 연관되어 있지 않기 때문에 {@link +android.app.Fragment#onCreateView onCreateView()}로의 호출은 받지 않습니다. 따라서 그 메서드는 구현하지 않아도 됩니다.

+ +

프래그먼트에 대해 문자열 태그를 제공하는 것은 엄밀히 말해 비 UI 프래그먼트에만 해당되는 것은 아닙니다. UI가 있는 +프래그먼트에도 문자열 태그를 제공할 수 있습니다. 하지만 프래그먼트에 +UI가 없는 경우라면 이를 식별할 방법은 문자열 태그뿐입니다. 액티비티에서 나중에 +프래그먼트를 가져오고자 하는 경우, {@link android.app.FragmentManager#findFragmentByTag +findFragmentByTag()}를 사용해야 합니다.

+ +

예를 들어 어떤 액티비티에서 UI 없이 프래그먼트를 배경 작업자로 사용한다고 가정해봅시다. 이것의 예로 {@code +FragmentRetainInstance.java} 샘플을 들 수 있으며 +이는 SDK 샘플에 포함되어 있고(Android SDK Manager를 통해 이용 가능), 시스템에는 +<sdk_root>/APIDemos/app/src/main/java/com/example/android/apis/app/FragmentRetainInstance.java로 찾을 수 있습니다.

+ + + +

프래그먼트 관리

+ +

액티비티 내의 프래그먼트를 관리하려면 {@link android.app.FragmentManager}를 사용해야 합니다. 이것을 +가져오려면 액티비티에서 {@link android.app.Activity#getFragmentManager()}를 호출하십시오.

+ +

{@link android.app.FragmentManager}를 가지고 할 수 있는 여러 가지 일 중에 예를 들면 다음과 같습니다.

+ +
    +
  • 액티비티 내에 존재하는 프래그먼트를 {@link +android.app.FragmentManager#findFragmentById findFragmentById()}로 가져오기(액티비티 레이아웃 내에서 +UI를 제공하는 프래그먼트의 경우) 또는 {@link android.app.FragmentManager#findFragmentByTag +findFragmentByTag()}로 가져오기(UI를 제공하거나 하지 않는 프래그먼트의 경우).
  • +
  • 백 스택에서 프래그먼트를 {@link +android.app.FragmentManager#popBackStack()}을 사용하여 튀어나오게 하기(사용자가 내린 뒤로 명령을 시뮬레이트함).
  • +
  • 백 스택에 변경 내용이 있는지 알아보기 위해 {@link +android.app.FragmentManager#addOnBackStackChangedListener addOnBackStackChangedListener()}로 수신기 등록하기.
  • +
+ +

이와 같은 메서드와 그 외 다른 메서드에 대한 자세한 정보는 {@link +android.app.FragmentManager} 클래스 관련 문서를 참조하십시오.

+ +

이전 섹션에서 설명한 바와 같이 {@link android.app.FragmentManager}를 +사용해서도 {@link android.app.FragmentTransaction} +을 열 수 있습니다. 이렇게 하면 프래그먼트 추가 및 제거 등 트랜잭션을 수행할 수 있게 해줍니다.

+ + +

프래그먼트 트랜잭션 수행

+ +

액티비티에서 프래그먼트를 사용하는 경우 특히 유용한 점은 사용자 상호 작용에 응답하여 추가, +제거, 교체 및 다른 작업을 수행할 수 있다는 것입니다. 액티비티에 적용한 +변경 내용의 집합을 하나의 트랜잭션이라 칭합니다. 이것을 수행하려면 {@link +android.app.FragmentTransaction} 내의 API를 사용하면 됩니다. 해당 액티비티가 관리하는 백 스택에 행해진 각 트랜잭션을 +저장할 수도 있습니다. 이렇게 하면 사용자가 프래그먼트 변경 내역을 거쳐 뒤로 탐색할 수 있습니다(액티비티를 통과해 +뒤로 탐색하는 것과 비슷합니다).

+ +

{@link android.app.FragmentTransaction}의 인스턴스를 {@link +android.app.FragmentManager}로부터 가져오는 방법은 다음과 같습니다.

+ +
+FragmentManager fragmentManager = {@link android.app.Activity#getFragmentManager()};
+FragmentTransaction fragmentTransaction = fragmentManager.{@link android.app.FragmentManager#beginTransaction()};
+
+ +

각 트랜잭션은 동시에 수행하고자 하는 여러 변경을 집합적으로 일컫는 말입니다. 주어진 +트랜잭션에 대해 수행하고자 하는 모든 변경 사항을 설정하려면 {@link +android.app.FragmentTransaction#add add()}, {@link android.app.FragmentTransaction#remove remove()}, +및 {@link android.app.FragmentTransaction#replace replace()}와 같은 메서드를 사용하면 됩니다. 그런 다음, +트랜잭션을 액티비티에 적용하려면 반드시 {@link android.app.FragmentTransaction#commit()}을 호출해야 합니다.

+ + +

하지만 {@link +android.app.FragmentTransaction#commit()}을 호출하기 전에 먼저 호출해야 할 것이 있습니다. 바로 {@link +android.app.FragmentTransaction#addToBackStack addToBackStack()}입니다. +이렇게 해야 트랜잭션을 프래그먼트 트랜잭션의 백 스택에 추가할 수 있습니다. 이 백 스택을 액티비티가 관리하며, +이를 통해 사용자가 이전 프래그먼트 상태로 되돌아갈 수 있습니다. 이때 뒤로 버튼을 누르면 됩니다.

+ +

예를 들어 다음은 한 프래그먼트를 다른 것으로 교체하고 이전 상태를 백 스택에 보존하는 법을 +나타낸 것입니다.

+ +
+// Create new fragment and transaction
+Fragment newFragment = new ExampleFragment();
+FragmentTransaction transaction = getFragmentManager().beginTransaction();
+
+// Replace whatever is in the fragment_container view with this fragment,
+// and add the transaction to the back stack
+transaction.replace(R.id.fragment_container, newFragment);
+transaction.addToBackStack(null);
+
+// Commit the transaction
+transaction.commit();
+
+ +

이 예시에서 {@code newFragment}가 현재 레이아웃 컨테이너에 있는 +프래그먼트(있는 경우)를 교체합니다. 이는 {@code R.id.fragment_container} ID로 식별할 수 있습니다. {@link +android.app.FragmentTransaction#addToBackStack addToBackStack()}를 호출하면 교체 트랜잭션이 +백 스택에 저장되고, 따라서 사용자가 트랜잭션을 거꾸로 수행하여 +이전 프래그먼트를 도로 가져올 수 있습니다. 뒤로 버튼을 사용하면 됩니다.

+ +

트랜잭션에 여러 개의 변경을 추가하고(예를 들어 또 다른 {@link +android.app.FragmentTransaction#add add()} 또는 {@link android.app.FragmentTransaction#remove +remove()}) {@link +android.app.FragmentTransaction#addToBackStack addToBackStack()}을 호출하면, {@link android.app.FragmentTransaction#commit commit()}을 호출하기 전에 적용된 모든 변경 내용이 +백 스택에 하나의 트랜잭션으로 추가되며, 뒤로 버튼을 누르면 +모두 한꺼번에 역행하게 됩니다.

+ +

{@link android.app.FragmentTransaction}에 변경 내용을 추가하는 순서는 중요하지 않습니다. +다만 다음과 같은 예외가 있습니다.

+
    +
  • {@link android.app.FragmentTransaction#commit()}을 마지막으로 호출해야만 합니다.
  • +
  • 같은 컨테이너에 여러 개의 프래그먼트를 추가하는 경우, 이를 추가하는 순서가 이들이 +보기 계층에 나타나는 순서를 결정 짓습니다.
  • +
+ +

프래그먼트를 제거하는 트랜잭션을 수행하면서 {@link android.app.FragmentTransaction#addToBackStack(String) +addToBackStack()}을 호출하지 않는 경우, +해당 프래그먼트는 트랜잭션이 적용되면 소멸되고 사용자가 이를 되짚어 탐색할 수 없게 됩니다. 반면에 +프래그먼트를 제거하면서 {@link android.app.FragmentTransaction#addToBackStack(String) addToBackStack()}을 호출하면, +해당 프래그먼트는 중단되고 사용자가 뒤로 탐색하면 +재개됩니다.

+ +

팁: 각 프래그먼트 트랜잭션에 대해 전환 애니메이션을 적용하려면 +커밋하기 전에 {@link android.app.FragmentTransaction#setTransition setTransition()}을 +호출하면 됩니다.

+ +

{@link android.app.FragmentTransaction#commit()}을 호출해도 그 즉시 트랜잭션을 수행하지는 +않습니다. 그보다는, 액티비티의 UI 스레드("주요" 스레드)를 스레드가 할 수 있는 한 빨리 +이 트랜잭션을 수행하도록 일정을 예약하는 것에 가깝습니다. 하지만 필요한 경우 UI 스레드로부터 {@link +android.app.FragmentManager#executePendingTransactions()}를 호출하면 +{@link android.app.FragmentTransaction#commit()}이 제출한 트랜잭션을 즉시 실행할 수 있습니다. 트랜잭션이 +다른 스레드의 작업에 대한 종속성이 아니라면 굳이 이렇게 해야만 하는 것은 아닙니다.

+ +

주의: 트랜잭션을 적용할 때 {@link +android.app.FragmentTransaction#commit commit()}을 사용해도 되는 것은 액티비티가 그 상태를 +저장하기 전뿐입니다(사용자가 액티비티를 떠날 때). 그 시점 이후에 적용하려고 하면 예외가 +발생합니다. 이것은 액티비티를 복원해야 하는 경우 적용 이후의 상태가 손실될 수 +있기 때문입니다. 적용이 손실되어도 괜찮은 상황이라면, {@link +android.app.FragmentTransaction#commitAllowingStateLoss()}를 사용하십시오.

+ + + + +

액티비티와 통신

+ +

{@link android.app.Fragment}는 +{@link android.app.Activity}로부터 독립적인 객체로 구현되었고 여러 개의 액티비티 안에서 사용할 수 있는 것이 사실이지만, +프래그먼트의 주어진 인스턴스는 그것을 포함하고 있는 액티비티에 직접적으로 연결되어 있습니다.

+ +

구체적으로 말하면, 이 프래그먼트는 {@link +android.app.Fragment#getActivity()}를 사용하여 {@link android.app.Activity} 인스턴스에 액세스하여 +액티비티 레이아웃에서 보기를 찾는 것과 같은 작업을 손쉽게 수행할 수 있습니다.

+ +
+View listView = {@link android.app.Fragment#getActivity()}.{@link android.app.Activity#findViewById findViewById}(R.id.list);
+
+ +

이와 마찬가지로, 액티비티도 프래그먼트 안의 메서드를 호출할 수 있습니다. 그러려면 {@link android.app.FragmentManager}로부터의 +{@link android.app.Fragment}에 대한 참조를 가져와야 하며, 이때 {@link +android.app.FragmentManager#findFragmentById findFragmentById()} 또는 {@link +android.app.FragmentManager#findFragmentByTag findFragmentByTag()}를 사용합니다. 예:

+ +
+ExampleFragment fragment = (ExampleFragment) getFragmentManager().findFragmentById(R.id.example_fragment);
+
+ + +

액티비티로의 이벤트 콜백 생성

+ +

어떤 경우에는 프래그먼트로 하여금 액티비티와 이벤트를 공유하게 해야 할 수 있습니다. 이렇게 하기 위한 +한 가지 좋은 방법은 프래그먼트 내부의 콜백 인터페이스를 정의한 다음 해당 호스트 액티비티가 이를 구현하도록 +하는 것입니다. 액티비티가 인터페이스를 통해 콜백을 수신하면, 필요에 따라 그 정보를 레이아웃 내의 +다른 프래그먼트와 공유할 수 있습니다.

+ +

예를 들어 어떤 뉴스 애플리케이션에서 액티비티 하나에 프래그먼트가 두 개 있습니다. + 하나는 기사 목록을 표시(프래그먼트 A)하고 다른 하나는 기사 하나를 표시(프래그먼트 B)하는 경우 목록 항목이 선택되면 +프래그먼트 A가 액티비티에 알려야 프래그먼트 B에 해당 기사를 표시하라고 알릴 수 있습니다. 이 경우, +{@code OnArticleSelectedListener} 인터페이스는 프래그먼트 A 내부에 선언됩니다.

+ +
+public static class FragmentA extends ListFragment {
+    ...
+    // Container Activity must implement this interface
+    public interface OnArticleSelectedListener {
+        public void onArticleSelected(Uri articleUri);
+    }
+    ...
+}
+
+ +

그러면 프래그먼트를 호스팅하는 액티비티가 {@code OnArticleSelectedListener} + 인터페이스를 +구현하고 {@code onArticleSelected()}를 재정의하여 프래그먼트 A로부터 일어난 이벤트를 +프래그먼트 B에 알립니다. 호스트 액티비티가 이 인터페이스를 구현하도록 +확실히 하려면 프래그먼트 A의 {@link +android.app.Fragment#onAttach onAttach()} 콜백 메서드(프래그먼트를 액티비티에 추가할 때 시스템이 호출하는 것)가 {@code OnArticleSelectedListener}의 인스턴스를 인스턴트화해야 합니다. 이때 {@link android.app.Fragment#onAttach +onAttach()} 안으로 전달된 {@link android.app.Activity}를 +캐스팅하는 방법을 씁니다.

+ +
+public static class FragmentA extends ListFragment {
+    OnArticleSelectedListener mListener;
+    ...
+    @Override
+    public void onAttach(Activity activity) {
+        super.onAttach(activity);
+        try {
+            mListener = (OnArticleSelectedListener) activity;
+        } catch (ClassCastException e) {
+            throw new ClassCastException(activity.toString() + " must implement OnArticleSelectedListener");
+        }
+    }
+    ...
+}
+
+ +

액티비티가 인터페이스를 구현하지 않은 경우, 프래그먼트가 +{@link java.lang.ClassCastException}을 발생시킵니다. +성공 시, {@code mListener} 구성원이 액티비티의 +{@code OnArticleSelectedListener} 구현에 대한 참조를 보유하므로, 프래그먼트 A가 액티비티와 이벤트를 공유할 수 있습니다. +이때 {@code OnArticleSelectedListener} 인터페이스가 정의한 메서드를 호출하는 방법을 씁니다. 예를 들어 프래그먼트 A가 +{@link android.app.ListFragment}의 확장인 경우, +사용자가 목록 항목을 클릭할 때마다 시스템이 프래그먼트 안의 {@link android.app.ListFragment#onListItemClick +onListItemClick()}을 호출하고, 그러면 이것이 {@code onArticleSelected()}를 호출하여 +해당 이벤트를 액티비티와 공유하는 것입니다.

+ +
+public static class FragmentA extends ListFragment {
+    OnArticleSelectedListener mListener;
+    ...
+    @Override
+    public void onListItemClick(ListView l, View v, int position, long id) {
+        // Append the clicked item's row ID with the content provider Uri
+        Uri noteUri = ContentUris.{@link android.content.ContentUris#withAppendedId withAppendedId}(ArticleColumns.CONTENT_URI, id);
+        // Send the event and Uri to the host activity
+        mListener.onArticleSelected(noteUri);
+    }
+    ...
+}
+
+ +

{@link +android.app.ListFragment#onListItemClick onListItemClick()}에 전달된 {@code id} 매개변수가 클릭한 항목의 행 ID이며, +액티비티(또는 다른 프래그먼트)가 이것을 사용해 애플리케이션의 {@link +android.content.ContentProvider}에서 기사를 가져옵니다.

+ +

콘텐츠 제공자 사용법에 대한 자세한 정보는 +콘텐츠 제공자 문서에서 이용하실 수 있습니다.

+ + + +

작업 모음에 항목 추가

+ +

프래그먼트는 액티비티의 옵션 메뉴에(결과적으로 작업 모음에도) 메뉴 항목으로 참가할 수 있습니다. 이때 +{@link android.app.Fragment#onCreateOptionsMenu(Menu,MenuInflater) onCreateOptionsMenu()}를 구현하는 방법을 씁니다. 이 메서드가 +호출을 수신하도록 하려면, {@link +android.app.Fragment#onCreate(Bundle) onCreate()} 중에 {@link +android.app.Fragment#setHasOptionsMenu(boolean) setHasOptionsMenu()}를 호출하여 프래그먼트가 +옵션 메뉴에 항목을 추가하고자 한다는 것을 나타내야 합니다(그렇지 않으면 해당 프래그먼트가 +{@link android.app.Fragment#onCreateOptionsMenu onCreateOptionsMenu()}로의 호출을 받지 못하게 됩니다).

+ +

그런 다음 프래그먼트로부터 옵션 메뉴에 추가하는 모든 항목은 기존의 메뉴 항목에 +추가됩니다. 해당 프래그먼트는 메뉴 항목을 선택하면 {@link +android.app.Fragment#onOptionsItemSelected(MenuItem) onOptionsItemSelected()}로의 콜백도 +수신하게 됩니다.

+ +

또한 프래그먼트 레이아웃에 보기를 등록하여 컨텍스트 메뉴를 제공하도록 할 수도 있습니다. 이때 {@link +android.app.Fragment#registerForContextMenu(View) registerForContextMenu()}를 호출하면 됩니다. 사용자가 컨텍스트 메뉴를 열면, +해당 프래그먼트가 {@link +android.app.Fragment#onCreateContextMenu(ContextMenu,View,ContextMenu.ContextMenuInfo) +onCreateContextMenu()}로의 호출을 받습니다. 사용자가 항목을 하나 선택하면, 해당 프래그먼트는 {@link +android.app.Fragment#onContextItemSelected(MenuItem) onContextItemSelected()}로의 호출을 받습니다.

+ +

참고: 프래그먼트는 추가한 각 메뉴 항목에 대해 '항목 선택됨' 콜백을 +하나씩 받게 되지만, 사용자가 메뉴 항목을 선택할 때 그에 상응하는 콜백을 가장 처음 받는 것은 +액티비티입니다. 액티비티가 구현한 '항목 선택됨' 콜백이 선택된 항목을 다루지 않는 경우, +해당 이벤트는 프래그먼트의 콜백으로 전달됩니다. 이것은 +옵션 메뉴와 컨텍스트 메뉴에 모두 참입니다.

+ +

메뉴에 대한 더 자세한 정보는 메뉴작업 모음 개발자 가이드를 참조하십시오.

+ + + + +

프래그먼트 수명 주기 처리

+ +
+ +

그림 3. 액티비티 수명 주기가 프래그먼트 수명 주기에 미치는 +영향입니다.

+
+ +

프래그먼트의 수명 주기를 관리하는 것은 액티비티의 수명 주기를 관리하는 것과 매우 비슷합니다. 액티비티와 마찬가지로 +프래그먼트는 세 가지 상태로 존재할 수 있습니다.

+ +
+
재개됨
+
프래그먼트가 실행 중인 액티비티에 표시됩니다.
+ +
일시정지됨
+
또 다른 액티비티가 전경에 나와 있고 사용자가 이에 초점을 맞추고 있지만, +이 프래그먼트가 있는 액티비티도 여전히 표시되어 있습니다(전경의 액티비티가 부분적으로 투명하거나 +전체 화면을 뒤덮지 않습니다).
+ +
정지됨
+
프래그먼트가 표시되지 않습니다. 호스트 액티비티가 정지되었거나 +프래그먼트가 액티비티에서 제거되었지만 백 스택에 추가되었습니다. 정지된 프래그먼트도 +여전히 표시는 됩니다(모든 상태 및 구성원 정보를 시스템이 보존합니다). 하지만, 사용자에게는 +더 이상 표시되지 않으며 액티비티를 종료하면 이것도 종료됩니다.
+
+ +

이번에도 액티비티와 마찬가지로, 프래그먼트의 상태를 보존하려면 {@link +android.os.Bundle}을 사용합니다. 이는 혹시나 액티비티의 프로세스가 종료되고 액티비티를 +다시 만들 때 해당 프래그먼트의 상태를 복구해야 할 필요가 있을 때를 대비하는 것입니다. 상태를 저장하려면 프래그먼트의 {@link +android.app.Fragment#onSaveInstanceState onSaveInstanceState()} 콜백 중에 저장할 수 있고, 복구는 +{@link android.app.Fragment#onCreate onCreate()}, {@link +android.app.Fragment#onCreateView onCreateView()} 또는 {@link +android.app.Fragment#onActivityCreated onActivityCreated()} 중 한 가지가 진행되는 동안 할 수 있습니다. 상태 저장에 관한 자세한 정보는 +액티비티 +문서를 참조하십시오.

+ +

액티비티와 프래그먼트의 수명 주기에서 가장 중대한 차이점은 +해당되는 백 스택에 저장되는 방법입니다. 액티비티는 중단되었을 때 시스템이 관리하는 +액티비티 백 스택 안에 배치되는 것이 기본입니다(따라서 사용자가 뒤로 버튼을 사용하여 다시 이 액티비티로 +뒤로 탐색할 수 있습니다. 이 내용은 작업 및 백 스택에서 설명하였습니다). +하지만, 프래그먼트가 호스트 액티비티가 관리하는 백 스택 안에 배치되는 것은 해당 인스턴스를 저장하라고 명시적으로 요청하는 경우뿐입니다. +이때 프래그먼트를 제거하는 트랜잭션 중 {@link +android.app.FragmentTransaction#addToBackStack(String) addToBackStack()}을 +호출합니다.

+ +

이것만 제외하면, 프래그먼트 수명 주기를 관리하는 것은 액티비티의 수명 주기를 관리하는 것과 +아주 비슷합니다. 따라서, 액티비티 +수명 주기 관리에 쓰이는 실례가 프래그먼트에도 똑같이 적용되는 것입니다. 하지만 또 한 가지 이해해두어야 하는 것이 있습니다. 즉, +액티비티의 수명이 프래그먼트의 수명에 어떤 영향을 미치는지를 알아두어야 합니다.

+ +

주의: {@link android.app.Fragment} 내에서 {@link android.content.Context} +객체가 필요한 경우, {@link android.app.Fragment#getActivity()}를 호출하면 됩니다. +그러나 {@link android.app.Fragment#getActivity()}를 호출하는 것은 프래그먼트가 액티비티에 + 첨부되어 있는 경우뿐이니 유의하십시오. 프래그먼트가 아직 첨부되지 않았거나 수명 주기가 끝날 무렵 분리된 경우, +{@link android.app.Fragment#getActivity()}가 null을 반환합니다.

+ + +

액티비티 수명 주기와 조화

+ +

프래그먼트가 있는 액티비티의 수명 주기는 해당 프래그먼트의 수명 주기에 직접적인 +영향을 미칩니다. 따라서 액티비티에 대한 각 수명 주기 콜백이 각 프래그먼트에 대한 비슷한 콜백을 +유발합니다. 예를 들어 액티비티가 {@link android.app.Activity#onPause}를 받으면, +해당 액티비티 내의 각 프래그먼트가 {@link android.app.Fragment#onPause}를 받습니다.

+ +

하지만 프래그먼트에는 몇 가지 수명 주기 콜백이 더 있습니다. 이것은 액티비티와의 +고유한 상호 작용을 다루어 프래그먼트의 UI를 구축하고 소멸시키는 것과 같은 +작업을 수행합니다. 이러한 추가적인 콜백 메서드를 예로 들면 다음과 같습니다.

+ +
+
{@link android.app.Fragment#onAttach onAttach()}
+
프래그먼트가 액티비티와 연관되어 있었던 경우 호출됩니다(여기에서 {@link +android.app.Activity}가 전달됩니다).
+
{@link android.app.Fragment#onCreateView onCreateView()}
+
프래그먼트와 연관된 보기 계층을 생성하기 위해 호출됩니다.
+
{@link android.app.Fragment#onActivityCreated onActivityCreated()}
+
액티비티의 {@link android.app.Activity#onCreate +onCreate()} 메서드가 반환되면 호출됩니다.
+
{@link android.app.Fragment#onDestroyView onDestroyView()}
+
프래그먼트와 연관된 보기 계층이 제거되는 중일 때 호출됩니다.
+
{@link android.app.Fragment#onDetach onDetach()}
+
프래그먼트가 액티비티와 연결이 끊어지는 중일 때 호출됩니다.
+
+ +

호스트 액티비티의 영향을 받을 프래그먼트 수명 주기의 흐름은 그림 3에서 +확인하십시오. 이 그림을 보면 액티비티의 각 연속된 상태가 프래그먼트가 어느 +콜백 메서드를 받게 되는지 결정 짓는다는 것을 볼 수 있습니다. 예를 들어 액티비티가 자신의 {@link +android.app.Activity#onCreate onCreate()} 콜백을 받은 경우, 해당 액티비티 안에 있는 프래그먼트는 +{@link android.app.Fragment#onActivityCreated onActivityCreated()} 콜백을 받을 뿐입니다.

+ +

액티비티가 재개된 상태에 도달하면 자유자재로 프래그먼트를 액티비티에 추가하거나 액티비티에서 +제거해도 됩니다. 따라서, 액티비티가 재개된 상태에 있는 동안에만 프래그먼트의 수명 주기를 +독립적으로 변경할 수 있는 것입니다.

+ +

그러나 액티비티가 재개된 상태를 떠나면 액티비티는 다시 프래그먼트를 그 수명 주기 안으로 +밀어넣습니다.

+ + + + +

+ +

이 문서에서 논의한 모든 것을 한 번에 모아 보기 위해, 다음은 두 개의 프래그먼트를 사용하여 +창이 두 개인 레이아웃을 생성하는 액티비티를 예시로 나타낸 것입니다. 아래의 액티비티에 포함된 +한 프래그먼트는 셰익스피어 희곡 제목 목록을 표시하고, 또 다른 하나는 목록에서 선택했을 때 +해당 희곡의 요약을 표시합니다. 또한 화면 구성을 근거로 프래그먼트를 여러 가지로 구성하여 제공하는 방법도 +보여줍니다.

+ +

참고: 이 액티비티에 대한 완전한 소스 코드는 +{@code +FragmentLayout.java}에서 이용하실 수 있습니다.

+ +

주요 액티비티는 {@link +android.app.Activity#onCreate onCreate()} 중에 일반적인 방식으로 레이아웃을 적용합니다.

+ +{@sample development/samples/ApiDemos/src/com/example/android/apis/app/FragmentLayout.java main} + +

적용된 레이아웃은 {@code fragment_layout.xml}입니다.

+ +{@sample development/samples/ApiDemos/res/layout-land/fragment_layout.xml layout} + +

시스템은 이 레이아웃을 사용하여 액티비티가 레이아웃을 로딩하자마자 {@code TitlesFragment}를 초기화합니다(이것이 희곡 제목을 +목록으로 나열합니다). 반면 {@link android.widget.FrameLayout} +(희곡 요약을 표시하는 프래그먼트가 배치될 곳)은 화면 오른쪽에 있는 +공간을 차지하기는 하지만 처음에는 텅 빈 상태로 유지됩니다. 아래에서 볼 수 있듯이, 사용자가 해당 목록에서 +항목을 하나 선택해야만 프래그먼트가 {@link android.widget.FrameLayout} 안에 배치됩니다.

+ +

그러나 희곡 목록과 요약을 둘 다 나란히 표시할 만큼 너비가 넓지 않은 +화면 구성도 있습니다. 따라서 위의 레이아웃은 가로 방향 화면 구성에만 사용되며, +이를 {@code res/layout-land/fragment_layout.xml}에 저장하여 씁니다.

+ +

그러므로 화면이 세로 방향으로 구성된 경우, 시스템은 다음 레이아웃을 적용합니다. 이것은 +{@code res/layout/fragment_layout.xml}에 저장되어 있습니다.

+ +{@sample development/samples/ApiDemos/res/layout/fragment_layout.xml layout} + +

이 레이아웃에는 {@code TitlesFragment}만 포함되어 있습니다. 이는 다시 말해 기기가 세로 방향인 경우에는 +희곡 제목 목록만 표시된다는 뜻입니다. 따라서 사용자가 이 구성에서 목록 항목을 하나 클릭하면, +애플리케이션이 두 번째 프래그먼트를 로딩하는 대신 새 액티비티를 시작하여 요약을 +표시하게 됩니다.

+ +

다음으로, 프래그먼트 클래스에서 이것을 달성하는 방법을 보시겠습니다. 첫 번째가 {@code +TitlesFragment}로, 셰익스피어 희곡 제목 목록을 표시하는 것입니다. 이 프래그먼트는 {@link +android.app.ListFragment}를 확장하며 목록 보기 작업의 대부분을 처리하기 위해 여기에 의존합니다.

+ +

이 코드를 살펴보면서 사용자가 목록 항목을 클릭하면 일어날 수 있는 두 가지 동작이 +있다는 점을 눈여겨 보십시오. 두 레이아웃 중 어느 것이 활성화 상태인지에 따라 +같은 액티비티 내에서 세부 사항을 표시하기 위해 새 프래그먼트를 생성하거나 표시할 수도 있고(프래그먼트를 {@link +android.widget.FrameLayout}에 추가함으로써), 새 액티비티를 시작할 수도 있습니다(프래그먼트를 표시할 수 있는 곳).

+ +{@sample development/samples/ApiDemos/src/com/example/android/apis/app/FragmentLayout.java titles} + +

두 번째 프래그먼트인 {@code DetailsFragment}는 {@code TitlesFragment}에서 가져온 목록에서 선택한 항목에 대한 희곡 요약을 +표시하는 것입니다.

+ +{@sample development/samples/ApiDemos/src/com/example/android/apis/app/FragmentLayout.java details} + +

{@code TitlesFragment} 클래스에서 다룬 것을 되살려 보면, 사용자가 목록 항목을 클릭하고 +현재 레이아웃이 {@code R.id.details} 보기를 포함하지 않는 경우(이 보기가 +{@code DetailsFragment}가 속하는 곳임), 애플리케이션은 항목의 내용을 표시하기 위해 {@code DetailsActivity} + 액티비티를 시작하게 됩니다.

+ +

다음은 화면이 세로 방향으로 구성되어 있을 때 선택한 희곡의 요약을 표시하기 위해 단순히 {@code DetailsFragment}를 + 포함할 뿐인 {@code DetailsActivity}입니다.

+ +{@sample development/samples/ApiDemos/src/com/example/android/apis/app/FragmentLayout.java +details_activity} + +

이 액티비티는 구성이 가로 방향인 경우 알아서 종료한다는 점을 눈여겨 보십시오. 따라서 +주요 액티비티가 작업을 인계 받아 {@code DetailsFragment}를 {@code TitlesFragment}와 함께 표시할 수 있는 것입니다. +이것은 사용자가 세로 방향 구성에서 {@code DetailsActivity}를 시작했지만 +그런 다음 가로 방향으로 돌리는 경우(현재 액티비티를 다시 시작함) 일어날 수 있습니다.

+ + +

프래그먼트 사용에 대한 더 많은 샘플(및 이 예시에 대한 완전한 소스 파일)을 보시려면 + +ApiDemos에서 이용할 수 있는 API Demos 샘플 앱을 참조하십시오(샘플 SDK 구성 요소에서 다운로드할 수 있습니다).

+ + diff --git a/docs/html-intl/intl/ko/guide/components/fundamentals.jd b/docs/html-intl/intl/ko/guide/components/fundamentals.jd new file mode 100644 index 0000000000000000000000000000000000000000..608b5a2cba1b9a9322a0e64bb07ca358e903f08e --- /dev/null +++ b/docs/html-intl/intl/ko/guide/components/fundamentals.jd @@ -0,0 +1,480 @@ +page.title=애플리케이션 기본 항목 +@jd:body + + + +

Android 앱은 Java 프로그래밍 언어로 작성됩니다. Android SDK 도구는 +코드를 컴파일링하여 모든 데이터 및 리소스 파일과 함께 하나의 APK로 만듭니다. 이것은 즉, Android 패키지 +를 뜻하며, 이는 일종의 {@code .apk} 접미사가 있는 아카이브 파일입니다. 한 개의 APK 파일에는 +Android 앱의 모든 콘텐츠가 들어 있으며 이 파일이 바로 Android로 구동하는 기기가 앱을 설치할 때 사용하는 파일입니다.

+ +

Android 앱은 일단 기기에 설치되고 나면 각자 나름의 보안 샌드박스 안에 살게 됩니다.

+ +
    +
  • Android 운영 체제는 멀티 사용자 Linux 시스템으로, 여기서 각 앱은 각기 다른 사용자와 +같습니다.
  • + +
  • 기본적으로 시스템이 각 앱에 고유한 Linux ID를 할당합니다(이 ID는 시스템만 +사용할 수 있으며 앱은 이것을 알지 못합니다). 시스템은 앱 안의 모든 파일에 대해 권한을 설정하여 +해당 앱에 할당된 사용자 ID만 이에 액세스할 수 있도록 합니다.
  • + +
  • 각 프로세스에는 나름의 가상 머신(VM)이 있고, 그렇기 때문에 한 앱의 코드가 다른 여러 앱과는 격리된 상태로 +실행됩니다.
  • + +
  • 기본적으로 모든 앱이 나름의 Linux 프로세스에서 실행됩니다. Android는 앱의 구성 요소 중 +어느 것이라도 실행해야 하는 경우 프로세스를 시작하고, 이것이 더 이상 필요 없어지거나 시스템이 다른 앱을 위해 +메모리를 회복해야 하는 경우 해당 프로세스를 종료합니다.
  • +
+ +

Android 시스템은 이런 방식으로 최소 특권의 원리를 구현하는 것입니다. 다시 말해, +각 앱은 기본적으로 자신의 작업을 수행하기 위해 필요한 구성 요소에만 액세스 권한을 가지고 +그 이상은 허용되지 않습니다. 이렇게 하면 대단히 안전한 환경이 만들어져 앱이 시스템에서 +자신이 권한을 부여 받지 못한 부분에는 액세스할 수 없게 됩니다.

+ +

그러나, 앱이 다른 여러 앱과 데이터를 공유하는 것과 앱이 시스템 서비스에 액세스하는 데에는 +여러 가지 방법이 있습니다.

+ +
    +
  • 두 개의 앱이 같은 Linux 사용자 ID를 공유하도록 설정할 수도 있습니다. 이 경우 +두 앱은 서로의 파일에 액세스할 수 있게 됩니다. 시스템 리소스를 절약하려면, 같은 사용자 ID를 가진 앱이 +같은 Linux 프로세스에서 실행되도록 설정하고 같은 VM을 공유하도록 할 수도 있습니다(이들 앱은 같은 인증서로 +서명해야 합니다).
  • +
  • 앱은 사용자의 연락처, SMS 메시지, 마운트 가능한 저장소(SD 카드), +카메라, Bluetooth를 비롯하여 이외에도 여러 가지 기기 데이터에 액세스할 권한을 요청할 수 있습니다. 모든 +앱 권한은 설치 시점에 사용자가 허용해야 합니다.
  • +
+ +

이렇게 해서 Android 앱이 시스템 내에 어떤 식으로 존재하는지 기본 정보를 알아보았습니다. 이 문서의 +나머지 부분에서 소개될 내용은 다음과 같습니다.

+
    +
  • 앱을 정의하는 핵심 프레임워크 구성 요소.
  • +
  • 구성 요소를 선언하고 앱에 맞는 필수 기기 특징을 선언할 수 있는 매니페스트 +파일.
  • +
  • 앱 코드로부터 별도로 분리되어 있으며 앱이 다양한 기기 구성에 맞게 자신의 행동을 +안정적으로 최적화할 수 있도록 해주는 리소스.
  • +
+ + + +

앱 구성 요소

+ +

앱 구성 요소는 Android 앱을 이루는 가장 기본적인 구성 단위입니다. 각 +구성 요소는 시스템이 앱으로 들어올 수 있는 각기 다른 통과 지점을 나타냅니다. 구성 요소 중에는 +실제 사용자가 쓸 수 있는 진입 지점이 아닌 것도 있고, 일부는 서로에게 의존하지만, +각각의 구성 요소는 따로 떨어진 엔티티로서 존재하며 각기 특정한 역할을 수행합니다. 즉 하나하나가 +앱의 전반적인 행동을 정의하는 데 유용한 고유한 구성 단위인 것입니다.

+ +

앱 구성 요소에는 네 가지 서로 다른 유형이 있습니다. 각 유형이 뚜렷한 목적을 가지고 있으며 +각자 나름의 수명 주기가 있어 구성 요소의 생성 및 소멸 방식을 정의합니다.

+ +

다음은 네 가지 유형의 앱 구성 요소를 나타낸 것입니다.

+ +
+ +
액티비티
+ +
통상 액티비티 라고 하면, 사용자 인터페이스가 있는 화면 하나를 나타냅니다. 예를 들어 +이메일 앱이라면 새 이메일 목록을 표시하는 액티비티가 하나 있고, +이메일을 작성하는 액티비티가 또 하나, 그리고 이메일을 읽는 데 쓰는 액티비티가 또 하나 있을 수 있습니다. 여러 +액티비티가 함께 작동하여 해당 이메일 앱에서 짜임새 있는 사용자 환경을 형성하는 것은 사실이지만, 각자 서로와는 +독립적인 형태입니다. 따라서, 다른 앱이 이와 같은 액티비티 중 어느 것이라도 하나만 +시작할 수 있습니다(이메일 앱이 그렇게 하도록 허용하는 경우). 예를 들어, 카메라 앱이라면 이메일 앱 안의 +액티비티를 시작하여 새 메일을 작성하도록 해서 사용자가 사진을 공유하도록 할 수 있습니다. + +

액티비티는 {@link android.app.Activity}의 하위 클래스로 구현되며 이에 대한 더 자세한 내용은 +액티비티 +개발자 가이드에서 확인하실 수 있습니다.

+
+ + +
서비스
+ +
통상 서비스 라고 하면 배경에서 실행되는 구성 요소로, 오랫동안 실행되는 +작업을 수행하거나 원격 프로세스를 위한 작업을 수행하는 것입니다. 서비스는 +사용자 인터페이스를 제공하지 않습니다. 예를 들어 서비스는 사용자가 다른 앱에 있는 동안에 배경에서 음악을 재생할 수도 있고, +아니면 사용자와 액티비티 사이의 상호 작용을 차단하지 않고 네트워크를 가로질러 +데이터를 가져올 수도 있습니다. 또 다른 구성 요소(예: 액티비티)가 서비스를 시작한 다음 +실행되도록 두거나 자신에게 바인딩하여 상호 작용하도록 할 수도 있습니다. + +

서비스는 {@link android.app.Service}의 하위 클래스로 구현되며 이에 대한 더 자세한 내용은 +서비스 +개발자 가이드에서 확인하실 수 있습니다.

+
+ + +
콘텐츠 제공자
+ +
통상 콘텐츠 제공자 는 공유된 앱 데이터 집합을 관리합니다. 데이터는 파일 시스템이나 SQLite 데이터베이스, +또는 웹이나 기타 영구적인 저장소 위치 중 앱이 액세스할 수 있는 곳이라면 어디에든 저장할 수 +있습니다. 다른 여러 앱은 콘텐츠 제공자를 통해 해당 데이터를 쿼리하거나, 심지어는 수정할 수도 +있습니다(콘텐츠 제공자가 그렇게 하도록 허용하는 경우). 예를 들어, Android 시스템은 사용자의 연락처 정보를 +관리하는 콘텐츠 제공자를 제공합니다. 따라서, 적절한 권한을 가진 앱이라면 +어떤 것이든 해당 콘텐츠 제공자의 일부를 쿼리하여(예를 들어 {@link +android.provider.ContactsContract.Data} 등) 특정한 사람에 대한 정보를 읽고 쓸 수 있습니다. + +

콘텐츠 제공자는 앱에서 비공개이며 공유되지 않는 데이터를 읽고 쓰는 데에도 +유용합니다. 예를 들어 메모장 샘플 앱은 메모한 내용을 저장하는 데 +콘텐츠 제공자를 사용합니다.

+ +

콘텐츠 제공자는 {@link android.content.ContentProvider}의 +하위 클래스로 구현되며, 다른 앱이 트랜잭션을 수행할 수 있도록 활성화하는 표준 API 집합을 +구현해야 합니다. 자세한 내용은 콘텐츠 제공자 개발자 +가이드를 참조하십시오.

+
+ + +
브로드캐스트 수신기
+ +
통상 브로드캐스트 수신기 는 시스템 전체에 대한 브로드캐스트 공지에 응답하는 구성 요소를 +말합니다. 대다수의 브로드캐스트는 시스템에서 시작합니다. 예를 들어, 화면이 꺼졌다거나 +배터리 잔량이 부족하다거나, 사진을 캡처했다는 것을 알리는 브로드캐스트가 있습니다. +앱도 브로드캐스트를 시작합니다. 예를 들어, 기기에 몇 가지 데이터를 다운로드하여 다른 앱도 사용할 수 있다는 +사실을 다른 여러 앱에게 알리는 것입니다. 브로드캐스트 수신기는 사용자 인터페이스를 표시하지 않지만, +상태 표시줄 알림을 생성하여 +사용자에게 브로드캐스트 이벤트가 발생했다고 알릴 수 있습니다. 다만 브로드캐스트 수신기는 +그저 다른 구성 요소로의 "게이트웨이"인 경우가 더 보편적이고, 극소량의 작업만 수행하도록 만들어진 경우가 많습니다. 예컨대 +서비스를 시작하여 이벤트를 근거로 한 어떤 작업을 수행하도록 할 수 있습니다. + +

브로드캐스트 수신기는 {@link android.content.BroadcastReceiver}의 +하위 클래스로 구현되며 각 브로드캐스트는 {@link android.content.Intent} 객체로 전달됩니다. 자세한 정보는 +{@link android.content.BroadcastReceiver} 클래스를 참조하십시오.

+
+ +
+ + + +

Android 시스템 디자인의 독특한 점으로 어떤 앱이든 다른 앱의 구성 요소를 시작할 수 있다는 점을 +들 수 있습니다. 예를 들어 사용자가 기기 카메라로 사진을 캡처하기를 바라는 경우, +그런 작업을 수행하는 또 다른 앱이 있을 가능성이 높습니다. 그러면 사진을 캡처하는 액티비티를 직접 개발하는 대신 +여러분의 앱이 그 앱을 사용하도록 하면 됩니다. 카메라 앱에 +통합하기는커녕 카메라 앱의 코드에 연결시킬 필요조차도 없습니다. +그 대신, 그저 사진을 캡처하는 카메라 앱 안의 해당 액티비티를 시작하기만 하면 +됩니다. 작업이 완료되면 사진이 앱으로 반환되기까지 하여 바로 사용할 수 있습니다. 사용자에게는, +마치 카메라가 여러분의 앱의 일부분인 것처럼 보입니다.

+ +

시스템이 구성 요소를 시작하는 경우, 그 앱에 대한 프로세스를 시작하는 것이며(이미 +실행 중이지 않은 경우), 해당 구성 요소에 필요한 클래스를 인스턴트화하는 것입니다. 예를 들어 여러분의 앱이 +카메라 앱 내에서 사진을 캡처하는 액티비티를 시작한다고 하면, 해당 액티비티는 +여러분 앱의 프로세스가 아니라 카메라 앱에 속한 프로세스에서 실행됩니다. +따라서 대부분의 다른 시스템에서와는 달리 Android 앱에는 단일한 진입 +지점이 없습니다(예를 들어 {@code main()} 기능이 없습니다).

+ +

시스템이 각 앱을 별도의 프로세스에서 실행하며 다른 앱에 대한 액세스를 제한하는 +파일 권한을 가지고 실행하기 때문에 여러분의 앱은 또 다른 앱에서 곧바로 구성 요소를 +활성화할 수는 없습니다. 하지만 Android 시스템은 할 수 있습니다. 그래서 또 다른 앱에 있는 +구성 요소를 활성화하려면 시스템에 메시지를 전달하여 특정 구성 요소를 시작하고자 하는 인텐트를 +밝혀야 합니다. 그러면 시스템이 대신 해당 구성 요소를 활성화해줍니다.

+ + +

구성 요소 활성화

+ +

네 가지 구성 요소 중 세 가지—액티비티, 서비스 및 +브로드캐스트 수신기—는 일명 인텐트라고 하는 비동기식 메시지가 활성화합니다. +인텐트는 각각의 구성 요소를 런타임에 서로 바인딩하며(다른 구성 요소로부터 작업을 요청하는 +일종의 메신저로 생각하면 됩니다), 이는 구성 요소가 여러분의 앱에 속하든 아니든 +무관합니다.

+ +

인텐트는 {@link android.content.Intent} 객체로 생성되며, 이것이 +특정 구성 요소를 활성화할지 아니면 구성 요소의 특정 유형을 활성화할지를 나타내는 메시지를 정의합니다. 인텐트는 +각각 명시적이거나 암시적일 수 있습니다.

+ +

액티비티와 서비스의 경우, 인텐트는 수행할 작업을 정의하며(예를 들어 무언가를 '보기" 또는 +"보내기"), 작업을 수행할 데이터의 URI를 나타낼 수 있습니다(시작되는 구성 요소가 알아야 할 것은 +이외에도 많이 있습니다). 예를 들어, 인텐트는 액티비티에 이미지를 표시하거나 웹 페이지를 열라는 요청을 +전달할 수 있습니다. 어떤 경우에는 액티비티를 시작하여 +결과를 받아오도록 할 수 있습니다. 이런 경우 이 액티비티는 +{@link android.content.Intent}로 결과를 반환하기도 합니다(예를 들어, 사용자가 +개인적인 연락처를 선택하도록 한 다음 그것을 반환하도록 하는 인텐트를 발행할 수 있습니다—반환 인텐트에 +선택한 연락처를 가리키는 URI가 포함됩니다).

+ +

브로드캐스트 수신기의 경우, 인텐트는 단순히 브로드캐스트될 알림을 +정의할 뿐입니다(예를 들어, 기기 배터리 잔량이 낮다는 것을 나타내는 브로드캐스트에는 +"배터리 부족"을 나타내는 알려진 작업 문자열만 포함됩니다).

+ +

남은 하나의 구성 요소 유형, 즉 콘텐츠 제공자는 인텐트가 활성화하지 않습니다. 그보다는 +{@link android.content.ContentResolver}로부터의 요청으로 지정되면 활성화됩니다. 콘텐츠 +확인자는 콘텐츠 제공자와의 모든 직접적인 트랜잭션을 처리하여 +제공자와의 트랜잭션을 수행하는 구성 요소가 그런 일을 하지 않아도 되게 하고, 그 대신 {@link +android.content.ContentResolver} 객체에서 메서드를 호출합니다. 이렇게 되면 콘텐츠 제공자와 +정보를 요청하는 구성 요소 사이에 추상화 계층이 하나 남습니다(보안 목적).

+ +

각 유형의 구성 요소를 활성화하는 데에는 각기 별도의 메서드가 있습니다.

+
    +
  • 액티비티를 시작하려면(아니면 무언가 새로운 할 일을 주려면) +{@link android.content.Intent}를 {@link android.content.Context#startActivity +startActivity()} 또는 {@link android.app.Activity#startActivityForResult startActivityForResult()}에 +전달하면 됩니다(액티비티가 결과를 반환하기를 원하는 경우).
  • +
  • 서비스를 시작하려면(또는 진행 중인 서비스에 새로운 지침을 주려면) +{@link android.content.Intent}를 {@link android.content.Context#startService +startService()}에 전달하면 됩니다. 아니면 {@link android.content.Intent}를 +{@link android.content.Context#bindService bindService()}에 전달하여 서비스에 바인딩할 수도 있습니다.
  • +
  • 브로드캐스트를 시작하려면 {@link android.content.Intent}를 +{@link android.content.Context#sendBroadcast(Intent) sendBroadcast()}, {@link +android.content.Context#sendOrderedBroadcast(Intent, String) sendOrderedBroadcast()} 또는 {@link +android.content.Context#sendStickyBroadcast sendStickyBroadcast()}와 같은 메서드에 전달하면 됩니다.
  • +
  • 콘텐츠 제공자에 쿼리를 수행하려면 {@link android.content.ContentResolver}에서 {@link +android.content.ContentProvider#query query()}를 호출하면 됩니다.
  • +
+ +

인텐트 사용에 관한 자세한 정보는 인텐트 및 +인텐트 필터문서를 참조하십시오. 특정 구성 요소를 활성화하는 데 관한 자세한 정보 또한 다음 문서에 +제공되어 있습니다. 액티비티, 서비스, {@link +android.content.BroadcastReceiver} 및 콘텐츠 제공자.

+ + +

매니페스트 파일

+ +

Android 시스템이 앱 구성 요소를 시작하려면 시스템은 우선 해당 구성 요소가 +존재하는지 알아야 합니다. 그러기 위해 앱의 {@code AndroidManifest.xml} 파일을 읽습니다(즉 "매니페스트" +파일). 앱은 이 파일 안에 모든 구성 요소를 선언해야 하며, 이 파일은 앱 프로젝트 디렉터리의 루트에 +있어야 합니다.

+ +

매니페스트는 앱의 구성 요소를 선언하는 것 이외에도 수많은 역할을 합니다. +예를 들면 다음과 같습니다.

+
    +
  • 앱이 요구하는 모든 사용자 권한 식별(예: 인터넷 액세스 또는 사용자의 연락처로의 +읽기 액세스)
  • +
  • 앱이 어느 API를 사용하는지를 근거로 하여 앱에서 요구하는 최소 API 레벨 +선언
  • +
  • 앱에서 사용하거나 필요로 하는 하드웨어 및 소프트웨어 기능 선언(예: 카메라, +블루투스 서비스 또는 멀티터치 화면 등)
  • +
  • 앱이 링크되어야 하는 API 라이브러리(Android 프레임워크 +API 제외)(예: Google Maps +라이브러리)
  • +
  • 그 외 기타 등등
  • +
+ + +

구성 요소 선언

+ +

매니페스트의 주요 작업은 시스템에 앱의 구성 요소에 대해 알리는 것입니다. 예를 들어 +매니페스트 파일은 액티비티를 다음과 같이 선언할 수 있습니다.

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<manifest ... >
+    <application android:icon="@drawable/app_icon.png" ... >
+        <activity android:name="com.example.project.ExampleActivity"
+                  android:label="@string/example_label" ... >
+        </activity>
+        ...
+    </application>
+</manifest>
+ +

<application> +요소에서 {@code android:icon} 속성은 앱을 식별하는 아이콘에 대한 리소스를 +가리킵니다.

+ +

<activity> 요소에서는, +{@code android:name} 속성이 {@link +android.app.Activity} 하위 클래스의 완전히 정규화된 클래스 이름을 나타내며 {@code android:label} 속성은 액티비티의 +사용자에게 표시되는 레이블로 사용할 문자열을 나타냅니다.

+ +

모든 앱 구성 요소를 이렇게 선언해야 합니다.

+ + +

액티비티, 서비스를 비롯하여 소스에는 포함시키지만 매니페스트에서는 선언하지 않는 +콘텐츠 제공자는 시스템에 표시되지 않으며, 따라서 실행될 수 없습니다. 그러나 +브로드캐스트 + 수신기는 매니페스트에서 선언해도 되고 코드를 사용해( +{@link android.content.BroadcastReceiver} 객체로) 동적으로 생성한 다음 시스템에 등록해도 됩니다. 이때 +{@link android.content.Context#registerReceiver registerReceiver()}를 호출하는 방법을 씁니다.

+ +

앱에 맞는 매니페스트 파일을 구성하는 방법에 대한 자세한 내용은 AndroidManifest.xml 파일을 + 참조하십시오.

+ + + +

구성 요소 기능 선언

+ +

위에서 논한 바와 같이, 활성화 상태의 구성 요소에서는 +{@link android.content.Intent}를 사용하여 액티비티, 서비스 및 브로드캐스트 수신기를 시작할 수 있습니다. 그렇게 하려면 +대상 구성 요소를 인텐트 내에서 명시적으로 명명하면 됩니다(구성 요소 클래스 이름을 사용). 그러나, +인텐트의 진정한 힘은 암시적 인텐트의 개념에서 발휘됩니다. 암시적 인텐트는 +그저 수행할 작업의 유형을 설명할 뿐이며(또한, 선택 사항으로, 해당 작업을 수행하고자 하는 +데이터 위치도) 시스템에 기기에서 작업을 수행할 수 있는 구성 요소를 찾아 +시작하도록 해줍니다. 인텐트가 설명한 작업을 수행할 수 있는 구성 요소가 여러 개인 경우, +어느 것을 사용할지 사용자가 선택합니다.

+ +

시스템이 인텐트에 응답할 수 있는 구성 요소를 식별하는 방법은 수신한 인텐트를 + 인텐트 필터 와 비교하는 것입니다. 이 인텐트 필터는 기기의 다른 여러 앱의 매니페스트 +파일이 제공합니다.

+ +

앱의 매니페스트에서 액티비티를 선언하는 경우, 선택 사항으로 +해당 액티비티의 기능을 선언하는 인텐트 필터를 포함시켜서 다른 앱으로부터의 인텐트에 +응답할 수 있도록 할 수 있습니다. 구성 요소에 대한 인텐트 필터를 선언하려면 +{@code +<intent-filter>} 요소를 해당 구성 요소의 선언 요소 하위로 추가하면 됩니다.

+ +

예를 들어, 새 이메일을 작성하는 데 쓰는 액티비티가 있는 이메일 앱을 구축했다고 가정합시다. 이때 "전송" 인텐트에 +응답하는 인텐트 필터를 선언하려면(새 이메일을 전송하기 위해) 다음과 같이 하면 됩니다.

+
+<manifest ... >
+    ...
+    <application ... >
+        <activity android:name="com.example.project.ComposeEmailActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.SEND" />
+                <data android:type="*/*" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </activity>
+    </application>
+</manifest>
+
+ +

그런 다음, 다른 앱이 {@link +android.content.Intent#ACTION_SEND} 작업을 가진 인텐트를 생성하여 그것을 {@link android.app.Activity#startActivity +startActivity()}로 전달하면 시스템이 여러분의 액티비티를 시작하여 사용자가 이메일을 임시 보관하고 전송할 수 +있습니다.

+ +

인텐트 필터 생성에 관한 자세한 내용은 인텐트 및 인텐트 필터 문서를 참조하십시오. +

+ + + +

앱 요구 사항 선언

+ +

Android로 구동되는 기기는 수없이 많지만 모두 똑같은 특징을 갖고 같은 +기능을 제공하는 것은 아닙니다. 앱이 필요로 하는 기능이 부족한 기기에 앱을 설치하게 되는 불상사를 방지하려면, +앱이 지원하는 기기 유형에 대한 프로필을 명확하게 정의하는 것이 중요합니다. +그러려면 매니페스트 파일에 기기와 소프트웨어 요구 사항을 +선언하면 됩니다. 이와 같은 선언은 대부분 정보성일 뿐이며 시스템은 이를 읽지 않는 것이 일반적이지만, +Google Play와 같은 외부 서비스는 사용자가 본인의 기기에서 앱을 검색할 때 필터링을 제공하기 위해 +이와 같은 선언도 읽습니다.

+ +

예를 들어, 앱에 카메라가 필요하고 Android 2.1부터 도입된 API를 사용하는 경우(API 레벨 7) +이과 같은 내용을 매니페스트 파일에 요구 사항으로 선언하려면 다음과 같이 합니다.

+ +
+<manifest ... >
+    <uses-feature android:name="android.hardware.camera.any"
+                  android:required="true" />
+    <uses-sdk android:minSdkVersion="7" android:targetSdkVersion="19" />
+    ...
+</manifest>
+
+ +

이제 카메라가 없고 Android 버전이 2.1 이하인 기기는 +Google Play에서 여러분의 앱을 설치할 수 없습니다.

+ +

그러나, 앱이 카메라를 사용하기는 하지만 꼭 +필요한 것은 아니라고 선언할 수도 있습니다. 이 경우에는, 앱이 {@code required} + 속성을 {@code "false"}에 설정하고 런타임을 확인하여 +해당 기기에 카메라가 있는지, 경우에 따라 모든 카메라 기능을 비활성화할 수 있는지 알아봅니다.

+ +

여러 가지 기기와 앱의 호환성을 관리하는 방법에 대한 자세한 정보는 +기기 호환성 + 문서를 참조하십시오.

+ + + +

앱 리소스

+ +

Android 앱을 이루는 것은 코드만이 아닙니다. 소스 코드와는 별개인 여러 리소스가 필요합니다. +예를 들어 이미지, 오디오 파일과 앱을 시각적으로 표현하는 것과 관련된 모든 것들이 있습니다. +예컨대 애니메이션, 메뉴, 스타일, 색상과 액티비티 사용자 인터페이스의 레이아웃을 XML 파일로 +정의해야 합니다. 앱 리소스를 사용하면 앱의 다양한 특성을 +쉽게 업데이트할 수 있으며 코드를 수정하지 않아도 되고 일련의 대체 리소스를 +제공함으로써 다양한 기기 구성에 맞게 앱을 +최적화할 수도 있습니다(예: 여러 가지 언어 및 화면 크기).

+ +

Android 프로젝트에 포함시키는 리소스마다 SDK 빌드 도구가 고유한 +정수 ID를 정의하므로, 이를 사용하여 앱 코드에서의 리소스나 XML로 정의된 +다른 리소스에서 참조할 수 있습니다. 예를 들어 앱에 {@code +logo.png}라는 이름의 이미지 파일이 들어 있다고 하면({@code res/drawable/} 디렉터리에 저장됨) SDK 도구가 +{@code R.drawable.logo}라는 리소스 ID를 생성합니다. 이것을 사용하여 이미지를 참조하고 사용자 인터페이스에 +삽입할 수 있습니다.

+ +

소스 코드와는 별개로 리소스를 제공하는 것의 가장 중요한 측면 중 하나는 +여러 가지 기기 구성에 맞게 대체 리소스를 제공할 능력을 갖추게 +됩니다. 예를 들어 UI 문자열을 XML로 정의하면 이러한 문자열을 다른 언어로 변환한 뒤 +그러한 문자열을 별개의 파일에 저장할 수 있습니다. 그런 다음, 리소스 디렉터리 이름에 추가한 언어 한정자 +(예를 들어 프랑스어 문자열 값의 경우 {@code res/values-fr/}) 및 +사용자의 언어 설정을 근거로 하여 Android 시스템이 적절한 언어 문자열을 UI에 +적용하는 것입니다.

+ +

Android는 대체 리소스에 대해 다양한 한정자를 지원합니다. 한정자란 + 리소스 디렉터리의 이름에 포함시키는 짧은 문자열로, 이를 사용해 해당 리소스를 사용할 기기 구성을 +정의합니다. 또 다른 예를 들자면, +기기의 화면 방향과 크기에 따라 액티비티에 여러 가지 레이아웃을 생성해야 할 때가 +많습니다. 예를 들어 기기 화면이 세로 +방향(키가 큼)인 경우, 버튼이 세로 방향으로 되어 있는 레이아웃을 사용하는 것이 좋지만 화면이 +가로 방향(폭이 넓음)인 경우, 버튼이 가로 방향으로 정렬되어야 합니다. 방향에 따라 레이아웃을 변경하려면, +서로 다른 두 가지 레이아웃을 정의하여 적절한 한정자를 각각의 레이아웃의 디렉터리 이름에 +적용하면 됩니다. 그러면 시스템이 현재 기기 방향에 따라 적절한 레이아웃을 +자동으로 적용합니다.

+ +

애플리케이션에 포함할 수 있는 여러 가지 종류의 리소스와, 각기 다른 기기 구성에 따라 +대체 리소스를 생성하는 방법에 대한 자세한 내용은 리소스 제공을 읽어보십시오.

+ + + +
+
+

계속 읽기:

+
+
인텐트 및 인텐트 필터 +
+
{@link android.content.Intent} API를 사용하여 +앱 구성 요소(예: 액티비티 및 서비스 등)를 활성화하는 방법, 앱 구성 요소를 다른 여러 앱이 사용할 수 있도록 하는 방법 +등에 관한 정보입니다.
+
액티비티
+
{@link android.app.Activity} 클래스의 인스턴스를 생성하는 방법에 관한 정보로, +애플리케이션에 사용자 인터페이스가 있는 독특한 화면을 제공합니다.
+
리소스 제공
+
Android 앱이 앱 코드와는 별개의 앱 리소스에 대해 구조화된 방식에 관한 정보로, +특정 기기 구성에 맞게 대체 리소스를 제공하는 방법도 포함되어 +있습니다. +
+
+
+
+

혹시 다음과 같은 내용에도 흥미가 있으신가요?

+
+
기기 호환성
+
여러 가지 유형의 기기에서 Android의 작동 방식과 앱을 각 기기에 맞춰 최적화하는 방법 +또는 여러 가지 기기에 대해 앱의 가용성을 제한하는 방법 등에 관한 +정보입니다.
+
시스템 권한
+
Android가 특정 API에 대한 앱의 액세스를 제한하기 위해 권한 시스템을 +사용하는 방법으로, 그러한 API를 사용하려면 앱에 대해 사용자의 승인이 필요합니다.
+
+
+
+ diff --git a/docs/html-intl/intl/ko/guide/components/index.jd b/docs/html-intl/intl/ko/guide/components/index.jd new file mode 100644 index 0000000000000000000000000000000000000000..36626324e817871dfd68082d049a9bda187cc02e --- /dev/null +++ b/docs/html-intl/intl/ko/guide/components/index.jd @@ -0,0 +1,57 @@ +page.title=앱 구성 요소 +page.landing=true +page.landing.intro=Android의 애플리케이션 프레임워크는 일련의 재사용 가능한 구성 요소를 사용하여 풍성하고 혁신적인 앱을 생성할 수 있습니다. 이 섹션에서는 앱의 구성 단위를 정의 내리는 구성 요소를 구축하는 방법과 인텐트를 사용하여 이와 같은 구성 요소를 연결시키는 법을 설명합니다. +page.metaDescription=Android의 애플리케이션 프레임워크는 일련의 재사용 가능한 구성 요소를 사용하여 풍성하고 혁신적인 앱을 생성할 수 있습니다. 이 섹션에서는 앱의 구성 단위를 정의 내리는 구성 요소를 구축하는 방법과 인텐트를 사용하여 이와 같은 구성 요소를 연결시키는 법을 설명합니다. +page.landing.image=images/develop/app_components.png +page.image=images/develop/app_components.png + +@jd:body + +
+ + + + + +
diff --git a/docs/html-intl/intl/ko/guide/components/intents-filters.jd b/docs/html-intl/intl/ko/guide/components/intents-filters.jd new file mode 100644 index 0000000000000000000000000000000000000000..1488a1dd6472d1944c1db807c96855a885ba34b5 --- /dev/null +++ b/docs/html-intl/intl/ko/guide/components/intents-filters.jd @@ -0,0 +1,899 @@ +page.title=인텐트 및 인텐트 필터 +page.tags="IntentFilter" +@jd:body + + + + + + +

{@link android.content.Intent}는 일종의 메시지 객체입니다. 이것을 사용해 다른 +앱 구성 요소로부터 작업을 요청할 수 있습니다. +인텐트가 여러 구성 요소 사이의 통신을 용이하게 하는 데에는 몇 가지 방법이 있지만, +기본적인 사용 사례는 다음과 같은 세 가지입니다.

+ +
    +
  • 액티비티 시작하기: +

    {@link android.app.Activity}는 앱 안의 화면 하나를 나타냅니다. +{@link android.app.Activity}의 새 인스턴스를 시작하려면 {@link android.content.Intent}를 +{@link android.content.Context#startActivity startActivity()}로 전달하면 됩니다. {@link android.content.Intent}는 +시작할 액티비티를 설명하고 모든 필수 데이터를 담고 있습니다.

    + +

    액티비티가 완료되었을 때 결과를 수신하려면, +{@link android.app.Activity#startActivityForResult +startActivityForResult()}를 호출합니다. 액티비티는 해당 결과를 액티비티의 {@link +android.app.Activity#onActivityResult onActivityResult()} 콜백에서 별도의 +{@link android.content.Intent} 객체로 수신합니다. +자세한 정보는 액티비티 가이드를 참조하십시오.

  • + +
  • 서비스 시작하기: +

    {@link android.app.Service}는 사용자 인터페이스 없이 +배경에서 작업을 수행하는 구성 요소입니다. 서비스를 시작하여 일회성 작업을 수행하도록 하려면(예: 파일 다운로드) +{@link android.content.Intent}를 +{@link android.content.Context#startService startService()}에 전달하면 됩니다. {@link android.content.Intent}는 + 시작할 서비스를 설명하고 모든 필수 데이터를 담고 있습니다.

    + +

    서비스가 클라이언트-서버 인터페이스로 설계된 경우, 다른 구성 요소로부터 +서비스에 바인딩하려면 {@link android.content.Intent}를 {@link +android.content.Context#bindService bindService()}에 전달하면 됩니다. 자세한 정보는 서비스 가이드를 참조하십시오.

  • + +
  • 브로드캐스트 전달하기: +

    브로드캐스트는 모든 앱이 수신할 수 있는 메시지입니다. 시스템은 여러 시스템 이벤트에 대해 다양한 +브로드캐스트를 전달합니다. 예를 들어 시스템이 부팅될 때 또는 기기가 변경되기 시작할 때 등이 해당됩니다. +다른 여러 앱에 브로드캐스트를 전달하려면 {@link android.content.Intent}를 +{@link android.content.Context#sendBroadcast(Intent) sendBroadcast()}, +{@link android.content.Context#sendOrderedBroadcast(Intent, String) +sendOrderedBroadcast()} 또는 {@link +android.content.Context#sendStickyBroadcast sendStickyBroadcast()}에 전달하면 됩니다.

    +
  • +
+ + + + +

인텐트 유형

+ +

인텐트에는 두 가지 유형이 있습니다.

+ +
    +
  • 명시적 인텐트는 시작할 구성 요소를 이름으로 지정합니다(완전히 +정규화된 클래스 이름). 명시적 인텐트는 일반적으로 본인의 앱 안에서 구성 요소를 시작할 때 씁니다. +시작하고자 하는 액티비티 또는 서비스의 클래스 이름을 알고 있기 때문입니다. 예를 들어, +사용자 작업에 응답하여 새 액티비티를 시작하거나 배경에서 파일을 다운로드하기 위해 +서비스를 시작하는 것 등이 여기에 해당됩니다.
  • + +
  • 암시적 인텐트는 특정 구성 요소의 이름을 대지 않지만, 그 대신 수행할 일반적일 작업을 +선언하여 또 다른 앱의 구성 요소가 이를 처리할 수 있도록 해줍니다. 예를 들어, 사용자에게 지도에 있는 한 위치를 +표시해주고자 하는 경우, 암시적 인텐트를 사용하여 다른, 해당 기능을 갖춘 앱이 +지정된 위치를 지도에 표시하도록 요청할 수 있습니다.
  • +
+ +

명시적 인텐트를 생성하여 액티비티나 서비스를 시작하도록 하면, 시스템이 즉시 +{@link android.content.Intent} 객체에서 지정된 앱 구성 요소를 시작합니다.

+ +
+ +

그림 1. 암시적 인텐트가 시스템을 통해 전달되어 +다른 액티비티를 시작하게 하는 방법을 그림으로 나타낸 것입니다. [1] 액티비티 A가 작업 설명이 들어 있는 +{@link android.content.Intent}를 생성하여 이를 {@link +android.content.Context#startActivity startActivity()}에 전달합니다. [2] Android 시스템이 +해당 인텐트와 일치하는 인텐트 필터를 찾아 모든 앱을 검색합니다. 일치하는 것을 찾으면, [3] 시스템이 +일치하는 액티비티(액티비티 B)를 시작하기 위해 그 액티비티의 {@link +android.app.Activity#onCreate onCreate()} 메서드를 호출하여 이를 {@link android.content.Intent}에 전달합니다. +

+
+ +

암시적 인텐트를 생성하면 Android 시스템이 시작시킬 적절한 구성 요소를 찾습니다. +이때 인텐트의 내용을 기기에 있는 다른 여러 앱의 매니페스트 파일에서 선언된 인텐트 필터와 비교하는 방법을 +씁니다. 해당 인텐트와 일치하는 인텐트 필터가 있으면 시스템이 해당 구성 요소를 시작하고 이에 +{@link android.content.Intent} 객체를 전달합니다. 호환되는 인텐트 필터가 여러 개인 경우, 시스템은 +대화를 표시하여 사용자가 어느 앱을 사용할지 직접 선택할 수 있게 합니다.

+ +

인텐트 필터란 앱의 매니페스트 파일에 들어 있는 표현으로, +해당 구성 요소가 수신하고자 하는 인텐트의 유형을 +나타낸 것입니다. 예를 들어 액티비티에 대한 인텐트 필터를 선언하면 +다른 여러 앱이 특정한 종류의 인텐트를 가지고 여러분의 액티비티를 직접 시작할 수 있습니다. +이와 마찬가지로, 액티비티에 대한 인텐트 필터를 전혀 선언하지 않으면 명시적 인텐트로만 +시작할 수 있습니다.

+ +

주의: 앱의 보안을 보장하려면 +{@link android.app.Service}를 시작할 때에는 항상 명시적 인텐트만 사용하고 서비스에 대한 인텐트 필터는 +선언하지 마십시오. 암시적 인텐트를 사용하여 서비스를 시작하면 +보안 위험을 초래합니다. 인텐트에 어느 서비스가 응답할 것인지 확신할 수 없고, 사용자가 +어느 서비스가 시작되는지 볼 수 없기 때문입니다. Android 5.0(API 레벨 21)부터 시스템은 개발자가 암시적 인텐트로 +{@link android.content.Context#bindService bindService()}를 +호출하면 예외를 발생시킵니다.

+ + + + + +

인텐트 구축

+ +

{@link android.content.Intent} 객체에는 Android 시스템이 +어느 구성 요소를 시작할지 판별하는 데 사용하는 정보가 담겨 있습니다(예를 들어 정확한 구성 요소 이름 또는 인텐트를 +수신해야 하는 구성 요소 카테고리 등). 또한 수신자 구성 요소가 작업을 적절히 수행하기 위해 +사용할 정보(예: 수행할 작업 및 조치를 취할 데이터 위치 등)도 이 안에 담겨 있습니다.

+ + +

{@link android.content.Intent} 내에 들어 있는 기본 정보는 다음과 같습니다.

+ +
+ +
구성 요소 이름
+
시작할 구성 요소의 이름입니다. + +

이것은 선택 항목이지만, 이것이 바로 인텐트를 +명시적인 것으로 만들어주는 중요한 정보입니다. 다시 말해 이 인텐트는 구성 요소 이름이 정의한 앱 구성 요소에만 +전달되어야 한다는 뜻입니다. 구성 요소 이름이 없으면 해당 인텐트는 암시적이며, +인텐트를 수신해야 하는 구성 요소는 다른 인텐트 정보를 기반으로 시스템이 +결정합니다(예를 들어 작업, 데이터 및 카테고리 — 아래 설명 참조). 따라서 앱에서 특정한 구성 요소를 +시작해야 하는 경우에는, 구성 요소 이름을 지정해야 합니다.

+ +

참고: {@link android.app.Service}를 시작하는 경우, +항상 구성 요소 이름을 지정해야 합니다. 그렇지 않으면 인텐트에 어느 서비스가 응답할지 확신할 수 없고, +사용자도 어느 서비스가 시작되는지 볼 수 없게 됩니다.

+ +

{@link android.content.Intent}의 필드는 +{@link android.content.ComponentName} 객체로, 이것을 지정하려면 대상 구성 요소의 완전히 +정규화된 클래스 이름(앱의 패키지 이름 포함)을 사용해야 합니다. 예를 들면 +{@code com.example.ExampleActivity}를 쓰십시오. 구성 요소 이름을 설정하려면 {@link +android.content.Intent#setComponent setComponent()}, {@link android.content.Intent#setClass +setClass()}, {@link android.content.Intent#setClassName(String, String) setClassName()}을 사용하거나, 아니면 +{@link android.content.Intent} 생성자를 사용하면 됩니다.

+ +
+ +

작업
+
수행할 일반적인 작업을 나타내는 문자열입니다(예: 보기 또는 선택). + +

브로드캐스트 인텐트의 경우, 이것이 발생한 작업이자 보고되는 것이기도 합니다. +인텐트의 나머지 부분이 어떻게 구조화될지는 대부분 작업이 결정합니다. 특히, +데이터 안에 들어 있는 것과 추가 항목 등이 해당됩니다. + +

본인의 앱 내에 있는 인텐트가 사용할 작업(또는 다른 앱이 사용하여 +본인의 앱 안의 구성 요소를 호출하도록 함)을 직접 지정할 수도 있지만, 보통은 +{@link android.content.Intent} 클래스나 다른 프레임워크 클래스가 정의한 작업 상수를 써야 합니다. 다음은 +액티비티를 시작하는 데 쓰이는 보편적인 작업입니다.

+ +
+
{@link android.content.Intent#ACTION_VIEW}
+
이 작업은 액티비티가 사용자에게 표시할 수 있는 어떤 정보를 가지고 있을 때 {@link + android.content.Context#startActivity startActivity()}가 있는 인텐트에서 +사용합니다. 예를 들어 갤러리 앱에서 볼 사진이나 지도 앱에서 볼 주소 등이 +이에 해당됩니다.
+ +
{@link android.content.Intent#ACTION_SEND}
+
이것은 다른 이름으로 "공유" 인텐트라고도 합니다. 이것은 사용자가 다른 앱을 통해 공유할 수 있는 데이터를 가지고 있을 때 {@link + android.content.Context#startActivity startActivity()}가 있는 인텐트에서 사용합니다. 예를 들어 +이메일 앱 또는 소셜 공유 앱 등이 이에 해당됩니다.
+
+ +

일반적인 작업을 정의하는 상수에 대한 자세한 내용은 {@link android.content.Intent} 클래스 +참조를 확인하십시오. 다른 작업은 Android 프레임워크의 다른 곳에서 정의됩니다. 예를 들어 +시스템의 설정 앱에서 특정 화면을 여는 작업의 경우 {@link android.provider.Settings}에서 +정의됩니다.

+ +

인텐트에 대한 작업을 지정하려면 {@link android.content.Intent#setAction +setAction()} 또는 {@link android.content.Intent} 생성자를 사용하면 됩니다.

+ +

나름의 작업을 직접 정의하는 경우, 앱의 패키지 이름을 접두사로 포함시켜야 +합니다. 예:

+
static final String ACTION_TIMETRAVEL = "com.example.action.TIMETRAVEL";
+
+ +
데이터
+
작업을 수행할 데이터 및/또는 해당 데이터의 MIME 유형을 참조하는 URI({@link android.net.Uri} +객체)입니다. 제공된 데이터의 유형을 나타내는 것은 일반적으로 인텐트의 작업입니다. 예를 들어 +인텐트가 {@link android.content.Intent#ACTION_EDIT}인 경우, 데이터에 +편집할 문서의 URI가 들어있어야 합니다. + +

인텐트를 생성할 때에는 +URI외에도 데이터의 유형(MIME 유형)을 지정하는 것이 중요한 경우가 많습니다. +예를 들어, 이미지를 표시할 수 있는 액티비티는 아마도 오디오 파일을 재생할 수 없을 가능성이 +큽니다. 두 가지 기능의 URI 형식은 비슷할지라도 말입니다. +그러므로 데이터의 MIME 유형을 지정해두면 Android 시스템이 +인텐트를 수신할 최상의 구성 요소를 찾는 데 도움이 됩니다. +하지만, MIME 유형은 URI에서 추론되는 경우도 종종 있습니다. 특히 데이터가 +{@code content:} URI인 경우 더욱 그러한데, 이는 데이터가 기기에 위치하고 있으며 +{@link android.content.ContentProvider}가 제어한다는 것을 나타내어 해당 데이터 MIME 유형이 시스템에 표시되도록 합니다.

+ +

데이터 URI만 설정하려면 {@link android.content.Intent#setData setData()}를 호출하십시오. +MIME 유형만 설정하려면, {@link android.content.Intent#setType setType()}을 호출합니다. 필요한 경우, +두 가지를 모두 명시적으로 설정해도 됩니다. 이때는 {@link +android.content.Intent#setDataAndType setDataAndType()}을 사용하십시오.

+ +

주의: URI와 MIME 유형을 둘 다 설정하고자 하는 경우, {@link android.content.Intent#setData setData()} 및 +{@link android.content.Intent#setType setType()}을 호출하면 +안 됩니다. 이 둘은 서로의 값을 무효화하기 때문입니다. URI와 MIME 유형을 둘 모두 설정하려면 +항상 {@link android.content.Intent#setDataAndType setDataAndType()}을 +사용하십시오.

+
+ +

카테고리
+
인텐트를 처리해야 하는 구성 요소의 종류에 관한 추가 정보를 담은 +문자열입니다. 인텐트 안에는 카테고리 설명이 +얼마든지 들어있을 수 있지만, 대부분의 인텐트에는 카테고리가 없어도 됩니다. +다음은 몇 가지 보편적인 카테고리입니다. + +
+
{@link android.content.Intent#CATEGORY_BROWSABLE}
+
대상 액티비티가 스스로 웹 브라우저가 자신을 시작해도 되도록 허용하여 +링크로 참조된 데이터를 표시하게 합니다. 예컨대 이미지나 이메일 메시지 등이 이에 해당합니다. +
+
{@link android.content.Intent#CATEGORY_LAUNCHER}
+
이 액티비티가 작업의 최초 액티비티이며, 이것이 시스템의 애플리케이션 시작 관리자에 +목록으로 게재되어 있습니다. +
+
+ +

카테고리의 전체 목록을 보려면 {@link android.content.Intent} +클래스 설명을 참조하십시오.

+ +

카테고리를 지정하려면 {@link android.content.Intent#addCategory addCategory()}를 사용하면 됩니다.

+
+
+ + +

위에 목록으로 나열된 이러한 특성(구성 요소 이름, 작업, 데이터 및 카테고리)은 인텐트를 정의하는 특성을 +나타냅니다. Android 시스템은 이와 같은 속성을 읽어 +어느 앱 구성 요소를 시작해야 할지 확인할 수 있습니다.

+ +

그러나 인텐트는 앱 구성 요소로 확인되는 방법에 영향을 미치지 않는 +추가 정보도 담고 있을 수 있습니다. 인텐트가 제공할 수 있는 기타 정보는 다음과 같습니다.

+ +
+
추가 사항
+
요청한 작업을 수행하기 위해 필요한 추가 정보를 담고 있는 키-값 쌍입니다. +몇몇 작업이 특정한 종류의 데이터 URI를 사용하는 것과 마찬가지로, 몇몇 작업은 특정한 추가 사항도 사용합니다. + +

추가 데이터를 추가하려면 여러 가지 {@link android.content.Intent#putExtra putExtra()} 메서드를 +사용할 수 있습니다. 이들은 각기 두 개의 매개변수를 허용합니다. 즉 키 이름과 값입니다. +모든 추가 데이터를 가진 {@link android.os.Bundle} 객체를 생성할 수도 있고, 그런 다음 +{@link android.os.Bundle}을 {@link android.content.Intent}에 {@link +android.content.Intent#putExtras putExtras()}로 삽입해도 됩니다.

+ +

예를 들어 +{@link android.content.Intent#ACTION_SEND}로 이메일을 전송할 인텐트를 생성하는 경우 "받는 사람" 수신자를 지정할 때 +{@link android.content.Intent#EXTRA_EMAIL} 키를 사용한 다음 "제목"은 +{@link android.content.Intent#EXTRA_SUBJECT} 키로 지정하면 됩니다.

+ +

{@link android.content.Intent} 클래스는 표준화된 데이터 유형에 대해 수많은 {@code EXTRA_*} 상수를 +지정합니다. 나름의 추가 키를 선언해야 하는 경우(본인의 앱이 수신할 +인텐트에 대하여), 앱의 패키지 이름을 접두사로 포함시커야 +합니다. 예:

+
static final String EXTRA_GIGAWATTS = "com.example.EXTRA_GIGAWATTS";
+
+ +
플래그
+
{@link android.content.Intent} 클래스에서 정의된 플래그로, 인텐트에 대한 메타데이터와 같은 기능을 +합니다. 이런 플래그는 Android 시스템에 액티비티를 시작할 방법에 대한 지침을 줄 수도 있고(예를 들어 액티비티가 어느 +작업에 소속되어야 하는지 등) + 액티비티를 시작한 다음에 어떻게 처리해야 하는지도 알려줄 수 있습니다(예를 들어 해당 액티비티가 최근 +액티비티 목록에 소속되는지 여부). + +

자세한 정보는 {@link android.content.Intent#setFlags setFlags()} 메서드를 참조하십시오.

+
+ +
+ + + + +

명시적 인텐트 예시

+ +

명시적 인텐트는 특정한 앱 구성 요소를 시작하기 위해 사용하는 것입니다. 예를 들어 앱 내의 +특정 액티비티나 서비스를 말합니다. 명시적 인텐트를 생성하려면 +{@link android.content.Intent} 객체에 대한 구성 요소 이름을 정의합니다. 다른 +인텐트 속성은 모두 선택 사항입니다.

+ +

예를 들어 앱 안에 {@code DownloadService}라는 서비스를 구축했다고 합시다. +이 서비스는 웹 상에서 파일을 다운로드하도록 설계된 것입니다. 이것을 시작하려면 다음과 같은 코드를 사용하면 됩니다.

+ +
+// Executed in an Activity, so 'this' is the {@link android.content.Context}
+// The fileUrl is a string URL, such as "http://www.example.com/image.png"
+Intent downloadIntent = new Intent(this, DownloadService.class);
+downloadIntent.setData({@link android.net.Uri#parse Uri.parse}(fileUrl));
+startService(downloadIntent);
+
+ +

{@link android.content.Intent#Intent(Context,Class)} + 생성자가 앱에 {@link android.content.Context}를 제공하고 +구성 요소에 {@link java.lang.Class} 객체를 제공합니다. 이처럼, +이 인텐트는 앱 내의 {@code DownloadService} 클래스를 명시적으로 시작합니다.

+ +

서비스를 구축하고 시작하는 데 대한 자세한 정보는 +서비스 가이드를 참조하십시오.

+ + + + +

암시적 인텐트 예시

+ +

암시적 인텐트는 작업을 지정하여 기기에서 해당 작업을 수행할 수 있는 모든 앱을 호출할 수 +있도록 합니다. 암시적 인텐트를 사용하면 본인의 앱은 작업을 수행할 수 없지만 다른 앱은 아마도 할 수 있을 때, +그리고 사용자로 하여금 어느 앱을 사용할지 선택하도록 하고자 할 때 유용합니다.

+ +

예를 들어 사용자가 다른 사람들과 공유했으면 하는 콘텐츠를 가지고 있는 경우, +{@link android.content.Intent#ACTION_SEND} 작업이 있는 인텐트를 생성한 다음 +공유할 콘텐츠를 지정하는 추가 정보를 추가하면 됩니다. 해당 인텐트로 +{@link android.content.Context#startActivity startActivity()}를 호출하면 +사용자가 어느 앱을 통해 콘텐츠를 공유할지 선택할 수 있습니다.

+ +

주의: 개발자가 {@link android.content.Context#startActivity +startActivity()}로 전송한 암시적 인텐트를 처리할 앱이 사용자에게 전혀 +표시되지 않을 수도 있습니다. 이런 일이 발생하면, 호출이 실패하고 앱 작동이 중단됩니다. 어느 액티비티든 +이 인텐트를 수신하도록 확실히 하려면, {@link android.content.Intent} 객체의 {@link android.content.Intent#resolveActivity +resolveActivity()}를 호출합니다. 결과가 null이 아닌 경우, +인텐트를 처리할 수 있는 앱이 최소한 하나는 있다는 뜻이며 +{@link android.content.Context#startActivity startActivity()}를 호출해도 안전합니다. 결과가 null이면, +해당 인텐트를 사용해서는 안 되며 가능한 경우 해당 인텐트를 발생시키는 기능을 비활성화해야 +합니다.

+ + +
+// Create the text message with a string
+Intent sendIntent = new Intent();
+sendIntent.setAction(Intent.ACTION_SEND);
+sendIntent.putExtra(Intent.EXTRA_TEXT, textMessage);
+sendIntent.setType("text/plain");
+
+// Verify that the intent will resolve to an activity
+if (sendIntent.resolveActivity(getPackageManager()) != null) {
+    startActivity(sendIntent);
+}
+
+ +

참고: 이 경우에서는 URI를 사용하지 않았지만 인텐트의 데이터 유형이 정의되어 +추가 정보가 담고 있는 콘텐츠를 지정하였습니다.

+ + +

{@link android.content.Context#startActivity startActivity()}를 호출하면 시스템이 +설치된 앱을 모두 살펴보고 이런 종류의 인텐트를 처리할 수 있는 앱이 어느 것인지 알아봅니다( +{@link android.content.Intent#ACTION_SEND} 작업이 있는 인텐트이며 "텍스트/일반" +데이터가 담긴 것). 이것을 처리할 수 있는 앱이 하나뿐이면, 해당 앱이 즉시 열리고 이 앱에 인텐트가 +주어집니다. 인텐트를 허용하는 액티비티가 여러 개인 경우, 시스템은 +대화를 표시하여 사용자가 어느 앱을 사용할지 직접 선택할 수 있게 합니다.

+ + +
+ +

그림 2. 선택기 대화입니다.

+
+ +

앱 선택기 강제 적용하기

+ +

암시적 인텐트에 응답하는 앱이 하나 이상인 경우, +사용자가 어느 앱을 사용할지 선택할 수 있으며 해당 앱을 이 작업에 대한 기본 선택으로 만들 수 +있습니다. 이는 사용자가 이제부터 계속 같은 앱을 사용하고자 할 것이라고 추정되는 작업을 +수행할 때 좋습니다. 예를 들어 웹 페이지를 열 때가 이에 해당됩니다(대다수 사용자가 +웹 브라우저는 하나만 사용하는 것을 선호합니다).

+ +

그러나, 인텐트에 응답할 수 있는 앱이 여러 개이고 사용자가 매번 다른 앱을 사용하기를 원할 수도 있는 +경우라면, 선택기 대화를 명시적으로 표시해야 합니다. 선택기 대화 상자는 +사용자가 작업에 사용할 앱을 매번 선택하도록 물어봅니다(사용자는 작업에 사용할 +기본 앱을 선택할 수 없습니다). 예를 들어 앱이 {@link +android.content.Intent#ACTION_SEND} 작업에 "공유"를 수행하는 경우, 사용자는 각자의 현재 상황에 따라 여러 가지 다른 앱을 사용하여 공유하기를 +원할 수도 있습니다. 따라서 항상 선택기 대화를 사용해야 합니다(그림 2 참조).

+ + + + +

선택기를 표시하려면 {@link +android.content.Intent#createChooser createChooser()}를 사용하여 {@link android.content.Intent}를 생성한 후 {@link +android.app.Activity#startActivity startActivity()}에 전달합니다. 예:

+ +
+Intent sendIntent = new Intent(Intent.ACTION_SEND);
+...
+
+// Always use string resources for UI text.
+// This says something like "Share this photo with"
+String title = getResources().getString(R.string.chooser_title);
+// Create intent to show the chooser dialog
+Intent chooser = Intent.createChooser(sendIntent, title);
+
+// Verify the original intent will resolve to at least one activity
+if (sendIntent.resolveActivity(getPackageManager()) != null) {
+    startActivity(chooser);
+}
+
+ +

이 예시는 {@link +android.content.Intent#createChooser createChooser()} 메서드에 전달된 인텐트에 응답하는 앱 목록이 포함된 대화 상자를 표시하고 제공된 텍스트를 +대화 상자 제목으로 사용합니다.

+ + + + + + + + + +

암시적 인텐트 수신하기

+ +

본인의 앱이 수신할 수 있는 암시적 인텐트가 어느 것인지 알리려면, +각 앱 구성 요소에 대한 하나 이상의 인텐트 필터를 {@code <intent-filter>} + 요소로 매니페스트 파일에 선언합니다. +각 인텐트 필터가 인텐트의 작업, 데이터 및 카테고리를 근거로 어느 유형의 인텐트를 +허용하는지 나타냅니다. 시스템이 앱 구성 요소에 암시적 인텐트를 전달하는 것은 인텐트가 개발자의 인텐트 필터 중 하나를 +통과해 지나갈 수 있는 경우뿐입니다.

+ +

참고: 명시적 인텐트는 항상 자신의 대상에 전달되며, +이는 구성 요소가 어떤 인텐트 필터를 선언하든 무관합니다.

+ +

앱 구성 요소는 자신이 수행할 수 있는 각 고유한 작업에 대해 별도의 필터를 선언해야 합니다. +예를 들어 이미지 갤러리 앱에 있는 한 액티비티에 두 개의 필터가 있을 수 있습니다. 필터 하나는 +이미지를 보고, 또 다른 필터는 이미지를 편집하는 것입니다. 액티비티가 시작되면, 이는 +{@link android.content.Intent}를 검사한 다음 +{@link android.content.Intent}에 있는 정보를 근거로 어떻게 동작할 것인지 결정합니다(편집기 제어 항목을 표시할 것인지 말 것인지 등).

+ +

각 인텐트 필터는 앱의 매니페스트 파일에 있는 {@code <intent-filter>} +요소가 정의하며, 이는 상응하는 앱 구성 요소에 중첩되어 +있습니다(예: {@code <activity>} +요소). {@code <intent-filter>} 내부에서는 +다음과 같은 세 가지 요소 중 하나 이상을 사용하여 허용할 인텐트 유형을 지정할 수 +있습니다.

+ +
+
{@code <action>}
+
허용된 인텐트 작업을 {@code name} 속성에서 선언합니다. 이 값은 +어떤 작업의 리터럴 문자열 값이어야 하며, 클래스 상수가 아닙니다.
+
{@code <data>}
+
허용된 데이터 유형을 선언합니다. 이때 +데이터 URI(scheme, host, port, +path 등)와 MIME 유형의 여러 가지 측면을 나타내는 하나 이상의 속성을 사용하는 방법을 씁니다.
+
{@code <category>}
+
허용된 인텐트 카테고리를 {@code name} 속성에서 선언합니다. 이 값은 +어떤 작업의 리터럴 문자열 값이어야 하며, 클래스 상수가 아닙니다. + +

참고: 암시적 인텐트를 수신하려면 인텐트 필터 안에 + +{@link android.content.Intent#CATEGORY_DEFAULT} 카테고리를 반드시 포함해야 합니다. +{@link android.app.Activity#startActivity startActivity()} 및 +{@link android.app.Activity#startActivityForResult startActivityForResult()} 메서드는 +모든 인텐트를 마치 {@link android.content.Intent#CATEGORY_DEFAULT} 카테고리를 선언한 것처럼 취급합니다. + 이 카테고리를 인텐트 필터에서 선언하지 않으면 액티비티에 어떤 암시적 인텐트도 +확인되지 않습니다.

+
+
+ +

예를 들어, 다음은 데이터 유형이 텍스트인 경우 +{@link android.content.Intent#ACTION_SEND} 인텐트를 수신할 인텐트 필터가 있는 액티비티 선언입니다.

+ +
+<activity android:name="ShareActivity">
+    <intent-filter>
+        <action android:name="android.intent.action.SEND"/>
+        <category android:name="android.intent.category.DEFAULT"/>
+        <data android:mimeType="text/plain"/>
+    </intent-filter>
+</activity>
+
+ +

+{@code <action>}, +{@code <data>} 또는 +{@code <category>}의 인스턴스가 하나 이상 포함된 필터를 생성해도 됩니다. +그렇게 하는 경우, 그저 해당 구성 요소가 그와 같은 필터 요소의 모든 조합을 처리할 수 있도록 +확실히 하기만 하면 됩니다.

+ +

여러 가지 종류의 인텐트를 처리하고자 하되 특정 조합의 작업, 데이터 및 카테고리 유형으로만 한정하고자 한다면 +여러 가지 인텐트 필터를 생성해야 합니다.

+ + + + +

암시적 인텐트를 필터에 대해 테스트하려면 인텐트를 세 가지 요소에 대해 각기 +비교합니다. 인텐트가 구성 요소에 전달되려면 해당 인텐트는 세 개의 테스트를 모두 통과해야 합니다. +하나라도 일치하지 못하고 실패하면 Android 시스템이 해당 인텐트를 구성 요소에 전달하지 +않습니다. 그러나 구성 요소에는 여러 개의 인텐트 필터가 있을 수도 있으므로, 구성 요소의 +필터 중 하나를 통과하지 않는 인텐트도 다른 필터를 통하면 성공할 수 있습니다. +시스템이 인텐트를 확인하는 방법에 대한 자세한 정보는 +인텐트 확인에 대해 다룬 아래 섹션에 제공되어 있습니다.

+ +

주의: 부주의로 다른 앱의 +{@link android.app.Service}를 실행하는 일을 피하려면, 항상 명시적 인텐트를 사용하여 본인의 서비스를 시작하고 서비스에 대한 +인텐트 필터는 선언하지 마십시오.

+ +

참고: +액티비티 전체에 대해 인텐트 필터를 매니페스트 파일에서 선언해야 합니다. +다만 브로드캐스트 수신기에 대한 필터의 경우 동적으로 등록할 수도 있습니다. +{@link android.content.Context#registerReceiver(BroadcastReceiver, IntentFilter, String, +Handler) registerReceiver()}를 호출하면 됩니다. 그런 다음 해당 수신기를 등록 해제하려면 {@link +android.content.Context#unregisterReceiver unregisterReceiver()}를 사용하십시오. 이렇게 하면 앱이 +실행되는 동안에 정해진 기간 동안 특정한 브로드캐스트에 대해 수신 대기할 수 +있습니다.

+ + + + + + + +

필터 예시

+ +

몇 가지 인텐트 필터 동작에 대해 이해를 돕기 위해 소셜 공유 앱의 매니페스트 파일에서 +가져온 다음 조각을 참조하십시오.

+ +
+<activity android:name="MainActivity">
+    <!-- This activity is the main entry, should appear in app launcher -->
+    <intent-filter>
+        <action android:name="android.intent.action.MAIN" />
+        <category android:name="android.intent.category.LAUNCHER" />
+    </intent-filter>
+</activity>
+
+<activity android:name="ShareActivity">
+    <!-- This activity handles "SEND" actions with text data -->
+    <intent-filter>
+        <action android:name="android.intent.action.SEND"/>
+        <category android:name="android.intent.category.DEFAULT"/>
+        <data android:mimeType="text/plain"/>
+    </intent-filter>
+    <!-- This activity also handles "SEND" and "SEND_MULTIPLE" with media data -->
+    <intent-filter>
+        <action android:name="android.intent.action.SEND"/>
+        <action android:name="android.intent.action.SEND_MULTIPLE"/>
+        <category android:name="android.intent.category.DEFAULT"/>
+        <data android:mimeType="application/vnd.google.panorama360+jpg"/>
+        <data android:mimeType="image/*"/>
+        <data android:mimeType="video/*"/>
+    </intent-filter>
+</activity>
+
+ +

첫 번째 액티비티인 {@code MainActivity}는 앱의 주요 진입 지점입니다. 즉 이것이 +사용자가 시작 관리자 아이콘을 사용하여 앱을 처음 시작할 때 열리는 액티비티입니다.

+
    +
  • {@link android.content.Intent#ACTION_MAIN} 작업은 +이것이 주요 진입 지점이며 어느 인텐트 데이터도 기대하지 않는다는 것을 나타냅니다.
  • +
  • {@link android.content.Intent#CATEGORY_LAUNCHER} 카테고리는 +이 액티비티의 아이콘이 시스템의 앱 시작 관리자에 배치되어야 한다는 것을 나타냅니다. {@code <activity>} 요소가 +아이콘을 {@code icon}으로 지정하지 않은 경우, 시스템은 {@code <application>} + 요소로부터 가져온 아이콘을 사용합니다.
  • +
+

이들 두 가지가 함께 페어링되어야 액티비티가 앱 시작 관리자에 나타날 수 있습니다.

+ +

두 번째 액티비티인 {@code ShareActivity}는 텍스트와 미디어 콘텐츠 공유를 +용이하게 할 목적으로 만들어진 것입니다. 사용자가 {@code MainActivity}에서 이 액티비티를 향해 이동하다가 이 안에 진입할 수도 있지만, +두 가지 인텐트 필터 중 하나와 일치하는 암시적 인텐트를 발생시키는 또 다른 앱에서 {@code ShareActivity}에 직접 진입할 수도 +있습니다.

+ +

참고: MIME 유형, 즉 +{@code +application/vnd.google.panorama360+jpg}는 파노라마 사진을 나타내는 +특수 데이터 유형으로, 이것은 Google +파노라마 API로 처리할 수 있습니다.

+ + + + + + + + + + + + + +

보류 인텐트 사용하기

+ +

{@link android.app.PendingIntent} 객체는 {@link +android.content.Intent} 객체 주변을 감싸는 래퍼입니다. {@link android.app.PendingIntent}의 +기본 목적은 외래 애플리케이션에 권한을 허가하여 안에 들어 있는 +{@link android.content.Intent}를 +마치 본인 앱의 자체 프로세스에서 실행하는 것처럼 사용하게 하는 것입니다.

+ +

보류 인텐트의 주요 사용 사례는 다음과 같습니다.

+
    +
  • 사용자가 여러분의 알림으로 +작업을 수행할 때 인텐트가 실행되도록 선언합니다(Android 시스템의 {@link android.app.NotificationManager}가 +{@link android.content.Intent}를 실행합니다). +
  • 사용자가 여러분의 +앱 위젯으로 +작업을 수행할 때 인텐트가 실행되도록 선언합니다(메인 스크린 앱이 {@link android.content.Intent}를 실행합니다). +
  • 향후 지정된 시간에 인텐트가 실행되도록 선언합니다(Android +시스템의 {@link android.app.AlarmManager}가 {@link android.content.Intent}를 실행합니다). +
+ +

각 {@link android.content.Intent} 객체는 특정한 유형의 +앱 구성 요소가({@link android.app.Activity}, {@link android.app.Service} 또는 +{@link android.content.BroadcastReceiver}) 처리하도록 설계되어 있기 때문에, {@link android.app.PendingIntent}도 +같은 고려 사항을 염두에 두고 생성해야 합니다. 보류 인텐트를 사용하는 경우, 여러분의 앱은 +{@link android.content.Context#startActivity +startActivity()}와 같은 호출이 있는 앱을 실행하지 않게 됩니다. 대신 +{@link android.app.PendingIntent}를 생성할 때 원래 의도한 구성 요소 유형을 선언해야 합니다. 이때 각각의 생성자 메서드를 호출하는 방법을 씁니다.

+ +
    +
  • {@link android.app.Activity}를 시작하는 {@link android.content.Intent}의 경우, +{@link android.app.PendingIntent#getActivity PendingIntent.getActivity()}
  • +
  • {@link android.app.Service}를 시작하는 {@link android.content.Intent}의 경우, +{@link android.app.PendingIntent#getService PendingIntent.getService()}
  • +
  • {@link android.content.BroadcastReceiver}를 시작하는 {@link android.content.Intent}의 경우, +{@link android.app.PendingIntent#getBroadcast PendingIntent.getBroadcast()}
  • +
+ +

여러분의 앱이 다른 여러 앱에서 보류 인텐트를 수신하는 것이 아닌 한, +위의 메서드를 사용하여 {@link android.app.PendingIntent}를 생성하는 것이 아마도 여러분에게 필요한 유일한 +{@link android.app.PendingIntent} 메서드일 것입니다.

+ +

각 메서드는 현재 앱의 {@link android.content.Context}, 래핑하고자 하는 +{@link android.content.Intent}와 인텐트의 적절한 사용 방식을 나타내는 + 하나 이상의 플래그(인텐트를 한 번 이상 사용할 수 있는지 등) 등을 취합니다.

+ +

보류 인텐트 사용에 관한 자세한 정보는 각 해당되는 사용 사례에 대한 문서에 +제공되어 있습니다. 예를 들어 알림 + 및 앱 위젯 API 가이드가 이에 해당됩니다.

+ + + + + + + +

인텐트 확인

+ + +

시스템이 액티비티를 시작하라는 암시적 인텐트를 수신하면, 시스템은 해당 인텐트에 대한 최선의 액티비티를 검색합니다. +이때 다음과 같은 세 가지 측면을 근거로 인텐트를 인텐트 필터에 비교해 보는 방법을 씁니다.

+ +
    +
  • 인텐트 작업 +
  • 인텐트 데이터(URI와 데이터 유형 둘 다) +
  • 인텐트 카테고리 +
+ +

다음 섹션에서는 인텐트 필터가 앱의 매니페스트 파일에서 어떻게 선언되었는지와 관련하여 +인텐트가 적절한 구성 요소에 일치되는 방식을 설명합니다.

+ + +

작업 테스트

+ +

허용된 인텐트 작업을 나타내려면 인텐트 필터는 0개 이상의 +{@code +<action>} 요소를 선언할 수 있습니다. 예:

+ +
+<intent-filter>
+    <action android:name="android.intent.action.EDIT" />
+    <action android:name="android.intent.action.VIEW" />
+    ...
+</intent-filter>
+
+ +

이 필터를 통과하려면 {@link android.content.Intent}에 +지정된 작업이 필터에 목록으로 나열된 작업 중 하나와 일치해야만 합니다.

+ +

필터에 목록으로 나열된 작업이 없는 경우, 인텐트가 일치될 대상이 아무것도 없으므로 +모든 인텐트가 테스트에 실패합니다. 그러나, {@link android.content.Intent}가 +작업을 지정하지 않는 경우, 테스트를 통과하게 됩니다(필터에 최소한 한 개 이상의 작업이 +들어있지 않은 한).

+ + + +

카테고리 테스트

+ +

허용된 인텐트 카테고리를 나타내려면 인텐트 필터는 0개 이상의 +{@code +<category>} 요소를 선언할 수 있습니다. 예:

+ +
+<intent-filter>
+    <category android:name="android.intent.category.DEFAULT" />
+    <category android:name="android.intent.category.BROWSABLE" />
+    ...
+</intent-filter>
+
+ +

인텐트가 카테고리 테스트를 통과하려면 {@link android.content.Intent} 내의 +모든 카테고리가 필터 내의 카테고리에 일치해야 합니다. 이 반대로는 반드시 성립하지 않아도 됩니다. 인텐트 필터가 +{@link android.content.Intent}에서 지정된 것보다 더 많은 카테고리를 선언할 수도 있지만 그래도 +{@link android.content.Intent}는 통과합니다. 그러므로, 카테고리가 없는 인텐트라면 이 테스트를 항상 통과하는 것이 맞습니다. +이는 필터에 어떤 카테고리가 선언되어 있는지와는 무관합니다.

+ +

참고: +Android는 {@link android.content.Intent#CATEGORY_DEFAULT} 카테고리를 +{@link +android.content.Context#startActivity startActivity()} 및 {@link +android.app.Activity#startActivityForResult startActivityForResult()}에 전달된 모든 암시적 인텐트에 적용합니다. +따라서 액티비티가 암시적 인텐트를 수신하기를 원하는 경우, +그 인텐트 필터 내에 {@code "android.intent.category.DEFAULT"}에 대한 카테고리가 반드시 포함되어 있어야 합니다(이전의 +{@code <intent-filter>} 예시에서 표시된 내용 참조).

+ + + +

데이터 테스트

+ +

허용된 인텐트 데이터를 나타내려면 인텐트 필터는 0개 이상의 +{@code +<data>} 요소를 선언할 수 있습니다. 예:

+ +
+<intent-filter>
+    <data android:mimeType="video/mpeg" android:scheme="http" ... />
+    <data android:mimeType="audio/mpeg" android:scheme="http" ... />
+    ...
+</intent-filter>
+
+ +

<data> +요소는 URI 구조와 데이터 유형(MIME 미디어 유형)을 나타낼 수 있습니다. URI의 각 부분에 대해 별도의 +속성—{@code scheme}, {@code host}, {@code port} +및 {@code path}—이 있습니다. +

+ +

{@code <scheme>://<host>:<port>/<path>}

+ +

+예: +

+ +

{@code content://com.example.project:200/folder/subfolder/etc}

+ +

이 URI에서 구성표는 {@code content}이고 호스트는 {@code com.example.project}, +포트는 {@code 200}이며 경로는 {@code folder/subfolder/etc}입니다. +

+ +

이와 같은 속성은 각각 {@code <data>} 요소에서 선택 항목이지만, +이들 사이에 선형적 종속 관계가 있습니다.

+
    +
  • 구성표가 지정되지 않으면 호스트가 무시됩니다.
  • +
  • 호스트가 지정되지 않으면 포트가 무시됩니다.
  • +
  • 구성표와 호스트가 둘 다 지정되지 않으면 경로가 무시됩니다.
  • +
+ +

인텐트 안의 URI가 필터 안의 URI 사양에 비교되는 경우, 이것은 필터 내에 포함된 URI의 +몇몇 부분에만 비교되는 것입니다. 예:

+
    +
  • 필터가 구성표만 지정하는 경우, 해당 구성표가 있는 모든 URI가 필터와 +일치합니다.
  • +
  • 필터가 구성표와 권한은 지정하지만 경로는 지정하지 않는 경우, 같은 구성표와 권한이 있는 +모든 URI가 경로와 관계 없이 필터를 통과합니다.
  • +
  • 필터가 구성표, 권한과 경로를 지정하는 경우 같은 구성표, +권한과 경로가 있는 URI만 해당 필터를 통과합니다.
  • +
+ +

참고: 경로 사양에는 경로 이름이 +부분적으로만 일치하도록 요구하기 위해 와일드카드 별표(*)가 들어 있을 수 있습니다.

+ +

데이터 테스트는 인텐트 안의 URI와 MIME 유형 둘 모두를 필터 안에서 +지정된 MIME 유형과 비교합니다. 규칙은 다음과 같습니다. +

+ +
    +
  1. URI도 MIME 유형도 들어 있지 않은 인텐트가 테스트를 통과하는 것은 +필터가 URI나 MIME 유형을 전혀 지정하지 않은 경우뿐입니다.
  2. + +
  3. URI는 들어 있지만 MIME 유형은 없는 인텐트(URI로부터는 +명시적이지도 않고 추론할 수도 없음)가 테스트를 통과하는 것은 그 URI가 필터의 URI 형식과 일치하며 +필터가 인텐트와 마찬가지로 MIME 유형을 지정하지 않는 경우뿐입니다.
  4. + +
  5. MIME 유형은 들어 있지만 URI는 없는 인텐트가 테스트를 통과하는 것은 +해당 필터가 같은 MIME 유형을 목록으로 나열하지만 URI 형식은 지정하지 않은 경우뿐입니다.
  6. + +
  7. URI와 MIME 유형이 둘 다 들어 있는 인텐트(URI로부터는 +명시적이지도 않고 추론할 수도 없음)가 테스트의 MIME 유형 부분을 통과하는 것은 해당 유형이 +필터 내에 목록으로 나열된 한 유형에 일치하는 경우뿐입니다. 이것이 테스트의 URI 부분을 통과하는 것은 +URI가 필터 내의 URI와 일치하거나, {@code content:} + 또는 {@code file:} URI가 있고 필터가 URI를 지정하지 않은 경우뿐입니다. 달리 말하면, +구성 요소는 필터가 MIME 유형 하나만 목록으로 나열하는 경우 {@code content:} 및 {@code file:} 데이터를 +지원하는 것으로 간주한다는 뜻입니다.

  8. +
+ +

+마지막 규칙인 규칙 (d)는 +구성 요소가 파일이나 콘텐츠 제공자로부터 로컬 파일을 가져올 수 있는지에 대한 기대 수준을 반영합니다. +따라서 이러한 필터는 데이터 유형만 목록으로 나열해도 되고, +{@code content:} 및 {@code file:} 구성표의 이름을 명시적으로 제시하지 않아도 됩니다. +이것이 일반적인 경우입니다. 예를 들어, 다음 예시와 같은 {@code <data>} 요소는 +Android에 구성 요소가 콘텐츠 제공자에서 이미지 데이터를 가져와 표시할 수 있다고 +알립니다. +

+ +
+<intent-filter>
+    <data android:mimeType="image/*" />
+    ...
+</intent-filter>
+ +

+대부분의 사용 가능한 데이터는 콘텐츠 제공자가 제공하는 것이므로, 데이터 유형은 +지정하지만 URI는 지정하지 않는 필터가 아마 가장 보편적인 유형일 것입니다. +

+ +

+또 다른 보편적인 구성을 예로 들자면 구성표와 데이터 유형을 가진 필터가 있겠습니다. 예를 들어 +다음 예시와 같은 {@code <data>} + 요소는 Android에 구성 요소가 +작업을 수행하기 위해 네트워크에서 비디오 데이터를 검색할 수 있다고 알립니다. +

+ +
+<intent-filter>
+    <data android:scheme="http" android:type="video/*" />
+    ...
+</intent-filter>
+ + + +

인텐트 일치

+ +

인텐트를 인텐트 필터에 비교해 일치시키면 활성화할 대상 구성 요소를 +찾아낼 수 있을 뿐만 아니라, 기기에 있는 일련의 구성 요소에 대해 +무언가 알아낼 수도 있습니다. 예를 들어 홈 앱이 앱 시작 관리자를 채우려면 + +{@link android.content.Intent#ACTION_MAIN} 작업과 +{@link android.content.Intent#CATEGORY_LAUNCHER} 카테고리를 지정하는 인텐트 필터를 가진 액티비티를 모두 찾아야 합니다.

+ +

여러분의 애플리케이션도 인텐트 일치를 이와 비슷한 방식으로 사용할 수 있습니다. +{@link android.content.pm.PackageManager}에는 {@code query...()} + 메서드 집합이 있어 특정 인텐트를 허용하는 구성 요소를 모두 반환할 수 있고, +이와 유사한 일련의 {@code resolve...()} 메서드도 있어 한 인텐트에 응답하는 데 +가장 좋은 구성 요소를 판별할 수 있습니다. 예를 들어, +{@link android.content.pm.PackageManager#queryIntentActivities +queryIntentActivities()}는 인수로 통과한 +인텐트를 수행할 수 있는 모든 액티비티의 목록을 반환하며 {@link +android.content.pm.PackageManager#queryIntentServices +queryIntentServices()}는 이와 비슷한 서비스 목록을 반환합니다. +양쪽 메서드 모두 구성 요소를 활성화하지는 않습니다. 다만 응답할 수 있는 것들을 목록으로 +나열할 뿐입니다. 이와 비슷한 메서드인 +{@link android.content.pm.PackageManager#queryBroadcastReceivers +queryBroadcastReceivers()}는 브로드캐스트 수신기에 쓰입니다. +

+ + + + diff --git a/docs/html-intl/intl/ko/guide/components/loaders.jd b/docs/html-intl/intl/ko/guide/components/loaders.jd new file mode 100644 index 0000000000000000000000000000000000000000..cfbbb9133ed416d4ac66e20f0f5120b17414fb8f --- /dev/null +++ b/docs/html-intl/intl/ko/guide/components/loaders.jd @@ -0,0 +1,494 @@ +page.title=로더 +parent.title=액티비티 +parent.link=activities.html +@jd:body +
+
+

이 문서의 내용

+
    +
  1. 로더 API 요약
  2. +
  3. 애플리케이션 안에서 로더 사용하기 +
      +
    1. +
    2. 로더 시작
    3. +
    4. 로더 다시 시작
    5. +
    6. LoaderManager 콜백 사용하기
    7. +
    +
  4. +
  5. +
      +
    1. 추가 예
    2. +
    +
  6. +
+ +

Key 클래스

+
    +
  1. {@link android.app.LoaderManager}
  2. +
  3. {@link android.content.Loader}
  4. + +
+ +

관련 샘플

+
    +
  1. +LoaderCursor
  2. +
  3. +LoaderThrottle
  4. +
+
+
+ +

로더는 Android 3.0부터 도입된 것으로, 액티비티 또는 프래그먼트에서 비동기식으로 데이터를 쉽게 +로딩할 수 있게 합니다. 로더의 특성은 다음과 같습니다.

+
    +
  • 모든 {@link android.app.Activity}와 {@link +android.app.Fragment}에 사용할 수 있습니다.
  • +
  • 데이터의 비동기식 로딩을 제공합니다.
  • +
  • 데이터의 출처를 모니터링하여 그 콘텐츠가 변경되면 새 결과를 +전달합니다.
  • +
  • 구성 변경 후에 재생성된 경우, 마지막 로더의 커서로 자동으로 +다시 연결됩니다. 따라서 데이터를 다시 쿼리하지 않아도 +됩니다.
  • +
+ +

로더 API 요약

+ +

애플리케이션 안에서 로더를 사용하는 데 관련된 클래스와 인터페이스는 +여러 가지가 있습니다. 다음 표에서 요약되어 있습니다.

+ + + + + + + + + + + + + + + + + + + + + + + + + + +
클래스/인터페이스설명
{@link android.app.LoaderManager}{@link android.app.Activity} 또는 +{@link android.app.Fragment}와 연관된 추상 클래스로, 하나 이상의 {@link +android.content.Loader} 인스턴스를 관리하는 데 쓰입니다. 이것을 사용하면 애플리케이션이 +{@link android.app.Activity} + 또는 {@link android.app.Fragment} 수명 주기와 함께 실행 시간이 긴 작업을 관리하는 데 도움이 됩니다. 이것의 가장 보편적인 용법은 +{@link android.content.CursorLoader}와 함께 사용하는 것이지만, +다른 유형의 데이터를 로드하기 위해 애플리케이션이 자체 로더를 작성하는 것도 얼마든지 가능합니다. +
+
+ 액티비티 또는 프래그먼트당 {@link android.app.LoaderManager}는 하나씩밖에 없습니다. 하지만 {@link android.app.LoaderManager}에는 로더가 여러 개 있어도 +됩니다.
{@link android.app.LoaderManager.LoaderCallbacks}클라이언트에 대해 {@link +android.app.LoaderManager}와 상호 작용하도록 하는 콜백 인터페이스입니다. 예를 들어 {@link +android.app.LoaderManager.LoaderCallbacks#onCreateLoader onCreateLoader()} +콜백 메서드를 사용하여 새 로더를 생성할 수 있습니다.
{@link android.content.Loader}데이터의 비동기식 로딩을 수행하는 추상 클래스입니다. 이것이 로더의 기본 +클래스입니다. 보통은 {@link +android.content.CursorLoader}를 사용하기 마련이지만, 자신만의 하위 클래스를 구현해도 됩니다. 로더가 활성 상태인 동안에는 +소속 데이터의 출처를 모니터링하고 콘텐츠가 변경되면 새 결과를 +전달하는 것이 정상입니다.
{@link android.content.AsyncTaskLoader}작업을 수행할 {@link android.os.AsyncTask}를 제공하는 추상 로더입니다.
{@link android.content.CursorLoader}{@link android.content.AsyncTaskLoader}의 하위 클래스로, 이것이 +{@link android.content.ContentResolver}를 쿼리하고 {@link +android.database.Cursor}를 반환합니다. 이 클래스는 커서 쿼리에 대한 표준 방식으로 {@link +android.content.Loader} 프로토콜을 구현하며, +{@link android.content.AsyncTaskLoader}에 구축되어 +배경 스레드에서 커서 쿼리를 수행하므로 애플리케이션의 UI를 차단하지 않습니다. +이 로더를 사용하는 것이 프래그먼트나 액티비티의 API를 통해 관리된 쿼리를 수행하는 대신 {@link +android.content.ContentProvider}에서 +비동기식으로 데이터를 로딩하는 최선의 방법입니다.
+ +

위의 표에 나열된 클래스와 인터페이스가 애플리케이션 내에서 로더를 구현하는 데 +사용할 기본적인 구성 요소입니다. 생성하는 로더마다 +이 모든 것이 다 필요한 것은 아니지만, 로더를 초기화하려면 항상 {@link +android.app.LoaderManager}를 참조해야 하고 {@link +android.content.CursorLoader}와 같은 {@link android.content.Loader} +클래스도 구현해야 합니다. 다음 몇 섹션에서는 애플리케이션 안에서 이와 같은 +클래스와 인터페이스를 사용하는 법을 보여줍니다.

+ +

애플리케이션 안에서 로더 사용하기

+

이 섹션에서는 Android 애플리케이션 내에서 로더를 사용하는 방법을 설명합니다. 로더를 +사용하는 애플리케이션에는 보통 다음이 포함되어 있습니다.

+
    +
  • {@link android.app.Activity} 또는 {@link android.app.Fragment}.
  • +
  • {@link android.app.LoaderManager}의 인스턴스.
  • +
  • {@link +android.content.ContentProvider}가 지원하는 데이터를 로딩할 {@link android.content.CursorLoader}. 아니면, 개발자 나름의 +{@link android.content.Loader} 또는 {@link android.content.AsyncTaskLoader} 하위 클래스를 구현하여 +다른 출처에서 데이터를 로딩해도 됩니다.
  • +
  • {@link android.app.LoaderManager.LoaderCallbacks}의 구현. +여기에서 새 로더를 생성하고 기존 로더에 대한 참조를 +관리합니다.
  • +
  • 로더의 데이터를 표시하는 방법(예: {@link +android.widget.SimpleCursorAdapter})
  • +
  • {@link android.content.ContentProvider}와 같은 데이터 출처로, +{@link android.content.CursorLoader}를 사용하는 경우에 해당.
  • +
+

로더 시작

+ +

{@link android.app.LoaderManager}는 {@link android.app.Activity} 또는 +{@link android.app.Fragment} 내에서 하나 이상의 {@link +android.content.Loader} 인스턴스를 관리합니다. 액티비티 또는 프래그먼트당 {@link +android.app.LoaderManager}는 하나씩밖에 없습니다.

+ +

보통은 +액티비티의 {@link +android.app.Activity#onCreate onCreate()} 메서드 내에서, 또는 프래그먼트의 +{@link android.app.Fragment#onActivityCreated onActivityCreated()} 메서드 내에서 {@link android.content.Loader}를 초기화합니다. 이렇게 하려면 +다음과 같은 방법을 따릅니다.

+ +
// Prepare the loader.  Either re-connect with an existing one,
+// or start a new one.
+getLoaderManager().initLoader(0, null, this);
+ +

{@link android.app.LoaderManager#initLoader initLoader()} 메서드는 +다음과 같은 인수를 취합니다.

+
    +
  • 로더를 식별하는 고유한 ID. 이 예시에서 ID는 0입니다.
  • +
  • 생성 시 로더에 제공할 선택적 인수 +(이 예시에서는 null).
  • + +
  • {@link android.app.LoaderManager.LoaderCallbacks} 구현. 로더 이벤트를 보고하기 위해 +{@link android.app.LoaderManager}가 이것을 호출합니다. 이 예시에서는 + 로컬 클래스가 {@link +android.app.LoaderManager.LoaderCallbacks} 인터페이스를 구현하여 자신에 대한 참조인 +{@code this}를 통과합니다.
  • +
+

{@link android.app.LoaderManager#initLoader initLoader()} 호출로 로더가 초기화되었고 활성 상태이도록 +확실히 합니다. 이로써 발생할 수 있는 결과가 두 가지 있습니다.

+
    +
  • ID가 지정한 로더가 이미 존재하는 경우, 마지막으로 생성된 로더를 +재사용합니다.
  • +
  • ID가 지정한 로더가 존재하지 않는 경우, +{@link android.app.LoaderManager#initLoader initLoader()}가 +{@link android.app.LoaderManager.LoaderCallbacks} 메서드 {@link android.app.LoaderManager.LoaderCallbacks#onCreateLoader onCreateLoader()}를 발생시킵니다. +여기에서 인스턴트화할 코드를 구현하고 새 로더를 반환합니다. +자세한 논의는 onCreateLoader 섹션을 참조하십시오.
  • +
+

어떤 경우에든 주어진 {@link android.app.LoaderManager.LoaderCallbacks} +구현은 해당 로더와 연관되어 있으며, 로더 상태가 변경되면 +이것이 호출됩니다. 이 호출의 그러한 시점에서 발신자가 +시작된 상태에 있으며 요청한 로더가 이미 존재하고 자신의 데이터를 +생성해 놓은 경우, 시스템은 즉시 {@link +android.app.LoaderManager.LoaderCallbacks#onLoadFinished onLoadFinished()}를 +호출합니다({@link android.app.LoaderManager#initLoader initLoader()} 도중). +따라서 이런 일이 발생할 것에 대비해두어야만 합니다. 이 콜백에 대한 자세한 논의는 +onLoadFinished를 참조하십시오.

+ +

{@link android.app.LoaderManager#initLoader initLoader()} +메서드는 생성된 {@link android.content.Loader}를 반환하지만, 이에 대한 참조를 캡처하지 않아도 된다는 점을 +유의하십시오. {@link android.app.LoaderManager}는 로더의 수명을 +자동으로 관리합니다. {@link android.app.LoaderManager}는 +필요에 따라 로딩을 시작하고 중단하며, 로더와 그에 연관된 콘텐츠의 상태를 +유지관리합니다. 이것이 시사하는 바와 같이, 로더와 직접적으로 +상호 작용하는 경우는 극히 드뭅니다(다만, 로더의 행동을 미세하게 조정하기 위해 로더 메서드를 사용하는 사례를 알아보려면 + LoaderThrottle 샘플을 참조하면 좋습니다). +가장 보편적으로 사용되는 메서드는 {@link +android.app.LoaderManager.LoaderCallbacks}로, 이를 사용해 특정한 이벤트가 일어났을 때 +로딩 프로세스에 개입하게 됩니다. 이 주제에 대한 자세한 논의는 LoaderManager 콜백 사용하기를 참조하십시오.

+ +

로더 다시 시작

+ +

위에서 나타난 것과 같이 {@link android.app.LoaderManager#initLoader initLoader()}를 사용하는 경우, +이것은 지정된 ID에 해당되는 기존 로더가 있으면 그것을 사용합니다. +없으면, 하나 생성합니다. 하지만 때로는 오래된 데이터를 폐기하고 새로 시작하고 싶을 때가 +있습니다.

+ +

오래된 데이터를 폐기하려면 {@link +android.app.LoaderManager#restartLoader restartLoader()}를 사용합니다. 예를 들어, 다음의 +{@link android.widget.SearchView.OnQueryTextListener} 구현은 +사용자의 쿼리가 변경되면 로더를 다시 시작합니다. 로더를 다시 시작해야 수정된 검색 필터를 사용하여 +새 쿼리를 수행할 수 있습니다.

+ +
+public boolean onQueryTextChanged(String newText) {
+    // Called when the action bar search text has changed.  Update
+    // the search filter, and restart the loader to do a new query
+    // with this filter.
+    mCurFilter = !TextUtils.isEmpty(newText) ? newText : null;
+    getLoaderManager().restartLoader(0, null, this);
+    return true;
+}
+ +

LoaderManager 콜백 사용하기

+ +

{@link android.app.LoaderManager.LoaderCallbacks}는 클라이언트가 +{@link android.app.LoaderManager}와 상호 작용할 수 있게 해주는 콜백 인터페이스입니다.

+

로더, 특히 {@link android.content.CursorLoader}는 중단된 후에도 +자신의 데이터를 유지할 것으로 기대됩니다. 이 때문에 애플리케이션이 +액티비티 또는 프래그먼트의 @link android.app.Activity#onStop +onStop()} 및 {@link android.app.Activity#onStart onStart()}를 가로질러 데이터를 유지할 수 있고, +따라서 사용자가 애플리케이션에 되돌아오면 데이터가 다시 로딩되기를 기다리지 +않아도 됩니다. 새 로더를 언제 생성해야 할지 알아보려면 {@link android.app.LoaderManager.LoaderCallbacks} +메서드를 사용합니다. 또한 로더의 데이터 사용을 중단할 때가 되면 +이를 애플리케이션에 알리는 데에도 이것을 사용할 수 있습니다.

+ +

{@link android.app.LoaderManager.LoaderCallbacks}에는 다음과 같은 메서드가 +포함됩니다.

+
    +
  • {@link android.app.LoaderManager.LoaderCallbacks#onCreateLoader onCreateLoader()} - +주어진 ID에 대하여 인스턴트화하고 새 {@link android.content.Loader}를 반환합니다. +
+
    +
  • {@link android.app.LoaderManager.LoaderCallbacks#onLoadFinished onLoadFinished()} +- 이전에 생성된 로더가 로딩을 완료하면 호출됩니다. +
+
    +
  • {@link android.app.LoaderManager.LoaderCallbacks#onLoaderReset onLoaderReset()} +- 이전에 생성된 로더가 휴식 중이라서 해당되는 데이터를 사용할 수 없을 경우 +호출됩니다. +
  • +
+

이러한 메서드는 다음 섹션에 보다 자세하게 설명되어 있습니다.

+ +

onCreateLoader

+ +

로더에 액세스하려 시도하는 경우(예를 들어 {@link +android.app.LoaderManager#initLoader initLoader()}를 통해), 로더는 해당 ID가 지정하는 로더가 존재하는지 +여부를 확인합니다. 그런 로더가 없으면, {@link +android.app.LoaderManager.LoaderCallbacks} 메서드 {@link +android.app.LoaderManager.LoaderCallbacks#onCreateLoader onCreateLoader()}를 발생시킵니다. 여기에서 +새 로더를 생성합니다. 이것은 보통 {@link +android.content.CursorLoader}이지만, 개발자 나름대로 {@link +android.content.Loader} 하위 클래스를 구현해도 됩니다.

+ +

이 예시에서는, {@link +android.app.LoaderManager.LoaderCallbacks#onCreateLoader onCreateLoader()} + 콜백 메서드가 {@link android.content.CursorLoader}를 생성합니다. +{@link android.content.CursorLoader}를 자체 생성자 메서드를 사용하여 구축해야 합니다. 이것은 +{@link +android.content.ContentProvider}로 쿼리를 수행하기 위해 필요한 모든 정보 집합을 필요로 합니다. 구체적으로 필요한 것은 다음과 같습니다.

+
    +
  • uri — 검색할 콘텐츠의 URI입니다.
  • +
  • 예측 — 반환할 열 목록입니다. +null을 전달하면 모든 열을 반환하며, 이는 비효율적입니다.
  • +
  • 선택 — 반환할 행을 선언하는 필터로, +SQL WHERE 절로 형식이 설정됩니다(WHERE 자체는 제외). +null을 반환하면 주어진 URI에 대한 모든 행을 반환합니다.
  • +
  • selectionArgs — 선택에 ?를 포함해도 됩니다. 이렇게 하면 +이것이 selectionArgs에서 가져온 값으로 교체되며, 이때 선택에 표시되는 +순서를 따릅니다. 값은 문자열로 바인딩됩니다.
  • +
  • sortOrder — SQL ORDER BY 절 형식으로 설정된 +행의 순서 지정 방법입니다(ORDER BY 자체는 제외). null을 +전달하면 기본 정렬 순서를 사용하는데, 이는 순서가 없습니다.
  • +
+

예:

+
+ // If non-null, this is the current filter the user has provided.
+String mCurFilter;
+...
+public Loader<Cursor> onCreateLoader(int id, Bundle args) {
+    // This is called when a new Loader needs to be created.  This
+    // sample only has one Loader, so we don't care about the ID.
+    // First, pick the base URI to use depending on whether we are
+    // currently filtering.
+    Uri baseUri;
+    if (mCurFilter != null) {
+        baseUri = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI,
+                  Uri.encode(mCurFilter));
+    } else {
+        baseUri = Contacts.CONTENT_URI;
+    }
+
+    // Now create and return a CursorLoader that will take care of
+    // creating a Cursor for the data being displayed.
+    String select = "((" + Contacts.DISPLAY_NAME + " NOTNULL) AND ("
+            + Contacts.HAS_PHONE_NUMBER + "=1) AND ("
+            + Contacts.DISPLAY_NAME + " != '' ))";
+    return new CursorLoader(getActivity(), baseUri,
+            CONTACTS_SUMMARY_PROJECTION, select, null,
+            Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC");
+}
+

onLoadFinished

+ +

이 메서드는 이전에 생성된 로더가 로딩을 완료하면 호출됩니다. +이 로더에 대해 제공된 마지막 데이터가 릴리스되기 전에 틀림없이 +이 메서드가 호출됩니다. 이 시점에서 오래된 데이터의 +사용 내용을 모두 제거해야 하지만(곧 릴리스될 것이므로), 데이터 릴리스를 직접 수행해서는 안 됩니다. 해당 데이터는 +로더의 소유이며, 로더가 알아서 처리할 것이기 때문입니다.

+ + +

로더는 애플리케이션이 데이터를 더 이상 사용하지 않는다는 사실을 알게 되면 곧바로 해당 데이터를 +릴리스할 것입니다. 예를 들어 데이터가 {@link +android.content.CursorLoader}의 커서인 경우, 거기에서 직접 {@link +android.database.Cursor#close close()}를 호출하면 안 됩니다. 커서가 +{@link android.widget.CursorAdapter}에 배치되는 경우, {@link +android.widget.SimpleCursorAdapter#swapCursor swapCursor()} 메서드를 사용해야 합니다. 그래야 +오래된 {@link android.database.Cursor}가 종료되지 않습니다. 예:

+ +
+// This is the Adapter being used to display the list's data.
SimpleCursorAdapter mAdapter; +... + +public void onLoadFinished(Loader<Cursor> loader, Cursor data) { + // Swap the new cursor in.  (The framework will take care of closing the + // old cursor once we return.) + mAdapter.swapCursor(data); +}
+ +

onLoaderReset

+ +

이 메서드는 이전에 생성된 로더가 휴식 중이라서 해당되는 데이터를 사용할 수 없는 경우 +호출됩니다. 이 콜백을 사용하면 데이터가 언제 릴리스될지 알아낼 수 있어 +이에 대한 참조를 직접 제거할 수 있습니다.  

+

이 구현은 +{@link android.widget.SimpleCursorAdapter#swapCursor swapCursor()}를 호출하며, +이때 값은 null을 씁니다.

+ +
+// This is the Adapter being used to display the list's data.
+SimpleCursorAdapter mAdapter;
+...
+
+public void onLoaderReset(Loader<Cursor> loader) {
+    // This is called when the last Cursor provided to onLoadFinished()
+    // above is about to be closed.  We need to make sure we are no
+    // longer using it.
+    mAdapter.swapCursor(null);
+}
+ + +

+ +

일례를 제시하기 위해 아래에 {@link android.widget.ListView}를 표시하는 {@link +android.app.Fragment}의 전체 구현을 나타내었습니다. 여기에는 +연락처 콘텐츠 제공자에 대한 쿼리 결과가 들어 있습니다. 이것은 {@link +android.content.CursorLoader}를 사용하여 제공자에 대한 쿼리를 관리합니다.

+ +

이 예시에서 나타낸 바와 같이 애플리케이션이 사용자의 연락처에 액세스하려면 +애플리케이션의 매니페스트에 +{@link android.Manifest.permission#READ_CONTACTS READ_CONTACTS} 권한이 포함되어 있어야 합니다.

+ +
+public static class CursorLoaderListFragment extends ListFragment
+        implements OnQueryTextListener, LoaderManager.LoaderCallbacks<Cursor> {
+
+    // This is the Adapter being used to display the list's data.
+    SimpleCursorAdapter mAdapter;
+
+    // If non-null, this is the current filter the user has provided.
+    String mCurFilter;
+
+    @Override public void onActivityCreated(Bundle savedInstanceState) {
+        super.onActivityCreated(savedInstanceState);
+
+        // Give some text to display if there is no data.  In a real
+        // application this would come from a resource.
+        setEmptyText("No phone numbers");
+
+        // We have a menu item to show in action bar.
+        setHasOptionsMenu(true);
+
+        // Create an empty adapter we will use to display the loaded data.
+        mAdapter = new SimpleCursorAdapter(getActivity(),
+                android.R.layout.simple_list_item_2, null,
+                new String[] { Contacts.DISPLAY_NAME, Contacts.CONTACT_STATUS },
+                new int[] { android.R.id.text1, android.R.id.text2 }, 0);
+        setListAdapter(mAdapter);
+
+        // Prepare the loader.  Either re-connect with an existing one,
+        // or start a new one.
+        getLoaderManager().initLoader(0, null, this);
+    }
+
+    @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+        // Place an action bar item for searching.
+        MenuItem item = menu.add("Search");
+        item.setIcon(android.R.drawable.ic_menu_search);
+        item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
+        SearchView sv = new SearchView(getActivity());
+        sv.setOnQueryTextListener(this);
+        item.setActionView(sv);
+    }
+
+    public boolean onQueryTextChange(String newText) {
+        // Called when the action bar search text has changed.  Update
+        // the search filter, and restart the loader to do a new query
+        // with this filter.
+        mCurFilter = !TextUtils.isEmpty(newText) ? newText : null;
+        getLoaderManager().restartLoader(0, null, this);
+        return true;
+    }
+
+    @Override public boolean onQueryTextSubmit(String query) {
+        // Don't care about this.
+        return true;
+    }
+
+    @Override public void onListItemClick(ListView l, View v, int position, long id) {
+        // Insert desired behavior here.
+        Log.i("FragmentComplexList", "Item clicked: " + id);
+    }
+
+    // These are the Contacts rows that we will retrieve.
+    static final String[] CONTACTS_SUMMARY_PROJECTION = new String[] {
+        Contacts._ID,
+        Contacts.DISPLAY_NAME,
+        Contacts.CONTACT_STATUS,
+        Contacts.CONTACT_PRESENCE,
+        Contacts.PHOTO_ID,
+        Contacts.LOOKUP_KEY,
+    };
+    public Loader<Cursor> onCreateLoader(int id, Bundle args) {
+        // This is called when a new Loader needs to be created.  This
+        // sample only has one Loader, so we don't care about the ID.
+        // First, pick the base URI to use depending on whether we are
+        // currently filtering.
+        Uri baseUri;
+        if (mCurFilter != null) {
+            baseUri = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI,
+                    Uri.encode(mCurFilter));
+        } else {
+            baseUri = Contacts.CONTENT_URI;
+        }
+
+        // Now create and return a CursorLoader that will take care of
+        // creating a Cursor for the data being displayed.
+        String select = "((" + Contacts.DISPLAY_NAME + " NOTNULL) AND ("
+                + Contacts.HAS_PHONE_NUMBER + "=1) AND ("
+                + Contacts.DISPLAY_NAME + " != '' ))";
+        return new CursorLoader(getActivity(), baseUri,
+                CONTACTS_SUMMARY_PROJECTION, select, null,
+                Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC");
+    }
+
+    public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
+        // Swap the new cursor in.  (The framework will take care of closing the
+        // old cursor once we return.)
+        mAdapter.swapCursor(data);
+    }
+
+    public void onLoaderReset(Loader<Cursor> loader) {
+        // This is called when the last Cursor provided to onLoadFinished()
+        // above is about to be closed.  We need to make sure we are no
+        // longer using it.
+        mAdapter.swapCursor(null);
+    }
+}
+

추가 예

+ +

로더를 사용하는 법을 보여주는 몇 가지 다른 예가 ApiDemos에 +소개되어 있습니다.

+
    +
  • +LoaderCursor — 위에 표시된 코드 조각의 완전한 +버전입니다.
  • +
  • LoaderThrottle — 데이터가 변경될 때 콘텐츠 제공자가 +수행하는 쿼리의 수를 줄이기 위해 제한을 사용하는 방법을 예시로 나타낸 것입니다.
  • +
+ +

SDK 샘플을 다운로드하고 설치하는 데 대한 정보는 샘플 +가져오기를 참조하십시오.

+ diff --git a/docs/html-intl/intl/ko/guide/components/processes-and-threads.jd b/docs/html-intl/intl/ko/guide/components/processes-and-threads.jd new file mode 100644 index 0000000000000000000000000000000000000000..850d55cf9c71d5898ba354f955cf072d57a97143 --- /dev/null +++ b/docs/html-intl/intl/ko/guide/components/processes-and-threads.jd @@ -0,0 +1,411 @@ +page.title=프로세스 및 스레드 +page.tags=수명 주기, 배경 + +@jd:body + + + +

애플리케이션 구성 요소가 시작되고 애플리케이션에 실행 중인 다른 구성 요소가 없으면 +Android 시스템은 하나의 실행 스레드로 애플리케이션의 Linux 프로세스를 +시작합니다. 기본적으로 같은 애플리케이션의 모든 구성 요소는 같은 프로세스와 스레드에서 실행됩니다 +("기본" 스레드라고 합니다). 애플리케이션 구성 요소가 시작되고 (애플리케이션의 다른 구성 요소가 존재해서) +해당 애플리케이션의 프로세스가 이미 존재하면 해당 구성 요소는 +프로세스 내에서 시작되고 같은 실행 스레드를 사용합니다. 하지만 애플리케이션 내의 +여러 가지 구성 요소가 각자 별도의 프로세스에서 실행되도록 할 수도 있고, 어느 프로세스에든 추가 스레드를 +만들 수 있습니다.

+ +

이 문서는 프로세스와 스레드가 Android 애플리케이션에서 작동하는 방식을 설명합니다.

+ + +

프로세스

+ +

기본적으로 같은 애플리케이션의 모든 구성 요소는 같은 프로세스와 스레드에서 실행되고, +대부분의 애플리케이션은 이를 바꿔서는 안 됩니다. 그러나 어느 프로세스가 특정 구성 요소에 속하는지 +확인해야 할 경우 매니페스트 파일에서 확인할 수 있습니다.

+ +

구성 요소 —{@code +<activity>}{@code +<service>}, {@code +<receiver>}{@code +<provider>}—의 각 유형에 대한 매니페스트 항목은 구성 요소를 실행할 프로세스를 지정하는 {@code android:process} 속성을 지원합니다. + 이러한 속성을 설정하여 각 구성 요소를 자체 프로세스에서 실행시키거나 +다른 구성 요소를 제외한 일부 구성 요소만 프로세스를 공유하게 할 수 있습니다 또한, +{@code android:process}를 설정하여 다른 애플리케이션의 구성 요소를 같은 프로세스에서 실행시킬 수 있습니다. +단, 이는 애플리케이션이 같은 Linux 사용자 ID를 공유하고 같은 인증서에 +서명되었을 경우에 한합니다.

+ +

{@code +<application>} 요소도 {@code android:process} 속성을 지원하여, +모든 구성 요소에 적용되는 기본값을 설정합니다.

+ +

Android는 어느 시점엔가 프로세스를 종료하기로 결정할 수도 있습니다. 즉 메모리가 부족하거나, 사용자에게 더욱 즉각적인 서비스를 제공하는 +다른 프로세스가 이 프로세스의 중단을 필요로 하는 경우 등입니다. 그러면 중단된 +프로세스에서 실행되고 있던 애플리케이션 구성 요소도 따라서 소멸됩니다. 그와 같은 구성 요소가 할 작업이 다시 생기면 +그에 대한 프로세스도 다시 시작됩니다.

+ +

어느 프로세스를 삭제할지 결정할 때, Android 시스템은 +사용자에 대한 이들의 상대적 중요성을 가늠합니다. 예를 들어, 눈에 보이는 액티비티를 호스팅하는 프로세스와 비교하여 +화면에 보이지 않는 액티비티를 호스팅하는 프로세스를 쉽게 종료할 수 있습니다. 프로세스 종료 결정은 +해당 프로세스에서 실행되는 구성 요소의 상태에 따라 달라집니다. 종료할 +프로세스를 결정하는 데 사용하는 규칙은 아래에 설명되어 있습니다.

+ + +

프로세스 수명 주기

+ +

Android 시스템은 최대한 오래 애플리케이션 프로세스를 유지하려고 시도하지만, +결국 오래된 프로세스를 제거하고 새 프로세스나 더 중요한 프로세스에 사용할 메모리를 확보해야 합니다. 유지할 +프로세스와 종료할 프로세스를 결정하기 위해 +시스템은 프로세스에서 실행되는 구성 요소와 해당 구성 요소의 상태에 기초하여 각 프로세스에 +"중요 계층"을 부여합니다. 중요도가 낮은 +프로세스가 먼저 제거되고, 그 다음으로 중요도가 낮은 프로세스를 제거하는 식으로 +필요에 따라 시스템 리소스를 회복하는 것입니다.

+ +

중요 계층에는 다섯 가지 단계가 있습니다. 다음 목록은 +중요도 순서에 따른 프로세스 유형을 나타낸 것입니다(첫 번째 프로세스가 가장 중요하고 +마지막으로 종료됩니다).

+ +
    +
  1. 전경 프로세스 +

    사용자가 현재 진행하는 작업에 필요한 프로세스입니다. 다음 조건 중 +하나가 참일 경우 프로세스가 전경에 있는 것으로 간주합니다.

    + +
      +
    • 사용자와 상호 작용하는 {@link android.app.Activity}를 호스팅할 경우({@link +android.app.Activity}의 {@link android.app.Activity#onResume onResume()} 메서드가 호출되었을 경우 +).
    • + +
    • 사용자와 상호 작용하는 액티비티에 바인딩된 {@link android.app.Service}를 호스팅할 경우. +
    • + +
    • "전경에서" 실행되는 {@link android.app.Service}를 호스팅할 경우( +해당 서비스가 {@link android.app.Service#startForeground startForeground()}를 호출했을 경우). + +
    • 수명 주기 콜백 중 하나를 실행하는 {@link android.app.Service}를 호스팅할 경우 +({@link android.app.Service#onCreate onCreate()}, {@link android.app.Service#onStart +onStart()} 또는 {@link android.app.Service#onDestroy onDestroy()}).
    • + +
    • {@link +android.content.BroadcastReceiver#onReceive onReceive()} 메서드를 실행하는 {@link android.content.BroadcastReceiver}를 호스팅할 경우.
    • +
    + +

    일반적으로, 주어진 시간에 존재하는 전경 프로세스는 몇 개밖에 되지 않습니다. 이들은 최후의 +수단으로서만 종료됩니다. 즉, 메모리가 너무 부족해 계속 실행할 수 없는 경우를 말합니다. 일반적으로 그 시점이 되면 +기기가 메모리 페이징 상태에 도달한 것이므로 전경 프로세스 몇 개를 중단해야만 +사용자 인터페이스의 반응성을 유지할 수 있습니다.

  2. + +
  3. 가시적 프로세스 +

    전경 구성 요소는 없지만 +사용자가 화면에서 보는 것에 영향을 미칠 수 있는 프로세스입니다. 다음 조건 중 하나가 참이면 +가시적 프로세스로 간주합니다.

    + +
      +
    • 전경에 있지는 않지만 사용자에게 보이는 {@link android.app.Activity}를 호스팅할 경우 +({@link android.app.Activity#onPause onPause()} 메서드가 호출되었을 경우). +예를 들어 이것은 전경 액티비티가 대화를 시작하여 이전 액티비티가 그 뒤에 보일 경우 + 발생합니다.
    • + +
    • 눈에 보이는(또는 전경) 액티비티에 바인딩된 {@link android.app.Service}를 호스팅할 경우. +
    • +
    + +

    가시적인 프로세스는 매우 중요도가 높은 것으로 취급하고 모든 전경 프로세스를 실행하는 데 필요할 경우에만 +종료됩니다.

    +
  4. + +
  5. 서비스 프로세스 +

    {@link +android.content.Context#startService startService()} 메서드로 시작되었지만 두 개의 상위 계층 분류에 +들어가지 않는 서비스를 실행하는 프로세스입니다. 서비스 프로세스는 사용자가 보는 것과 직접 연결되어 있지는 않지만, +일반적으로 사용자가 신경 쓰는 작업을 하므로(배경에서 음악 재생 또는 네트워크에서 데이터 다운로드) +시스템은 모든 전경 및 가시적 프로세스와 함께 실행할 만큼 메모리가 충분하지 않을 경우에만 +프로세스를 중단합니다.

    +
  6. + +
  7. 배경 프로세스 +

    현재 사용자에게 보이지 않는 액티비티를 보유한 프로세스입니다(액티비티의 +{@link android.app.Activity#onStop onStop()} 메서드가 호출되었습니다). 이와 같은 프로세스는 +사용자 환경에 직접적 영향을 미치지 않고, 시스템은 언제든 이 프로세스를 중단시켜 +전경, +가시적 또는 서비스 프로세스를 위한 메모리를 확보할 수 있습니다. 보통 한번에 실행 중인 배경 프로세스가 많은 편이므로 이들은 +LRU(최저 사용 빈도) 목록에 보관하여 사용자가 가장 최근에 본 액티비티가 있는 +프로세스가 가장 마지막에 중단되도록 합니다. 액티비티가 수명 주기 메서드를 올바르게 구현하고 +자신의 현재 상태를 저장할 경우, 사용자가 액티비티로 다시 이동할 때 액티비티가 모든 가시적 상태를 +복원하므로 프로세스를 중단시키더라도 사용자 환경에는 눈에 띄게 영향을 미치지 +않습니다. 상태 저장과 복원에 관한 정보는 액티비티 +문서를 참조하십시오.

    +
  8. + +
  9. 빈 프로세스 +

    활성 애플리케이션 구성 요소를 보유하지 않은 프로세스입니다. 이런 프로세스를 +유지하는 유일한 이유는 다음에 내부 구성 요소를 실행할 때 시작 시간을 절약하기 위한 캐싱 +때문입니다. 시스템은 프로세스 캐시와 기본 커널 캐시 사이에서 전반적인 시스템 리소스의 균형을 맞추기 위해 +이 프로세스를 중단시키는 경우가 많습니다.

    +
  10. +
+ + +

Android는 프로세스에서 현재 활성 상태인 구성 요소의 중요도에 따라 +프로세스에 가장 높은 수준을 부여합니다. 예를 들어, 프로세스가 서비스와 가시적 액티비티를 호스팅할 경우, +해당 프로세스는 서비스 프로세스가 아니라 가시적 프로세스 등급이 부여됩니다.

+ +

또한, 프로세스의 등급은 다른 프로세스가 이에 의존할 경우 상승할 수 있습니다. +즉, 다른 프로세스에 서비스를 제공하는 프로세스가 서비스 제공 대상 프로세스보다 +등급이 낮은 경우는 있을 수 없습니다. 예를 들어 프로세스 A의 콘텐츠 제공자가 프로세스 B의 클라이언트에 서비스를 제공하거나 +프로세스 A의 서비스가 프로세스 B의 구성 요소에 바인딩되어 있을 경우 프로세스 A는 항상 중요도가 +프로세스 B와 같거나 그보다 높습니다.

+ +

서비스를 실행하는 프로세스가 배경 액티비티가 포함된 프로세스보다 높으므로, +장기 작업을 시작하는 액티비티는 작업자 스레드만 생성하기보다는 해당 작업에 대한 서비스를 시작하는 것이 좋습니다. +이는 특히 작업이 해당 액티비티보다 오래 지속될 경우 더욱 중요합니다. +예를 들어, 웹사이트에 사진을 업로드하는 액티비티가 업로드를 수행하는 서비스를 시작해야 +사용자가 액티비티를 떠나더라도 배경에서 업로드를 지속할 수 있습니다. +서비스를 사용하면 액티비티에 어떤 일이 발생하든 해당 작업에 반드시 +"서비스 프로세스" 우선 순위 이상이 부여됩니다. 이것이 브로드캐스트 수신기가 시간이 오래 걸리는 작업을 +스레드에 넣기보다는 서비스를 사용해야 하는 것과 같은 이유입니다.

+ + + + +

스레드

+ +

애플리케이션이 시작되면 시스템이 애플리케이션에 대한 실행의 스레드를 생성하며, 이를 +"기본"이라고 합니다. 이 스레드는 드로어블 이벤트를 포함하여 적절한 사용자 인터페이스 위젯에 +이벤트를 발송하는 역할을 맡기 때문에 중요합니다. 이것은 Android UI 도구 키트의 구성 요소({@link +android.widget}과 {@link android.view} 패키지의 구성 요소)와 개발자의 애플리케이션이 +상호 작용하는 스레드이기도 합니다. 따라서 기본 스레드는 +UI 스레드라고 불릴 때도 있습니다.

+ +

시스템은 구성 요소의 각 인스턴스에 대해 별도의 스레드를 생성하지 않습니다. 같은 +프로세스에서 실행되는 모든 구성 요소는 UI 스레드에서 시작되고, 각 구성 요소를 호출하는 시스템이 +해당 스레드에서 발송됩니다. 따라서 +시스템 콜백에 응답하는 메서드(사용자 작업을 보고하는 {@link android.view.View#onKeyDown onKeyDown()} 또는 +수명 주기 콜백 메서드)는 항상 프로세스의 UI 스레드에서 실행됩니다.

+ +

예를 들어, 사용자가 화면의 버튼을 터치하면, 앱 UI 스레드가 위젯에 터치 이벤트를 발송하고, +위젯은 눌린 상태를 설정하고 이벤트 대기열에 +무효화 요청을 게시합니다. UI 스레드가 이 요청을 대기열에서 해제하고 위젯에 스스로를 다시 +그려야 한다고 알립니다.

+ +

앱이 사용자 상호작용에 응답하여 집약적인 작업을 수행할 때는 이 단일 스레드 모델은 +애플리케이션을 제대로 구현하지 않으면 낮은 성능을 보일 수 있습니다. 특히, +모든 것이 UI 스레드에서 발생하고 네트워크 액세스나 데이터 베이스 쿼리 등의 긴 작업을 수행하면 +UI가 통째로 차단됩니다. 스레드가 차단되면 드로잉 이벤트를 포함하여 +모든 이벤트가 발송되지 않습니다. 사용자가 보기에는 애플리케이션이 +중단된 것처럼 보입니다. 더 나쁜 경우, UI 스레드가 몇 초 이상 차단되어 있으면 +(현재 약 5초) 사용자에게 악명 높은 "애플리케이션이 응답하지 +않습니다"(ANR) 대화가 표시됩니다. 그러면 사용자가 여러분의 애플리케이션을 종료 할 수도 있고, 불만족한 경우 앱을 +제거할 수도 있습니다.

+ +

또한, Andoid UI 도구 키트는 스레드로부터 안전하지 않습니다. 따라서 UI를 +작업자 스레드에서 조작해서는 안 됩니다. 사용자 인터페이스 조작 작업은 모두 UI +스레드에서 해야만 합니다. 결론적으로, Android의 단일 스레드 모델에는 두 가지 단순한 규칙이 있습니다.

+ +
    +
  1. UI 스레드를 차단하지 마십시오. +
  2. Ui 스레드 외부에서 Android UI 도구 키트에 액세스하지 마십시오. +
+ +

작업자 스레드

+ +

위에 설명한 단일 스레드 모델로 인해, 애플리케이션 UI의 반응성을 위해서는 +UI 스레드를 차단하지 않는 것이 매우 중요합니다. 수행해야 할 작업이 있는데 +이들이 즉각적인 조치를 요하지 않는 경우, 이런 작업은 반드시 별도의 스레드에서 수행해야 합니다("배경" 또는 +"작업자" 스레드).

+ +

예를 들어, 아래는 별도의 스레드에서 이미지를 다운로드하고 이를 +{@link android.widget.ImageView}에 표시하는 클릭 수신기에 대한 몇 가지 코드입니다.

+ +
+public void onClick(View v) {
+    new Thread(new Runnable() {
+        public void run() {
+            Bitmap b = loadImageFromNetwork("http://example.com/image.png");
+            mImageView.setImageBitmap(b);
+        }
+    }).start();
+}
+
+ +

처음에는 네트워크 작업을 처리하기 위한 새 스레드를 생성하므로 +아무 문제 없이 작동하는 것처럼 보입니다. 하지만, 이것은 단일 스레드된 모델의 두 번째 규칙 즉, 'UI 스레드 외부에서 +Android UI 도구 키트에 액세스하지 마세요.'를 위반합니다. 이 샘플은 UI 스레드 대신 작업자 스레드에서 {@link +android.widget.ImageView}를 수정합니다. 이렇게 되면 +정의되지 않고 예기치 못한 동작이 발생하는 결과를 초래할 수 있고, 이는 추적하기 어려워 시간도 오래 걸립니다.

+ +

이 문제를 해결하기 위해 Android는 다른 스레드에서 UI 스레드에 액세스하는 여러 가지 방식을 +제안합니다. 다음은 몇 가지 유용한 메서드 목록입니다.

+ +
    +
  • {@link android.app.Activity#runOnUiThread(java.lang.Runnable) +Activity.runOnUiThread(Runnable)}
  • +
  • {@link android.view.View#post(java.lang.Runnable) View.post(Runnable)}
  • +
  • {@link android.view.View#postDelayed(java.lang.Runnable, long) View.postDelayed(Runnable, +long)}
  • +
+ +

예를 들어 위의 코드를 수정하려면 {@link +android.view.View#post(java.lang.Runnable) View.post(Runnable)} 메서드를 사용하면 됩니다.

+ +
+public void onClick(View v) {
+    new Thread(new Runnable() {
+        public void run() {
+            final Bitmap bitmap = loadImageFromNetwork("http://example.com/image.png");
+            mImageView.post(new Runnable() {
+                public void run() {
+                    mImageView.setImageBitmap(bitmap);
+                }
+            });
+        }
+    }).start();
+}
+
+ +

이 구현은 이제 스레드로부터 안전합니다. 네트워크 작업은 별도의 스레드에서 수행된 반면 +{@link android.widget.ImageView}는 UI 스레드에서 조작되었기 때문입니다.

+ +

그러나, 작업이 복잡해질수록 이런 종류의 코드가 더 복잡해질 수 있고 유지 관리하기 +까다로워질 수 있습니다. 더 복잡한 상호 작용을 작업자 스레드로 처리하려면, 작업자 스레드에서 +{@link android.os.Handler}를 사용하여 UI 스레드에서 전달 받은 메시지를 처리하는 방안을 +고려해보십시오. 하지만 최선의 해결책은 {@link android.os.AsyncTask} 클래스를 확장하는 방법일 것입니다. +이것은 UI와 상호 작용해야 하는 작업자 스레드 작업의 실행을 단순화합니다.

+ + +

AsyncTask 사용

+ +

{@link android.os.AsyncTask}를 사용하면 사용자 인터페이스에서 비동기식 작업을 수행할 수 +있게 해줍니다. 이것은 작업자 스레드에서 차단 작업을 수행하고 그런 다음 그 결과를 UI 스레드에 +게시하며, 개발자가 직접 스레드 및/또는 처리기를 처리할 필요가 없습니다.

+ +

이를 사용하려면 우선 {@link android.os.AsyncTask}를 하위 클래스로 한 다음 {@link +android.os.AsyncTask#doInBackground doInBackground()} 콜백 메서드를 구현해야 합니다. 이것은 여러 가지 +배경 스레드에서 실행됩니다. UI를 업데이트하려면 {@link +android.os.AsyncTask#onPostExecute onPostExecute()}를 구현해야 합니다. 이는 {@link +android.os.AsyncTask#doInBackground doInBackground()}로부터의 결과를 전달하며 UI 스레드에서 실행되므로, +안전하게 UI를 업데이트할 수 있습니다. 그런 다음 UI 스레드에서 {@link android.os.AsyncTask#execute execute()}를 +호출하여 해당 작업을 실행하면 됩니다.

+ +

예를 들어, 이런 방식으로 {@link android.os.AsyncTask}를 사용하여 +이전의 예시를 구현할 수 있습니다.

+ +
+public void onClick(View v) {
+    new DownloadImageTask().execute("http://example.com/image.png");
+}
+
+private class DownloadImageTask extends AsyncTask<String, Void, Bitmap> {
+    /** The system calls this to perform work in a worker thread and
+      * delivers it the parameters given to AsyncTask.execute() */
+    protected Bitmap doInBackground(String... urls) {
+        return loadImageFromNetwork(urls[0]);
+    }
+    
+    /** The system calls this to perform work in the UI thread and delivers
+      * the result from doInBackground() */
+    protected void onPostExecute(Bitmap result) {
+        mImageView.setImageBitmap(result);
+    }
+}
+
+ +

이제 UI는 안전하고 코드는 더욱 단순해졌습니다. 작업을 작업자 스레드에서 수행되어야 하는 +부분과 UI 스레드에서 수행되어야 하는 부분으로 구분하기 때문입니다.

+ +

이 클래스를 사용하는 법을 완전히 숙지하려면 {@link android.os.AsyncTask} 참조를 +읽어보시는 것이 좋습니다. 개괄적인 작동 방식은 아래에 간략히 소개해 놓았습니다.

+ +
    +
  • 매개 변수의 유형, 진행률 값과 작업의 최종 값을 제네릭을 사용하여 +지정할 수 있습니다.
  • +
  • 메서드 {@link android.os.AsyncTask#doInBackground doInBackground()}는 작업자 스레드에서 자동으로 +실행됩니다.
  • +
  • {@link android.os.AsyncTask#onPreExecute onPreExecute()}, {@link +android.os.AsyncTask#onPostExecute onPostExecute()} 및 {@link +android.os.AsyncTask#onProgressUpdate onProgressUpdate()}는 모두 UI 스레드에서 호출됩니다.
  • +
  • {@link android.os.AsyncTask#doInBackground doInBackground()}가 반환한 값이 +{@link android.os.AsyncTask#onPostExecute onPostExecute()}로 전송됩니다.
  • +
  • 언제든 {@link android.os.AsyncTask#publishProgress publishProgress()}를 {@link +android.os.AsyncTask#doInBackground doInBackground()}에서 호출하여 UI 스레드에서 {@link +android.os.AsyncTask#onProgressUpdate onProgressUpdate()}를 실행하도록 할 수 있습니다.
  • +
  • 모든 스레드에서 언제든 작업을 취소할 수 있습니다.
  • +
+ +

주의: 작업자 스레드를 사용할 때 마주칠 수 있는 또 한 가지 문제는 +런타임 구성 변경으로 인해 액티비티가 예기치 못하게 다시 시작되는 것입니다 +(예를 들어 사용자가 화면 방향을 바꾸는 경우). 이 경우 작업자 스레드를 소멸시킬 수 있습니다. +스레드가 재시작될 때 작업을 지속하는 방법과 액티비티가 제거되었을 때 작업을 적절히 취소하는 방법은 +Shelves 샘플 애플리케이션의 소스 코드를 참조하십시오.

+ + +

스레드로부터 안전한 메서드

+ +

어떤 경우에는 구현하는 메서드가 하나 이상의 스레드에서 호출되는 일도 있습니다. 따라서 +이를 스레드로부터 안전하게 작성해야만 합니다.

+ +

이것은 주로 원격으로 호출할 수 있는 메서드에 대해 참입니다. 예를 들어 바인딩된 서비스 내의 메서드 등이 해당됩니다. +{@link android.os.IBinder}에서 구현된 메서드가 +{@link android.os.IBinder IBinder}가 실행되는 프로세스에서 호출될 경우, 해당 메서드는 발신자의 스레드에서 실행됩니다. +그러나 호출이 다른 프로세스에서 발생하면, 해당 메서드는 시스템이 +{@link android.os.IBinder +IBinder}와 같은 프로세스에 유지하는 스레드 풀에서 선택된 스레드에서 실행됩니다(프로세스의 UI 스레드에서 실행되지 않습니다). 예를 들어, 어느 서비스의 +{@link android.app.Service#onBind onBind()} 메서드는 해당 서비스 +프로세스의 UI 스레드에서 호출되고, {@link android.app.Service#onBind +onBind()}가 반환하는 객체에서 구현된 메서드는(예: RPC 메서드를 구현하는 하위 클래스) 해당 풀 안의 여러 스레드에서 +호출되게 됩니다. 서비스에 클라이언트가 하나 이상 있을 수 있으므로, 하나 이상의 풀이 +동시에 같은 {@link android.os.IBinder IBinder} 메서드에 참여할 수 있습니다. 그러므로 {@link android.os.IBinder +IBinder} 메서드는 스레드로부터 안전하게 구현되어야 합니다.

+ +

마찬가지로 콘텐츠 제공자는 다른 프로세스에서 발생한 데이터 요청을 수신할 수 있습니다. +{@link android.content.ContentResolver}와 {@link android.content.ContentProvider} +클래스가 세부적인 프로세스 간 통신의 관리 방식을 숨길 수는 있지만, 이러한 요청에 응답하는 {@link +android.content.ContentProvider} 메서드(—{@link +android.content.ContentProvider#query query()}, {@link android.content.ContentProvider#insert +insert()}, {@link android.content.ContentProvider#delete delete()}, {@link +android.content.ContentProvider#update update()} 및 {@link android.content.ContentProvider#getType +getType()} 메서드—)가 프로세스의 UI 스레드가 아니라 +콘텐츠 제공자 프로세스의 스레드 풀에서 호출됩니다. 이러한 메서드가 동시에 몇 개의 스레드에서 호출될 수 있으므로, +스레드로부터 안전하게 구현되어야 합니다.

+ + +

프로세스 간 통신

+ +

Android는 원격 프로시저 호출(RPC)을 사용한 프로세스 간 통신(IPC) 메커니즘을 제공합니다. +여기서 메서드는 액티비티나 다른 애플리케이션 구성 요소에 호출되지만 +원격으로 (또 다른 프로세스에서) 실행되고, 결과는 모두 발신자에게 되돌려 +보냅니다. 메서드 호출과 메서드의 데이터는 운영 체제가 이해할 수 있는 수준으로 분해되고, +로컬 프로세스와 주소 공간에서 원격 프로세스와 주소 공간으로 전송된 다음 +다시 결합되어 여기서 호출에 다시 응답합니다. 그런 다음 반환 값이 +반대 방향으로 전송됩니다. Android가 이와 같은 IPC 트랜잭션을 수행하는 데 필요한 +모든 코드를 제공하므로, 개발자는 그저 RPC 프로그래밍 인터페이스를 정의하고 구현하는 데에만 집중하면 됩니다.

+ +

IPC를 수행하려면 애플리케이션이 반드시 서비스에 바인딩되어야만 하며, 이때 {@link +android.content.Context#bindService bindService()}를 사용해야 합니다. 자세한 정보는 서비스 개발자 가이드를 참조하십시오.

+ + + diff --git a/docs/html-intl/intl/ko/guide/components/recents.jd b/docs/html-intl/intl/ko/guide/components/recents.jd new file mode 100644 index 0000000000000000000000000000000000000000..cba3c45da3daeab4ce42883e75f559a2a3642254 --- /dev/null +++ b/docs/html-intl/intl/ko/guide/components/recents.jd @@ -0,0 +1,256 @@ +page.title=개요 화면 +page.tags="recents","overview" + +@jd:body + +
+
+ +

이 문서의 내용

+
    +
  1. 개요 화면에 작업 추가 +
      +
    1. 작업 추가에 인텐트 플래그 사용
    2. +
    3. 작업 추가에 액티비티 특성 사용
    4. +
    +
  2. +
  3. 작업 제거 +
      +
    1. 작업 제거에 AppTask 클래스 사용
    2. +
    3. 완료된 작업 보존
    4. +
    +
  4. +
+ +

Key 클래스

+
    +
  1. {@link android.app.ActivityManager.AppTask}
  2. +
  3. {@link android.content.Intent}
  4. +
+ +

샘플 코드

+
    +
  1. 문서 중심 앱
  2. +
+ +
+
+ +

개요 화면(다른 말로 최근 사용 화면, 최근 작업 목록 또는 최근 앱이라고도 함)은 +시스템 수준 UI로, 최근에 액세스한 +액티비티작업을 목록으로 나열한 것입니다. 사용자는 +목록을 가로질러 이동하며 작업을 선택해서 재개할 수도 있고, 아니면 +목록에서 한 작업을 스와이프하여 밀어내어 목록에서 제거할 수도 있습니다. Android 5.0 릴리스(API 레벨 21)의 경우, 같은 액티비티의 여러 인스턴스에 +각기 다른 문서가 담겨 있는 경우 이들이 개요 화면에 작업으로 나타날 수 있습니다. 예를 들어 +Google Drive에 여러 개의 Google 문서 각각에 대한 작업이 있을 수 있습니다. 각 문서는 개요 화면에 하나의 +작업으로 나타납니다.

+ + +

그림 1. 세 개의 Google Drive 문서가 각기 별도의 +작업을 나타내는 모습을 표시한 개요 화면입니다.

+ +

보통은 개요 화면에 작업과 액티비티가 어떻게 표현될지는 시스템이 +정의하도록 두어야 합니다. 이 행동을 개발자가 수정할 필요도 없습니다. +하지만, 개요 화면에 액티비티가 언제, 어떻게 나타날지는 앱이 결정할 수 있습니다. +{@link android.app.ActivityManager.AppTask} 클래스를 사용하면 작업을 관리할 수 있게 해주고, +{@link android.content.Intent} 클래스의 액티비티 플래그를 사용하면 개요 화면에서 액티비티가 추가되거나 제거되는 시점을 +지정할 수 있습니다. 또한, +<activity> 특성을 사용하면 매니페스트에서의 동작을 설정할 수 있습니다.

+ +

개요 화면에 작업 추가

+ +

{@link android.content.Intent} 클래스의 플래그를 사용하여 작업을 추가하면 +개요 화면에서 문서가 열리거나 다시 열리는 시점과 방법을 보다 철저히 통제할 수 있습니다. +<activity> + 특성을 사용하는 경우, 문서를 항상 새 작업에서 여는 방법과 기존 작업을 해당 문서에 다시 사용하는 방법 중에서 +선택할 수 있습니다.

+ +

작업 추가에 인텐트 플래그 사용

+ +

액티비티를 위해 새 문서를 생성하는 경우, +{@link android.app.ActivityManager.AppTask} 클래스의 +{@link android.app.ActivityManager.AppTask#startActivity(android.content.Context, android.content.Intent, android.os.Bundle) startActivity()} + 메서드를 호출하고, 이것을 액티비티를 시작하는 인텐트에 전달하면 됩니다. 논리적인 중단을 삽입하여 시스템이 개요 화면에서 액티비티를 +새 작업으로 처리하도록 하려면, {@link android.content.Intent}의 +{@link android.content.Intent#addFlags(int) addFlags()} 메서드에 있는 {@link android.content.Intent#FLAG_ACTIVITY_NEW_DOCUMENT} +플래그를 전달하여 액티비티를 시작하도록 합니다.

+ +

참고: {@link android.content.Intent#FLAG_ACTIVITY_NEW_DOCUMENT} +플래그가 {@link android.content.Intent#FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET} 플래그를 +대체합니다. 이것은 Android 5.0(API 레벨 21)부터 사용이 중단되었습니다.

+ +

새 문서를 생성하면서 {@link android.content.Intent#FLAG_ACTIVITY_MULTIPLE_TASK} 플래그를 설정하는 경우, +시스템은 항상 대상 액티비티를 루트로 하여 새 작업을 만듭니다. +이렇게 설정하면 같은 문서를 하나 이상의 작업에서 열 수 있습니다. 다음 코드는 기본 액티비티가 이 작업을 수행하는 방법을 +보여줍니다.

+ +

+DocumentCentricActivity.java

+
+public void createNewDocument(View view) {
+      final Intent newDocumentIntent = newDocumentIntent();
+      if (useMultipleTasks) {
+          newDocumentIntent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
+      }
+      startActivity(newDocumentIntent);
+  }
+
+  private Intent newDocumentIntent() {
+      boolean useMultipleTasks = mCheckbox.isChecked();
+      final Intent newDocumentIntent = new Intent(this, NewDocumentActivity.class);
+      newDocumentIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
+      newDocumentIntent.putExtra(KEY_EXTRA_NEW_DOCUMENT_COUNTER, incrementAndGet());
+      return newDocumentIntent;
+  }
+
+  private static int incrementAndGet() {
+      Log.d(TAG, "incrementAndGet(): " + mDocumentCounter);
+      return mDocumentCounter++;
+  }
+}
+
+ +

참고: {@code FLAG_ACTIVITY_NEW_DOCUMENT} + 플래그로 시작된 액티비티의 경우, 반드시 매니페스트에 {@code android:launchMode="standard"} 특성 값(기본)이 설정되어 +있어야 합니다.

+ +

기본 액티비티가 새 액티비티를 시작하면 시스템은 기존의 작업을 검색하여 그 중 +해당 액티비티에 대한 인텐트 구성 요소 이름과 인텐트 데이터와 일치하는 인텐트를 가진 작업을 찾습니다. 그러한 작업을 +찾을 수 없거나 {@link android.content.Intent#FLAG_ACTIVITY_MULTIPLE_TASK} + 플래그에 들어 있는 인텐트를 찾을 수 없는 경우, 해당 액티비티를 루트로 하여 새 작업이 생성됩니다. 원하는 항목을 찾으면, 시스템은 해당 작업을 전경으로 가지고 와 +새 인텐트를 {@link android.app.Activity#onNewIntent onNewIntent()}에 전달합니다. +새 액티비티가 인텐트를 받아 개요 화면에서 새 문서를 생성하며, 이는 다음 예시에 나타낸 +것과 같습니다.

+ +

+NewDocumentActivity.java

+
+@Override
+protected void onCreate(Bundle savedInstanceState) {
+    super.onCreate(savedInstanceState);
+    setContentView(R.layout.activity_new_document);
+    mDocumentCount = getIntent()
+            .getIntExtra(DocumentCentricActivity.KEY_EXTRA_NEW_DOCUMENT_COUNTER, 0);
+    mDocumentCounterTextView = (TextView) findViewById(
+            R.id.hello_new_document_text_view);
+    setDocumentCounterText(R.string.hello_new_document_counter);
+}
+
+@Override
+protected void onNewIntent(Intent intent) {
+    super.onNewIntent(intent);
+    /* If FLAG_ACTIVITY_MULTIPLE_TASK has not been used, this activity
+    is reused to create a new document.
+     */
+    setDocumentCounterText(R.string.reusing_document_counter);
+}
+
+ + +

작업 추가에 액티비티 특성 사용

+ +

액티비티는 자신의 매니페스트에 새 작업을 시작할 때는 항상 +<activity> + 특성, +{@code android:documentLaunchMode}를 사용한다고 나타낼 수도 있습니다. 이 특성에는 네 가지 값이 있어 사용자가 애플리케이션으로 문서를 열면 +다음과 같은 효과를 발생시킵니다.

+ +
+
"{@code intoExisting}"
+
액티비티가 문서에 대해 기존의 작업을 재사용합니다. 이것은 +{@link android.content.Intent#FLAG_ACTIVITY_NEW_DOCUMENT} 플래그를 설정할 때 +{@link android.content.Intent#FLAG_ACTIVITY_MULTIPLE_TASK} 플래그 없이 설정하는 것과 같습니다. +이는 위의 작업 추가에 인텐트 플래그 사용에서 설명한 것과 같습니다.
+ +
"{@code always}"
+
액티비티가 문서에 대해 새 작업을 생성하며, 이는 문서가 이미 열려 있는 경우라도 마찬가지입니다. 이 값을 +사용하는 것은 {@link android.content.Intent#FLAG_ACTIVITY_NEW_DOCUMENT} + 및 {@link android.content.Intent#FLAG_ACTIVITY_MULTIPLE_TASK} 플래그를 둘 다 설정하는 것과 같습니다.
+ +
"{@code none”}"
+
액티비티가 문서에 대해 새 작업을 생성하지 않습니다. 개요 화면은 액티비티를 기본 상태에서와 +같이 다룹니다. 즉 앱에 대한 하나의 작업을 표시하며, 이때 사용자가 +마지막으로 호출한 작업이 무엇이든 관계 없이 그 작업에서부터 재개합니다.
+ +
"{@code never}"
+
액티비티가 문서에 대해 새 작업을 생성하지 않습니다. 이 값을 설정하면 +{@link android.content.Intent#FLAG_ACTIVITY_NEW_DOCUMENT} + 및 {@link android.content.Intent#FLAG_ACTIVITY_MULTIPLE_TASK} 플래그의 행동을 +재정의합니다(이들 중 하나가 인텐트에서 설정되어 있는 경우). 개요 화면은 앱에 대한 하나의 작업을 표시하고, +이것이 사용자가 마지막으로 호출한 액티비티가 무엇이든 그것부터 재개합니다.
+
+ +

참고: {@code none} 및 {@code never}를 제외한 다른 값의 경우, +액티비티를 {@code launchMode="standard"}로 정의해야 합니다. 이 특성을 지정하지 않으면 +{@code documentLaunchMode="none"}이 사용됩니다.

+ +

작업 제거

+ +

기본적으로 문서 작업은 해당되는 액티비티가 완료되면 자동으로 개요 화면에서 +제거됩니다. 이 행동을 재정의하려면 {@link android.app.ActivityManager.AppTask} 클래스를 사용합니다. 이때 +{@link android.content.Intent} 플래그 또는 +<activity> 특성을 함께 사용하십시오.

+ +

개요 화면에서 작업을 완전히 제외하려면 언제든 +<activity> +특성, +{@code android:excludeFromRecents}를 {@code true}로 설정합니다.

+ +

개요 화면에서 앱이 포함할 수 있는 작업의 최대 개수를 설정하려면 +<activity> + 특성 {@code android:maxRecents} +를 정수 값으로 설정합니다. 기본은 16개입니다. 작업의 최대 대수에 도달하면 가장 예전에 사용된 작업이 개요 화면에서 +제거됩니다. {@code android:maxRecents} 최대값은 +50입니다(메모리 용량이 적은 기기에서는 25). 1 미만의 값은 유효하지 않습니다.

+ +

작업 제거에 AppTask 클래스 사용

+ +

개요 화면에서 새 작업을 생성하는 액티비티에서는 작업을 언제 제거할 것인지와 +그와 관련된 모든 액티비티를 언제 완료할 것인지 지정할 수 있습니다. +{@link android.app.ActivityManager.AppTask#finishAndRemoveTask() finishAndRemoveTask()} 메서드를 호출하면 됩니다.

+ +

+NewDocumentActivity.java

+
+public void onRemoveFromRecents(View view) {
+    // The document is no longer needed; remove its task.
+    finishAndRemoveTask();
+}
+
+ +

참고: +{@link android.app.ActivityManager.AppTask#finishAndRemoveTask() finishAndRemoveTask()} 메서드를 +사용하면 {@link android.content.Intent#FLAG_ACTIVITY_RETAIN_IN_RECENTS} 태그 사용을 재정의합니다. +이 내용은 아래에서 설명합니다.

+ +

완료된 작업 보존

+ +

작업을 개요 화면에 보존하려면(액티비티가 완료되었더라도), +액티비티를 시작하는 인텐트의 {@link android.content.Intent#addFlags(int) addFlags()} 메서드에 있는 +{@link android.content.Intent#FLAG_ACTIVITY_RETAIN_IN_RECENTS} 플래그를 전달합니다.

+ +

+DocumentCentricActivity.java

+
+private Intent newDocumentIntent() {
+    final Intent newDocumentIntent = new Intent(this, NewDocumentActivity.class);
+    newDocumentIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT |
+      android.content.Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS);
+    newDocumentIntent.putExtra(KEY_EXTRA_NEW_DOCUMENT_COUNTER, incrementAndGet());
+    return newDocumentIntent;
+}
+
+ +

같은 효과를 얻기 위해 +<activity> + 특성 +{@code android:autoRemoveFromRecents}를 {@code false}로 설정해도 됩니다. 기본 값은 문서 액티비티의 경우 {@code true} +이고, 일반 액티비티의 경우 {@code false}입니다. 이 특성을 사용하면 이전에 논한 것과 같이 +{@link android.content.Intent#FLAG_ACTIVITY_RETAIN_IN_RECENTS} 플래그를 재정의하게 됩니다.

+ + + + + + + diff --git a/docs/html-intl/intl/ko/guide/components/services.jd b/docs/html-intl/intl/ko/guide/components/services.jd new file mode 100644 index 0000000000000000000000000000000000000000..fb95ea5ed0fa3a2ed3f3fca333a0fd8255702a19 --- /dev/null +++ b/docs/html-intl/intl/ko/guide/components/services.jd @@ -0,0 +1,813 @@ +page.title=서비스 +@jd:body + + + + +

{@link android.app.Service}는 배경에서 오래 실행되는 작업을 +수행할 수 있는 애플리케이션 구성 요소이며 사용자 인터페이스를 제공하지 않습니다. 또 다른 +애플리케이션 구성 요소가 서비스를 시작할 수 있으며, 이는 사용자가 또 다른 +애플리케이션으로 전환하더라도 배경에서 계속해서 실행됩니다. 이외에도, 구성 요소를 서비스에 바인딩하여 +서비스와 상호 작용할 수 있으며, 심지어는 프로세스 간 통신(IPC)도 수행할 수 있습니다. 예를 들어 한 서비스는 +네트워크 트랜잭션을 처리하고, 음악을 재생하고 파일 I/O를 수행하거나 콘텐츠 제공자와 상호 작용할 수 있으며 +이 모든 것을 배경에서 수행할 수 있습니다.

+ +

서비스는 본질적으로 두 가지 형식을 취합니다.

+ +
+
시작됨
+
서비스가 "시작된" 상태가 되려면 애플리케이션 구성 요소(예: 액티비티)가 +{@link android.content.Context#startService startService()}를 호출하여 시작하면 됩니다. 서비스는 한 번 시작되고 나면 +배경에서 무기한으로 실행될 수 있으며, 이는 해당 서비스를 시작한 구성 요소가 소멸되었더라도 무관합니다. 보통, +시작된 서비스는 한 작업을 수행하고 결과를 발신자에게 반환하지 않습니다. +예를 들어 네트워크에서 파일을 다운로드하거나 업로드할 수 있습니다. 작업을 완료하면, 해당 서비스는 +알아서 중단되는 것이 정상입니다.
+
바인딩됨
+
서비스가 "바인딩된" 상태가 되려면 애플리케이션 구성 요소가 {@link +android.content.Context#bindService bindService()}를 사용하여 해당 서비스에 바인딩되면 됩니다. 바인딩된 서비스는 클라이언트-서버 +인터페이스를 제공하여 구성 요소가 서비스와 상호 작용할 수 있도록 해주며, 결과를 가져올 수도 있고 심지어 +이와 같은 작업을 여러 프로세스에 걸쳐 프로세스 간 통신(IPC)으로 수행할 수도 있습니다. 바인딩된 서비스는 또 다른 애플리케이션 구성 요소가 +이에 바인딩되어 있는 경우에만 실행됩니다. 여러 개의 구성 요소가 서비스에 한꺼번에 바인딩될 수 있지만, +이 모든 것이 바인딩을 해제하면 해당 서비스는 소멸됩니다.
+
+ +

이 문서는 주로 이러한 두 가지 유형의 서비스를 따로따로 논하지만, 서비스는 +두 가지 방식 모두로 작동할 수 있습니다. 즉 서비스가 시작될 수도 있고(나아가 무기한으로 실행되고) 바인딩도 허용할 수 있다는 뜻입니다. +이는 그저 두어 가지 콜백 메서드의 구현 여부에 달린 문제입니다. {@link +android.app.Service#onStartCommand onStartCommand()}를 사용하면 구성 요소가 서비스를 시작할 수 있게 허용하고, {@link +android.app.Service#onBind onBind()}를 사용하면 바인딩을 허용합니다.

+ +

애플리케이션이 시작되었든, 바인딩되었든 아니면 양쪽 모두이든 모든 애플리케이션 구성 요소가 +해당 서비스를 사용할 수 있으며(별도의 애플리케이션에서라도), 이는 어느 구성 요소든 액티비티를 +사용할 수 있는 것과 같습니다. 이를 {@link android.content.Intent}로 시작하면 됩니다. 그러나, +매니페스트에서 서비스를 비공개로 선언하고 다른 애플리케이션으로부터의 액세스를 차단할 수도 있습니다. 이것은 +매니페스트에서 서비스 +선언하기에 관한 섹션에서 더 자세히 이야기합니다.

+ +

주의: 서비스는 자신의 호스팅 프로세스의 +기본 스레드에서 실행됩니다. 서비스는 자신의 스레드를 직접 생성하지 않으며, +별도의 프로세스에서 실행되지도 않습니다(별도로 지정하는 경우는 예외). 이것은 즉, +서비스가 CPU 집약적인 작업을 수행할 예정이거나 차단적인 작업을 수행할 예정인 경우(예를 들어 MP3 +재생 또는 네트워킹 등), 서비스 내에 새 스레드를 생성하여 해당 작업을 수행하도록 해야 한다는 뜻입니다. 별도의 스레드를 사용하면 +'애플리케이션이 응답하지 않습니다(ANR)' 오류가 일어날 위험을 줄일 수 있으며 +애플리케이션의 기본 스레드는 액티비티와 사용자 상호 작용 전용으로 유지될 수 있습니다.

+ + +

기본 정보

+ + + +

서비스를 생성하려면 {@link android.app.Service}의 하위 클래스를 생성해야 합니다(아니면 이의 +기존 하위 클래스 중 하나). 구현에서는 서비스 수명 주기의 주요 측면을 처리하는 콜백 메서드를 +몇 가지 재정의해야 하며 서비스에 바인딩할 구성 요소에 대한 메커니즘을 +제공해야 합니다(해당되는 경우). 재정의해야 하는 가장 중요한 콜백 메서드는 다음과 같습니다.

+ +
+
{@link android.app.Service#onStartCommand onStartCommand()}
+
시스템이 이 메서드를 호출하는 것은 또 다른 구성 요소(예: 액티비티)가 서비스를 +시작하도록 요청하는 경우입니다. 이때 {@link android.content.Context#startService +startService()}를 호출하는 방법을 씁니다. 이 메서드가 실행되면 서비스가 시작되고 배경에서 무기한으로 실행될 수 +있습니다. 이것을 구성하면 서비스의 작업이 완료되었을 때 해당 서비스를 중단하는 것은 +개발자 본인의 책임입니다. 이때 {@link android.app.Service#stopSelf stopSelf()} 또는 {@link +android.content.Context#stopService stopService()}를 호출하면 됩니다 (바인딩만 제공하고자 하는 경우, 이 메서드를 구현하지 +않아도 됩니다).
+
{@link android.app.Service#onBind onBind()}
+
시스템이 이 메서드를 호출하는 것은 또 다른 구성 요소가 해당 서비스에 바인딩되고자 하는 경우 +(예를 들어 RPC를 수행하기 위해)입니다. 이때 {@link android.content.Context#bindService +bindService()}를 호출하는 방법을 씁니다. 이 메서드를 구현할 때에는 클라이언트가 서비스와 통신을 주고받기 위해 사용할 +인터페이스를 제공해야 합니다. 이때 {@link android.os.IBinder}를 반환하면 됩니다. 이 메서드는 항상 +구현해야 하지만, 바인딩을 허용하지 않고자 하면 null을 반환해야 합니다.
+
{@link android.app.Service#onCreate()}
+
시스템이 이 메서드를 호출하는 것은 서비스가 처음 생성되어 일회성 설정 +절차를 수행하는 경우입니다({@link android.app.Service#onStartCommand onStartCommand()} 또는 +{@link android.app.Service#onBind onBind()}를 호출하기 전에). 서비스가 이미 실행 중인 경우, 이 메서드는 호출되지 +않습니다.
+
{@link android.app.Service#onDestroy()}
+
시스템이 이 메서드를 호출하는 것은 해당 서비스를 더 이상 사용하지 않고 소멸시키는 경우입니다. +서비스에 이것을 구현해야 스레드, 등록된 각종 수신기(listener, receiver) 등 +모든 리소스를 정리할 수 있습니다. 이것이 서비스가 수신하는 마지막 호출입니다.
+
+ +

한 구성 요소가 {@link +android.content.Context#startService startService()}를 호출하여 서비스를 시작하면({@link +android.app.Service#onStartCommand onStartCommand()}로의 호출을 초래함), 해당 서비스는 +알아서 {@link android.app.Service#stopSelf()}로 스스로를 중단할 때까지 또는 +또 다른 구성 요소가 {@link android.content.Context#stopService stopService()}를 호출하여 서비스를 중단시킬 때까지 실행 중인 상태로 유지됩니다.

+ +

한 구성 요소가 +{@link android.content.Context#bindService bindService()}를 호출하여 서비스를 생성하는 경우(그리고 {@link +android.app.Service#onStartCommand onStartCommand()}를 호출하지 않은 경우), 해당 서비스는 +해당 구성 요소가 바인딩되어 있는 경우에만 실행됩니다. 서비스가 모든 클라이언트로부터 바인딩 해제되면 시스템이 이를 +소멸시킵니다.

+ +

Android 시스템이 서비스를 강제 중단시키는 것은 메모리가 부족하여 사용자가 초점을 집중하고 있는 +액티비티를 위해 시스템 리소스를 회복해야만 하는 경우로만 국한됩니다. 해당 서비스가 사용자의 주목을 +끌고 있는 액티비티에 바인딩되어 있다면 중단될 가능성이 낮고, 서비스가 전경에서 실행된다고 선언된 경우(나중에 자세히 논함), 거의 절대 중단되지 않습니다. +그렇지 않으면, 서비스가 시작되었고 오랫동안 실행되는 경우 +시간이 지나면서 시스템이 배경 작업 목록에서의 이 서비스의 위치를 점점 낮추고 +서비스는 중단되기 매우 쉬워집니다. 서비스가 시작되었다면 이를 시스템에 의한 재시작을 정상적으로 +처리하도록 디자인해야 합니다. 시스템이 서비스를 중단시키는 경우, 리소스를 다시 사용할 수 있게 되면 +시스템이 가능한 한 빨리 이를 다시 시작합니다(다만 이것은 개발자가 {@link +android.app.Service#onStartCommand onStartCommand()}에서 반환하는 값에도 좌우됩니다. 이 내용은 나중에 논합니다). 시스템이 서비스를 +소멸시킬 수 있는 경우에 대한 자세한 정보는 프로세스 및 스레딩 +문서를 참조하십시오.

+ +

다음 섹션에서는 각 유형의 서비스를 생성하는 방법과 다른 애플리케이션 구성 요소에서 +이를 사용하는 방법을 배우게 됩니다.

+ + + +

매니페스트에서 서비스 선언하기

+ +

액티비티(및 다른 구성 요소)와 마찬가지로, 서비스는 모두 애플리케이션의 매니페스트 +파일에서 선언해야 합니다.

+ +

서비스를 선언하려면 {@code <service>} 요소를 +{@code <application>} + 요소의 하위로 추가하면 됩니다. 예:

+ +
+<manifest ... >
+  ...
+  <application ... >
+      <service android:name=".ExampleService" />
+      ...
+  </application>
+</manifest>
+
+ +

매니페스트에서 서비스를 선언하는 데 대한 자세한 정보는 {@code <service>} +요소 참조를 확인하십시오.

+ +

{@code <service>} 요소에 포함시킬 수 있는 다른 속성도 있습니다. +이를 포함시켜 서비스를 시작하는 데 필요한 권한과 서비스가 실행되어야 하는 프로세스 등의 +속성을 정의할 수 있습니다. {@code android:name} +속성이 유일한 필수 속성입니다. 이것은 서비스의 클래스 이름을 나타냅니다. 일단 애플리케이션을 +게시하고 나면 이 이름을 변경해서는 안 됩니다. 이름을 변경하면 +서비스를 시작하거나 바인딩할 명시적 인텐트에 대한 종속성으로 인해 코드를 단절시킬 위험이 있기 때문입니다(블로그 게시물의 +바꿀 수 없는 항목을 참조하십시오). + +

앱의 보안을 보장하려면 +{@link android.app.Service}을 시작하거나 바인딩할 때 항상 명시적 인텐트를 사용하고 서비스에 대한 인텐트 필터는 선언하지 마십시오. 어느 +서비스를 시작할지 어느 정도 모호성을 허용하는 것이 중요한 경우, 서비스에 대해 +인텐트 필터를 제공하고 구성 요소 이름을 {@link +android.content.Intent}에서 배제할 수 있지만, 그러면 해당 인텐트에 대한 패키지를 {@link +android.content.Intent#setPackage setPackage()}로 설정하여 대상 서비스에 대해 충분한 명확화를 +제공하도록 해야 합니다.

+ +

이외에도 서비스를 본인의 앱에만 사용 가능하도록 보장할 수도 있습니다. +{@code android:exported} + 속성을 포함시킨 뒤 이를 {@code "false"}로 설정하면 됩니다. 이렇게 하면 다른 앱이 여러분의 서비스를 시작하지 못하도록 효과적으로 방지해주며, +이는 명시적 인텐트를 사용하는 경우에도 문제 없이 적용됩니다.

+ + + + +

시작된 서비스 생성하기

+ +

시작된 서비스란 다른 구성 요소가 {@link +android.content.Context#startService startService()}를 호출하여 시작하고, 그 결과 서비스의 +{@link android.app.Service#onStartCommand onStartCommand()} 메서드를 호출하는 결과를 초래한 것을 말합니다.

+ +

서비스가 시작되면 이를 시작한 구성 요소와 독립된 자신만의 +수명 주기를 가지며 해당 서비스는 배경에서 무기한으로 실행될 수 있습니다. 이는 해당 서비스를 +시작한 구성 요소가 소멸되었더라도 무관합니다. 따라서, 서비스는 작업이 완료되면 +{@link android.app.Service#stopSelf stopSelf()}를 호출하여 스스로 알아서 중단되는 것이 정상이며 아니면 다른 구성 요소가 +{@link android.content.Context#stopService stopService()}를 호출하여 중단시킬 수도 있습니다.

+ +

애플리케이션 구성 요소(예: 액티비티)가 서비스를 시작하려면 {@link +android.content.Context#startService startService()}를 호출하고, {@link android.content.Intent}를 +전달하면 됩니다. 이것은 서비스를 나타내고 서비스가 사용할 모든 데이터를 포함합니다. 서비스는 이 +{@link android.content.Intent}를 {@link android.app.Service#onStartCommand +onStartCommand()} 메서드에서 수신합니다.

+ +

예를 들어 어느 액티비티가 온라인 데이터베이스에 데이터를 약간 저장해야 한다고 가정합니다. 액티비티가 +동반자 서비스를 시작하여 저장할 데이터를 이에 전달할 수 있습니다. 이때 인텐트를 {@link +android.content.Context#startService startService()}에 전달하면 됩니다. 서비스는 이 인텐트를 {@link +android.app.Service#onStartCommand onStartCommand()}에서 수신하고 인터넷에 연결한 다음 데이터베이스 +트랜잭션을 수행합니다. 작업을 완료하면, 해당 서비스는 알아서 스스로 중단되고 +소멸됩니다.

+ +

주의: 서비스는 기본적으로 자신이 선언된 애플리케이션의 같은 +프로세스에서 실행되기도 하고 해당 애플리케이션의 기본 스레드에서 실행되기도 합니다. 따라서, 사용자가 +같은 애플리케이션의 액티비티와 상호 작용하는 동안 서비스가 집약적이거나 차단적인 작업을 수행하는 경우, +해당 서비스 때문에 액티비티 성능이 느려지게 됩니다. 애플리케이션 성능에 영향을 미치는 것을 방지하려면, +서비스 내에서 새 스레드를 시작해야 합니다.

+ +

기존에는 시작된 서비스를 생성하기 위해 확장할 수 있는 클래스가 두 개 있었습니다.

+
+
{@link android.app.Service}
+
이것이 모든 서비스의 기본 클래스입니다. 이 클래스를 확장하는 경우, 서비스의 모든 작업을 수행할 +새 스레드를 만드는 것이 중요합니다. 서비스가 기본적으로 애플리케이션의 기본 스레드를 사용하기 +때문인데, 이로 인해 애플리케이션이 실행 중인 모든 액티비티의 성능이 +느려질 수 있기 때문입니다.
+
{@link android.app.IntentService}
+
이것은 {@link android.app.Service}의 하위 클래스로, 작업자 스레드를 +사용하여 모든 시작 요청을 처리하되 한 번에 하나씩 처리합니다. 서비스가 여러 개의 요청을 +동시에 처리하지 않아도 되는 경우 이것이 최선의 옵션입니다. 해야 할 일은 {@link +android.app.IntentService#onHandleIntent onHandleIntent()}를 구현하는 것뿐으로, 이것이 각 시작 요청에 대한 인텐트를 수신하여 +개발자는 배경 작업을 수행할 수 있습니다.
+
+ +

다음 섹션에서는 이와 같은 클래스 중 하나를 사용하여 서비스를 구현하는 방법을 +설명합니다.

+ + +

IntentService 클래스 확장하기

+ +

대부분의 시작된 서비스는 여러 개의 요청을 동시에 처리하지 않아도 되기 때문에 +(이는 사실 위험한 다중 스레딩 시나리오일 수 있습니다), 서비스를 구현할 때에는 +{@link android.app.IntentService} 클래스를 사용하는 것이 최선의 방안일 것입니다.

+ +

{@link android.app.IntentService}는 다음과 같은 작업을 수행합니다.

+ +
    +
  • 애플리케이션의 기본 스레드와는 별도로 {@link +android.app.Service#onStartCommand onStartCommand()}에 전달된 모든 인텐트를 실행하는 기본 작업자 스레드를 +생성합니다.
  • +
  • 한 번에 인텐트를 하나씩 {@link +android.app.IntentService#onHandleIntent onHandleIntent()} 구현에 전달하는 작업 대기열을 생성하므로 다중 스레딩에 대해 염려할 필요가 +전혀 없습니다.
  • +
  • 시작 요청이 모두 처리된 후 서비스를 중단하므로 개발자가 +{@link android.app.Service#stopSelf}를 호출할 필요가 전혀 없습니다.
  • +
  • {@link android.app.IntentService#onBind onBind()}의 기본 구현을 제공하여 null을 +반환하도록 합니다.
  • +
  • {@link android.app.IntentService#onStartCommand +onStartCommand()}의 기본 구현을 제공하여 인텐트를 작업 대기열에 보내고, 다음으로 {@link +android.app.IntentService#onHandleIntent onHandleIntent()} 구현에 보내도록 합니다.
  • +
+ +

이 모든 것은 결론적으로 개발자가 직접 할 일은 클라이언트가 제공한 작업을 수행할 {@link +android.app.IntentService#onHandleIntent onHandleIntent()}를 구현하는 것뿐이라는 사실로 +이어집니다. (다만, 서비스에 대해 작은 생성자를 제공해야 하기도 합니다.)

+ +

다음은 {@link android.app.IntentService}의 구현을 예시로 나타낸 것입니다.

+ +
+public class HelloIntentService extends IntentService {
+
+  /**
+   * A constructor is required, and must call the super {@link android.app.IntentService#IntentService}
+   * constructor with a name for the worker thread.
+   */
+  public HelloIntentService() {
+      super("HelloIntentService");
+  }
+
+  /**
+   * The IntentService calls this method from the default worker thread with
+   * the intent that started the service. When this method returns, IntentService
+   * stops the service, as appropriate.
+   */
+  @Override
+  protected void onHandleIntent(Intent intent) {
+      // Normally we would do some work here, like download a file.
+      // For our sample, we just sleep for 5 seconds.
+      long endTime = System.currentTimeMillis() + 5*1000;
+      while (System.currentTimeMillis() < endTime) {
+          synchronized (this) {
+              try {
+                  wait(endTime - System.currentTimeMillis());
+              } catch (Exception e) {
+              }
+          }
+      }
+  }
+}
+
+ +

필요한 것은 이게 전부입니다. 생성자 하나와 {@link +android.app.IntentService#onHandleIntent onHandleIntent()} 구현뿐이죠.

+ +

다른 콜백 메서드도 재정의하기로 결정하는 경우-예를 들어 {@link +android.app.IntentService#onCreate onCreate()}, {@link +android.app.IntentService#onStartCommand onStartCommand()} 또는 {@link +android.app.IntentService#onDestroy onDestroy()}-슈퍼 구현을 꼭 호출해야 합니다. +그래야 {@link android.app.IntentService}가 작업자 스레드의 수명을 적절하게 처리할 수 있습니다.

+ +

예를 들어 {@link android.app.IntentService#onStartCommand onStartCommand()}는 반드시 +기본 구현을 반환해야 합니다(이로써 인텐트가 {@link +android.app.IntentService#onHandleIntent onHandleIntent()}로 전달되는 것입니다).

+ +
+@Override
+public int onStartCommand(Intent intent, int flags, int startId) {
+    Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show();
+    return super.onStartCommand(intent,flags,startId);
+}
+
+ +

{@link android.app.IntentService#onHandleIntent onHandleIntent()} 외에 슈퍼 클래스를 +호출하지 않아도 되는 유일한 메서드는 {@link android.app.IntentService#onBind +onBind()}입니다(다만 이를 구현하는 것은 서비스가 바인딩을 허용할 때에만 필요합니다).

+ +

다음 섹션에서는 기본 {@link android.app.Service} +클래스를 확장할 때 같은 종류의 서비스를 구현하는 방법을 배우게 됩니다. 이때에는 코드가 훨씬 많이 필요하지만, +동시 시작 요청을 처리해야 하는 경우 이것이 적절할 수 있습니다.

+ + +

서비스 클래스 확장하기

+ +

이전 섹션에서 본 것과 같이 {@link android.app.IntentService}를 사용하면 +시작된 서비스 구현이 매우 단순해집니다. 하지만 서비스가 다중 스레딩을 +수행해야 하는 경우(작업 대기열을 통해 시작 요청을 처리하는 대신), 그때는 +{@link android.app.Service} 클래스를 확장하여 각 인텐트를 처리하게 할 수 있습니다.

+ +

비교를 위해 다음 예시의 코드를 보겠습니다. 이는 {@link +android.app.Service} 클래스의 구현으로, 위의 예시에서 {@link +android.app.IntentService}를 사용하여 수행한 것과 똑같은 작업을 수행합니다. 바꿔 말하면 각 시작 요청에 대해 +작업자 스레드를 사용하여 작업을 수행하고 한 번에 요청을 하나씩만 처리한다는 뜻입니다.

+ +
+public class HelloService extends Service {
+  private Looper mServiceLooper;
+  private ServiceHandler mServiceHandler;
+
+  // Handler that receives messages from the thread
+  private final class ServiceHandler extends Handler {
+      public ServiceHandler(Looper looper) {
+          super(looper);
+      }
+      @Override
+      public void handleMessage(Message msg) {
+          // Normally we would do some work here, like download a file.
+          // For our sample, we just sleep for 5 seconds.
+          long endTime = System.currentTimeMillis() + 5*1000;
+          while (System.currentTimeMillis() < endTime) {
+              synchronized (this) {
+                  try {
+                      wait(endTime - System.currentTimeMillis());
+                  } catch (Exception e) {
+                  }
+              }
+          }
+          // Stop the service using the startId, so that we don't stop
+          // the service in the middle of handling another job
+          stopSelf(msg.arg1);
+      }
+  }
+
+  @Override
+  public void onCreate() {
+    // Start up the thread running the service.  Note that we create a
+    // separate thread because the service normally runs in the process's
+    // main thread, which we don't want to block.  We also make it
+    // background priority so CPU-intensive work will not disrupt our UI.
+    HandlerThread thread = new HandlerThread("ServiceStartArguments",
+            Process.THREAD_PRIORITY_BACKGROUND);
+    thread.start();
+
+    // Get the HandlerThread's Looper and use it for our Handler
+    mServiceLooper = thread.getLooper();
+    mServiceHandler = new ServiceHandler(mServiceLooper);
+  }
+
+  @Override
+  public int onStartCommand(Intent intent, int flags, int startId) {
+      Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show();
+
+      // For each start request, send a message to start a job and deliver the
+      // start ID so we know which request we're stopping when we finish the job
+      Message msg = mServiceHandler.obtainMessage();
+      msg.arg1 = startId;
+      mServiceHandler.sendMessage(msg);
+
+      // If we get killed, after returning from here, restart
+      return START_STICKY;
+  }
+
+  @Override
+  public IBinder onBind(Intent intent) {
+      // We don't provide binding, so return null
+      return null;
+  }
+
+  @Override
+  public void onDestroy() {
+    Toast.makeText(this, "service done", Toast.LENGTH_SHORT).show();
+  }
+}
+
+ +

보시다시피 {@link android.app.IntentService}를 사용할 때보다 훨씬 손이 많이 갑니다.

+ +

그러나, 각 호출을 {@link android.app.Service#onStartCommand +onStartCommand()}로 직접 처리할 수 있기 때문에 여러 개의 요청을 동시에 수행할 수 있습니다. 이 예시는 그것을 +보여주는 것은 아니지만, 그런 작업을 원하는 경우 각 요청에 대해 새 스레드를 +하나씩 생성한 다음 곧바로 실행하면 됩니다(이전 요청이 끝날 때까지 기다리는 대신).

+ +

{@link android.app.Service#onStartCommand onStartCommand()} 메서드가 반드시 +정수를 반환해야 한다는 사실을 유의하십시오. 정수는 시스템이 서비스를 중단시킨 경우 시스템이 해당 서비스를 +계속하는 방법에 대해 설명하는 값입니다(위에서 논한 바와 같이 {@link +android.app.IntentService}의 기본 구현이 이것을 개발자 대신 처리해줍니다. 개발자가 이를 수정할 수도 있습니다). +{@link android.app.Service#onStartCommand onStartCommand()}로부터의 반환 값은 반드시 +다음 상수 중 하나여야 합니다.

+ +
+
{@link android.app.Service#START_NOT_STICKY}
+
시스템이 서비스를 {@link android.app.Service#onStartCommand +onStartCommand()} 반환 후에 중단시키면 서비스를 재생성하면 안 됩니다. 다만 전달할 +보류 인텐트가 있는 경우는 예외입니다. 이것은 서비스가 불필요하게 실행되는 일을 피할 수 있는 가장 안전한 옵션이며, +애플리케이션이 완료되지 않은 모든 작업을 단순히 재시작할 수 있을 때 좋습니다.
+
{@link android.app.Service#START_STICKY}
+
시스템이 서비스를 {@link android.app.Service#onStartCommand +onStartCommand()} 반환 후에 중단시키는 경우, 서비스를 재생성하고 {@link +android.app.Service#onStartCommand onStartCommand()}를 호출하되 마지막 인텐트를 다시 전달하지는 마십시오. +그 대신, 시스템이 null 인텐트로 {@link android.app.Service#onStartCommand onStartCommand()}를 +호출합니다. 다만 서비스를 시작할 보류 인텐트가 있는 경우만은 예외이며, 이럴 때에는 +그러한 인텐트를 전달합니다. 이것은 명령을 실행하지는 않지만 무기한으로 실행 중이며 작업을 기다리고 있는 +미디어 플레이어(또는 그와 비슷한 서비스)에 적합합니다.
+
{@link android.app.Service#START_REDELIVER_INTENT}
+
시스템이 서비스를 {@link android.app.Service#onStartCommand +onStartCommand()} 반환 후에 중단시키는 경우, 서비스를 재생성하고 서비스에 전달된 마지막 인텐트로 {@link +android.app.Service#onStartCommand onStartCommand()}를 +호출하십시오. 모든 보류 인텐트가 차례로 전달됩니다. 이것은 즉시 재개되어야 하는 작업을 +능동적으로 수행 중인 서비스(예를 들어 파일 다운로드 등)에 적합합니다.
+
+

이러한 반환 값에 대한 자세한 내용은 각 상수에 대해 링크로 연결된 참조 문서를 +확인하십시오.

+ + + +

서비스 시작

+ +

액티비티나 다른 구성 요소에서 서비스를 시작하려면 +{@link android.content.Intent}를(시작할 서비스를 나타냄) {@link +android.content.Context#startService startService()}에 전달하면 됩니다. Android 시스템이 서비스의 {@link +android.app.Service#onStartCommand onStartCommand()} 메서드를 호출하여 여기에 {@link +android.content.Intent}를 전달합니다. ({@link android.app.Service#onStartCommand +onStartCommand()}를 직접 호출하면 절대로 안 됩니다.)

+ +

예를 들어 이전 섹션의 예시 서비스({@code +HelloService})를 액티비티가 시작하려면 {@link android.content.Context#startService +startService()}로 명시적 인텐트를 사용하면 됩니다.

+ +
+Intent intent = new Intent(this, HelloService.class);
+startService(intent);
+
+ +

{@link android.content.Context#startService startService()} 메서드가 즉시 반환되며 +Android 시스템이 서비스의 {@link android.app.Service#onStartCommand +onStartCommand()} 메서드를 호출합니다. 서비스가 이미 실행 중이지 않은 경우, 시스템은 우선 {@link +android.app.Service#onCreate onCreate()}를 호출하고, 다음으로 {@link android.app.Service#onStartCommand +onStartCommand()}를 호출합니다.

+ +

서비스가 바인딩도 제공하지 않는 경우, {@link +android.content.Context#startService startService()}와 함께 전달된 인텐트가 애플리케이션 구성 요소와 서비스 사이의 +유일한 통신 방법입니다. 그러나 서비스가 결과를 돌려보내기를 원하는 경우, 서비스를 시작한 +클라이언트가 브로드캐스트를 위해 {@link android.app.PendingIntent}를 +만들 수 있고({@link android.app.PendingIntent#getBroadcast getBroadcast()} 사용) 이를 서비스를 시작한 +{@link android.content.Intent} 내의 서비스에 전달할 수 있습니다. 그러면 서비스가 +이 브로드캐스트를 사용하여 결과를 전달할 수 있게 됩니다.

+ +

서비스를 시작하기 위한 여러 개의 요청은 서비스의 +{@link android.app.Service#onStartCommand onStartCommand()}로의 상응하는 여러 개의 호출이라는 결과를 낳습니다. 하지만, 서비스를 중단하려면 +이를 중단하라는 요청 하나({@link android.app.Service#stopSelf stopSelf()} 또는 {@link +android.content.Context#stopService stopService()} 사용)만 있으면 됩니다.

+ + +

서비스 중단

+ +

시작된 서비스는 자신만의 수명 주기를 직접 관리해야 합니다. 다시 말해, 시스템이 +서비스를 중단하거나 소멸시키지 않는다는 뜻입니다. 다만 시스템 메모리를 회복해야 하고 서비스가 +{@link android.app.Service#onStartCommand onStartCommand()} 반환 후에도 계속 실행되는 경우는 예외입니다. 따라서, +서비스는 {@link android.app.Service#stopSelf stopSelf()}를 호출하여 스스로 중단시켜야 하고, 아니면 +다른 구성 요소가 {@link android.content.Context#stopService stopService()}를 호출하여 이를 중단시킬 수 있습니다.

+ +

일단 {@link android.app.Service#stopSelf stopSelf()} 또는 {@link +android.content.Context#stopService stopService()}로 중단하기를 요청하고 나면 시스템이 서비스를 가능한 한 빨리 +소멸시킵니다.

+ +

그러나, 서비스가 {@link +android.app.Service#onStartCommand onStartCommand()}로의 요청을 동시에 여러 개 처리하기를 바라는 경우라면 시작 요청 처리를 완료한 뒤에도 +서비스를 중단하면 안 됩니다. 그 이후 새 시작 요청을 받았을 수 있기 +때문입니다(첫 요청 종료 시에 중단하면 두 번째 요청을 종료시킵니다). 이 문제를 +피하려면, {@link android.app.Service#stopSelf(int)}를 사용하여 서비스를 +중단시키라는 개발자의 요청이 항상 최신 시작 요청에 기반하도록 해야 합니다. 다시 말해, {@link +android.app.Service#stopSelf(int)}를 호출할 때면 시작 요청의 ID({@link android.app.Service#onStartCommand onStartCommand()}에 전달된 +startId)를 전달하게 됩니다. 여기에 중단 요청이 +부합됩니다. 그런 다음 개발자가 {@link +android.app.Service#stopSelf(int)}를 호출할 수 있기 전에 서비스가 새 시작 요청을 받은 경우, ID가 일치하지 않게 되고 서비스는 중단되지 않습니다.

+ +

주의: 서비스가 작업을 완료한 다음 애플리케이션이 +소속 서비스를 중단할 수 있어야 한다는 점이 중요합니다. 그래야 시스템 리소스 낭비를 피하고 배터리 전력 소모를 줄일 수 있습니다. 필요한 경우 +다른 구성 요소도 서비스를 중단시킬 수 있습니다. {@link +android.content.Context#stopService stopService()}를 호출하면 됩니다. 서비스에 대해 바인딩을 활성화하더라도, +서비스가 {@link +android.app.Service#onStartCommand onStartCommand()}로의 호출을 한 번이라도 받았으면 항상 서비스를 직접 중단시켜야 합니다.

+ +

서비스의 수명 주기에 대한 자세한 정보는 아래에 있는 서비스 수명 주기 관리에 관한 섹션을 참고하세요.

+ + + +

바인딩된 서비스 생성

+ +

바인딩된 서비스는 애플리케이션 구성 요소가 자신에게 바인딩될 수 있도록 허용하는 서비스로, 이때 {@link +android.content.Context#bindService bindService()}를 호출하여 오래 지속되는 연결을 생성합니다 +(또한 보통은 구성 요소가 {@link +android.content.Context#startService startService()}를 호출하여 서비스를 시작하는 것을 허용하지 않습니다).

+ +

액티비티와 애플리케이션의 다른 구성 요소에서 서비스와 상호 작용하기를 원하는 경우 +바인딩된 서비스를 생성해야 합니다. 아니면 애플리케이션의 기능 몇 가지를 프로세스 간 통신(IPC)을 통해 +다른 애플리케이션에 노출하고자 하는 경우에도 좋습니다.

+ +

바인딩된 서비스를 생성하려면 {@link +android.app.Service#onBind onBind()} 콜백 메서드를 구현하여 서비스와의 통신을 위한 인터페이스를 정의하는 +{@link android.os.IBinder}를 반환하도록 해야 합니다. 그러면 다른 애플리케이션 구성 요소가 +{@link android.content.Context#bindService bindService()}를 호출하여 해당 인터페이스를 검색하고, 서비스에 있는 메서드를 +호출하기 시작할 수 있습니다. 서비스는 자신에게 바인딩된 애플리케이션 구성 요소에게 도움이 되기 위해서만 +존재하는 것이므로, 서비스에 바인딩된 구성 요소가 없으면 시스템이 이를 소멸시킵니다(바인딩된 서비스는 시작된 서비스처럼 +{@link android.app.Service#onStartCommand onStartCommand()}를 통해 +중단시키지 않아도 됩니다).

+ +

바인딩된 서비스를 생성하려면 가장 먼저 해야 할 일은 클라이언트가 서비스와 +통신할 수 있는 방법을 나타내는 인터페이스를 정의하는 것입니다. 서비스와 클라이언트 사이에서 쓰이는 이 인터페이스는 +반드시 {@link android.os.IBinder}의 구현이어야 하며 이를 +서비스가 {@link android.app.Service#onBind +onBind()} 콜백 메서드에서 반환해야 합니다. 클라이언트가 {@link android.os.IBinder}를 수신하면 해당 인터페이스를 통해 서비스와 +상호 작용을 시작할 수 있습니다.

+ +

여러 클라이언트가 서비스에 한꺼번에 바인딩될 수 있습니다. 클라이언트가 서비스와의 상호 작용을 완료하면 이는 +{@link android.content.Context#unbindService unbindService()}를 호출하여 바인딩을 해제합니다. 서비스에 +바인딩된 클라이언트가 하나도 없으면 시스템이 해당 서비스를 소멸시킵니다.

+ +

바인딩된 서비스를 구현하는 데에는 여러 가지 방법이 있으며 그러한 구현은 시작된 서비스보다 +훨씬 복잡합니다. 따라서 바인딩된 서비스 논의는 +바인딩된 서비스에 관한 별도의 문서에서 다룹니다.

+ + + +

사용자에게 알림 전송

+ +

서비스는 일단 실행되고 나면 사용자에게 알림 메시지 또는 상태 표시줄 알림 등을 사용해 이벤트를 알릴 수 있습니다.

+ +

알림 메시지란 현재 창의 표면에 잠시 나타났다가 사라지는 메시지이고, +상태 표시줄 알림은 상태 표시줄에 메시지가 담긴 아이콘을 제공하여 사용자가 이를 선택하여 +조치를 취할 수 있게 하는 것입니다(예: 액티비티 시작).

+ +

보통, 일종의 배경 작업이 완료되었고 +(예: 파일 다운로드 완료) 이제 사용자가 그에 대해 조치를 취할 수 있는 경우 상태 표시줄 알림이 +최선의 기법입니다. 사용자가 확장된 보기에서 알림을 선택하면, +해당 알림이 액티비티를 시작할 수 있습니다(예: 다운로드한 파일 보기).

+ +

자세한 정보는 알림 메시지 또는 상태 표시줄 알림 +개발자 가이드를 참조하십시오.

+ + + +

전경에서 서비스 실행하기

+ +

전경 서비스는 사용자가 능동적으로 인식하고 있으므로 메모리 부족 시에도 +시스템이 중단할 후보로 고려되지 않는 서비스를 말합니다. 전경 +서비스는 상태 표시줄에 대한 알림을 제공해야 합니다. 이것은 +"진행 중" 제목 아래에 배치되며, 이는 곧 해당 알림은 서비스가 중단되었거나 +전경에서 제거되지 않은 이상 무시할 수 없다는 뜻입니다.

+ +

예를 들어 서비스에서 음악을 재생하는 음악 플레이어는 전경에서 +실행되도록 설정해야 합니다. 사용자가 이것의 작동을 분명히 인식하고 있기 +때문입니다. 상태 표시줄에 있는 알림은 현재 노래를 나타내고 +사용자로 하여금 음악 플레이어와 상호 작용할 액티비티를 시작하게 해줄 수도 있습니다.

+ +

서비스가 전경에서 실행되도록 요청하려면 {@link +android.app.Service#startForeground startForeground()}를 호출하면 됩니다. 이 메서드는 두 개의 매개변수를 취합니다. +그 중 하나는 해당 알림을 고유하게 식별하는 정수이고 다른 하나는 상태 표시줄에 해당되는 {@link +android.app.Notification}입니다. 예:

+ +
+Notification notification = new Notification(R.drawable.icon, getText(R.string.ticker_text),
+        System.currentTimeMillis());
+Intent notificationIntent = new Intent(this, ExampleActivity.class);
+PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);
+notification.setLatestEventInfo(this, getText(R.string.notification_title),
+        getText(R.string.notification_message), pendingIntent);
+startForeground(ONGOING_NOTIFICATION_ID, notification);
+
+ +

주의: {@link +android.app.Service#startForeground startForeground()}에 부여하는 정수 ID가 0이면 안 됩니다.

+ + +

서비스를 전경에서 제거하려면 {@link +android.app.Service#stopForeground stopForeground()}를 호출하면 됩니다. 이 메서드는 부울 값을 취하며, 이것이 +상태 표시줄 알림도 제거할지 여부를 나타냅니다. 이 메서드는 서비스를 중단시키지 않습니다. + 그러나, 서비스가 전경에서 실행 중인 동안 서비스를 중단시키면 +알림도 마찬가지로 제거됩니다.

+ +

알림에 대한 자세한 정보는 상태 표시줄 +알림 생성을 참조하십시오.

+ + + +

서비스 수명 주기 관리

+ +

서비스의 수명 주기는 액티비티의 수명 주기보다 훨씬 간단합니다. 하지만, 서비스를 생성하고 +소멸시키는 방법에 특히 주의를 기울여야 한다는 면에서 중요도는 이쪽이 더 높습니다. 서비스는 사용자가 모르는 채로 +배경에서 실행될 수 있기 때문입니다.

+ +

서비스 수명 주기—생성되었을 때부터 소멸될 때까지—는 두 가지 서로 다른 경로를 +따를 수 있습니다.

+ +
    +
  • 시작된 서비스 +

    서비스는 또 다른 구성 요소가 {@link +android.content.Context#startService startService()}를 호출하면 생성됩니다. 그러면 서비스가 무기한으로 실행될 수 있으며 +스스로 알아서 중단되어야 합니다. 이때 {@link +android.app.Service#stopSelf() stopSelf()}를 호출하는 방법을 씁니다. 또 다른 구성 요소도 서비스를 중단시킬 수 +있습니다. {@link android.content.Context#stopService +stopService()}를 호출하면 됩니다. 서비스가 중단되면 시스템이 이를 소멸시킵니다.

  • + +
  • 바인딩된 서비스 +

    서비스는 또 다른 구성 요소(클라이언트)가 {@link +android.content.Context#bindService bindService()}를 호출하면 생성됩니다. 그러면 클라이언트가 +{@link android.os.IBinder} 인터페이스를 통해 서비스와 통신을 주고받을 수 있습니다. 클라이언트가 연결을 종료하려면 +{@link android.content.Context#unbindService unbindService()}를 호출하면 됩니다. 여러 클라이언트가 같은 서비스에 +바인딩될 수 있으며, 이 모두가 바인딩을 해제하면 시스템이 해당 서비스를 소멸시킵니다 (서비스가 스스로를 중단시키지 +않아도 됩니다).

  • +
+ +

이와 같은 두 가지 경로는 완전히 별개의 것은 아닙니다. 다시 말해, 이미 +{@link android.content.Context#startService startService()}로 시작된 서비스에 바인딩할 수도 있다는 뜻입니다. 예를 +들어, 배경 음악 서비스를 시작하려면 {@link android.content.Context#startService +startService()}를 호출하되 재생할 음악을 식별하는 {@link android.content.Intent}를 사용하면 됩니다. 나중에, +아마도 사용자가 플레이어에 좀 더 많은 통제권을 발휘하고자 하거나 +현재 노래에 대한 정보를 얻고자 할 때, 액티비티가 서비스에 바인딩될 수 있습니다. {@link +android.content.Context#bindService bindService()}를 사용하면 됩니다. 이런 경우에는 {@link +android.content.Context#stopService stopService()} 또는 {@link android.app.Service#stopSelf +stopSelf()}도 클라이언트가 모두 바인딩 해제될 때까지 실제로 서비스를 중단시키지 않습니다.

+ + +

수명 주기 콜백 구현하기

+ +

액티비티와 마찬가지로 서비스에도 수명 주기 콜백 메서드가 있어 이를 구현하면 서비스의 +상태 변경 내용을 모니터링할 수 있고 적절한 시기에 작업을 수행할 수 있습니다. 다음의 골격 +서비스는 각 수명 주기 메서드를 설명한 것입니다.

+ +
+public class ExampleService extends Service {
+    int mStartMode;       // indicates how to behave if the service is killed
+    IBinder mBinder;      // interface for clients that bind
+    boolean mAllowRebind; // indicates whether onRebind should be used
+
+    @Override
+    public void {@link android.app.Service#onCreate onCreate}() {
+        // The service is being created
+    }
+    @Override
+    public int {@link android.app.Service#onStartCommand onStartCommand}(Intent intent, int flags, int startId) {
+        // The service is starting, due to a call to {@link android.content.Context#startService startService()}
+        return mStartMode;
+    }
+    @Override
+    public IBinder {@link android.app.Service#onBind onBind}(Intent intent) {
+        // A client is binding to the service with {@link android.content.Context#bindService bindService()}
+        return mBinder;
+    }
+    @Override
+    public boolean {@link android.app.Service#onUnbind onUnbind}(Intent intent) {
+        // All clients have unbound with {@link android.content.Context#unbindService unbindService()}
+        return mAllowRebind;
+    }
+    @Override
+    public void {@link android.app.Service#onRebind onRebind}(Intent intent) {
+        // A client is binding to the service with {@link android.content.Context#bindService bindService()},
+        // after onUnbind() has already been called
+    }
+    @Override
+    public void {@link android.app.Service#onDestroy onDestroy}() {
+        // The service is no longer used and is being destroyed
+    }
+}
+
+ +

참고: 액티비티 수명 주기 콜백 메서드와는 달리 이와 같은 콜백 메서드를 구현하는 데에는 +슈퍼클래스 구현을 호출하지 않아도 됩니다.

+ + +

그림 2. 서비스 수명 주기입니다. 왼쪽의 다이어그램은 +서비스가 {@link android.content.Context#startService +startService()}로 생성된 경우의 수명 주기를 나타내며 오른쪽의 다이어그램은 서비스가 +{@link android.content.Context#bindService bindService()}로 생성된 경우의 수명 주기를 나타낸 것입니다.

+ +

이와 같은 메서드를 구현함으로써, 서비스 수명 주기의 두 가지 중첩된 루프를 모니터링할 수 있습니다.

+ +
    +
  • 서비스의 수명 주기 전체는 {@link +android.app.Service#onCreate onCreate()}가 호출된 시점과 {@link +android.app.Service#onDestroy}가 반환된 시점 사이에 일어납니다. 액티비티와 마찬가지로 서비스는 자신의 초기 설정을 +{@link android.app.Service#onCreate onCreate()}에서 수행하며 남은 리소스를 모두 {@link +android.app.Service#onDestroy onDestroy()}에 릴리스합니다. 예를 들어 +음악 재생 서비스의 경우 음악이 재생될 스레드를 {@link +android.app.Service#onCreate onCreate()}로 생성하고, 그럼 다음 해당 스레드를 중단할 때에는 {@link +android.app.Service#onDestroy onDestroy()}에서 할 수도 있습니다. + +

    {@link android.app.Service#onCreate onCreate()}와 {@link android.app.Service#onDestroy +onDestroy()} 메서드는 모든 서비스에 대해 호출됩니다. 이는 서비스가 +{@link android.content.Context#startService startService()}로 생성되었든 {@link +android.content.Context#bindService bindService()}로 생성되었든 관계 없이 적용됩니다.

  • + +
  • 서비스의 활성 수명 주기는 {@link +android.app.Service#onStartCommand onStartCommand()} 또는 {@link android.app.Service#onBind onBind()}로의 호출과 함께 시작됩니다. +각 메서드에 {@link +android.content.Intent}가 전달되는데 이것은 각각 {@link android.content.Context#startService +startService()} 또는 {@link android.content.Context#bindService bindService()} 중 하나에 전달된 것입니다. +

    서비스가 시작되면 수명 주기 전체가 종료되는 것과 동시에 활성 수명 주기도 종료됩니다 +(서비스는 {@link android.app.Service#onStartCommand +onStartCommand()}가 반환된 뒤에도 여전히 활성 상태입니다). 서비스가 바인딩된 경우, 활성 수명 주기는 {@link +android.app.Service#onUnbind onUnbind()}가 반환되면 종료됩니다.

    +
  • +
+ +

참고: 시작된 서비스를 중단하려면 +{@link android.app.Service#stopSelf stopSelf()} 또는 {@link +android.content.Context#stopService stopService()}를 호출하면 되지만, 서비스에 대한 상응하는 콜백은 +없습니다(즉 {@code onStop()} 콜백이 없습니다). 그러므로, 서비스가 클라이언트에 바인딩되어 있지 않은 한 +시스템은 서비스가 중단되면 이를 소멸시킵니다. 수신되는 콜백은 {@link +android.app.Service#onDestroy onDestroy()}가 유일합니다.

+ +

그림 2는 서비스에 대한 일반적인 콜백 메서드를 나타낸 것입니다. 이 그림에서는 +{@link android.content.Context#startService startService()}로 생성된 서비스와 +{@link android.content.Context#bindService bindService()}로 생성된 서비스를 +구분하고 있지만, 어떤 식으로 시작되었든 모든 서비스는 클라이언트가 자신에 바인딩되도록 허용할 수 있다는 점을 명심하십시오. +말하자면, {@link android.app.Service#onStartCommand +onStartCommand()}로 처음 시작된 서비스(클라이언트가 {@link android.content.Context#startService startService()}를 호출해서)라고 해도 +여전히 {@link android.app.Service#onBind onBind()}로의 호출을 받을 수 있습니다(클라이언트가 +{@link android.content.Context#bindService bindService()}를 호출하는 경우).

+ +

바인딩을 제공하는 서비스 생성에 대한 자세한 내용은 바인딩된 서비스 문서를 참조하십시오. 이 안에는 {@link android.app.Service#onRebind onRebind()} +콜백 메서드에 대한 자세한 정보가 바인딩된 서비스의 +수명 주기 관리에 관한 섹션에 +담겨 있습니다.

+ + + diff --git a/docs/html-intl/intl/ko/guide/components/tasks-and-back-stack.jd b/docs/html-intl/intl/ko/guide/components/tasks-and-back-stack.jd new file mode 100644 index 0000000000000000000000000000000000000000..6b896f9d50ad481b927a4c0c5dd0bac193a56e54 --- /dev/null +++ b/docs/html-intl/intl/ko/guide/components/tasks-and-back-stack.jd @@ -0,0 +1,578 @@ +page.title=작업 및 백 스택 +parent.title=액티비티 +parent.link=activities.html +@jd:body + + + + +

하나의 애플리케이션에는 보통 여러 개의 액티비티가 들어있습니다. 각 액티비티는 +사용자가 수행할 수 있는 특정한 종류의 작업을 중심으로 디자인되어야 하며 다른 액티비티를 +시작할 수 있는 기능이 있습니다. 예를 들어 이메일 애플리케이션에는 새 메시지 목록을 표시하는 하나의 액티비티가 있을 수 있습니다. +사용자가 메시지를 하나 선택하면, 새 액티비티가 열려 해당 메시지를 볼 수 있게 합니다.

+ +

액티비티는 기기에서 다른 애플리케이션에 존재하는 액티비티를 시작할 수도 있습니다. 예를 들어 +애플리케이션이 이메일 메시지를 보내고자 하는 경우, "전송" 작업을 수행할 인텐트를 +정의하여 이메일 주소와 메시지 등의 몇 가지 데이터를 포함시키면 됩니다. 그러면 다른 애플리케이션에서 가져온 액티비티 중 +이러한 종류의 인텐트를 처리한다고 스스로 선언한 것이 열립니다. 이 경우, 이 인텐트는 +이메일을 전송하기 위한 것이므로 이메일 애플리케이션의 "작성" 액티비티가 시작됩니다(같은 인텐트를 +지원하는 액티비티가 여러 개 있는 경우, 시스템은 사용자에게 어느 것을 사용할지 선택하도록 합니다). 이메일이 전송되면 +액티비티가 재개되고 해당 이메일 액티비티가 애플리케이션의 일부였던 것처럼 보입니다. 액티비티는 +서로 다른 애플리케이션에서 온 것일 수 있지만, Android는 두 액티비티를 +모두 같은 작업 안에 유지하여 이처럼 막힘 없는 사용자 환경을 유지합니다.

+ +

작업이란 액티비티 컬렉션을 일컫는 말로, 사용자가 특정 작업을 수행할 때 이것과 +상호 작용합니다. 액티비티는 스택 안에 정렬되며(백 스택), 이때 +순서는 각 액티비티가 열린 순서와 같습니다.

+ + + +

기기 메인 스크린이 대다수 작업의 시작 지점입니다. 사용자가 +애플리케이션 +시작 관리자에 있는 아이콘(또는 메인 스크린의 바로 가기)을 터치하면 해당 애플리케이션의 작업이 전경으로 나옵니다. 해당 애플리케이션에 대한 +작업이 존재하지 않으면(이 애플리케이션을 최근에 사용한 적이 없는 경우), 새 작업이 생성되고 +해당 애플리케이션의 "기본" 액티비티가 스택에 있는 루트 액티비티로 열립니다.

+ +

현재 액티비티가 또 다른 액티비티를 시작하는 경우, 새 액티비티가 스택의 맨 위로 밀어올려지고 +사용자의 초점이 이에 맞춰집니다. 이전 액티비티는 스택에 유지되지만, 중단됩니다. 액티비티가 중단되면 +시스템은 이 액티비티의 사용자 인터페이스의 현재 상태를 보존합니다. 사용자가 +뒤로 + 버튼을 누르면, 현재 액티비티가 스택의 맨 위에서 튀어나오고(해당 액티비티는 소멸됩니다) +이전 액티비티가 재개됩니다(이것의 UI 이전 상태가 복원됩니다). 스택에 있는 액티비티는 +결코 다시 정렬되지 않습니다. 다만 스택에서 밀어올려지거나 튀어나올 뿐입니다. 즉, 현재 액티비티에 의해 +시작되면 스택 위로 밀어올려지고, 사용자가 뒤로 버튼을 사용하여 액티비티를 떠나면 튀어나와 사라지는 것입니다. 따라서, +백 스택은 +일종의 "후입선출" 객체 구조로서 작동한다고 할 수 있습니다. 그림 1은 +이 행동을 시간 표시 막대와 함께 표시하여 여러 액티비티 사이의 진행률을 보여주며, +각 시점에서 현재 백 스택의 모습을 나타낸 것입니다.

+ + +

그림 1. 작업에 있는 각각의 새 액티비티가 백 스택에 항목을 추가하는 +방법을 나타낸 것입니다. 사용자가 뒤로 버튼을 누르면 현재 +액티비티가 +소멸되고 이전 액티비티가 재개됩니다.

+ + +

사용자가 계속해서 뒤로 버튼을 누르면, 스택에 있는 각 액티비티가 하나씩 튀어나가 +이전 것을 +드러내고, 마침내는 사용자가 메인 스크린으로 되돌아가게 됩니다(아니면 작업이 시작되었을 때 +실행 중이던 액티비티가 무엇이든 그것으로 되돌아갑니다). 스택에서 모든 액티비티가 제거되면 이 작업은 더 이상 존재하지 않게 됩니다.

+ +
+

그림 2. 두 개의 작업: 작업 B가 전경에서 사용자 상호 작용을 수신하는 한편, +작업 A는 배경에서 재개되기를 기다립니다.

+
+
+

그림 3. 하나의 액티비티가 여러 번 인스턴트화됩니다.

+
+ +

작업이란 하나의 잘 짜여진 단위로 사용자가 새 작업을 시작할 때 "배경"으로 이동할 수도 있고 + 버튼을 통해 메인 스크린으로 이동할 수도 있습니다. 작업의 모든 액티비티는 배경에 있는 동안은 +중단되지만 +, 해당 작업에 대한 백 스택은 그대로 변함 없이 유지됩니다. 이 작업은 또 다른 작업이 발생하는 동안 +초점을 잃을 뿐입니다(그림 2 참조). 그런 다음 작업이 "전경"으로 되돌아와 사용자가 +이전에 하던 일을 계속할 수 있습니다. 예를 들어 현재 작업(작업 A)의 스택에 세 개의 액티비티가 있다고 +가정하면 그 중 둘은 현재 액티비티 아래에 있습니다. 사용자가 + 버튼을 누른 다음 +애플리케이션 시작 관리자로부터 새 애플리케이션을 시작합니다. 메인 스크린이 나타나면 작업 A는 +배경으로 이동합니다. 새 애플리케이션이 시작되면 시스템은 해당 애플리케이션에 대한 작업을 시작하며 +(작업 B) 여기에는 나름의 액티비티 스택이 딸려 있습니다. 해당 애플리케이션과 +상호 작용한 후, 사용자는 다시 홈으로 돌아와 원래 작업 A를 시작한 +애플리케이션을 선택합니다. 이제 작업 A가 전경으로 옵니다. +이 스택에 있는 액티비티 세 개는 모두 멀쩡하고, 스택 맨 위에 있는 액티비티가 +재개됩니다. 이 시점에서 +사용자는 작업 B로 도로 전환할 수도 있습니다. 홈으로 이동하여 해당 작업을 +시작한 애플리케이션 아이콘을 선택하면 됩니다(아니면 +개요 화면에서 해당 앱의 작업을 선택해도 됩니다). +이것이 Android에서 멀티태스킹을 하는 작업의 예시입니다.

+ +

참고: 여러 개의 작업을 배경에 한꺼번에 대기시킬 수 있습니다. +하지만, 사용자가 수많은 배경 작업을 동시에 실행하면 시스템이 메모리를 복원하기 위해 +배경 액티비티를 소멸시키기 시작할 수 있고, 그러면 액티비티 상태가 손실됩니다. +다음의 액티비티 상태에 관한 섹션을 참조하십시오.

+ +

백 스택에 있는 액티비티는 결코 다시 정렬되지 않으므로, 애플리케이션에서 +사용자에게 하나 이상의 액티비티로부터 특정 액티비티를 시작하도록 허용하는 경우, 해당 액티비티의 새 인스턴스가 +생성되어 스택 위로 밀려옵니다(해당 액티비티의 기존 인스턴스를 +맨 위로 가져오는 대신). 따라서, 애플리케이션 안의 한 액티비티가 여러 번 +인스턴트화될 수 있으며(서로 다른 작업으로부터도 가능), 이를 나타낸 것이 그림 3입니다. 이 때문에 사용자가 +뒤로 버튼을 사용하여 뒤로 이동하는 경우, 액티비티의 각 인스턴스가 열린 순서대로 드러납니다 +(각자 나름의 +UI 상태를 가지고). 다만, 액티비티가 한 번 이상 인스턴트화되는 것을 원치 않으면 이 행동은 수정할 수 +있습니다. 그 방법에 대해서는 작업 관리하기에 관한 이후 섹션에서 이야기합니다.

+ + +

액티비티 및 작업에 대한 기본 행동을 요약하려면 다음과 같이 합니다.

+ +
    +
  • 액티비티 A가 액티비티 B를 시작하면 액티비티 A는 중단되지만, 시스템이 그 상태를 +(예: 스크롤 위치 및 양식에 입력된 텍스트 등) 보존합니다. +사용자가 액티비티 B에 있는 동안 뒤로 버튼을 누르면 액티비티 A가 재개되며 상태도 +복원됩니다.
  • +
  • 사용자가 버튼을 눌러 작업을 떠나면 현재 액티비티가 +중단되고 +그 소속 작업이 배경으로 들어갑니다. 시스템은 작업에 속한 모든 액티비티의 상태를 보존합니다. 사용자가 +나중에 작업을 시작한 시작 관리자 아이콘을 선택하여 해당 작업을 재개하면, 그 작업이 +전경으로 나오고 스택 맨 위에서 액티비티를 재개합니다.
  • +
  • 사용자가 뒤로 버튼을 누르면, 현재 액티비티가 스택에서 튀어나오고 +소멸됩니다. + 스택에 있던 이전 액티비티가 재개됩니다. 액티비티가 소멸되면, 시스템은 그 액티비티의 상태를 +보존하지 않습니다.
  • +
  • 액티비티는 여러 번 인스턴트화할 수 있으며, 다른 작업에서도 이를 수행할 수 있습니다.
  • +
+ + +
+

탐색 디자인

+

Android에서 앱 탐색의 작동 원리를 자세히 알아보려면, Android 디자인의 탐색 가이드를 읽어보십시오.

+
+ + +

액티비티 상태 저장하기

+ +

위에서 논한 바와 같이, 시스템의 기본 행동은 액티비티가 중단되면 그 상태를 보존해두는 +것입니다. 이렇게 하면, 사용자가 이전 액티비티로 도로 이동했을 때 그에 속한 사용자 인터페이스가 이전 상태 +그대로 표시됩니다. 그러나 액티비티의 상태를 미리 보존할 수도 있으며 사전에 이렇게 해야 합니다. +이때에는, 액티비티가 소멸되고 다시 만들어야 하는 경우를 대비해 +콜백 메서드를 사용합니다.

+ +

시스템이 액티비티 중 하나를 중단시키는 경우(예를 들어 새 액티비티가 시작되었을 때 또는 작업이 +배경으로 이동하는 경우), 시스템은 시스템 메모리를 회복해야 하는 경우 액티비티를 +완전히 소멸시켜버릴 수도 있습니다. 이런 상황이 벌어지면, 액티비티 상태에 대한 정보는 손실됩니다. 이런 일이 벌어지더라도, +시스템은 여전히 +백 스택에 해당 액티비티의 자리가 있다는 것을 알고 있습니다. 다만 액티비티가 스택 맨 위로 올라오면 +시스템이 이를 (재개하는 것이 아니라) 재생성해야만 합니다. 사용자의 작업 내용을 +잃어버리는 불상사를 피하려면 그 내용을 미리 보존해두어야 합니다. 이때 액티비티의 +{@link android.app.Activity#onSaveInstanceState onSaveInstanceState()} 콜백 +메서드를 구현하는 방법을 씁니다.

+ +

액티비티 상태를 저장하는 방법에 대한 자세한 정보는 액티비티 +문서를 참조하십시오.

+ + + +

작업 관리하기

+ +

Android가 작업과 백 스택을 관리하는 방식은 위에 설명된 바와 같고—같은 작업 안에서 +연이어 시작된 모든 작업을 한곳에 배치하되 "후입선출" 스택에 두는 것—이 방식은 +대부분의 애플리케이션에 아주 효과적입니다. 여러분은 액티비티가 작업과 연관된 방식이나 +백 스택에서의 존재 방식에 대해 염려하지 않아도 됩니다. 그러나, 정상적인 동작을 인터럽트하기로 결정할 수도 +있습니다. 애플리케이션의 액티비티 하나가 시작되면 새 작업을 시작하려 +할 수도 있습니다(현재 작업 내에 배치되는 것 대신에). 아니면, 액티비티를 시작하면 그것의 +기존 인스턴스 하나를 앞으로 가져오고자 할 수도 있습니다(백 스택 맨 위에서 새 인스턴스를 +생성하는 것 대신에). 또는 백 스택에서 사용자가 작업을 떠날 때의 루트 액티비티를 제외하고 +모든 액티비티를 지우고자 할 수도 있습니다.

+ +

이 모든 것과 그 외에도 많은 것을 할 수 있는 것이 바로 +{@code <activity>} +매니페스트 요소 안에 있는 속성과, +{@link android.app.Activity#startActivity startActivity()}에 전달한 인텐트에 있는 플래그입니다.

+ +

이런 면에서, 여러분이 사용할 수 있는 주요 +{@code <activity>} 속성은 다음과 같습니다.

+ + + +

그리고 다음은 여러분이 사용할 수 있는 주요 인텐트 플래그입니다.

+ +
    +
  • {@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK}
  • +
  • {@link android.content.Intent#FLAG_ACTIVITY_CLEAR_TOP}
  • +
  • {@link android.content.Intent#FLAG_ACTIVITY_SINGLE_TOP}
  • +
+ +

다음 섹션에서는 이와 같은 매니페스트 속성과 인텐트 플래그를 사용하여 +액티비티가 작업과 연관되는 방식을 정의하고 백 스택에서 액티비티가 동작하는 방식을 정의하는 방법을 배우게 됩니다.

+ +

이외에도 별도로 작업과 액티비티를 표시하는 방법에 대한 고려 사항과 +개요 화면에서의 관리 방법을 논합니다. 자세한 정보는 개요 화면을 +참조하십시오. 보통은 개요 화면에 작업과 액티비티가 어떻게 표현될지는 +시스템이 정의하도록 두어야 합니다. 이 동작을 개발자가 수정할 필요도 없습니다.

+ +

주의: 대부분의 애플리케이션은 액티비티와 작업에 대한 +기본 동작을 인터럽트하지 않는 것이 정상입니다. 액티비티가 기본 동작을 수정하는 것이 필요하다는 +판단이 서면, 시작 과정 중에 액티비티의 유용성을 테스트하십시오. +또한 다른 액티비티와 작업에서 뒤로 버튼을 써서 해당 액티비티로 돌아올 때에도 유용성을 테스트해야 합니다. +사용자의 예상되는 동작과 충돌할 가능성이 있는 탐색 동작을 꼭 테스트하십시오.

+ + +

시작 모드 정의하기

+ +

시작 모드를 사용하면 액티비티의 새 인스턴스가 현재 작업과 연관된 방식을 정의할 수 있게 +해줍니다. 여러 가지 시작 모드를 두 가지 방식으로 정의할 수 있습니다.

+
    +
  • 매니페스트 파일 사용하기 +

    매니페스트 파일에서 액티비티를 선언하는 경우, 액티비티가 시작될 때 여러 작업과 어떤 식으로 +연관을 맺어야 하는지 지정할 수 있습니다.

  • +
  • 인텐트 플래그 사용하기 +

    {@link android.app.Activity#startActivity startActivity()}를 호출하는 경우 +{@link android.content.Intent}에 플래그를 포함시켜 새 액티비티가 현재 작업과 어떻게 연관되어야 할지(또는 +애초에 연관을 맺을지 아닐지) 선언하도록 할 수 있습니다.

  • +
+ +

따라서, 액티비티 A가 액티비티 B를 시작하면 액티비티 B는 자신의 매니페스트에서 +현재 작업과 연관을 맺는 데 적당한 방식(연관을 맺어야 한다면)을 정의할 수 있고 액티비티 A 또한 +액티비티 B가 현재 작업과 연관을 맺는 방식을 요청할 수 있습니다. 두 액티비티가 모두 액티비티 B가 작업과 +연관되는 방식을 정의하는 경우, 액티비티 A의 요청(인텐트에 정의된 바를 따름)을 액티비티 B의 +요청(자신의 매니페스트에서 정의)보다 우위로 인식합니다.

+ +

참고: 매니페스트 파일에 사용할 수 있는 시작 모드 중에는 +인텐트의 플래그로 사용할 수는 없는 것도 있으며, 이와 마찬 가지로 인텐트의 플래그로 사용할 수 있는 시작 모드 중에는 +매니페스트에서 정의할 수 없는 것도 있습니다.

+ + +

매니페스트 파일 사용하기

+ +

매니페스트 파일에서 액티비티를 선언하는 경우, 액티비티가 작업과 +어떤 식으로 연관되어야 할지 지정하려면 {@code <activity>} +요소의 {@code +launchMode} 속성을 사용하면 됩니다.

+ +

{@code +launchMode} 속성은 액티비티가 작업 안으로 들어가며 시작되는 방법에 대한 지침을 +나타냅니다. +launchMode +속성에 할당할 수 있는 시작 모드는 네 가지가 있습니다.

+ +
+
{@code "standard"} (기본 모드)
+
기본입니다. 시스템이 액티비티가 시작된 작업에서 액티비티의 새 인스턴스를 만들고 +인텐트의 경로를 이것으로 지정합니다. 액티비티는 여러 번 인스턴트화될 수 있고, +각 인스턴스는 서로 다른 작업에 속할 수 있으며 한 작업에 여러 개의 인스턴스가 있을 수 있습니다.
+
{@code "singleTop"}
+
액티비티의 인스턴스가 이미 현재 작업의 맨 위에 존재하는 경우, 시스템은 인텐트의 경로를 +해당 인스턴스로 지정합니다. 이때 액티비티의 새 인스턴스를 만들기보다는 해당 인스턴스의 {@link +android.app.Activity#onNewIntent onNewIntent()} 메서드를 호출하는 방법을 +통합니다. 액티비티는 여러 번 인스턴트화될 수 있고, 각 인스턴스는 서로 다른 작업에 +속할 수 있으며 한 작업에 여러 개의 인스턴스가 있을 수 있습니다(다만 백 스택의 맨 위에 있는 +액티비티가 액티비티의 기존 인스턴스가 아닌 경우에만 이것이 적용됩니다). +

예를 들어 어느 작업의 백 스택이 루트 액티비티 A와 액티비티 B, C, 그리고 맨 위의 액티비티 D로 +구성되어 있다고 가정합니다(이 스택은 A-B-C-D 형태를 띠며 D가 맨 위에 있습니다). 유형 D의 액티비티에 대한 인텐트가 도착합니다. +D에 기본 {@code "standard"} 시작 모드가 있는 경우, 클래스의 새 인스턴스가 시작되고 이 스택은 +A-B-C-D-D가 됩니다. 하지만, D의 시작 모드가 {@code "singleTop"}인 경우, D의 +기존 인스턴스가 해당 인텐트를 {@link +android.app.Activity#onNewIntent onNewIntent()}를 통해 받게 됩니다. 이것이 스택의 맨 위에 있기 때문입니다. 스택은 +계속 A-B-C-D로 유지됩니다. 그러나 유형 B의 액티비티에 대한 인텐트가 도착하는 경우, +B의 새 인스턴스가 스택에 추가되며 이는 액티비티의 시작 모드가 {@code "singleTop"}이더라도 무관하게 적용됩니다.

+

참고: 어느 액티비티의 새 인스턴스가 생성되면, +사용자가 뒤로 버튼을 눌러 이전 액티비티로 되돌아갈 수 있게 됩니다. 그러나 액티비티의 기존 +인스턴스가 +새 인텐트를 처리하는 경우, 사용자가 뒤로 버튼을 눌러도 새 인텐트가 {@link android.app.Activity#onNewIntent +onNewIntent()}에 도착하기 전의 액티비티 +상태로 +되돌아갈 수 없습니다.

+
+ +
{@code "singleTask"}
+
시스템이 새 작업을 만들고 새 작업의 루트에 있는 액티비티를 인스턴트화합니다. +하지만, 액티비티의 인스턴스가 이미 별개의 작업에 존재하는 경우, 시스템은 인텐트의 경로를 +기존 인스턴스로 지정합니다. 이때 새 인스턴스를 만들기보다 해당 인스턴스의 {@link +android.app.Activity#onNewIntent onNewIntent()} 메서드를 호출하는 방법을 통합니다. 한 번에 +액티비티 인스턴스 한 개씩만 존재할 수 있습니다. +

참고: 액티비티가 새 작업에서 시작되더라도, +뒤로 버튼을 누르면 여전히 사용자를 이전 액티비티로 돌려보냅니다.

+
{@code "singleInstance"}.
+
{@code "singleTask"}와 같습니다. 다만 시스템이 인스턴스를 보유하고 있는 작업 안으로 +다른 어떤 액티비티도 시작하지 않는다는 것은 예외입니다. 액티비티는 언제나 자신의 작업의 유일무이한 구성원입니다. +이것으로 시작한 액티비티는 모두 별개의 작업에서 열립니다.
+
+ + +

또 다른 예로 Android 브라우저 애플리케이션이 있습니다. 이것은 웹 브라우저 액티비티가 항상 +자신만의 작업에서 열려야 한다고 선언합니다. 이때 {@code <activity>} 요소에 {@code singleTask} 시작 모드를 지정하는 방법을 씁니다. +다시 말해 애플리케이션이 Android 브라우저를 열라는 인텐트를 발행하면 +브라우저의 액티비티가 애플리케이션과 같은 작업에 배치되지 않는다는 +뜻입니다. 그 대신, 브라우저에 대한 새 작업이 시작되거나, 브라우저에 이미 +배경에서 실행 중인 작업이 있는 경우 해당 작업이 전경으로 불려나와 새 인텐트를 처리하게 +됩니다.

+ +

액티비티가 새 작업에서 시작되었든 액티비티를 시작한 것과 같은 작업에서 시작되었든 관계 없이 +뒤로 버튼을 사용하면 언제나 사용자를 이전 액티비티로 돌려보냅니다. 다만, +{@code singleTask} 시작 모드를 나타내는 액티비티를 시작한 다음 해당 +액티비티의 인스턴스가 이미 배경 작업에 존재하는 경우, 그 작업 전체가 전경에 불려나옵니다. 이 시점에서 +백 스택에는 이제 앞으로 가져온 작업에서 가져온 모든 액티비티가 포함되어 있으며, 이는 스택의 +맨 위에 위치합니다. 그림 4는 이와 같은 유형의 시나리오를 나타낸 것입니다.

+ + +

그림 4. 시작 모드가 "singleTask"인 액티비티가 +백 스택에 추가되는 방법을 표현한 것입니다. 이 액티비티가 이미 자신의 백 스택을 가지고 있는 +배경 작업의 일부인 경우, 해당 백 스택도 모두 전경으로 +불려나오며, 이는 현재 작업 위에 배치됩니다.

+ +

매니페스트 파일에서 시작 모드를 사용하는 것에 대한 자세한 정보는 +<activity> +요소 문서를 참조하십시오. 여기에서 {@code launchMode} 속성과 허용된 값을 더 자세히 +논합니다.

+ +

참고: 액티비티에 대하여 {@code launchMode} 속성으로 지정한 동작을 +재정의하려면 액티비티를 시작한 인텐트에 포함된 플래그를 사용하면 됩니다. 이 내용은 +다음 섹션에서 논합니다.

+ + + +

인텐트 플래그 사용하기

+ +

액티비티를 시작할 때면, 액티비티가 자신의 작업과 연관되는 기본 방식을 수정할 수 있습니다. +{@link +android.app.Activity#startActivity startActivity()}에 전달한 인텐트 안에 있는 플래그를 포함시키면 됩니다. 기본 동작을 수정하는 데 사용할 수 있는 +플래그는 다음과 같습니다.

+ +

+

{@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK}
+
액티비티를 새 작업에서 시작합니다. 지금 시작하고 있는 액티비티에 대해 이미 실행 중인 작업이 있으면, +해당 작업의 마지막 상태를 복원하여 전경으로 불려나오고 액티비티는 새 인텐트를 +{@link android.app.Activity#onNewIntent onNewIntent()}에서 수신합니다. +

이렇게 하면 {@code "singleTask"} {@code launchMode} 값에서와 같은 동작을 발생시키며, +이는 이전 섹션에서 논한 것과 같습니다.

+
{@link android.content.Intent#FLAG_ACTIVITY_SINGLE_TOP}
+
시작되고 있는 액티비티가 현재 액티비티인 경우(백 스택 맨 위에 있는), 해당 액티비티의 새 인스턴스를 생성하는 대신 기존 +인스턴스가 {@link android.app.Activity#onNewIntent onNewIntent()}에 +대한 호출을 받습니다. +

이렇게 하면 {@code "singleTop"} {@code launchMode} 값에서와 같은 동작을 발생시키며, +이는 이전 섹션에서 논한 것과 같습니다.

+
{@link android.content.Intent#FLAG_ACTIVITY_CLEAR_TOP}
+
시작되고 있는 액티비티가 이미 현재 작업에서 실행 중인 경우, 해당 액티비티의 +새 인스턴스를 시작하는 대신 그 위에 있는 모든 다른 액티비티가 +소멸되고 이 인텐트는 해당 액티비티(이제 맨 위로 올라옴)의 재개된 인스턴스로, +{@link android.app.Activity#onNewIntent onNewIntent()}를 통해 전달됩니다. +

이 동작을 발생시키는 {@code launchMode} +속성에 대한 값은 없습니다.

+

{@code FLAG_ACTIVITY_CLEAR_TOP}는 +{@code FLAG_ACTIVITY_NEW_TASK}와 함께 쓰이는 경우가 가장 보편적입니다. +이들 플래그를 함께 사용하면 다른 작업에 있는 기존 액티비티의 위치를 +찾아 이를 인텐트에 응답할 수 있는 위치에 놓을 한 가지 방편이 됩니다.

+

참고: 지정된 액티비티의 시작 모드가 +{@code "standard"}인 경우, +이것 또한 스택에서 제거되고 그 자리에 새 인스턴스가 대신 생성되어 수신되는 인텐트를 +처리하게 됩니다. 이는 시작 모드가 +{@code "standard"}인 경우, 새 인텐트에 대해서는 항상 새 인스턴스가 생성되기 때문입니다.

+
+ + + + + + +

유사성 처리하기

+ +

유사성이란 액티비티가 어느 작업에 소속되기를 선호하는지를 나타내는 것입니다. 기본적으로, +같은 애플리케이션에서 나온 액티비티는 서로 유사성을 지니고 있습니다. 따라서, 기본적으로 +같은 애플리케이션 안에 있는 모든 액티비티는 같은 작업 안에 있는 것을 선호합니다. 하지만 액티비티에 대한 기본 유사성은 개발자가 +수정할 수 있습니다. 각기 다른 애플리케이션에서 정의된 +액티비티가 하나의 유사성을 공유할 수도 있고, 같은 애플리케이션에서 정의된 여러 액티비티에 +서로 다른 작업 유사성을 할당할 수도 있습니다.

+ +

어느 액티비티라도 {@code <activity>} +요소의 {@code taskAffinity} 속성을 +사용하여 유사성을 수정할 수 있습니다.

+ +

{@code taskAffinity} +속성은 문자열 값을 취합니다. 이는 + +{@code <manifest>} + 요소에서 선언한 기본 패키지 이름과 달리 고유해야 합니다. 왜냐하면 시스템이 이 이름을 사용하여 애플리케이션의 기본 작업 유사성을 +식별하기 때문입니다.

+ +

유사성이 역할을 갖는 것은 다음과 같은 두 가지 상황에서입니다.

+
    +
  • 액티비티를 시작한 인텐트에 +{@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK} + 플래그가 들어 있는 경우. + +

    새로운 액티비티는 기본적으로 +{@link android.app.Activity#startActivity startActivity()}를 호출한 액티비티의 작업 안으로 들어가며 시작됩니다. 이것은 발신자와 같은 +백 스택 위로 밀어내집니다. 하지만 +{@link android.app.Activity#startActivity startActivity()}에 +전달된 인텐트에 {@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK} + 플래그가 들어있는 경우, 시스템은 새 액티비티를 담을 다른 작업을 찾습니다. 이는 새 작업인 경우가 많습니다. +그렇지만 꼭 그래야 하는 것은 아닙니다. 새 액티비티와 같은 유사성을 가진 기존 작업이 이미 존재하는 경우, +해당 액티비티는 그 작업 안으로 들어가며 시작됩니다. 그렇지 않으면, 새 작업을 시작합니다.

    + +

    이 플래그 때문에 액티비티가 새 작업을 시작하게 되고 사용자가 버튼을 눌러 이 액티비티를 +떠나고자 +하는 경우, 사용자가 작업으로 도로 이동할 방법이 있어야 합니다. 엔티티 중에는(예를 들어 +알림 관리자) 액티비티를 항상 외부 작업으로만 시작하고 자신의 일부로서는 절대 시작하지 않는 것이 있습니다. +따라서 이들은 {@code FLAG_ACTIVITY_NEW_TASK}를 +{@link android.app.Activity#startActivity startActivity()}에 전달하는 인텐트에 포함시킵니다. +이 플래그를 사용할 수 있는 외부 엔티티가 +호출할 수 있는 액티비티를 가지고 있는 경우, 사용자가 시작된 작업에 돌아갈 수 있는 +방법을 따로 가지고 있어야 합니다. 예를 들어 시작 관리자 아이콘을 이용한다든지 하는 방법입니다(작업의 루트 액티비티에 +{@link android.content.Intent#CATEGORY_LAUNCHER} 인텐트 필터가 있습니다. 아래의 작업 시작하기 섹션을 참조하십시오).

    +
  • + +
  • 액티비티의 +{@code allowTaskReparenting} 속성이 {@code "true"}로 설정된 경우. +

    이 경우, 액티비티는 자신이 시작한 작업에서 벗어나 유사성을 가진 다른 작업이 전경으로 +나오면 그 작업으로 이동할 수 있습니다.

    +

    예를 들어 선택한 몇몇 도시에서 기상 상태를 예보하는 어느 액티비티가 +여행 애플리케이션의 일부로 정의되어 있다고 가정합니다. 이것은 같은 애플리케이션에 있는 +다른 여러 액티비티와 같은 유사성을 가지며(기본 애플리케이션 유사성) 이 속성으로 상위 재지정을 허용하기도 합니다. +액티비티 중 하나가 일기 예보 액티비티를 시작하면, 이는 처음에는 액티비티와 같은 작업에 +속합니다. 하지만 여행 애플리케이션의 작업이 전경으로 불려나오면 +일기 예보 액티비티는 그 작업에 다시 할당되며 그 안에 표시됩니다.

    +
  • +
+ +

팁: {@code .apk} 파일에 사용자 쪽에서 보기에 하나 이상의 "애플리케이션"이 +들어있는 경우, {@code taskAffinity} +속성을 사용하여 각 "애플리케이션"과 연관된 액티비티에 서로 다른 유사성을 할당하는 것이 좋습니다.

+ + + +

백 스택 지우기

+ +

사용자가 작업을 오랜 시간 동안 떠나 있으면, 시스템이 루트 액티비티만 빼고 모든 액티비티를 +해당 작업에서 지웁니다. 사용자가 다시 작업으로 돌아오면, 루트 액티비티만 복원됩니다. +시스템이 이런 식으로 동작하는 것은 오랜 시간이 지난 다음에는 사용자가 전에 하던 일을 중단하고 +새로운 일을 시작하기 위해 작업에 돌아올 가능성이 크기 때문입니다.

+ +

이 동작을 수정하는 데 사용할 수 있는 액티비티 속성이 몇 가지 있습니다.

+ +
+
alwaysRetainTaskState +
+
이 속성이 작업의 루트 액티비티 안에서 {@code "true"}로 설정되어 있는 경우, +방금 설명한 기본 동작이 일어나지 않습니다. +작업은 오랜 시간이 지난 뒤에도 자신의 스택에 있는 모든 액티비티를 유지합니다.
+ +
clearTaskOnLaunch
+
이 속성이 작업의 루트 액티비티 안에서 {@code "true"}로 설정되어 있는 경우, +사용자가 작업을 떠났다가 다시 돌아올 때마다 스택을 루트 액티비티까지 +지웁니다. 바꿔 말하면, 이것은 + +{@code alwaysRetainTaskState}와 정반대입니다. 사용자는 항상 작업의 초기 상태로 돌아오게 되며, +이는 아주 잠깐 동안만 작업을 떠난 경우에도 마찬가지입니다.
+ +
finishOnTaskLaunch +
+
이 속성은 {@code clearTaskOnLaunch}와 같지만, +작업 전체가 아니라 +하나의 액티비티에서 작동합니다. 이것은 루트 액티비티를 포함한 모든 액티비티가 없어지게 +하기도 합니다. 이것을 {@code "true"}로 설정하면, +액티비티는 현재 세션에 대해서만 작업의 일부로 유지됩니다. 사용자가 작업을 떠났다가 +다시 돌아오면 이 작업은 더 이상 존재하지 않습니다.
+
+ + + + +

작업 시작하기

+ +

액티비티를 작업의 진입 지점으로 설정하려면 여기에 작업에서 지정한 대로 +{@code "android.intent.action.MAIN"}이 있는 인텐트 필터를 부여하고 +{@code "android.intent.category.LAUNCHER"}를 +지정된 카테고리로 설정하면 됩니다. 예:

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

이런 종류의 인텐트 필터를 사용하면 액티비티에 대한 아이콘과 레이블이 +애플리케이션 시작 관리자에 표시되어 사용자에게 액티비티를 시작할 방법을 부여하며, +액티비티를 시작하고 나면 이것이 생성한 작업에 언제든 돌아올 수 있게 됩니다. +

+ +

이 두 번째 능력이 중요합니다. 사용자는 작업을 떠났다가 이 액티비티 시작 관리자를 사용하여 나중에 작업에 +돌아올 수 있어야 합니다. 이러한 이유로, 액티비티가 항상 작업을 시작하는 것으로 표시하는 시작 +모드 두 가지, 즉 {@code "singleTask"}와 +{@code "singleInstance"}는 액티비티에 +{@link android.content.Intent#ACTION_MAIN} + 및 {@link android.content.Intent#CATEGORY_LAUNCHER} 필터가 있을 때에만 사용해야 합니다. 예를 들어 필터가 누락되면 다음과 같은 일이 +발생합니다. 어느 인텐트가 {@code "singleTask"} 액티비티를 시작하여 새 작업을 시작하고, +사용자가 이 작업에서 일하며 어느 정도 시간을 보냅니다. 그런 다음 사용자가 + 버튼을 누릅니다. 이제 이 작업은 배경으로 전송되었으며 눈에 보이지 않습니다. 이제 사용자가 작업으로 되돌아갈 +방법이 없어졌습니다. 이는 애플리케이션 시작 관리자에 표시되지 않기 때문입니다.

+ +

사용자가 액티비티로 되돌아갈 수 있도록 하는 것을 원치 않는 경우, +<activity> + 요소의 +{@code finishOnTaskLaunch} +를 {@code "true"}로 설정하면 됩니다(스택 지우기를 참조하십시오).

+ +

작업과 액티비티가 개요 화면에서 어떻게 표시되고 관리되는지에 대한 +자세한 정보는 +개요 화면에서 확인하실 수 있습니다.

+ + diff --git a/docs/html-intl/intl/ko/guide/index.jd b/docs/html-intl/intl/ko/guide/index.jd new file mode 100644 index 0000000000000000000000000000000000000000..73af3df9b8c0ffc8a6fc5d88685f8450bb98ed92 --- /dev/null +++ b/docs/html-intl/intl/ko/guide/index.jd @@ -0,0 +1,74 @@ +page.title=Android 소개 + +@jd:body + + + + +

Android는 풍성한 애플리케이션 프레임워크를 제공하여 Java 언어 환경에서 실행되는 +모바일 기기에서 사용할 혁신적인 앱과 게임을 구축할 수 있습니다. 왼쪽 탐색 영역에 목록으로 나열된 +여러 문서에서 Android의 다양한 API를 사용하여 앱을 구축하는 방법에 대한 상세한 정보를 제공합니다.

+ +

Android 개발을 처음 시도하신다면, 다음과 같은 +Android 앱 프레임워크 기본 개념을 숙지하는 것이 중요합니다.

+ + +
+ +
+ +

앱은 여러 개의 진입 지점을 제공합니다.

+ +

Android 앱은 여러 가지 고유한 구성 요소들의 조합으로 구축되며, 이러한 구성 요소는 개별적으로 +호출할 수도 있습니다. 예를 들어 어떤 하나의 액티비티가 사용자 인터페이스를 위한 +화면을 하나 제공하고, 서비스가 배경에서 독립적으로 작업을 수행할 +수 있습니다.

+ +

한 구성 요소에서 또 다른 구성 요소를 시작하려면 인텐트를 사용하면 됩니다. 심지어 다른 앱에서도 +구성 요소를 시작할 수 있습니다. 지도 앱에서 주소를 표시하는 액티비티를 시작하는 것이 좋은 예입니다. 이 모델은 +하나의 앱에 대한 여러 개의 진입 지점을 제공하여 어느 앱이라도 다른 여러 앱이 호출할 수 있는 작업에 대해 +사용자의 "기본" 앱 역할을 합니다.

+ + +

자세히 알아보기:

+ + +
+ + +
+ +

앱은 여러 가지 기기에 맞게 변경됩니다.

+ +

Android는 적응형 앱 프레임워크를 제공하여 여러 가지 기기 구성에 맞게 +고유한 리소스를 제공할 수 있습니다. 예를 들어, 여러 가지 화면 크기에 맞춰 각기 다른 XML +레이아웃 파일을 생성하면 시스템이 현재 기기의 화면 크기를 근거로 +어느 레이아웃을 적용할지 결정합니다.

+ +

앱 기능이 특정한 하드웨어(예: 카메라)를 필요로 하는 경우 런타임에 +기기 특징의 기능을 쿼리할 수 있습니다. 필요하다면 앱이 필요로 하는 기능을 선언할 수도 있습니다. +그러면 Google Play Store와 같은 앱 마켓에서 해당 기능을 지원하지 않는 기기에서 +설치를 허용하지 않습니다.

+ + +

자세히 알아보기:

+ + +
+ +
+ + + diff --git a/docs/html-intl/intl/ko/guide/topics/manifest/manifest-intro.jd b/docs/html-intl/intl/ko/guide/topics/manifest/manifest-intro.jd new file mode 100644 index 0000000000000000000000000000000000000000..c3550d0e7dc8bf2ebeaac90efff2c730feb48119 --- /dev/null +++ b/docs/html-intl/intl/ko/guide/topics/manifest/manifest-intro.jd @@ -0,0 +1,517 @@ +page.title=앱 매니페스트 +@jd:body + + + +

+ 모든 애플리케이션에는 루트 라이브러리에 AndroidManifest.xml 파일(정확히 +이 이름으로)이 있어야 합니다. 매니페스트 파일은 +Android 시스템에 대한 여러분의 앱 관련 필수 정보를 나타냅니다. +즉 앱의 코드를 실행하기 전에 시스템이 반드시 필요로 하는 정보를 +말합니다. 매니페스트가 하는 일에는 여러 가지가 있지만, 그 중에서 몇 가지만 소개하면 다음과 같습니다. +

+ +
    +
  • 애플리케이션에 대한 Java 패키지의 이름을 나타냅니다. +패키지 이름이 애플리케이션에 대한 고유한 식별자 역할을 합니다.
  • + +
  • 애플리케이션의 구성 요소를 설명합니다. 액티비티, +서비스, 브로드캐스트 수신기 및 콘텐츠 제공자 등 애플리케이션을 이루는 여러 항목을 +말합니다. 이것은 각 구성 요소를 구현하는 클래스의 이름을 나타내고 +각각의 기능을 게시합니다(예를 들어 처리할 수 있는 {@link android.content.Intent +Intent} 메시지 종류 등). 이러한 선언을 통해 Android 시스템이 여러 구성 요소가 +각각 무엇인지 알게 되고, 어떤 조건에서 시작해야 하는지 알 수 있습니다.
  • + +
  • 어느 프로세스가 애플리케이션 구성 요소를 호스팅할 것인지 결정합니다.
  • + +
  • API의 보호된 부분에 액세스하여 다른 애플리케이션과 상호 작용하려면 +애플리케이션에 어느 권한이 꼭 필요한지 선언합니다.
  • + +
  • 또한, 이 애플리케이션의 구성 요소와 상호 작용하려면 다른 애플리케이션이 +반드시 가지고 있어야 하는 권한도 선언합니다.
  • + +
  • 이는 애플리케이션이 실행 중일 때 프로파일링과 기타 정보를 제공하는 +{@link android.app.Instrumentation} 클래스를 목록으로 표시합니다. 이러한 선언이 매니페스트에 나타나는 것은 +애플리케이션이 개발 중이고 테스트되는 단계에만 국한됩니다. +이들은 애플리케이션이 게시되기 전에 제거됩니다.
  • + +
  • 이는 애플리케이션이 필요로 하는 Android API의 최소 레벨을 +선언합니다.
  • + +
  • 애플리케이션이 연결되어야 하는 라이브러리를 목록으로 표시합니다.
  • +
+ + +

매니페스트 파일의 구조

+ +

+아래의 다이어그램은 매니페스트 파일의 일반적인 구조와 매니페스트 파일에 +들어있을 수 있는 모든 요소를 표시한 것입니다. 각 요소와 각각의 속성을 모두 문서화한 +전문은 별도의 파일에서 확인하실 수 있습니다. 어떤 요소에 대해서든 +상세한 정보를 보려면 다이어그램에서 해당 요소 이름을 클릭하십시오. +이름은 다이어그램 뒤에 나오는 요소 목록(알파벳 순) 또는 +요소 이름이 언급되는 기타 영역 어디서든 클릭할 수 있습니다. +

+ +
+<?xml version="1.0" encoding="utf-8"?>
+
+<manifest>
+
+    <uses-permission />
+    <permission />
+    <permission-tree />
+    <permission-group />
+    <instrumentation />
+    <uses-sdk />
+    <uses-configuration />  
+    <uses-feature />  
+    <supports-screens />  
+    <compatible-screens />  
+    <supports-gl-texture />  
+
+    <application>
+
+        <activity>
+            <intent-filter>
+                <action />
+                <category />
+                <data />
+            </intent-filter>
+            <meta-data />
+        </activity>
+
+        <activity-alias>
+            <intent-filter> . . . </intent-filter>
+            <meta-data />
+        </activity-alias>
+
+        <service>
+            <intent-filter> . . . </intent-filter>
+            <meta-data/>
+        </service>
+
+        <receiver>
+            <intent-filter> . . . </intent-filter>
+            <meta-data />
+        </receiver>
+
+        <provider>
+            <grant-uri-permission />
+            <meta-data />
+            <path-permission />
+        </provider>
+
+        <uses-library />
+
+    </application>
+
+</manifest>
+
+ +

+매니페스트 파일에 표시될 수 있는 모든 요소는 아래에 알파벳 순서로 +목록으로 표시되어 있습니다. 합법적인 요소는 이들이 전부입니다. 개발자 나름대로 요소 또는 속성을 +추가해서는 안 됩니다. +

+ +

+<action> +
<activity> +
<activity-alias> +
<application> +
<category> +
<data> +
<grant-uri-permission> +
<instrumentation> +
<intent-filter> +
<manifest> +
<meta-data> +
<permission> +
<permission-group> +
<permission-tree> +
<provider> +
<receiver> +
<service> +
<supports-screens> +
<uses-configuration> +
<uses-feature> +
<uses-library> +
<uses-permission> +
<uses-sdk> +

+ + + + +

파일 규칙

+ +

+몇몇 규칙과 규정은 매니페스트 내의 모든 요소와 속성에 전반적으로 +적용됩니다. +

+ +
+
요소
+
필수 요소는 +<manifest> 및 +<application> 요소뿐으로, +이들은 각기 따로 표시되어야 하며 한 번씩만 발생할 수 있습니다. +나머지는 대부분 여러 번 발생할 수 있거나 전혀 발생하지 않기도 합니다. 다만, +그 중 최소한 몇몇은 있어야 매니페스트가 무엇이든 의미 있는 작업을 +달성할 수 있습니다. + +

+요소에 무엇이든 들어있기만 하면 다른 요소가 그 요소에 들어 있는 것입니다. +모든 값은 요소 내의 문자 데이터로서가 아니라 속성을 통해 설정됩니다. +

+ +

+같은 레벨에 있는 여러 요소는 보통 순서가 지정되지 않습니다. 예를 들어 +<activity>, +<provider> 및 +<service> +요소는 어떤 순서로든 서로 섞여도 됩니다 (이 규칙에서 +<activity-alias> +요소는 예외입니다. 이것은 +<activity>의 별칭이므로 +이를 반드시 따라야 합니다). +

+ +
속성
+
공식적인 의미에서 모든 속성은 선택 항목입니다. 그러나, 요소가 목적을 달성하기 +위해서 반드시 지정되어야 하는 것이 몇 가지 있습니다. 관련 문서를 +지침으로 사용하십시오. 정말로 선택적인 속성의 경우, 기본 값을 언급하거나 +사양이 없으면 어떤 일이 벌어지는지 진술합니다. + +

루트 +<manifest> +요소의 몇 가지 속성을 제외하고 모든 속성 이름은 {@code android:alwaysRetainTaskState} 접두사로 시작합니다. +예를 들어, {@code android:}와 같습니다. 이 접두사는 범용이기 때문에 +속성을 이름으로 참조하는 경우 관련 문서가 이를 생략하는 경우가 +일반적입니다.

+ +
클래스 이름 선언
+
대다수의 요소가 Java 객체에 상응합니다. 여기에는 +애플리케이션 자체에 대한 요소가 포함되며( +<application> +요소), 그것의 주 구성 요소도 포함됩니다. 즉, 액티비티 +(<activity>), +서비스 +(<service>), +브로드캐스트 수신기 +(<receiver>) 및 +콘텐츠 제공자 +(<provider>) 등이 이에 해당됩니다. + +

+하위 클래스를 정의하는 경우 구성 요소 클래스 +({@link android.app.Activity}, {@link android.app.Service}, +{@link android.content.BroadcastReceiver} 및 {@link android.content.ContentProvider})는 거의 항상 이렇게 하게 되는데, +이때 하위 클래스는 {@code name} 속성을 통해 선언됩니다. 이 이름에 반드시 +완전한 패키지 지정이 포함되어 있어야 합니다. +예를 들어, {@link android.app.Service} 하위 클래스를 선언하려면 다음과 같이 할 수 있습니다. +

+ +
<manifest . . . >
+    <application . . . >
+        <service android:name="com.example.project.SecretService" . . . >
+            . . .
+        </service>
+        . . .
+    </application>
+</manifest>
+ +

+그러나 일종의 줄임으로서 문자열의 첫 번째 글자가 마침표인 경우, 해당 +문자열은 애플리케이션의 패키지 이름에 추가됩니다( +<manifest> +요소의 +package +속성에서 지정한 바와 같이). 다음 할당은 위의 것과 같습니다. +

+ +
<manifest package="com.example.project" . . . >
+    <application . . . >
+        <service android:name=".SecretService" . . . >
+            . . .
+        </service>
+        . . .
+    </application>
+</manifest>
+ +

+Android는 구성 요소를 시작할 때 이름이 명명된 하위 클래스의 인스턴스를 생성합니다. +하위 클래스가 지정되지 않은 경우, 기본 클래스의 인스턴스를 생성합니다. +

+ +
여러 개의 값
+
하나 이상의 값을 지정할 수 있는 경우, 해당 요소는 +한 요소 안에 여러 개의 값을 목록으로 표시하기보다 거의 항상 반복됩니다. +예를 들어 한 인텐트 필터가 여러 개의 작업을 목록으로 표시할 수 있습니다. + +
<intent-filter . . . >
+    <action android:name="android.intent.action.EDIT" />
+    <action android:name="android.intent.action.INSERT" />
+    <action android:name="android.intent.action.DELETE" />
+    . . .
+</intent-filter>
+ +
리소스 값
+
몇몇 속성에는 사용자에게 표시될 수 있는 값이 있습니다. 예를 들어 +액티비티에 대한 레이블과 아이콘 등이 이에 해당됩니다. 이러한 속성의 값은 +지역화해야 하며 따라서 리소스나 테마에서 설정됩니다. 리소스 +값은 다음과 같은 형식으로 표현됩니다.

+ +

{@code @[패키지:]유형:이름}

+ +

+여기에서 패키지 이름은 리소스가 애플리케이션과 같은 패키지에 있으면 생략할 수 있고, + 유형 은 "문자열" 또는 "그릴 수 있음" 같은 리소스 유형입니다. 그리고 + 이름 은 특정 리소스를 식별하는 이름입니다. +예: +

+ +
<activity android:icon="@drawable/smallPic" . . . >
+ +

+테마에서 가져온 값도 비슷한 방식으로 표현되지만, 처음 부분에 '{@code ?}'를 사용합니다 +('{@code @}' 대신). +

+ +

{@code ?[패키지:]유형:이름} +

+ +
문자열 값
+
속성 값이 문자열인 경우, 이중 백슬래시('{@code \\}')를 사용하여 +문자 이스케이프를 수행해야 합니다. 예를 들어 줄바꿈에는 {@code \\n}, +유니코드 문자에는 '{@code \\uxxxx}'를 쓰십시오.
+
+ + +

파일 기능

+ +

+다음 섹션에서는 Android 기능을 매니페스트 파일에 반영하는 +몇 가지 방식을 설명합니다. +

+ + +

인텐트 필터

+ +

+애플리케이션의 핵심 구성 요소(액티비티, 서비스 및 브로드캐스트 +수신기)를 활성화하는 것은 인텐트입니다. 인텐트는 +원하는 작업을 설명하는 정보 묶음입니다({@link android.content.Intent} 객체). +여기에는 작업을 수행할 데이터, 작업을 수행할 구성 요소의 카테고리와 +기타 관련 지침 등이 포함됩니다. +Android는 인텐트에 응답할 적절한 구성 요소를 찾아 필요한 경우 구성 요소의 +새 인스턴스를 시작하고, 이것을 인텐트 객체에 +전달합니다. +

+ +

+구성 요소는 자신의 능력을 알립니다. 즉, 자신이 응답할 수 있는 +인텐트 종류를 밝힙니다. 이때 사용하는 것이 인텐트 필터입니다. Android 시스템은 +구성 요소를 시작하기 전에 해당 구성 요소가 처리할 수 있는 인텐트에 대해 학습해야 하기 때문에, +인텐트 필터는 매니페스트 파일에 +<intent-filter> +요소로 지정됩니다. 구성 요소 하나에 필터는 얼마든지 있을 수 있으며, 각각 서로 다른 기능을 +설명하게 됩니다. +

+ +

+대상 구성 요소를 명시적으로 지명하는 인텐트가 해당 구성 요소를 활성화합니다. +필터는 아무런 역할을 하지 않습니다. 하지만 대상을 이름으로 지정하지 않는 인텐트의 경우에는 +자신이 구성 요소의 필터 중 하나를 통과할 수 있을 때에만 해당 구성 요소를 활성화할 수 +있습니다. +

+ +

+인텐트 객체를 인텐트 필터에 대해 테스트하는 방법에 대한 자세한 방법은 +별도의 문서인 +인텐트 +및 인텐트 필터를 참조하십시오. +

+ + +

아이콘 및 레이블

+ +

+대다수의 요소에 {@code icon}과 {@code label} 속성이 있으며 +이것으로 사용자에게 표시될 수 있는 작은 아이콘과 텍스트 레이블을 나타냅니다. 몇몇 요소에는 +{@code description} 속성도 있어 좀 더 긴 설명 텍스트를 나타낼 수 있고, 이것 또한 화면에 +표시될 수 있습니다. 예를 들어 +<permission> +요소는 이와 같은 속성을 셋 모두 가지고 있어 사용자가 이를 요청한 애플리케이션에 대한 +권한을 허가할 것인지 여부를 물으면 해당 권한, +권한의 이름과 그에 수반되는 내용에 대한 설명을 +사용자에게 표시할 수 있습니다. +

+ +

+어떤 경우에든, 요소에서 설정된 아이콘과 레이블이 해당 컨테이너의 모든 하위 요소에 대한 기본 +{@code icon}과 {@code label} 설정이 됩니다. +따라서 +<application> +요소에서 설정된 아이콘과 레이블이 애플리케이션의 각 요소에 대한 기본 아이콘과 레이블입니다. +이와 유사하게, 구성 요소에 대해 설정된 아이콘과 레이블이 — 예를 들어 +<activity> +요소 — 각 구성 요소의 +<intent-filter> +요소에 대한 기본 설정입니다. +<application> +요소가 레이블을 설정하지만 액티비티와 그 인텐트 필터는 이를 설정하지 않는 경우, +애플리케이션 레이블을 액티비티와 인텐트 필터 양쪽 모두의 레이블인 것으로 +취급합니다. +

+ +

+인텐트 필터에 대해 설정된 아이콘과 레이블은 구성 요소가 사용자에게 +표시될 때마다 구성 요소를 나타내는 데 사용되며, 이는 필터가 알린 기능을 +충족하는 것입니다. 예를 들어 +"{@code android.intent.action.MAIN}" 및 +"{@code android.intent.category.LAUNCHER}"가 설정된 필터는 +액티비티를 애플리케이션을 초기화하는 주역으로 알립니다. 다시 말해, +애플리케이션 시작 관리자에 표시되어야 하는 것이 됩니다. 따라서 필터에서 설정된 아이콘과 레이블이 +시작 관리자에 표시되는 아이콘과 레이블입니다. +

+ + +

권한

+ +

+통상 권한 이란 기기에서 코드의 일부분 또는 데이터에 대한 액세스를 한정하는 +제한입니다. 이런 한계를 부과하는 것은 중요한 데이터와 코드를 보호하여 +이들이 남용되어서 사용자 환경을 왜곡하거나 손상시키지 않도록 하기 위해서입니다. +

+ +

+각 권한은 고유한 레이블로 식별할 수 있습니다. 레이블을 보면 자신이 어떤 작업을 제한하는지 +나타내는 경우가 잦습니다. 예를 들어 다음은 Android가 정의하는 몇 가지 권한을 나타낸 +것입니다. +

+ +

{@code android.permission.CALL_EMERGENCY_NUMBERS} +
{@code android.permission.READ_OWNER_DATA} +
{@code android.permission.SET_WALLPAPER} +
{@code android.permission.DEVICE_POWER}

+ +

+하나의 기능을 보호하는 데에는 권한 하나면 충분합니다. +

+ +

+애플리케이션에서 권한으로 보호하는 기능에 액세스해야 하는 경우, +해당 권한이 필요하다고 매니페스트의 +<uses-permission> +요소로 선언해야 합니다. 그런 다음, 해당 애플리케이션이 기기에 설치되고 나면 +설치 관리자가 요청한 권한을 허가할지 여부를 판별합니다. +이때 애플리케이션의 인증서를 서명한 권한을 확인하고 어떤 경우에는 사용자에게 +묻기도 합니다. +권한이 허가되면 해당 애플리케이션은 보호된 기능을 사용할 수 +있습니다. 허가되지 않으면, 그러한 기능에 액세스하려는 애플리케이션의 시도가 단순히 실패하고 사용자에게는 +아무런 알림도 표시되지 않습니다. +

+ +

+애플리케이션은 권한을 사용하여 자신의 구성 요소를(액티비티, 서비스, +브로드캐스트 수신기 및 콘텐츠 제공자) 보호할 수도 있습니다. Android가 정의한 +권한이라면 어떤 것이든 사용할 수 있고( +{@link android.Manifest.permission android.Manifest.permission}에 목록으로 나열), +아니면 다른 애플리케이션이 선언한 권한을 사용해도 됩니다. 아예 직접 자신만의 권한을 정의해도 됩니다. 새 권한을 선언할 때에는 + +<permission> +요소를 사용합니다. 예를 들어 액티비티를 보호하려면 다음과 같이 하면 됩니다. +

+ +
+<manifest . . . >
+    <permission android:name="com.example.project.DEBIT_ACCT" . . . />
+    <uses-permission android:name="com.example.project.DEBIT_ACCT" />
+    . . .
+    <application . . .>
+        <activity android:name="com.example.project.FreneticActivity"
+                  android:permission="com.example.project.DEBIT_ACCT"
+                  . . . >
+            . . .
+        </activity>
+    </application>
+</manifest>
+
+ +

+이 예시에서는 {@code DEBIT_ACCT} 권한이 + +<permission> +요소로 선언하였을 뿐만 아니라, 해당 권한의 사용 또한 +<uses-permission> +요소로 요청되었다는 점을 눈여겨 보십시오. 이것의 사용을 요청해야 애플리케이션의 다른 구성 요소가 보호된 +액티비티를 시작할 수 있습니다. 이는 해당 보호를 애플리케이션 자신이 부과한 것이더라도 +관계 없이 적용됩니다. +

+ +

+같은 예시에서, {@code permission} 속성이 다른 곳에서 +선언한 권한에 설정된 경우 +(예: {@code android.permission.CALL_EMERGENCY_NUMBERS}), 이것을 + +<permission> +요소를 사용하여 다시 선언할 필요가 없습니다. 하지만 해당 권한의 사용은 여전히 +<uses-permission>로 요청해야 합니다. +

+ +

+ +<permission-tree> +요소는 코드로 정의될 권한 그룹에 대한 네임스페이스를 +선언합니다. 그리고 +<permission-group> +가 권한 집합에 대한 레이블을 정의합니다(매니페스트에 +<permission> +요소로 선언한 것과 다른 곳에서 선언한 것 양쪽 모두). 이것은 권한이 사용자에게 표시될 때 +그룹 지정될 방식에만 영향을 미칩니다. +<permission-group> +요소는 그룹에 어느 권한이 속해 있는지 지정하는 것이 아니라, 그저 +그룹에 이름을 부여할 뿐입니다. 그룹에 권한을 배치하려면 그룹 이름을 + +<permission> +요소의 +permissionGroup +속성에 할당하면 됩니다. +

+ + +

라이브러리

+ +

+모든 애플리케이션은 기본 Android 라이브러리에 연결되어 있습니다. 여기에는 +애플리케이션 구축을 위한 기본적인 패키지(액티비티, 서비스, +인텐트, 보기, 버튼, 애플리케이션, ContentProvider 등 보편적인 클래스 포함)가 포함되어 +있습니다. +

+ +

+그러나 패키지 가운데에는 자신만의 라이브러리에 속한 것도 있습니다. 애플리케이션이 +사용하는 코드의 출처가 이러한 패키지 가운데 어느 한 가지에 해당되는 경우, 해당 패키지에 연결되도록 +명시적으로 요청해야만 합니다. 매니페스트에는 별도의 +<uses-library> +요소가 들어 있어 각 라이브러리의 이름을 나타내야 합니다 (라이브러리 이름은 패키지에 대한 +관련 문서에서 찾을 수 있습니다). +

diff --git a/docs/html-intl/intl/ko/guide/topics/providers/calendar-provider.jd b/docs/html-intl/intl/ko/guide/topics/providers/calendar-provider.jd new file mode 100644 index 0000000000000000000000000000000000000000..4d69b6029ec4907f3faa3b19a196dd1d84048fc1 --- /dev/null +++ b/docs/html-intl/intl/ko/guide/topics/providers/calendar-provider.jd @@ -0,0 +1,1184 @@ +page.title=캘린더 제공자 +@jd:body + +
+
+

이 문서의 내용

+
    +
  1. 기본 정보
  2. +
  3. 사용자 권한
  4. +
  5. 캘린더 테이블 +
      +
    1. 캘린더 쿼리
    2. +
    3. 캘린더 수정
    4. +
    5. 캘린더 삽입
    6. +
    +
  6. +
  7. 이벤트 테이블 +
      +
    1. 이벤트 추가
    2. +
    3. 이벤트 업데이트
    4. +
    5. 이벤트 삭제
    6. +
    +
  8. +
  9. 참석자 테이블 +
      +
    1. 참석자 추가
    2. +
    +
  10. +
  11. 알림 테이블 +
      +
    1. 알림 추가
    2. +
    +
  12. +
  13. 인스턴스 테이블 +
      +
    1. 인스턴스 테이블 쿼리
    2. +
  14. +
  15. 캘린더 인텐트 +
      +
    1. 인텐트를 사용하여 이벤트 삽입
    2. +
    3. 인텐트를 사용하여 이벤트 편집
    4. +
    5. 인텐트를 사용하여 캘린더 데이터 보기
    6. +
    +
  16. + +
  17. 동기화 어댑터
  18. +
+ +

Key 클래스

+
    +
  1. {@link android.provider.CalendarContract.Calendars}
  2. +
  3. {@link android.provider.CalendarContract.Events}
  4. +
  5. {@link android.provider.CalendarContract.Attendees}
  6. +
  7. {@link android.provider.CalendarContract.Reminders}
  8. +
+
+
+ +

캘린더 제공자는 사용자의 캘린더 이벤트를 저장하는 리포지토리입니다. +캘린더 제공자 API를 사용하면 캘린더, 이벤트, 참석자, 알림 등의 쿼리, 삽입, 업데이트 및 +삭제 등의 작업을 수행할 수 있습니다.

+ + +

캘린더 제공자 API는 애플리케이션과 동기화 어댑터에서 사용할 수 있습니다. +어떤 유형의 프로그램이 호출을 하는 주체인지에 따라 규칙이 각기 다릅니다. +이 문서는 주로 캘린더 제공자 API를 애플리케이션으로 사용하는 것에 주안점을 두었습니다. +여러 동기화 어댑터가 서로 어떻게 다른지 논의한 내용은 +동기화 어댑터를 참조하십시오.

+ + +

캘린더 데이터를 읽거나 쓰려면 보통 애플리케이션의 매니페스트에 +적절한 권한이 포함되어 있어야 합니다. 이는 사용자 +권한에 설명되어 있습니다. 공통 작업을 쉽게 수행하기 위해 캘린더 +제공자는 캘린더 +인텐트에 설명된 바와 같이 인텐트 집합을 제공합니다. 이와 같은 인텐트는 사용자를 캘린더 애플리케이션으로 이동시켜 +이벤트 삽입, 보기 및 편집을 할 수 있게 해줍니다. 사용자는 캘린더 애플리케이션과 상호 작용한 다음 +원래 애플리케이션으로 돌아옵니다. 따라서, 여러분의 애플리케이션이 이벤트를 보거나 +생성하기 위해 권한 허가를 요청할 필요도 없고 사용자 인터페이스를 제공할 필요도 없는 것입니다.

+ +

기본 정보

+ +

콘텐츠 제공자는 데이터를 저장하여 애플리케이션에서 +이에 액세스할 수 있도록 합니다. 일반적으로, Android 플랫폼에서 제공하는 콘텐츠 제공자(캘린더 제공자 포함)는 +관계 데이터베이스 모델에 기초하여 테이블 집합으로 데이터를 노출합니다. 이 모델에서 각 행은 레코드이고, +각 열은 특정한 유형과 의미를 가진 데이터입니다. 애플리케이션과 동기화 어댑터는 +캘린더 제공자 API를 통해 사용자의 캘린더 데이터를 보관하고 있는 데이터베이스 테이블에 +읽기/쓰기 액세스 권한을 얻을 수 있습니다.

+ +

모든 콘텐츠 제공자는 데이터 세트를 고유하게 식별하는 공개 URI( +{@link android.net.Uri} +개체로 래핑됨)를 노출합니다. 여러 데이터 세트(여러 테이블)를 제어하는 콘텐츠 제공자는 +각 데이터 세트에 별도의 URI를 노출합니다. +제공자에 대한 URI는 모두 문자열 "content://"로 시작합니다. +이것을 보면 데이터를 콘텐츠 제공자가 제어하고 있다는 것을 알아볼 수 있습니다. +캘린더 제공자가 각각의 클래스(테이블)에 대한 URI의 상수를 정의합니다. +이와 같은 URI는 <class>.CONTENT_URI 형식을 취합니다. +예를 들면 다음과 같습니다. {@link android.provider.CalendarContract.Events#CONTENT_URI Events.CONTENT_URI}

+ +

그림 1은 캘린더 제공자 데이터 모델을 그림으로 나타낸 것입니다. +이 그림에는 메인 테이블과이들을 서로 연결하는 필드가 표시되어 있습니다.

+ +Calendar Provider Data Model +

그림 1. 캘린더 제공자 데이터 모델.

+ +

한 사용자가 여러 개의 캘린더를 가질 수 있으며, 여러 가지 캘린더는 각기 다른 유형의 계정(Google 캘린더, Exchange 등)과 연결될 수 있습니다.

+ +

{@link android.provider.CalendarContract}가 캘린더의 데이터 모델과 이벤트 관련 정보를 정의합니다. 이 데이터는 아래에 나열한 것과 같은 여러 테이블에 저장됩니다.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
테이블(클래스)설명

{@link android.provider.CalendarContract.Calendars}

이 테이블에는 캘린더별 정보가 담겨 있습니다. + 이 테이블의 행마다 한 캘린더의 세부 정보, +예를 들어 이름, 색상, 동기화 정보 등이 들어갑니다.
{@link android.provider.CalendarContract.Events}이 테이블에는 이벤트별 정보가 담겨 있습니다. + 이 테이블의 행마다 한 이벤트의 세부 정보 +예를 들어 이벤트 제목, 위치, 시작 시간, 종료 시간 등의 정보가 들어갑니다. + 이벤트는 일회성일 수도 있고 여러 번 반복될 수도 있습니다. +참석자, 알림 및 확장된 속성 등은 별도의 테이블에 저장됩니다. +이들 테이블에는 각기 {@link android.provider.CalendarContract.AttendeesColumns#EVENT_ID}가 있어 +이벤트 테이블의 {@link android.provider.BaseColumns#_ID}를 참조합니다.
{@link android.provider.CalendarContract.Instances}이 테이블에는 각 이벤트 발생의 시작 시간과 종료 시간이 +담겨 있습니다. 이 테이블의 각 행이 하나의 이벤트 발생을 나타냅니다. + 일회성 이벤트의 경우, 이벤트에 대한 1:1 인스턴스 매핑이 있습니다. + 반복되는 이벤트의 경우, 해당 이벤트가 여러 번 발생하는 것에 맞추어 +자동으로 여러 행이 생성됩니다.
{@link android.provider.CalendarContract.Attendees}이 테이블에는 이벤트 참석자(게스트) 정보가 담겨 있습니다. + 각 행이 주어진 이벤트의 게스트 한 사람을 나타냅니다. + 이것이 게스트의 유형과, 이벤트에 대한 해당 게스트의 참석 여부 응답을 +나타냅니다.
{@link android.provider.CalendarContract.Reminders}이 테이블에는 경고/알림 데이터가 담겨 있습니다. + 각 행이 주어진 이벤트에 대한 경고 하나를 나타냅니다. +이벤트 하나에 여러 개의 알림이 있을 수 있습니다. 이벤트당 최대 알림 개수는 + +{@link android.provider.CalendarContract.CalendarColumns#MAX_REMINDERS}에서 지정되고, +이는 주어진 캘린더를 소유한 동기화 어댑터가 설정합니다. + 알림은 이벤트 몇 분 전에 지정되며 사용자에게 어떻게 경고할 것인지를 +결정하는 메서드를 가지고 있습니다.
+ +

캘린더 제공자 API는 유연성과 강력함을 염두에 두고 만들어진 것입니다. +그와 동시에 우수한 최종 사용자 경험을 제공하고 캘린더와 그 데이터의 +무결성을 보호하는 것 또한 중요합니다. 이를 위해서 +API를 사용할 때 유념해야 할 사항은 다음과 같습니다.

+ +
    + +
  • 캘린더 이벤트 삽입, 업데이트 및 보기. 캘린더 제공자로부터 직접 이벤트를 삽입, 변경하고 읽으려면 적절한 권한이 필요합니다. 그러나, 완전한 캘린더 애플리케이션 또는 동기화 어댑터를 구축하는 경우가 아니라면 이와 같은 권한을 요청할 필요가 없습니다. 대신 Android의 캘린더 애플리케이션이 지원하는 인텐트를 사용하여 해당 애플리케이션에 읽기 및 쓰기 작업을 분배하면 됩니다. 인텐트를 사용하면, 애플리케이션이 사용자를 캘린더 애플리케이션으로 보내 사전에 작성된 양식으로 원하는 작업을 +수행하게 합니다. 작업이 끝나면 사용자는 애플리케이션으로 돌아옵니다. +캘린더를 통해 공통 작업을 수행하도록 애플리케이션을 설계함으로써 사용자에게 일관되고 강력한 +사용자 인터페이스를 제공하는 것입니다. 이것이 권장 방법입니다. + 자세한 정보는 캘린더 +인텐트를 참조하십시오.

    + + +
  • 동기화 어댑터. +동기화 어댑터는 사용자의 기기에 있는 캘린더 데이터를 다른 서버 또는 데이터 소스와 동기화합니다. +{@link android.provider.CalendarContract.Calendars}와 +{@link android.provider.CalendarContract.Events} 테이블에는 +동기화 어댑터가 사용하도록 예약된 열이 있습니다. +제공자와 애플리케이션은 이를 수정해서는 안 됩니다. 사실, 동기화 어댑터로 액세스하지 않는 한 +이 열이 표시되지 않습니다. +동기화 어댑터에 대한 자세한 정보는 동기화 어댑터를 참조하십시오.
  • + +
+ + +

사용자 권한

+ +

캘린더 데이터를 읽으려면 애플리케이션의 매니페스트 파일에 {@link +android.Manifest.permission#READ_CALENDAR} 권한이 포함되어 있어야 합니다. +캘린더 데이터를 삭제, 삽입 또는 업데이트하려면{@link android.Manifest.permission#WRITE_CALENDAR} +권한이 포함되어 있어야 합니다.

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"...>
+    <uses-sdk android:minSdkVersion="14" />
+    <uses-permission android:name="android.permission.READ_CALENDAR" />
+    <uses-permission android:name="android.permission.WRITE_CALENDAR" />
+    ...
+</manifest>
+
+ + +

캘린더 테이블

+ +

{@link android.provider.CalendarContract.Calendars} +테이블에는 각각의 캘린더에 대한 세부 정보가 들어 있습니다. +다음 캘린더 열은 애플리케이션과 동기화 어댑터 모두 쓸 수 있는 것입니다. +지원되는 필드의 전체 목록은 +{@link android.provider.CalendarContract.Calendars} 참조를 확인하십시오.

+ + + + + + + + + + + + + + + + + + + + + + + + + +
상수설명
{@link android.provider.CalendarContract.Calendars#NAME}캘린더 이름입니다.
{@link android.provider.CalendarContract.Calendars#CALENDAR_DISPLAY_NAME}사용자에게 표시되는 이 캘린더의 이름입니다.
{@link android.provider.CalendarContract.Calendars#VISIBLE}캘린더를 표시하기로 선택했는지를 나타내는 부울입니다. +값이 0이면 이 캘린더와 연관된 이벤트는 표시하면 안 된다는 뜻입니다. + 값이 1이면 이 캘린더와 연관된 이벤트를 표시해야 한다는 뜻입니다. + 이 값이 {@link +android.provider.CalendarContract.Instances} 테이블의 행 생성에 영향을 미칩니다.
{@link android.provider.CalendarContract.CalendarColumns#SYNC_EVENTS}캘린더를 동기화하고 이 캘린더의 이벤트를 기기에 저장해야할지를 +나타내는 부울입니다. 값이 0이면 이 캘린더를 동기화하거나 이에 속한 이벤트를 +기기에 저장하면 안 된다는 뜻입니다. 값이 1이면 이 캘린더에 대한 이벤트를 동기화하고 이에 속한 +이벤트를 기기에 저장하라는 뜻입니다.
+ +

캘린더 쿼리

+ +

다음은 특정한 사용자가 소유한 캘린더를 가져오는 법을 나타낸 예시입니다. + 이 예시에서는 단순하게 나타내기 위해 쿼리 작업을 사용자 인터페이스 스레드("주 스레드")에 표시했습니다. + 실제로는, 이 작업은 주 스레드 대신 비동기화 스레드에서 해야 합니다. + 자세한 논의는 +로더를 참조하십시오. 데이터를 읽기만 하는 것이 아니라 변경도 하는 경우라면, +{@link android.content.AsyncQueryHandler}를 참조하십시오. +

+ + +
+// Projection array. Creating indices for this array instead of doing
+// dynamic lookups improves performance.
+public static final String[] EVENT_PROJECTION = new String[] {
+    Calendars._ID,                           // 0
+    Calendars.ACCOUNT_NAME,                  // 1
+    Calendars.CALENDAR_DISPLAY_NAME,         // 2
+    Calendars.OWNER_ACCOUNT                  // 3
+};
+  
+// The indices for the projection array above.
+private static final int PROJECTION_ID_INDEX = 0;
+private static final int PROJECTION_ACCOUNT_NAME_INDEX = 1;
+private static final int PROJECTION_DISPLAY_NAME_INDEX = 2;
+private static final int PROJECTION_OWNER_ACCOUNT_INDEX = 3;
+ + + + + +

다음 예시에서는 여러분이 직접 나름의 쿼리를 생성해보십시오. 선택 영역이 쿼리의 기준을 나타냅니다. + 이 예시에서 쿼리는 +ACCOUNT_NAME +"sampleuser@google.com", ACCOUNT_TYPE +"com.google" 및 OWNER_ACCOUNT +"sampleuser@google.com"을 가지고 있는 캘린더를 찾고 있습니다. 사용자가 소유한 캘린더뿐만 아니라 사용자가 전에 본 캘린더까지 모두 확인하려면 +OWNER_ACCOUNT를 생략합니다. +쿼리가 {@link android.database.Cursor} +개체를 반환하여 이를 시용하면 데이터베이스 쿼리가 반환한 결과 집합을 트래버스할 수 있습니다. + 콘텐츠 제공자에서 쿼리를 사용하는 법에 대한 자세한 논의는 +콘텐츠 제공자를 참조하십시오.

+ + +
// Run query
+Cursor cur = null;
+ContentResolver cr = getContentResolver();
+Uri uri = Calendars.CONTENT_URI;   
+String selection = "((" + Calendars.ACCOUNT_NAME + " = ?) AND (" 
+                        + Calendars.ACCOUNT_TYPE + " = ?) AND ("
+                        + Calendars.OWNER_ACCOUNT + " = ?))";
+String[] selectionArgs = new String[] {"sampleuser@gmail.com", "com.google",
+        "sampleuser@gmail.com"}; 
+// Submit the query and get a Cursor object back. 
+cur = cr.query(uri, EVENT_PROJECTION, selection, selectionArgs, null);
+ +

다음에 표시된 섹션에서는 커서를 사용하여 결과 집합을 단계별로 살펴봅니다. +여기에서는 예시의 시작 부분에서 설정된 상수를 사용하여 각 필드에 대한 값을 반환합니다. +

+ +
// Use the cursor to step through the returned records
+while (cur.moveToNext()) {
+    long calID = 0;
+    String displayName = null;
+    String accountName = null;
+    String ownerName = null;
+      
+    // Get the field values
+    calID = cur.getLong(PROJECTION_ID_INDEX);
+    displayName = cur.getString(PROJECTION_DISPLAY_NAME_INDEX);
+    accountName = cur.getString(PROJECTION_ACCOUNT_NAME_INDEX);
+    ownerName = cur.getString(PROJECTION_OWNER_ACCOUNT_INDEX);
+              
+    // Do something with the values...
+
+   ...
+}
+
+ +

캘린더 수정

+ +

캘린더 업데이트를 수행하려면 캘린더의 {@link +android.provider.BaseColumns#_ID}를 +URI에 추가된 ID로 + +({@link android.content.ContentUris#withAppendedId(android.net.Uri,long) withAppendedId()}), 또는 첫 번째 선택 항목으로 제공하면 됩니다. + +선택은 "_id=?"로 시작해야 하며, 첫 번째 +selectionArg는 캘린더의 {@link +android.provider.BaseColumns#_ID}여야 합니다. +또한 ID를 URI에 인코딩해서도 업데이트를 수행할 수 있습니다. 이 예시에서는 캘린더의 표시 이름을 + +({@link android.content.ContentUris#withAppendedId(android.net.Uri,long) withAppendedId()}) +방식으로 변경하였습니다.

+ +
private static final String DEBUG_TAG = "MyActivity";
+...
+long calID = 2;
+ContentValues values = new ContentValues();
+// The new display name for the calendar
+values.put(Calendars.CALENDAR_DISPLAY_NAME, "Trevor's Calendar");
+Uri updateUri = ContentUris.withAppendedId(Calendars.CONTENT_URI, calID);
+int rows = getContentResolver().update(updateUri, values, null, null);
+Log.i(DEBUG_TAG, "Rows updated: " + rows);
+ +

캘린더 삽입

+ +

캘린더는 주로 동기화 어댑터가 관리하도록 설계되어 있습니다. 따라서 새 캘린더는 +동기화 어댑터로서만 삽입해야 합니다. 대다수의 경우 애플리케이션은 캘린더에 +표면적인 사항만 변경할 수 있게 되어 있습니다(예: 표시 이름 변경 등). +어떤 애플리케이션이 로컬 캘린더를 생성해야 하는 경우, 캘린더 삽입을 동기화 어댑터로 수행하면 됩니다. +이때 {@link +android.provider.CalendarContract#ACCOUNT_TYPE_LOCAL}의 {@link +android.provider.CalendarContract.SyncColumns#ACCOUNT_TYPE}을 사용합니다. +{@link android.provider.CalendarContract#ACCOUNT_TYPE_LOCAL} +은 기기 계정과 연관되지 않은 캘린더에 적용되는 특별한 유형의 계정입니다. + 이 유형의 캘린더는 서버에 동기화되지 않습니다. +동기화 어댑터에 대한 논의는 동기화 어댑터를 참조하십시오.

+ +

이벤트 테이블

+ +

{@link android.provider.CalendarContract.Events} +테이블에는 각각의 이벤트에 대한 세부 정보가 들어 있습니다. 이벤트를 추가, 업데이트 또는 삭제하려면 애플리케이션의 +매니페스트 파일에 {@link android.Manifest.permission#WRITE_CALENDAR} +권한이 포함되어 있어야 합니다.

+ +

다음 이벤트 열은 애플리케이션과 +동기화 어댑터 모두 쓸 수 있는 것입니다. 지원되는 필드의 전체 목록은 {@link +android.provider.CalendarContract.Events} 참조를 확인하십시오.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
상수설명
{@link android.provider.CalendarContract.EventsColumns#CALENDAR_ID}이벤트가 속한 캘린더의 {@link android.provider.BaseColumns#_ID}입니다.
{@link android.provider.CalendarContract.EventsColumns#ORGANIZER}이벤트 조직자(소유자)의 이메일입니다.
{@link android.provider.CalendarContract.EventsColumns#TITLE}이벤트 제목입니다.
{@link android.provider.CalendarContract.EventsColumns#EVENT_LOCATION}이벤트가 일어나는 장소입니다.
{@link android.provider.CalendarContract.EventsColumns#DESCRIPTION}이벤트 설명입니다.
{@link android.provider.CalendarContract.EventsColumns#DTSTART}이벤트가 시작되는 시간을 Epoch 이후 UTC 밀리초 단위로 나타낸 것입니다.
{@link android.provider.CalendarContract.EventsColumns#DTEND}이벤트가 종료되는 시간을 Epoch 이후 UTC 밀리초 단위로 나타낸 것입니다.
{@link android.provider.CalendarContract.EventsColumns#EVENT_TIMEZONE}이벤트의 표준 시간대입니다.
{@link android.provider.CalendarContract.EventsColumns#EVENT_END_TIMEZONE}이벤트 종료 시간의 표준 시간대입니다.
{@link android.provider.CalendarContract.EventsColumns#DURATION}이벤트 기간을 RFC5545 형식으로 나타낸 것입니다. +예를 들어 "PT1H" 값을 보면 이벤트가 한 시간 지속될 것임을 알 수 있고, +"P2W"는 2주의 지속 기간을 나타냅니다. +
{@link android.provider.CalendarContract.EventsColumns#ALL_DAY}값이 1이면 이 이벤트가 현지 시간대에서 정의한 바에 의해 하루 종일 걸린다는 것을 나타냅니다. + 값이 0이면 이것이 하루 중 언제라도 시작하고 종료될 수 있는 정기 이벤트라는 것을 나타냅니다. +
{@link android.provider.CalendarContract.EventsColumns#RRULE}이벤트 형식의 반복 규칙입니다. +예를 들면 다음과 같습니다. "FREQ=WEEKLY;COUNT=10;WKST=SU" 더 많은 예시를 확인하려면 +여기를 참조하십시오.
{@link android.provider.CalendarContract.EventsColumns#RDATE}이벤트의 반복 날짜입니다. +일반적으로 {@link android.provider.CalendarContract.EventsColumns#RDATE} +를 {@link android.provider.CalendarContract.EventsColumns#RRULE} +과 함께 사용하여 반복되는 발생의 집계 집합을 정의하게 됩니다. + 자세한 논의는 RFC5545 사양을 참조하십시오.
{@link android.provider.CalendarContract.EventsColumns#AVAILABILITY}이 이벤트가 사용 중인 시간으로 간주되는지, 다시 일정을 예약할 수 있는 자유 시간으로 간주되는지를 나타냅니다. +
{@link android.provider.CalendarContract.EventsColumns#GUESTS_CAN_MODIFY}게스트가 이벤트를 수정할 수 있는지를 나타냅니다.
{@link android.provider.CalendarContract.EventsColumns#GUESTS_CAN_INVITE_OTHERS}게스트가 다른 게스트를 초대할 수 있는지를 나타냅니다.
{@link android.provider.CalendarContract.EventsColumns#GUESTS_CAN_SEE_GUESTS}게스트가 참석자 목록을 볼 수 있는지를 나타냅니다.
+ +

이벤트 추가

+ +

애플리케이션이 새 이벤트를 추가하는 경우, +{@link android.content.Intent#ACTION_INSERT INSERT} 인텐트를 사용하는 것이 좋습니다. 이때 인텐트를 사용하여 이벤트 삽입에서 설명한 대로 따릅니다. 그러나, 필요한 경우 직접 이벤트를 삽입해도 됩니다. + 이 섹션에서는 이렇게 하는 방법을 설명합니다. +

+ + +

다음은 새 이벤트를 삽입할 때 따라야 하는 규칙입니다.

+
    + +
  • {@link +android.provider.CalendarContract.EventsColumns#CALENDAR_ID}와 {@link +android.provider.CalendarContract.EventsColumns#DTSTART}를 포함해야 합니다.
  • + +
  • {@link +android.provider.CalendarContract.EventsColumns#EVENT_TIMEZONE}을 포함해야 합니다. +시스템에 설치된 표준 시간대 ID 목록을 가져오려면 {@link +java.util.TimeZone#getAvailableIDs()}를 사용하십시오. 이 규칙은 인텐트를 사용하여 이벤트 삽입에서 설명한 바와 같이 +{@link +android.content.Intent#ACTION_INSERT INSERT} 인텐트를 통해서 이벤트를 삽입할 경우에는 적용되지 않습니다. 이 시나리오의 경우, +기본 시간대가 제공됩니다.
  • + +
  • 비반복적인 이벤트의 경우, {@link +android.provider.CalendarContract.EventsColumns#DTEND}를 포함해야 합니다.
  • + + +
  • 반복적인 이벤트의 경우 {@link +android.provider.CalendarContract.EventsColumns#DURATION}과 {@link +android.provider.CalendarContract.EventsColumns#RRULE} 또는 {@link +android.provider.CalendarContract.EventsColumns#RDATE}를 포함해야 합니다. 이 규칙은 인텐트를 사용하여 이벤트 삽입에서 설명한 바와 같이 +{@link +android.content.Intent#ACTION_INSERT INSERT} 인텐트를 통해서 이벤트를 삽입할 경우에는 적용되지 않습니다. +이 시나리오에서는 {@link android.provider.CalendarContract.EventsColumns#DTSTART} 및 {@link android.provider.CalendarContract.EventsColumns#DTEND}와 함께 {@link +android.provider.CalendarContract.EventsColumns#RRULE}를 사용할 수 있고, 캘린더 애플리케이션이 이것을 기간으로 자동 변환해줍니다. +
  • + +
+ +

다음은 이벤트 삽입의 예입니다. 단순하게 나타내기 위해 UI 스레드에서 수행한 것입니다. + 실제로, 삽입과 업데이트는 비동기화 스레드에서 수행해야 작업을 배경 스레드로 이동시킬 수 있습니다. + +자세한 정보는 {@link android.content.AsyncQueryHandler}를 참조하십시오.

+ + +
+long calID = 3;
+long startMillis = 0; 
+long endMillis = 0;     
+Calendar beginTime = Calendar.getInstance();
+beginTime.set(2012, 9, 14, 7, 30);
+startMillis = beginTime.getTimeInMillis();
+Calendar endTime = Calendar.getInstance();
+endTime.set(2012, 9, 14, 8, 45);
+endMillis = endTime.getTimeInMillis();
+...
+
+ContentResolver cr = getContentResolver();
+ContentValues values = new ContentValues();
+values.put(Events.DTSTART, startMillis);
+values.put(Events.DTEND, endMillis);
+values.put(Events.TITLE, "Jazzercise");
+values.put(Events.DESCRIPTION, "Group workout");
+values.put(Events.CALENDAR_ID, calID);
+values.put(Events.EVENT_TIMEZONE, "America/Los_Angeles");
+Uri uri = cr.insert(Events.CONTENT_URI, values);
+
+// get the event ID that is the last element in the Uri
+long eventID = Long.parseLong(uri.getLastPathSegment());
+// 
+// ... do something with event ID
+//
+//
+ +

참고: 이벤트가 생성된 다음 이 예시가 이벤트 ID를 캡처하는 법을 눈여겨 보십시오. + 이것이 이벤트 ID를 가져오는 가장 쉬운 방법입니다. +다른 캘린더 작업을 수행하기 위해 이벤트 ID가 필요한 경우가 자주 있습니다. 예를 들어 이벤트에 참석자나 알림을 추가하는 데 필요합니다. +

+ + +

이벤트 업데이트

+ +

애플리케이션이 사용자에게 이벤트 편집을 허용할 경우, 인텐트로 이벤트 편집에서 설명한 바와 같이 +{@link android.content.Intent#ACTION_EDIT EDIT} 인텐트 +를 사용하는 것이 좋습니다. +그러나 필요한 경우 직접 이벤트를 편집해도 됩니다. +이벤트 업데이트를 수행하려면 이벤트의 _ID를 URI에 추가된 ID로({@link +android.content.ContentUris#withAppendedId(android.net.Uri,long) withAppendedId()}) +또는 첫 번째 선택 항목으로 제공하면 됩니다. + +선택은 "_id=?"로 시작해야 하며, 첫 번째 +selectionArg는 이벤트의 _ID여야 합니다. +ID 없이 선택을 사용해도 업데이트를 수행할 수 있습니다. 다음은 이벤트 업데이트의 예입니다. + 여기에서는 이벤트 제목을 변경할 때 +{@link android.content.ContentUris#withAppendedId(android.net.Uri,long) withAppendedId()} + 방법을 사용합니다.

+ + +
private static final String DEBUG_TAG = "MyActivity";
+...
+long eventID = 188;
+...
+ContentResolver cr = getContentResolver();
+ContentValues values = new ContentValues();
+Uri updateUri = null;
+// The new title for the event
+values.put(Events.TITLE, "Kickboxing"); 
+updateUri = ContentUris.withAppendedId(Events.CONTENT_URI, eventID);
+int rows = getContentResolver().update(updateUri, values, null, null);
+Log.i(DEBUG_TAG, "Rows updated: " + rows);  
+ +

이벤트 삭제

+ +

이벤트를 삭제하려면 이벤트의 {@link +android.provider.BaseColumns#_ID}를 URI에 추가된 ID로 써도 되고, 표준 선택을 사용해도 됩니다. + 추가된 ID를 사용하는 경우, 선택도 할 수 없습니다. +삭제에는 두 가지 버전이 있습니다. 애플리케이션으로 삭제와 동기화 어댑터로의 삭제입니다. +애플리케이션 삭제의 경우 삭제된 열을 1로 설정합니다. +이것은 동기화 어댑터에 행이 삭제되었다고 알리는 플래그이며, +이 삭제를 서버에 알려야 한다는 것을 나타내기도 합니다. +동기화 어댑터 삭제의 경우, 이벤트를 연관된 데이터 일체와 함께 데이터베이스에서 제거합니다. +다음은 애플리케이션이 이벤트를 {@link android.provider.BaseColumns#_ID}를 통해 삭제하는 예입니다.

+ + +
private static final String DEBUG_TAG = "MyActivity";
+...
+long eventID = 201;
+...
+ContentResolver cr = getContentResolver();
+ContentValues values = new ContentValues();
+Uri deleteUri = null;
+deleteUri = ContentUris.withAppendedId(Events.CONTENT_URI, eventID);
+int rows = getContentResolver().delete(deleteUri, null, null);
+Log.i(DEBUG_TAG, "Rows deleted: " + rows);  
+
+ +

참석자 테이블

+ +

{@link android.provider.CalendarContract.Attendees} 테이블의 각 행은 +이벤트의 참석자 또는 게스트 하나를 나타냅니다. +{@link android.provider.CalendarContract.Reminders#query(android.content.ContentResolver, long, java.lang.String[]) query()} +를호출하면 주어진 +{@link android.provider.CalendarContract.AttendeesColumns#EVENT_ID}와 함께 해당 이벤트의 참석자 목록을 반환합니다. +이 {@link android.provider.CalendarContract.AttendeesColumns#EVENT_ID}는 + 특정 이벤트의 {@link +android.provider.BaseColumns#_ID}와 반드시 일치해야 합니다.

+ +

다음 표는 쓸 수 있는 필드를 목록으로 나열한 것입니다. + 새 참석자를 삽입하는 경우 이 모두를 포함해야 하며, +단 ATTENDEE_NAME은 예외입니다. +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
상수설명
{@link android.provider.CalendarContract.AttendeesColumns#EVENT_ID}이벤트 ID입니다.
{@link android.provider.CalendarContract.AttendeesColumns#ATTENDEE_NAME}참석자 이름입니다.
{@link android.provider.CalendarContract.AttendeesColumns#ATTENDEE_EMAIL}참석자 이메일 주소입니다.
{@link android.provider.CalendarContract.AttendeesColumns#ATTENDEE_RELATIONSHIP}

참석자와 이벤트의 관계입니다. 다음 중 하나로 정해집니다.

+
    +
  • {@link android.provider.CalendarContract.AttendeesColumns#RELATIONSHIP_ATTENDEE}
  • +
  • {@link android.provider.CalendarContract.AttendeesColumns#RELATIONSHIP_NONE}
  • +
  • {@link android.provider.CalendarContract.AttendeesColumns#RELATIONSHIP_ORGANIZER}
  • +
  • {@link android.provider.CalendarContract.AttendeesColumns#RELATIONSHIP_PERFORMER}
  • +
  • {@link android.provider.CalendarContract.AttendeesColumns#RELATIONSHIP_SPEAKER}
  • +
+
{@link android.provider.CalendarContract.AttendeesColumns#ATTENDEE_TYPE}

참석자 유형입니다. 다음 중 하나로 정해집니다.

+
    +
  • {@link android.provider.CalendarContract.AttendeesColumns#TYPE_REQUIRED}
  • +
  • {@link android.provider.CalendarContract.AttendeesColumns#TYPE_OPTIONAL}
  • +
{@link android.provider.CalendarContract.AttendeesColumns#ATTENDEE_STATUS}

참석자의 참석 상태입니다. 다음 중 하나로 정해집니다.

+
    +
  • {@link android.provider.CalendarContract.AttendeesColumns#ATTENDEE_STATUS_ACCEPTED}
  • +
  • {@link android.provider.CalendarContract.AttendeesColumns#ATTENDEE_STATUS_DECLINED}
  • +
  • {@link android.provider.CalendarContract.AttendeesColumns#ATTENDEE_STATUS_INVITED}
  • +
  • {@link android.provider.CalendarContract.AttendeesColumns#ATTENDEE_STATUS_NONE}
  • +
  • {@link android.provider.CalendarContract.AttendeesColumns#ATTENDEE_STATUS_TENTATIVE}
  • +
+ +

참석자 추가

+ +

다음은 이벤트에 참석자 한 명을 추가하는 예입니다. +{@link android.provider.CalendarContract.AttendeesColumns#EVENT_ID} +가 필수인 점을 유념하십시오.

+ +
+long eventID = 202;
+...
+ContentResolver cr = getContentResolver();
+ContentValues values = new ContentValues();
+values.put(Attendees.ATTENDEE_NAME, "Trevor");
+values.put(Attendees.ATTENDEE_EMAIL, "trevor@example.com");
+values.put(Attendees.ATTENDEE_RELATIONSHIP, Attendees.RELATIONSHIP_ATTENDEE);
+values.put(Attendees.ATTENDEE_TYPE, Attendees.TYPE_OPTIONAL);
+values.put(Attendees.ATTENDEE_STATUS, Attendees.ATTENDEE_STATUS_INVITED);
+values.put(Attendees.EVENT_ID, eventID);
+Uri uri = cr.insert(Attendees.CONTENT_URI, values);
+
+ +

알림 테이블

+ +

{@link android.provider.CalendarContract.Reminders} +테이블의 각 행은 이벤트의 알림 하나를 나타냅니다. +{@link android.provider.CalendarContract.Reminders#query(android.content.ContentResolver, long, java.lang.String[]) query()} 를 호출하면 + +주어진 {@link android.provider.CalendarContract.AttendeesColumns#EVENT_ID}와 함께 이벤트 알림 목록을 반환합니다.

+ + +

다음 표는 알림의 쓸 수 있는 필드를 목록으로 나열한 것입니다. 새 알림을 삽입하는 경우 이 모두를 포함해야 합니다. + 동기화 어댑터가 {@link +android.provider.CalendarContract.Calendars} 테이블에서 지원하는 알림을 나타낸다는 점을 눈여겨 보십시오. + 자세한 내용은 +{@link android.provider.CalendarContract.CalendarColumns#ALLOWED_REMINDERS} +를 참조하십시오.

+ + + + + + + + + + + + + + + + + + + +
상수설명
{@link android.provider.CalendarContract.RemindersColumns#EVENT_ID}이벤트 ID입니다.
{@link android.provider.CalendarContract.RemindersColumns#MINUTES}이벤트 몇 분 전에 알림을 보내야 하는지 나타냅니다.
{@link android.provider.CalendarContract.RemindersColumns#METHOD}

알림 메서드이며, 서버에서 설정한 대로 따릅니다. 다음 중 하나로 정해집니다.

+
    +
  • {@link android.provider.CalendarContract.RemindersColumns#METHOD_ALERT}
  • +
  • {@link android.provider.CalendarContract.RemindersColumns#METHOD_DEFAULT}
  • +
  • {@link android.provider.CalendarContract.RemindersColumns#METHOD_EMAIL}
  • +
  • {@link android.provider.CalendarContract.RemindersColumns#METHOD_SMS}
  • +
+ +

알림 추가

+ +

이 예시는 이벤트에 알림을 추가하는 것입니다. 알림이 이벤트 15분 전에 발송됩니다. +

+
+long eventID = 221;
+...
+ContentResolver cr = getContentResolver();
+ContentValues values = new ContentValues();
+values.put(Reminders.MINUTES, 15);
+values.put(Reminders.EVENT_ID, eventID);
+values.put(Reminders.METHOD, Reminders.METHOD_ALERT);
+Uri uri = cr.insert(Reminders.CONTENT_URI, values);
+ +

인스턴스 테이블

+ +

+{@link android.provider.CalendarContract.Instances} 테이블에는 +이벤트 발생의 시작 및 종료 시간이 담겨 있습니다. 이 테이블의 각 행이 하나의 이벤트 발생을 나타냅니다. + 이 인스턴스 테이블은 쓸 수 없으며 이벤트 발생 쿼리 방법을 제공할 뿐입니다. +

+ +

다음 표에는 인스턴스에 대해 쿼리할 수 있는 몇 가지 필드를 목록으로 나열하였습니다. +표준 시간대가 +{@link android.provider.CalendarContract.CalendarCache#KEY_TIMEZONE_TYPE} + 및 +{@link android.provider.CalendarContract.CalendarCache#KEY_TIMEZONE_INSTANCES}에 의해 정의된다는 점을 눈여겨 보십시오.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
상수설명
{@link android.provider.CalendarContract.Instances#BEGIN}인스턴스 시작 시간을 UTC 밀리초로 나타낸 것입니다.
{@link android.provider.CalendarContract.Instances#END}인스턴스 종료 시간을 UTC 밀리초로 나타낸 것입니다.
{@link android.provider.CalendarContract.Instances#END_DAY}인스턴스의 율리우스력 종료 날짜를 캘린더의 시간대에 비례하여 나타낸 것입니다. + + +
{@link android.provider.CalendarContract.Instances#END_MINUTE}인스턴스의 종료 시간(분 단위)을 캘린더 시간대의 자정부터 측정한 것입니다. +
{@link android.provider.CalendarContract.Instances#EVENT_ID}이 인스턴스에 대한 이벤트의 _ID입니다.
{@link android.provider.CalendarContract.Instances#START_DAY}인스턴스의 율리우스력 시작 날짜를 캘린더의 시간대에 비례하여 나타낸 것입니다. +
{@link android.provider.CalendarContract.Instances#START_MINUTE}인스턴스의 시작 시간(분 단위)을 캘린더 시간대에 비례하여 자정부터 측정한 것입니다. + +
+ +

인스턴스 테이블 쿼리

+ +

인스턴스 테이블을 쿼리하려면, 해당 쿼리에 대한 범위 시간을 URI에 지정해야 합니다. + 이 예시에서는 {@link android.provider.CalendarContract.Instances} +가 {@link +android.provider.CalendarContract.EventsColumns#TITLE} 필드에 액세스 권한을 얻으며, 이때 +{@link android.provider.CalendarContract.EventsColumns} 인터페이스의 구현을 통합니다. +바꿔 말하면, {@link +android.provider.CalendarContract.EventsColumns#TITLE}이 +데이터베이스 보기를 통해 반환되며 원시 {@link +android.provider.CalendarContract.Instances} 테이블 쿼리를 통해서가 아니라는 뜻입니다.

+ +
+private static final String DEBUG_TAG = "MyActivity";
+public static final String[] INSTANCE_PROJECTION = new String[] {
+    Instances.EVENT_ID,      // 0
+    Instances.BEGIN,         // 1
+    Instances.TITLE          // 2
+  };
+  
+// The indices for the projection array above.
+private static final int PROJECTION_ID_INDEX = 0;
+private static final int PROJECTION_BEGIN_INDEX = 1;
+private static final int PROJECTION_TITLE_INDEX = 2;
+...
+
+// Specify the date range you want to search for recurring
+// event instances
+Calendar beginTime = Calendar.getInstance();
+beginTime.set(2011, 9, 23, 8, 0);
+long startMillis = beginTime.getTimeInMillis();
+Calendar endTime = Calendar.getInstance();
+endTime.set(2011, 10, 24, 8, 0);
+long endMillis = endTime.getTimeInMillis();
+  
+Cursor cur = null;
+ContentResolver cr = getContentResolver();
+
+// The ID of the recurring event whose instances you are searching
+// for in the Instances table
+String selection = Instances.EVENT_ID + " = ?";
+String[] selectionArgs = new String[] {"207"};
+
+// Construct the query with the desired date range.
+Uri.Builder builder = Instances.CONTENT_URI.buildUpon();
+ContentUris.appendId(builder, startMillis);
+ContentUris.appendId(builder, endMillis);
+
+// Submit the query
+cur =  cr.query(builder.build(), 
+    INSTANCE_PROJECTION, 
+    selection, 
+    selectionArgs, 
+    null);
+   
+while (cur.moveToNext()) {
+    String title = null;
+    long eventID = 0;
+    long beginVal = 0;    
+    
+    // Get the field values
+    eventID = cur.getLong(PROJECTION_ID_INDEX);
+    beginVal = cur.getLong(PROJECTION_BEGIN_INDEX);
+    title = cur.getString(PROJECTION_TITLE_INDEX);
+              
+    // Do something with the values. 
+    Log.i(DEBUG_TAG, "Event:  " + title); 
+    Calendar calendar = Calendar.getInstance();
+    calendar.setTimeInMillis(beginVal);  
+    DateFormat formatter = new SimpleDateFormat("MM/dd/yyyy");
+    Log.i(DEBUG_TAG, "Date: " + formatter.format(calendar.getTime()));    
+    }
+ }
+ +

캘린더 인텐트

+

캘린더 데이터를 읽고 쓰려면 애플리케이션에 권한이 없어도 됩니다. 대신 Android의 캘린더 애플리케이션이 지원하는 인텐트를 사용하여 해당 애플리케이션에 읽기 및 쓰기 작업을 분배하면 됩니다. 다음 표는 캘린더 제공자가 지원하는 인텐트를 목록으로 나열한 것입니다.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
동작URI설명추가

+ {@link android.content.Intent#ACTION_VIEW VIEW}

content://com.android.calendar/time/<ms_since_epoch>

+ +{@link android.provider.CalendarContract#CONTENT_URI CalendarContract.CONTENT_URI}로도 URI를 참조할 수 있습니다. +이 인텐트 사용법의 예시를 보려면 인텐트를 사용하여 캘린더 데이터 보기를 참조하십시오. + +
캘린더를 <ms_since_epoch>에 의해 지정된 시간으로 엽니다.없음.

{@link android.content.Intent#ACTION_VIEW VIEW}

+ +

content://com.android.calendar/events/<event_id>

+ + +{@link android.provider.CalendarContract.Events#CONTENT_URI Events.CONTENT_URI}로도 URI를 참조할 수 있습니다. +이 인텐트 사용법의 예시를 보려면 인텐트를 사용하여 캘린더 데이터 보기를 참조하십시오. + +
<event_id>에 의해 지정된 이벤트를 봅니다.{@link android.provider.CalendarContract#EXTRA_EVENT_BEGIN_TIME CalendarContract.EXTRA_EVENT_BEGIN_TIME}
+
+
+ {@link android.provider.CalendarContract#EXTRA_EVENT_END_TIME CalendarContract.EXTRA_EVENT_END_TIME}
{@link android.content.Intent#ACTION_EDIT EDIT}

content://com.android.calendar/events/<event_id>

+ + +{@link android.provider.CalendarContract.Events#CONTENT_URI Events.CONTENT_URI}로도 URI를 참조할 수 있습니다. +이 인텐트 사용법의 예시를 보려면 인텐트를 사용하여 이벤트 편집을 참조하십시오. + + +
<event_id>에 의해 지정된 이벤트를 편집합니다.{@link android.provider.CalendarContract#EXTRA_EVENT_BEGIN_TIME CalendarContract.EXTRA_EVENT_BEGIN_TIME}
+
+
+ {@link android.provider.CalendarContract#EXTRA_EVENT_END_TIME CalendarContract.EXTRA_EVENT_END_TIME}
{@link android.content.Intent#ACTION_EDIT EDIT}
+
+ {@link android.content.Intent#ACTION_INSERT INSERT}

content://com.android.calendar/events

+ + +{@link android.provider.CalendarContract.Events#CONTENT_URI Events.CONTENT_URI}로도 URI를 참조할 수 있습니다. +이 인텐트 사용법의 예시를 보려면 인텐트를 사용하여 이벤트 삽입을 참조하십시오. + +
이벤트를 생성합니다.아래 테이블에 목록으로 표시된 추가 사항 모두입니다.
+ +

다음 표에는 캘린더 제공자가 지원하는 인텐트 추가 사항이 목록으로 나열되어 있습니다. +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
인텐트 추가 사항설명
{@link android.provider.CalendarContract.EventsColumns#TITLE Events.TITLE}이벤트의 이름입니다.
{@link android.provider.CalendarContract#EXTRA_EVENT_BEGIN_TIME +CalendarContract.EXTRA_EVENT_BEGIN_TIME}이벤트 시작 시간을 Epoch로부터 밀리초 단위로 나타낸 것입니다.
{@link android.provider.CalendarContract#EXTRA_EVENT_END_TIME +CalendarContract.EXTRA_EVENT_END_TIME}이벤트 종료 시간을 Epoch로부터 밀리초 단위로 나타낸 것입니다.
{@link android.provider.CalendarContract#EXTRA_EVENT_ALL_DAY +CalendarContract.EXTRA_EVENT_ALL_DAY}이벤트가 종일 이벤트인지 나타내는 부울입니다. 값은 +true 또는 false가 될 수 있습니다.
{@link android.provider.CalendarContract.EventsColumns#EVENT_LOCATION +Events.EVENT_LOCATION}이벤트 위치입니다.
{@link android.provider.CalendarContract.EventsColumns#DESCRIPTION +Events.DESCRIPTION}이벤트 설명입니다.
+ {@link android.content.Intent#EXTRA_EMAIL Intent.EXTRA_EMAIL}초대할 사람의 이메일 주소를 쉼표로 구분한 목록입니다.
+ {@link android.provider.CalendarContract.EventsColumns#RRULE Events.RRULE}이벤트의 반복 규칙입니다.
+ {@link android.provider.CalendarContract.EventsColumns#ACCESS_LEVEL +Events.ACCESS_LEVEL}이벤트가 비공개인지 공개인지 나타냅니다.
{@link android.provider.CalendarContract.EventsColumns#AVAILABILITY +Events.AVAILABILITY}이 이벤트가 사용 중인 시간으로 간주되는지, 다시 일정을 예약할 수 있는 자유 시간으로 간주되는지를 나타냅니다.
+

아래 섹션에서는 이와 같은 인텐트의 사용법을 설명합니다.

+ + +

인텐트를 사용하여 이벤트 삽입

+ +

{@link android.content.Intent#ACTION_INSERT INSERT} 인텐트를 사용하면 +캘린더에 이벤트 삽입 작업을 분배할 수 있습니다. +이 방법을 사용하는 경우, 애플리케이션의 매니페스트 파일에 {@link +android.Manifest.permission#WRITE_CALENDAR} 권한을 포함할 필요가 없습니다.

+ + +

사용자가 이 방법을 사용하는 애플리케이션을 실행하면 해당 애플리케이션이 +사용자를 캘린더로 보내 이벤트 추가를 완료합니다. {@link +android.content.Intent#ACTION_INSERT INSERT} 인텐트는 추가 필드를 사용하여 +캘린더에 있는 이벤트 세부 정보로 양식을 미리 채웁니다. +그러면 사용자가 이벤트를 취소하거나 양식을 필요에 따라 편집할 수 있고, +이벤트를 본인의 캘린더에 저장할 수도 있습니다.

+ + + +

다음은 2012년 1월 19일에 이벤트 일정을 예약하는 코드 조각으로, +이는 오전 7:30~오전 8:30까지 실행됩니다. 이 코드 조각에 관해서는 다음 내용을 주의하십시오.

+ +
    +
  • 이것은 {@link android.provider.CalendarContract.Events#CONTENT_URI Events.CONTENT_URI}를 URI로 지정합니다. +
  • + +
  • 이것은 {@link +android.provider.CalendarContract#EXTRA_EVENT_BEGIN_TIME +CalendarContract.EXTRA_EVENT_BEGIN_TIME} 및 {@link +android.provider.CalendarContract#EXTRA_EVENT_END_TIME +CalendarContract.EXTRA_EVENT_END_TIME} 추가 필드를 사용하여 이벤트 시간으로 양식을 미리 채웁니다. + 이러한 시간에 해당하는 값은 Epoch로부터 UTC 밀리초 단위로 표시해야 합니다. +
  • + +
  • 이것은 {@link android.content.Intent#EXTRA_EMAIL Intent.EXTRA_EMAIL} +추가 필드를 사용하여 쉼표로 구분된 초청인 목록을 제공하며, 이는 이메일 주소로 나타납니다.
  • + +
+
+Calendar beginTime = Calendar.getInstance();
+beginTime.set(2012, 0, 19, 7, 30);
+Calendar endTime = Calendar.getInstance();
+endTime.set(2012, 0, 19, 8, 30);
+Intent intent = new Intent(Intent.ACTION_INSERT)
+        .setData(Events.CONTENT_URI)
+        .putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, beginTime.getTimeInMillis())
+        .putExtra(CalendarContract.EXTRA_EVENT_END_TIME, endTime.getTimeInMillis())
+        .putExtra(Events.TITLE, "Yoga")
+        .putExtra(Events.DESCRIPTION, "Group class")
+        .putExtra(Events.EVENT_LOCATION, "The gym")
+        .putExtra(Events.AVAILABILITY, Events.AVAILABILITY_BUSY)
+        .putExtra(Intent.EXTRA_EMAIL, "rowan@example.com,trevor@example.com");
+startActivity(intent);
+
+ +

인텐트를 사용하여 이벤트 편집

+ +

이벤트 업데이트에서 설명한 바와 같이 이벤트를 직접 업데이트할 수 있습니다. 그러나 {@link +android.content.Intent#ACTION_EDIT EDIT} 인텐트를 사용하면 +캘린더 애플리케이션에 이벤트 편집을 분배할 권한이 없는 애플리케이션을 허용합니다. +사용자가 캘린더에서 이벤트 편집을 마치면 원래 애플리케이션으로 돌아오게 됩니다. +

다음은 지정된 이벤트에 새 제목을 설정하여 사용자에게 캘린더에서 이벤트를 편집할 수 있도록 해주는 인텐트의 예입니다. +

+ + +
long eventID = 208;
+Uri uri = ContentUris.withAppendedId(Events.CONTENT_URI, eventID);
+Intent intent = new Intent(Intent.ACTION_EDIT)
+    .setData(uri)
+    .putExtra(Events.TITLE, "My New Title");
+startActivity(intent);
+ +

인텐트를 사용하여 캘린더 데이터 보기

+

캘린더 제공자는 {@link android.content.Intent#ACTION_VIEW VIEW} 인텐트를 사용하는 두 가지 방식을 제공합니다.

+
    +
  • 캘린더를 특정 날짜에 여는 방식
  • +
  • 이벤트를 보는 방식
  • + +
+

다음은 캘린더를 특정 날짜에 여는 방법을 보여주는 예입니다.

+
// A date-time specified in milliseconds since the epoch.
+long startMillis;
+...
+Uri.Builder builder = CalendarContract.CONTENT_URI.buildUpon();
+builder.appendPath("time");
+ContentUris.appendId(builder, startMillis);
+Intent intent = new Intent(Intent.ACTION_VIEW)
+    .setData(builder.build());
+startActivity(intent);
+ +

다음은 이벤트를 보기 위해 여는 방법을 나타낸 예입니다.

+
long eventID = 208;
+...
+Uri uri = ContentUris.withAppendedId(Events.CONTENT_URI, eventID);
+Intent intent = new Intent(Intent.ACTION_VIEW)
+   .setData(uri);
+startActivity(intent);
+
+ + +

동기화 어댑터

+ + +

애플리케이션과 동기화 어댑터가 캘린더 제공자에 액세스하는 방식에는 사소한 차이만이 있을 뿐입니다. +

+ +
    +
  • 동기화 어댑터는 {@link android.provider.CalendarContract#CALLER_IS_SYNCADAPTER}를 true로 설정하여 이것이 동기화 어댑터라는 것을 나타내야 합니다.
  • + + +
  • 동기화 어댑터는 URI에서 쿼리 매개변수로 {@link +android.provider.CalendarContract.SyncColumns#ACCOUNT_NAME}과 {@link +android.provider.CalendarContract.SyncColumns#ACCOUNT_TYPE}을 제공해야 합니다.
  • + +
  • 동기화 어댑터에는 애플리케이션 또는 위젯에 비해 더 많은 열에 대한 쓰기 액세스 권한이 있습니다. + 예를 들어, 애플리케이션은 캘린더의 몇 가지 특성만 수정할 수 있습니다. +즉 이름, 표시 이름, 가시성 설정 및 캘린더 동기화 여부 등만 해당됩니다. + 이에 비해 동기화 어댑터의 경우 이 열만이 아니라 다른 수많은 열에도 액세스할 수 있습니다. +예를 들어 캘린더 색상, 표준 시간대, 액세스 수준 등이 해당됩니다. +다만, 동기화 어댑터는 지정된 ACCOUNT_NAME 및 +ACCOUNT_TYPE에 한정됩니다.
+ +

다음은 URI를 반환하여 동기화 어댑터와 사용하도록 할 때 쓸 수 있는 도우미 메서드입니다.

+
 static Uri asSyncAdapter(Uri uri, String account, String accountType) {
+    return uri.buildUpon()
+        .appendQueryParameter(android.provider.CalendarContract.CALLER_IS_SYNCADAPTER,"true")
+        .appendQueryParameter(Calendars.ACCOUNT_NAME, account)
+        .appendQueryParameter(Calendars.ACCOUNT_TYPE, accountType).build();
+ }
+
+

동기화 어댑터의 샘플 구현(캘린더에 구체적으로 관련된 것이 아님)은 +SampleSyncAdpater를 참조하십시오. diff --git a/docs/html-intl/intl/ko/guide/topics/providers/contacts-provider.jd b/docs/html-intl/intl/ko/guide/topics/providers/contacts-provider.jd new file mode 100644 index 0000000000000000000000000000000000000000..94d3295c1d465da128385bccf62976bf328fd0de --- /dev/null +++ b/docs/html-intl/intl/ko/guide/topics/providers/contacts-provider.jd @@ -0,0 +1,2356 @@ +page.title=연락처 제공자 +@jd:body +

+
+

간략히 보기

+
    +
  • 사람들 관련 정보를 저장한 Android의 리포지토리입니다.
  • +
  • + 웹과 동기화합니다. +
  • +
  • + 소셜 스트림 데이터와 통합됩니다. +
  • +
+

이 문서의 내용

+
    +
  1. + 연락처 제공자 조직 +
  2. +
  3. + 원시 연락처 +
  4. +
  5. + 데이터 +
  6. +
  7. + 연락처 +
  8. +
  9. + 동기화 어댑터의 데이터 +
  10. +
  11. + 필수 권한 +
  12. +
  13. + 사용자 프로필 +
  14. +
  15. + 연락처 제공자 메타데이터 +
  16. +
  17. + 연락처 제공자 액세스 +
  18. +
  19. +
  20. + 연락처 제공자 동기화 어댑터 +
  21. +
  22. + 소셜 스트림 데이터 +
  23. +
  24. + 추가 연락처 제공자 기능 +
  25. +
+

Key 클래스

+
    +
  1. {@link android.provider.ContactsContract.Contacts}
  2. +
  3. {@link android.provider.ContactsContract.RawContacts}
  4. +
  5. {@link android.provider.ContactsContract.Data}
  6. +
  7. {@code android.provider.ContactsContract.StreamItems}
  8. +
+

관련 샘플

+
    +
  1. + + 연락처 관리자 + +
  2. +
  3. + + 샘플 동기화 어댑터 +
  4. +
+

참고 항목

+
    +
  1. + + 콘텐츠 제공자 기본 정보 + +
  2. +
+
+
+

+ 콘텐츠 제공자는 기기의 사람에 대한 중앙 데이터 리포지토리를 관리하는 강력하고 유연한 +Android 구성 요소입니다. 콘텐츠 제공자는 기기의 연락처 애플리케이션에서 개발자에게 표시되는 +데이터의 출처입니다. 여기의 데이터에는 개발자 자신의 애플리케이션에서 +액세스하여 기기와 온라인 서비스 사이에서 데이터를 전송할 수도 있습니다. 제공자는 +광범위한 데이터 소스를 수용하며 각 인물에 대해 가능한 한 많은 데이터를 관리하여야 하므로, 그 결과 조직이 무척 +복잡합니다. 이 때문에 이 제공자의 API에는 +광범위한 계약 클래스와 인터페이스 세트가 포함되어 있어 데이터 검색과 수정을 모두 한층 +용이하게 해줍니다. +

+

+ 이 가이드에서 설명하는 내용은 다음과 같습니다. +

+
    +
  • + 기본적인 제공자 구조. +
  • +
  • + 제공자에서 데이터를 검색하는 방법. +
  • +
  • + 제공자에서 데이터를 수정하는 방법. +
  • +
  • + 동기화 어댑터를 작성하여 서버에서 가져온 데이터를 연락처 제공자와 +동기화하는 방법. +
  • +
+

+ 이 가이드는 독자가 Android 콘텐츠 제공자의 기본 정보를 알고 있는 것으로 간주합니다. Android 콘텐츠 제공자에 +관한 자세한 내용은 + +콘텐츠 제공자 기본 정보 가이드를 읽어보십시오. +샘플 동기화 어댑터 +샘플 앱은 동기화 어댑터를 사용하여 연락처 제공자와 Google Web Services가 호스팅하는 샘플 애플리케이션 사이에서 +데이터를 전송하는 동기화 어댑터의 사용 예시입니다. +

+

연락처 제공자 조직

+

+ 연락처 제공자는 Android 콘텐츠 제공자 구성 요소입니다. 이것은 한 사람에 대해 +각기 세 가지 유형의 데이터를 관리합니다. 각 데이터는 그림 1에서 설명하는 바와 같이 제공자가 제공하는 +각 테이블에 상응합니다. +

+ +

+ 그림 1. 연락처 제공자 테이블 구조입니다. +

+

+ 이 세 개의 테이블은 보통 자신의 계약 클래스의 이름으로 불립니다. 이들 클래스는 +테이블에서 사용하는 콘텐츠 URI, 열 이름 및 열 값의 상수를 정의합니다. +

+
+
+ {@link android.provider.ContactsContract.Contacts} 테이블 +
+
+ 행은 원시 연락처 행의 집계에 기초하여 각기 다른 사람을 나타냅니다. +
+
+ {@link android.provider.ContactsContract.RawContacts} 테이블 +
+
+ 사용자 계정과 유형을 기준으로, 한 사람에 대한 데이터 요약이 들어있는 행입니다. +
+
+ {@link android.provider.ContactsContract.Data} 테이블 +
+
+ 이메일 주소나 전화 번호와 같은 원시 연락처의 세부 정보가 들어있는 행입니다. +
+
+

+ {@link android.provider.ContactsContract}의 계약 클래스가 대표하는 다른 테이블은 +보조 테이블로, 연락처 제공자는 이들을 사용하여 작업을 관리하거나 기기의 연락처에 있는 +특정 기능 또는 전화 통신 애플리케이션 등을 지원합니다. +

+

원시 연락처

+

+ 원시 연락처는 단일 계정 유형과 계정 이름에서 가져오는 +한 사람의 데이터를 나타냅니다. 연락처 제공자는 한 사람에 대해 하나 이상의 온라인 서비스를 데이터의 출처로 허용하므로, +연락처 제공자에서는 같은 사람에 대해 여러 개의 원시 연락처를 허용합니다. + 원시 연락처를 여러 개 사용하면 사용자가 같은 계정 유형의 하나 이상의 계정에서 가져온 +한 사람의 여러 데이터를 조합할 수 있습니다. +

+

+ 원시 연락처의 데이터 대부분은 +{@link android.provider.ContactsContract.RawContacts} 테이블에 저장되지 않습니다. 대신, +{@link android.provider.ContactsContract.Data} 테이블에서 하나 이상의 행에 저장됩니다. 각 데이터 행에는 +상위 {@link android.provider.ContactsContract.RawContacts} 행의 {@code android.provider.BaseColumns#_ID RawContacts._ID} 값을 포함하는 +열 {@link android.provider.ContactsContract.DataColumns#RAW_CONTACT_ID Data.RAW_CONTACT_ID}가 +있습니다. +

+

중요한 원시 연락처 열

+

+ {@link android.provider.ContactsContract.RawContacts} 테이블의 중요한 열은 +표 1에 나열되어 있습니다. 표 뒤에 이어지는 참고 사항을 꼭 읽어주십시오. +

+

+ 표 1. 중요한 원시 연락처 열입니다. +

+ + + + + + + + + + + + + + + + + + + + +
열 이름용도참고
+ {@link android.provider.ContactsContract.SyncColumns#ACCOUNT_NAME} + + 이 원시 연락처의 소스인 계정 유형에 대한 계정 이름입니다. + 예를 들어, Google 계정의 계정 이름은 +기기 소유자의 Gmail 주소 중 하나입니다. 자세한 정보는 +{@link android.provider.ContactsContract.SyncColumns#ACCOUNT_TYPE}의 +다음 항목을 참조하십시오. + + 이 이름의 형식은 각자의 계정 유형별로 다릅니다. 이것은 꼭 +이메일 주소여야 하는 것은 아닙니다. +
+ {@link android.provider.ContactsContract.SyncColumns#ACCOUNT_TYPE} + + 이 원시 연락처의 소스인 계정 유형입니다. 예를 들어, Google 계정의 +계정 유형은 com.google입니다. 계정 유형을 정규화할 때에는 항상 +본인이 소유하거나 제어하는 도메인의 도메인 식별자를 사용하십시오. 이렇게 하면 계정 유형이 고유한 것이도록 +확실히 해둘 수 있습니다. + + 연락처 데이터를 제공하는 계정 유형은 대개 연락처 제공자와 동기화되는 동기화 어댑터와 +연관되어 있습니다. +
+ {@link android.provider.ContactsContract.RawContactsColumns#DELETED} + + 원시 연락처에 대한 "삭제됨" 플래그입니다. + + 이 플래그를 사용하면 연락처 제공자가 해당 행을 내부에 계속 유지할 수 있습니다. +이는 동기화 어댑터가 해당 행을 자신의 서버에서 삭제하고 마침내는 행을 리포지토리에서도 삭제할 수 있을 +때까지만입니다. +
+

참고

+

+ 다음은 +{@link android.provider.ContactsContract.RawContacts} 테이블에 관한 중요한 참고 사항입니다. +

+
    +
  • + 원시 연락처의 이름은 +{@link android.provider.ContactsContract.RawContacts}에 있는 자신의 행에 저장되지 않습니다. 대신, +{@link android.provider.ContactsContract.CommonDataKinds.StructuredName} 행에 있는 +{@link android.provider.ContactsContract.Data} 테이블에 저장됩니다. 원시 연락처 하나에는 +{@link android.provider.ContactsContract.Data} 테이블에서 이런 유형의 행이 하나씩만 있습니다. +
  • +
  • + 주의: 원시 연락처에서 본인의 계정 데이터를 사용하려면 이를 우선 +{@link android.accounts.AccountManager}로 등록해야 합니다. 이렇게 하려면, +사용자에게 계정 유형과 본인의 계정 이름을 계정 목록에 추가하라는 프롬프트를 표시하십시오. 이렇게 하지 않으면, +연락처 제공자가 원시 연락처 행을 자동으로 삭제합니다. +

    + 예를 들어, 앱에서 도메인 {@code com.example.dataservice}로 웹 베이스 서비스에 대한 연락처 데이터를 유지하고 +서비스에 대한 사용자 계정이 +{@code becky.sharp@dataservice.example.com}이라면, 사용자는 앱이 원시 연락처 행을 추가하기 전에 +계정 "유형"({@code com.example.dataservice})과 계정 "이름" +({@code becky.smart@dataservice.example.com})을 먼저 추가해야 합니다. + 이 요구 사항을 사용자에게 설명하려면 관련 문서를 사용해도 되고, 아니면 사용자에게 +유형과 이름을 추가하라는 프롬프트를 표시해도 되고 두 가지 방법을 다 써도 됩니다. 계정 유형과 계정 이름은 +다음 섹션에서 더 자세히 설명되어 있습니다. +

  • +
+

원시 연락처 데이터 소스

+

+ 원시 연락처의 작동 원리를 이해하기 위해, 다음과 같이 기기에서 정의한 사용자 계정 세 가지를 보유한 사용자 "Emily Dickinson"이 있다고 +가정해 봅시다. +

+
    +
  • emily.dickinson@gmail.com
  • +
  • emilyd@gmail.com
  • +
  • Twitter 계정 "belle_of_amherst"
  • +
+

+ 이 사용자는 계정 설정에서 모든 세 가지 계정에 대해 연락처 동기화를 +활성화했습니다. +

+

+ Emily Dickinson이 브라우저 창을 열고, +Gmail에 emily.dickinson@gmail.com으로 로그인하고, +연락처를 열어서 "Thomas Higginson"을 추가한다고 가정하겠습니다. 이 사용자는 나중에 Gmail에 +emilyd@gmail.com으로 로그인하고 "Thomas Higginson"에게 이메일을 전송합니다. +이렇게 하면 이 사람을 자동으로 연락처로 추가합니다. Emily는 Twitter에서 "colonel_tom"(Thomas Higginson의 Twitter ID)도 +팔로우합니다. +

+

+ 연락처 제공자는 이 작업의 결과로 원시 연락처를 세 개 생성합니다. +

+
    +
  1. + emily.dickinson@gmail.com과 연관된 "Thomas Higginson"의 원시 연락처입니다. + 사용자 계정 유형은 Google입니다. +
  2. +
  3. + emilyd@gmail.com과 연관된 "Thomas Higginson"의 두 번째 원시 연락처입니다. + 사용자 계정 유형은 마찬가지로 Google입니다. 이름이 이전 이름과 똑같더라도 두 번째 원시 연락처가 +더해집니다. 왜냐하면 이 사람은 아까와 다른 +사용자 계정에 추가되었기 때문입니다. +
  4. +
  5. + "belle_of_amherst"와 연관된 "Thomas Higginson"의 세 번째 원시 연락처입니다. 사용자 +계정 유형은 Twitter입니다. +
  6. +
+

데이터

+

+ 이전에 언급한 바와 같이, 원시 연락처의 데이터는 +원시 연락처의 _ID 값과 연결된{@link android.provider.ContactsContract.Data} 행에 +저장됩니다. 이렇게 하면 하나의 원시 연락처에 같은 유형의 데이터의 인스턴스가 여러 개 있을 수 있게 됩니다. +예를 들어 이메일 주소 또는 전화 번호 등이 이에 해당됩니다. 예를 들어, +{@code emilyd@gmail.com}에 대한 "Thomas Higginson"(Google 계정 emilyd@gmail.com과 연관된 Thomas Higginson의 +원시 연락처)에는 +thigg@gmail.com이라는 집 이메일 주소와 +thomas.higginson@gmail.com이라는 직장 이메일 주소가 있고, 연락처 제공자는 두 개의 이메일 주소 행을 저장하고 +원시 연락처에 두 가지를 연결합니다. +

+

+ 이 테이블 하나에 여러 가지 유형의 데이터가 저장된 점에 주의하십시오. 표시 이름, +전화 번호, 이메일, 우편 주소, 사진 및 웹사이트 세부 정보 행은 모두 +{@link android.provider.ContactsContract.Data} 테이블에서 찾을 수 있습니다. 이런 데이터 관리를 돕기 위해 +{@link android.provider.ContactsContract.Data} 테이블에는 설명이 포함된 이름이 있는 열이 몇 개 있고 +일반적 이름이 포함된 열도 몇 개 있습니다. 설명이 포함된 이름 열의 콘텐츠는 행 안의 데이터 유형과 관계 없이 모두 의미가 같고, +일반적인 이름 열의 콘텐츠는 데이터 유형에 따라 +서로 의미가 다릅니다. +

+

설명이 포함된 열 이름

+

+ 다음은 설명이 포함된 열 이름의 몇 가지 예시입니다. +

+
+
+ {@link android.provider.ContactsContract.Data#RAW_CONTACT_ID} +
+
+ 이 데이터에 대한 원시 연락처의 _ID 열 값입니다. +
+
+ {@link android.provider.ContactsContract.Data#MIMETYPE} +
+
+ 이 행에 저장되는 데이터 유형으로, 사용자 지정 MIME 유형으로 표현됩니다. 연락처 제공자는 +{@link android.provider.ContactsContract.CommonDataKinds}의 하위 클래스에서 정의된 +MIME 유형을 사용합니다. 이러한 MIME 유형은 오픈 소스이고, +연락처 제공자와 함께 사용할 수 있는 모든 애플리케이션 또는 동기화 어댑터가 사용할 수 있습니다. +
+
+ {@link android.provider.ContactsContract.DataColumns#IS_PRIMARY} +
+
+ 이 유형의 데이터 행이 원시 연락처에서 한 번 이상 발생하는 경우, +{@link android.provider.ContactsContract.DataColumns#IS_PRIMARY} 열은 +해당 유형의 기본 데이터가 들어있는 데이터 행을 플래그로 표시합니다. 예를 들어, +사용자가 연락처의 전화 번호를 길게 누르고 기본값으로 설정을 선택하면 +그 번호가 들어있는 {@link android.provider.ContactsContract.Data} 행이 +{@link android.provider.ContactsContract.DataColumns#IS_PRIMARY} 열을 +0이 아닌 값으로 설정합니다. +
+
+

일반 열 이름

+

+ 15개의 일반 열 중에서 DATA1부터 +DATA15까지는 일반적으로 이용할 수 있고 이외에 추가로 마련된 네 개의 일반 +열, 즉 SYNC1부터 SYNC4까지는 +동기화 어댑터 전용입니다. 일반 열 이름 상수는 해당 행에 들어있는 데이터 유형과 관계 없이 +언제나 통합니다. +

+

+ DATA1 열은 색인됩니다. 연락처 제공자는 제공자가 가장 자주 쿼리의 대상이 될 것으로 예상하는 +데이터에 대해 항상 이 열을 사용합니다. 예컨대 +이메일 행의 경우, 이 열에 실제 이메일 주소가 들어있습니다. +

+

+ 규칙에 따라 열 DATA15는 사진 미리 보기와 같은 BLOB(Binary Large Object) +데이터를 저장할 목적으로 예약되어 있습니다. +

+

유형별 열 이름

+

+ 특정 유형의 행에 대한 열과의 작업을 돕기 위해, 연락처 제공자는 + 유형별 열 이름 상수도 제공합니다. 이는 +{@link android.provider.ContactsContract.CommonDataKinds}의 하위 클래스에서 정의합니다. 이 상수는 그저 같은 열 이름에 +서로 다른 상수 이름을 부여할 뿐이며, 이렇게 하면 개발자가 특정 유형의 행에 있는 데이터에 +액세스하기 쉽습니다. +

+

+ 예를 들어, {@link android.provider.ContactsContract.CommonDataKinds.Email} 클래스는 +MIME 유형{@link android.provider.ContactsContract.CommonDataKinds.Email#CONTENT_ITEM_TYPE +Email.CONTENT_ITEM_TYPE}을 갖는 +{@link android.provider.ContactsContract.Data} 행에 +대한 유형별 열 이름 상수를 정의합니다. 이 클래스에는 이메일 주소 열에 대한 + 상수 {@link android.provider.ContactsContract.CommonDataKinds.Email#ADDRESS}가 +들어있습니다. +{@link android.provider.ContactsContract.CommonDataKinds.Email#ADDRESS}의 실제 값은 +"data1"이고, 이는 열의 일반 이름과 같습니다. +

+

+ 주의: 개발자 본인의 사용자 지정 데이터를 +{@link android.provider.ContactsContract.Data} 테이블에 +추가할 때 제공자의 미리 정의된 MIME 유형 중 하나가 있는 행을 사용하면 안 됩니다. 그렇게 하면 데이터가 손실되거나 제공자의 오작동을 +유발할 수 있습니다. 예를 들어, MIME 유형 + {@link android.provider.ContactsContract.CommonDataKinds.Email#CONTENT_ITEM_TYPE + Email.CONTENT_ITEM_TYPE} 안에 +DATA1 열에 있는 이메일 주소 대신 사용자 이름이 들어있는 행은 추가하면 안 됩니다. 해당 행에 개발자 나름의 사용자 지정 MIME 유형을 사용하는 경우 +본인만의 유형별 열 이름을 자유자재로 정의하고 이러한 열을 마음대로 사용해도 됩니다. +

+

+ 그림 2는 +{@link android.provider.ContactsContract.Data} 행에서 설명 열과 데이터 열이 나타나는 방식과 유형별 열 이름이 +일반 열 이름에 "오버레이"되는 방식을 나타낸 것입니다. +

+How type-specific column names map to generic column names +

+ 그림 2. 유형별 열 이름과 일반 열 이름입니다. +

+

유형별 열 이름 클래스

+

+ 표 2는 가장 보편적으로 사용되는 유형별 열 이름 클래스를 목록으로 나열한 것입니다. +

+

+ 표 2. 유형별 열 이름 클래스

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
매핑 클래스데이터 유형참고
{@link android.provider.ContactsContract.CommonDataKinds.StructuredName}이 데이터 행과 연관된 원시 연락처의 이름 데이터입니다.하나의 원시 연락처에는 이러한 행이 딱 하나만 있습니다.
{@link android.provider.ContactsContract.CommonDataKinds.Photo}이 데이터 행과 연관된 원시 연락처의 기본 사진입니다.하나의 원시 연락처에는 이러한 행이 딱 하나만 있습니다.
{@link android.provider.ContactsContract.CommonDataKinds.Email}이 데이터 행과 연관된 원시 연락처의 이메일 주소입니다.하나의 원시 연락처에는 여러 개의 이메일 주소가 있을 수 있습니다.
{@link android.provider.ContactsContract.CommonDataKinds.StructuredPostal}이 데이터 행과 연관된 원시 연락처의 우편 주소입니다.하나의 원시 연락처에는 여러 개의 우편 주소가 있을 수 있습니다.
{@link android.provider.ContactsContract.CommonDataKinds.GroupMembership}원시 연락처를 연락처 제공자의 그룹 중 하나와 연결하는 식별자입니다. + 그룹은 계정 유형과 계정 이름의 선택적 기능입니다. 이러한 내용은 +연락처 그룹 섹션에 자세히 설명되어 있습니다. +
+

연락처

+

+ 연락처 제공자는 모든 계정 유형과 계정 이름을 통틀어 원시 연락처 행을 조합하여 +하나의 연락처를 형성합니다. 이렇게 하면 사용자가 한 사람에 대해 수집한 +모든 데이터를 표시하고 수정하기 쉽습니다. 연락처 제공자는 새 연락처 행의 생성을 관리하고 +원시 연락처를 기존 연락처 행과 통합하기도 합니다. 애플리케이션과 +동기화 어댑터는 모두 연락처를 추가할 수 없으며, 연락처 행에 있는 열 중 몇몇은 읽기 전용입니다. +

+

+ 참고: 연락처 제공자에 연락처를 추가하려고 +{@link android.content.ContentResolver#insert(Uri,ContentValues) insert()}를 사용하는 경우, +{@link java.lang.UnsupportedOperationException} 예외가 발생합니다. "읽기 전용"으로 표시된 열을 업데이트하려고 하면 +그 업데이트는 무시됩니다. +

+

+ 연락처 제공자는 기존 연락처 어느 것과도 일치하지 않는 새로운 원시 연락처가 추가되면 +새로운 연락처를 생성합니다. 제공자가 이 작업을 하는 또 다른 경우는 +기존 원시 연락처의 데이터가 변경되어 이전에 첨부되어 있던 연락처에 더 이상 일치하지 않는 +경우입니다. 애플리케이션이나 동기화 어댑터가 +기존 연락처와 일치하는 새로운 원시 연락처를 생성하면, 새로운 원시 연락처는 +기존 연락처에 통합됩니다. +

+

+ 연락처 제공자는 +{@link android.provider.ContactsContract.Contacts Contacts} 테이블에 있는 연락처 행의 _ID 열로 +연락처 행과 원시 연락처 행를 연결합니다. 원시 연락처 테이블 {@link android.provider.ContactsContract.RawContacts}의 CONTACT_ID 행에는 +각 원시 연락처 행과 관련된 연락처 행에 대한 _ID 값이 +들어있습니다. +

+

+ {@link android.provider.ContactsContract.Contacts} 테이블에는 연락처 행에 대한 "영구" 링크인 +{@code android.provider.ContactsContract.ContactsColumns#LOOKUP_KEY} 열도 +있습니다. 연락처 제공자가 연락처를 자동으로 관리하므로, +통합이나 동기화에 응답하여 연락처 행의 {@code android.provider.BaseColumns#_ID} 값을 +변경할 수도 있습니다. 이런 일이 발생한다 하더라도 콘텐츠 URI +{@link android.provider.ContactsContract.Contacts#CONTENT_LOOKUP_URI}와 +연락처의 {@code android.provider.ContactsContract.ContactsColumns#LOOKUP_KEY}는 여전히 +연락처 행을 가리키므로, +{@code android.provider.ContactsContract.ContactsColumns#LOOKUP_KEY}를 +사용하여 "즐겨찾기" 연락처에 대한 연결 등을 그대로 유지할 수 있습니다. 이 열에는 +{@code android.provider.BaseColumns#_ID} 열의 형식과 관련이 없는 나름의 형식이 있습니다. +

+

+ 그림 3은 세 가지 기본 테이블이 서로 관련되는 방식을 나타낸 것입니다. +

+Contacts provider main tables +

+ 그림 3. 연락처, 원시 연락처 및 세부 사항 테이블의 관계입니다. +

+

동기화 어댑터의 데이터

+

+ 사용자가 연락처 데이터를 기기에 직접 입력하기도 하지만, 데이터는 웹 서비스에서 +동기화 어댑터를 통해 연락처 제공자로 흘러들어가기도 합니다. 이것이 기기와 +서비스 사이에서 데이터의 전송을 자동화하는 것입니다. 동기화 어댑터는 시스템의 제어를 받으며 +배경에서 실행되고, {@link android.content.ContentResolver} 메서드를 +호출하여 데이터를 관리합니다. +

+

+ Android에서 동기화 어댑터와 함께 작업하는 웹 서비스는 계정 유형으로 식별됩니다. + 각 동기화 어댑터는 계정 유형 하나에 통하지만, 그 유형에 대한 여러 개의 계정이름을 +지원할 수 있습니다. 계정 유형과 계정 이름은 +원시 연락처 데이터 소스 섹션에 간단히 설명되어 있습니다. 다음 정의는 좀 더 자세한 내용을 제공하며, +계정 유형과 이름이 동기화 어댑터와 서비스에 관련되는 방식을 설명합니다. +

+
+
+ 계정 유형 +
+
+ 사용자가 데이터를 저장해둔 서비스를 식별합니다. 대부분의 경우, 사용자가 +서비스로 인증해야 합니다. 예를 들어, Google 주소록은 계정 유형이고, 이는 +코드 google.com으로 식별됩니다. 이 값은 +{@link android.accounts.AccountManager}가 사용하는 계정 유형에 상응합니다. +
+
+ 계정 이름 +
+
+ 하나의 계정 유형에 대한 특정 계정 또는 로그인을 식별합니다. Google 주소록 계정은 +Google 계정과 같고, 이는 계정 이름으로 이메일 주소를 사용합니다. + 다른 서비스는 한 단어로 된 사용자 이름이나 숫자 ID를 사용할 수 있습니다. +
+
+

+ 계정 유형은 고유하지 않아도 됩니다. 한 사람의 사용자가 여러 개의 Google 주소록을 구성할 수 있고 +그 데이터를 연락처 제공자에 다운로드할 수 있습니다. 이런 일은 사용자에게 +개인용 계정 이름에 대한 개인용 연락처가 한 세트 있고, 업무용으로 또 한 세트가 있는 경우 일어납니다. 계정 이름은 보통 +고유합니다. 이 둘은 함께 사용되어 연락처 제공자와 외부 서비스 사이의 특정 데이터 +흐름을 식별합니다. +

+

+ 서비스의 데이터를 연락처 제공자에 전송하려면, 나름의 +동기화 어댑터를 작성해야 합니다. 이 내용은 +연락처 제공자 동기화 어댑터 섹션에 자세히 설명되어 있습니다. +

+

+ 그림 4는 연락처 제공자가 사람에 대한 데이터 흐름에 +어떻게 들어맞는지 나타낸 것입니다. "동기화 어댑터"라고 표시된 상자에서, 각 어댑터에는 계정 유형에 따라 레이블이 붙어 있습니다. +

+Flow of data about people +

+ 그림 4. 연락처 제공자의 데이터 흐름입니다. +

+

필수 권한

+

+ 연락처 제공자에 액세스하고자 하는 애플리케이션은 다음 권한을 +요청해야 합니다. +

+
+
하나 이상의 테이블에 대한 읽기 액세스
+
+ {@link android.Manifest.permission#READ_CONTACTS}, +AndroidManifest.xml에서 + + <uses-permission> 요소와 함께 +<uses-permission android:name="android.permission.READ_CONTACTS">로 지정된 것. +
+
하나 이상의 테이블에 대한 쓰기 액세스
+
+ {@link android.Manifest.permission#WRITE_CONTACTS}, +AndroidManifest.xml에서 + + <uses-permission> 요소와 함께 +<uses-permission android:name="android.permission.WRITE_CONTACTS">로 지정된 것. +
+
+

+ 이들 권한은 사용자 프로필 데이터로 확대되지 않습니다. 사용자 프로필과 +필수 권한은 +다음 섹션인 사용자 프로필에서 논의합니다. +

+

+ 사용자의 연락처 데이터는 중요한 개인 정보라는 사실을 명심하십시오. 사용자는 자신의 +개인정보보호를 중요하게 생각하고 신경 쓰기 때문에 애플리케이션이 자신이나 자신의 연락처에 관한 정보를 수집하는 것을 바라지 않습니다. + 사용자의 연락처 데이터에 액세스할 권한이 필요한 이유가 분명하지 않으면 여러분의 +애플리케이션에 낮은 순위를 매기거나 설치를 거부할 수도 있습니다. +

+

사용자 프로필

+

+ {@link android.provider.ContactsContract.Contacts} 테이블에 있는 한 개의 행에는 기기의 사용자에 대한 프로필 +데이터가 담겨 있습니다. 이 데이터는 사용자의 연락처 중 하나라기보다는 기기의 user를 +설명하는 것입니다. 프로필 연락처 행은 +프로필을 사용하는 각 시스템에 대한 원시 연락처 행에 연결되어 있습니다. + 각 프로필 원시 연락처 행에는 여러 개의 데이터 행이 있을 수 있습니다. 사용자 프로필에 액세스하기 위한 상수는 +{@link android.provider.ContactsContract.Profile} 클래스에서 이용할 수 있습니다. +

+

+ 사용자 프로필에 액세스하려면 특수 권한이 필요합니다. 읽기와 쓰기에 필요한 +{@link android.Manifest.permission#READ_CONTACTS}와 +{@link android.Manifest.permission#WRITE_CONTACTS} 권한 외에도, +사용자 프로필에 액세스하려면 각각 읽기와 쓰기 액세스를 위한{@code android.Manifest.permission#READ_PROFILE}과 +{@code android.Manifest.permission#WRITE_PROFILE} 권한이 +필요합니다. +

+

+ 사용자의 프로필은 중요한 정보로 간주해야 한다는 점을 명심하십시오. +{@code android.Manifest.permission#READ_PROFILE}권한을 사용하면 개발자가 기기 사용자의 +개인 식별 데이터에 액세스할 수 있게 해줍니다. 애플리케이션 설명에서 +사용자에게 왜 여러분이 사용자 프로필 권한을 필요로 하는지 밝혀두어야 합니다. +

+

+ 사용자 프로필이 포함된 연락처 행을 검색하려면, +{@link android.content.ContentResolver#query(Uri,String[], String, String[], String) +ContentResolver.query()}를 호출합니다. 콘텐츠 URI 를 +{@link android.provider.ContactsContract.Profile#CONTENT_URI}로 설정하고 +선택 기준은 아무것도 제공하지 마십시오. 이 콘텐츠 URI는 원시 연락처 또는 프로필에 대한 데이터를 검색하기 위한 +기본 URI로도 쓸 수 있습니다. 예를 들어, 이 코드 조각은 프로필에 대한 데이터를 검색합니다. +

+
+// Sets the columns to retrieve for the user profile
+mProjection = new String[]
+    {
+        Profile._ID,
+        Profile.DISPLAY_NAME_PRIMARY,
+        Profile.LOOKUP_KEY,
+        Profile.PHOTO_THUMBNAIL_URI
+    };
+
+// Retrieves the profile from the Contacts Provider
+mProfileCursor =
+        getContentResolver().query(
+                Profile.CONTENT_URI,
+                mProjection ,
+                null,
+                null,
+                null);
+
+

+ 참고: 여러 개의 연락처 행을 검색하고 그 중 하나가 +사용자 프로필인지 판별하고자 하는 경우, +행의 {@link android.provider.ContactsContract.ContactsColumns#IS_USER_PROFILE} 열을 테스트합니다. 이 열은 +해당 연락처가 사용자 프로필이면 "1"로 설정됩니다. +

+

연락처 제공자 메타데이터

+

+ 연락처 제공자는 리포지토리에서 연락처 데이터 상태를 +추적하는 데이터를 관리합니다. 이 리포지토리 관련 데이터는 +원시 연락처, 데이터 및 연락처 테이블 행, +{@link android.provider.ContactsContract.Settings} 테이블 및 +{@link android.provider.ContactsContract.SyncState} 테이블 등의 여러 장소에 저장됩니다. 다음 표는 각 메타데이터 조각이 미치는 +영향을 나타낸 것입니다. +

+

+ 표 3. 연락처 제공자의 메타데이터

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
테이블의미
{@link android.provider.ContactsContract.RawContacts}{@link android.provider.ContactsContract.SyncColumns#DIRTY}"0" - 마지막 동기화 이후로 변경되지 않았습니다. + 기기에서 변경되었고 서버로 다시 동기화되어야 하는 원시 데이터를 +표시합니다. 이 값은 Android 애플리케이션이 행을 업데이트하면 연락처 제공자가 +자동으로 설정합니다. +

+ 원시 연락처나 데이터 테이블을 수정하는 동기화 어댑터는 +언제나 문자열 {@link android.provider.ContactsContract#CALLER_IS_SYNCADAPTER}를 +자신이 사용하는 콘텐츠 URI에 추가해야 합니다. 이렇게 하면 제공자가 행을 변경(dirty)으로 표시하지 못하게 방지합니다. + 그렇지 않으면, 동기화 어댑터 수정이 로컬 수정으로 나타나며 +서버가 수정의 근원이었다 하더라도 이 내용이 다시 서버로 전송됩니다. +

+
"1" - 마지막 동기화 이후 변경되었고, 서버에 다시 동기화해야 합니다.
{@link android.provider.ContactsContract.RawContacts}{@link android.provider.ContactsContract.SyncColumns#VERSION}이 행의 버전 번호입니다. + 연락처 제공자는 행이나 관련 데이터가 변경될 때마다 이 값을 자동으로 +증가시킵니다. +
{@link android.provider.ContactsContract.Data}{@link android.provider.ContactsContract.DataColumns#DATA_VERSION}이 행의 버전 번호입니다. + 연락처 제공자는 데이터 행이 변경될 때마다 이 값을 자동으로 +증가시킵니다. +
{@link android.provider.ContactsContract.RawContacts}{@link android.provider.ContactsContract.SyncColumns#SOURCE_ID} + 이 원시 연락처를 자신이 생성된 계정에 대해 고유하게 식별하는 +문자열 값입니다. + + 동기화 어댑터가 새로운 원시 연락처를 생성하면, 이 열이 +해당 원시 연락처에 대한 서버의 고유 ID로 설정되어야 합니다. Android 애플리케이션이 새로운 원시 연락처를 생성하면, +애플리케이션은 이 열을 빈 채로 두어야 합니다. 이것은 동기화 어댑터에 +서버에 새 원시 데이터를 생성해야 한다고 신호하고, +{@link android.provider.ContactsContract.SyncColumns#SOURCE_ID}에 대한 값을 가져오라고 알립니다. +

+ 특히, 소스 ID는 각 계정 유형에 대해 고유해야 하고 +동기화 전체에서 안정적이어야 합니다. +

+
    +
  • + 고유: 하나의 계정에 대한 각 원시 연락처에는 나름의 소스 ID가 있어야 합니다. 개발자가 +이것을 강제 적용하지 않으면 연락처 애플리케이션에 문제를 유발하게 됩니다. + 같은 계정 유형에 대한 두 개의 원시 연락처는 소스 ID가 +같을 수 있다는 점을 유의하십시오. 예를 들어, +{@code emily.dickinson@gmail.com} 계정에 대한 원시 연락처 "Thomas Higginson"은 +{@code emilyd@gmail.com} 계정에 대한 +원시 연락처 "Thomas Higginson"과 소스 ID가 같을 수 있습니다. +
  • +
  • + 안정적: 소스 ID는 원시 연락처에 대한 온라인 서비스의 데이터 중 영구적인 +부분입니다. 예를 들어, 사용자가 앱 설정에서 연락처 저장소를 삭제하고 다시 동기화하면 +복원된 원시 연락처의 소스 ID는 전과 같아야 +합니다. 개발자가 이것을 강제 적용하지 않으면 바로 가기가 더 이상 + 작동하지 않습니다. +
  • +
+
{@link android.provider.ContactsContract.Groups}{@link android.provider.ContactsContract.GroupsColumns#GROUP_VISIBLE}"0" - 이 그룹의 연락처는 Android 애플리케이션 UI에 표시되지 않아야 합니다. + 이 열은 사용자가 특정 그룹에 연락처를 숨길 수 있게 해주는 서버와의 +호환성을 위한 것입니다. +
"1" - 이 그룹의 연락처는 애플리케이션 UI에 표시되어도 됩니다.
{@link android.provider.ContactsContract.Settings} + {@link android.provider.ContactsContract.SettingsColumns#UNGROUPED_VISIBLE} + "0" - 이 계정과 계정 유형의 경우, 그룹에 속하지 않는 연락처는 Android 애플리케이션 UI에 +표시되지 않습니다. + + 기본적으로, 연락처에 그룹에 속한 원시 데이터가 하나도 없는 경우 이는 표시되지 않습니다(원시 연락처의 그룹 구성원은 +{@link android.provider.ContactsContract.Data} 테이블에서 +하나 이상의 {@link android.provider.ContactsContract.CommonDataKinds.GroupMembership} 행으로 +표시됩니다). + 계정 유형과 계정에 대한 {@link android.provider.ContactsContract.Settings} 테이블 행에서 +이 플래그를 설정하면 그룹이 없는 연락처가 표시되도록 강제할 수 있습니다. + 이 플래그의 용도 중 하나는 그룹을 사용하지 않는 서버로부터 가져온 연락처를 표시하는 것입니다. +
+ "1" - 이 계정과 계정 유형의 경우, 그룹에 속하지 않는 연락처가 애플리케이션 UI에 +표시됩니다. +
{@link android.provider.ContactsContract.SyncState}(모두) + 이 테이블을 사용하여 동기화 어댑터의 메타데이터를 저장합니다. + + 이 테이블을 사용하면 동기화 상태와 기타 동기화 관련 데이터를 기기에 영구적으로 +저장할 수 있습니다. +
+

연락처 제공자 액세스

+

+ 이 섹션은 연락처 제공자에서 가져온 데이터에 액세스하는 법에 대한 지침을 제공하며, +요점은 다음과 같습니다. +

+
    +
  • + 엔티티 쿼리. +
  • +
  • + 일괄 수정. +
  • +
  • + 인텐트로 검색 및 수정. +
  • +
  • + 데이터 무결성. +
  • +
+

+ 동기화 어댑터에서 수정하는 방법은 +연락처 제공자 동기화 어댑터 섹션에도 자세히 설명되어 있습니다. +

+

엔티티 쿼리

+

+ 연락처 제공자 테이블은 계층을 사용하여 조직화되어 있으므로, +행과 그 행에 연결된 모든 "하위" 행을 검색하는 것이 유용할 때가 많습니다. 예를 들어, +어떤 개인의 모든 정보를 표시하려면 +하나의 {@link android.provider.ContactsContract.Contacts} 행에 대한 모든 +{@link android.provider.ContactsContract.RawContacts} 행을 검색하거나 하나의 +{@link android.provider.ContactsContract.RawContacts} 행에 대한 모든 +{@link android.provider.ContactsContract.CommonDataKinds.Email} 행을 검색하는 것이 좋습니다. 이를 용이하게 하기 위해, +연락처 제공자는 테이블 사이를 연결하는 데이터베이스 역할을 하는 엔티티 구조를 +제공합니다. +

+

+ 하나의 엔티티는 마치 상위 테이블과 그 하위 테이블에서 가져온 선택된 몇 개의 열로 이루어진 테이블과 같습니다. + 엔티티를 쿼리하는 경우, 해당 엔티티에서 사용할 수 있는 열을 기반으로 하여 예측과 검색 +기준을 제공합니다. 그 결과도 도출되는 것이 {@link android.database.Cursor}이며, +여기에 검색된 각 하위 테이블에 대해 행이 하나씩 들어있습니다. 예를 들어 연락처 이름에 대해 +{@link android.provider.ContactsContract.Contacts.Entity}를 쿼리하고 +그 이름에 대한 모든 원시 연락처에 대한 모든 {@link android.provider.ContactsContract.CommonDataKinds.Email} 행을 쿼리하면 +{@link android.database.Cursor}를 돌려받게 되며 이 안에 +각 {@link android.provider.ContactsContract.CommonDataKinds.Email}행에 대한 행이 하나씩 들어있습니다. +

+

+ 엔티티는 쿼리를 단순화합니다. 엔티티를 사용하면 연락처나 원시 연락처에 대한 모든 연락처 데이터를 +한꺼번에 검색할 수 있습니다. 즉 우선 상위 테이블을 검색하여 ID를 가져오고, 그런 다음 +하위 테이블을 그 ID로 검색하지 않아도 된다는 뜻입니다. 또한, 연락처 제공자에는 엔티티에 대한 쿼리를 +하나의 트랜잭션으로 처리하므로, 검색된 데이터가 내부적으로 일관성을 유지하도록 +보장합니다. +

+

+ 참고: 하나의 엔티티에 상위 및 하위 테이블의 모든 열이 들어있지는 않은 것이 +보통입니다. 엔티티에 대한 열 이름 상수 목록에 없는 열 이름으로 작업하려 시도하면, +{@link java.lang.Exception}이 발생합니다. +

+

+ 다음 조각은 하나의 연락처에 대해 모든 원시 연락처 행을 검색하는 방법을 나타낸 것입니다. 이 조각은 +두 개의 액티비티, 즉 "기본"과 "세부"를 가진 더 큰 애플리케이션의 일부입니다. 기본 액티비티는 +연락처 행 목록을 보여줍니다. 사용자가 하나를 선택하면, 이 액티비티가 해당 목록의 ID를 +세부 액티비티에 전송합니다. 세부 액티비티는 {@link android.provider.ContactsContract.Contacts.Entity}를 사용하여 +선택된 연락처와 연관된 모든 원시 연락처에서 +모든 데이터 행을 표시합니다. +

+

+ 이 조각은 "세부" 액티비티에서 가져온 것입니다. +

+
+...
+    /*
+     * Appends the entity path to the URI. In the case of the Contacts Provider, the
+     * expected URI is content://com.google.contacts/#/entity (# is the ID value).
+     */
+    mContactUri = Uri.withAppendedPath(
+            mContactUri,
+            ContactsContract.Contacts.Entity.CONTENT_DIRECTORY);
+
+    // Initializes the loader identified by LOADER_ID.
+    getLoaderManager().initLoader(
+            LOADER_ID,  // The identifier of the loader to initialize
+            null,       // Arguments for the loader (in this case, none)
+            this);      // The context of the activity
+
+    // Creates a new cursor adapter to attach to the list view
+    mCursorAdapter = new SimpleCursorAdapter(
+            this,                        // the context of the activity
+            R.layout.detail_list_item,   // the view item containing the detail widgets
+            mCursor,                     // the backing cursor
+            mFromColumns,                // the columns in the cursor that provide the data
+            mToViews,                    // the views in the view item that display the data
+            0);                          // flags
+
+    // Sets the ListView's backing adapter.
+    mRawContactList.setAdapter(mCursorAdapter);
+...
+@Override
+public Loader<Cursor> onCreateLoader(int id, Bundle args) {
+
+    /*
+     * Sets the columns to retrieve.
+     * RAW_CONTACT_ID is included to identify the raw contact associated with the data row.
+     * DATA1 contains the first column in the data row (usually the most important one).
+     * MIMETYPE indicates the type of data in the data row.
+     */
+    String[] projection =
+        {
+            ContactsContract.Contacts.Entity.RAW_CONTACT_ID,
+            ContactsContract.Contacts.Entity.DATA1,
+            ContactsContract.Contacts.Entity.MIMETYPE
+        };
+
+    /*
+     * Sorts the retrieved cursor by raw contact id, to keep all data rows for a single raw
+     * contact collated together.
+     */
+    String sortOrder =
+            ContactsContract.Contacts.Entity.RAW_CONTACT_ID +
+            " ASC";
+
+    /*
+     * Returns a new CursorLoader. The arguments are similar to
+     * ContentResolver.query(), except for the Context argument, which supplies the location of
+     * the ContentResolver to use.
+     */
+    return new CursorLoader(
+            getApplicationContext(),  // The activity's context
+            mContactUri,              // The entity content URI for a single contact
+            projection,               // The columns to retrieve
+            null,                     // Retrieve all the raw contacts and their data rows.
+            null,                     //
+            sortOrder);               // Sort by the raw contact ID.
+}
+
+

+ 로딩이 완료되면, {@link android.app.LoaderManager}가 +{@link android.app.LoaderManager.LoaderCallbacks#onLoadFinished(Loader, D) +onLoadFinished()}에 대한 콜백을 호출합니다. 이 메서드로 수신되는 인수 중 하나가 +{@link android.database.Cursor}로, 여기에 쿼리 결과도 함께 들어옵니다. 개발자 본인의 앱에서는, 이 +{@link android.database.Cursor}에서 데이터를 가져와 이를 표시할 수도 있고 여기에 작업을 더할 수도 있습니다. +

+

일괄 수정

+

+ 연락처 제공자에서 데이터를 삽입, 업데이트 및 삭제하는 경우 가급적이면 +"일괄 모드"를 쓰는 것이 좋습니다. 이때 +{@link android.content.ContentProviderOperation} 객체의 {@link java.util.ArrayList}를 생성하고 +{@link android.content.ContentResolver#applyBatch(String, ArrayList) applyBatch()}를 호출하면 됩니다. 연락처 제공자는 + +{@link android.content.ContentResolver#applyBatch(String, ArrayList) applyBatch()}에서의 모든 작업을 +하나의 트랜잭션으로 수행하기 때문에, 수정한 내용이 일관되지 않은 상태로 연락처 리포지토리를 +떠날 일이 결코 없습니다. 일괄 수정을 사용하면 원시 연락처와 그 세부 데이터를 동시에 삽입하는 것도 +쉽습니다. +

+

+ 참고: 하나의 원시 연락처를 수정하려면 수정 작업을 앱에서 처리하는 것보다는 +인텐트를 기기의 연락처 애플리케이션에 보내는 방안을 고려하십시오. +이렇게 하는 방법이 +인텐트로 검색 및 수정 섹션에 자세히 설명되어 있습니다. +

+

양보 지점

+

+ 대량의 작업이 들어있는 일괄 수정은 다른 프로세스를 차단하므로, +그 결과 전반적으로 불량한 사용자 환경을 유발할 수 있습니다. 수행하고자 하는 모든 수정 작업을 가능한 한 +적은 수의 별도의 목록으로 정리하고 그와 동시에 이 작업이 시스템을 차단하지 못하도록 방지하려면 +하나 이상의 작업에 양보 지점을 설정해야 합니다. + 양보 지점은 {@link android.content.ContentProviderOperation#isYieldAllowed()} 값이 true로 설정된 {@link android.content.ContentProviderOperation} 객체입니다. + + 연락처 제공자가 양보 지점을 만나면 +다른 프로세스가 실행되도록 작업을 잠시 멈추고 현재 트랜잭션을 종료합니다. 제공자가 다시 시작되면, 이는 +{@link java.util.ArrayList}에서 다음 작업을 계속 진행하고 +새 트랜잭션을 시작합니다. +

+

+ 양보 지점을 사용하면 그 결과 +{@link android.content.ContentResolver#applyBatch(String, ArrayList) applyBatch()}로의 호출 한 건당 하나 이상의 트랜잭션이 생기는 것은 사실입니다. 이 때문에, + 양보 지점은 관련된 행 한 세트에서 마지막 작업에 설정해야 합니다. + 예를 들어, 원시 연락처 행과 관련된 데이터 행을 추가하는 세트의 마지막 작업에 +양보 지점을 설정하거나, 하나의 연락처와 관련된 행 한 세트의 +마지막 작업에 양보 지점을 설정해야 합니다. +

+

+ 양보 지점은 원자성 작업의 단위이기도 합니다. 두 개의 양보 지점 사이를 향한 액세스는 모두 +한 가지 단위로서 성공 또는 실패합니다. 양보 지점을 설정하지 않는 경우, 가장 작은 +원자성 작업은 작업 전체가 됩니다. 양보 지점을 사용하면, 작업이 +시스템 성능을 저하하지 않도록 방지하는 동시에 작업의 하위 세트가 원자성 작업이도록 +보장할 수 있습니다. +

+

수정 역참조

+

+ 새로운 원시 연락처 행과 관련 데이터 행을 +일련의 {@link android.content.ContentProviderOperation} 개체로 삽입할 때는, + 원시 연락처의 +{@code android.provider.BaseColumns#_ID} 값을 +{@link android.provider.ContactsContract.DataColumns#RAW_CONTACT_ID} 값으로 삽입하여 데이터 행과 원시 연락처 행을 연결해야 합니다. 그러나, 이 값은 +데이터 행에 대하여 {@link android.content.ContentProviderOperation}을 +생성하는 경우에는 사용할 수 없습니다. 원시 연락처 행에 대해 {@link android.content.ContentProviderOperation} +을 아직 적용하지 않았기 때문입니다. 이 문제를 해결하려면 +{@link android.content.ContentProviderOperation.Builder} 클래스에 +{@link android.content.ContentProviderOperation.Builder#withValueBackReference(String, int) withValueBackReference()} 메서드가 있어야 합니다. + 이 메서드를 사용하면 열을 이전 작업의 결과로 삽입 또는 수정할 수 +있습니다. +

+

+ {@link android.content.ContentProviderOperation.Builder#withValueBackReference(String, int) withValueBackReference()} +메서드에는 인수가 두 가지 있습니다. +

+
+
+ key +
+
+ 키-값 쌍의 키입니다. 이 인수의 값은 수정하는 테이블의 +열 이름이어야 합니다. +
+
+ previousResult +
+
+ +{@link android.content.ContentResolver#applyBatch(String, ArrayList) applyBatch()}의 {@link android.content.ContentProviderResult} 객체 배열에 있는 +값의 0 기반 색인입니다. +일괄 작업이 적용되면 각 작업의 결과가 +결과의 중간 배열에 저장됩니다. previousResult 값은 +이러한 결과 중 하나의 색인이고, 이는 key 값으로 +검색 및 저장됩니다. 이것을 사용하면 새 원시 연락처 레코드를 삽입하고 +{@code android.provider.BaseColumns#_ID} 값을 다시 가져온 뒤, +{@link android.provider.ContactsContract.Data} 행을 추가할 때 해당 값을 "역참조"할 수 있게 해줍니다. +

+ +{@link android.content.ContentResolver#applyBatch(String, ArrayList) applyBatch()}를 처음 호출할 때, +개발자가 제공하는 {@link android.content.ContentProviderOperation} 객체의 {@link java.util.ArrayList} 크기와 같은 크기로 +전체 결과 배열이 생성됩니다. 그러나 +결과 배열의 모든 요소는 null로 설정되고, +아직 적용되지 않은 작업 결과에 대한 역참조를 수행하려 시도하면 +{@link android.content.ContentProviderOperation.Builder#withValueBackReference(String, int) withValueBackReference()}가 +{@link java.lang.Exception}을 발생시킵니다. + +

+
+
+

+ 다음 조각은 새로운 원시 연락처와 데이터를 일괄 삽입하는 방법을 나타낸 것입니다. 여기에는 +양보 지점을 지정하고 역참조를 사용하는 코드가 포함되어 있습니다. 이 조각은 +createContacEntry() 메서드의 확장 버전이며, 이는 + + Contact Manager 샘플 애플리케이션에 있는 ContactAdder 클래스의 +일부입니다. +

+

+ 첫 번째 조각은 UI에서 연락처 데이터를 검색합니다. 이 시점에서 사용자는 이미 +새로운 원시 연락처를 추가할 계정을 선택하였습니다. +

+
+// Creates a contact entry from the current UI values, using the currently-selected account.
+protected void createContactEntry() {
+    /*
+     * Gets values from the UI
+     */
+    String name = mContactNameEditText.getText().toString();
+    String phone = mContactPhoneEditText.getText().toString();
+    String email = mContactEmailEditText.getText().toString();
+
+    int phoneType = mContactPhoneTypes.get(
+            mContactPhoneTypeSpinner.getSelectedItemPosition());
+
+    int emailType = mContactEmailTypes.get(
+            mContactEmailTypeSpinner.getSelectedItemPosition());
+
+

+ 다음 조각은 +{@link android.provider.ContactsContract.RawContacts} 테이블에 원시 연락처 행을 삽입하는 작업을 생성합니다. +

+
+    /*
+     * Prepares the batch operation for inserting a new raw contact and its data. Even if
+     * the Contacts Provider does not have any data for this person, you can't add a Contact,
+     * only a raw contact. The Contacts Provider will then add a Contact automatically.
+     */
+
+     // Creates a new array of ContentProviderOperation objects.
+    ArrayList<ContentProviderOperation> ops =
+            new ArrayList<ContentProviderOperation>();
+
+    /*
+     * Creates a new raw contact with its account type (server type) and account name
+     * (user's account). Remember that the display name is not stored in this row, but in a
+     * StructuredName data row. No other data is required.
+     */
+    ContentProviderOperation.Builder op =
+            ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI)
+            .withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, mSelectedAccount.getType())
+            .withValue(ContactsContract.RawContacts.ACCOUNT_NAME, mSelectedAccount.getName());
+
+    // Builds the operation and adds it to the array of operations
+    ops.add(op.build());
+
+

+ 그런 다음, 코드가 표시 이름, 전화 및 이메일 행에 대한 데이터 행을 생성합니다. +

+

+ 각 작업 빌더 개체는 +{@link android.content.ContentProviderOperation.Builder#withValueBackReference(String, int) withValueBackReference()}를 +사용하여 +{@link android.provider.ContactsContract.DataColumns#RAW_CONTACT_ID}를 가져옵니다. 참조는 +첫 번째 작업의 {@link android.content.ContentProviderResult} 객체를 다시 가리키고, +이것이 원시 연락처 행을 추가한 뒤 이의 새 {@code android.provider.BaseColumns#_ID} +값을 반환합니다. 그 결과, 각 데이터 행은 자동으로 자신의 +{@link android.provider.ContactsContract.DataColumns#RAW_CONTACT_ID} +에 의해 자신이 속하는 새 {@link android.provider.ContactsContract.RawContacts} 행에 연결됩니다. +

+

+ 이메일 행을 추가하는 {@link android.content.ContentProviderOperation.Builder} 객체는 +양보 지점을 설정하는 {@link android.content.ContentProviderOperation.Builder#withYieldAllowed(boolean) +withYieldAllowed()}로 플래그 표시합니다. +

+
+    // Creates the display name for the new raw contact, as a StructuredName data row.
+    op =
+            ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
+            /*
+             * withValueBackReference sets the value of the first argument to the value of
+             * the ContentProviderResult indexed by the second argument. In this particular
+             * call, the raw contact ID column of the StructuredName data row is set to the
+             * value of the result returned by the first operation, which is the one that
+             * actually adds the raw contact row.
+             */
+            .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
+
+            // Sets the data row's MIME type to StructuredName
+            .withValue(ContactsContract.Data.MIMETYPE,
+                    ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE)
+
+            // Sets the data row's display name to the name in the UI.
+            .withValue(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, name);
+
+    // Builds the operation and adds it to the array of operations
+    ops.add(op.build());
+
+    // Inserts the specified phone number and type as a Phone data row
+    op =
+            ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
+            /*
+             * Sets the value of the raw contact id column to the new raw contact ID returned
+             * by the first operation in the batch.
+             */
+            .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
+
+            // Sets the data row's MIME type to Phone
+            .withValue(ContactsContract.Data.MIMETYPE,
+                    ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE)
+
+            // Sets the phone number and type
+            .withValue(ContactsContract.CommonDataKinds.Phone.NUMBER, phone)
+            .withValue(ContactsContract.CommonDataKinds.Phone.TYPE, phoneType);
+
+    // Builds the operation and adds it to the array of operations
+    ops.add(op.build());
+
+    // Inserts the specified email and type as a Phone data row
+    op =
+            ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
+            /*
+             * Sets the value of the raw contact id column to the new raw contact ID returned
+             * by the first operation in the batch.
+             */
+            .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
+
+            // Sets the data row's MIME type to Email
+            .withValue(ContactsContract.Data.MIMETYPE,
+                    ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE)
+
+            // Sets the email address and type
+            .withValue(ContactsContract.CommonDataKinds.Email.ADDRESS, email)
+            .withValue(ContactsContract.CommonDataKinds.Email.TYPE, emailType);
+
+    /*
+     * Demonstrates a yield point. At the end of this insert, the batch operation's thread
+     * will yield priority to other threads. Use after every set of operations that affect a
+     * single contact, to avoid degrading performance.
+     */
+    op.withYieldAllowed(true);
+
+    // Builds the operation and adds it to the array of operations
+    ops.add(op.build());
+
+

+ 마지막 조각은 새로운 원시 연락처와 데이터 행을 삽입하는 +{@link android.content.ContentResolver#applyBatch(String, ArrayList) applyBatch()}에 대한 호출을 +나타낸 것입니다. +

+
+    // Ask the Contacts Provider to create a new contact
+    Log.d(TAG,"Selected account: " + mSelectedAccount.getName() + " (" +
+            mSelectedAccount.getType() + ")");
+    Log.d(TAG,"Creating contact: " + name);
+
+    /*
+     * Applies the array of ContentProviderOperation objects in batch. The results are
+     * discarded.
+     */
+    try {
+
+            getContentResolver().applyBatch(ContactsContract.AUTHORITY, ops);
+    } catch (Exception e) {
+
+            // Display a warning
+            Context ctx = getApplicationContext();
+
+            CharSequence txt = getString(R.string.contactCreationFailure);
+            int duration = Toast.LENGTH_SHORT;
+            Toast toast = Toast.makeText(ctx, txt, duration);
+            toast.show();
+
+            // Log exception
+            Log.e(TAG, "Exception encountered while inserting contact: " + e);
+    }
+}
+
+

+ 일괄 작업을 사용하면 낙관적 동시성 제어도 구현할 수 있습니다. +이것은 기본 리포지토리를 잠그지 않고도 수정 트랜잭션을 적용할 수 있는 메서드입니다. + 이 메서드를 사용하려면 트랜잭션을 적용하고 동시에 발생한 +다른 수정 사항이 있는지 확인합니다. 부합하지 않는 수정이 발생한 것을 발견하면 +트랜잭션을 롤백하고 다시 시도합니다. +

+

+ 낙관적 동시성 제어는 한 번에 한 명의 사용자만 존재하고 데이터 리포지토리에 대한 동시 액세스가 드문 모바일 기기에 +유용합니다. 잠금을 사용하지 않으므로 +잠금을 설정하거나 다른 트랜잭션이 잠금을 해제하기를 기다리면서 시간을 낭비하지 않습니다. +

+

+ 하나의 +{@link android.provider.ContactsContract.RawContacts} 행을 업데이트하면서 동시에 낙관적 동시성 제어를 사용하려면, 다음 단계를 따르십시오. +

+
    +
  1. + 검색하는 다른 데이터와 함께 행 연락처의 {@link android.provider.ContactsContract.SyncColumns#VERSION}을 +검색합니다. +
  2. +
  3. + 제약을 강제 적용하는 데 적합한 +{@link android.content.ContentProviderOperation.Builder} 객체를 하나 생성하되 +{@link android.content.ContentProviderOperation#newAssertQuery(Uri)} 메서드를 사용합니다. 콘텐츠 URI의 경우, +{@link android.provider.ContactsContract.RawContacts#CONTENT_URI + RawContacts.CONTENT_URI}를 사용하되 +이에 추가된 원시 데이터의 {@code android.provider.BaseColumns#_ID}를 함께 씁니다. +
  4. +
  5. + {@link android.content.ContentProviderOperation.Builder} 개체의 경우, +{@link android.content.ContentProviderOperation.Builder#withValue(String, Object) +withValue()}를 호출하여 방금 검색한 버전 번호와 {@link android.provider.ContactsContract.SyncColumns#VERSION} 열을 +비교합니다. +
  6. +
  7. + 같은 {@link android.content.ContentProviderOperation.Builder}의 경우, +{@link android.content.ContentProviderOperation.Builder#withExpectedCount(int) +withExpectedCount()}를 호출하여 이 어설션이 테스트하는 행이 하나뿐이도록 보장합니다. +
  8. +
  9. + {@link android.content.ContentProviderOperation.Builder#build()}를 호출하여 +{@link android.content.ContentProviderOperation} 객체를 생성하고, 이 객체를 +{@link android.content.ContentResolver#applyBatch(String, ArrayList) applyBatch()}에 전달하는 {@link java.util.ArrayList}의 첫 번째 객체로 추가합니다. + +
  10. +
  11. + 일괄 트랜잭션을 적용합니다. +
  12. +
+

+ 행을 읽고 수정하려고 시도하는 사이에 다른 작업이 원시 연락처 행을 업데이트하면, +"어설션" {@link android.content.ContentProviderOperation}은 +실패하고 전체 일괄 작업이 취소됩니다. 그러면 일괄 작업을 다시 시도하거나 +다른 조치를 취하기로 선택할 수 있습니다. +

+

+ 다음 조각은 {@link android.content.CursorLoader}를 사용하여 단일 원시 연락처를 쿼리한 후 +"어설션" {@link android.content.ContentProviderOperation}을 +생성하는 방법을 나타낸 것입니다. +

+
+/*
+ * The application uses CursorLoader to query the raw contacts table. The system calls this method
+ * when the load is finished.
+ */
+public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
+
+    // Gets the raw contact's _ID and VERSION values
+    mRawContactID = cursor.getLong(cursor.getColumnIndex(BaseColumns._ID));
+    mVersion = cursor.getInt(cursor.getColumnIndex(SyncColumns.VERSION));
+}
+
+...
+
+// Sets up a Uri for the assert operation
+Uri rawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, mRawContactID);
+
+// Creates a builder for the assert operation
+ContentProviderOperation.Builder assertOp = ContentProviderOperation.netAssertQuery(rawContactUri);
+
+// Adds the assertions to the assert operation: checks the version and count of rows tested
+assertOp.withValue(SyncColumns.VERSION, mVersion);
+assertOp.withExpectedCount(1);
+
+// Creates an ArrayList to hold the ContentProviderOperation objects
+ArrayList ops = new ArrayList<ContentProviderOperationg>;
+
+ops.add(assertOp.build());
+
+// You would add the rest of your batch operations to "ops" here
+
+...
+
+// Applies the batch. If the assert fails, an Exception is thrown
+try
+    {
+        ContentProviderResult[] results =
+                getContentResolver().applyBatch(AUTHORITY, ops);
+
+    } catch (OperationApplicationException e) {
+
+        // Actions you want to take if the assert operation fails go here
+    }
+
+

인텐트로 검색 및 수정

+

+ 기기 연락처 애플리케이션에 인텐트를 전송하면 연락처 제공자에 +간접적으로 액세스할 수 있습니다. 인텐트는 기기의 연락처 애플리케이션 UI를 시작하고, 여기서 사용자는 +연락처 관련 작업을 할 수 있습니다. 사용자가 이런 액세스 유형을 가지고 할 수 있는 일은 다음과 같습니다. +

    +
  • 목록에서 연락처를 선택하고 추가 작업을 위해 앱에 반환시킵니다.
  • +
  • 기존 연락처의 데이터를 편집합니다.
  • +
  • 새 원시 데이터를 해당되는 계정 중 아무 곳에나 삽입합니다.
  • +
  • 연락처 또는 연락처 데이터를 삭제합니다.
  • +
+

+ 사용자가 데이터를 삽입하거나 업데이트하고 있다면, 먼저 데이터를 수집하고 +인텐트의 일부로 전송할 수 있습니다. +

+

+ 인텐트를 사용하여 기기의 연락처 애플리케이션을 통해 연락처 제공자에 액세스하는 경우 +제공자에 액세스하기 위해 개발자 나름의 UI나 코드를 작성하지 않아도 됩니다. 제공자에 대한 읽기 또는 쓰기 권한도 요청하지 않아도 +됩니다. 기기 연락처 애플리케이션은 +연락처에 대한 읽기 권한을 위임할 수 있고, 다른 애플리케이션을 통해 수정하기 때문에 +쓰기 권한도 필요 없습니다. +

+

+ 제공자에 액세스하기 위해 인텐트를 전송하는 일반적인 과정은 +"인텐트를 통한 데이터 액세스" 섹션의 +콘텐츠 제공자 기본 정보 가이드에 상세히 설명되어 있습니다. 이용 가능한 작업에 대해 사용하는 동작, +MIME 유형 및 데이터 값은 표 4에 요약되어 있고, + +{@link android.content.Intent#putExtra(String, String) putExtra()}와 함께 사용할 수 있는 추가 값은 +{@link android.provider.ContactsContract.Intents.Insert}의 참조 문서에 나열되어 있습니다. +

+

+ 표 4. 연락처 제공자 인텐트 +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
작업동작데이터MIME 유형참고
목록에서 연락처 선택{@link android.content.Intent#ACTION_PICK} + 다음 중 하나로 정해집니다. +
    +
  • +{@link android.provider.ContactsContract.Contacts#CONTENT_URI Contacts.CONTENT_URI}, +이는 연락처 목록을 표시합니다. +
  • +
  • +{@link android.provider.ContactsContract.CommonDataKinds.Phone#CONTENT_URI Phone.CONTENT_URI}, +이는 원시 연락처에 대한 전화 번호 목록을 표시합니다. +
  • +
  • +{@link android.provider.ContactsContract.CommonDataKinds.StructuredPostal#CONTENT_URI +StructuredPostal.CONTENT_URI}, +이는 원시 연락처에 대한 우편 주소 목록을 표시합니다. +
  • +
  • +{@link android.provider.ContactsContract.CommonDataKinds.Email#CONTENT_URI Email.CONTENT_URI}, +이는 원시 연락처에 대한 이메일 주소 목록을 표시합니다. +
  • +
+
+ 사용하지 않음 + + 개발자가 제공하는 콘텐츠 URI에 따라 원시 연락처 목록이나 원시 연락처에서 가져온 +데이터 목록을 표시합니다. +

+ +{@link android.app.Activity#startActivityForResult(Intent, int) startActivityForResult()} 호출, +이는 선택한 행의 콘텐츠 URI를 반환합니다. URI의 형태는 +테이블의 콘텐츠 URI에 행의 LOOKUP_ID를 추가한 것입니다. + 기기의 연락처 앱은 액티비티 수명 동안 이 콘텐츠 URI에 +읽기 및 쓰기 권한을 위임합니다. 자세한 내용은 + +콘텐츠 제공자 기본 정보 가이드를 참조하십시오. +

+
새 원시 연락처 삽입{@link android.provider.ContactsContract.Intents.Insert#ACTION Insert.ACTION}N/A + {@link android.provider.ContactsContract.RawContacts#CONTENT_TYPE +RawContacts.CONTENT_TYPE}, 일련의 원시 연락처에 대한 MIME 유형입니다. + + 기기의 연락처 애플리케이션의 연락처 추가 화면을 표시합니다. 개발자가 +인텐트에 추가하는 추가 사항 값이 표시됩니다. +{@link android.app.Activity#startActivityForResult(Intent, int) startActivityForResult()}와 함께 전송되는 경우, +새로 추가된 원시 연락처의 콘텐츠 URI는 +"데이터" 필드의 {@link android.content.Intent} 인수에 있는 액티비티의 {@link android.app.Activity#onActivityResult(int, int, Intent) onActivityResult()} +콜백 메서드로 +다시 전달됩니다. 값을 가져오려면, {@link android.content.Intent#getData()}를 호출합니다. +
연락처 편집{@link android.content.Intent#ACTION_EDIT} + 연락처에 대한 +{@link android.provider.ContactsContract.Contacts#CONTENT_LOOKUP_URI}입니다. 편집자 액티비티를 사용하면 사용자가 이 연락처와 관련된 데이터를 어느 것이든 +편집할 수 있습니다. + + {@link android.provider.ContactsContract.Contacts#CONTENT_ITEM_TYPE +Contacts.CONTENT_ITEM_TYPE}, 하나의 연락처입니다. + 연락처 애플리케이션에 연락처 편집 화면을 표시합니다. 개발자가 +인텐트에 추가하는 추가 사항 값이 표시됩니다. 사용자가 완료를 클릭하여 편집 내용을 저장하면, +액티비티가 전경으로 돌아옵니다. +
데이터를 추가할 수 있는 선택기를 표시합니다.{@link android.content.Intent#ACTION_INSERT_OR_EDIT} + N/A + + {@link android.provider.ContactsContract.Contacts#CONTENT_ITEM_TYPE} + + 이 인텐트는 항상 연락처 앱의 선택기 화면을 표시합니다. 사용자는 +편집할 연락처를 선택하거나 새 연락처를 추가할 수 있습니다. 사용자의 선택에 따라 +편집 또는 추가 화면이 나타나고 개발자가 인텐트에 전달하는 추가 사항 데이터가 +표시됩니다. 앱이 이메일이나 전화 번호 등의 연락처 데이터를 표시하는 경우, +이 인텐트를 사용하면 사용자가 기존 연락처에 데이터를 추가할 수 +있습니다. +

+ 참고: 이 인텐트의 추가 사항에서는 이름 값을 전송하지 않아도 됩니다. +사용자가 항상 기존의 이름을 선택하거나 새 이름을 추가하기 때문입니다. 게다가, +개발자가 이름을 전송하고 사용자가 편집을 선택하면 연락처 앱은 개발자가 전송한 이름을 표시하면서 +이전 값을 재정의하게 됩니다. 사용자가 +이를 눈치채지 못하고 편집 내용을 저장하면 이전 값은 손실됩니다. +

+
+

+ 기기의 연락처 앱은 개발자가 인텐트로 원시 연락처 또는 그에 속한 모든 데이터를 삭제하도록 +허용하지 않습니다. 대신, 원시 연락처를 삭제하려면 +{@link android.content.ContentResolver#delete(Uri, String, String[]) ContentResolver.delete()} +또는 {@link android.content.ContentProviderOperation#newDelete(Uri) +ContentProviderOperation.newDelete()}를 사용합니다. +

+

+ 다음 조각은 새로운 원시 연락처와 +데이터를 삽입하는 인텐트를 구성, 전송하는 방법을 나타낸 것입니다. +

+
+// Gets values from the UI
+String name = mContactNameEditText.getText().toString();
+String phone = mContactPhoneEditText.getText().toString();
+String email = mContactEmailEditText.getText().toString();
+
+String company = mCompanyName.getText().toString();
+String jobtitle = mJobTitle.getText().toString();
+
+// Creates a new intent for sending to the device's contacts application
+Intent insertIntent = new Intent(ContactsContract.Intents.Insert.ACTION);
+
+// Sets the MIME type to the one expected by the insertion activity
+insertIntent.setType(ContactsContract.RawContacts.CONTENT_TYPE);
+
+// Sets the new contact name
+insertIntent.putExtra(ContactsContract.Intents.Insert.NAME, name);
+
+// Sets the new company and job title
+insertIntent.putExtra(ContactsContract.Intents.Insert.COMPANY, company);
+insertIntent.putExtra(ContactsContract.Intents.Insert.JOB_TITLE, jobtitle);
+
+/*
+ * Demonstrates adding data rows as an array list associated with the DATA key
+ */
+
+// Defines an array list to contain the ContentValues objects for each row
+ArrayList<ContentValues> contactData = new ArrayList<ContentValues>();
+
+
+/*
+ * Defines the raw contact row
+ */
+
+// Sets up the row as a ContentValues object
+ContentValues rawContactRow = new ContentValues();
+
+// Adds the account type and name to the row
+rawContactRow.put(ContactsContract.RawContacts.ACCOUNT_TYPE, mSelectedAccount.getType());
+rawContactRow.put(ContactsContract.RawContacts.ACCOUNT_NAME, mSelectedAccount.getName());
+
+// Adds the row to the array
+contactData.add(rawContactRow);
+
+/*
+ * Sets up the phone number data row
+ */
+
+// Sets up the row as a ContentValues object
+ContentValues phoneRow = new ContentValues();
+
+// Specifies the MIME type for this data row (all data rows must be marked by their type)
+phoneRow.put(
+        ContactsContract.Data.MIMETYPE,
+        ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE
+);
+
+// Adds the phone number and its type to the row
+phoneRow.put(ContactsContract.CommonDataKinds.Phone.NUMBER, phone);
+
+// Adds the row to the array
+contactData.add(phoneRow);
+
+/*
+ * Sets up the email data row
+ */
+
+// Sets up the row as a ContentValues object
+ContentValues emailRow = new ContentValues();
+
+// Specifies the MIME type for this data row (all data rows must be marked by their type)
+emailRow.put(
+        ContactsContract.Data.MIMETYPE,
+        ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE
+);
+
+// Adds the email address and its type to the row
+emailRow.put(ContactsContract.CommonDataKinds.Email.ADDRESS, email);
+
+// Adds the row to the array
+contactData.add(emailRow);
+
+/*
+ * Adds the array to the intent's extras. It must be a parcelable object in order to
+ * travel between processes. The device's contacts app expects its key to be
+ * Intents.Insert.DATA
+ */
+insertIntent.putParcelableArrayListExtra(ContactsContract.Intents.Insert.DATA, contactData);
+
+// Send out the intent to start the device's contacts app in its add contact activity.
+startActivity(insertIntent);
+
+

데이터 무결성

+

+ 연락처 리포지토리에는 사용자 측에서 올바르고 최신일 것으로 기대하는 중요하고 민감한 데이터가 들어있으므로 +연락처 제공자는 데이터 무결성에 대한 잘 정의된 규칙이 있습니다. 개발자 +여러분에게는 연락처 데이터를 수정할 때 이와 같은 규칙을 준수할 의무가 있습니다. 아래에는 중요한 규칙을 +몇 가지 나열해 놓았습니다. +

+
+
+ {@link android.provider.ContactsContract.RawContacts} 행을 추가할 때마다 언제나 +{@link android.provider.ContactsContract.CommonDataKinds.StructuredName} 행을 추가합니다. +
+
+ {@link android.provider.ContactsContract.Data} 테이블에 {@link android.provider.ContactsContract.CommonDataKinds.StructuredName} 행이 없는 {@link android.provider.ContactsContract.RawContacts} 행이 있으면 +통합 과정에서 +문제가 발생할 수 있습니다. + +
+
+ 언제나 새로운 {@link android.provider.ContactsContract.Data} 행을 +해당 상위 {@link android.provider.ContactsContract.RawContacts} 행에 연결합니다. +
+
+ {@link android.provider.ContactsContract.RawContacts}에 연결되지 않은 {@link android.provider.ContactsContract.Data} 행은 +기기 연락처 애플리케이션에 표시되지 않고, +동기화 어댑터에서 문제를 발생시킬 수 있습니다. +
+
+ 개발자 본인의 소유인 원시 연락처에 대한 데이터만 변경하십시오. +
+
+ 연락처 제공자는 보통 여러 가지 서로 다른 계정 유형/온라인 서비스에서 가져온 +데이터를 관리한다는 점을 명심하십시오. 개발자의 애플리케이션은 본인에게 +속한 행에 대한 데이터만 수정 또는 삭제하도록 확실히 해두어야 하며, 데이터를 삽입할 때에도 개발자 본인이 +제어하는 계정 유형과 이름만 사용해야 합니다. +
+
+ 권한, 콘텐츠 URI, URI 경로, 열 이름, MIME 유형 및 +{@link android.provider.ContactsContract.CommonDataKinds.CommonColumns#TYPE} 값에 대해서는 항상 +{@link android.provider.ContactsContract} 및 그 하위 클래스에서 정의한 상수를 사용합니다. +
+
+ 이런 상수를 사용하면 오류를 피하는 데 도움이 됩니다. 이런 상수 중 하나라도 사용하지 않게 되는 경우 컴파일러로부터 +알림을 받기도 합니다. +
+
+

사용자 지정 데이터 행

+

+ 사용자 지정 MIME 유형을 생성하여 사용하면, +{@link android.provider.ContactsContract.Data} 테이블에 있는 본인의 데이터 행을 삽입, 편집, 삭제 및 검색할 수 있습니다. 개발자의 행은 +{@link android.provider.ContactsContract.DataColumns}에서 +정의된 열만 사용하도록 제한되어 있습니다. 다만 나름의 유형별 열 이름을 +기본 열 이름에 매핑할 수는 있습니다. 기기의 연락처 애플리케이션에서는 +개발자의 행에 대한 데이터가 표시는 되지만 편집이나 삭제는 할 수 없고, 사용자가 추가 데이터를 +추가할 수도 없습니다. 사용자가 개발자의 사용자 지정 데이터 행을 수정하도록 허용하려면, 본인의 애플리케이션에 +편집기 액티비티를 제공해야 합니다. +

+

+ 개발자의 사용자 지정 데이터를 표시하려면, <ContactsAccountType> 요소와 하나 이상의 <ContactsDataKind> 하위 요소를 포함하는 contacts.xml 파일을 +제공합니다. + 이 내용은 +<ContactsDataKind> element 섹션에 자세히 설명되어 있습니다. +

+

+ 사용자 지정 MIME 유형에 대해 자세히 알아보려면, + +콘텐츠 제공자 생성 가이드를 참조하십시오. +

+

연락처 제공자 동기화 어댑터

+

+ 연락처 제공자는 기기와 온라인 서비스 사이에서 연락처 데이터의 동기화를 +처리한다는 구체적인 목적을 두고 디자인된 것입니다. 이것을 사용하면 사용자가 기존의 +데이터를 새 기기에 다운로드할 수도 있고, 기존의 데이터를 새 계정에 업로드할 수도 있습니다. + 동기화를 사용하면 사용자가 추가나 변경의 출처와 관계 없이 최신 데이터를 +편리하게 사용할 수 있게 보장하기도 합니다. 동기화의 또 다른 장점은 +기기가 네트워크에 연결되어 있지 않더라도 연락처 데이터를 사용할 수 있다는 것입니다. +

+

+ 다양한 방식으로 동기화를 구현할 수 있지만, Android 시스템은 +플러그인 동기화 프레임워크를 제공하여 다음과 같은 작업들을 자동화해줍니다. +

    + +
  • + 네트워크 가용성을 확인합니다. +
  • +
  • + 사용자 기본 설정에 따라 동기화를 예약하고 실행합니다. +
  • +
  • + 중단된 동기화를 다시 시작합니다. +
  • +
+

+ 이 프레임워크를 사용하려면 동기화 어댑터 플러그인은 개발자가 직접 제공해야 합니다. 동기화 어댑터는 +서비스와 콘텐츠 제공자마다 각기 다르지만, 같은 서비스에 대해 여러 개의 계정 이름을 처리할 수 있습니다. 이 +프레임워크 또한 같은 서비스와 제공자에 대해 여러 개의 동기화 어댑터를 허용합니다. +

+

동기화 어댑터 클래스 및 파일

+

+ 동기화 어댑터를 +{@link android.content.AbstractThreadedSyncAdapter}의 +하위 클래스로 구현하고 이를Android 애플리케이션의 일부로 설치합니다. 시스템은 애플리케이션 매니페스트의 요소와 매니페스트가 가리키는 +특수 XML 파일에서 동기화 어댑터에 관한 정보를 얻습니다. XML 파일은 +온라인 서비스와 콘텐츠 제공자의 권한에 대한 계정 유형을 정의하고, +이들이 함께 어댑터를 고유하게 식별합니다. 동기화 어댑터가 활성화되려면 사용자가 +동기화 어댑터의 계정 유형에 대해 계정을 추가하고 해당 동기화 어댑터와 동기화하는 콘텐츠 제공자의 동기화를 +활성화해야 합니다. 이 시점에서, 시스템이 어댑터 관리를 시작하고, +콘텐츠 제공자와 서버 사이에서 동기화가 필요할 때 이를 호출합니다. +

+

+ 참고: 계정 유형을 동기화 어댑터 식별의 일부로 사용하면 +시스템이 동일한 같은 조직에서 여러 서비스에 액세스하는 동기화 어댑터를 +감지하고 그룹화할 수 있습니다. 예를 들어, Google 온라인 서비스의 동기화 어댑터는 계정 유형이 모두 +com.google로 같습니다. 사용자가 기기에 Google 계정을 추가하면, +Google 서비스에 설치된 모든 동기화 어댑터가 함께 목록으로 표시됩니다. 목록에 게재된 각 동기화는 +기기에서 각기 다른 콘텐츠 제공자와 동기화합니다. +

+

+ 대부분의 서비스에서는 사용자가 데이터에 액세스하기 전에 +ID를 확인해야 하기 때문에 Android에서는 동기화 어댑터 프레임워크와 비슷하면서 종종 이와 함께 쓰이기도 하는 +인증 프레임워크를 제공합니다. 인증 프레임워크는 +{@link android.accounts.AbstractAccountAuthenticator}의 하위 클래스인 +플러그인 인증자를 사용합니다. 인증자는 다음 절차에 따라 +사용자의 ID를 확인합니다. +

    +
  1. + 사용자 이름, 암호 또는 유사한 정보(사용자의 +자격 증명)를 수집합니다. +
  2. +
  3. + 자격 증명을 서비스로 전송합니다. +
  4. +
  5. + 서비스의 회신을 검토합니다. +
  6. +
+

+ 서비스가 자격 증명을 수락하면 +인증자가 자격 증명을 저장하여 나중에 사용할 수 있습니다. 플러그인 인증자 프레임워크로 인해, +{@link android.accounts.AccountManager}는 Oauth2 authToken과 같이 인증자가 지원하고 노출하기로 선택하는 모든 authToken에 액세스를 +제공합니다. +

+

+ 인증이 꼭 필요한 것은 아니지만, 대부분의 연락처 서비스는 이를 사용합니다. + 다만, 인증을 수행하기 위해 꼭 Android 인증 프레임워크를 사용해야 하는 것은 아닙니다. +

+

동기화 어댑터 구현

+

+ 연락처 제공자에 대한 동기화 어댑터를 구현하려면, +다음이 들어있는 Android 애플리케이션을 생성하는 것으로 시작합니다. +

+
+
+ 시스템의 요청에 응답하여 동기화 어댑터에 바인딩하는 {@link android.app.Service} +구성 요소. +
+
+ 시스템이 동기화를 실행하고자 하는 경우, 이는 +서비스의 {@link android.app.Service#onBind(Intent) onBind()} 메서드를 호출하여 +동기화 어댑터의 {@link android.os.IBinder}를 가져옵니다. 이렇게 하면 시스템이 어댑터의 +메서드에 대해 프로세스간 호출을 수행할 수 있습니다. +

+ +샘플 동기화 어댑터 샘플 앱에서 이 서비스의 클래스 이름은 +com.example.android.samplesync.syncadapter.SyncService입니다. +

+
+
+ {@link android.content.AbstractThreadedSyncAdapter}의 +하위 클래스로 구현된 실제 동기화 어댑터. +
+
+ 이 클래스는 서버에서 데이터를 다운로드하고, 기기에서 데이터를 업로드하고, +충돌을 해결하는 작업을 수행합니다. 어댑터의 주요 작업은 +{@link android.content.AbstractThreadedSyncAdapter#onPerformSync( +Account, Bundle, String, ContentProviderClient, SyncResult) +onPerformSync()} 메서드에서 실행합니다. 이 클래스는 반드시 단일 항목으로 인스턴트화해야 합니다. +

+ +샘플 동기화 어댑터 샘플 앱에서 동기화 어댑터는 +com.example.android.samplesync.syncadapter.SyncAdapter 클래스에서 정의됩니다. +

+
+
+ {@link android.app.Application}의 하위 클래스. +
+
+ 이 클래스는 동기화 어댑터 단일 항목의 팩터리 역할을 합니다. +{@link android.app.Application#onCreate()} 메서드는 +동기화 어댑터를 인스턴트화하고, 단일 항목을 동기화 어댑터 서비스의 +{@link android.app.Service#onBind(Intent) onBind()} 메서드에 반환할 정적 "getter" 메서드를 제공하는 데 +사용하십시오. +
+
+ 선택 항목: 사용자 인증에 대한 시스템으로부터의 요청에 응답하는 {@link android.app.Service} +구성 요소. +
+
+ {@link android.accounts.AccountManager}가 이 서비스를 시작하여 인증 +절차를 시작합니다. 서비스의 {@link android.app.Service#onCreate()} 메서드가 +인증자 객체를 인스턴트화합니다. 시스템이 애플리케이션 동기화 어댑터의 사용자 계정을 인증하고자 하는 경우, +시스템은 서비스의 +{@link android.app.Service#onBind(Intent) onBind()} 메서드를 호출하여 +인증자의 {@link android.os.IBinder}를 가져옵니다. 이렇게 하면 시스템이 인증자의 +메서드에 대해 프로세스간 호출을 수행할 수 있습니다. +

+ +샘플 동기화 어댑터 샘플 앱에서 이 서비스의 클래스 이름은 +com.example.android.samplesync.authenticator.AuthenticationService입니다. +

+
+
+ 선택 항목: 인증에 대한 요청을 처리하는 +{@link android.accounts.AbstractAccountAuthenticator}의 구체적인 +하위 클래스. +
+
+ 이 클래스는 {@link android.accounts.AccountManager}가 +서버로 사용자 자격 증명 인증을 호출하는 메서드를 제공합니다. 인증 절차의 세부 사항은 +사용하는 서버 기술에 따라 매우 차이가 있습니다. 인증에 대한 +자세한 정보는 각자의 서버 소프트웨어에 해당되는 관련 문서를 참조하십시오. +

+ +샘플 동기화 어댑터 샘플 앱에서 인증자는 +com.example.android.samplesync.authenticator.Authenticator 클래스에서 정의됩니다. +

+
+
+ 동기화 어댑터와 서버의 인증자를 정의하는 XML 파일. +
+
+ 이전에 설명한 동기화 어댑터와 인증자 서비스 구성 요소는 +애플리케이션 매니페스트의 +<service> 요소에서 +정의합니다. 이런 요소에는 +시스템에 특정 데이터를 제공하는 +<meta-data> +하위 요소가 +들어있습니다. +
    +
  • + 동기화 어댑터 서비스에 대한 +<meta-data> +요소는 +XML 파일 res/xml/syncadapter.xml을 가리킵니다. 그런가 하면 +이 파일은 연락처 제공자와 동기화될 웹 서비스에 대한 URI와 웹 서비스에 대한 계정 유형을 +나타냅니다. +
  • +
  • + 선택 항목: 인증자의 +<meta-data> +요소는 XML 파일 +res/xml/authenticator.xml을 가리킵니다. 이 파일은 다시 +이 인증이 지원하는 계정 유형과, 인증 과정 중에 표시되는 UI 리소스를 +나타냅니다. 이 요소에서 지정한 계정 유형은 반드시 +동기화 어댑터에 대해 지정된 계정 유형과 +같아야 합니다. +
  • +
+
+
+

소셜 스트림 데이터

+

+ {@code android.provider.ContactsContract.StreamItems}와 +{@code android.provider.ContactsContract.StreamItemPhotos} 테이블은 +소셜 네트워크에서 수신하는 데이터를 관리합니다. 개발자는 본인의 네트워크의 스트림 데이터를 이 테이블에 추가하는 +동기화 어댑터를 작성할 수도 있고, 이 테이블에서 스트림 데이터를 읽어서 +본인의 애플리케이션에 표시할 수도 있으며 두 가지를 모두 해도 됩니다. 이 기능을 사용하면 소셜 네트워킹 +서비스와 애플리케이션을 Android의 소셜 네트워킹 환경에 통합할 수 있습니다. +

+

소셜 스트림 텍스트

+

+ 스트림 항목은 항상 원시 연락처와 연관됩니다. +{@code android.provider.ContactsContract.StreamItemsColumns#RAW_CONTACT_ID}는 +원시 연락처의 _ID 값과 연관됩니다. 원시 연락처의 계정 유형과 계정 이름도 +스트림 항목 행에 저장됩니다. +

+

+ 스트림에서 가져온 데이터는 다음 열에 저장합니다. +

+
+
+ {@code android.provider.ContactsContract.StreamItemsColumns#ACCOUNT_TYPE} +
+
+ 필수입니다. 이 스트림 항목과 연관된 원시 연락처에 대한 +사용자 계정입니다. 스트림 항목을 삽입할 때 이 값을 설정하는 것을 잊지 마십시오. +
+
+ {@code android.provider.ContactsContract.StreamItemsColumns#ACCOUNT_NAME} +
+
+ 필수입니다. 이 스트림 항목과 연관된 원시 연락처에 대한 +사용자 계정 이름입니다. 스트림 항목을 삽입할 때 이 값을 설정하는 것을 잊지 마십시오. +
+
+ 식별자 열 +
+
+ 필수입니다. 스트림 항목을 삽입할 때 +다음 식별자 열을 삽입해야 합니다. +
    +
  • + {@code android.provider.ContactsContract.StreamItemsColumns#CONTACT_ID}: 이 +스트림 항목과 연관된 연락처의 {@code android.provider.BaseColumns#_ID} +값입니다. +
  • +
  • + {@code android.provider.ContactsContract.StreamItemsColumns#CONTACT_LOOKUP_KEY}: 이 +스트림 항목과 연관된 연락처의 {@code android.provider.ContactsContract.ContactsColumns#LOOKUP_KEY} +값입니다. +
  • +
  • + {@code android.provider.ContactsContract.StreamItemsColumns#RAW_CONTACT_ID}: 이 +스트림 항목과 연관된 원시 연락처의 {@code android.provider.BaseColumns#_ID} +값입니다. +
  • +
+
+
+ {@code android.provider.ContactsContract.StreamItemsColumns#COMMENTS} +
+
+ 선택 사항입니다. 스트림 항목의 시작 부분에 표시할 수 있는 요약 정보를 저장합니다. +
+
+ {@code android.provider.ContactsContract.StreamItemsColumns#TEXT} +
+
+ 스트림 항목의 텍스트로, 항목의 소스가 게시한 콘텐츠 또는 +스트림 항목을 생성하는 작업의 설명 중 하나입니다. 이 열에는 +{@link android.text.Html#fromHtml(String) fromHtml()}가 렌더링할 수 있는 모든 서식과 포함된 리소스 이미지가 +들어있을 수 있습니다. 제공자는 긴 콘텐츠를 +자르거나 생략할 수 있지만, 가능하면 태그를 손상시키는 것은 피하려 듭니다. +
+
+ {@code android.provider.ContactsContract.StreamItemsColumns#TIMESTAMP} +
+
+ 스트림 항목이 삽입되거나 업데이트된 시간이 들어있는 텍스트 문자열로, 형식은 +epoch 이후 밀리초 형태를 취합니다. 이 열을 관리할 책임은 +스트림 항목을 삽입 또는 업데이트하는 애플리케이션에 있으며, 이것은 연락처 제공자가 자동으로 +유지 관리하지 않습니다. +
+
+

+ 스트림 항목의 식별 정보를 표시하려면 +{@code android.provider.ContactsContract.StreamItemsColumns#RES_ICON}, +{@code android.provider.ContactsContract.StreamItemsColumns#RES_LABEL}, +{@code android.provider.ContactsContract.StreamItemsColumns#RES_PACKAGE}를 사용하여 애플리케이션에서 +리소스를 연결하십시오. +

+

+ {@code android.provider.ContactsContract.StreamItems} 테이블에도 +동기화 어댑터가 독점적으로 사용하는 {@code android.provider.ContactsContract.StreamItemsColumns#SYNC1}에서 +{@code android.provider.ContactsContract.StreamItemsColumns#SYNC4}까지의 열이 +들어있습니다. +

+

소셜 스트림 사진

+

+ {@code android.provider.ContactsContract.StreamItemPhotos} 테이블은 스트림 항목과 연관된 +사진을 저장합니다. 테이블의 +{@code android.provider.ContactsContract.StreamItemPhotosColumns#STREAM_ITEM_ID} 열은 +{@code android.provider.ContactsContract.StreamItems} 테이블의 {@code android.provider.BaseColumns#_ID} 열에 있는 값과 +연결됩니다. 사진 참조는 +다음 열의 테이블에 저장됩니다. +

+
+
+ {@code android.provider.ContactsContract.StreamItemPhotos#PHOTO} 열(BLOB). +
+
+ 사진의 바이너리 표현으로, 제공자가 저장하고 표시하기 위해 크기를 조정한 것입니다. + 이 열은 사진을 저장하는 데 사용한 연락처 제공자의 이전 버전과 +호환됩니다. 그러나 현재 버전에서는 +이 열을 사진 저장에 사용하면 안 됩니다. 대신, +{@code android.provider.ContactsContract.StreamItemPhotosColumns#PHOTO_FILE_ID} 또는 +{@code android.provider.ContactsContract.StreamItemPhotosColumns#PHOTO_URI}( +다음 항목에서 두 가지 모두 설명)를 사용하여 사진을 파일로 저장합니다. 지금 이 열에는 +사진의 미리 보기가 들어있어 읽기 작업에 사용할 수 있습니다. +
+
+ {@code android.provider.ContactsContract.StreamItemPhotosColumns#PHOTO_FILE_ID} +
+
+ 원시 연락처에 대한 사진의 숫자 식별자입니다. 이 값을 +상수 {@link android.provider.ContactsContract.DisplayPhoto#CONTENT_URI DisplayPhoto.CONTENT_URI}에 추가하여 +하나의 사진 파일을 가리키는 콘텐츠 URI를 가져온 다음, +{@link android.content.ContentResolver#openAssetFileDescriptor(Uri, String) +openAssetFileDescriptor()}를 호출하여 사진 파일에 대한 핸들을 가져옵니다. +
+
+ {@code android.provider.ContactsContract.StreamItemPhotosColumns#PHOTO_URI} +
+
+ 이 행이 나타내는 사진에 대한 사진 파일을 직접 가리키는 콘텐츠 URI입니다. + 이 URI로 {@link android.content.ContentResolver#openAssetFileDescriptor(Uri, String) +openAssetFileDescriptor()}를 호출하면 사진 파일에 대한 핸들을 가져올 수 있습니다. +
+
+

소셜 스트림 테이블 사용

+

+ 이들 테이블은 연락처 제공자의 다른 주요 테이블과 똑같이 작동하지만, 다음 예외가 적용됩니다. +

+
    +
  • + 이 테이블에는 추가 액세스 권한이 필요합니다. 여기서 읽기 작업을 수행하려면 애플리케이션에 +{@code android.Manifest.permission#READ_SOCIAL_STREAM} 권한이 있어야 합니다. 여기서 수정 작업을 수행하려면 +애플리케이션에 +{@code android.Manifest.permission#WRITE_SOCIAL_STREAM} 권한이 있어야 합니다. +
  • +
  • + {@code android.provider.ContactsContract.StreamItems} 테이블의 경우, 각 원시 연락처에 저장되는 +행 개수가 제한되어 있습니다. 이 한계에 도달하면, +연락처 제공자가 새 스트림 항목 열에 필요한 공간을 만들어야 합니다. +이때 가장 오래된 {@code android.provider.ContactsContract.StreamItemsColumns#TIMESTAMP}가 +있는 행부터 자동으로 삭제하는 방법을 씁니다. 이 한계를 +가져오려면, 콘텐츠 URI +{@code android.provider.ContactsContract.StreamItems#CONTENT_LIMIT_URI}에 쿼리를 발행합니다. 콘텐츠 +URI를 뺀 나머지 모든 인수는 null로 설정한 채 두면 됩니다. 이 쿼리는 +행이 하나 들어 있는 커서를 반환하며, +{@code android.provider.ContactsContract.StreamItems#MAX_ITEMS} 열 하나가 수반됩니다. +
  • +
+ +

+ {@code android.provider.ContactsContract.StreamItems.StreamItemPhotos} 클래스는 +스트림 항목 하나의 사진 행을 포함하는 {@code android.provider.ContactsContract.StreamItemPhotos}의 +하위 테이블을 정의합니다. +

+

소셜 스트림 상호 작용

+

+ 연락처 제공자가 기기 연락처 애플리케이션과 함께 관리하는 소셜 스트림 데이터는 +소셜 네트워킹 시스템과 +기존 연락처를 연결하는 강력한 방법을 제공합니다. 사용할 수 있는 기능은 다음과 같습니다. +

+
    +
  • + 소셜 네트워킹 서비스를 동기화 어댑터로 연락처 제공자에 동기화함으로써, +사용자 연락처의 최근 활동을 검색하고 이를 +{@code android.provider.ContactsContract.StreamItems} 및 +{@code android.provider.ContactsContract.StreamItemPhotos} 테이블에 저장해 두어 나중에 사용할 수 있습니다. +
  • +
  • + 정기 동기화 외에도 사용자가 볼 연락처를 선택하면 동기화 어댑터를 트리거하여 +추가 데이터를 검색하게 할 수 있습니다. 이렇게 하면 동기화 어댑터가 +해당 연락처의 고해상도 사진과 가장 최근 스트림 항목을 검색할 수 있습니다. +
  • +
  • + 기기 연락처 애플리케이션과 연락처 제공자에 알림을 등록하면, +연락처가 열람될 때 인텐트를 수신하고, +그 시점에 개발자의 서비스로부터 연락처의 상태를 업데이트할 수 있습니다. 이 방법을 사용하면 동기화 어댑터로 +완전 동기화를 수행하는 것보다 빠르고 대역폭도 적게 사용합니다. +
  • +
  • + 사용자는 기기의 연락처 애플리케이션을 보면서 여러분의 소셜 네트워킹 서비스에 +연락처를 추가할 수 있습니다. 이는 "연락처 초대" 기능으로 사용할 수 있습니다. +연락처 초대 기능은 기존 연락처를 네트워크에 추가하는 액티비티와 +기기의 연락처 애플리케이션을 제공하는 XML 파일, +애플리케이션의 세부 정보가 포함된 연락처 제공자를 조합하여 활성화합니다. +
  • +
+

+ 연락처 제공자로 스트림 항목을 정기 동기화하는 방법은 +다른 동기화와 같습니다. 동기화에 관한 자세한 내용은 +연락처 제공자 동기화 어댑터 섹션을 참조하십시오. 알림을 등록하고 연락처를 초대하는 방법은 +다음 두 섹션에서 다룰 것입니다. +

+

소셜 네트워킹 보기를 처리하기 위한 등록

+

+ 동기화 어댑터를 등록하여 사용자가 동기화 어댑터에서 관리하는 연락처를 볼 때 알림을 +수신하는 방법: +

+
    +
  1. + 프로젝트의 res/xml/ 디렉터리에 contacts.xml 파일을 +만듭니다. 이미 이 파일이 있다면 이 절차를 건너뛰어도 됩니다. +
  2. +
  3. + 이 파일에서, +<ContactsAccountType xmlns:android="http://schemas.android.com/apk/res/android"> 요소를 추가합니다. + 이 요소가 이미 존재한다면 이 절차를 건너뛰어도 됩니다. +
  4. +
  5. + 사용자가 기기의 연락처 애플리케이션에서 +연락처 세부 정보 페이지를 열면 알림을 보내는 서비스를 등록하려면, +viewContactNotifyService="serviceclass" 속성을 요소에 추가합니다. +이 요소에서 serviceclass는 기기의 연락처 애플리케이션에서 인텐트를 수신하는 서비스의 +완전히 정규화된 클래스 이름입니다. 알림 서비스의 경우, +{@link android.app.IntentService}를 확장하는 클래스를 사용하여 서비스가 인텐트를 수신하도록 +허용합니다. 수신되는 인텐트의 데이터에는 +사용자가 클릭한 원시 연락처의 콘텐츠 URI가 들어있습니다. 알림 서비스에서 동기화 어댑터에 바인딩한 다음 동기화 어댑터를 호출하여 +원시 연락처의 데이터를 업데이트할 수 있습니다. +
  6. +
+

+ 사용자가 스트림 항목이나 사진, 또는 그 두 가지를 모두 클릭할 때 호출할 액티비티를 등록하는 방법: +

+
    +
  1. + 프로젝트의 res/xml/ 디렉터리에 contacts.xml 파일을 +만듭니다. 이미 이 파일이 있다면 이 절차를 건너뛰어도 됩니다. +
  2. +
  3. + 이 파일에서, +<ContactsAccountType xmlns:android="http://schemas.android.com/apk/res/android"> 요소를 추가합니다. + 이 요소가 이미 존재한다면 이 절차를 건너뛰어도 됩니다. +
  4. +
  5. + 사용자가 기기의 연락처 애플리케이션에서 스트림 항목을 클릭했을 때 +처리할 액티비티를 등록하려면, +viewStreamItemActivity="activityclass" 속성을 요소에 추가합니다. +이 요소에서 activityclass는 기기의 연락처 애플리케이션에서 인텐트를 수신하는 액티비티의 +완전히 정규화된 클래스 이름입니다. +
  6. +
  7. + 사용자가 기기의 연락처 애플리케이션에서 스트림 사진을 클릭했을 때 +처리할 액티비티를 등록하려면, +viewStreamItemPhotoActivity="activityclass" 속성을 요소에 추가합니다. +이 요소에서 activityclass는 기기의 연락처 애플리케이션에서 인텐트를 수신하는 액티비티의 +완전히 정규화된 클래스 이름입니다. +
  8. +
+

+ <ContactsAccountType> 요소는 +<ContactsAccountType> 요소 섹션에 자세히 설명되어 있습니다. +

+

+ 수신되는 인텐트에 사용자가 클릭한 항목 또는 사진의 콘텐츠 URI가 들어있습니다. + 텍스트 항목과 사진에 각기 별도의 액티비티를 적용하려면, 두 속성을 모두 같은 파일에서 사용하십시오. +

+

소셜 네트워킹 서비스로 상호 작용

+

+ 사용자는 소셜 네트워킹 사이트에 연락처를 초대할 때 +기기의 연락처 애플리케이션을 떠나지 않아도 됩니다. 대신, 개발자가 기기의 연락처 앱에 액티비티 중 하나로 연락처를 초대하는 인텐트를 +보내게 할 수 있습니다. 이렇게 설정하는 방법은 다음과 같습니다. +

+
    +
  1. + 프로젝트의 res/xml/ 디렉터리에 contacts.xml 파일을 +만듭니다. 이미 이 파일이 있다면 이 절차를 건너뛰어도 됩니다. +
  2. +
  3. + 이 파일에서, +<ContactsAccountType xmlns:android="http://schemas.android.com/apk/res/android"> 요소를 추가합니다. + 이 요소가 이미 존재한다면 이 절차를 건너뛰어도 됩니다. +
  4. +
  5. + 다음 속성을 추가합니다. +
      +
    • inviteContactActivity="activityclass"
    • +
    • + inviteContactActionLabel="@string/invite_action_label" +
    • +
    + activityclass 값은 인텐트를 수신해야 하는 액티비티의 +완전히 정규화된 클래스 이름입니다. invite_action_label +값은 기기의 연락처 애플리케이션에 있는 연결 추가 메뉴에 +표시되는 텍스트 문자열입니다. +
  6. +
+

+ 참고: ContactsSource는 +ContactsAccountType에 대하여 이제 사용하지 않는 태그 이름입니다. +

+

contacts.xml 참조

+

+ contacts.xml 파일에는 XML 요소가 들어있어 개발자의 동기화 어댑터와 +애플리케이션, 연락처 애플리케이션과 연락처 제공자 사이의 상호 작용을 제어합니다. 이런 +요소는 다음 섹션에 설명되어 있습니다. +

+

<ContactsAccountType> 요소

+

+ <ContactsAccountType>요 요소는 개발자의 애플리케이션과 +연락처 애플리케이션 사이의 상호 작용을 제어합니다. 이 요소에는 다음 구문이 있습니다. +

+
+<ContactsAccountType
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        inviteContactActivity="activity_name"
+        inviteContactActionLabel="invite_command_text"
+        viewContactNotifyService="view_notify_service"
+        viewGroupActivity="group_view_activity"
+        viewGroupActionLabel="group_action_text"
+        viewStreamItemActivity="viewstream_activity_name"
+        viewStreamItemPhotoActivity="viewphotostream_activity_name">
+
+

+ 포함 장소: +

+

+ res/xml/contacts.xml +

+

+ 포함 가능 요소: +

+

+ <ContactsDataKind> +

+

+ 설명: +

+

+ 사용자가 연락처 중 하나를 소셜 네트워크에 초대하고, +소셜 네트워킹 스트림이 업데이트되면 사용자에게 알리는 등의 작업을 허용하는 +Android 구성 요소와 UI 레이블을 선언합니다. +

+

+ 속성 접두사 android:는 +<ContactsAccountType>의 속성에는 필요하지 않다는 점을 눈여겨보십시오. +

+

+ 속성: +

+
+
{@code inviteContactActivity}
+
+ 사용자가 기기의 연락처 애플리케이션에서 +연결 추가를 선택했을 때 활성화하고자 하는 +애플리케이션 액티비티의 완전히 정규화된 클래스 이름입니다. +
+
{@code inviteContactActionLabel}
+
+ 연결 추가 메뉴의 +{@code inviteContactActivity}에서 지정된 액티비티에 대해 표시되는 텍스트 문자열입니다. + 예를 들어, 문자열 "제 네트워크를 팔로우하세요"를 사용할 수 있습니다. 이 레이블에 대한 문자열 리소스 +식별자를 사용할 수 있습니다. +
+
{@code viewContactNotifyService}
+
+ 사용자가 연락처를 볼 때 알림을 수신해야 하는 +애플리케이션 서비스의 완전히 정규화된 클래스 이름입니다. 이 알림은 +기기의 연락처 애플리케이션이 전송합니다. 이것을 사용하면 개발자의 애플리케이션이 데이터 집약적인 작업을 필요할 때까지 +연기할 수 있습니다. 예를 들어, 개발자의 애플리케이션은 +연락처의 고해상도 사진과 가장 최근 소셜 스트림 항목을 읽어서 표시함으로써 +이 알림에 응답할 수 있습니다. 이 기능은 +소셜 스트림 상호 작용에 상세히 설명되어 있습니다. 알림 서비스의 예시를 +보려면 SampleSyncAdapter + 샘플 앱에 있는 NotifierService.java 파일을 +확인합니다. +
+
{@code viewGroupActivity}
+
+ 그룹 정보를 표시할 수 있는 애플리케이션 액티비티의 +완전히 정규화된 클래스 이름입니다. 사용자가 기기의 연락처 애플리케이션에서 그룹 레이블을 클릭하면, +이 액티비티의 UI가 표시됩니다. +
+
{@code viewGroupActionLabel}
+
+ 사용자가 개발자의 애플리케이션에서 그룹을 살펴볼 수 있도록 해주는 UI 제어에 대해 +연락처 애플리케이션이 표시하는 레이블입니다. +

+ 예를 들어, 기기에 Google+ 애플리케이션을 설치하고 +Google+를 연락처 애플리케이션과 동기화하면, Google+ 서클이 +연락처 애플리케이션의 그룹 탭에 표시되는 것을 볼 수 있습니다. Google+ 서클을 +클릭하면 해당 서클에서 "그룹"으로 표시된 사람들을 볼 수 있습니다. 이 표시의 맨 위에 +Google+ 아이콘이 표시되며, 이것을 클릭하면 제어가 +Google+ 앱으로 전환됩니다. 연락처 애플리케이션은 이 작업을 +{@code viewGroupActivity}로 수행하며, Google+ 아이콘을 +{@code viewGroupActionLabel}의 값으로 사용합니다. +

+

+ 이 속성에서는 문자열 리소스 식별자가 허용됩니다. +

+
+
{@code viewStreamItemActivity}
+
+ 사용자가 원시 연락처의 스트림 항목을 클릭할 때 기기의 연락처 애플리케이션이 시작하는 +애플리케이션 액티비티의 완전히 정규화된 클래스 이름입니다. +
+
{@code viewStreamItemPhotoActivity}
+
+ 사용자가 원시 연락처 스트림 항목의 사진을 클릭할 때 +기기의 연락처 애플리케이션이 시작하는 애플리케이션 액티비티의 +완전히 정규화된 클래스 이름입니다. +
+
+

<ContactsDataKind> 요소

+

+ <ContactsDataKind> 요소는 연락처 애플리케이션 UI에서 애플리케이션의 사용자 지정 데이터 행 표시를 +제어합니다. 이 요소에는 다음 구문이 있습니다. +

+
+<ContactsDataKind
+        android:mimeType="MIMEtype"
+        android:icon="icon_resources"
+        android:summaryColumn="column_name"
+        android:detailColumn="column_name">
+
+

+ 포함 장소: +

+<ContactsAccountType> +

+ 설명: +

+

+ 이 요소를 사용하여 연락처 애플리케이션이 사용자 지정 데이터 행의 콘텐츠를 +원시 연락처 세부 정보의 일부로 표시하게 합니다. <ContactsAccountType>의 각 <ContactsDataKind> 하위 요소는 +동기화 어댑터가 {@link android.provider.ContactsContract.Data}에 추가하는 +사용자 지정 데이터 행의 유형을 나타냅니다. 개발자가 사용하는 사용자 지정 MIME 유형 하나마다 +<ContactsDataKind> 요소를 하나씩 추가합니다. 데이터를 +표시하는 것을 원치 않는 사용자 지정 데이터 행이 있으면, 이 요소를 추가하지 않아도 됩니다. +

+

+ 속성: +

+
+
{@code android:mimeType}
+
+ {@link android.provider.ContactsContract.Data} 테이블에서 +사용자 지정 데이터 행 유형 중 하나로 지정한 사용자 지정 MIME 유형입니다. 예를 들어, +vnd.android.cursor.item/vnd.example.locationstatus 값은 연락처의 마지막으로 알려진 위치를 기록하는 +데이터 행에 대한 사용자 지정 MIME 유형이 될 수 있습니다. +
+
{@code android:icon}
+
+ 연락처 애플리케이션이 개발자의 데이터 옆에 표시하는 +Android 드로어블 리소스 +입니다. 이 리소스를 사용하여 사용자에게 +데이터 출처가 개발자의 서비스임을 나타내는 것입니다. +
+
{@code android:summaryColumn}
+
+ 데이터 행에서 검색한 두 개 값 중에서 첫 번째 값에 대한 열 이름입니다. 이 값은 +이 데이터 행에 대한 항목의 첫 번째 줄로 표시됩니다. 첫 번째 줄은 +데이터 요약으로 사용되는 것이 본 목적이지만, 이것은 선택 사항입니다. +android:detailColumn도 참조하십시오. +
+
{@code android:detailColumn}
+
+ 데이터 행에서 검색한 두 개 값 중에서 두 번째 값에 대한 열 이름입니다. 이 값은 +이 데이터 행에 대한 항목의 두 번째 줄로 표시됩니다. +{@code android:summaryColumn}도 참조하십시오. +
+
+

추가 연락처 제공자 기능

+

+ 이전 섹션에서 설명한 주요 기능 외에도 연락처 제공자는 연락처 데이터를 다루는 데 +유용한 기능을 많이 제공합니다. 예를 들면 다음과 같습니다. +

+
    +
  • 연락처 그룹
  • +
  • 사진 기능
  • +
+

연락처 그룹

+

+ 연락처 제공자는 관련된 연락처 컬렉션에 +그룹 데이터로 레이블을 붙이기로 선택할 수 있습니다. 사용자 계정과 연관된 서버에서 +그룹을 관리하고자 하는 경우, 계정의 계정 유형에 대한 동기화 어댑터가 +연락처 제공자와 서버 사이에서 그룹 데이터를 전송해야 합니다. 사용자가 해당 서버에 새 연락처를 추가하고 +이 연락처를 새 그룹에 넣으면, 동기화 어댑터가 해당 새 그룹을 +{@link android.provider.ContactsContract.Groups} 테이블에 추가해야 합니다. 원시 연락처가 속한 그룹(또는 여러 그룹)은 +{@link android.provider.ContactsContract.Data} 테이블에 저장되며, 이때 +{@link android.provider.ContactsContract.CommonDataKinds.GroupMembership} MIME 유형을 사용합니다. +

+

+ 개발자가 서버에서 가져온 원시 연락처 데이터를 연락처 제공자에 추가할 +동기화 어댑터를 디자인하는 중이고 그룹은 사용하지 않는다면, +제공자 쪽에 데이터를 표시하라고 지시해야 합니다. 사용자가 기기에 계정을 추가했을 때 실행되는 코드에서 +연락처 제공자가 계정에 추가하는{@link android.provider.ContactsContract.Settings} 행을 +업데이트하십시오. 이 행에서 +{@link android.provider.ContactsContract.SettingsColumns#UNGROUPED_VISIBLE +Settings.UNGROUPED_VISIBLE} 열의 값을 1로 설정합니다. 이렇게 하면 연락처 제공자가 +개발자의 연락처 데이터를 항상 표시하게 되고, 이는 그룹을 사용하지 않더라도 관계 없습니다. +

+

연락처 사진

+

+ {@link android.provider.ContactsContract.Data} 테이블은 +{@link android.provider.ContactsContract.CommonDataKinds.Photo#CONTENT_ITEM_TYPE +Photo.CONTENT_ITEM_TYPE} MIME 유형으로 사진을 행에 저장합니다. 이 행의 +{@link android.provider.ContactsContract.RawContactsColumns#CONTACT_ID} 열은 +행이 속한 원시 연락처의 {@code android.provider.BaseColumns#_ID} 열과 연결됩니다. + 클래스 {@link android.provider.ContactsContract.Contacts.Photo}는 +연락처 기본 사진의 사진 정보가 들어있는 {@link android.provider.ContactsContract.Contacts} 하위 테이블을 정의합니다. +연락처의 기본 사진은 연락처 기본 원시 연락처의 기본 사진입니다. 마찬가지로, +{@link android.provider.ContactsContract.RawContacts.DisplayPhoto} 클래스는 +원시 연락처의 기본 사진의 사진 정보가 들어있는 {@link android.provider.ContactsContract.RawContacts} 하위 테이블을 +정의합니다. +

+

+ {@link android.provider.ContactsContract.Contacts.Photo} 및 +{@link android.provider.ContactsContract.RawContacts.DisplayPhoto}에 대한 참조 문서에 +사진 정보를 검색하는 예시가 들어있습니다. 원시 연락처에 대한 기본 미리 보기를 검색하는 데 쓰이는 +편의 클래스는 없습니다. 하지만 +{@link android.provider.ContactsContract.Data} 테이블에 쿼리를 보내 원시 연락처의 +{@code android.provider.BaseColumns#_ID}, + {@link android.provider.ContactsContract.CommonDataKinds.Photo#CONTENT_ITEM_TYPE + Photo.CONTENT_ITEM_TYPE}, 및 {@link android.provider.ContactsContract.Data#IS_PRIMARY} + 열을 선택하면 해당 원시 연락처의 기본 사진 행을 찾을 수 있습니다. +

+

+ 한 사람의 소셜 스트림 데이터에도 사진이 포함되어 있을 수 있습니다. 이런 사진은 +{@code android.provider.ContactsContract.StreamItemPhotos} 테이블에 저장되며, 이 내용은 +소셜 스트림 사진에 더 자세하게 설명되어 있습니다. +

diff --git a/docs/html-intl/intl/ko/guide/topics/providers/content-provider-basics.jd b/docs/html-intl/intl/ko/guide/topics/providers/content-provider-basics.jd new file mode 100644 index 0000000000000000000000000000000000000000..953f92ab3026c90aae71207b530e6062ae9804e0 --- /dev/null +++ b/docs/html-intl/intl/ko/guide/topics/providers/content-provider-basics.jd @@ -0,0 +1,1196 @@ +page.title=콘텐츠 제공자 기본 정보 +@jd:body +
+
+ +

이 문서의 내용

+
    +
  1. + 개요 +
      +
    1. + 제공자 액세스 +
    2. +
    3. + 콘텐츠 URI +
    4. +
    +
  2. +
  3. + 제공자에서 데이터 검색 +
      +
    1. + 읽기 액세스 권한 요청 +
    2. +
    3. + 쿼리 구성 +
    4. +
    5. + 쿼리 결과 표시 +
    6. +
    7. + 쿼리 결과에서 데이터 가져오기 +
    8. +
    +
  4. +
  5. + 콘텐츠 제공자 권한 +
  6. +
  7. + 데이터 삽입, 업데이트 및 삭제 +
      +
    1. + 데이터 삽입 +
    2. +
    3. + 데이터 업데이트 +
    4. +
    5. + 데이터 삭제 +
    6. +
    +
  8. +
  9. + 제공자 데이터 유형 +
  10. +
  11. + 제공자 액세스의 대체 형식 +
      +
    1. + 일괄 액세스 +
    2. +
    3. + 인텐트를 통한 데이터 액세스 +
    4. +
    +
  12. +
  13. + 계약 클래스 +
  14. +
  15. + MIME 유형 참조 +
  16. +
+ + +

Key 클래스

+
    +
  1. + {@link android.content.ContentProvider} +
  2. +
  3. + {@link android.content.ContentResolver} +
  4. +
  5. + {@link android.database.Cursor} +
  6. +
  7. + {@link android.net.Uri} +
  8. +
+ + +

관련 샘플

+
    +
  1. + + 커서(피플) +
  2. +
  3. + + 커서(전화) +
  4. +
+ + +

참고 항목

+
    +
  1. + + 콘텐츠 제공자 생성 +
  2. +
  3. + + 캘린더 제공자 +
  4. +
+
+
+ + +

+ 콘텐츠 제공자는 데이터의 중앙 리포지토리로의 액세스를 관리합니다. +제공자는 Android 애플리케이션의 일부이며, 이는 종종 나름의 UI를 제공하여 데이터에 작용하도록 합니다. + 그러나 콘텐츠 제공자는 기본적으로 다른 애플리케이션이 사용하도록 만들어진 것입니다. +이들은 제공자 클라이언트 개체를 사용하여 제공자에 액세스합니다. +제공자와 제공자 클라이언트가 결합되면 데이터에 하나의 일관적인 표준 인터페이스를 제공하여 +이것이 프로세스간 통신과 보안 데이터 액세스도 처리합니다. +

+

+ 이 주제에서는 다음의 기본 정보를 설명합니다. +

+
    +
  • 콘텐츠 제공자의 작동 원리
  • +
  • 콘텐츠 제공자에서 데이터를 검색할 때 사용하는 API
  • +
  • 콘텐츠 제공자 내의 데이터를 삽입, 업데이트 및 삭제하는 데 사용하는 API
  • +
  • 제공자를 다루는 데 도움이 되는 기타 API 기능
  • +
+ + +

개요

+

+ 콘텐츠 제공자는 외부 애플리케이션에 데이터를 표시하며, 이때 데이터는 +관계형 데이터베이스에서 찾을 수 있는 테이블과 유사한 하나 이상의 테이블로서 표시됩니다. +한 행은 제공자가 수집하는 어떤 유형의 데이터 인스턴스를 나타내며, +행 안의 각 열은 인스턴스에 대해 수집된 개별적인 데이터를 나타냅니다. +

+

+ 예를 들어 Android 플랫폼 안에 내장된 여러 제공자 중에 사용자 사전이라는 것이 있습니다. +이것은 사용자가 보관하고 싶어하는 비표준 단어의 철자를 저장합니다. 표 1은 이 제공자의 테이블에서 +데이터가 어떤 형태를 띨 수 있는지를 나타낸 것입니다. +

+

+ 표 1: 샘플 사용자 사전 테이블입니다. +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
단어앱 ID빈도로케일_ID
mapreduceuser1100en_US1
precompileruser14200fr_FR2
appletuser2225fr_CA3
constuser1255pt_BR4
intuser5100en_UK5
+

+ 표 1에서, 각 행은 일반적인 사전에 나오지 않는 단어의 인스턴스를 +나타냅니다. 각 열은 해당 단어에 대한 일부 데이터를 나타냅니다. 예를 들어 +단어가 처음 나온 로케일 등을 들 수 있습니다. 열 헤더는 제공자에 저장된 +열 이름입니다. 행의 로케일을 참조하려면 그 행의 locale 열을 참조합니다. +이 제공자의 경우, _ID 열은 제공자가 자동으로 유지하는 "기본 키" 열 +역할을 합니다. +

+

+ 참고: 제공자에 기본 키가 꼭 있어야 하는 것은 아니고, +기본 키가 있는 경우 _ID를 열 이름으로 사용하지 않아도 됩니다. 그러나 제공자의 데이터를 +{@link android.widget.ListView}에 바인딩하려면 +열 이름 중 하나는_ID여야 합니다. 이 요구 사항은 +쿼리 결과 표시 섹션에 자세히 설명되어 있습니다. +

+

제공자 액세스

+

+ 애플리케이션은 콘텐츠 제공자로부터의 데이터에 +{@link android.content.ContentResolver} 클라이언트 개체로 액세스합니다. +이 개체에는 제공자 개체 내의 같은 이름을 가진 메서드를 호출하는 메서드가 있습니다. +이는 {@link android.content.ContentProvider}의 구체적인 하위 클래스 중 하나의 인스턴스입니다. +{@link android.content.ContentResolver} 메서드는 +영구적 저장소의 기본적인 "CRUD"(생성, 검색, 업데이트 및 삭제) 기능을 제공합니다. +

+

+ 클라이언트 애플리케이션의 프로세스 내에 있는 {@link android.content.ContentResolver} 개체와 + 제공자를 소유하는 애플리케이션 내의 {@link android.content.ContentProvider} 개체가 +자동으로 프로세스간 통신을 처리합니다. +{@link android.content.ContentProvider} 또한 +콘텐츠 제공자의 데이터 리포지토리와 외부에 테이블로 표시되는 데이터 모습 사이에서 추상화 계층 역할을 합니다. +

+

+ 참고: 제공자에 액세스하려면 보통은 애플리케이션이 +제공자의 매니페스트 파일에 있는 특정 권한을 요청해야 합니다. 이것은 +콘텐츠 제공자 권한 섹션에 더 자세히 설명되어 있습니다. +

+

+ 예를 들어 사용자 사전 제공자로부터 단어와 그에 해당하는 로케일 목록을 가져오려면, +{@link android.content.ContentResolver#query ContentResolver.query()}를 호출하면 됩니다. + {@link android.content.ContentResolver#query query()} 메서드는 사용자 사전 제공자가 정의한 +{@link android.content.ContentProvider#query ContentProvider.query()} 메서드를 +호출합니다. 다음 몇 줄의 코드는 +{@link android.content.ContentResolver#query ContentResolver.query()} 호출을 나타낸 것입니다. +

+

+// Queries the user dictionary and returns results
+mCursor = getContentResolver().query(
+    UserDictionary.Words.CONTENT_URI,   // The content URI of the words table
+    mProjection,                        // The columns to return for each row
+    mSelectionClause                    // Selection criteria
+    mSelectionArgs,                     // Selection criteria
+    mSortOrder);                        // The sort order for the returned rows
+
+

+ 표 2는 +{@link android.content.ContentResolver#query +query(Uri,projection,selection,selectionArgs,sortOrder)}에 대한 인수가 SQL SELECT 문과 일치하는 방식을 나타낸 것입니다. +

+

+ 표 2: Query()를 SQL 쿼리에 비교한 것입니다. +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
query() 인수SELECT 키워드/매개변수참고
UriFROM table_nameUritable_name이라 불리는 제공자에 있는 테이블에 매핑됩니다.
projectioncol,col,col,... + projection은 검색된 각 행에 포함되어야 하는 일련의 열입니다. + +
selectionWHERE col = valueselection은 행을 선택하는 기준을 나타냅니다.
selectionArgs + (정확한 등가는 없습니다. 선택 인수는 선택 절에 있는 ? + 자리 표시자를 대체합니다.) +
sortOrderORDER BY col,col,... + sortOrder는 반환된 +{@link android.database.Cursor} 내에 행이 나타나는 순서를 지정합니다. +
+

콘텐츠 URI

+

+ 콘텐츠 URI는 제공자에서 데이터를 식별하는 URI입니다. +콘텐츠 URI에는 전체 제공자의 상징적인 이름(제공자의 권한)과 +테이블을 가리키는 이름(경로)이 포함됩니다. +제공자 내의 테이블에 액세스하기 위해 클라이언트 메서드를 호출하는 경우, +그 테이블에 대한 콘텐츠 URI는 인수 중 하나입니다. +

+

+ 앞선 몇 줄의 코드에서 상수 +{@link android.provider.UserDictionary.Words#CONTENT_URI}에 +사용자 사전의 "단어" 테이블의 콘텐츠 URI가 들어있습니다. {@link android.content.ContentResolver} + 개체가 이 URI의 권한을 구문 분석한 다음, 이를 이용해 제공자를 "확인"합니다. 즉 이 권한을 알려진 제공자로 이루어진 시스템 테이블과 비교하는 것입니다. + +그러면 {@link android.content.ContentResolver}가 쿼리 인수를 +올바른 제공자에게 발송할 수 있습니다. +

+

+ {@link android.content.ContentProvider}는 콘텐츠 URI의 경로 부분을 사용하여 +액세스할 테이블을 선택합니다. 제공자에는 보통 제공자가 노출하는 테이블마다 경로가 있습니다. +

+

+ 앞선 몇 줄의 코드에서 "단어" 테이블에 대한 전체 URI는 다음과 같습니다. +

+
+content://user_dictionary/words
+
+

+ 여기에서 user_dictionary 문자열은 제공자의 권한이고 +words 문자열은 테이블의 경로입니다. 문자열 +content://(구성표)는 언제나 표시되며, +이것을 콘텐츠 URI로 식별합니다. +

+

+ 대다수의 제공자에서는 URI의 맨 끝에 ID 값을 추가하면 +테이블 내 하나의 행에 액세스할 수 있게 해줍니다. 예를 들어 _ID가 +사용자 사전의 4인 행을 검색하려면, 이 콘텐츠 URI를 사용하면 됩니다. +

+
+Uri singleUri = ContentUris.withAppendedId(UserDictionary.Words.CONTENT_URI,4);
+
+

+ 일련의 행을 검색한 다음 그 중 하나를 업데이트하거나 삭제하고자 하는 경우 종종 ID 값을 +이용하곤 합니다. +

+

+ 참고: {@link android.net.Uri}와 +{@link android.net.Uri.Builder} 클래스에는 문자열에서 잘 구성된(Well-Formed) URI 개체를 구성하기 위한 편의 메서드가 들어 있습니다. +{@link android.content.ContentUris}에는 URI에 ID 값을 추가하기 위한 편의 메서드가 들어 있습니다. +이전 조각은 {@link android.content.ContentUris#withAppendedId +withAppendedId()}를 사용하여 UserDictionary 콘텐츠 URI에 ID를 추가합니다. +

+ + + +

제공자에서 데이터 검색

+

+ 이 섹션은 사용자 사전 제공자를 예시로 사용하여 제공자에서 데이터를 검색하는 +방법을 설명합니다. +

+

+ 명확히 나타내기 위해 이 섹션의 코드 조각은 +{@link android.content.ContentResolver#query ContentResolver.query()}를 "UI 스레드"에서 호출합니다. +그러나 실제 코드의 경우 쿼리는 별도의 스레드에서 비동기식으로 수행해야 합니다. 이를 위한 한 가지 방식으로 +{@link android.content.CursorLoader} +클래스를 쓰는 것을 들 수 있습니다. 이 내용은 +로더 가이드에 더 자세히 설명되어 있습니다. 또한, 이 코드 줄은 조각일 뿐이며 애플리케이션을 전체적으로 표시한 것이 아닙니다. + +

+

+ 제공자에서 데이터를 검색하려면 다음과 같은 기본 단계를 따르십시오. +

+
    +
  1. + 제공자에 대한 읽기 액세스 권한을 요청합니다. +
  2. +
  3. + 제공자에게 쿼리를 보내는 코드를 정의합니다. +
  4. +
+

읽기 액세스 권한 요청

+

+ 제공자에서 데이터를 검색하려면 애플리케이션에 해당 제공자에 대한 "읽기 액세스 권한"이 필요합니다. + 런타임에는 이 권한을 요청할 수 없습니다. 대신 이 권한이 필요하다는 것을 매니페스트에 나타내야 합니다. 이때, + +<uses-permission> + 요소와 제공자가 정의한 정확한 권한 이름을 사용하면 됩니다. + 매니페스트에서 이 요소를 지정하는 것은 사실상 애플리케이션을 위해 이 권한을 "요청"하는 것과 +같습니다. 사용자가 애플리케이션을 설치할 때면, 이 요청을 암시적으로 허용하게 됩니다. + +

+

+ 사용 중인 제공자에 대한 읽기 액세스 권한의 정확한 이름과 해당 제공자가 사용하는 +다른 액세스 권한의 이름을 찾아보려면 제공자의 문서를 살펴보십시오. + +

+

+ 제공자에 액세스하는 데 있어 권한이 어떤 역할을 하는지는 +콘텐츠 제공자 권한 섹션에 더 자세하게 설명되어 있습니다. +

+

+ 사용자 사전 제공자는 +android.permission.READ_USER_DICTIONARY 권한을 자신의 매니페스트 파일에 정의합니다. 따라서 해당 제공자에서 +읽기 작업을 하고자 하는 애플리케이션은 반드시 이 권한을 요청해야 합니다. +

+ +

쿼리 구성

+

+ 제공자에서 데이터를 검색할 때 다음 단계는 쿼리를 구성하는 것입니다. 다음의 첫 번째 조각은 +사용자 사전 제공자에 액세스하는 데 필요한 몇 가지 변수를 정의한 것입니다. +

+
+
+// A "projection" defines the columns that will be returned for each row
+String[] mProjection =
+{
+    UserDictionary.Words._ID,    // Contract class constant for the _ID column name
+    UserDictionary.Words.WORD,   // Contract class constant for the word column name
+    UserDictionary.Words.LOCALE  // Contract class constant for the locale column name
+};
+
+// Defines a string to contain the selection clause
+String mSelectionClause = null;
+
+// Initializes an array to contain selection arguments
+String[] mSelectionArgs = {""};
+
+
+

+ 다음 조각은 사용자 사전 제공자를 예시로 사용하여 +{@link android.content.ContentResolver#query ContentResolver.query()}를 + 사용하는 방법을 나타낸 것입니다. 제공자 클라이언트 쿼리는 SQL 쿼리와 비슷하며, +반환할 열 집합과 선택 기준 집합, 그리고 정렬 순서가 이 안에 들어 있습니다. +

+

+ 쿼리가 반환해야 할 열 집합을 프로젝션 +(변수mProjection)이라고 합니다. +

+

+ 검색할 행을 나타내는 식은 선택 절과 선택 인수로 분할되어 있습니다. + 선택 절은 논리와 부울 식, 열 이름과 값 +(변수 mSelectionClause)을 조합한 것입니다. +값 대신 대체 가능한 매개변수 ?를 지정하면, +쿼리 메서드가 그 값을 선택 인수 배열에서 검색합니다(변수 mSelectionArgs). +

+

+ 다음 조각의 경우, 사용자가 단어를 입력하지 않으면 선택 절이 +null로 설정되고, 쿼리는 제공자 안의 모든 단어를 반환합니다. +사용자가 단어를 입력하면 선택 절은 UserDictionary.Words.WORD + " = ?"로 설정되며 +선택 인수의 첫 번째 요소가 사용자가 입력한 단어로 설정됩니다. +

+
+/*
+ * This defines a one-element String array to contain the selection argument.
+ */
+String[] mSelectionArgs = {""};
+
+// Gets a word from the UI
+mSearchString = mSearchWord.getText().toString();
+
+// Remember to insert code here to check for invalid or malicious input.
+
+// If the word is the empty string, gets everything
+if (TextUtils.isEmpty(mSearchString)) {
+    // Setting the selection clause to null will return all words
+    mSelectionClause = null;
+    mSelectionArgs[0] = "";
+
+} else {
+    // Constructs a selection clause that matches the word that the user entered.
+    mSelectionClause = UserDictionary.Words.WORD + " = ?";
+
+    // Moves the user's input string to the selection arguments.
+    mSelectionArgs[0] = mSearchString;
+
+}
+
+// Does a query against the table and returns a Cursor object
+mCursor = getContentResolver().query(
+    UserDictionary.Words.CONTENT_URI,  // The content URI of the words table
+    mProjection,                       // The columns to return for each row
+    mSelectionClause                   // Either null, or the word the user entered
+    mSelectionArgs,                    // Either empty, or the string the user entered
+    mSortOrder);                       // The sort order for the returned rows
+
+// Some providers return null if an error occurs, others throw an exception
+if (null == mCursor) {
+    /*
+     * Insert code here to handle the error. Be sure not to use the cursor! You may want to
+     * call android.util.Log.e() to log this error.
+     *
+     */
+// If the Cursor is empty, the provider found no matches
+} else if (mCursor.getCount() < 1) {
+
+    /*
+     * Insert code here to notify the user that the search was unsuccessful. This isn't necessarily
+     * an error. You may want to offer the user the option to insert a new row, or re-type the
+     * search term.
+     */
+
+} else {
+    // Insert code here to do something with the results
+
+}
+
+

+ 이 쿼리는 SQL 문에 대한 아날로그입니다. +

+
+SELECT _ID, word, locale FROM words WHERE word = <userinput> ORDER BY word ASC;
+
+

+ 이 SQL 문에서는 계약 클래스 상수 대신 실제 열 이름을 사용하였습니다. +

+

악의적인 입력에 대한 보호

+

+ 콘텐츠 제공자가 관리하는 데이터가 SQL 데이터베이스에 있는 경우, +원시 SQL 문에 외부의 신뢰할 수 없는 데이터를 포함시키면 SQL 삽입을 초래할 수 있습니다. +

+

+ 이 선택 절을 예로 들어 봅시다. +

+
+// Constructs a selection clause by concatenating the user's input to the column name
+String mSelectionClause =  "var = " + mUserInput;
+
+

+ 이렇게 하면 사용자로 하여금 여러분의 SQL 문에 악의적인 SQL을 연결할 수 있도록 허용합니다. + 예를 들어 사용자가 mUserInput에 대해 "nothing; DROP TABLE *;"을 입력할 수 있습니다. +그러면 그 결과로 선택 절 var = nothing; DROP TABLE *;이 나옵니다. +선택 절이 일종의 SQL 문으로 취급되었기 때문에 제공자가 기본 SQLite 데이터베이스에서 테이블을 +모두 삭제하는 결과를 낳을 수도 있습니다(제공자가 SQL 삽입 +시도를 잡아내도록 설정된 경우는 예외입니다). +

+

+ 이 문제를 피하려면 ?를 대체 가능한 매개변수로 사용하는 선택 절과, +별도의 선택 인수 배열을 사용하면 됩니다. 이렇게 하면, 사용자 입력이 SQL 문의 일부로 해석되기보다 쿼리에 직접 바인딩됩니다. + + 이것은 SQL로 취급되지 않기 때문에 사용자 입력이 악의적인 SQL을 삽입할 수 없습니다. +사용자 입력을 포함하는 데 연결을 사용하는 대신 다음 선택 절을 사용합니다. +

+
+// Constructs a selection clause with a replaceable parameter
+String mSelectionClause =  "var = ?";
+
+

+ 선택 인수 배열을 이렇게 설정합니다. +

+
+// Defines an array to contain the selection arguments
+String[] selectionArgs = {""};
+
+

+ 선택 인수 배열에 값을 입력할 때는 이렇게 합니다. +

+
+// Sets the selection argument to the user's input
+selectionArgs[0] = mUserInput;
+
+

+ ?를 대체 가능한 매개변수로 사용하는 선택 절과 +선택 인수 배열을 사용하는 것이 선택을 지정하는 데 선호되는 방법입니다. +이는 제공자가 SQL 데이터베이스 기반이 아닐 때에도 마찬가지입니다. +

+ +

쿼리 결과 표시

+

+ {@link android.content.ContentResolver#query ContentResolver.query()} +클라이언트 메서드는 언제나 쿼리 선택 기준과 일치하는 행에 대해 쿼리 프로젝션이 지정한 열을 포함하는 +{@link android.database.Cursor}를 반환합니다. +{@link android.database.Cursor} 개체가 자신이 포함한 행과 열에 무작위 읽기 액세스를 제공합니다. + {@link android.database.Cursor} 메서드를 사용하면 행을 결과에서 반복할 수 있고, +각 열의 데이터 유형을 결정하며 열에서 데이터를 꺼내거나 결과의 다른 속성을 검토할 수도 있습니다. + 일부 {@link android.database.Cursor} 구현은 제공자의 데이터가 변경될 경우, +{@link android.database.Cursor}가 변경될 때 관찰자 개체 내의 메서드를 트리거하는 경우 +또는 두 가지가 한 번에 발생할 경우 자동으로 개체를 업데이트합니다. +

+

+ 참고: 제공자는 쿼리를 수행하는 개체의 성격을 근거로 +열에 대한 액세스를 제한할 수 있습니다. 예를 들어 연락처 제공자는 동기화 어댑터로의 몇몇 열에 대한 액세스를 제한합니다. +이렇게 해야 액티비티 또는 서비스에 열을 반환하지 않기 때문입니다. +

+

+ 선택 기준에 일치하는 행이 없으면, 제공자는 +{@link android.database.Cursor} 개체를 반환합니다. 이 개체의 +{@link android.database.Cursor#getCount Cursor.getCount()}는 0(빈 커서)입니다. +

+

+ 내부 오류가 발생하는 경우, 쿼리 결과는 특정 제공자에 따라 달라집니다. +null을 반환하기로 선택할 수도 있고, {@link java.lang.Exception}을 발생시킬 수도 있습니다. +

+

+ {@link android.database.Cursor}는 행의 "목록"이므로, +{@link android.database.Cursor}의 콘텐츠를 표시하는 좋은 방법은 {@link android.widget.SimpleCursorAdapter}를 통해 {@link android.widget.ListView}에 +연결하는 것입니다. +

+

+ 다음 조각은 이전 조각으로부터 코드를 계속 이어가는 것입니다. +이는 해당 쿼리가 검색한 {@link android.database.Cursor}가 들어 있는 +{@link android.widget.SimpleCursorAdapter} 개체를 생성하며, 이 개체를 +{@link android.widget.ListView}에 대한 어댑터로 설정합니다. +

+
+// Defines a list of columns to retrieve from the Cursor and load into an output row
+String[] mWordListColumns =
+{
+    UserDictionary.Words.WORD,   // Contract class constant containing the word column name
+    UserDictionary.Words.LOCALE  // Contract class constant containing the locale column name
+};
+
+// Defines a list of View IDs that will receive the Cursor columns for each row
+int[] mWordListItems = { R.id.dictWord, R.id.locale};
+
+// Creates a new SimpleCursorAdapter
+mCursorAdapter = new SimpleCursorAdapter(
+    getApplicationContext(),               // The application's Context object
+    R.layout.wordlistrow,                  // A layout in XML for one row in the ListView
+    mCursor,                               // The result from the query
+    mWordListColumns,                      // A string array of column names in the cursor
+    mWordListItems,                        // An integer array of view IDs in the row layout
+    0);                                    // Flags (usually none are needed)
+
+// Sets the adapter for the ListView
+mWordList.setAdapter(mCursorAdapter);
+
+

+ 참고: {@link android.database.Cursor}로 {@link android.widget.ListView}를 뒷받침하려면, +커서에 _ID라는 열이 포함되어야 합니다. + 이것 때문에 이전에 표시된 쿼리가 "단어" 테이블에 대하여 _ID 열을 +검색하며, {@link android.widget.ListView}가 이를 표시하지 않더라도 무관합니다. + 이 제한은 대부분의 제공자에 각 테이블에 대한 _ID 열이 있는 이유를 설명해주기도 합니다. + +

+ + +

쿼리 결과에서 데이터 가져오기

+

+ 쿼리 결과를 단순히 표시만 하는 것보다 이를 다른 작업에 사용할 수 있습니다. +예를 들어, 사용자 사전에서 철자를 검색한 다음 이것을 다른 제공자 내에서 찾아볼 수 있습니다. + 이렇게 하려면, {@link android.database.Cursor}에서 행을 계속 반복하면 됩니다. +

+
+
+// Determine the column index of the column named "word"
+int index = mCursor.getColumnIndex(UserDictionary.Words.WORD);
+
+/*
+ * Only executes if the cursor is valid. The User Dictionary Provider returns null if
+ * an internal error occurs. Other providers may throw an Exception instead of returning null.
+ */
+
+if (mCursor != null) {
+    /*
+     * Moves to the next row in the cursor. Before the first movement in the cursor, the
+     * "row pointer" is -1, and if you try to retrieve data at that position you will get an
+     * exception.
+     */
+    while (mCursor.moveToNext()) {
+
+        // Gets the value from the column.
+        newWord = mCursor.getString(index);
+
+        // Insert code here to process the retrieved word.
+
+        ...
+
+        // end of while loop
+    }
+} else {
+
+    // Insert code here to report an error if the cursor is null or the provider threw an exception.
+}
+
+

+ {@link android.database.Cursor} 구현에는 +여러 개의 "가져오기" 메서드가 들어 있어 개체로부터 여러 가지 유형의 데이터를 검색합니다. 예를 들어 이전 조각에서는 +{@link android.database.Cursor#getString getString()}을 사용합니다. +여기에는 해당 열의 데이터 유형을 나타내는 값을 반환하는 +{@link android.database.Cursor#getType getType()} 메서드도 있습니다. +

+ + + +

콘텐츠 제공자 권한

+

+ 제공자의 애플리케이션은 해당 제공자의 데이터에 액세스하려면 다른 애플리케이션이 반드시 가지고 있어야 하는 +권한을 지정할 수 있습니다. 이와 같은 권한을 통해 사용자는 한 애플리케이션이 어느 데이터에 액세스하려 시도할지 +알 수 있습니다. 다른 애플리케이션은 제공자의 요구 사항을 근거로 해당 제공자에 액세스하기 위해 필요한 +권한을 요청합니다. 최종 사용자는 애플리케이션을 설치할 때 요청된 권한을 보게 됩니다. + +

+

+ 제공자의 애플리케이션이 아무 권한도 지정하지 않은 경우, 다른 애플리케이션은 해당 제공자의 +데이터에 액세스할 수 없습니다. 그러나 제공자의 애플리케이션 내에 있는 구성 요소는 +지정된 권한과 무관하게 항상 읽기 및 쓰기 액세스 권한을 모두 가지고 있습니다. +

+

+ 이전에 언급한 것과 같이 사용자 사전 제공자에서 데이터를 검색하려면 +android.permission.READ_USER_DICTIONARY 권한이 필요합니다. + 이 제공자에게는 데이터 삽입, 업데이트 또는 삭제에 각각 별도의 android.permission.WRITE_USER_DICTIONARY +권한이 있습니다. +

+

+ 제공자에 액세스하는 데 필요한 권한을 얻으려면 애플리케이션은 +자신의 매니페스트 파일에 있는 <uses-permission> +으로 그러한 권한을 요청합니다. Android 패키지 관리자가 애플리케이션을 설치하는 경우, +사용자는 애플리케이션이 요청하는 권한을 모두 승인해야 합니다. 사용자가 이를 모두 승인하면 +패키지 관리자가 설치를 계속하지만, 사용자가 이를 승인하지 않으면 패키지 관리자는 설치를 중단합니다. + +

+

+ +다음 <uses-permission> + 요소는 사용자 사전 제공자에 읽기 액세스 권한을 요청하는 것입니다. +

+
+    <uses-permission android:name="android.permission.READ_USER_DICTIONARY">
+
+

+ 제공자 액세스 권한이 미치는 영향은 +보안 및 권한 가이드에 좀 더 자세히 설명되어 있습니다. +

+ + + +

데이터 삽입, 업데이트 및 삭제

+

+ 제공자로부터 데이터를 검색하는 것과 같은 방식으로, 데이터를 수정할 때에도 제공자 클라이언트와 제공자의 +{@link android.content.ContentProvider} 사이의 상호작용을 사용합니다. + {@link android.content.ContentResolver}의 메서드를 호출하면서 +{@link android.content.ContentProvider}의 상응하는 메서드로 전달된 인수를 사용합니다. +제공자와 제공자의 클라이언트가 보안과 프로세스간 통신을 자동으로 처리합니다. +

+

데이터 삽입

+

+ 데이터를 제공자 안으로 삽입하려면, +{@link android.content.ContentResolver#insert ContentResolver.insert()} + 메서드를 호출합니다. 이 메서드는 제공자에 새로운 행을 삽입하고 해당 열에 대한 콘텐츠 URI를 반환합니다. + 이 조각은 사용자 사전 제공자에 새 단어를 삽입하는 방법을 나타낸 것입니다. +

+
+// Defines a new Uri object that receives the result of the insertion
+Uri mNewUri;
+
+...
+
+// Defines an object to contain the new values to insert
+ContentValues mNewValues = new ContentValues();
+
+/*
+ * Sets the values of each column and inserts the word. The arguments to the "put"
+ * method are "column name" and "value"
+ */
+mNewValues.put(UserDictionary.Words.APP_ID, "example.user");
+mNewValues.put(UserDictionary.Words.LOCALE, "en_US");
+mNewValues.put(UserDictionary.Words.WORD, "insert");
+mNewValues.put(UserDictionary.Words.FREQUENCY, "100");
+
+mNewUri = getContentResolver().insert(
+    UserDictionary.Word.CONTENT_URI,   // the user dictionary content URI
+    mNewValues                          // the values to insert
+);
+
+

+ 새로운 행에 대한 데이터는 단일 행 커서와 형태가 유사한 단일 {@link android.content.ContentValues} 개체로 +이동합니다. 이 개체 내의 열은 모두 같은 데이터 유형을 가지지 않아도 됩니다. +또한 아예 값을 지정하고 싶지 않은 경우라면 열을 null로 설정할 수 있습니다. +이때 {@link android.content.ContentValues#putNull ContentValues.putNull()}을 사용하면 됩니다. +

+

+ 조각은 _ID 열을 추가하지 않습니다. 이 열은 자동으로 유지관리되기 때문입니다. + 제공자는 추가된 모든 열마다 고유한 _ID 값을 할당합니다. + 제공자는 보통 이 값을 테이블의 기본 키로 사용합니다. +

+

+ newUri에 반환된 콘텐츠 URI는 다음과 같은 형식으로 새로 추가된 행을 식별합니다. + +

+
+content://user_dictionary/words/<id_value>
+
+

+ <id_value>는 새로운 행에 대한 _ID의 콘텐츠입니다. + 대부분의 제공자는 이런 형태의 콘텐츠 URI를 자동으로 감지할 수 있으며, 그런 다음 해당 행에서 요청된 작업을 수행합니다. + +

+

+ 반환된 {@link android.net.Uri}에서 _ID 값을 가져오려면 +{@link android.content.ContentUris#parseId ContentUris.parseId()}를 호출합니다. +

+

데이터 업데이트

+

+ 행을 업데이트하려면 업데이트된 값과 함께 {@link android.content.ContentValues} 개체를 사용합니다. +이때 값은 삽입할 때와 똑같고, 선택 기준은 쿼리할 때와 같습니다. + 사용하는 클라이언트 메서드는 +{@link android.content.ContentResolver#update ContentResolver.update()}입니다. +값을 추가하는 것은 업데이트 중인 열에 대한 {@link android.content.ContentValues} 개체에만 하면 됩니다. +열의 콘텐츠를 삭제하려면, 값을 null로 설정하십시오. +

+

+ 다음 조각에서는 로케일이 언어 "en"인 행 모두를 로케일 null을 가지도록 변경합니다. + 반환 값이 업데이트된 행 수입니다. +

+
+// Defines an object to contain the updated values
+ContentValues mUpdateValues = new ContentValues();
+
+// Defines selection criteria for the rows you want to update
+String mSelectionClause = UserDictionary.Words.LOCALE +  "LIKE ?";
+String[] mSelectionArgs = {"en_%"};
+
+// Defines a variable to contain the number of updated rows
+int mRowsUpdated = 0;
+
+...
+
+/*
+ * Sets the updated value and updates the selected words.
+ */
+mUpdateValues.putNull(UserDictionary.Words.LOCALE);
+
+mRowsUpdated = getContentResolver().update(
+    UserDictionary.Words.CONTENT_URI,   // the user dictionary content URI
+    mUpdateValues                       // the columns to update
+    mSelectionClause                    // the column to select on
+    mSelectionArgs                      // the value to compare to
+);
+
+

+ +{@link android.content.ContentResolver#update ContentResolver.update()}를 호출하는 경우에는 사용자 입력도 삭제해야 합니다. 이 내용에 관해 자세히 알아보려면 +악의적인 입력에 대한 보호 섹션을 읽어 보십시오. +

+

데이터 삭제

+

+ 행을 삭제하는 것은 행 데이터를 검색하는 것과 비슷합니다. 즉, 삭제하고자 하는 행에 대한 선택 기준을 지정하면 +클라이언트 메서드가 삭제된 행 수를 반환하는 식입니다. + 다음 조각은 앱 ID가 "user"와 일치하는 행을 삭제합니다. 메서드가 삭제된 행 수를 반환합니다. + +

+
+
+// Defines selection criteria for the rows you want to delete
+String mSelectionClause = UserDictionary.Words.APP_ID + " LIKE ?";
+String[] mSelectionArgs = {"user"};
+
+// Defines a variable to contain the number of rows deleted
+int mRowsDeleted = 0;
+
+...
+
+// Deletes the words that match the selection criteria
+mRowsDeleted = getContentResolver().delete(
+    UserDictionary.Words.CONTENT_URI,   // the user dictionary content URI
+    mSelectionClause                    // the column to select on
+    mSelectionArgs                      // the value to compare to
+);
+
+

+ {@link android.content.ContentResolver#delete ContentResolver.delete()}를 +호출하는 경우에는 사용자 입력도 삭제해야 합니다. 이 내용에 관해 자세히 알아보려면 +악의적인 입력에 대한 보호 섹션을 읽어 보십시오. +

+ +

제공자 데이터 유형

+

+ 콘텐츠 제공자는 아주 다양한 데이터 유형을 제공할 수 있습니다. +사용자 사전 제공자는 텍스트만 제공하지만, 제공자는 다음과 같은 형식도 제공할 수 있습니다. +

+
    +
  • + 정수 +
  • +
  • + 긴 정수(Long) +
  • +
  • + 부동 소수점 수 +
  • +
  • + 긴 부동 소수점 수(double) +
  • +
+

+ 제공자가 종종 사용하는 또 다른 데이터 유형은 64KB 바이트 배열로 구현되는 BLOB(Binary Large OBject)입니다. + 이용 가능한 데이터 유형을 확인하려면 +{@link android.database.Cursor} 클래스 "가져오기" 메서드를 살펴보면 됩니다. +

+

+ 제공자 내의 각 열에 대한 데이터 유형은 보통 자신의 문서에 목록으로 나열되어 있습니다. + 사용자 사전 제공자의 데이터 유형은 제공자의 계약 클래스 +{@link android.provider.UserDictionary.Words}의 참조 문서에 나열되어 있습니다(계약 클래스는 +계약 클래스 섹션에 설명되어 있습니다). + @link android.database.Cursor#getType + Cursor.getType()}을 호출해서도 데이터 유형을 결정할 수 있습니다. +

+

+ 제공자는 스스로 정의하는 각 콘텐츠 URI의 MIME 데이터 유형 정보도 유지관리합니다. +MIME 유형 정보를 사용하면 애플리케이션이 제공자가 제공하는 데이터를 처리할 수 있을지 알아낼 수도 있고, +MIME 유형을 근거로 처리 유형을 선택할 수도 있습니다. +MIME 유형이 필요한 시점은 주로 복잡한 데이터 구조 또는 파일이 들어 있는 제공자를 다룰 때입니다. + 예를 들어 연락처 제공자 내의 {@link android.provider.ContactsContract.Data} + 테이블은 MIME 유형을 사용하여 각 행에 저장된 연락처 데이터의 유형에 레이블을 붙입니다. + 콘텐츠 URI에 상응하는 MIME 유형을 가져오려면 +{@link android.content.ContentResolver#getType ContentResolver.getType()}을 호출하십시오. +

+

+ MIME 유형 참조 섹션에서 표준 및 사용자 지정 MIME 유형의 +두 가지를 모두 설명하고 있습니다. +

+ + + +

제공자 액세스의 대체 형식

+

+ 애플리케이션 개발에 있어 중요한 제공자 액세스의 대체 형식에는 다음과 같은 세 가지가 있습니다. +

+
    +
  • + 일괄 액세스: {@link android.content.ContentProviderOperation} + 클래스에 있는 메서드로 일괄 액세스 호출을 생성하고 +{@link android.content.ContentResolver#applyBatch ContentResolver.applyBatch()}로 이를 적용할 수 있습니다. +
  • +
  • + 비동기식 쿼리: 쿼리는 별도의 스레드에서 수행해야 합니다. 이 작업을 수행하는 한 가지 방법으로 +{@link android.content.CursorLoader} 개체를 사용하는 것이 있습니다. 이 사용 방법은 +로더 가이드에 있는 예시에서 설명합니다. + +
  • +
  • + 인텐트를 통한 데이터 액세스: +인텐트를 제공자에 직접 보낼 수는 없지만, 인텐트를 제공자의 애플리케이션에 보낼 수는 있습니다. +보통은 이것이 제공자의 데이터를 수정하기에 가장 좋습니다. +
  • +
+

+ 일괄 액세스와 인텐트를 통한 수정은 다음 섹션에서 설명되어 있습니다. +

+

일괄 액세스

+

+ 제공자에 일괄 액세스를 하면 많은 수의 행을 삽입할 때, 같은 메서드 호출 내에서 여러 개의 테이블에 여러 행을 삽입할 때 +또는 전반적으로, 프로세스 경계를 가로질러 일련의 작업을 수행하는 경우(원자성 작업) 유용합니다. + +

+

+ "일괄 모드"로 제공자에 액세스하려면 +{@link android.content.ContentProviderOperation} 개체의 배열을 생성한 다음 이를 콘텐츠 제공자에게 +{@link android.content.ContentResolver#applyBatch ContentResolver.applyBatch()}로 + 발송하면 됩니다. +이 메서드에는 특정한 콘텐츠 URI보다는 콘텐츠 제공자의 권한을 전달합니다. +이렇게 하면 배열 내의 각 {@link android.content.ContentProviderOperation} 개체가 +서로 다른 테이블에 대해 작용하도록 할 수 있습니다. {@link android.content.ContentResolver#applyBatch + ContentResolver.applyBatch()}를 호출하면 일련의 결과를 반환합니다. +

+

+ {@link android.provider.ContactsContract.RawContacts} 계약 클래스의 설명에 + 일괄 삽입을 설명하는 코드 조각이 포함되어 있습니다. +연락처 관리자 +샘플 애플리케이션에는 ContactAdder.java +소스 파일의 일괄 액세스 예시가 포함되어 있습니다. +

+ +

인텐트를 통한 데이터 액세스

+

+ 인텐트는 콘텐츠 제공자에 간접 액세스를 제공할 수 있습니다. 애플리케이션에 액세스 권한이 없는데도 +사용자에게 제공자 내의 데이터에 액세스 권한을 허가하려면, 권한을 가지고 있는 애플리케이션에서 결과 인텐트를 다시 가져오거나 +권한이 있는 애플리케이션을 활성화한 다음 사용자에게 그 애플리케이션에서 작업하도록 하면 됩니다. + +

+

임시 권한으로 액세스 얻기

+

+ 적절한 액세스 권한이 없더라도 콘텐츠 제공자 내의 데이터에 액세스할 수는 있습니다. +권한을 가지고 있는 애플리케이션에 인텐트를 보내 "URI" 권한이 들어 있는 결과 인텐트를 돌려받으면 됩니다. + + 이들 권한은 특정 콘텐츠 URI에 대한 권한으로, 이를 수신하는 액티비티가 완료될 때까지 유지됩니다. + 영구 권한을 가지고 있는 애플리케이션은 결과 인텐트에 다음과 같이 플래그를 설정하여 임시 권한을 허가합니다. + +

+
    +
  • + 읽기 권한: +{@link android.content.Intent#FLAG_GRANT_READ_URI_PERMISSION} +
  • +
  • + 쓰기 권한: +{@link android.content.Intent#FLAG_GRANT_WRITE_URI_PERMISSION} +
  • +
+

+ 참고: 이와 같은 플래그는 콘텐츠 URI에 권한이 들어 있는 제공자에 일반적인 읽기 또는 쓰기 액세스 +권한을 부여하지는 않습니다. 이 액세스는 URI 자체에만 해당됩니다. +

+

+ 제공자는 자신의 매니페스트 내의 콘텐츠 URI에 대한 URI 권한을 정의합니다. 이때 +<provider> + 요소의 +android:grantUriPermission + 속성을 사용하며, +<provider> + 요소의 +<grant-uri-permission> + 하위 요소도 사용합니다. +URI 권한 메커니즘은 "URI 권한" 섹션의 보안 및 권한 가이드에 +자세히 설명되어 있습니다. +

+

+ 예를 들어, {@link android.Manifest.permission#READ_CONTACTS} 권한이 없더라도 +연락처 제공자 내의 연락처에 대한 데이터를 검색할 수 있습니다. +이 작업을 하면 좋은 예로, 연락처에 기재된 사람의 생일에 전자 축하 카드를 보내주는 애플리케이션을 들 수 있습니다. +{@link android.Manifest.permission#READ_CONTACTS}를 요청하면 +사용자의 연락처 전체와 해당 정보 일체에 대한 액세스를 부여하므로, 그 대신 애플리케이션에서 어느 연락처를 사용할지 사용자가 직접 제어하도록 해주는 편이 낫습니다. + 이렇게 하려면, 다음 절차를 사용합니다. +

+
    +
  1. + 애플리케이션이{@link android.app.Activity#startActivityForResult +startActivityForResult()} 메서드를 사용해서 +{@link android.content.Intent#ACTION_PICK} 작업과 "contacts" MIME 유형 +{@link android.provider.ContactsContract.RawContacts#CONTENT_ITEM_TYPE}이 들어 있는 인텐트를 보냅니다. + +
  2. +
  3. + 이 인텐트는 피플 앱의 "선택" 액티비티에 대한 인텐트 필터와 일치하기 때문에, 이 액티비티가 전경으로 나옵니다. + +
  4. +
  5. + 선택 액티비티에서 사용자가 업데이트할 연락처를 선택합니다. + 이렇게 되면 선택 액티비티가 +{@link android.app.Activity#setResult setResult(resultcode, intent)} +를 호출하여 애플리케이션에 돌려줄 인텐트를 설정합니다. +이 인텐트에 사용자가 선택한 연락처의 콘텐츠 URI와 "추가" 플래그 +{@link android.content.Intent#FLAG_GRANT_READ_URI_PERMISSION}이 들어 있습니다. +이러한 플래그가 URI에 앱으로의 권한을 허가하여 콘텐츠 URI가 가리킨 연락처에 대한 데이터를 읽을 수 있도록 합니다. +그런 다음 선택 액티비티는 {@link android.app.Activity#finish()}를 호출하여 +애플리케이션에 제어를 반환합니다. +
  6. +
  7. + 액티비티가 전경으로 돌아오고, 시스템은 액티비티의 +{@link android.app.Activity#onActivityResult onActivityResult()} 메서드를 +호출합니다. 이 메서드가 피플 앱의 선택 액티비티가 생성한 결과 인텐트를 수신합니다. + +
  8. +
  9. + 결과 인텐트로부터 받은 콘텐츠 URI를 사용하면 연락처 제공자에서 연락처의 데이터를 읽을 수 있습니다. +이것은 매니페스트에서 제공자로의 영구 읽기 액세스 권한을 요청하지 않았어도 적용됩니다. + 그러면 연락처의 생일 정보나 당사자의 이메일 주소를 가져와 전자 축하 카드를 보낼 수 있게 됩니다. + +
  10. +
+

다른 애플리케이션 사용

+

+ 개발자에게 액세스 권한이 없는 데이터를 사용자가 수정할 수 있도록 허용하는 간단한 방법은 +해당 권한을 가지고 있는 애플리케이션을 활성화한 다음 사용자에게 그곳에서 작업하도록 해주는 것입니다. +

+

+ 예를 들어, 캘린더 애플리케이션은 +애플리케이션의 삽입 UI를 활성화해주는 {@link android.content.Intent#ACTION_INSERT} 인텐트를 수락합니다. 애플리케이션이 UI를 미리 채우는 데 사용하는 이 인텐트의 "extras" 데이터를 +전달할 수 있게 됩니다. 반복적인 이벤트의 구문은 복잡하므로 +캘린더 제공자에 이벤트를 삽입하기 좋은 방법은 +{@link android.content.Intent#ACTION_INSERT}로 캘린더 앱을 활성화하고 사용자에게 그곳에서 이벤트를 +삽입하게 하는 것입니다. +

+ +

계약 클래스

+

+ 계약 클래스는 애플리케이션이 콘텐츠 URI, 열 이름, 인텐트 작업 및 콘텐츠 제공자의 다른 기능과 +작업할 수 있게 도와주는 상수를 정의합니다. 계약 클래스는 제공자와 함께 자동으로 포함되지 않습니다. +해당 제공자의 개발자가 이를 정의한 다음 다른 개발자가 사용할 수 있도록 해야 합니다. + Android 플랫폼 내에 포함된 제공자는 대부분 패키지 +{@link android.provider} 안에 상응하는 계약 클래스를 가지고 있습니다. +

+

+ 예를 들어, 사용자 사전 제공자에는 콘텐츠 URI와 열 이름 상수가 들어 있는 +{@link android.provider.UserDictionary} 계약 클래스가 있습니다. +"단어" 테이블에 대한 콘텐츠 URI는 상수 +{@link android.provider.UserDictionary.Words#CONTENT_URI UserDictionary.Words.CONTENT_URI}에 정의됩니다. + {@link android.provider.UserDictionary.Words} 클래스에도 +열 이름 상수가 들어 있으며, 이는 이 가이드의 예시 조각에서 사용됩니다. +예를 들어 쿼리 프로젝션은 다음과 같이 정의될 수 있습니다. +

+
+String[] mProjection =
+{
+    UserDictionary.Words._ID,
+    UserDictionary.Words.WORD,
+    UserDictionary.Words.LOCALE
+};
+
+

+ 또 다른 계약 클래스는 연락처 제공자의 {@link android.provider.ContactsContract}입니다. + 이 클래스에 대한 참조 문서에는 예시 코드 조각이 포함되어 있습니다. +이것의 하위 클래스 중 하나인 {@link android.provider.ContactsContract.Intents.Insert}는 + 인텐트와 인텐트 데이터의 상수가 들어 있는 계약 클래스입니다. +

+ + + +

MIME 유형 참조

+

+ 콘텐츠 제공자는 표준 MIME 미디어 유형이나 사용자 지정 MIME 유형 문자열, 또는 그 두 가지를 모두 반환할 수 있습니다. +

+

+ MIME 유형의 형식은 다음과 같습니다. +

+
+type/subtype
+
+

+ 예를 들어, 잘 알려진 MIME 유형 text/html에는 text 유형과 +html 하위 유형이 있습니다. 제공자가 URI에 대해 이 유형을 반환하면, +해당 URI를 사용하는 쿼리가 HTML 태그가 들어 있는 텍스트를 반환할 것이라는 뜻입니다. +

+

+ 사용자 지정 MIME 유형 문자열은 "공급업체별" MIME 유형이라고도 불리며 +이쪽의 유형하위 유형 값이 더 복잡합니다. 유형 값은 경우에 따라 항상 다음과 같습니다. +

+
+vnd.android.cursor.dir
+
+

+ (여러 행의 경우) +

+
+vnd.android.cursor.item
+
+

+ (하나의 행의 경우). +

+

+ 하위 유형은 제공자별로 다릅니다. Android 내장 제공자는 보통 단순한 하위 유형을 가지고 있습니다. + 예를 들어 연락처 애플리케이션이 전화 번호에 대한 행을 생성한다고 합니다. 이때 애플리케이션은 해당 행에 다음과 같은 MIME 유형을 설정하게 됩니다. + +

+
+vnd.android.cursor.item/phone_v2
+
+

+ 하위 유형 값은 단순히 phone_v2인 것을 눈여겨 보십시오. +

+

+ 다른 제공자 개발자도 해당 제공자의 권한과 테이블 이름을 근거로 나름의 하위 유형 패턴을 만들 수 있습니다. + 예를 들어 기차 시간표가 들어 있는 제공자가 있다고 생각해 보겠습니다. + 제공자의 권한은 com.example.trains이고 이 안에 테이블 +Line1, Line2, Line3이 들어 있습니다. 다음과 같은 콘텐츠 URI에 대응하는 방식을 살펴보십시오. +

+

+

+content://com.example.trains/Line1
+
+

+ 테이블 Line1의 경우, 제공자는 다음과 같은 MIME 유형을 반환합니다. +

+
+vnd.android.cursor.dir/vnd.example.line1
+
+

+ 다음과 같은 콘텐츠 URI에 대응하는 방식을 살펴보십시오. +

+
+content://com.example.trains/Line2/5
+
+

+ 테이블 Line2의 행 5에 대해 제공자가 반환하는 MIME 유형은 다음과 같습니다. +

+
+vnd.android.cursor.item/vnd.example.line2
+
+

+ 대부분의 콘텐츠 제공자는 자신이 사용하는 MIME 유형에 대한 계약 클래스 상수를 정의합니다. +예를 들어, 연락처 제공자 계약 클래스 {@link android.provider.ContactsContract.RawContacts}는 +단일 연락처 행의 MIME 유행에 대한 + 상수 {@link android.provider.ContactsContract.RawContacts#CONTENT_ITEM_TYPE}을 +정의합니다. +

+

+ 한 행에 대한 콘텐츠 URI는 +콘텐츠 URI 섹션에 설명되어 있습니다. +

diff --git a/docs/html-intl/intl/ko/guide/topics/providers/content-provider-creating.jd b/docs/html-intl/intl/ko/guide/topics/providers/content-provider-creating.jd new file mode 100644 index 0000000000000000000000000000000000000000..6757194a9f288242f1e2179adbf6e82f8197eb3b --- /dev/null +++ b/docs/html-intl/intl/ko/guide/topics/providers/content-provider-creating.jd @@ -0,0 +1,1214 @@ +page.title= 콘텐츠 제공자 생성 +@jd:body +
+
+ + +

이 문서의 내용

+
    +
  1. + 데이터 저장소 설계 +
  2. +
  3. + 콘텐츠 URI 설계 +
  4. +
  5. + ContentProvider 클래스 구현 +
      +
    1. + 필수 메서드 +
    2. +
    3. + query() 메서드 구현 +
    4. +
    5. + insert() 메서드 구현 +
    6. +
    7. + delete() 메서드 구현 +
    8. +
    9. + update() 메서드 구현 +
    10. +
    11. + onCreate() 메서드 구현 +
    12. +
    +
  6. +
  7. + 콘텐츠 제공자 MIME 유형 구현 +
      +
    1. + 테이블의 MIME 유형 +
    2. +
    3. + 파일의 MIME 유형 +
    4. +
    +
  8. +
  9. + 계약 클래스 구현 +
  10. +
  11. + 콘텐츠 제공자 권한 구현 +
  12. +
  13. + <provider> 요소 +
  14. +
  15. + 인텐트 및 데이터 액세스 +
  16. +
+

Key 클래스

+
    +
  1. + {@link android.content.ContentProvider} +
  2. +
  3. + {@link android.database.Cursor} +
  4. +
  5. + {@link android.net.Uri} +
  6. +
+

관련 샘플

+
    +
  1. + + 메모장 샘플 애플리케이션 + +
  2. +
+

참고 항목

+
    +
  1. + + 콘텐츠 제공자 기본 정보 +
  2. +
  3. + + 캘린더 제공자 +
  4. +
+
+
+ + +

+ 콘텐츠 제공자는 데이터의 중앙 리포지토리로의 액세스를 관리합니다. Android 애플리케이션에서는 +제공자를 하나 이상의 클래스로, 매니페스트 파일에 있는 요소와 함께 구현합니다. + 클래스 중 하나가 하위 클래스 +{@link android.content.ContentProvider}를 구현하며, +이것이 제공자와 다른 애플리케이션 사이의 인터페이스입니다. 콘텐츠 제공자는 다른 애플리케이션에 데이터를 사용할 수 있게 해주도록 만들어져 있지만, +물론 애플리케이션 내에 사용자로 하여금 제공자가 관리하는 데이터를 쿼리하고 수정할 수 있게 허용하는 +액티비티가 있을 수도 있습니다. +

+

+ 이 주제의 나머지 부분은 콘텐츠 제공자를 구축하기 위한 기본 단계 목록과 +사용할 API 목록으로 이루어져 있습니다. +

+ + + +

구축을 시작하기 전에

+

+ 제공자 구축을 시작하기 전에 우선 다음 단계를 수행하십시오. +

+
    +
  1. + 콘텐츠 제공자가 필요한지 결정합니다. 다음 기능 중 하나 이상을 제공하려면 +콘텐츠 제공자를 구축해야 합니다. +
      +
    • 다른 애플리케이션에 복잡한 데이터나 파일을 제공하고자 하는 경우
    • +
    • 사용자로 하여금 개발자의 앱에서 다른 앱으로 복잡한 데이터를 복사하도록 허용하고자 하는 경우
    • +
    • 검색 프레임워크를 사용한 사용자 지정 검색 제안을 제공하고자 하는 경우
    • +
    +

    + 용도가 본인의 애플리케이션 안에서로 완전히 한정되어 있는 경우에는 +제공자가 SQLite 데이터베이스를 사용하도록 하지 않아도 됩니다. +

    +
  2. +
  3. + 아직 읽지 않았다면, 지금 바로 + +콘텐츠 제공자 기본 정보를 읽고 제공자에 대해 자세히 알아보십시오. +
  4. +
+

+ 그런 다음, 다음 단계를 따라 제공자를 구축합니다. +

+
    +
  1. + 데이터를 위한 원시 저장소를 설계합니다. 콘텐츠 제공자는 두 가지 방식으로 데이터를 제공합니다. +
    +
    + 파일 데이터 +
    +
    + 일반적으로 사진, 오디오 또는 동영상과 같은 +파일에 들어가는 데이터입니다. 이런 파일을 애플리케이션의 비공개 +공간에 저장합니다. 제공자는 다른 애플리케이션으로부터 온 파일 요청에 응답하여 +해당 파일로의 핸들을 제공할 수 있습니다. +
    +
    + "구조적" 데이터 +
    +
    + 일반적으로 데이터베이스, 배열 또는 유사 구조에 들어가는 데이터입니다. + 이 데이터를 행과 열로 이루어진 테이블과 호환되는 형식으로 저장합니다. +행은 사람이나 인벤토리의 항목과 같은 엔티티를 나타냅니다. +열은 해당 엔티티에 대한 몇 가지 데이터, 예를 들어 사람 이름이나 항목 가격 등을 나타냅니다. +이 유형의 데이터를 저장하는 보편적인 방법은 SQLite 데이터베이스 안에 저장하는 것이지만, +모든 유형의 영구적인 저장소를 사용해도 됩니다. Android 시스템에서 사용할 수 있는 저장소 유형에 대해 자세히 알아보려면, + +데이터 저장소 설계 섹션을 참조하십시오. +
    +
    +
  2. +
  3. + {@link android.content.ContentProvider} 클래스와 +필수 메서드의 구체적인 구현을 정의합니다. 이 클래스는 데이터와 나머지 Android 시스템 사이의 +인터페이스입니다. 이 클래스에 관한 자세한 정보는 +ContentProvider 클래스 구현 섹션을 참조하십시오. +
  4. +
  5. + 제공자의 권한 문자열, 그 콘텐츠 URI 및 열 이름을 정의합니다. +제공자 애플리케이션이 인텐트를 처리하게 하려면, 인텐트 작업과 추가 데이터 및 +플래그도 정의합니다. 데이터에 액세스하기를 원하는 애플리케이션에 요구할 권한도 +정의합니다. 이 모든 값은 별도의 계약 클래스에서 상수로 정의하는 것을 고려해보는 +것이 좋습니다. 이 클래스를 나중에 다른 개발자에게 노출할 수 있습니다. +콘텐츠 URI에 관한 자세한 정보는 +콘텐츠 URI 설계 섹션을 참조하십시오. + 인텐트에 관한 자세한 정보는 +인텐트 및 데이터 액세스 섹션을 참조하십시오. +
  6. +
  7. + 샘플 데이터, 또는 제공자와 클라우드 기반 데이터 사이에서 +데이터를 동기화할 수 있는 {@link android.content.AbstractThreadedSyncAdapter} 구현 등과 같이 +다른 선택적 조각을 추가합니다. +
  8. +
+ + + +

데이터 저장소 설계

+

+ 콘텐츠 제공자는 구조화된 형식으로 저장된 데이터로의 인터페이스입니다. +인터페이스를 생성하기 전에 우선 데이터 저장 방식부터 결정해야 합니다. +데이터는 원하는 형식 아무 것으로나 저장할 수 있으며 그런 다음에 필요에 따라 해당 데이터를 읽고 쓸 인터페이스를 설계합니다. +

+

+ 다음은 Android에서 사용할 수 있는 몇 가지 데이터 저장소 기술입니다. +

+
    +
  • + Android 시스템에는 Android 자체 제공자가 테이블 지향적 데이터를 +저장하는 데 사용하는 SQLite 데이터베이스 API가 포함됩니다. +{@link android.database.sqlite.SQLiteOpenHelper} 클래스는 데이터베이스를 생성할 수 있게 돕고, +{@link android.database.sqlite.SQLiteDatabase} 클래스는 데이터베이스 액세스를 위한 +기본 클래스입니다. +

    + 리포지토리를 구현하기 위해 데이터베이스를 사용하지 않아도 된다는 점을 기억하십시오. +제공자는 외부에 테이블 집합으로 나타나 관계적 데이터베이스와 비슷해 보이지만, +이것은 제공자의 내부 구현에 필요한 것은 아닙니다. +

    +
  • +
  • + 파일 데이터를 저장하는 데 있어 Android에는 다양한 파일 지향적 API가 있습니다. + 파일 저장소에 관해 자세히 알아보려면 +데이터 저장소 주제를 읽어 보십시오. +음악이나 동영상 등 미디어 관련 데이터를 제공하는 제공자를 설계하는 경우, +제공자가 테이블 데이터와 파일을 조합 할 수 있습니다. +
  • +
  • + 네트워크 기반 데이터를 다루는 경우, {@link java.net} 및 +{@link android.net} 내의 클래스를 사용하십시오. 네트워크 기반 데이터를 +데이터베이스와 같은 로컬 데이터 스토어와 동기화한 다음, 해당 데이터를 테이블이나 파일로 제공할 수도 있습니다. + +샘플 동기화 어댑터 샘플 애플리케이션이 이런 유형의 동기화를 보여줍니다. +
  • +
+

+ 데이터 설계 시 고려할 사항 +

+

+ 다음은 제공자의 데이터 구조를 설계할 때 유용한 몇 가지 팁입니다. +

+
    +
  • + 테이블 데이터는 언제나 제공자가 유지관리하는 "기본 키" 열을 +각 행의 고유한 숫자 값으로 보유하고 있어야 합니다. 이 값을 사용하여 해당 행을 다른 테이블의 +관련 행에 연결시킬 수 있습니다(이를 "외래 키"로 사용). 이 열에는 어느 이름이든 사용할 수 있지만 +{@link android.provider.BaseColumns#_ID BaseColumns._ID}를 사용하는 것이 가장 좋습니다. +왜냐하면 제공자 쿼리 결과를 +{@link android.widget.ListView}에 연결하려면 검색된 열 중 하나가 +_ID라는 이름을 사용해야 하기 때문입니다. +
  • +
  • + 비트맵 이미지나 파일 지향적 데이터의 매우 큰 조각을 제공하려면 +테이블 안에 직접 저장하기보다는 파일에 데이터를 저장한 뒤 +간접적으로 제공합니다. 이렇게 하는 경우, 제공자의 사용자들에게 데이터에 액세스하려면 +{@link android.content.ContentResolver} 파일 메서드를 사용해야 한다고 알려야 합니다. +
  • +
  • + BLOB(Binary Large OBject) 데이터 유형을 사용하여 크기가 다르거나 +구조가 다양한 데이터를 저장합니다. 예를 들어, BLOB 열을 사용하여 +프로토콜 버퍼 또는 +JSON 구조를 저장할 수 있습니다. +

    + BLOB를 사용하여 스키마에 종속되지 않은 테이블을 구현할 수도 있습니다. +이 유형의 테이블에서는, 기본 키 열, MIME 유형 열 및 하나 이상의 일반적인 열을 BLOB로 정의합니다. + +BLOB 열에 있는 데이터의 의미는 MIME 유형 열에 있는 값으로 나타냅니다. +이렇게 하면 같은 테이블에 여러 가지 행 유형을 저장할 수 있습니다. 연락처 제공자의 "데이터" 테이블 +{@link android.provider.ContactsContract.Data}가 +스키마에 종속되지 않은 테이블의 한 가지 예입니다. +

    +
  • +
+ +

콘텐츠 URI 설계

+

+ 콘텐츠 URI는 제공자에서 데이터를 식별하는 URI입니다. +콘텐츠 URI에는 전체 제공자의 상징적인 이름(제공자의 권한)과 +테이블 또는 파일을 가리키는 이름(경로)이 포함됩니다. +선택 항목 ID 부분은 테이블 내의 개별적인 행을 가리킵니다. +{@link android.content.ContentProvider}의 모든 데이터 액세스 메서드는 +콘텐츠 URI를 인수로 가집니다. 이를 통해 액세스할 테이블, 행 또는 파일을 결정할 수 있습니다. +

+

+ 콘텐츠 URI의 기본 정보는 + +콘텐츠 제공자 기본 정보에 설명되어 있습니다. +

+

권한 설계

+

+ 제공자에는 보통 하나의 권한이 있으며, 이것이 Android 내부 이름 역할을 합니다. +다른 제공자와의 충돌을 피하려면, 제공자 권한의 기반으로 인터넷 도메인 소유권(역방향)을 +사용해야 합니다. 이 권장 사항은 Android 패키지 이름에도 적용되므로, +제공자 권한을 제공자가 들어 있는 패키지의 이름 확장자로 정의해도 됩니다. + 예를 들어, Android 패키지 이름이 +com.example.<appname>라면, 제공자에게 +com.example.<appname>.provider 권한을 부여해야 합니다. +

+

경로 구조 설계

+

+ 개발자는 보통 권한으로부터 콘텐츠 URI를 생성할 때 개별적인 테이블을 가리키는 +경로를 추가하는 방식을 사용합니다. 예를 들어, table1과 +table2라는 테이블이 있다면, 이전 예시의 권한을 조합하여 +콘텐츠 URIcom.example.<appname>.provider/table1와 +com.example.<appname>.provider/table2를 도출합니다. + +경로는 하나의 세그먼트에 국한되지 않으며, 경로의 각 수준에 대한 테이블이 아니어도 됩니다. +

+

콘텐츠 URI ID 처리

+

+ 규칙에 의하면, 제공자는 URI 맨 끝에서 행에 대한 ID 값이 있는 콘텐츠 URI를 허용하여 +테이블 내 하나의 행으로의 액세스를 제공합니다. 또한 규칙에 의해 제공자는 +이 ID 값을 테이블의 _ID 열에 일치시켜야 하며, +일치한 행에 대하여 요청된 액세스 허가를 수행해야 합니다. +

+

+ 이 규칙은 제공자에 액세스하는 앱을 위한 공통 설계 패턴을 세우는 데 유용합니다. +앱이 제공자에 대한 쿼리를 수행하고 그 결과로 나온 {@link android.database.Cursor}를 +{@link android.widget.ListView}에 {@link android.widget.CursorAdapter}를 사용하여 표시합니다. + {@link android.widget.CursorAdapter}의 정의에 따르면 +{@link android.database.Cursor} 안의 열 중 하나는 _ID여야 합니다. +

+

+ 그러면 사용자가 데이터를 살펴보거나 수정하기 위하여 +UI에서 표시된 여러 행 중 하나를 선택합니다. 앱은 {@link android.widget.ListView}를 지원하는 {@link android.database.Cursor}에서 해당하는 열을 가져오고, +해당 열에 대한 _ID 값을 가져와서 +콘텐츠 URI에 추가하고, 제공자에 액세스 요청을 전송합니다. 그런 다음 제공자는 +사용자가 선택한 바로 그 행에 대해 쿼리 또는 수정 작업을 수행할 수 있습니다. +

+

콘텐츠 URI 패턴

+

+ 수신되는 콘텐츠 URI에 대해 어떤 조치를 취할지 선택하는 데 도움이 되도록 하기 위해 제공자 API에 +편의 클래스 {@link android.content.UriMatcher}가 +포함되어 있습니다. 이는 콘텐츠 URI "패턴"을 정수값으로 매핑합니다. 이 정수값은 특정 패턴에 일치하는 +콘텐츠 URI 또는 여러 URI에 대해 원하는 작업을 선택하는 데 switch 문에서 사용할 수 있습니다. +

+

+ 콘텐츠 URI 패턴은 와일드카드 문자를 사용하는 콘텐츠 URI와 일치합니다. +

+
    +
  • + *: 모든 길이의 모든 유효한 문자로 구성된 문자열과 일치합니다. +
  • +
  • + #: 모든 길이의 숫자 문자로 구성된 문자열과 일치합니다. +
  • +
+

+ 콘텐츠 URI 처리의 설계와 코딩에 대한 예시로서 임의의 제공자를 들어 보겠습니다. +이 제공자에는 권한 com.example.app.provider가 있고 +이 권한이 테이블을 가리키는 다음 콘텐츠 URI를 인식합니다. +

+
    +
  • + content://com.example.app.provider/table1: table1이라는 테이블입니다. +
  • +
  • + content://com.example.app.provider/table2/dataset1: +dataset1이라는 테이블입니다. +
  • +
  • + content://com.example.app.provider/table2/dataset2: +dataset2라는 테이블입니다. +
  • +
  • + content://com.example.app.provider/table3: table3이라는 테이블입니다. +
  • +
+

+ 제공자는 추가된 행 ID가 있으면 이런 콘텐츠 URI도 인식합니다. +예를 들어, table3에서 1이 식별한 행에 대한 +content://com.example.app.provider/table3/1이 이에 해당됩니다. +

+

+ 가능한 콘텐츠 URI 패턴은 다음과 같습니다. +

+
+
+ content://com.example.app.provider/* +
+
+ 제공자에 있는 모든 콘텐츠 URI와 일치합니다. +
+
+ content://com.example.app.provider/table2/*: +
+
+ 테이블 dataset1과 +dataset2의 콘텐츠 URI와 일치하지만 table1이나 +table3에 대한 콘텐츠 URI와 일치하지 않습니다. +
+
+ content://com.example.app.provider/table3/#: +table3의 단일 행에 대한 콘텐츠 URI와 일치합니다. 예를 들어, +6이 식별한 행에 대한 content://com.example.app.provider/table3/6이 이에 해당됩니다. + +
+
+

+ 다음 코드 조각은 {@link android.content.UriMatcher} 작업에서 메서드의 작용 원리를 나타낸 것입니다. + 이 코드는 테이블에 대한 콘텐츠 URI 패턴 content://<authority>/<path>와 +단일 행에 대한 콘텐츠 URI 패턴 content://<authority>/<path>/<id>를 사용하여 +단일 행에 대한 URI와 전체 테이블에 대한 URI를 서로 다르게 처리합니다. + +

+

+ {@link android.content.UriMatcher#addURI(String, String, int) addURI()} 메서드는 +권한과 경로를 정수값으로 매핑합니다. 메서드 {@link android.content.UriMatcher#match(Uri) +match()}는 URI에 대한 정수값을 반환합니다. switch 문이 +전체 테이블을 쿼리할 것인지, 하나의 레코드를 쿼리할 것인지 선택합니다. +

+
+public class ExampleProvider extends ContentProvider {
+...
+    // Creates a UriMatcher object.
+    private static final UriMatcher sUriMatcher;
+...
+    /*
+     * The calls to addURI() go here, for all of the content URI patterns that the provider
+     * should recognize. 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
+     */
+    sUriMatcher.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.
+     */
+    sUriMatcher.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 (sUriMatcher.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 is not recognized, you should do some error handling here.
+        }
+        // call the code to actually do the query
+    }
+
+

+ 또 다른 클래스, {@link android.content.ContentUris}가 +콘텐츠 URI의 id 부분을 다루기 위한 편의 메서드를 제공합니다. 클래스 {@link android.net.Uri}와 +{@link android.net.Uri.Builder}에는 +기존 {@link android.net.Uri} 개체를 구문 분석하고 새로운 개체를 구축하기 위한 편의 메서드가 포함되어 있습니다. +

+ + +

ContentProvider 클래스 구현

+

+ {@link android.content.ContentProvider} 인스턴스는 +다른 애플리케이션으로부터의 요청을 처리하여 구조화된 데이터 세트로의 액세스를 관리합니다. +모든 형태의 액세서가 궁극적으로 {@link android.content.ContentResolver}를 호출하며, +그러면 이것이 액세스 권한을 얻기 위해 구체적인 {@link android.content.ContentProvider} 메서드를 호출합니다. +

+

필수 메서드

+

+ 추상 클래스 {@link android.content.ContentProvider}는 +개발자가 나름의 구체적인 하위 클래스의 일부분으로 구현해야만 하는 여섯 가지 추상 메서드를 정의합니다. 이와 같은 메서드는 모두 +({@link android.content.ContentProvider#onCreate() onCreate()}는 예외) +콘텐츠 제공자에 액세스하려 시도 중인 클라이언트 애플리케이션이 호출합니다. +

+
+
+ {@link android.content.ContentProvider#query(Uri, String[], String, String[], String) +query()} +
+
+ 제공자에서 데이터를 검색합니다. 인수를 사용하여 쿼리할 테이블과 반환할 열/행, 결과의 정렬 순서를 선택합니다. + + 데이터를 {@link android.database.Cursor} 개체로 반환합니다. +
+
+ {@link android.content.ContentProvider#insert(Uri, ContentValues) insert()} +
+
+ 제공자에 새로운 행을 삽입합니다. 인수를 사용하여 대상 테이블을 선택하고 +사용할 열 값을 가져옵니다. +새로 삽입된 행에 대한 콘텐츠 URI를 반환합니다. +
+
+ {@link android.content.ContentProvider#update(Uri, ContentValues, String, String[]) +update()} +
+
+ 제공자 내의 기존 행을 업데이트합니다. 인수를 사용하여 +업데이트할 테이블과 행을 선택하고 업데이트한 열 값을 가져옵니다. 업데이트한 행 개수를 반환합니다. +
+
+ {@link android.content.ContentProvider#delete(Uri, String, String[]) delete()} +
+
+ 제공자에서 행을 삭제합니다. 인수를 사용하여 삭제할 테이블과 행을 선택합니다. + 삭제한 행 개수를 반환합니다. +
+
+ {@link android.content.ContentProvider#getType(Uri) getType()} +
+
+ 콘텐츠 URI에 상응하는 MIME 유형을 반환합니다. 이 메서드는 +콘텐츠 제공자 MIME 유형 섹션에 더 자세하게 설명되어 있습니다. +
+
+ {@link android.content.ContentProvider#onCreate() onCreate()} +
+
+ 제공자를 초기화합니다. Android 시스템은 제공자를 생성한 직후 +이 메서드를 호출합니다. +{@link android.content.ContentResolver} 개체가 제공자에 액세스하려고 시도할 때까지는 제공자가 생성된 것이 아니라는 점을 유의하십시오. +
+
+

+ 이와 같은 메서드에는 동일하게 이름 붙여진 +{@link android.content.ContentResolver} 메서드와 같은 서명이 있다는 것을 눈여겨 보십시오. +

+

+ 이러한 메서드의 구현에는 다음과 같은 내용을 감안해야 합니다. +

+
    +
  • + 이런 메서드는 모두({@link android.content.ContentProvider#onCreate() onCreate()}는 예외) + 한꺼번에 여러 스레드가 호출할 수 있으므로, 스레드로부터 안전해야 합니다. +다중 스레드에 대한 자세한 내용은 + +프로세스 및 스레드 주제를 참조하십시오. +
  • +
  • + {@link android.content.ContentProvider#onCreate() +onCreate()}에서는 긴 작업을 수행하는 것을 삼가는 것이 좋습니다. 실제로 필요할 때까지 초기화 작업을 미뤄두십시오. + 이 내용은 onCreate() 메서드 구현 +섹션에서 더욱 자세히 논의합니다. +
  • +
  • + 이와 같은 메서드는 반드시 구현해야 하는 것이지만, +예상되는 데이터 유형을 반환하는 것 외에 달리 코드가 해야 할 일은 없습니다. +예를 들어 몇몇 테이블에 다른 애플리케이션이 데이터를 삽입하지 못하도록 방지하려고 합니다. 이렇게 하려면, +{@link android.content.ContentProvider#insert(Uri, ContentValues) insert()}로의 +호출을 무시하고 0을 반환하면 됩니다. +
  • +
+

query() 메서드 구현

+

+ +{@link android.content.ContentProvider#query(Uri, String[], String, String[], String) +ContentProvider.query()} 메서드는 {@link android.database.Cursor} 개체를 반환해야 하고, 그렇지 못할 경우 +{@link java.lang.Exception}을 발생시킵니다. SQLite 데이터베이스를 데이터 저장소로 사용하는 경우, +{@link android.database.sqlite.SQLiteDatabase} 클래스의 query() 메서드 중 하나로 반환되는 {@link android.database.Cursor}를 +반환하기만 하면 됩니다. + 쿼리가 어느 행에도 일치하지 않으면, {@link android.database.Cursor#getCount()} 메서드가 0을 반환하는 +{@link android.database.Cursor} 인스턴스를 반환해야 합니다. + null을 반환하는 것은 쿼리 과정 중에 내부 오류가 발생했을 때뿐입니다. +

+

+ SQLite 데이터베이스를 데이터 저장소로 사용하지 않는 경우, {@link android.database.Cursor}의 + 구체적인 하위 클래스 중 하나를 사용하십시오. 예를 들어, {@link android.database.MatrixCursor} 클래스는 +각 행이 {@link java.lang.Object} 배열인 커서를 구현합니다. 이 클래스에서는 +{@link android.database.MatrixCursor#addRow(Object[]) addRow()}를 사용하여 새 행을 추가합니다. +

+

+ Android 시스템이 프로세스 경계를 가로질러 {@link java.lang.Exception}을 + 통신으로 전달할 수 있어야 한다는 점을 유의하십시오. Android가 이 작업을 할 수 있는 경우는 +쿼리 오류 처리에 유용할 수 있는 다음과 같은 예외에 해당될 때입니다. +

+
    +
  • + {@link java.lang.IllegalArgumentException} +(제공자가 유효하지 않은 콘텐츠 URI를 수신할 경우 이 예외를 발생시킬 수 있습니다.) +
  • +
  • + {@link java.lang.NullPointerException} +
  • +
+

insert() 메서드 구현

+

+ {@link android.content.ContentProvider#insert(Uri, ContentValues) insert()} 메서드는 +{@link android.content.ContentValues} 인수의 값을 이용하여 +적절한 테이블에 새 행을 추가합니다. 열 이름이 {@link android.content.ContentValues} 인수에 없는 경우, +제공자 코드 또는 데이터베이스 스키마 내에 그에 대한 기본값을 제공하는 것이 좋을 수도 있습니다. + +

+

+ 이 메서드가 새 행에 대한 콘텐츠 URI를 반환하는 것이 정상입니다. 이것을 구성하려면 새 행의 +_ID(또는 다른 기본 키) 값을 테이블의 콘텐츠 URI에 추가하며, 이때 +{@link android.content.ContentUris#withAppendedId(Uri, long) withAppendedId()}를 사용합니다. +

+

delete() 메서드 구현

+

+ {@link android.content.ContentProvider#delete(Uri, String, String[]) delete()} 메서드의 경우 + 데이터 저장소에서 물리적으로 행을 삭제하지 않아도 됩니다. +제공자와 동기화 어댑터를 함께 사용하고 있는 경우, +삭제된 행을 완전히 제거하기보다는 "삭제" 플래그로 표시하는 방법을 고려해볼 만합니다. +동기화 어댑터가 삭제된 행을 확인한 다음, 이를 제공자에서 삭제하기 전에 우선 서버에서 제거합니다. +

+

Update() 메서드 구현

+

+ {@link android.content.ContentProvider#update(Uri, ContentValues, String, String[]) +update()} 메서드는{@link android.content.ContentProvider#insert(Uri, ContentValues) insert()}가 사용하는 것과 같은 {@link android.content.ContentValues} 인수와 + +{@link android.content.ContentProvider#delete(Uri, String, String[]) delete()}와 {@link android.content.ContentProvider#query(Uri, String[], String, String[], String) +ContentProvider.query()}가 사용하는 것과 같은 selectionselectionArgs 인수를 +취합니다. + 이 때문에 이와 같은 메서드 사이에서 코드를 다시 사용할 수 있게 해주기도 합니다. +

+

onCreate() 메서드 구현

+

+ Android 시스템은 제공자를 시작할 때 {@link android.content.ContentProvider#onCreate() +onCreate()}를 호출합니다. 이 메서드에서는 빠르게 실행되는 초기화만 수행해야 하며, +데이터베이스 생성과 데이터 로딩은 제공자가 실제로 데이터에 대한 요청을 받을 때까지 미뤄두어야 합니다. + +{@link android.content.ContentProvider#onCreate() onCreate()}에서 긴 작업을 수행하면 +제공자의 시동 속도가 느려집니다. 이 때문에 제공자에서 다른 애플리케이션으로 전달되는 응답도 따라서 느려집니다. + +

+

+ 예를 들어, SQLite 데이터베이스를 사용하는 경우 +{@link android.content.ContentProvider#onCreate() ContentProvider.onCreate()}에서 +새로운 {@link android.database.sqlite.SQLiteOpenHelper} 개체를 생성하고, +그런 다음 데이터베이스를 처음 열 때 SQL 테이블을 생성할 수 있습니다. 이를 용이하게 하기 위해 +{@link android.database.sqlite.SQLiteOpenHelper#getWritableDatabase +getWritableDatabase()}를 처음 호출하면 이것이 자동으로 +{@link android.database.sqlite.SQLiteOpenHelper#onCreate(SQLiteDatabase) +SQLiteOpenHelper.onCreate()} 메서드를 호출합니다. +

+

+ 다음 두 개의 조각은 +{@link android.content.ContentProvider#onCreate() ContentProvider.onCreate()}와 +{@link android.database.sqlite.SQLiteOpenHelper#onCreate(SQLiteDatabase) +SQLiteOpenHelper.onCreate()} 사이의 상호 작용을 나타낸 것입니다. 첫 번째 조각은 +{@link android.content.ContentProvider#onCreate() ContentProvider.onCreate()}의 구현입니다. +

+
+public class ExampleProvider extends ContentProvider
+
+    /*
+     * Defines a handle to the database helper object. The MainDatabaseHelper class is defined
+     * in a following snippet.
+     */
+    private MainDatabaseHelper mOpenHelper;
+
+    // Defines the database name
+    private static final String DBNAME = "mydb";
+
+    // Holds the database object
+    private SQLiteDatabase db;
+
+    public boolean onCreate() {
+
+        /*
+         * Creates a new helper object. This method always returns quickly.
+         * Notice that the database itself isn't created or opened
+         * until SQLiteOpenHelper.getWritableDatabase is called
+         */
+        mOpenHelper = new MainDatabaseHelper(
+            getContext(),        // the application context
+            DBNAME,              // the name of the database)
+            null,                // uses the default SQLite cursor
+            1                    // the version number
+        );
+
+        return true;
+    }
+
+    ...
+
+    // Implements the provider's insert method
+    public Cursor insert(Uri uri, ContentValues values) {
+        // Insert code here to determine which table to open, handle error-checking, and so forth
+
+        ...
+
+        /*
+         * Gets a writeable database. This will trigger its creation if it doesn't already exist.
+         *
+         */
+        db = mOpenHelper.getWritableDatabase();
+    }
+}
+
+

+ 그 다음 조각은 +도우미 클래스를 포함한 {@link android.database.sqlite.SQLiteOpenHelper#onCreate(SQLiteDatabase) +SQLiteOpenHelper.onCreate()}의 구현입니다. +

+
+...
+// A string that defines the SQL statement for creating a table
+private static final String SQL_CREATE_MAIN = "CREATE TABLE " +
+    "main " +                       // Table's name
+    "(" +                           // The columns in the table
+    " _ID INTEGER PRIMARY KEY, " +
+    " WORD TEXT"
+    " FREQUENCY INTEGER " +
+    " LOCALE TEXT )";
+...
+/**
+ * Helper class that actually creates and manages the provider's underlying data repository.
+ */
+protected static final class MainDatabaseHelper extends SQLiteOpenHelper {
+
+    /*
+     * Instantiates an open helper for the provider's SQLite data repository
+     * Do not do database creation and upgrade here.
+     */
+    MainDatabaseHelper(Context context) {
+        super(context, DBNAME, null, 1);
+    }
+
+    /*
+     * Creates the data repository. This is called when the provider attempts to open the
+     * repository and SQLite reports that it doesn't exist.
+     */
+    public void onCreate(SQLiteDatabase db) {
+
+        // Creates the main table
+        db.execSQL(SQL_CREATE_MAIN);
+    }
+}
+
+ + + +

ContentProvider MIME 유형 구현

+

+ {@link android.content.ContentProvider} 클래스에는 MIME 유형을 반환하는 두 가지 메서드가 있습니다. +

+
+
+ {@link android.content.ContentProvider#getType(Uri) getType()} +
+
+ 모든 제공자에 대해 구현해야 하는 필수 메서드 중 하나입니다. +
+
+ {@link android.content.ContentProvider#getStreamTypes(Uri, String) getStreamTypes()} +
+
+ 제공자가 파일을 제공하는 경우 구현해야 하는 메서드입니다. +
+
+

테이블의 MIME 유형

+

+ {@link android.content.ContentProvider#getType(Uri) getType()} 메서드는 +콘텐츠 URI 인수가 반환하는 데이터 유형을 설명하는 MIME 형식의 {@link java.lang.String}을 반환합니다. + {@link android.net.Uri} 인수는 특정 URI라기보다 패턴일 수 있습니다. +이 경우, 이 패턴과 일치하는 콘텐츠 URI와 연관된 유형의 데이터를 반환해야 합니다. + +

+

+ 텍스트, HTML 또는 JPEG와 같은 보편적인 유형의 데이터라면 +{@link android.content.ContentProvider#getType(Uri) getType()}이 +해당 데이터에 대한 표준 MIME 유형을 반환하는 것이 정상입니다. 이러한 표준 유형의 전체 목록은 +IANA MIME 미디어 유형 +웹사이트에서 확인할 수 있습니다. +

+

+ 테이블 데이터의 행 하나 또는 여러 행을 가리키는 콘텐츠 URI의 경우, +{@link android.content.ContentProvider#getType(Uri) getType()}이 Android의 공급업체별 MIME 형식에서 +MIME 형식을 반환해야 합니다. +

+
    +
  • + 유형 부분: vnd +
  • +
  • + 하위 유형 부분: +
      +
    • + URI 패턴이 하나의 행에 대한 것일 경우: android.cursor.item/ +
    • +
    • + URI 패턴이 하나 이상의 행에 대한 것일 경우: android.cursor.dir/ +
    • +
    +
  • +
  • + 제공자별 부분: vnd.<name>.<type> +

    + 개발자가 <name><type>을 제공합니다. + <name> 값은 전체적으로 고유해야 하고, +<type> 값은 상응하는 URI 패턴에 고유해야 +합니다. <name>으로 좋은 예는 회사 이름이나 +애플리케이션의 Android 패키지 이름을 들 수 있습니다. +<type>으로 좋은 예는 URI와 연관된 테이블을 식별하는 +문자열을 들 수 있습니다. +

    + +
  • +
+

+ 예를 들어 어떤 제공자의 권한이 +com.example.app.provider이고, 이것이 +table1이라는 테이블을 노출하는 경우, table1의 여러 행에 대한 MIME 유형은 다음과 같습니다. +

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

+ table1의 행 하나의 경우, MIME 유형은 다음과 같습니다. +

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

파일의 MIME 유형

+

+ 제공자가 파일을 제공하는 경우, +{@link android.content.ContentProvider#getStreamTypes(Uri, String) getStreamTypes()}를 구현합니다. + 이 메서드는 제공자가 주어진 콘텐츠 URI에 대해 반환할 수 있는 파일에 대한 MIME 유형의 {@link java.lang.String} 배열을 반환합니다. +제공하는 MIME 유형을 MIME 유형 필터 인수 기준으로 필터링해야 +클라이언트가 처리하고자 하는 MIME 유형만 반환할 수 있습니다. +

+

+ 예를 들어, 사진 이미지를 .jpg, +.png.gif 형식의 파일로 제공하는 제공자가 있다고 하겠습니다. + 애플리케이션이 필터 문자열 image/*("이미지"인 어떤 것)로 {@link android.content.ContentResolver#getStreamTypes(Uri, String) +ContentResolver.getStreamTypes()}를 호출하면 + +{@link android.content.ContentProvider#getStreamTypes(Uri, String) +ContentProvider.getStreamTypes()} 메서드가 다음과 같은 배열을 반환하는 것이 정상입니다. +

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

+ 앱이 .jpg 파일에만 관심이 있는 경우에는 +필터 문자열 *\/jpeg으로 {@link android.content.ContentResolver#getStreamTypes(Uri, String) + ContentResolver.getStreamTypes()}를 호출할 수 있습니다. 그러면 + {@link android.content.ContentProvider#getStreamTypes(Uri, String) + ContentProvider.getStreamTypes()}가 다음과 같이 반환하는 것이 정상입니다. +

+{"image/jpeg"}
+
+

+ 제공자가 필터 문자열에서 요청한 MIME 유형 중 제공하는 것이 없는 경우, +{@link android.content.ContentProvider#getStreamTypes(Uri, String) getStreamTypes()}가 + null을 반환하는 것이 정상입니다. +

+ + + +

계약 클래스 구현

+

+ 계약 클래스는 public final 클래스로, 이 안에 URI, 열 이름, MIME 유형의 +상수 정의 및 제공자에 관련된 다른 메타 데이터가 들어 있습니다. +이 클래스는 URI, 열 이름 등의 실제 값에 변경된 내용이 있더라도 + 제공자에 올바르게 액세스할 수 있도록 보장하여 제공자와 +다른 애플리케이션 사이의 계약을 확립합니다. +

+

+ 계약 클래스가 개발자에게 유용한 이유는 또 있습니다. 이 클래스는 보통 자신의 상수 이름으로 +니모닉 이름을 가지기 때문에 개발자가 열 이름 또는 URI에 잘못된 값을 사용할 가능성이 덜합니다. +이것도 클래스의 일종이기 때문에 Javadoc 문서를 포함할 수 있습니다. +Eclipse와 같은 통합 개발 환경은 계약 클래스의 상수 이름을 자동 완성하고 +해당 상수에 대한 Javadoc을 표시할 수 있습니다. +

+

+ 개발자가 애플리케이션에서 계약 클래스의 클래스 파일에 액세스할 수는 없지만 +여러분이 제공하는 .jar 파일에서 이를 애플리케이션 안으로 정적으로 컴파일링할 수 있습니다. +

+

+ {@link android.provider.ContactsContract} 클래스와 +이에 중첩된 클래스가 계약 클래스의 예시입니다. +

+

콘텐츠 제공자 권한 구현

+

+ Android 시스템의 모든 측면에 대한 권한과 액세스는 +보안 및 권한 주제에 설명되어 있습니다. + 데이터 저장소 주제에서도 +다양한 유형의 저장소에 적용되는 보안 및 권한을 설명하고 있습니다. + 간략히 말해 요점은 다음과 같습니다. +

+
    +
  • + 기본적으로, 기기의 내부 저장소에 저장된 데이터 파일은 +본인의 애플리케이션과 제공자 전용입니다. +
  • +
  • + 본인이 생성한 {@link android.database.sqlite.SQLiteDatabase} 데이터베이스는 +본인의 애플리케이션과 제공자만의 비공개 데이터입니다. +
  • +
  • + 기본적으로 외부 저장소에 저장하는 데이터 파일은 공개이고 +누구나 읽을 수 있습니다. 외부 저장소에 있는 파일로의 액세스를 제공하는 데 콘텐츠 제공자를 쓸 수는 +없습니다. 다른 애플리케이션이 다른 API 호출을 사용하여 해당 파일을 읽고 쓸 수 있기 때문입니다. +
  • +
  • + 기기의 내부 저장소에 있는 파일 또는 SQLite 데이터베이스를 열거나 생성하기 위한 메서드 호출은 +다른 모든 애플리케이션에 읽기 및 쓰기 액세스 권한을 허가할 가능성이 있습니다. +내부 파일이나 데이터베이스를 제공자의 리포지토리로 사용하고 +"누구나 읽을 수 있는" 또는 "누구나 쓸 수 있는" 액세스를 부여하면 +매니페스트에서 제공자에 대해 설정한 권한이 데이터를 보호하지 못합니다. +내부 저장소 안에 있는 파일과 데이터베이스에 대한기본 액세스는 "비공개"이며, 제공자의 리포지토리가 이것을 변경하면 안 됩니다. +
  • +
+

+ 데이터로의 액세스를 제어하기 위해 콘텐츠 제공자 권한을 쓰고자 하는 경우, +데이터를 내부 파일, SQLite 데이터베이스 또는 "클라우드"(예: 원격 서버) 안의 +내부 파일로 저장해야 하고, 파일과 데이터베이스를 애플리케이션만의 비공개로 유지해야 합니다. +

+

권한 구현

+

+ 기본 데이터가 비공개라고 하더라도 모든 애플리케이션이 제공자를 읽고 제공자에 쓸 수 있습니다. +기본적으로 제공자에는 권한이 설정되어 있지 않기 때문입니다. 이를 변경하려면, + + <provider> 요소의 속성이나 하위 요소를 사용하여 +매니페스트 파일에 있는 제공자의 권한을 설정합니다. 권한은 제공자 전체에 적용되도록 설정할 수도 있고, +특정 테이블에, 또는 심지어 특정 레코드에 적용되게 할 수도 있고 세 가지 모두를 택할 수도 있습니다. +

+

+ 제공자에 대한 권한은 매니페스트 파일에 있는 하나 이상의 + + <permission> 요소로 정의합니다. +제공자에 고유한 권한을 설정하려면 + + android:name 속성에 Java 스타일 범위를 사용합니다. 예를 들어 읽기 권한의 이름을 +com.example.app.provider.permission.READ_PROVIDER로 지정합니다. + +

+

+ 다음 목록은 제공자 권한의 범위를 설명한 것입니다. +제공자 전체에 적용되는 권한부터 시작하여 점차 세분화된 권한이 됩니다. + 보다 세부화된 권한이 범위가 큰 것보다 우선합니다. +

+
+
+ 단일 읽기-쓰기 제공자 수준 권한 +
+
+ 제공자 전체로의 읽기와 쓰기 액세스 양쪽 모두를 제어하는 하나의 권한으로, + + <provider> 요소의 + + android:permission 속성으로 지정됩니다. +
+
+ 별도의 읽기 및 쓰기 제공자 수준 권한 +
+
+ 제공자 전체에 대한 읽기 권한과 쓰기 권한입니다. 이들 권한은 + + <provider> 요소의 + android:readPermission와 + + android:writePermission 속성으로 +지정합니다. 이들이 + + android:permission에서 요구하는 권한보다 우선합니다. +
+
+ 경로 수준 권한 +
+
+ 제공자의 콘텐츠 URI에 대한 읽기, 쓰기 또는 읽기/쓰기 권한입니다. 제어하고자 하는 각 URI를 직접 지정하되, +이때 + + <provider> 요소의 + + <path-permission> 하위 요소를 사용합니다. 지정하는 콘텐츠 URI마다 +읽기/쓰기 권한, 읽기 권한 또는 쓰기 권한을 하나씩 지정하거나 셋 모두를 지정할 수 있습니다. +읽기 및 쓰기 권한이 읽기/쓰기 권한보다 우선합니다. +또한, 경로 수준 권한이 제공자 수준 권한보다 우선합니다. +
+
+ 임시 권한 +
+
+ 애플리케이션에 임시 액세스를 허용하는 권한 수준입니다. +해당 애플리케이션에 일반적으로 요구되는 권한이 없더라도 무관합니다. +임시 액세스 기능은 매니페스트에서 요청해야 하는 +권한과 애플리케이션 개수를 줄여줍니다. 임시 권한을 사용하는 경우, +제공자에 대하여 "영구" 권한을 필요로하는 애플리케이션은 +모든 데이터에 지속적으로 액세스하는 것들뿐입니다. +

+ 이메일 제공자와 앱을 구현할 때 필요한 권한을 예로 들어 보겠습니다. +외부 이미지 뷰어 애플리케이션으로 하여금 제공자에서 보낸 사진 첨부 파일을 +표시하도록 허용하고자 한다고 가정합니다. 권한을 요구하지 않고 이미지 뷰어에 필수 액세스를 부여하려면, +사진에 대한 콘텐츠 URI에 해단되는 임시 권한을 설정하십시오. +사용자가 사진을 표시하기를 원할 때 앱이 사진의 콘텐츠 URI와 권한 플래그를 포함하는 인텐트를 +이미지 뷰어에 보내도록 이메일 앱을 설계합니다. 그러면 해당 이미지 뷰어가 +이메일 제공자에 사진 검색을 쿼리할 수 있으며, 이 뷰어에 제공자에 대한 정상적인 읽기 권한이 없더라도 무방합니다. + +

+

+ 임시 권한을 사용하려면, + + <provider> 요소의 + android:grantUriPermissions 속성을 설정하거나 +하나 이상의 + + <grant-uri-permission> 하위 요소를 + + <provider> 요소에 추가하면 됩니다. 임시 권한을 사용하는 경우, +제공자에서 콘텐츠 URI에 대한 지원을 제거할 때마다 {@link android.content.Context#revokeUriPermission(Uri, int) + Context.revokeUriPermission()}을 호출해야 합니다. +그러면 콘텐츠 URI가 임시 권한과 연관됩니다. +

+

+ 속성의 값에 따라 제공자에 액세스 가능한 정도가 결정됩니다. + 속성이 true로 설정되어 있는 경우라면 +시스템이 제공자 전체에 임시 권한을 허용하며, 제공자 수준 또는 +경로 수준 권한에서 요구하는 다른 모든 권한을 재정의합니다. +

+

+ 이 플래그가 false로 설정되면, 반드시 + + <grant-uri-permission> 하위 요소를 + + <provider> 요소에 추가해야 합니다. 각 하위 요소는 임시 권한을 허용한 +콘텐츠 URI(하나 또는 여러 개)를 나타냅니다. +

+

+ 애플리케이션에 임시 액세스를 위임하려면, 인텐트에 +{@link android.content.Intent#FLAG_GRANT_READ_URI_PERMISSION} 또는 +{@link android.content.Intent#FLAG_GRANT_WRITE_URI_PERMISSION} 플래그, 또는 둘 모두가 들어 있어야 합니다. 이들은 +{@link android.content.Intent#setFlags(int) setFlags()} 메서드로 설정됩니다. +

+

+ + android:grantUriPermissions 속성이 존재하지 않으면 +false인 것으로 가정합니다. +

+
+
+ + + + +

<provider> 요소

+

+ {@link android.app.Activity}와 {@link android.app.Service} 구성 요소와 마찬가지로, +{@link android.content.ContentProvider}의 하위 클래스는 + + <provider> 요소를 사용하여 매니페스트 파일에서 +애플리케이션에 대해 정의되어야 합니다. Android 시스템이 +해당 요소에서 다음과 같은 정보를 가져옵니다. +

+
+ 권한 +({@code +android:authorities}) +
+
+ 시스템 내에서 제공자 전체를 식별하는 상징적 이름입니다. 이 속성은 + +콘텐츠 URI 설계 섹션에서 자세히 설명되어 있습니다. +
+
+ 제공자 클래스 이름 +( +android:name + ) +
+
+ {@link android.content.ContentProvider}를 구현하는 클래스입니다. 이 클래스는 + +ContentProvider 클래스 구현 섹션에 자세히 설명되어 있습니다. +
+
+ 권한 +
+
+ 제공자의 데이터에 액세스하기 위해 다른 애플리케이션이 +반드시 가지고 있어야 하는 권한을 나타내는 속성입니다. + +

+ 각종 권한과 그에 상응하는 속성은 + +콘텐츠 제공자 권한 구현 섹션에 자세히 설명되어 있습니다. +

+
+
+ 시작 및 제어 속성 +
+
+ 이와 같은 속성은 Android 시스템이 제공자를 시작하는 방법과 시점, +제공자의 프로세스 특징과 기타 런타임 설정 등을 결정합니다. +
    +
  • + + android:enabled: 시스템이 제공자를 시작할 수 있게 해주는 플래그입니다. +
  • +
  • + + android:exported: 다른 애플리케이션이 이 제공자를 사용할 수 있게 해주는 플래그입니다. +
  • +
  • + + android:initOrder: 같은 프로세스 내의 다른 제공자와 비교하여 +이 제공자가 시작되어야 하는 순서입니다. +
  • +
  • + + android:multiProcess: 클라이언트를 호출하는 것과 +같은 프로세스에서 시스템이 제공자를 시작할 수 있게 해주는 플래그입니다. +
  • +
  • + + android:process: 제공자가 실행해야 하는 프로세스의 +이름입니다. +
  • +
  • + + android:syncable: 제공자의 데이터가 +서버에 있는 데이터와 동기화될 예정임을 나타내는 플래그입니다. +
  • +
+

+ 이 속성은 + + <provider> +요소에 대한 개발자 가이드 주제에 상세하게 기록되어 있습니다. +

+
+
+ 정보 속성 +
+
+ 제공자에 대한 선택 항목 아이콘 및 레이블입니다. +
    +
  • + + android:icon: 제공자의 아이콘이 들어 있는 드로어블 리소스입니다. + 이 아이콘은 +설정 > > 모두에 있는 앱 목록에서 제공자의 레이블 옆에 표시됩니다. +
  • +
  • + + android:label: 제공자 또는 그 데이터, 또는 둘 모두를 설명하는 정보 레이블입니다. + 이 레이블은 +설정 > > 모두에 있는 앱 목록에 표시됩니다. +
  • +
+

+ 이 속성은 + + <provider>요소에 대한 개발자 가이드 주제에 상세하게 기록되어 있습니다. +

+
+
+ + +

인텐트 및 데이터 액세스

+

+ 애플리케이션이 콘텐츠 제공자에 간접적으로 액세스하려면 {@link android.content.Intent}를 사용하면 됩니다. + 이 애플리케이션은 {@link android.content.ContentResolver} 또는 +{@link android.content.ContentProvider}의 메서드 중 어느 하나도 호출하지 않습니다. +대신, 액티비티를 시작하는 인텐트를 전송합니다. 이 인텐트는 제공자가 소유한 애플리케이션의 일부인 경우가 많습니다. +대상 액티비티가 데이터를 자체 UI에서 검색하고 표시하는 역할을 맡습니다. +인텐트의 동작에 따라 대상 액티비티가 사용자에게 프롬프트를 표시하여 제공자의 데이터를 수정하도록 할 수도 있습니다. + 인텐트에는 대상 액티비티가 UI에 표시하는 "추가" 데이터가 들어 있을 수도 있습니다. +그러면 사용자에게 이 데이터를 변경할 수 있는 옵션이 주어지고, 그런 다음 이를 사용하여 +제공자 내의 데이터를 수정할 수 있습니다. +

+

+ +

+

+ 데이터 무결성을 보장하는 데 유용한 것을 원하면 인텐트 액세스를 사용하는 것이 좋습니다. +엄격하게 정의된 비즈니스 논리에 따라 데이터가 삽입, 업데이트되고 삭제되는 것이 제공자를 크게 좌우할 수도 있습니다. +이런 경우에 해당되면, 다른 애플리케이션에 데이터를 직접 수정하도록 허용하면 데이터가 잘못되는 +결과를 초래할 수 있습니다. 개발자들에게 인텐트 액세스 사용을 허용하려면, 그 내용을 철저히 기록해두어야 합니다. + 개발자들에게 자기 애플리케이션의 UI를 사용한 인텐트 액세스가 +코드로 데이터를 수정하려 시도하는 것보다 나은 이유를 설명해주십시오. +

+

+ 제공자의 데이터를 수정하고자 하는 수신되는 인텐트 처리도 다른 인텐트 처리와 다를 바가 없습니다. + 인텐트 사용에 대한 자세한 내용은 +인텐트 및 인텐트 필터 주제를 읽으면 확인할 수 있습니다. +

diff --git a/docs/html-intl/intl/ko/guide/topics/providers/content-providers.jd b/docs/html-intl/intl/ko/guide/topics/providers/content-providers.jd new file mode 100644 index 0000000000000000000000000000000000000000..ce98840e16db4c15449dfdaf84fe94bcc9282f7e --- /dev/null +++ b/docs/html-intl/intl/ko/guide/topics/providers/content-providers.jd @@ -0,0 +1,108 @@ +page.title=콘텐츠 제공자 +@jd:body + +

+ 콘텐츠 제공자는 구조화된 데이터 세트로의 액세스를 관리합니다. +데이터를 캡슐화하여 데이터 보안을 정의하는 데 필요한 메커니즘을 제공하기도 합니다. +콘텐츠 제공자는 한 프로세스의 데이터에 다른 프로세스에서 실행 중인 코드를 연결하는 표준 인터페이스입니다. +

+

+ 콘텐츠 제공자 내의 데이터에 액세스하고자 하는 경우, +애플리케이션의 {@link android.content.Context}에 있는 +{@link android.content.ContentResolver} 개체를 사용하여 클라이언트로서 제공자와 통신을 주고받으면 됩니다. + {@link android.content.ContentResolver} 개체가 제공자 개체와 통신하며, 이 개체는 +{@link android.content.ContentProvider}를 구현하는 클래스의 인스턴스입니다. +제공자 개체가 클라이언트로부터 데이터 요청을 받아 요청된 작업을 수행하며 결과를 반환합니다. + +

+

+ 데이터를 다른 애플리케이션과 공유할 생각이 없으면 나름의 제공자를 개발하지 않아도 됩니다. + 그러나, 자체 애플리케이션에서 사용자 지정 검색 제안을 제공하려면 나름의 제공자가 꼭 필요합니다. + 또한, 복잡한 데이터나 파일을 자신의 애플리케이션에서 다른 애플리케이션으로 복사하여 붙여넣고자 하는 경우에도 +나름의 제공자가 필요합니다. +

+

+ Android 자체에 오디오, 동영상, 이미지 및 개인 연락처 정보 등의 데이터를 관리하는 콘텐츠 제공자가 +포함되어 있습니다. 그중 몇 가지를 목록으로 나열한 것을 + +android.provider + 패키지에 대한 참조 문서에서 확인할 수 있습니다. 이와 같은 제공자는 몇 가지 제약이 있지만, +어느 Android 애플리케이션에나 액세스할 수 있습니다. +

+ 다음 주제에서는 콘텐츠 제공자에 대해 좀 더 자세히 설명합니다. +

+
+
+ + 콘텐츠 제공자 기본 정보 +
+
+ 데이터가 여러 개의 표로 정리되어 있을 때 콘텐츠 제공자 내의 데이터에 액세스하는 방법입니다. +
+
+ + 콘텐츠 제공자 생성 +
+
+ 나름의 콘텐츠 제공자를 직접 만드는 방법입니다. +
+
+ + 캘린더 제공자 +
+
+ Android 플랫폼의 일부인 캘린더 제공자에 액세스하는 방법입니다. +
+
+ + 연락처 제공자 +
+
+ Android 플랫폼의 일부인 연락처 제공자에 액세스하는 방법입니다. +
+
diff --git a/docs/html-intl/intl/ko/guide/topics/providers/document-provider.jd b/docs/html-intl/intl/ko/guide/topics/providers/document-provider.jd new file mode 100644 index 0000000000000000000000000000000000000000..e356e22d17c9b76358bd8271a00e09df5b61c70d --- /dev/null +++ b/docs/html-intl/intl/ko/guide/topics/providers/document-provider.jd @@ -0,0 +1,916 @@ +page.title=저장소 액세스 프레임워크 +@jd:body + + + +

Android 4.4(API 레벨 19)에서는 저장소 액세스 프레임워크(SAF)를 처음 도입하게 되었습니다. SAF는 +사용자가 선호하는 문서 저장소 제공자 전체를 걸쳐 문서, 이미지 및 각종 다른 파일을 +탐색하고 여는 작업을 간편하게 만들어줍니다. 표준형의, 사용하기 쉬운 UI로 +사용자가 각종 앱과 제공자에 걸쳐 일관된 방식으로 파일을 탐색하고 최근 내용에 액세스할 수 있게 해줍니다.

+ +

클라우드 또는 로컬 저장소 서비스가 이 에코시스템에 참가하려면 자신의 서비스를 캡슐화하는 +{@link android.provider.DocumentsProvider}를 구현하면 됩니다. +제공자의 문서에 액세스해야 하는 클라이언트 앱의 경우 단 몇 줄의 코드만으로 +SAF와 통합할 수 있습니다.

+ +

SAF에는 다음과 같은 항목이 포함됩니다.

+ +
    +
  • 문서 제공자—일종의 콘텐츠 제공자로 +저장소 서비스(예: Google Drive 등)로 하여금 자신이 관리하는 파일을 드러내도록 허용합니다. 문서 제공자는 +{@link android.provider.DocumentsProvider} 클래스의 하위 클래스로 구현됩니다. +문서 제공자 스키마는 기존의 파일 계층을 근거로 하지만, +문서 제공자가 데이터를 저장하는 물리적인 방법은 개발자가 선택하기 나름입니다. +Android 플랫폼에는 내장된 문서 제공자가 여러 개 있습니다. +예를 들어 다운로드, 이미지 및 비디오 등입니다.
  • + +
  • 클라이언트 앱—일종의 사용자 지정 앱으로 +{@link android.content.Intent#ACTION_OPEN_DOCUMENT} 및/또는 +{@link android.content.Intent#ACTION_CREATE_DOCUMENT} 인텐트를 호출하고, +문서 제공자가 반환하는 파일을 수신합니다.
  • + +
  • 선택기—일종의 시스템 UI로 사용자가 클라이언트 앱의 +검색 기준을 만족하는 모든 문서 제공자에서 문서에 액세스할 수 있도록 해줍니다.
  • +
+ +

SAF가 제공하는 기능을 몇 가지 예로 들면 다음과 같습니다.

+
    +
  • 사용자들로 하여금 하나의 앱만이 아니라 모든 문서 제공자에서 콘텐츠를 탐색할 수 있게 해줍니다.
  • +
  • 여러분의 앱이 문서 제공자가 소유한 문서에 대한 장기적, 영구적 액세스 권한을 가질 수 있도록 +해줍니다. 이 액세스 권한을 통해 사용자가 제공자에 있는 파일을 추가, 편집, +저장 및 삭제할 수 있습니다.
  • +
  • 여러 개의 사용자 계정을 지원하며 USB 저장소 제공자와 같은 임시 루트도 지원합니다. +이는 드라이브가 연결되어 있을 때만 나타납니다.
  • +
+ +

개요

+ +

SAF는 {@link android.provider.DocumentsProvider} 클래스의 +하위 클래스인 콘텐츠 제공자를 중심으로 둘러싸고 있습니다. 데이터는 문서 제공자 내에서 일반적인 파일 계층으로 +구조화됩니다.

+

data model

+

그림 1. 문서 제공자 데이터 모델입니다. 루트 하나가 하나의 문서를 가리키며, +이는 다시 트리 전체의 팬아웃을 시작합니다.

+ +

다음 내용을 참고하십시오.

+
    + +
  • 각 문서 제공자는 하나 이상의 "루트"를 보고합니다. +이는 문서 트리 속을 탐색할 시작 지점입니다. +각 루트에는 고유한 {@link android.provider.DocumentsContract.Root#COLUMN_ROOT_ID}가 있으며, +이는 해당 루트 아래의 콘텐츠를 나타내는 문서(디렉터리)를 +가리킵니다. +루트는 설계상 동적으로 만들어져 있어 여러 개의 계정, 임시 USB 저장소 기기 +또는 사용자 로그인/로그아웃 등과 같은 경우를 지원하도록 되어 있습니다.
  • + +
  • 각 루트 아래에 문서가 하나씩 있습니다. 해당 문서는 1부터 N까지의 문서를 가리키는데, +이는 각각 1부터 N의 문서를 가리킬 수 있습니다.
  • + +
  • 각 저장소의 백엔드가 +개별적인 파일과 디렉터리를 고유한 +{@link android.provider.DocumentsContract.Document#COLUMN_DOCUMENT_ID}로 +참조하여 드러냅니다.문서 ID는 고유해야 하며 한 번 발행되고 나면 변경되지 않습니다. +이들은 기기 재부팅을 통괄하여 영구적인 URI 허가에 사용되기 때문입니다.
  • + + +
  • 문서는 열 수 있는 파일이거나(특정 MIME 유형으로), +추가 문서가 들어있는 디렉터리일 수 있습니다( +{@link android.provider.DocumentsContract.Document#MIME_TYPE_DIR} MIME 유형으로).
  • + +
  • 각 문서는 서로 다른 기능을 가지고 있을 수 있습니다. 이는 +{@link android.provider.DocumentsContract.Document#COLUMN_FLAGS COLUMN_FLAGS}에서 설명한 것과 같습니다. + 예를 들어 {@link android.provider.DocumentsContract.Document#FLAG_SUPPORTS_WRITE}, +{@link android.provider.DocumentsContract.Document#FLAG_SUPPORTS_DELETE} 및 +{@link android.provider.DocumentsContract.Document#FLAG_SUPPORTS_THUMBNAIL} 등입니다. +같은 {@link android.provider.DocumentsContract.Document#COLUMN_DOCUMENT_ID}가 +여러 디렉터리에 포함되어 있을 수도 있습니다.
  • +
+ +

제어 흐름

+

위에서 언급한 바와 같이, 문서 제공자 데이터 모델은 일반적인 +파일 계층을 기반으로 합니다. 그러나, 데이터를 물리적으로 저장하는 방식은 마음대로 선택할 수 있습니다. 다만 +{@link android.provider.DocumentsProvider} API를 통해 액세스할 수 있기만 하면 됩니다. +예를 들어, 데이터를 저장하기 위해 태그 기반 클라우드 저장소를 사용해도 됩니다.

+ +

그림 2는 사진 앱이 SAF를 사용하여 저장된 데이터에 액세스할 수 있는 방법을 +예시로 나타낸 것입니다.

+

app

+ +

그림 2. 저장소 액세스 프레임워크 흐름

+ +

다음 내용을 참고하십시오.

+
    + +
  • SAF에서는 제공자와 클라이언트가 직접 상호 작용하지 않습니다. + 클라이언트가 파일과 상호 작용하기 위한 권한을 요청합니다(다시 말해, +파일을 읽고, 편집하고 생성 또는 삭제할 권한을 말합니다).
  • + +
  • 상호 작용은 애플리케이션(이 예시에서는 주어진 사진 앱)이 인텐트 +{@link android.content.Intent#ACTION_OPEN_DOCUMENT} 또는 {@link android.content.Intent#ACTION_CREATE_DOCUMENT}를 실행시키면 시작합니다. 이 인텐트에는 +기준을 한층 더 정밀하게 하기 위한 필터가 포함될 수 있습니다. 예를 들어, "열 수 있는 파일 중에서 +'이미지' MIME 유형을 가진 파일을 모두 주세요"라고 할 수 있습니다.
  • + +
  • 인텐트가 실행되면 시스템 선택기가 각각의 등록된 제공자로 이동하여 사용자에게 +일치하는 콘텐츠 루트를 보여줍니다.
  • + +
  • 선택기는 사용자에게 문서에 액세스하는 데 쓰는 표준 인터페이스를 부여합니다. 이는 +기본 문서 제공자 사이에 큰 차이가 있더라도 무관합니다. 예를 들어, 그림 2는 +Google Drive 제공자, USB 제공자와 클라우드 제공자를 나타낸 것입니다.
  • +
+ +

그림 3은 이미지를 검색 중인 사용자가 Google Drive 계정을 선택한 +선택기를 나타낸 것입니다.

+ +

picker

+ +

그림 3. 선택기

+ +

사용자가 Google Drive를 선택하면 이미지가 그림 4에 나타난 것처럼 +표시됩니다. 그때부터 사용자는 제공자와 클라이언트 앱이 지원하는 방식이라면 어떤 식으로든 +이들 이미지와 상호 작용할 수 있게 됩니다. + +

picker

+ +

그림 4. 이미지

+ +

클라이언트 앱 작성

+ +

Android 4.3 이하에서는 앱이 또 다른 앱에서 파일을 검색할 수 있도록 하려면 + {@link android.content.Intent#ACTION_PICK} + 또는 {@link android.content.Intent#ACTION_GET_CONTENT}와 같은 인텐트를 호출해야만 했습니다. 그런 다음 +파일을 선택할 앱을 하나 선택하고, 선택한 앱이 사용자 인터페이스를 제공하여야 사용자가 +이용 가능한 파일 중에서 탐색하고 선택할 수 있었습니다.

+ +

Android 4.4 이상에는 +{@link android.content.Intent#ACTION_OPEN_DOCUMENT} 인텐트를 사용할 수 있다는 추가 옵션이 있습니다. +이는 시스템이 제어하는 선택기를 표시하여 사용자가 다른 앱에서 이용할 수 있게 만든 파일을 +모두 탐색할 수 있게 해줍니다. 이 하나의 UI로부터 +사용자는 지원되는 모든 앱에서 파일을 선택할 수 있는 것입니다.

+ +

{@link android.content.Intent#ACTION_OPEN_DOCUMENT}는 +{@link android.content.Intent#ACTION_GET_CONTENT}를 +대체할 목적으로 만들어진 것이 아닙니다. 어느 것을 사용해야 할지는 각자의 앱에 필요한 것이 무엇인지에 좌우됩니다.

+ +
    +
  • 앱이 단순히 데이터를 읽고/가져오기만을 바란다면 +{@link android.content.Intent#ACTION_GET_CONTENT}를 사용하십시오. +이 방식을 사용하면 앱은 이미지 파일과 같은 데이터 사본을 가져오게 됩니다.
  • + +
  • 앱이 문서 제공자가 보유한 문서에 장기적, 영구적 액세스 권한을 가지기를 바라는 경우 +{@link android.content.Intent#ACTION_OPEN_DOCUMENT}를 사용하십시오. + 일례로 사용자들에게 문서 제공자에 저장된 이미지를 편집할 수 있게 해주는 +사진 편집 앱을 들 수 있겠습니다.
  • + +
+ + +

이 섹션에서는 +{@link android.content.Intent#ACTION_OPEN_DOCUMENT} 및 +{@link android.content.Intent#ACTION_CREATE_DOCUMENT} 인텐트를 근거로 클라이언트 앱을 작성하는 방법을 설명합니다.

+ + + + +

+다음 조각에서는 {@link android.content.Intent#ACTION_OPEN_DOCUMENT}를 +사용하여 이미지 파일이 들어 있는 문서 제공자를 +검색합니다.

+ +
private static final int READ_REQUEST_CODE = 42;
+...
+/**
+ * Fires an intent to spin up the "file chooser" UI and select an image.
+ */
+public void performFileSearch() {
+
+    // ACTION_OPEN_DOCUMENT is the intent to choose a file via the system's file
+    // browser.
+    Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
+
+    // Filter to only show results that can be "opened", such as a
+    // file (as opposed to a list of contacts or timezones)
+    intent.addCategory(Intent.CATEGORY_OPENABLE);
+
+    // Filter to show only images, using the image MIME data type.
+    // If one wanted to search for ogg vorbis files, the type would be "audio/ogg".
+    // To search for all documents available via installed storage providers,
+    // it would be "*/*".
+    intent.setType("image/*");
+
+    startActivityForResult(intent, READ_REQUEST_CODE);
+}
+ +

다음 내용을 참고하십시오.

+
    +
  • 앱이 {@link android.content.Intent#ACTION_OPEN_DOCUMENT} + 인텐트를 실행시키면 이는 일치하는 문서 제공자를 모두 표시하는 선택기를 시작합니다.
  • + +
  • {@link android.content.Intent#CATEGORY_OPENABLE} 카테고리를 +인텐트에 추가하면 결과를 필터링하여 이미지 파일 등 열 수 있는 문서만 표시합니다.
  • + +
  • {@code intent.setType("image/*")} 문으로 한층 더 필터링을 수행하여 +MIME 데이터 유형이 이미지인 문서만 표시하도록 합니다.
  • +
+ +

결과 처리

+ +

사용자가 선택기에서 문서를 선택하면 +{@link android.app.Activity#onActivityResult onActivityResult()}가 호출됩니다. +선택한 문서를 가리키는 URI는 {@code resultData} +매개변수 안에 들어있습니다. 이 URI를 {@link android.content.Intent#getData getData()}를 사용하여 추출합니다. +일단 이것을 가지게 되면 이를 사용하여 사용자가 원하는 문서를 검색하면 됩니다. 예: +

+ +
@Override
+public void onActivityResult(int requestCode, int resultCode,
+        Intent resultData) {
+
+    // The ACTION_OPEN_DOCUMENT intent was sent with the request code
+    // READ_REQUEST_CODE. If the request code seen here doesn't match, it's the
+    // response to some other intent, and the code below shouldn't run at all.
+
+    if (requestCode == READ_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
+        // The document selected by the user won't be returned in the intent.
+        // Instead, a URI to that document will be contained in the return intent
+        // provided to this method as a parameter.
+        // Pull that URI using resultData.getData().
+        Uri uri = null;
+        if (resultData != null) {
+            uri = resultData.getData();
+            Log.i(TAG, "Uri: " + uri.toString());
+            showImage(uri);
+        }
+    }
+}
+
+ +

문서 메타데이터 살펴보기

+ +

문서의 URI를 얻은 다음에는 그 문서의 메타데이터에 액세스할 수 있습니다. 이 +조각은 해당 URI가 나타내는 문서의 메타데이터를 가져와 다음과 같이 기록합니다.

+ +
public void dumpImageMetaData(Uri uri) {
+
+    // The query, since it only applies to a single document, will only return
+    // one row. There's no need to filter, sort, or select fields, since we want
+    // all fields for one document.
+    Cursor cursor = getActivity().getContentResolver()
+            .query(uri, null, null, null, null, null);
+
+    try {
+    // moveToFirst() returns false if the cursor has 0 rows.  Very handy for
+    // "if there's anything to look at, look at it" conditionals.
+        if (cursor != null && cursor.moveToFirst()) {
+
+            // Note it's called "Display Name".  This is
+            // provider-specific, and might not necessarily be the file name.
+            String displayName = cursor.getString(
+                    cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME));
+            Log.i(TAG, "Display Name: " + displayName);
+
+            int sizeIndex = cursor.getColumnIndex(OpenableColumns.SIZE);
+            // If the size is unknown, the value stored is null.  But since an
+            // int can't be null in Java, the behavior is implementation-specific,
+            // which is just a fancy term for "unpredictable".  So as
+            // a rule, check if it's null before assigning to an int.  This will
+            // happen often:  The storage API allows for remote files, whose
+            // size might not be locally known.
+            String size = null;
+            if (!cursor.isNull(sizeIndex)) {
+                // Technically the column stores an int, but cursor.getString()
+                // will do the conversion automatically.
+                size = cursor.getString(sizeIndex);
+            } else {
+                size = "Unknown";
+            }
+            Log.i(TAG, "Size: " + size);
+        }
+    } finally {
+        cursor.close();
+    }
+}
+
+ +

문서 열기

+ +

문서의 URI를 얻은 다음에는 문서를 열 수도 있고 원하는 대로 무엇이든 +할 수 있습니다.

+ +

비트맵

+ +

다음은 {@link android.graphics.Bitmap}을 여는 방법을 예시로 나타낸 것입니다.

+ +
private Bitmap getBitmapFromUri(Uri uri) throws IOException {
+    ParcelFileDescriptor parcelFileDescriptor =
+            getContentResolver().openFileDescriptor(uri, "r");
+    FileDescriptor fileDescriptor = parcelFileDescriptor.getFileDescriptor();
+    Bitmap image = BitmapFactory.decodeFileDescriptor(fileDescriptor);
+    parcelFileDescriptor.close();
+    return image;
+}
+
+ +

이 작업을 UI 스레드에서 해서는 안 된다는 점을 유의하십시오. 이것은 배경에서 하되, +{@link android.os.AsyncTask}를 사용합니다. 비트맵을 열고 나면 이를 +{@link android.widget.ImageView}로 표시할 수 있습니다. +

+ +

InputStream 가져오기

+ +

다음은 URI에서 {@link java.io.InputStream}을 가져오는 방법을 예시로 나타낸 것입니다. 이 조각에서 +파일의 줄이 문자열로 읽히고 있습니다.

+ +
private String readTextFromUri(Uri uri) throws IOException {
+    InputStream inputStream = getContentResolver().openInputStream(uri);
+    BufferedReader reader = new BufferedReader(new InputStreamReader(
+            inputStream));
+    StringBuilder stringBuilder = new StringBuilder();
+    String line;
+    while ((line = reader.readLine()) != null) {
+        stringBuilder.append(line);
+    }
+    fileInputStream.close();
+    parcelFileDescriptor.close();
+    return stringBuilder.toString();
+}
+
+ +

새 문서 생성하기

+ +

개발자의 앱은 문서 제공자에서 새 문서를 생성할 수 있습니다. 이때 +{@link android.content.Intent#ACTION_CREATE_DOCUMENT} + 인텐트를 사용하면 됩니다. 파일을 생성하려면 인텐트에 MIME 유형과 파일 이름을 부여하고, +고유한 요청 코드로 이를 시작하면 됩니다. 나머지는 여러분 대신 알아서 해드립니다.

+ + +
+// Here are some examples of how you might call this method.
+// The first parameter is the MIME type, and the second parameter is the name
+// of the file you are creating:
+//
+// createFile("text/plain", "foobar.txt");
+// createFile("image/png", "mypicture.png");
+
+// Unique request code.
+private static final int WRITE_REQUEST_CODE = 43;
+...
+private void createFile(String mimeType, String fileName) {
+    Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
+
+    // Filter to only show results that can be "opened", such as
+    // a file (as opposed to a list of contacts or timezones).
+    intent.addCategory(Intent.CATEGORY_OPENABLE);
+
+    // Create a file with the requested MIME type.
+    intent.setType(mimeType);
+    intent.putExtra(Intent.EXTRA_TITLE, fileName);
+    startActivityForResult(intent, WRITE_REQUEST_CODE);
+}
+
+ +

새 문서를 생성하고 나면 +{@link android.app.Activity#onActivityResult onActivityResult()}에서 URI를 가져와 거기에 계속해서 +쓸 수 있습니다.

+ +

문서 삭제하기

+ +

어느 문서에 대한 URI가 있고 해당 문서의 +{@link android.provider.DocumentsContract.Document#COLUMN_FLAGS Document.COLUMN_FLAGS} +에 +{@link android.provider.DocumentsContract.Document#FLAG_SUPPORTS_DELETE SUPPORTS_DELETE}가 들어 있는 경우, +해당 문서를 삭제할 수 있습니다. 예:

+ +
+DocumentsContract.deleteDocument(getContentResolver(), uri);
+
+ +

문서 편집하기

+ +

준비된 텍스트 문서를 편집하는 데 SAF를 사용할 수 있습니다. +이 조각은 +{@link android.content.Intent#ACTION_OPEN_DOCUMENT} 인텐트를 실행하며 +{@link android.content.Intent#CATEGORY_OPENABLE} 카테고리를 사용해 +열 수 있는 문서만 표시하도록 합니다. 이것을 한층 더 필터링하여 텍스트 파일만 표시하게 하려면 다음과 같이 합니다.

+ +
+private static final int EDIT_REQUEST_CODE = 44;
+/**
+ * Open a file for writing and append some text to it.
+ */
+ private void editDocument() {
+    // ACTION_OPEN_DOCUMENT is the intent to choose a file via the system's
+    // file browser.
+    Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
+
+    // Filter to only show results that can be "opened", such as a
+    // file (as opposed to a list of contacts or timezones).
+    intent.addCategory(Intent.CATEGORY_OPENABLE);
+
+    // Filter to show only text files.
+    intent.setType("text/plain");
+
+    startActivityForResult(intent, EDIT_REQUEST_CODE);
+}
+
+ +

다음으로, {@link android.app.Activity#onActivityResult onActivityResult()} +(결과 처리 참조)에서 코드를 호출하여 편집 작업을 수행하도록 하면 됩니다. +다음 조각은 {@link android.content.ContentResolver}에서 {@link java.io.FileOutputStream} +을 가져온 것입니다. 이것은 기본적으로 "쓰기" 모드를 사용합니다. +필요한 최소 수량의 액세스만을 요청하는 것이 가장 좋으니 쓰기만 필요하다면 +읽기/쓰기를 요청하지 마십시오.

+ +
private void alterDocument(Uri uri) {
+    try {
+        ParcelFileDescriptor pfd = getActivity().getContentResolver().
+                openFileDescriptor(uri, "w");
+        FileOutputStream fileOutputStream =
+                new FileOutputStream(pfd.getFileDescriptor());
+        fileOutputStream.write(("Overwritten by MyCloud at " +
+                System.currentTimeMillis() + "\n").getBytes());
+        // Let the document provider know you're done by closing the stream.
+        fileOutputStream.close();
+        pfd.close();
+    } catch (FileNotFoundException e) {
+        e.printStackTrace();
+    } catch (IOException e) {
+        e.printStackTrace();
+    }
+}
+ +

권한 유지

+ +

앱이 읽기 또는 쓰기 작업에 대한 파일을 열면 시스템이 앱에 해당 파일에 대한 URI 권한 허가를 +부여합니다. 이것은 사용자의 장치를 다시 시작할 때까지 유지됩니다. +하지만 만일 앱이 이미지 편집 앱이고, 사용자가 여러분의 앱에서 바로 편집한 5개의 이미지에 +액세스할 수 있도록 하고자 한다고 가정해봅시다. 사용자의 기기가 재시작되면 +여러분이 사용자에게 시스템 선택기를 다시 보내 해당 파일을 검색하도록 해야 할 텐데, +이것은 물론 이상적인 것과는 거리가 멉니다.

+ +

이런 일이 일어나지 않도록 방지하기 위해 시스템이 앱에 부여한 권한을 유지할 수 있습니다. +여러분의 앱은 시스템이 제공하는 유지 가능한 URI 권한 허가를 +효율적으로 "받아들입니다". 이렇게 하면 사용자가 여러분의 앱을 통해 파일에 지속적인 액세스 권한을 가질 수 있으며, +이는 기기가 다시 시작되더라도 관계 없습니다.

+ + +
final int takeFlags = intent.getFlags()
+            & (Intent.FLAG_GRANT_READ_URI_PERMISSION
+            | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+// Check for the freshest data.
+getContentResolver().takePersistableUriPermission(uri, takeFlags);
+ +

마지막 한 단계가 남았습니다. 여러분의 앱이 액세스한 가장 최근의 URI를 +저장해두었을 수 있지만, 이는 더 이상 유효하지 않을 수 있습니다. 또 다른 앱이 문서를 +삭제하거나 수정했을 수 있기 때문입니다. 따라서, 항상 +{@code getContentResolver().takePersistableUriPermission()}을 +호출하여 최신 데이터를 확인해야 합니다.

+ +

사용자 지정 문서 제공자 작성하기

+ +

+파일용 저장소 서비스를 제공하는 앱을 개발 중인 경우(예를 들어 +클라우드 저장 서비스 등), SAF를 통해 파일을 사용할 수 있도록 하려면 사용자 지정 문서 제공자를 +작성하면 됩니다. 이 섹션에서는 이렇게 하는 방법을 설명합니다. +

+ + +

매니페스트

+ +

사용자 지정 문서 제공자를 구현하려면 애플리케이션의 매니페스트에 다음과 같은 항목을 +추가하십시오.

+
    + +
  • API 레벨 19 이상의 대상.
  • + +
  • 사용자 지정 저장소 제공자를 선언하는 <provider> +요소.
  • + +
  • 제공자의 이름은 그 클래스 이름이며 여기에 패키지 이름도 포함됩니다. +예: com.example.android.storageprovider.MyCloudProvider
  • + +
  • 권한의 이름, 이는 패키지 이름과 같으며(이 예시에서는 +com.example.android.storageprovider)여기에 콘텐츠 제공자 유형을 더합니다 +(documents). 예: {@code com.example.android.storageprovider.documents}
  • + +
  • android:exported 속성을 "true"로 설정. +제공자를 내보내 다른 앱이 볼 수 있도록 해야 합니다.
  • + +
  • android:grantUriPermissions 속성을 +"true"로 설정. 이렇게 설정하면 시스템이 여러분의 제공자 안에 있는 콘텐츠에 액세스하도록 다른 앱에 +권한을 허가할 수 있게 해줍니다. 특정 문서에 대한 권한 부여를 유지하는 방법에 대한 논의는 +권한 유지를 참조하십시오.
  • + +
  • {@code MANAGE_DOCUMENTS} 권한. 기본적으로 제공자는 누구나 이용할 수 있습니다. + 이 권한을 추가하면 여러분의 제공자를 시스템에 제한하게 됩니다. +이 제한은 보안상 매우 중요합니다.
  • + +
  • {@code android:enabled} 속성을 리소스 파일에서 정의한 부울 값으로 +설정합니다. 이 속성의 목적은 Android 4.3 이하에서 실행되는 기기에서 제공자를 비활성화하는 데 있습니다. +예를 들어 {@code android:enabled="@bool/atLeastKitKat"} 등입니다. 이 속성을 +매니페스트에 추가하는 것 이외에도 다음과 같은 작업을 해야 합니다. +
      +
    • {@code res/values/} 아래의 {@code bool.xml} 리소스 파일에 +이 라인을 추가합니다.
      <bool name="atLeastKitKat">false</bool>
    • + +
    • {@code res/values-v19/} 아래의 {@code bool.xml} 리소스 파일에 +이 라인을 추가합니다.
      <bool name="atLeastKitKat">true</bool>
    • +
  • + +
  • +{@code android.content.action.DOCUMENTS_PROVIDER} 동작을 포함한 인텐트 필터가 있어야 +시스템이 제공자를 검색할 때 여러분의 제공자가 선택기에 나타날 수 있습니다.
  • + +
+

다음은 제공자를 포함한 샘플 매니페스트에서 발췌한 것입니다.

+ +
<manifest... >
+    ...
+    <uses-sdk
+        android:minSdkVersion="19"
+        android:targetSdkVersion="19" />
+        ....
+        <provider
+            android:name="com.example.android.storageprovider.MyCloudProvider"
+            android:authorities="com.example.android.storageprovider.documents"
+            android:grantUriPermissions="true"
+            android:exported="true"
+            android:permission="android.permission.MANAGE_DOCUMENTS"
+            android:enabled="@bool/atLeastKitKat">
+            <intent-filter>
+                <action android:name="android.content.action.DOCUMENTS_PROVIDER" />
+            </intent-filter>
+        </provider>
+    </application>
+
+</manifest>
+ +

Android 4.3 이하에서 실행되는 기기 지원

+ +

+{@link android.content.Intent#ACTION_OPEN_DOCUMENT} 인텐트는 +Android 4.4 이상에서 실행되는 기기에서만 사용할 수 있습니다. +애플리케이션이 {@link android.content.Intent#ACTION_GET_CONTENT}를 지원하도록 하여 +Android 4.3 이하에서 실행되는 기기에도 적용되도록 하려면 Android 4.4 이상에서 실행되는 기기용 매니페스트에 있는 +{@link android.content.Intent#ACTION_GET_CONTENT} + 인텐트 필터를 비활성화해야 합니다. +문서 제공자와 {@link android.content.Intent#ACTION_GET_CONTENT}는 상호 배타적인 것으로 +간주해야 합니다. 둘을 모두 동시에 지원하는 경우, 앱이 시스템 선택기 UI에 +두 번 나타나 저장된 데이터에 액세스할 두 가지 서로 다른 방법을 제안하게 됩니다. + 이렇게 되면 사용자에게 혼동을 주게 되겠죠.

+ +

다음은 Android 버전 4.4 이상에서 실행되는 기기용 +{@link android.content.Intent#ACTION_GET_CONTENT} 인텐트 필터를 +비활성화하는 데 권장되는 방법입니다.

+ +
    +
  1. {@code res/values/} 아래의 {@code bool.xml} 리소스 파일에 +이 라인을 추가합니다.
    <bool name="atMostJellyBeanMR2">true</bool>
  2. + +
  3. {@code res/values-v19/} 아래의 {@code bool.xml} 리소스 파일에 +이 라인을 추가합니다.
    <bool name="atMostJellyBeanMR2">false</bool>
  4. + +
  5. +액티비티 +별칭을 추가하여 버전 4.4(API 레벨 19) 이상을 대상으로 한 {@link android.content.Intent#ACTION_GET_CONTENT} +인텐트 필터를 비활성화합니다. 예: + +
    +<!-- This activity alias is added so that GET_CONTENT intent-filter
    +     can be disabled for builds on API level 19 and higher. -->
    +<activity-alias android:name="com.android.example.app.MyPicker"
    +        android:targetActivity="com.android.example.app.MyActivity"
    +        ...
    +        android:enabled="@bool/atMostJellyBeanMR2">
    +    <intent-filter>
    +        <action android:name="android.intent.action.GET_CONTENT" />
    +        <category android:name="android.intent.category.OPENABLE" />
    +        <category android:name="android.intent.category.DEFAULT" />
    +        <data android:mimeType="image/*" />
    +        <data android:mimeType="video/*" />
    +    </intent-filter>
    +</activity-alias>
    +
    +
  6. +
+

계약

+ +

사용자 지정 제공자를 작성할 때면 일반적으로 수반되는 작업 중 하나가 +계약 클래스를 구현하는 것입니다. 이는 + +콘텐츠 제공자 개발자 가이드에서 설명한 것과 같습니다. 계약 클래스는 {@code public final} 클래스로, +이 안에 URI에 대한 상수 정의, 열 이름, MIME 유형 및 제공자에 관련된 +다른 메타 데이터가 들어 있습니다. SAF가 +이와 같은 계약 클래스를 대신 제공해주므로 직접 쓰지 않아도 +됩니다.

+ +
    +
  • {@link android.provider.DocumentsContract.Document}
  • +
  • {@link android.provider.DocumentsContract.Root}
  • +
+ +

예를 들어 다음은 여러분의 문서 제공자가 문서 또는 루트에 대해 쿼리된 경우 +커서로 반환할 수 있는 열을 나타낸 것입니다.

+ +
private static final String[] DEFAULT_ROOT_PROJECTION =
+        new String[]{Root.COLUMN_ROOT_ID, Root.COLUMN_MIME_TYPES,
+        Root.COLUMN_FLAGS, Root.COLUMN_ICON, Root.COLUMN_TITLE,
+        Root.COLUMN_SUMMARY, Root.COLUMN_DOCUMENT_ID,
+        Root.COLUMN_AVAILABLE_BYTES,};
+private static final String[] DEFAULT_DOCUMENT_PROJECTION = new
+        String[]{Document.COLUMN_DOCUMENT_ID, Document.COLUMN_MIME_TYPE,
+        Document.COLUMN_DISPLAY_NAME, Document.COLUMN_LAST_MODIFIED,
+        Document.COLUMN_FLAGS, Document.COLUMN_SIZE,};
+
+ +

하위 클래스 DocumentsProvider

+ +

사용자 지정 문서 제공자를 작성하기 위한 다음 단계는 +추상 클래스 {@link android.provider.DocumentsProvider}를 하위 클래스로 만드는 것입니다. 최소한 다음과 같은 메서드를 구현해야 합니다. +

+ +
    +
  • {@link android.provider.DocumentsProvider#queryRoots queryRoots()}
  • + +
  • {@link android.provider.DocumentsProvider#queryChildDocuments queryChildDocuments()}
  • + +
  • {@link android.provider.DocumentsProvider#queryDocument queryDocument()}
  • + +
  • {@link android.provider.DocumentsProvider#openDocument openDocument()}
  • +
+ +

꼭 구현해야만 하는 메서드는 이들뿐이지만, 개발자 여러분이 구현하고자 하는 메서드는 이보다 +훨씬 많을 수도 있습니다. 자세한 내용은{@link android.provider.DocumentsProvider} +를 참조하십시오.

+ +

QueryRoots 구현

+ +

{@link android.provider.DocumentsProvider#queryRoots +queryRoots()} 구현은 반드시 {@link android.database.Cursor}를 반환해야 하며, +이는 문서 제공자의 모든 루트 디렉터리를 가리켜야 합니다. 이때 +{@link android.provider.DocumentsContract.Root}에서 정의한 열을 사용합니다.

+ +

다음 조각에서 {@code projection} 매개변수는 +발신자가 돌려받고자 하는 특정 필드를 나타냅니다. 이 조각은 새 커서를 생성하며 +그에 하나의 행을 추가합니다. 하나의 루트, +다운로드 또는 이미지와 같은 최상위 레벨 디렉터리가 해당됩니다. 대부분의 제공자에는 루트가 하나뿐입니다. 하나 이상이 있을 수도 있습니다. +예를 들어 사용자 계정이 여러 개인 경우가 있습니다. 그런 경우에는 커서에 두 번째 행을 +추가하면 됩니다.

+ +
+@Override
+public Cursor queryRoots(String[] projection) throws FileNotFoundException {
+
+    // Create a cursor with either the requested fields, or the default
+    // projection if "projection" is null.
+    final MatrixCursor result =
+            new MatrixCursor(resolveRootProjection(projection));
+
+    // If user is not logged in, return an empty root cursor.  This removes our
+    // provider from the list entirely.
+    if (!isUserLoggedIn()) {
+        return result;
+    }
+
+    // It's possible to have multiple roots (e.g. for multiple accounts in the
+    // same app) -- just add multiple cursor rows.
+    // Construct one row for a root called "MyCloud".
+    final MatrixCursor.RowBuilder row = result.newRow();
+    row.add(Root.COLUMN_ROOT_ID, ROOT);
+    row.add(Root.COLUMN_SUMMARY, getContext().getString(R.string.root_summary));
+
+    // FLAG_SUPPORTS_CREATE means at least one directory under the root supports
+    // creating documents. FLAG_SUPPORTS_RECENTS means your application's most
+    // recently used documents will show up in the "Recents" category.
+    // FLAG_SUPPORTS_SEARCH allows users to search all documents the application
+    // shares.
+    row.add(Root.COLUMN_FLAGS, Root.FLAG_SUPPORTS_CREATE |
+            Root.FLAG_SUPPORTS_RECENTS |
+            Root.FLAG_SUPPORTS_SEARCH);
+
+    // COLUMN_TITLE is the root title (e.g. Gallery, Drive).
+    row.add(Root.COLUMN_TITLE, getContext().getString(R.string.title));
+
+    // This document id cannot change once it's shared.
+    row.add(Root.COLUMN_DOCUMENT_ID, getDocIdForFile(mBaseDir));
+
+    // The child MIME types are used to filter the roots and only present to the
+    //  user roots that contain the desired type somewhere in their file hierarchy.
+    row.add(Root.COLUMN_MIME_TYPES, getChildMimeTypes(mBaseDir));
+    row.add(Root.COLUMN_AVAILABLE_BYTES, mBaseDir.getFreeSpace());
+    row.add(Root.COLUMN_ICON, R.drawable.ic_launcher);
+
+    return result;
+}
+ +

QueryChildDocuments 구현

+ +

+{@link android.provider.DocumentsProvider#queryChildDocuments queryChildDocuments()} + 구현은 반드시 {@link android.database.Cursor}를 반환해야 하며, +이는 지정된 디렉터리 내의 모든 파일을 가리켜야 합니다. 이때 +{@link android.provider.DocumentsContract.Document}에서 정의한 열을 사용합니다.

+ +

이 메서드는 선택기 UI에서 애플리케이션 루트를 선택하는 경우 호출됩니다. +이는 해당 루트 아래 디렉터리의 하위 문서를 가져옵니다. 이것은 루트에서뿐만 아니라 파일 계층의 어느 레벨에서나 +호출할 수 있습니다. 이 조각은 요청한 열로 새 커서를 만든 다음, +상위 디렉터리에 있는 모든 직속 하위에 대한 정보를 커서에 추가합니다. +하위는 이미지, 또 다른 디렉터리가 될 수도 있고 +어느 파일이라도 될 수 있습니다.

+ +
@Override
+public Cursor queryChildDocuments(String parentDocumentId, String[] projection,
+                              String sortOrder) throws FileNotFoundException {
+
+    final MatrixCursor result = new
+            MatrixCursor(resolveDocumentProjection(projection));
+    final File parent = getFileForDocId(parentDocumentId);
+    for (File file : parent.listFiles()) {
+        // Adds the file's display name, MIME type, size, and so on.
+        includeFile(result, null, file);
+    }
+    return result;
+}
+
+ +

QueryDocuments 구현

+ +

+{@link android.provider.DocumentsProvider#queryDocument queryDocument()} + 구현은 반드시 {@link android.database.Cursor}를 반환해야 하며, +이는 지정된 파일을 가리켜야 합니다. 이때 {@link android.provider.DocumentsContract.Document}에서 정의한 열을 사용합니다. +

+ +

{@link android.provider.DocumentsProvider#queryDocument queryDocument()} + 메서드는 +{@link android.provider.DocumentsProvider#queryChildDocuments queryChildDocuments()}에서 +전달된 것과 같은 정보를 반환하지만, 특정한 파일에만 해당됩니다.

+ + +
@Override
+public Cursor queryDocument(String documentId, String[] projection) throws
+        FileNotFoundException {
+
+    // Create a cursor with the requested projection, or the default projection.
+    final MatrixCursor result = new
+            MatrixCursor(resolveDocumentProjection(projection));
+    includeFile(result, documentId, null);
+    return result;
+}
+
+ +

OpenDocument 구현

+ +

지정된 파일을 나타내는 +{@link android.os.ParcelFileDescriptor}를 반환하려면 {@link android.provider.DocumentsProvider#openDocument +openDocument()}를 구현해야 합니다. 다른 앱들이 반환된 {@link android.os.ParcelFileDescriptor}를 + 사용하여 데이터를 스트리밍할 수 있습니다. 시스템은 사용자가 파일을 선택하고 +클라이언트 앱이 이에 대한 액세스를 요청하면서 +{@link android.content.ContentResolver#openFileDescriptor openFileDescriptor()}를 사용할 때 이 메서드를 호출합니다. +예를 들면 다음과 같습니다.

+ +
@Override
+public ParcelFileDescriptor openDocument(final String documentId,
+                                         final String mode,
+                                         CancellationSignal signal) throws
+        FileNotFoundException {
+    Log.v(TAG, "openDocument, mode: " + mode);
+    // It's OK to do network operations in this method to download the document,
+    // as long as you periodically check the CancellationSignal. If you have an
+    // extremely large file to transfer from the network, a better solution may
+    // be pipes or sockets (see ParcelFileDescriptor for helper methods).
+
+    final File file = getFileForDocId(documentId);
+
+    final boolean isWrite = (mode.indexOf('w') != -1);
+    if(isWrite) {
+        // Attach a close listener if the document is opened in write mode.
+        try {
+            Handler handler = new Handler(getContext().getMainLooper());
+            return ParcelFileDescriptor.open(file, accessMode, handler,
+                        new ParcelFileDescriptor.OnCloseListener() {
+                @Override
+                public void onClose(IOException e) {
+
+                    // Update the file with the cloud server. The client is done
+                    // writing.
+                    Log.i(TAG, "A file with id " +
+                    documentId + " has been closed!
+                    Time to " +
+                    "update the server.");
+                }
+
+            });
+        } catch (IOException e) {
+            throw new FileNotFoundException("Failed to open document with id "
+            + documentId + " and mode " + mode);
+        }
+    } else {
+        return ParcelFileDescriptor.open(file, accessMode);
+    }
+}
+
+ +

보안

+ +

여러분의 문서 제공자가 암호로 보호된 클라우드 저장소 서비스이고 +여러분은 사용자가 파일을 공유하기 전에 우선 로그인부터 하도록 확실히 해두고 싶다고 가정합니다. +사용자가 로그인되지 않은 경우 앱은 어떻게 해야 합니까? 해법은 +{@link android.provider.DocumentsProvider#queryRoots +queryRoots()} 구현에서 루트를 반환하지 않는 것입니다. 다시 말해, 텅 빈 루트 커서를 반환하는 것입니다.

+ +
+public Cursor queryRoots(String[] projection) throws FileNotFoundException {
+...
+    // If user is not logged in, return an empty root cursor.  This removes our
+    // provider from the list entirely.
+    if (!isUserLoggedIn()) {
+        return result;
+}
+
+ +

또 다른 단계는 {@code getContentResolver().notifyChange()}를 호출하는 것입니다. +{@link android.provider.DocumentsContract}를 기억하십니까? 이것을 사용하는 이유는 +바로 이 URI를 만들기 위해서입니다. 다음 조각은 사용자의 로그인 상태가 변경될 때마다 +시스템이 문서 제공자의 루트를 쿼리하도록 지시하고 있습니다. 사용자가 로그인되어 있지 않은 상태에서 +{@link android.provider.DocumentsProvider#queryRoots queryRoots()}를 호출하면 +위에서 나타낸 것과 같이 빈 커서를 반환합니다. 이렇게 하면 사용자가 제공자에 로그인되었을 때만 +제공자의 문서를 사용할 수 있도록 보장할 수 있습니다.

+ +
private void onLoginButtonClick() {
+    loginOrLogout();
+    getContentResolver().notifyChange(DocumentsContract
+            .buildRootsUri(AUTHORITY), null);
+}
+
\ No newline at end of file diff --git a/docs/html-intl/intl/ko/guide/topics/resources/accessing-resources.jd b/docs/html-intl/intl/ko/guide/topics/resources/accessing-resources.jd new file mode 100644 index 0000000000000000000000000000000000000000..be9dd6bb0b20db8d2403d236add7e6614659f14f --- /dev/null +++ b/docs/html-intl/intl/ko/guide/topics/resources/accessing-resources.jd @@ -0,0 +1,337 @@ +page.title=리소스 액세스 +parent.title=애플리케이션 리소스 +parent.link=index.html +@jd:body + +
+
+

간략히 보기

+
    +
  • 리소스는 {@code R.java}의 정수를 사용하는 코드, 예를 들어 +{@code R.drawable.myimage}에서 참조할 수 있습니다.
  • +
  • 리소스는 특수 XML 구문을 사용하는 리소스, 예를 들어 {@code +@drawable/myimage}에서 참조할 수 있습니다.
  • +
  • +{@link android.content.res.Resources}의 메서드로 앱 리소스에 액세스할 수도 있습니다.
  • +
+ +

Key 클래스

+
    +
  1. {@link android.content.res.Resources}
  2. +
+ +

이 문서의 내용

+
    +
  1. 코드에서 리소스 액세스
  2. +
  3. XML에서 리소스 액세스 +
      +
    1. 스타일 속성 참조
    2. +
    +
  4. +
  5. 플랫폼 리소스 액세스
  6. +
+ +

참고 항목

+
    +
  1. 리소스 제공
  2. +
  3. 리소스 유형
  4. +
+
+
+ + + + +

일단 어떤 리소스를 애플리케이션에 제공한 다음에는(리소스 제공에서 논의), +해당 리소스의 리소스 ID를 참조함으로써 이를 적용할 수 있습니다. 모든 리소스 ID는 +{@code aapt} 도구가 자동으로 생성하는 프로젝트의 {@code R} 클래스에서 정의됩니다.

+ +

애플리케이션이 컴파일링되면, {@code aapt}가 {@code R} 클래스를 생성하며, 이 클래스 안에 {@code +res/} 디렉터리에 있는 모든 리소스의 +리소스 ID가 들어있습니다. 각 리소스 유형에는 {@code R} 하위 클래스가 있고(예: 모든 드로어블 리소스에 대한 +{@code R.drawable}), 해당 유형의 각 리소스에는 정적 +정수가 있습니다(예: {@code R.drawable.icon}). 이 정수가 +리소스를 검색하는 데 사용할 수 있는 리소스 ID입니다.

+ +

{@code R} 클래스가 리소스 ID가 지정되는 곳이기는 하지만, 리소스 ID를 찾기 위해 +이곳을 볼 필요는 전혀 없습니다. 하나의 리소스 ID는 항상 다음과 같이 구성됩니다.

+
    +
  • 리소스 유형: 각 리소스는 "유형"으로 그룹화됩니다. 예: {@code +string}, {@code drawable} 및 {@code layout} 다양한 유형에 관한 자세한 정보는 리소스 유형을 참조하십시오. +
  • +
  • 리소스 이름: +리소스가 단순 값(예: 문자열 등)일 경우, +확장자를 제외한 파일 이름이나 XML {@code android:name} 속성 값 중 하나입니다.
  • +
+ +

리소스에 액세스하는 방법은 두 가지가 있습니다.

+
    +
  • 코드 내부에서: {@code R} +클래스의 하위 클래스에서 정적 정수를 사용합니다. 예: +
    R.string.hello
    +

    {@code string}은 리소스 유형이고 {@code hello}는 리소스 이름입니다. 리소스 ID를 이 형식으로 제공하면 리소스에 액세스할 수 있는 +Android API가 많습니다. +코드 내 리소스 액세스를 참조하십시오.

    +
  • +
  • XML 내부에서: {@code R} 클래스에서 정의된 +리소스 ID에 상응하기도 하는 특수 XML 구문을 사용합니다. 예: +
    @string/hello
    +

    {@code string}은 리소스 유형이고 {@code hello}는 리소스 이름입니다. 이 +구문은 리소스로 값을 제공할 것으로 예상되는 어느 곳에서나 XML 리소스 형태로 사용할 수 있습니다. XML에서 리소스 액세스를 참조하십시오.

    +
  • +
+ + + +

코드 내 리소스 액세스

+ +

리소스 ID를 메서드 매개변수로 전달하면 코드 내 리소스를 사용할 수 있습니다. 예를 들어 +, {@link android.widget.ImageView}를 설정하여 {@link android.widget.ImageView#setImageResource(int) setImageResource()}를 사용하는 {@code res/drawable/myimage.png} +리소스를 사용할 수 있습니다.

+
+ImageView imageView = (ImageView) findViewById(R.id.myimageview);
+imageView.setImageResource(R.drawable.myimage);
+
+ +

{@link +android.content.res.Resources}에서 메서드를 사용하는 개별 리소스를 검색할 수도 있으며, 이는 +{@link android.content.Context#getResources()}로 인스턴스를 가져올 수 있습니다.

+ + + + +

구문

+ +

다음은 코드로 리소스를 참조하는 데 쓰는 구문입니다.

+ +
+[<package_name>.]R.<resource_type>.<resource_name>
+
+ +
    +
  • {@code <package_name>}은(는) 리소스가 위치한 패키지의 이름입니다( +자체 패키지의 리소스를 참조할 경우에는 필요하지 않습니다).
  • +
  • {@code <resource_type>}은(는) 해당 리소스 유형의 {@code R} 하위 클래스입니다.
  • +
  • {@code <resource_name>}은(는) 확장자가 없는 리소스 파일 이름이거나 +XML 요소의 {@code android:name} 속성 값입니다(단순 +값의 경우).
  • +
+

각 리소스 유형과 참조 방법에 관한 자세한 내용은 리소스 유형 +을 참조하십시오.

+ + +

사용 사례

+ +

리소스 ID 매개변수를 허용하는 메서드가 많이 있고, {@link android.content.res.Resources}에서 메서드를 + 사용하여 리소스를 검색할 수 있습니다. {@link android.content.Context#getResources +Context.getResources()}로 {@link +android.content.res.Resources}의 인스턴스를 가져올 수 있습니다.

+ + +

다음은 코드로 리소스에 액세스한 몇 가지 예시입니다.

+ +
+// Load a background for the current screen from a drawable resource
+{@link android.app.Activity#getWindow()}.{@link
+android.view.Window#setBackgroundDrawableResource(int)
+setBackgroundDrawableResource}(R.drawable.my_background_image) ;
+
+// Set the Activity title by getting a string from the Resources object, because
+//  this method requires a CharSequence rather than a resource ID
+{@link android.app.Activity#getWindow()}.{@link android.view.Window#setTitle(CharSequence)
+setTitle}(getResources().{@link android.content.res.Resources#getText(int)
+getText}(R.string.main_title));
+
+// Load a custom layout for the current screen
+{@link android.app.Activity#setContentView(int)
+setContentView}(R.layout.main_screen);
+
+// Set a slide in animation by getting an Animation from the Resources object
+mFlipper.{@link android.widget.ViewAnimator#setInAnimation(Animation)
+setInAnimation}(AnimationUtils.loadAnimation(this,
+        R.anim.hyperspace_in));
+
+// Set the text on a TextView object using a resource ID
+TextView msgTextView = (TextView) findViewById(R.id.msg);
+msgTextView.{@link android.widget.TextView#setText(int)
+setText}(R.string.hello_message);
+
+ + +

주의: 손으로 {@code +R.java} 파일을 수정해서는 안 됩니다.—이것은 프로젝트가 +컴파일되었을 때 {@code aapt} 도구가 생성한 파일입니다. 모든 변경 사항은 다음 번 컴파일에서 재정의됩니다.

+ + + +

XML에서 리소스 액세스

+ +

기존 리소스에 대한 참조를 사용하여 +일부 XML 속성과 요소의 값을 정의할 수 있습니다. 이 작업은 레이아웃 파일을 생성할 때 위젯에 문자열과 이미지를 제공하기 위해 +자주 하게 됩니다.

+ +

예를 들어, 레이아웃에 {@link android.widget.Button}을 추가하면 +해당 버튼 텍스트에 문자열 리소스를 사용해야 합니다.

+ +
+<Button
+    android:layout_width="fill_parent"
+    android:layout_height="wrap_content"
+    android:text="@string/submit" />
+
+ + +

구문

+ +

다음은 XML 리소스로 리소스를 참조하는 데 쓰는 구문입니다.

+ +
+@[<package_name>:]<resource_type>/<resource_name>
+
+ +
    +
  • {@code <package_name>}은(는) 리소스가 위치한 패키지의 이름입니다( +같은 패키지의 리소스를 참조할 경우에는 필요하지 않습니다).
  • +
  • {@code <resource_type>}은(는) 해당 리소스 유형의 +{@code R} 하위 클래스입니다.
  • +
  • {@code <resource_name>}은(는) 확장자가 없는 리소스 파일 이름이거나 +XML 요소의 {@code android:name} 속성 값입니다(단순 +값의 경우).
  • +
+ +

각 리소스 유형과 참조 방법에 관한 자세한 내용은 리소스 유형 +을 참조하십시오.

+ + +

사용 사례

+ +

몇몇 경우에는 값에 대한 리소스를 반드시 XML로 사용해야 하지만(예: 위젯에 드로어블 이미지를 + 적용하기 위해), 단순 값을 허용하는 곳이라면 어디서든 XML로 리소스를 사용할 수도 있습니다. 예를 들어 +, 색상 리소스문자열 리소스를 포함한 다음과 같은 리소스 파일을 가지고 있다고 합시다.

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+   <color name="opaque_red">#f00</color>
+   <string name="hello">Hello!</string>
+</resources>
+
+ +

텍스트 색상과 +텍스트 문자열을 설정하는 데 이와 같은 리소스를 다음의 레이아웃 파일에서 사용할 수 있습니다.

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<EditText xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    android:textColor="@color/opaque_red"
+    android:text="@string/hello" />
+
+ +

이 경우, 리소스를 자체 패키지에서 가져왔으므로 리소스 참조에 패키지 이름을 +지정하지 않아도 됩니다. +시스템 리소스를 참조하려면 패키지 이름을 포함해야 합니다. 예:

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<EditText xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    android:textColor="@android:color/secondary_text_dark"
+    android:text="@string/hello" />
+
+ +

참고: 항상 문자열 리소스를 사용해야 +사용자의 애플리케이션이 다른 언어에 맞게 지역화될 수 있습니다. +대체 +리소스(예: 지역화된 문자열) 생성에 관한 자세한 정보는 대체 +리소스 제공을 참조하십시오. 다른 언어로 애플리케이션을 지역화하기 위한 전체 지침은 +지역화를 참조하십시오.

+ +

XML의 리소스를 사용하여 별명을 생성할 수도 있습니다. 예를 들어, 드로어블 리소스이면서 +또 다른 드로어블 리소스의 별명인 것을 생성할 수 있습니다.

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
+    android:src="@drawable/other_drawable" />
+
+ +

이것은 일견 중복되는 것처럼 들리지만, 대체 리소스를 사용할 때 매우 유용하게 쓰일 수 있습니다. +별명 리소스 생성에 관해 자세히 알아보십시오.

+ + + +

스타일 속성 참조

+ +

스타일 속성 리소스는 현재 적용된 테마의 속성 값을 +참조할 수 있게 해줍니다. 스타일 속성을 참조하면 +하드 코드로 작성된 값을 제공하는 것 대신에 UI 요소의 외관을 사용자 지정하여 +현재 테마에서 제공한 표준 변형에 맞춰 스타일링할 수 있습니다. 스타일 속성을 참조하는 것은 +기본적으로 "이 속성이 정의한 스타일을 현재 테마로 사용하라"는 말과 같습니다.

+ +

스타일 속성을 참조하는 경우, 이름 구문은 보통 +리소스와 거의 똑같습니다. 다만 앳 기호({@code @})를 사용하는 대신 물음표({@code ?})를 사용하며, +리소스 유형 부분이 선택 사항이라는 점만이 다릅니다. 예:

+ +
+?[<package_name>:][<resource_type>/]<resource_name>
+
+ +

예컨대 다음은 텍스트 색상을 시스템 테마의 "기본" 텍스트 색상에 +일치하도록 설정하기 위해 속성을 참조하는 방법을 나타낸 것입니다.

+ +
+<EditText id="text"
+    android:layout_width="fill_parent"
+    android:layout_height="wrap_content"
+    android:textColor="?android:textColorSecondary"
+    android:text="@string/hello_world" />
+
+ +

여기서 {@code android:textColor} 속성이 주어진 스타일 속성의 이름을 현재 테마대로 +지정합니다. Android는 이제 {@code android:textColorSecondary} +스타일 속성에 적용된 값을 이 위젯의 {@code android:textColor}에 대한 값으로 사용합니다. 시스템 +리소스 도구는 속성 리소스가 이 컨텍스트에서 예상된다는 것을 알기 때문에 +유형( +?android:attr/textColorSecondary)을 명시적으로 선언하지 않아도 됩니다.—{@code attr} 유형은 배제해도 됩니다.

+ + + + +

플랫폼 리소스 액세스

+ +

Android에는 스타일, 테마 및 레이아웃 등 여러 가지 표준 리소스가 포함되어 있습니다. +이와 같은 리소스에 액세스하려면 +android 패키지 이름으로 리소스 참조를 한정합니다. 예를 들어 Android는 +{@link android.widget.ListAdapter}의 목록 항목으로 사용할 수 있는 레이아웃 리소스를 제공합니다.

+ +
+{@link android.app.ListActivity#setListAdapter(ListAdapter)
+setListAdapter}(new {@link
+android.widget.ArrayAdapter}<String>(this, android.R.layout.simple_list_item_1, myarray));
+
+ +

이 예시에서 {@link android.R.layout#simple_list_item_1}은 +{@link android.widget.ListView}의 항목에 대해 플랫폼이 정의한 레이아웃 리소스입니다. 목록 항목에 대해 나름의 레이아웃을 +만드는 대신 이것을 사용해도 됩니다. 자세한 내용은 +목록 보기 개발자 가이드를 참조하십시오.

+ diff --git a/docs/html-intl/intl/ko/guide/topics/resources/overview.jd b/docs/html-intl/intl/ko/guide/topics/resources/overview.jd new file mode 100644 index 0000000000000000000000000000000000000000..e98a6775077252264d995895c97d37995c4609b7 --- /dev/null +++ b/docs/html-intl/intl/ko/guide/topics/resources/overview.jd @@ -0,0 +1,103 @@ +page.title=리소스 개요 +@jd:body + + + + +

이미지나 문자열 같은 리소스는 항상 애플리케이션 코드에서 +외부화하여 독립적으로 유지해야 합니다. 리소스를 외부화하면 +다양한 언어나 화면 크기와 같은 특정 기기 구성을 지원하는 +대체 리소스를 제공할 수 있습니다. 이러한 기능은 Android 구동 장치를 +다양한 구성에서 이용하게 되면서 점점 더 중요해지고 있습니다. 여러 가지 구성에 +호환성을 제공하려면 프로젝트의 +{@code res/} 디렉터리 안에 리소스를 정리해야 합니다. 이때 여러 가지 하위 디렉터리를 사용하여 리소스를 유형과 구성 +기준으로 그룹화하면 좋습니다.

+ +
+ +

+그림 1. 각각 기본 레이아웃을 사용하는 서로 다른 두 개의 기기입니다 +(앱에서 대체 레이아웃을 제공하지 않습니다).

+
+ +
+ +

+그림 2. 서로 다른 두 개의 기기로, 각각 다른 화면 크기에 맞게 제공된 서로 다른 +레이아웃을 사용하고 있습니다.

+
+ +

어떤 유형의 리소스든 애플리케이션에 맞게기본값과 여러 +대체 리소스를 지정할 수 있습니다.

+
    +
  • 기본 리소스는 기기 구성에 관계없이 항상 사용하거나 +기존 구성에 일치하는 대체 리소스가 없을 때 +사용합니다.
  • +
  • 대체 리소스는 특정 구성에서 사용하기 위해 개발자가 특별히 디자인한 것을 +말합니다. 리소스 그룹을 특정 구성용으로 지정하려면, +디렉터리 이름에 적절한 구성 한정자를 추가하십시오.
  • +
+ +

예를 들어 기본 UI 레이아웃은 +{@code res/layout/} 디렉터리에 저장되어 있더라도 화면이 가로 방향일 때 사용할 +다른 레이아웃을 지정할 수도 있습니다. 이를 {@code res/layout-land/} +디렉터리에 저장하면 됩니다. Android는 +기기의 현재 구성을 리소스 디렉터리 이름과 일치시켜서 적절한 리소스를 적용합니다.

+ +

그림 1은 이용 가능한 대체 리소스가 없을 경우 시스템이 서로 다른 두 개의 기기에 +같은 레이아웃을 적용하는 방법을 보여줍니다. 그림 2는 +같은 애플리케이션에 큰 화면용 레이아웃 리소스를 추가한 모습을 나타낸 것입니다.

+ +

다음 문서는 대체 리소스를 체계화하고, +대체 리소스를 지정하고, 애플리케이션에 액세스 하는 등의 방법에 관한 완전한 지침을 제공합니다.

+ +
+
리소스 제공
+
앱에 포함할 수 있는 여러 가지 종류의 리소스와, 이러한 리소스를 저장하는 장소, 특정 기기 구성에 대한 +대체 리소스를 생성하는 방법입니다.
+
리소스 액세스
+
제공한 리소스를 사용하는 방법입니다. 이를 애플리케이션 코드에서 참조하거나 +다른 XML 리소스에서 참조하는 방식을 씁니다.
+
런타임 변경 처리
+
액티비티가 실행 중인 동안 발생한 구성 변경을 관리하는 방법입니다.
+
지역화
+
대체 리소스를 사용하여 애플리케이션을 지역화하는 방법에 대한 상세한 가이드입니다. 이것은 대체 +리소스를 사용하는 한 가지 방법에 불과하지만, 더 많은 사용자에게 도달하려면 매우 중요한 +방법입니다.
+
리소스 유형
+
개발자가 제공할 수 있는 다양한 리소스 유형의 참조로, 각각의 XML 요소, +속성과 구문을 설명하는 것입니다. 예를 들어, 이 참조는 애플리케이션 메뉴와 드로어블 리소스, +애니메이션에 대한 리소스를 생성하는 법을 보여줍니다.
+
+ + diff --git a/docs/html-intl/intl/ko/guide/topics/resources/providing-resources.jd b/docs/html-intl/intl/ko/guide/topics/resources/providing-resources.jd new file mode 100644 index 0000000000000000000000000000000000000000..681cbb31c4fb599a80eca3f55f3c30a47e22a89b --- /dev/null +++ b/docs/html-intl/intl/ko/guide/topics/resources/providing-resources.jd @@ -0,0 +1,1094 @@ +page.title=리소스 제공 +parent.title=애플리케이션 리소스 +parent.link=index.html +@jd:body + +
+
+

간략히 보기

+
    +
  • {@code res/}의 여러 가지 하위 디렉터리에 속한 여러 가지 유형의 리소스
  • +
  • 구성별 리소스 파일을 제공하는 대체 리소스
  • +
  • 항상 기본 리소스를 포함해야 앱이 특정 기기 구성에 +좌우되지 않습니다.
  • +
+

이 문서의 내용

+
    +
  1. 리소스 유형 그룹화
  2. +
  3. 대체 리소스 제공 +
      +
    1. 한정자 이름 규칙
    2. +
    3. 별명 리소스 생성
    4. +
    +
  4. +
  5. 리소스와 연관된 최선의 기기 호환성 제공
  6. +
  7. Android가 가장 잘 일치하는 리소스를 찾는 방법
  8. +
+ +

참고 항목

+
    +
  1. 리소스 액세스
  2. +
  3. 리소스 유형
  4. +
  5. 다중 +화면 지원
  6. +
+
+
+ +

이미지나 문자열과 같은 애플리케이션 리소스는 항상 코드에서 외부화해야 합니다. +그래야 이들을 독립적으로 유지관리할 수 있습니다. 특정 기기 구성에 대한 대체 리소스도 +제공해야 합니다. 이것은 특별하게 명명한 리소스 디렉터리에 그룹화하는 방법을 씁니다. Android는 +런타임에 현재 구성을 근거로 적절한 리소스를 사용합니다. 예를 들어 +여러 가지 화면 크기에 따라 여러 가지 UI 레이아웃을 제공하거나 언어 설정에 따라 +각기 다른 문자열을 제공하고자 할 수 있습니다.

+ +

애플리케이션 리소스를 외부화하면 +프로젝트 {@code R} 클래스에서 발생하는 리소스 ID로 액세스할 수 있습니다. 애플리케이션에서 +리소스를 사용하는 방법은 리소스 +액세스에서 설명합니다. 이 문서에서는 Android 프로젝트에서 리소스를 그룹화하는 방법과 특정 기기 구성에 대한 +대체 리소스를 제공하는 법을 보여드립니다.

+ + +

리소스 유형 그룹화

+ +

프로젝트 +{@code res/} 디렉터리의 특정 하위 디렉터리에 각 유형의 리소스를 배치해야 합니다. 예를 들어, 다음은 간단한 프로젝트의 파일 계층입니다.

+ +
+MyProject/
+    src/  
+        MyActivity.java  
+    res/
+        drawable/  
+            graphic.png  
+        layout/  
+            main.xml
+            info.xml
+        mipmap/  
+            icon.png 
+        values/  
+            strings.xml  
+
+ +

이 예시에서 {@code res/} 디렉터리가 모든 리소스(이미지 리소스, 레이아웃 리소스 두 개, 시작 관리자 아이콘용 {@code mipmap/} 디렉터리, + 문자열 리소스 파일)를 (하위 디렉터리에 +) 포함하는 것을 볼 수 있습니다. 리소스 디렉터리 이름은 +중요하며 표1에 설명되어 있습니다.

+ +

참고: Mipmap 폴더를 사용하는 자세한 방법은 +프로젝트 관리 개요를 참조하십시오.

+ +

표 1. 프로젝트 +{@code res/} 디렉터리 내부에서 지원하는 리소스 디렉터리입니다.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
디렉터리리소스 유형
animator/속성 +애니메이션을 정의하는 XML 파일입니다.
anim/tween +애니메이션을 정의하는 XML 파일입니다 (속성 애니메이션도 이 디렉터리에 저장할 수 있지만 +두 가지 유형을 구분하기 위해 속성 애니메이션에는 {@code animator/} 디렉터리가 기본 설정됩니다 +).
color/색상의 상태 목록을 정의하는 XML 파일입니다. 색상 상태 +목록 리소스를 참조하십시오.
drawable/

다음 드로어블 리소스 하위 유형으로 컴파일 되는 비트맵 파일({@code .png}, {@code .9.png}, {@code .jpg}, {@code .gif}) 또는 XML 파일입니다. +

+
    +
  • 비트맵 파일
  • +
  • 나인 패치(크기 조절 가능한 비트맵)
  • +
  • 상태 목록
  • +
  • 형태
  • +
  • 드로어블 애니메이션
  • +
  • 기타 드로어블
  • +
+

드로어블 리소스를 참조하십시오.

+
mipmap/각기 다른 시작 관리자 아이콘 밀도에 대한 드로어블 파일입니다. +{@code mipmap/} 폴더로 시작 관리자 아이콘을 관리하는 자세한 방법은 +프로젝트 관리 개요를 참조하십시오.
layout/사용자 인터페이스 레이아웃을 정의하는 XML 파일입니다. + 레이아웃 리소스를 참조하십시오.
menu/옵션 메뉴, 컨텍스트 메뉴 또는 하위 +메뉴 등과 같은 애플리케이션 메뉴를 정의하는 XML입니다. 메뉴 리소스를 참조하십시오.
raw/

원시 형태로 저장하기 위한 임의의 파일입니다. 원시 +{@link java.io.InputStream}으로 이런 리소스를 열려면 리소스 ID, {@code R.raw.filename}으로 {@link android.content.res.Resources#openRawResource(int) +Resources.openRawResource()}를 호출합니다.

+

그러나 원본 파일 이름과 파일 계층에 액세스해야 하는 경우, +({@code res/raw/}가 아니라) {@code +assets/} 디렉터리에 몇몇 리소스를 저장해두는 것을 고려해 볼 수 있습니다. {@code assets/}에 있는 파일에는 +리소스 ID가 주어지지 않으므로, 이들을 읽는 유일한 방법은 {@link android.content.res.AssetManager}를 사용하는 것뿐입니다.

values/

문자열, 정수 및 색과 같은 단순 값이 들어있는 XML 파일입니다.

+

다른 {@code res/} 하위 디렉터리에 있는 XML 리소스 파일은 XML 파일 이름을 근거로 +하나의 리소스를 정의하는 반면, {@code values/} 디렉터리에 있는 파일은 여러 개의 리소스를 설명합니다. +이 디렉터리 안에 있는 파일의 경우, {@code <resources>} 요소의 각 하위 요소가 리소스를 하나씩 +정의합니다. 예를 들어 {@code <string>} 요소는 +{@code R.string} 리소스를 생성하고 {@code <color>} 요소는 {@code R.color} + 리소스를 생성합니다.

+

각 리소스가 자체 XML 요소로 정의되므로, 원하는 대로 파일을 정의하고 하나의 파일에 여러 가지 리소스 유형을 +배정할 수 있습니다. 하지만 명확히 하려면 여러 가지 파일에 +각기 고유한 리소스를 배치하는 것이 좋을 수도 있습니다. 예를 들어, 다음은 이 디렉터리에서 생성할 수 있는 리소스를 위한 +파일 이름 명명법입니다.

+ +

문자열 리소스, +스타일 리소스 및 +자세한 리소스 유형을 참조하십시오.

+
xml/런타임에 읽을 수 있는 임의의 XML 파일로, 이때 {@link +android.content.res.Resources#getXml(int) Resources.getXML()}을 호출하는 방법을 씁니다. 다양한 XML 구성 파일을 여기에 저장해야 합니다. 예를 들어 +검색 가능한 구성 등이 이에 해당됩니다. +
+ +

주의: 리소스 파일을 +{@code res/} 디렉터리에 직접 저장하면 절대로 안 됩니다. 컴파일러 오류를 초래하게 됩니다.

+ +

특정 유형의 리소스에 관한 자세한 정보는 리소스 유형 문서를 참조하십시오.

+ +

표1에 정의된 하위 디렉터리에 저장하는 리소스는 "기본" +리소스입니다. 다시 말해, 이러한 리소스가 애플리케이션의 기본 디자인과 콘텐츠를 정의한다는 뜻입니다. +그러나, 여러 가지 유형의 Android 구동 기기는 각기 다른 유형의 리소스를 호출할 수도 있습니다. +예를 들어 어느 기기의 화면이 보통보다 큰 편이라면, 추가적인 화면 공간의 이점을 활용할 수 있는 +다른 레이아웃 리소스를 제공해야 합니다. 또는, 기기에 다른 언어 설정이 있을 경우 +해당 텍스트를 사용자 인터페이스에 번역하는 다른 문자열 리소스를 +제공해야 합니다. 여러 가지 기기 구성에 여러 가지 리소스를 제공하려면, +기본 리소스 외에 대체 리소스를 +제공해야 합니다.

+ + +

대체 리소스 제공

+ + +
+ +

+그림 1. 서로 다른 두 개의 기기로, 서로 다른 레이아웃 리소스를 사용합니다.

+
+ +

거의 모든 애플리케이션이 특정 기기 구성을 지원하는 +대체 리소스를 제공해야 합니다. 예를 들어 여러 가지 화면 밀도에 맞는 대체 드로어블 리소스를 +포함시켜야 하며 여러 가지 언어에 맞게 대체 문자열 리소스도 포함시켜야 합니다. Android는 런타임에 +현재 기기 구성을 감지하고 애플리케이션에 대해 적절한 리소스를 +로드합니다.

+ +

리소스 세트에 대하여 구성별로 적절한 대체를 지정하려면 다음과 같이 합니다.

+
    +
  1. {@code res/}에 {@code +<resources_name>-<config_qualifier>} 형식으로 이름을 지정한 새 디렉터리를 만듭니다. +
      +
    • {@code <resources_name>}은 해당 기본 리소스의 디렉터리 이름입니다(표1에 정의). +
    • +
    • {@code <qualifier>}는 리소스를 사용할 개별 구성을 지정하는 +이름입니다(표2에 정의).
    • +
    +

    하나 이상의 {@code <qualifier>}를 추가할 수 있습니다. 각기 대시로 +구분합니다.

    +

    주의: 여러 한정자를 추가할 때는 +표2에 나열된 것과 같은 순서로 배치해야 합니다. 한정자의 순서가 잘못 지정되면 +해당 리소스가 무시됩니다.

    +
  2. +
  3. 해당되는 각 대체 리소스를 이 새 디렉터리에 저장하십시오. 이 리소스 파일은 기본 리소스 파일과 +똑같은 이름을 지정해야 합니다.
  4. +
+ +

예를 들어 다음은 기본 리소스와 대체 리소스입니다.

+ +
+res/
+    drawable/   
+        icon.png
+        background.png    
+    drawable-hdpi/  
+        icon.png
+        background.png  
+
+ +

{@code hdpi} 한정자는 해당 디렉터리의 리소스가 +고화질 화면 기기용이라는 것을 나타냅니다. 각 드로어블 디렉터리의 이미지는 특정 화면 화질에 맞추어 +크기가 지정되었으나 파일 이름은 +똑같습니다. 이렇게 하면 {@code icon.png} 또는 {@code +background.png} 이미지를 참조하는 데 사용하는 리소스 ID는 항상 같지만 Android가 각 리소스 중에서 현재 기기에 가장 잘 일치하는 +버전을 선택하게 됩니다. 이때 리소스 디렉터리 이름의 한정자를 기기 구성 정보와 +비교하는 방법을 씁니다.

+ +

Android는 여러 가지 구성 한정자를 지원하며 한 디렉터리 이름에 +여러 개의 한정자를 추가할 수 있습니다. 각 한정자를 대시로 구분하면 됩니다. 표 2는 +유효한 구성 한정자를 우선 순위대로 나열한 것입니다. 리소스 디렉터리에 여러 개의 +한정자를 사용하는 경우, 해당 한정자를 디렉터리 이름에 추가할 때 이 표에 나열된 것과 같은 +순서로 추가해야 합니다.

+ + +

표 2. 구성 한정자 +이름입니다.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
구성한정자 값설명
MCC 및 MNC예:
+ mcc310
+ mcc310-mnc004
+ mcc208-mnc00
+ 등. +
+

이동통신 국가 코드(MCC)에 선택적으로 이동통신 네트워크 코드(MNC)가 이어지는 형태로, +기기의 SIM 카드에서 가져옵니다. 예를 들어, mcc310은 모든 이동통신사를 포함한 미국이고, +mcc310-mnc004는 Verizon을 사용하는 미국, mcc208-mnc00은 Orange를 사용하는 +프랑스입니다.

+

기기가 무선 연결(GSM 전화)을 사용할 경우, MCC와 MNC 값은 +SIM 카드에서 가져옵니다.

+

MCC만 단독으로 사용할 수도 있습니다(예를 들어, 애플리케이션에 국가별 합법적 리소스를 + 포함하는 경우). 언어에 기초해서만 지정해야 할 경우, +언어 및 지역 한정자를 대신 사용합니다(아래에 설명). MCC와 +MNC 한정자를 사용할 경우, 조심해서 사용하고 예상한 대로 작동하는지 테스트해야 합니다.

+

또한, 구성 필드 {@link +android.content.res.Configuration#mcc}와 {@link +android.content.res.Configuration#mnc}를 참조하십시오. 이 구성 필드는 각각 이동통신 국가 코드와 +이동통신 네트워크 코드를 나타냅니다.

+
언어 및 지역예:
+ en
+ fr
+ en-rUS
+ fr-rFR
+ fr-rCA
+ 등. +

언어는 두 글자의 ISO +639-1 언어 코드로 정의되고, +뒤이어 두 글자의 ISO +3166-1-alpha-2 지역 코드가 선택적으로 따라옵니다(소문자 "{@code r}" 뒤에 붙음). +

+ 코드는 대소문자를 구별하지 않습니다. {@code r} 접두사는 +지역 부분을 구별하기 위해 사용합니다. + 지역만 지정할 수는 없습니다.

+

사용자가 시스템 설정에서 언어를 변경할 경우 +애플리케이션 수명 중에 변경될 수 있습니다. 런타임에서 애플리케이션에 어떤 영향을 미치는지 자세히 알아보려면 런타임 변경 처리를 참조하십시오. +

+

다른 여러 언어에 맞게 애플리케이션을 지역화하기 위한 전체 지침은 +지역화를 참조하십시오.

+

또한, 현재 로케일을 나타내는 {@link android.content.res.Configuration#locale} 구성 필드도 +참조하십시오.

+
레이아웃 방향ldrtl
+ ldltr
+

애플리케이션의 레이아웃 방향입니다. {@code ldrtl}는 "오른쪽에서 왼쪽 방향 레이아웃"을 나타냅니다. +{@code ldltr}는 "왼쪽에서 오른쪽 방향 레이아웃"을 나타내고 기본 암시적 값입니다. +

+

이는 레이아웃이나 드로어블, 값 등의 모든 리소스에 적용할 수 있습니다. +

+

예를 들어, 아랍어에 대한 특정 레이아웃을 제공하고 +다른 "오른쪽에서 왼쪽으로 쓰는" 언어(히브리어 또는 페르시아어)에 제네릭 레이아웃을 제공하고 싶다면 다음과 같이 해야 합니다. +

+
+res/
+    layout/   
+        main.xml  (Default layout)
+    layout-ar/  
+        main.xml  (Specific layout for Arabic)
+    layout-ldrtl/  
+        main.xml  (Any "right-to-left" language, except
+                  for Arabic, because the "ar" language qualifier
+                  has a higher precedence.)
+
+

참고: 앱에서 오른쪽에서 왼쪽 레이아웃 기능을 활성화하려면 +{@code +supportsRtl}을 {@code "true"}로 설정하고 {@code targetSdkVersion}을 17 이상으로 설정해야 합니다.

+

API 레벨 17에서 추가되었습니다.

+
smallestWidthsw<N>dp

+ 예:
+ sw320dp
+ sw600dp
+ sw720dp
+ 등. +
+

화면의 기본 크기로, 사용 가능한 화면 영역의 가장 짧은 치수가 +나타냅니다. 구체적으로 기기의 smallestWidth는 해당 화면의 이용 가능한 높이와 너비의 +가장 짧은 치수를 말합니다(이것을 화면에 대한 "가능한 한 가장 좁은 너비"로 생각해도 됩니다). 이 한정자를 사용하면 +화면의 현재 방향에 관계 없이 +애플리케이션이 해당 UI에서 이용 가능한 너비 중 최소 {@code <N>}dps를 확보하도록 할 수 있습니다.

+

예를 들어, 레이아웃에 언제나 +600dp 이상의 화면 최소 치수가 필요하다면, 이 한정자를 사용하여 레이아웃 리소스, {@code +res/layout-sw600dp/}를 만들 수 있습니다. 시스템이 이러한 리소스를 사용하는 것은 사용 가능한 화면의 최소 치수가 적어도 600dp가 +되는 경우뿐이며, 이때 600dp라는 크기가 사용자 쪽에서 보기에 높이이든 너비이든 +관계 없습니다. 이 smallestWidth는 기기의 고정된 화면 크기 특성입니다. +기기의 smallestWidth는 화면 방향이 변경되어도 바뀌지 않습니다.

+

기기의 smallestWidth는 화면 장식과 시스템 UI를 감안합니다. 예를 들어, +화면 상에서 최소 너비의 축 주변 공간을 차지하는 영구 UI 요소가 있다면, +시스템은 smallestWidth를 실제 화면 크기보다 작게 선언합니다. +이것은 개발자의 UI가 사용할 수 없는 화면 픽셀이기 때문입니다. 따라서 개발자가 사용하는 값은 +레이아웃에서 요구하는 실제 최소 치수여야 합니다(일반적으로 이 값은 화면의 현재 방향과 관계없이 +레이아웃이 지원하는 "최소 너비"가 됩니다.).

+

다음의 몇몇 값은 보편적인 화면 크기에 대하여 사용할 수 있습니다.

+
    +
  • 화면 크기 320에 화면 구성이 아래와 같은 기기: +
      +
    • 240x320ldpi(QVGA 핸드셋)
    • +
    • 320x480mdpi(핸드셋)
    • +
    • 480x800hdpi(고화질 핸드셋)
    • +
    +
  • +
  • 480x800mdpi(태블릿/핸드셋) 등의 화면에는 480을 사용합니다.
  • +
  • 600x1024mdpi (7인치 태블릿) 등의 화면에는 600을 사용합니다.
  • +
  • 720x1280mdpi (10인치 태블릿) 등의 화면에는 720을 사용합니다.
  • +
+

애플리케이션이 +smallestWidth 한정자의 여러 값이 포함된 여러 리소스 디렉터리를 제공하면, 시스템은 +기기의 smallestWidth에 가장 가까운(그러나 이를 초과하지 않는) 값을 사용합니다.

+

API 레벨 13에서 추가되었습니다.

+

이외에도 애플리케이션과 호환되는 최소한의 smallestWidth를 선언하는 {@code +android:requiresSmallestWidthDp} 속성과 +기기의 smallestWidth 값을 보유한 {@link +android.content.res.Configuration#smallestScreenWidthDp} +구성 필드도 참조하십시오.

+

여러 가지 화면에 맞는 디자인과 한정자 사용에 관한 자세한 정보는 +다중 화면 +지원 개발자 가이드를 참조하십시오.

+
이용 가능한 너비w<N>dp

+ 예:
+ w720dp
+ w1024dp
+ 등. +
+

리소스를 사용해야 하는 {@code dp} 단위에서 최소 이용 가능한 화면 너비를 지정합니다. +이는 <N> 값이 정의합니다. 이 구성 +값은 현재 실제 너비에 맞추기 위해 화면 방향이 가로와 세로 사이를 오가며 바뀔 때 +변경됩니다.

+

애플리케이션이 이 구성에 대해 서로 다른 값이 포함된 여러 개의 리소스 디렉터리를 제공하면, +시스템은 기기의 현재 화면 너비에 가장 가까운(그러나 이를 초과하지 않는) +값을 사용합니다. 이 값은 +화면 장식을 감안한 것이므로, 기기의 왼쪽이나 오른쪽 가장자리에 +영구 UI 요소가 있을 경우, 기기는 +이러한 UI 요소를 감안하여 애플리케이션의 이용 가능한 공간을 줄여서 + 실제 화면 크기보다 작은 너비 값을 사용합니다.

+

API 레벨 13에서 추가되었습니다.

+

현재 화면 너비를 보유한 {@link android.content.res.Configuration#screenWidthDp} + 구성 필드도 참조하십시오.

+

여러 가지 화면에 맞는 디자인과 한정자 사용에 관한 자세한 정보는 +다중 화면 +지원 개발자 가이드를 참조하십시오.

+
이용 가능한 높이h<N>dp

+ 예:
+ h720dp
+ h1024dp
+ 등. +
+

리소스가 사용되어야 하는 최소한의 사용 가능한 화면 높이를 "dp" 단위로 나타냅니다. +이는 <N> 값이 정의합니다. 이 구성 +값은 현재 실제 높이에 맞추기 위해 화면 방향이 가로와 세로 사이를 오가며 바뀔 때 +변경됩니다.

+

애플리케이션이 이 구성에 대해 서로 다른 값이 포함된 여러 개의 리소스 디렉터리를 제공하면, +시스템은 기기의 현재 화면 높이에 가장 가까운(그러나 이를 초과하지 않는) +값을 사용합니다. 이 값은 +화면 장식을 감안한 것이므로, 기기의 상단이나 하단 가장자리에 +영구 UI 요소가 있을 경우, 기기는 +이러한 UI 요소를 감안하여 애플리케이션의 이용 가능한 공간을 줄여서 + 실제 화면 크기보다 작은 높이 값을 사용합니다. +상태 표시줄에 고정되지 않은 화면 장식(예를 들어 +전화 상태 표시줄은 전체 화면에서 숨길 수 있음)은 여기에서 감안하지 않았고, +제목 표시줄이나 작업 모음 등의 창 장식도 감안되지 않았으므로, 애플리케이션 입장에서는 자신이 지정한 것보다 어느 정도 작은 공간을 +받아들일 대비를 해야 합니다. +

API 레벨 13에서 추가되었습니다.

+

현재 화면 너비를 보유한 {@link android.content.res.Configuration#screenHeightDp} + 구성 필드도 참조하십시오.

+

여러 가지 화면에 맞는 디자인과 한정자 사용에 관한 자세한 정보는 +다중 화면 +지원 개발자 가이드를 참조하십시오.

+
화면 크기 + small
+ normal
+ large
+ xlarge +
+
    +
  • {@code small}: 저밀도 QVGA 화면과 비슷한 +크기의 화면입니다. 작은 화면의 최소 레이아웃 크기는 +약 320x426 dp단위입니다. 이 화면의 예시로는 QVGA 저밀도 및 VGA 고밀도가 +있습니다.
  • +
  • {@code normal}: 중밀도 HVGA 화면과 +비슷한 크기의 화면입니다. 정상 화면의 + 최소 레이아웃 크기는 약 320x470 dp 단위입니다. 이 화면의 예로는 +WQVGA 저밀도, HVGA 중밀도, WVGA 고밀도 등이 있습니다. +
  • +
  • {@code large}: 중밀도 VGA 화면과 +비슷한 크기의 화면입니다. + 큰 화면의 최소 레이아웃 크기는 약 480x640 dp 단위입니다. + 이 화면의 예시로는 VGA 및 WVGA 중밀도 화면이 있습니다.
  • +
  • {@code xlarge}: 일반적인 중밀도 HVGA 화면보다 상당히 큰 화면을 +말합니다. 초대형 화면의 + 최소 레이아웃 크기는 약 720x960 dp 단위입니다. 대부분의 경우, 초대형 화면 기기는 +주머니에 넣어 다니기에 너무 큽니다. 따라서 태블릿 스타일의 기기일 가능성이 +높습니다. API 레벨 9에서 추가되었습니다.
  • +
+

참고: 크기 한정자를 사용하더라도 해당 리소스가 그 크기의 화면 +전용이라는 뜻은 아닙니다. 현재 기기 구성과 더욱 잘 맞는 한정자가 포함된 대체 리소스를 +제공하지 않으면, +시스템이 가장 잘 일치하는 리소스를 사용합니다.

+

주의: 모든 리소스가 현재 화면보다 + 크기 한정자를 사용하는 경우, 시스템은 리소스를 사용하지 않으며 애플리케이션은 런타임에 작동이 중단됩니다(예를 들어, 모든 레이아웃 리소스에 {@code +xlarge} 한정자가 태그되어 있지만 기기는 일반 크기 화면일 경우 +).

+

API 레벨 4에서 추가되었습니다.

+ +

자세한 정보는 다중 화면 +지원을 참조하십시오.

+

{@link android.content.res.Configuration#screenLayout} 구성 필드도 참조하십시오. +이것은 화면이 소형, 일반 크기 또는 +대형인지를 나타냅니다.

+
화면 비율 + long
+ notlong +
+
    +
  • {@code long}: WQVGA, WVGA, FWVGA 등의 긴 화면
  • +
  • {@code notlong}: QVGA, HVGA 및 VGA 등의 길지 않은 화면
  • +
+

API 레벨 4에서 추가되었습니다.

+

이것은 순전히 화면 비율에만 기초합니다("긴" 화면이 더 넓습니다). 이는 +화면 방향과 관계가 없습니다.

+

{@link android.content.res.Configuration#screenLayout}도 참조하십시오. +이것은 화면이 긴 화면인지 여부를 나타냅니다.

+
화면 방향 + port
+ land +
+
    +
  • {@code port}: 기기가 세로 방향(수직)입니다.
  • +
  • {@code land}: 기기가 가로 방향(수평)입니다.
  • + +
+

이것은 사용자가 화면을 돌리는 경우 애플리케이션 수명 중에 +변경될 수 있습니다. 이것이 런타임에 애플리케이션에 어떤 영향을 미치는지 알아보려면 +런타임 변경 처리를 참조하십시오.

+

{@link android.content.res.Configuration#orientation} 구성 필드도 참조하십시오. +이는 현재 기기 방향을 나타냅니다.

+
UI 모드 + car
+ desk
+ television
+ appliance + watch +
+
    +
  • {@code car}: 기기가 차량용 도크에서 표시되고 있습니다.
  • +
  • {@code desk}: 기기가 데스크용 도크에서 표시되고 있습니다.
  • +
  • {@code television}: 기기가 텔레비전에서 표시되고 있으며, +UI가 큰 화면에 있고 사용자가 여기에서 멀리 떨어져 있는 +"텐 풋(ten foot)" 환경을 제공하고 있습니다. 이는 주로 DPAD 또는 +기타 비-포인터 상호 작용 주변을 가리킵니다.
  • +
  • {@code appliance}: 기기가 가전 제품 역할을 하고 있으며, 디스플레이 +화면이 없습니다.
  • +
  • {@code watch}: 기기에 디스플레이 화면이 있고 손목에 착용됩니다.
  • +
+

API 레벨 8에서 추가되었고, 텔레비전은 API 13에서, 시계는 API 20에서 추가되었습니다.

+

기기가 도크에 삽입되거나 제거될 때 앱이 응답하는 방식에 관한 정보는 +도킹 상태 및 유형 +판별과 모니터링을 읽어보십시오.

+

이것은 사용자가 기기를 도크에 놓는 경우 애플리케이션 수명 중에 +변경될 수 있습니다. 이러한 모드 중 몇 가지는 {@link +android.app.UiModeManager}를 사용하여 활성화 또는 비활성화할 수 있습니다. 이것이 런타임에 애플리케이션에 어떤 영향을 미치는지 알아보려면 런타임 변경 처리를 +참조하십시오.

+
야간 모드 + night
+ notnight +
+
    +
  • {@code night}: 야간
  • +
  • {@code notnight}: 주간
  • +
+

API 레벨 8에서 추가되었습니다.

+

이것은 야간 모드가 자동 모드인 상태(기본)에서 애플리케이션의 수명 중에 +변경될 수 있습니다. 이 경우 모드는 하루 중 시간대를 기반으로 변경됩니다. 이 모드는 +{@link android.app.UiModeManager}를 사용하여 활성화 또는 비활성화할 수 있습니다. 이것이 런타임 중 애플리케이션에 어떤 영향을 미치는지 알아보려면 런타임 변경 처리를 +참조하십시오.

+
화면 픽셀 밀도(dpi) + ldpi
+ mdpi
+ hdpi
+ xhdpi
+ xxhdpi
+ xxxhdpi
+ nodpi
+ tvdpi +
+
    +
  • {@code ldpi}: 저밀도 화면, 약 120dpi.
  • +
  • {@code mdpi}: 중밀도(일반적인 HVGA에서) 화면, 약 +160dpi.
  • +
  • {@code hdpi}: 고밀도 화면, 약 240dpi.
  • +
  • {@code xhdpi}: 초고밀도 화면, 약 320dpi. API 레벨 8에서 +추가되었습니다.
  • +
  • {@code xxhdpi}: 슈퍼 초고밀도 화면, 약 480dpi. API 레벨 16에서 +추가되었습니다.
  • +
  • {@code xxxhdpi}: 울트라 슈퍼 초고밀도 화면 사용(시작 관리자 아이콘만 해당, +다중 화면 지원의 +참고를 참조하십시오), 약 640dpi. API 레벨 18에서 +추가되었습니다.
  • +
  • {@code nodpi}: 이것은 기기 밀도에 일치하도록 크기를 조정하고자 하지 않는 비트맵 리소스에 +사용할 수 있습니다.
  • +
  • {@code tvdpi}: Mdpi와 hdpi 사이 어딘가에 해당되는 화면, 약 213dpi. 이것은 +"기본" 밀도 그룹으로 간주되지 않습니다. 이는 대체로 텔레비전용으로 만들어진 것이며 +대부분의 앱에는 필요하지 않는 것이 정상입니다. mdpi 및 hdpi만 제공하면 대부분의 앱에는 충분하고 +시스템이 필요에 따라 크기를 조정해줍니다. 이 한정자는 API 레벨 13과 함께 도입되었습니다.
  • +
+

여섯 가지 기본 밀도간에 3:4:6:8:12:16 비율 척도가 있습니다(tvdpi 밀도는 +무시). 그러므로 ldpi의 9x9 비트맵은 mdpi에서 12x12이고, hdpi에서 18x18, xhdpi에서 24x24 등, 이런 식으로 적용됩니다. +

+

이미지 리소스가 텔레비전이나 특정 기기에서 제대로 보이지 않는다고 결정하고 +tvdpi 리소스를 사용하려 할 경우, 배율은 1.33*mdpi입니다. 예를 들어, +mdpi 화면의 100px x 100px 이미지는 tvdpi에서 133px x 133px가 되어야 합니다.

+

참고: 밀도 한정자를 사용하더라도 해당 리소스가 그 밀도의 화면 +전용이라는 뜻은 아닙니다. 현재 기기 구성과 더욱 잘 맞는 한정자가 포함된 대체 리소스를 +제공하지 않으면, +시스템이 가장 잘 일치하는 리소스를 사용합니다.

+

다양한 화질을 처리하는 방법과 +Android가 현재 화질에 맞춰 비트맵을 축소하는 방법에 관한 자세한 정보는 다중 화면 +지원을 참조하십시오.

+
터치 스크린 유형 + notouch
+ finger +
+
    +
  • {@code notouch}: 기기에 터치 스크린이 없습니다.
  • +
  • {@code finger}: 기기에 터치 스크린이 있으며 이를 +사용자의 손가락을 사용한 방향 지시 상호 작용을 통해 쓰도록 되어 있습니다.
  • +
+

{@link android.content.res.Configuration#touchscreen} 구성 필드도 참조하십시오. +이는 기기에서 사용되는 터치 스크린의 유형을 나타냅니다.

+
키보드 가용성 + keysexposed
+ keyshidden
+ keyssoft +
+
    +
  • {@code keysexposed}: 기기에서 키보드를 사용할 수 있습니다. 키보드에 +소프트웨어 키보드가 활성화되어 있으면(이럴 가능성이 큽니다), 하드웨어 키보드가 사용자에게 노출되어 있지 +않거나 기기에 하드웨어 키보드가 없더라도 이 리소스를 사용할 수 있습니다. 소프트웨어 +키보드가 제공되어 있지 않거나 비활성화되어 있는 경우 이것은 하드웨어 키보드가 노출되어 있을 때에만 +사용할 수 있습니다.
  • +
  • {@code keyshidden}: 기기에서 하드웨어 키보드를 사용할 수 있지만 +숨겨져 있고 이에 더하여 기기에 소프트웨어 키보드가 활성화되어 있지 않습니다.
  • +
  • {@code keyssoft}: 기기에 활성화된 소프트웨어 키보드가 있습니다(표시 여부는 +무관).
  • +
+

keysexposed 리소스를 제공하지만 keyssoft +리소스는 제공하지 않는다면, 시스템은 소프트웨어 키보드가 활성화되어 있는 동안은 키보드가 보이는지 여부와 관계없이 keysexposed +리소스를 사용합니다.

+

이것은 사용자가 하드웨어 키보드를 여는 경우 애플리케이션 수명 중에 +변경될 수 있습니다. 이것이 런타임에 애플리케이션에 어떤 영향을 미치는지 알아보려면 런타임 변경 처리를 +참조하십시오.

+

또한, 구성 필드 {@link +android.content.res.Configuration#hardKeyboardHidden}과 {@link +android.content.res.Configuration#keyboardHidden}을 참조하십시오. 이 필드는 각각 하드웨어 키보드의 가시성과 +모든 종류의 키보드(소프트웨어 포함)의 가시성을 나타냅니다.

+
기본 텍스트 입력 메서드 + nokeys
+ qwerty
+ 12key +
+
    +
  • {@code nokeys}: 기기에 텍스트 입력을 위한 하드웨어 키가 없습니다.
  • +
  • {@code qwerty}: 기기에 하드웨어 쿼티 키보드가 있습니다(이것이 +사용자 +에게 표시되는지 여부는 무관).
  • +
  • {@code 12key}: 기기에 하드웨어 12-키 키보드가 있습니다(이것이 사용자에게 표시되는지 여부는 +무관).
  • +
+

{@link android.content.res.Configuration#keyboard} 구성 필드도 참조하십시오. +이는 기본 텍스트 입력 메서드를 사용할 수 있는지 여부를 나타냅니다.

+
플랫폼 버전(API 레벨)예:
+ v3
+ v4
+ v7
+ 등.
+

기기에서 지원하는 API 레벨입니다. 예를 들어, v1는 API 레벨 +1용이고(Android 1.0 이상 기기), v4는 API 레벨 4용(Android +1.6 이상 기기)입니다. 이러한 값에 관한 자세한 정보는 Android API 레벨 문서를 +참조하십시오.

+
+ + +

참고: 일부 구성 한정자는 Android +1.0 이후부터 추가되었으므로 모든 Android 버전이 모든 한정자를 지원하는 것은 아닙니다. 새로운 한정자를 사용하면 암시적으로 +플랫폼 버전 한정자도 추가하므로 구형 기기가 이를 무시하게 됩니다. 예를 들어 +w600dp 한정자를 사용하면 자동적으로 v13 한정자를 포함합니다. +사용 가능한 너비 한정자가 API 레벨 13부터 새로 도입되었기 때문입니다. 문제를 애초에 피하려면, 항상 +기본 리소스를 한 세트 포함하세요(한정자 없는 리소스 한 세트). 자세한 정보는 +리소스와 연관된 최선의 기기 호환성 제공 +을 참조하십시오.

+ + + +

한정자 이름 규칙

+ +

다음은 구성 한정자 이름 사용에 관한 규칙입니다.

+ +
    +
  • 한 가지 리소스 세트에 여러 개의 한정자를 사용할 수 있으며, 이를 대시로 구분하면 됩니다. 예를 들어, +drawable-en-rUS-land는 수평 방향의 +US-English 기기에 적용합니다.
  • +
  • 한정자는 표2에 나열된 순서를 따라야 합니다. 예: + +
      +
    • 잘못된 배열: drawable-hdpi-port/
    • +
    • 맞는 배열: drawable-port-hdpi/
    • +
    +
  • +
  • 대체 리소스 디렉터리는 중첩될 수 없습니다. 예를 들어, +res/drawable/drawable-en/는 있을 수 없습니다.
  • +
  • 값은 대소문자를 구분하지 않습니다. 리소스 컴파일러가 처리 전에 디렉터리 이름을 +소문자로 바꿔 대소문자를 구분하지 않는 +파일 시스템에서 문제를 일으키지 않도록 방지합니다. 이름에 대문자가 있는 것은 오로지 가독성을 향상하기 위해서입니다.
  • +
  • 각 한정자 유형마다 한 개의 값만 지원됩니다. 예를 들어, 스페인과 프랑스에 +같은 드로어블 파일을 사용하고자 하는 경우 디렉터리 이름이 +drawable-rES-rFR/이면 안 됩니다. 대신 +drawable-rES/drawable-rFR/ 같이 적절한 파일이 포함된 두 개의 리소스 디렉터리가 필요합니다. +그러나 두 위치에서 같은 파일을 실제로 복제할 필요는 없습니다. 대신 +리소스에 별명을 만들면 됩니다. 아래의 별명 리소스 +생성을 참조하십시오.
  • +
+ +

이런 한정자로 이름을 지은 디렉터리에 대체 리소스를 저장하고 나면 +Android가 현재 기기 구성에 기초하여 애플리케이션에 자동으로 리소스를 적용합니다. + 리소스가 요청될 때마다 Android가 요청한 리소스 파일이 들어있는 +대체 리소스 디렉터리를 확인하고, 그런 다음 가장 잘 일치하는 +리소스를 찾습니다(아래에서 논함). 특정 기기 구성에 일치하는 대체 리소스가 없는 경우, +Android는 상응하는 기본 리소스(구성 한정자를 포함하지 않는 +특정 리소스 유형에 대한 리소스 세트 +)를 사용합니다.

+ + + +

별명 리소스 생성

+ +

어떤 리소스를 하나 이상의 기기 구성에서 사용하고자 하는 경우(그렇지만 +이를 기본 리소스를 제공하는 것은 원치 않는 경우), 같은 리소스를 하나 이상의 대체 리소스 디렉터리에 +넣지 않아도 됩니다. 대신, 기본 리소스 디렉터리에 저장된 리소스에 대해 별명 역할을 하는 +대체 +리소스를 만들면 됩니다(경우에 따라).

+ +

참고: 모든 리소스가 다른 리소스에 대한 +별명을 생성할 수 있는 메커니즘을 제공하는 것은 아닙니다. 특히, {@code xml/} 디렉터리의 애니메이션, 메뉴, 원시 및 기타 지정되지 않은 +리소스는 이 기능을 제공하지 않습니다.

+ +

예를 들어, 애플리케이션 아이콘 {@code icon.png}이 있고 서로 다른 로케일에서 이 아이콘의 고유 버전이 +필요한 경우가 있습니다. 그러나 두 로케일, English-Canadian과 French-Canadian은 같은 버전을 +사용해야 합니다. 같은 이미지를 English-Canadian과 French-Canadian 양쪽 +모두에 대한 리소스 디렉터리에 복사해야 한다고 생각할 수 있지만, 실은 +그렇지 않습니다. 대신, 두 로케일에서 사용하는 이미지를 {@code icon_ca.png}( +{@code icon.png} 이외에 어떤 이름이든 가능)로 저장하고 이를 +기본 {@code res/drawable/} 디렉터리에 넣으면 됩니다. 그런 다음 {@code icon.xml} 파일을 {@code icon_ca.png} +리소스를 참조하는 {@code +res/drawable-en-rCA/} 및 {@code res/drawable-fr-rCA/}로 생성합니다. 이때, {@code <bitmap>} 요소를 사용하면 됩니다. 이렇게 하면 +PNG 파일 버전 하나와 그것을 가리키는 작은 XML 파일 두 개만 저장할 수 있습니다. (XML 파일 예시는 아래와 같습니다.)

+ + +

드로어블

+ +

기존 드로어블에 별명을 생성하려면 {@code <bitmap>} 요소를 사용합니다. +예를 들면 다음과 같습니다.

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
+    android:src="@drawable/icon_ca" />
+
+ +

파일을 ( +{@code res/drawable-en-rCA/}와 같은 대체 리소스 디렉터리에) {@code icon.xml}로 저장하면, +{@code R.drawable.icon}으로 참조할 수 있지만 실제로는 {@code +R.drawable.icon_ca} 리소스({@code res/drawable/}에 저장됨)의 별명인 리소스로 컴파일링됩니다.

+ + +

레이아웃

+ +

기존 레이아웃에 별명을 생성하려면 {@code <merge>}로 래핑되어 있는 {@code <include>} +요소를 사용합니다. 예:

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<merge>
+    <include layout="@layout/main_ltr"/>
+</merge>
+
+ +

파일을 {@code main.xml}로 저장하면, {@code R.layout.main}으로 참조할 수 있지만 실제로는 {@code R.layout.main_ltr} + 리소스의 별명인 리소스로 +컴파일링됩니다.

+ + +

문자열 및 기타 단순 값

+ +

기존 문자열에 별명을 생성하려면 원하는 문자열의 리소스 ID를 +새 문자열의 값으로 사용하면 됩니다. 예:

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <string name="hello">Hello</string>
+    <string name="hi">@string/hello</string>
+</resources>
+
+ +

이제 {@code R.string.hi} 리소스는 {@code R.string.hello}의 별명입니다.

+ +

기타 단순 값도 같은 방식으로 +동작합니다. 예를 들면 색상은 다음과 같습니다.

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <color name="yellow">#f00</color>
+    <color name="highlight">@color/red</color>
+</resources>
+
+ + + + +

리소스와 연관된 최선의 기기 호환성 제공

+ +

애플리케이션이 여러 기기 구성을 지원하게 하려면, +언제나 애플리케이션이 사용하는 각 유형의 리소스에 기본 리소스를 제공하는 것이 매우 중요합니다.

+ +

예를 들어 애플리케이션이 여러 언어를 지원할 경우, 항상 언어 및 지역 한정자 없이 {@code +values/} 디렉터리(여기에 문자열 저장)를 포함시켜야 합니다. 그렇게 하지 않고 언어와 지역 한정자가 있는 디렉터리에 +모든 문자열을 넣으면, 문자열이 지원하지 않는 언어로 설정된 기기에서 애플리케이션을 +실행하면 작동이 중단됩니다. 그러나 기본 +{@code values/} 리소스를 제공하는 한은 애플리케이션이 제대로 실행됩니다(사용자가 이해하지 못하는 +언어로라도 작동합니다. 작동 중단보다 낫습니다.)

+ +

마찬가지로 화면 방향에 기초하여 여러 가지 레이아웃 리소스를 제공하는 경우 +하나의 방향을 기본값으로 선택해야 합니다. 예를 들어 가로 방향에는 {@code +layout-land/}로, 세로 방향에는 {@code layout-port/}로 레이아웃 리소스를 제공하는 대신 하나를 기본으로 남겨두십시오. +가로 방향에 {@code layout/}, 세로 방향에 {@code layout-port/}와 같은 식으로 하면 됩니다.

+ +

애플리케이션이 예상치 못한 구성에서 실행될 수 있을 뿐만 아니라 +Android의 새 버전에서 이전 버전에서는 지원하지 않는 구성 한정자를 추가할 수도 있으므로, +기본 리소스를 제공하는 것이 중요합니다. 새 리소스 한정자를 사용하지만, +Android 이전 버전과 코드 호환성은 유지한 경우, 그 후 Android 이전 버전이 +애플리케이션을 실행하면 새로운 한정자로 이름을 지정한 리소스를 사용할 수 없으므로 기본 리소스를 제공하지 않으면 +애플리케이션 작동이 중단됩니다. 예를 들어, {@code +minSdkVersion}이 4로 설정되고 야간 모드(API 레벨 8에서 추가된 {@code night} 또는 {@code notnight})를 사용하는 모든 드로어블 리소스를 한정할 경우, API 레벨 4 기기는 +드로어블 리소스에 액세스하지 못하고 사용이 중단됩니다. 이 경우, +{@code notnight}를 기본 리소스로 제공하는 것이 좋습니다. 그래야 해당 한정자를 배제하고 +드로어블 리소스가 {@code drawable/} 또는 {@code drawable-night/}이 됩니다.

+ +

그러므로 최선의 기기 호환성을 제공하려면 언제나 +애플리케이션에서 반드시 제대로 수행해야 하는 리소스에 대해 기본 리소스를 제공하십시오. 그런 다음 구성 한정자를 사용하여 +특정 기기 구성에 대해 대체 리소스를 생성하면 됩니다.

+ +

이 규칙에는 한 가지 예외가 있습니다. 애플리케이션의 {@code minSdkVersion} 이 4 이상이면 +화면 밀도 한정자로 대체 드로어블 리소스를 제공할 때 기본 드로어블 +리소스가 없어도 됩니다. 기본 +드로어블 리소스가 없더라도 Android가 대체 화면 화질 중에서 가장 잘 맞는 리소스를 찾고 +필요에 따라 비트맵을 축소합니다. 그러나 모든 유형의 기기에서 최상의 경험을 제공하려면, +모든 세 가지 유형의 밀도에 대해 대체 드로어블을 제공해야 합니다.

+ + + +

Android가 가장 잘 일치하는 리소스를 찾는 방법

+ +

개발자가 자신이 대체를 제공하는 리소스를 요청하면 Android가 런타임에 어느 대체 리소스를 +사용할지 현대 기기 구성에 따라 여러 가지로 선택합니다. Android가 +대체 리소스를 선택하는 방법을 보여주기 위해 다음 드로어블 디렉터리에 각각 같은 이미지의 +서로 다른 버전이 들어있다고 가정하겠습니다.

+ +
+drawable/
+drawable-en/
+drawable-fr-rCA/
+drawable-en-port/
+drawable-en-notouch-12key/
+drawable-port-ldpi/
+drawable-port-notouch-12key/
+
+ +

그리고 기기 구성은 다음과 같다고 가정해봅시다.

+ +

+로케일 = en-GB
+화면 방향 = port
+화면 픽셀 밀도 = hdpi
+터치 스크린 유형 = notouch
+기본 텍스트 입력 메서드 = 12key +

+ +

Android는 기기 구성을 이용 가능한 대체 리소스와 비교하여, +{@code drawable-en-port}에서 드로어블을 선택합니다.

+ +

시스템은 다음과 같은 논리에 따라 +어느 리소스를 사용할지 결정합니다.

+ + +
+ +

그림 2. Android가 가장 잘 일치하는 리소스를 찾는 방법을 나타낸 +흐름도입니다.

+
+ + +
    +
  1. 기기 구성과 충돌하는 리소스 파일을 제거합니다. +

    drawable-fr-rCA/는 +en-GB 로케일과 충돌하므로 제거되었습니다.

    +
    +drawable/
    +drawable-en/
    +drawable-fr-rCA/
    +drawable-en-port/
    +drawable-en-notouch-12key/
    +drawable-port-ldpi/
    +drawable-port-notouch-12key/
    +
    +

    예외: 화면 픽셀 밀도 한정자 하나만은 충돌을 이유로 제거되지 +않습니다. 기기의 화면 밀도가 hdpi라도, +현 시점에서는 모든 화면 밀도가 일치로 간주되므로 drawable-port-ldpi/를 제거하지 않습니다. + 자세한 정보는 다중 화면 +지원 문서에서 이용하실 수 있습니다.

  2. + +
  3. 목록(표2)에서 (그 다음으로) 우선 순위가 가장 높은 한정자를 선택합니다 +(MCC부터 시작하여 아래로 내려가십시오).
  4. +
  5. 리소스 디렉터리 중에 이 한정자를 포함한 것이 있나요?
  6. +
      +
    • 없는 경우, 2단계로 돌아가 다음 한정자를 살펴보십시오 (이 예시의 경우 +언어 한정자에 도달할 때까지 답은 "없습니다"입니다).
    • +
    • 있는 경우, 4단계로 계속 진행합니다.
    • +
    + + +
  7. 이 한정자를 포함하지 않는 디렉터리를 제거합니다. 이 예시에서는 시스템이 +언어 한정자를 포함하지 않는 디렉터리를 모두 제거합니다.
  8. +
    +drawable/
    +drawable-en/
    +drawable-en-port/
    +drawable-en-notouch-12key/
    +drawable-port-ldpi/
    +drawable-port-notouch-12key/
    +
    +

    예외: 문제의 한정자가 화면 픽셀 밀도라면, +Android는 기기 화면 밀도와 가장 가깝게 일치하는 옵션을 선택합니다. +일반적으로, Android는 작은 원본 이미지를 확대하는 것보다 +큰 원본 이미지를 축소하는 것을 선호합니다. 다중 화면 +지원을 참조하십시오.

    + + +
  9. 뒤로 돌아가 디렉터리가 한 개만 남을 때까지 2, 3 및 4단계를 반복합니다. 이 예시에서, 일치하는 것이 있는 다음 한정자는 +화면 방향입니다. +그러므로 화면 방향을 지정하지 않는 리소스가 제거됩니다. +
    +drawable-en/
    +drawable-en-port/
    +drawable-en-notouch-12key/
    +
    +

    남은 디렉터리는 {@code drawable-en-port}입니다.

    +
  10. +
+ +

이 절차는 요청된 각 리소스에 대해 실행하지만, 시스템이 몇 가지 측면을 추가로 +최적화합니다. 그러한 최적화 가운데에는 일단 기기 구성을 알게 되고 나면 절대 일치할 가능성이 없는 +대체 리소스를 시스템이 제거할 수 있다는 점도 있습니다. 예를 들어, 구성 언어가 +영어("en")이고 영어 이외의 다른 언어 한정자로 설정된 리소스 디렉터리는 +절대 확인된 리소스 풀에 포함되지 않습니다( +언어 한정자가 포함되지 않는 리소스 디렉터리는 여전히 포함됩니다).

+ +

화면 크기 한정자에 기초하여 리소스를 선택할 때 시스템은 가장 잘 일치하는 리소스가 없다면 +현재 화면보다 작은 화면에 지정된 리소스를 사용합니다 +(예를 들어, 큰 화면은 필요에 따라 일반 크기 화면 리소스를 사용합니다). 그러나 +이용 가능한 리소스가 현재 화면보다 리소스뿐이라면, 시스템은 +이를 사용하지 않고, 기기 구성에 일치하는 리소스가 없으면 애플리케이션 사용이 중단됩니다 +(예를 들어 모든 레이아웃 리소스가 {@code xlarge} 한정자에 태그되어 있지만, +기기가 보통 크기 화면일 경우).

+ +

참고: 한정자의 우선 순위(표 2 참조)가 기기와 정확하게 일치하는 +한정자 수보다 더 중요합니다. 예를 들어 위의 4단계에서 +목록의 마지막 선택에 기기와 정확히 일치하는 한정자가 세 개 포함되어 있지만(방향, 터치 스크린 +유형 및 입력 메서드), drawable-en에는 일치하는 매개변수가 +하나뿐입니다(언어). 다만, 언어가 이러한 다른 한정자보다 우선 순위가 높기 때문에 +drawable-port-notouch-12key는 탈락합니다.

+ +

애플리케이션에서 리소스를 사용하는 것에 대한 자세한 정보는 리소스 액세스로 계속 진행하여 알아보십시오.

diff --git a/docs/html-intl/intl/ko/guide/topics/resources/runtime-changes.jd b/docs/html-intl/intl/ko/guide/topics/resources/runtime-changes.jd new file mode 100644 index 0000000000000000000000000000000000000000..a5e7f5bbad8830f0bcaa45cb2d6ffd0f3b77f3aa --- /dev/null +++ b/docs/html-intl/intl/ko/guide/topics/resources/runtime-changes.jd @@ -0,0 +1,281 @@ +page.title=런타임 변경 처리하기 +page.tags=액티비티, 수명 주기 +@jd:body + + + +

몇몇 기기 구성은 런타임 중에 변경될 수 있습니다 +(예: 화면 방향, 키보드 가용성 및 언어 등). 그러한 변경이 일어나는 경우, +Android는 실행 중인 +{@link android.app.Activity}를 다시 시작합니다({@link android.app.Activity#onDestroy()} 호출, 뒤이어 {@link +android.app.Activity#onCreate(Bundle) onCreate()} 호출). 이런 동작은 여러분이 제공한 +대체 리소스로 애플리케이션을 자동으로 다시 로딩함으로써 새로운 기기 구성에 애플리케이션이 적응하는 것을 돕도록 +설계되었습니다(예: 다양한 화면 방향과 크기에 대한 다양한 레이아웃).

+ +

다시 시작을 적절히 처리하려면 액티비티가 정상적인 +액티비티 +수명 주기를 통해 이전 상태를 복원하는 것이 중요합니다. 여기에서 Android는 +{@link android.app.Activity#onSaveInstanceState(Bundle) onSaveInstanceState()}를 호출한 다음에 액티비티를 소멸시켜 +애플리케이션 상태에 대한 데이터를 저장할 수 있습니다. 그러면 +{@link android.app.Activity#onCreate(Bundle) onCreate()} 또는 {@link +android.app.Activity#onRestoreInstanceState(Bundle) onRestoreInstanceState()} 중에 상태를 복원할 수 있습니다.

+ +

애플리케이션이 애플리케이션 상태를 그대로 유지한 채 스스로 다시 시작할 수 있는지 시험해 보려면, +구성 변경을 일으켜보아야 합니다(예를 들어 화면 방향 변경 등). 이는 애플리케이션에서 여러 가지 작업을 수행하는 +동안 해봅니다. 애플리케이션이 언제든 사용자 데이터나 상태를 손실하지 않고 +다시 시작할 수 있어야 합니다. 그래야 구성 변경과 같은 이벤트를 처리할 수 있기 때문입니다. 그렇지 않으면 +사용자가 걸려오는 전화를 받은 다음 한참 후에 애플리케이션으로 돌아오면 애플리케이션 프로세스가 이미 +소멸되어 있을 수 있습니다. 액티비티 상태를 복원하는 방법을 배우려면, 액티비티 수명 주기에 관해 읽어보십시오.

+ +

하지만, 애플리케이션을 다시 시작하고 상당량의 데이터를 복원하면 비용도 많이 들고 +불량한 사용자 환경이 만들어지는 상황에 직면할 수도 있습니다. 그러한 상황이라면, +두 가지 다른 옵션이 있습니다.

+ +
    +
  1. 구성 변경 중에 객체 보존하기 +

    구성이 변경되는 중에 액티비티가 다시 시작될 수 있게 허용하되, 액티비티의 새 인스턴스에 상태 +저장 객체를 넣습니다.

    + +
  2. +
  3. 구성 변경 직접 처리하기 +

    특정 구성 변경 중에 시스템이 액티비티를 다시 시작하도록 하지 못하게 방지하되, +구성이 실제로 변경되면 콜백을 수신하도록 하여 필요에 따라 액티비티를 수동으로 업데이트할 수 +있도록 합니다.

    +
  4. +
+ + +

구성 변경 중에 객체 보존하기

+ +

액티비티를 다시 시작하려면 많은 수의 데이터 세트를 복구해야 하는 경우, 네트워크 연결을 +다시 설정하거나 다른 집약적 작업을 수행한 다음 완전히 다시 시작하십시오. +구성 변경 때문에 사용자 환경이 느려질 수 있습니다. 또한, 액티비티 상태를 완전히 복원하려면 +{@link android.os.Bundle}을 사용할 수도 있습니다. 이것은 시스템이 개발자 대신 {@link +android.app.Activity#onSaveInstanceState(Bundle) onSaveInstanceState()} 콜백으로 저장해두는 것입니다. 이것은 대형 객체(예: 비트맵)를 담도록 +디자인된 것이 아니며 이 안의 데이터는 반드시 직렬화했다가 다시 역직렬화해야 합니다. 이렇게 하면 +메모리를 아주 많이 소모할 수 있으며 구성 변경이 느려질 수 있습니다. 이와 같은 상황에서는 +액티비티를 다시 초기화해야 한다는 부담을 해결하기 위해 액티비티가 구성 변경으로 인해 다시 시작되었을 때 {@link +android.app.Fragment}를 보존하면 됩니다. 이 프래그먼트에는 +보존하고자 하는 상태 저장 객체에 대한 참조를 담을 수 있습니다.

+ +

Android 시스템이 구성 변경으로 인하여 액티비티를 종료시킬 때, 액티비티에서 보존하기로 표시해둔 +프래그먼트는 소멸되지 않습니다. 그러한 프래그먼트를 액티비티에 추가하면 +상태 저장 객체를 보존할 수 있습니다.

+ +

런타임 구성 변경 중에 상태 저장 객체를 프래그먼트에 보존해두는 방법은 다음과 같습니다.

+ +
    +
  1. {@link android.app.Fragment} 클래스를 확장하고 상태 저장 +객체에 참조를 선언합니다.
  2. +
  3. 프래그먼트가 생성되면 {@link android.app.Fragment#setRetainInstance(boolean)}를 호출합니다. +
  4. +
  5. 해당 프래그먼트를 액티비티에 추가합니다.
  6. +
  7. {@link android.app.FragmentManager}를 사용하여 액티비티가 다시 시작될 때 프래그먼트를 +검색합니다.
  8. +
+ +

예를 들어 프래그먼트를 다음과 같이 정의합니다.

+ +
+public class RetainedFragment extends Fragment {
+
+    // data object we want to retain
+    private MyDataObject data;
+
+    // this method is only called once for this fragment
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        // retain this fragment
+        setRetainInstance(true);
+    }
+
+    public void setData(MyDataObject data) {
+        this.data = data;
+    }
+
+    public MyDataObject getData() {
+        return data;
+    }
+}
+
+ +

주의: 어느 객체든 저장할 수 있지만, +{@link android.app.Activity}에 묶여 있는 객체는 절대로 전달하면 안 됩니다. 예를 들어 {@link +android.graphics.drawable.Drawable}, {@link android.widget.Adapter}, {@link android.view.View} + 또는 {@link android.content.Context}와 연관된 기타 모든 객체가 이에 해당됩니다. 이런 것을 전달하면, +원래 액티비티 인스턴스의 모든 보기와 리소스를 몽땅 누출시킵니다. (리소스 누출이란 +애플리케이션이 리소스에 대한 보유권을 유지하고 있어 가비지 수집의 대상이 될 수 없고, 따라서 엄청난 양의 메모리가 +손실된다는 뜻입니다.)

+ +

그런 다음 {@link android.app.FragmentManager}를 사용하여 프래그먼트를 액티비티에 추가합니다. +프래그먼트에서 데이터 객체를 가져오려면 런타임 구성 변경 중에 액티비티가 다시 시작될 때 +가져오면 됩니다. 예를 들어, 액티비티를 다음과 같이 정의합니다.

+ +
+public class MyActivity extends Activity {
+
+    private RetainedFragment dataFragment;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.main);
+
+        // find the retained fragment on activity restarts
+        FragmentManager fm = getFragmentManager();
+        dataFragment = (DataFragment) fm.findFragmentByTag(“data”);
+
+        // create the fragment and data the first time
+        if (dataFragment == null) {
+            // add the fragment
+            dataFragment = new DataFragment();
+            fm.beginTransaction().add(dataFragment, “data”).commit();
+            // load the data from the web
+            dataFragment.setData(loadMyData());
+        }
+
+        // the data is available in dataFragment.getData()
+        ...
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+        // store the data in the fragment
+        dataFragment.setData(collectMyLoadedData());
+    }
+}
+
+ +

이 예시에서 {@link android.app.Activity#onCreate(Bundle) onCreate()}는 프래그먼트를 추가하거나 +이에 대한 참조를 복원합니다. {@link android.app.Activity#onCreate(Bundle) onCreate()} 또한 +프래그먼트 인스턴스 안에 상태 저장 객체를 저장합니다. +{@link android.app.Activity#onDestroy() onDestroy()}는 보존된 +프래그먼트 인스턴스 내부의 상태 저장 객체를 업데이트합니다.

+ + + + + +

구성 변경 직접 처리하기

+ +

애플리케이션이 특정 구성 변경 중에 리소스를 업데이트하지 않아도 되고 +그와 동시에 성능 한계가 있어 액티비티 다시 시작을 피해야 하는 경우, +액티비티가 구성 변경을 알아서 처리한다고 선언하면 됩니다. +이렇게 하면 시스템이 액티비티를 다시 시작하지 않도록 방지할 수 있습니다.

+ +

참고: 구성 변경을 직접 처리하면 대체 리소스를 사용하는 것이 +훨씬 더 까다로워질 수 있습니다. 시스템이 개발자 대신 자동으로 이를 적용해주지 않기 +때문입니다. 이 기법은 구성 변경으로 인한 재시작을 반드시 피해야만 하는 경우 최후의 수단으로서만 +고려해야 하며 대부분의 애플리케이션에는 권장하지 않습니다.

+ +

액티비티가 구성 변경을 직접 처리한다고 선언하려면, 매니페스트 파일의 적절한 {@code <activity>} 요소를 편집하여 +처리하고자 하는 구성을 나타내는 값이 있는 {@code +android:configChanges} 속성을 포함하도록 +합니다. 가능한 값은 {@code +android:configChanges} 속성에 대한 관련 문서에 목록으로 나열되어 있습니다(가장 보편적으로 사용되는 값은 화면 방향이 변경될 때 다시 시작을 방지하는 {@code "orientation"}과 +키보드 가용성이 변경될 때 다시 시작을 방지하는 {@code "keyboardHidden"} +입니다). 이 속성에는 여러 개의 구성 값을 선언할 수 있습니다. 각각을 +파이프 {@code |} 문자로 구분하면 됩니다.

+ +

예를 들어 다음 매니페스트 코드는 화면 방향 변경과 키보드 가용성 변경을 둘 다 +처리하는 액티비티를 선언하는 것입니다.

+ +
+<activity android:name=".MyActivity"
+          android:configChanges="orientation|keyboardHidden"
+          android:label="@string/app_name">
+
+ +

이제 이러한 구성 중 하나가 변경되어도 {@code MyActivity}는 다시 시작하지 않습니다. +그 대신, {@code MyActivity}가 {@link +android.app.Activity#onConfigurationChanged(Configuration) onConfigurationChanged()}로의 호출을 받습니다. 이 메서드는 +{@link android.content.res.Configuration} 객체로 전달되며, 이는 새 기기 구성을 +나타냅니다. {@link android.content.res.Configuration}의 필드를 읽어보면 +새 구성을 판별할 수 있고 적절한 변경을 할 수 있습니다. 그러려면 인터페이스에 사용된 리소스를 +업데이트하면 됩니다. 이 메서드가 +호출되면, 액티비티의 {@link android.content.res.Resources} 객체가 +업데이트되어 새 구성에 기반한 리소스를 반환하며, 따라서 시스템이 액티비티를 다시 시작하지 않아도 +UI의 요소를 손쉽게 재설정할 수 있게 됩니다.

+ +

주의: Android 3.2(API 레벨 13)부터 기기가 +세로 방향 및 가로 방향 사이를 전환할 때 "화면 크기"도 +같이 변경됩니다. 따라서, +API 레벨 13 이상({@code minSdkVersion}{@code targetSdkVersion} + 속성에서 선언한 내용에 따름)을 대상으로 개발하는 경우 방향 변경으로 인한 런타임 다시 시작을 방지하고자 하면, {@code +"orientation"} 값 외에 {@code "screenSize"} 값도 포함시켜야 합니다. 다시 말해, {@code +android:configChanges="orientation|screenSize"}를 선언해야 합니다. 하지만, 애플리케이션이 API 레벨 12 이하를 +대상으로 하는 경우라면 애플리케이션이 언제든 이 구성 변경을 알아서 처리합니다(이 구성 변경은 +액티비티를 다시 시작하지 않습니다. 이는 Android 3.2 이상 기기에서 실행되는 경우에도 마찬가지입니다).

+ +

예를 들어, 다음 {@link +android.app.Activity#onConfigurationChanged(Configuration) onConfigurationChanged()} 구현은 +현재 기기의 방향을 확인합니다.

+ +
+@Override
+public void onConfigurationChanged(Configuration newConfig) {
+    super.onConfigurationChanged(newConfig);
+
+    // Checks the orientation of the screen
+    if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
+        Toast.makeText(this, "landscape", Toast.LENGTH_SHORT).show();
+    } else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT){
+        Toast.makeText(this, "portrait", Toast.LENGTH_SHORT).show();
+    }
+}
+
+ +

{@link android.content.res.Configuration} 객체는 변경된 것만이 아니라 현재 +구성 전체를 나타냅니다. 대부분의 경우에는 구성이 정확히 어떻게 +변경되었는지에는 관심이 없고 처리 중인 구성에 대체 리소스를 제공하는 모든 리소스를 그저 +재할당하기만 하면 됩니다. 예를 들어 이제 {@link +android.content.res.Resources} 객체가 업데이트되었으니 {@link android.widget.ImageView#setImageResource(int) +setImageResource()}가 있는 모든 +{@link android.widget.ImageView}와 +새 구성에 대한 적절한 리소스를 재설정할 수 있습니다(리소스 제공에 설명된 바와 같음).

+ +

{@link +android.content.res.Configuration} 필드에서 가져온 값이 +{@link android.content.res.Configuration} 클래스에서 가져온 특정 상수와 일치하는 정수라는 점을 눈여겨 보십시오. 각 필드에 +어느 상수를 써야 하는지에 대한 관련 문서는 {@link +android.content.res.Configuration} 참조에 있는 적절한 필드를 참조하십시오.

+ +

명심할 점: 액티비티가 직접 구성 변경을 처리한다고 선언하는 경우, +대체를 제공하는 모든 요소에 대해 본인이 직접 책임을 지게 됩니다. 액티비티가 직접 +방향 변경을 처리하고 가로 및 세로 방향 사이에서 바뀌어야 하는 이미지가 있는 경우, +각 리소스를 각 요소에 재할당해야 하며 이를 {@link +android.app.Activity#onConfigurationChanged(Configuration) onConfigurationChanged()} 중에 수행해야 합니다.

+ +

이러한 구성 변경을 기반으로 애플리케이션을 업데이트하지 않아도 되는 경우, +대신 {@link +android.app.Activity#onConfigurationChanged(Configuration) onConfigurationChanged()}를 구현하지 않으면 됩니다. 이런 +경우, 구성 변경 전에 쓰였던 리소스가 모두 그대로 사용되고 액티비티의 다시 시작만 +피한 것이 됩니다. 그러나, 애플리케이션은 +언제든 종료되고 이전 상태를 그대로 유지한 채 다시 시작될 수 있어야 합니다 정상적인 액티비티 +수명 주기 중에 상태 유지에서의 탈출 방안으로 이 기법을 고려해서는 안 됩니다. 이는 애플리케이션이 +다시 시작되지 않도록 방지할 수 없는, 다른 구성 변경도 여럿 있어서일뿐만 아니라, 사용자가 +애플리케이션을 떠났을 경우 해당 사용자가 다시 돌아오기 전에 소멸되는 것과 같은 이벤트를 처리해야 하기 때문이라는 +이유도 있습니다.

+ +

액티비티 내에서 처리할 수 있는 구성 변경이 무엇인지에 대한 자세한 내용은 {@code +android:configChanges} 관련 문서와 {@link android.content.res.Configuration} +클래스를 참조하십시오.

diff --git a/docs/html-intl/intl/ko/guide/topics/ui/controls.jd b/docs/html-intl/intl/ko/guide/topics/ui/controls.jd new file mode 100644 index 0000000000000000000000000000000000000000..bf873980beb6abafb46104bf285714f04e62bc0f --- /dev/null +++ b/docs/html-intl/intl/ko/guide/topics/ui/controls.jd @@ -0,0 +1,90 @@ +page.title=입력 제어 +parent.title=사용자 인터페이스 +parent.link=index.html +@jd:body + +
+ +
+ +

입력 제어는 앱의 사용자 인터페이스에 있는 대화형 구성 요소입니다. +Android는 버튼, 텍스트 필드, 찾기 막대, 확인란, 확대 버튼, 전환 버튼 등과 같이 +UI에서 사용할 수 있도록 매우 다양한 제어를 제공합니다.

+ +

UI에 입력 제어를 추가하려면 단순히 XML 레이아웃에 XML 요소를 하나 추가하기만 하면 됩니다. +다음은 텍스트 필드와 버튼이 있는 레이아웃을 예시로 나타낸 것입니다.

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    android:orientation="horizontal">
+    <EditText android:id="@+id/edit_message"
+        android:layout_weight="1"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:hint="@string/edit_message" />
+    <Button android:id="@+id/button_send"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/button_send"
+        android:onClick="sendMessage" />
+</LinearLayout>
+
+ +

각 입력 제어는 특정한 입력 이벤트를 지원하므로, 사용자가 텍스트를 입력할 때 또는 버튼을 터치할 때 +이벤트를 처리할 수 있게 해줍니다.

+ + +

보편적인 제어

+

다음은 앱에서 사용할 수 있는 몇 가지 보편적인 제어를 목록으로 나열한 것입니다. 링크를 따라가면 각 제어에 대해 +좀 더 자세히 알아볼 수 있습니다.

+ +

참고: Android는 여기에 나열된 것보다 몇 가지 더 많은 제어를 제공합니다. + 더 많은 내용을 알아보려면 {@link android.widget} 패키지를 탐색해보십시오. +앱에 특정한 종류의 입력 제어가 필요한 경우, 나름의 사용자 지정 구성 요소를 직접 구축해도 됩니다.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
제어 유형설명관련 클래스
버튼사용자가 누르거나 클릭하여 작업을 수행할 수 있는 누름 버튼입니다.{@link android.widget.Button Button}
텍스트 필드편집 가능한 텍스트 필드입니다. AutoCompleteTextView 위젯을 사용하면 자동 완성 제안을 제공하는 텍스트 입력 위젯을 만들 수 있습니다.{@link android.widget.EditText EditText}, {@link android.widget.AutoCompleteTextView}
확인란사용자가 전환할 수 있는 켜기/끄기 스위치입니다. 확인란은 사용자에게 선택 가능한 옵션 그룹을 제시할 때 사용합니다. 이들 옵션은 상호 배타적이지 않아야 합니다.{@link android.widget.CheckBox CheckBox}
무선 버튼확인란과 비슷하지만, 예외가 있다면 그룹 내에서 하나만 선택할 수 있다는 점입니다.{@link android.widget.RadioGroup RadioGroup} +
{@link android.widget.RadioButton RadioButton}
전환 버튼조명 표시기가 있는 켜기/끄기 버튼입니다.{@link android.widget.ToggleButton ToggleButton}
회전자드롭다운 목록으로, 이것을 사용하면 사용자가 주어진 집합에서 하나의 값을 선택할 수 있습니다.{@link android.widget.Spinner Spinner}
선택기사용자를 위한 대화로, 위/아래 버튼을 사용하거나 스와이프 동작을 사용하여 주어진 집합에 대한 하나의 값을 선택할 수 있게 해줍니다. 날짜(월, 일, 연도) 값을 입력하려면 DatePicker코드> 위젯을 사용하면 되고, 아니면 시간(시, 분, 오전/오후) 값을 입력하는 데 TimePicker 위젯을 사용해도 됩니다. 이렇게 하면 사용자의 로케일에 맞게 자동으로 형식을 설정합니다.{@link android.widget.DatePicker}, {@link android.widget.TimePicker}
diff --git a/docs/html-intl/intl/ko/guide/topics/ui/declaring-layout.jd b/docs/html-intl/intl/ko/guide/topics/ui/declaring-layout.jd new file mode 100644 index 0000000000000000000000000000000000000000..78832368b222416cd099770bcfb96ecabf180ccd --- /dev/null +++ b/docs/html-intl/intl/ko/guide/topics/ui/declaring-layout.jd @@ -0,0 +1,492 @@ +page.title=레이아웃 +page.tags=view,viewgroup +@jd:body + +
+
+

이 문서의 내용

+
    +
  1. XML 쓰기
  2. +
  3. XML 리소스 로딩
  4. +
  5. 속성 +
      +
    1. ID
    2. +
    3. 레이아웃 매개변수
    4. +
    +
  6. +
  7. 레이아웃 위치
  8. +
  9. 크기, 안쪽 여백 및 여백
  10. +
  11. 보편적인 레이아웃
  12. +
  13. 어댑터로 레이아웃 구축하기 +
      +
    1. 데이터로 어댑터 보기 채우기
    2. +
    3. 클릭 이벤트 처리
    4. +
    +
  14. +
+ +

Key 클래스

+
    +
  1. {@link android.view.View}
  2. +
  3. {@link android.view.ViewGroup}
  4. +
  5. {@link android.view.ViewGroup.LayoutParams}
  6. +
+ +

참고 항목

+
    +
  1. 간단한 사용자 +인터페이스 구축
+
+ +

레이아웃은 사용자 인터페이스에 대한 시각적 구조를 정의합니다. 예컨대 액티비티 또는 앱 위젯에 대한 UI가 이에 해당됩니다. +레이아웃을 선언하는 데에는 다음과 같은 두 가지 방법이 있습니다.

+
    +
  • UI 요소를 XML로 선언. Android가 위젯과 레이아웃 등과 같이 +보기 클래스와 하위 클래스에 상응하는 간단한 XML 어휘를 제공합니다.
  • +
  • 런타임에 레이아웃 요소를 인스턴트화. 애플리케이션이 + 프로그래밍 방법으로 보기 및 ViewGroup객체를 만들 수 있습니다(그리고 그 속성을 조작하기도 합니다).
  • +
+ +

Android 프레임워크에서는 이와 같이 애플리케이션의 UI를 선언하고 관리하기 위한 메서드를 둘 중 하나만, 또는 둘 모두 사용할 수 있는 유연성을 부여합니다. 예를 들어, 애플리케이션의 기본 레이아웃을 XML로 선언하면서 그 안에 나타날 화면 요소와 그 속성을 포함할 수 있습니다. 그러면 이제 애플리케이션에 코드를 추가하여 화면 객체의 상태를 수정하도록 할 수 있으며, 여기에는 런타임에 XML로 선언된 것도 포함됩니다.

+ + + +

UI를 XML로 선언하는 것의 이점은 이렇게 하면 애플리케이션을 그 행동을 제어하는 코드로부터 따로 표시하기가 더 좋다는 것입니다. UI 설명은 애플리케이션 코드의 외부에 있습니다. 이는 다시 말해 소스 코드를 수정하고 다시 컴파일링하지 않아도 이를 수정 또는 변경할 수 있다는 뜻입니다. 예를 들어, 서로 다른 화면 방향, 사로 다른 기기 화면 크기 및 서로 다른 언어에 대해 XML 레이아웃을 생성할 수 있습니다. 이외에도 레이아웃을 XML로 선언하면 UI의 구조를 시각화하기가 더 쉬우므로 문제를 디버깅하기도 더 쉽습니다. 따라서, 이 문서는 레이아웃을 XML로 선언하는 방법을 가르치는 데 주안점을 두고 있습니다. 런타임에 보기 객체를 인스턴트화하는 것에 흥미가 있는 경우, +{@link android.view.ViewGroup} 및 +{@link android.view.View} 클래스 참조를 참조하십시오.

+ +

일반적으로 UI 요소를 선언하는 데 쓰이는 XML 어휘는 클래스와 메서드 명명을 충실히 따릅니다. 여기에서 요소 이름은 클래스 이름에 상응하며 속성 이름은 메서드에 상응합니다. 사실 이러한 일치성은 아주 직접적인 경우가 잦아 어느 XML 속성이 클래스 메서드에 상응하는지를 추측할 수 있고, 어느 클래스가 주어진 XML 요소에 상응하는지도 추측할 수 있습니다. 다만 모든 어휘가 다 같지는 않다는 점을 유의하십시오. 몇몇 경우에는 명명에 약간의 차이점이 있습니다. 예를 들어, +EditText 요소에는 text 속성이 있으며 이는 +EditText.setText()에 상응합니다.

+ +

팁: 여러 가지 레이아웃 유형에 대해서는 보편적인 +레이아웃 객체를 참조하십시오. 여러 가지 레이아웃을 구축하는 데 대한 튜토리얼 모음도 있습니다. +Hello 보기 튜토리얼 가이드를 참조하십시오.

+ +

XML 쓰기

+ +

Android의 XML 어휘를 사용하면 UI 레이아웃과 그 안에 들어있는 화면 요소를 HTML에서 웹 페이지를 디자인할 때와 같은 방식으로 신속하게 디자인할 수 있습니다. 즉 일련의 중첩된 요소를 사용하는 것입니다.

+ +

각 레이아웃 파일에는 반드시 딱 하나의 루트 요소만 있어야 하며, 이는 보기 또는 ViewGroup 객체여야 합니다. 루트 요소를 정의했으면, 하위 요소로 더 많은 레이아웃 요소 또는 위젯을 추가하여 점층적으로 레이아웃을 정의할 보기 계층을 구축할 수 있습니다. 예를 들어, 다음은 수직 {@link android.widget.LinearLayout}을 + 사용하여 {@link android.widget.TextView} 및 {@link android.widget.Button}을 보유하는 XML 레이아웃을 나타낸 것입니다.

+
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:layout_width="match_parent"
+              android:layout_height="match_parent"
+              android:orientation="vertical" >
+    <TextView android:id="@+id/text"
+              android:layout_width="wrap_content"
+              android:layout_height="wrap_content"
+              android:text="Hello, I am a TextView" />
+    <Button android:id="@+id/button"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Hello, I am a Button" />
+</LinearLayout>
+
+ +

레이아웃을 XML로 선언하고 나면 그 파일을 Android 프로젝트의 res/layout/ 디렉터리 내에 +.xml 확장자로 저장하여 적절하게 컴파일링되도록 합니다.

+ +

레이아웃 XML 파일의 구문에 대한 자세한 정보는 레이아웃 리소스 문서에서 확인할 수 있습니다.

+ +

XML 리소스 로딩

+ +

애플리케이션을 컴파일링하는 경우, 각 XML 레이아웃 파일이 +{@link android.view.View} 리소스 안에 컴파일링됩니다. 애플리케이션 코드로부터 가져온 레이아웃 리소스는 +{@link android.app.Activity#onCreate(android.os.Bundle) Activity.onCreate()} 콜백 +구현에 로딩해야 합니다. +이렇게 하려면 {@link android.app.Activity#setContentView(int) setContentView()}를 호출한 다음, 이를 +R.layout.layout_file_name 형태로 레이아웃 리소스의 참조에 전달합니다. + 예를 들어, XML 레이아웃이 main_layout.xml로 저장된 경우, 이것을 액티비티에 대해 로딩하려면 +다음과 같이 하면 됩니다.

+
+public void onCreate(Bundle savedInstanceState) {
+    super.onCreate(savedInstanceState);
+    setContentView(R.layout.main_layout);
+}
+
+ +

액티비티 내의 onCreate() 콜백 메서드는 액티비티가 시작될 때 +Android 프레임워크가 호출합니다(수명 주기에 대한 논의는 +액티비티 + 문서에서 확인하십시오).

+ + +

속성

+ +

모든 보기 및 ViewGroup 객체는 각자 나름대로의 다양한 XML 속성을 지원합니다. +몇몇 속성은 보기 객체에만 특화되어 있지만(예를 들어, TextView는 textSize +속성을 지원), 이와 같은 속성은 이 클래스를 확장할 수 있는 모든 보기 객체가 상속하기도 합니다. +모든 보기 객체에 공통으로 쓰이는 것도 몇 가지 있습니다. 왜냐하면 이들은 루트 보기 클래스에서 상속된 것이기 때문입니다(예: +id 속성). 그리고 나머지 속성은 "레이아웃 매개변수"로 간주됩니다. +이들은 보기 객체의 특정한 레이아웃 방향을 설명하는 것으로, 이는 해당 객체의 상위 VeiwGroup 객체에서 +정의된 바에 따릅니다.

+ +

ID

+ +

모든 보기 객체에는 연관된 정수 ID가 있을 수 있습니다. 이는 트리 내에서 해당 보기를 고유하게 식별하기 위한 것입니다. +애플리케이션이 컴파일링되면 이 ID가 정수로 참조되지만, ID는 +일반적으로 레이아웃 XML 파일에 문자열로 할당되며, id 속성으로 쓰입니다. +이것은 모든 보기 객체에 공통적인 XML 속성으로 +({@link android.view.View} 클래스가 정의) 이것을 매우 자주 사용하게 됩니다. +ID에 대한, XML 태그 내에 있는 구문은 다음과 같습니다.

+
android:id="@+id/my_button"
+ +

문자열 시작 부분에 있는 앳 기호(@)는 XML 파서가 ID 문자열의 나머지를 구문 분석하고 확장하여 +ID 리소스로 식별해야 한다는 것을 나타냅니다. 더하기 기호(+)는 이것이 새 리소스 이름이며, +이것을 반드시 생성하여 우리 리소스에 추가해야 한다는 것을 뜻합니다(R.java 파일에서). Android 프레임워크는 다른 ID 리소스도 아주 많이 +제공합니다. Android 리소스 ID를 참조할 때에는 더하기 기호는 필요하지 않지만 +android 패키지 네임스페이스를 다음과 같이 반드시 추가해야 합니다.

+
android:id="@android:id/empty"
+

android 패키지 네임스페이스를 제자리에 넣으면 이제 ID를 로컬 리소스 클래스에서가 아니라 android.R + 리소스 클래스에서 참조하고 있는 것이 됩니다.

+ +

보기를 생성하고 이를 애플리케이션에서 참조하는 데 쓰이는 보편적인 패턴은 다음과 같습니다.

+
    +
  1. 레이아웃 파일에서 보기/위젯을 정의한 다음 이를 고유한 ID에 할당합니다. +
    +<Button android:id="@+id/my_button"
    +        android:layout_width="wrap_content"
    +        android:layout_height="wrap_content"
    +        android:text="@string/my_button_text"/>
    +
    +
  2. +
  3. 그런 다음 보기 객체의 인스턴스를 생성하고 이를 레이아웃에서 캡처합니다 +(일반적으로 {@link android.app.Activity#onCreate(Bundle) onCreate()} 메서드에서). +
    +Button myButton = (Button) findViewById(R.id.my_button);
    +
    +
  4. +
+

{@link android.widget.RelativeLayout}을 생성할 때에는 보기 객체의 ID를 정의하는 것이 중요합니다. +관계 레이아웃에서는 형제 보기가 또 다른 형제 보기와 관련된 자신의 레이아웃을 정의할 수 있으며, +이를 고유한 ID로 참조하게 됩니다.

+

ID는 트리 전체를 통틀어 고유할 필요는 없지만, 트리에서 검색하고 있는 부분 내에서는 +고유해야 합니다(이것이 트리 전체인 경우가 잦으므로, 가급적이면 완전히 +고유한 것을 쓰는 것이 가장 좋습니다).

+ + +

레이아웃 매개변수

+ +

layout_something이라는 XML 레이아웃 속성이 +보기가 상주하는 ViewGroup에 대해 적절한 보기의 레이아웃 매개변수를 정의합니다.

+ +

모든 ViewGroup 클래스가 중첩된 클래스를 하나씩 구현하며 이것이 {@link +android.view.ViewGroup.LayoutParams}를 확장합니다. 이 하위 클래스에는 +각 하위 보기의 크기와 위치를 보기 그룹에 적절한 방식으로 정의하는 +속성 유형이 들어 있습니다. 그림 1에서 볼 수 있듯이, 상위 보기 그룹이 +각 하위 보기의 레이아웃 매개변수를 정의합니다(하위 보기 그룹 포함).

+ + +

그림 1. 각 보기와 연관된 레이아웃 매개변수가 +있는 보기 계층을 시각화한 것입니다.

+ +

모든 LayoutParams 하위 클래스에는 설정 값에 대한 각기 자신만의 구문이 있다는 점을 +눈여겨 보십시오. 각 하위 요소는 자신의 상위에 적합한 LayoutParams를 정의해야 합니다. +다만 이것은 자신의 하위에 대해 각기 다른 LayoutParams도 정의할 수 있습니다.

+ +

모든 보기 그룹에는 너비와 높이가 포함되며(layout_width 및 +layout_height), 각 보기는 이들을 반드시 정의해야 합니다. 선택 사항으로 +여백과 테두리도 포함하는 LayoutParams도 많습니다.

+ +

너비와 높이는 정확한 치수로 지정할 수 있습니다. 다만 이것은 자주 하지 +않는 것이 좋습니다. 그보다는 다음과 같은 상수 중 하나를 사용하여 너비 또는 높이를 설정하는 경우가 +더 많습니다.

+ +
    +
  • wrap_content 보기에 콘텐츠에 필요한 치수대로 알아서 +크기를 조정하라고 합니다.
  • +
  • match_parent (다른 이름은 fill_parent 로, API 레벨 8 이전에 해당) +보기에 상위 보기 그룹이 허용하는 한 최대한으로 커지라고 합니다.
  • +
+ +

일반적으로 픽셀과 같이 절대적인 단위를 사용하여 레이아웃 너비와 높이를 지정하는 것은 +권장하지 않습니다. 그 대신, 밀도 독립적인 픽셀 단위와 같이 상대적인 측정치를 +사용하는 것(dp), wrap_content, 또는 +match_parent등이 더 낫습니다. 이렇게 하면 +애플리케이션이 다양한 기기 화면 크기에 걸쳐서도 적절하게 표시되도록 보장하는 데 도움이 되기 때문입니다. +허용된 측정 유형은 + +사용 가능한 리소스에 정의되어 있습니다.

+ + +

레이아웃 위치

+

+ 보기의 모양은 직사각형입니다. 보기에는 위치가 있으며, 이는 + 한 쌍의 왼쪽상단 좌표, 그리고 두 개의 치수가 너비와 높이를 나타내는 +형식으로 표현됩니다. 위치와 치수의 단위는 픽셀입니다. + +

+ +

+ 보기의 위치를 검색할 수 있습니다. +{@link android.view.View#getLeft()} 및 {@link android.view.View#getTop()} 메서드를 호출하면 됩니다. 전자는 보기를 +나타내는 직사각형의 왼쪽, 즉 X 좌표를 반환합니다. 후자는 보기를 +나타내는 직사각형의 상단, 즉 Y 좌표를 반환합니다. 이들 메서드는 둘 다 +보기의 위치를 해당 보기의 상위와 관련지어 반환합니다. 예를 들어, +getLeft()가 20을 반환하는 경우 이는 해당 보기가 그 보기의 바로 상위의 왼쪽 가장자리에서 +오른쪽으로 20픽셀 떨어진 곳에 있다는 뜻입니다. +

+ +

+ 이외에도 불필요한 계산을 피하기 위해 여러 가지 편의 메서드가 제공됩니다. +구체적으로 {@link android.view.View#getRight()} 및 {@link android.view.View#getBottom()}을 들 수 있습니다. + 이들 메서드는 해당 보기를 나타내는 직사각형의 오른쪽과 하단 가장자리의 좌표를 반환합니다. + 예를 들어 {@link android.view.View#getRight()}를 + 호출하는 것은 다음 계산과 비슷합니다. getLeft() + getWidth() +

+ + +

크기, 안쪽 여백 및 여백

+

+ 보기의 크기는 너비와 높이로 표현됩니다. 사실 하나의 보기는 +두 쌍의 너비 및 높이 값을 소유합니다. +

+ +

+ 첫 번째 쌍을 측정된 너비 및 +측정된 높이라고 합니다. 이들 치수는 보기가 +상위 내에서 얼마나 커지고자 하는지를 정의합니다. 측정된 +치수를 가져오려면 {@link android.view.View#getMeasuredWidth()} + 및 {@link android.view.View#getMeasuredHeight()}를 호출하면 됩니다. +

+ +

+ 두 번째 쌍은 단순히 너비높이라고 일컬으며, +때로는 그리기 너비그리기 높이로 부를 때도 있습니다. 이러한 +치수는 그리기 시간 및 레이아웃 후에 보기가 화면에 표시되는 실제 크기를 +정의합니다. 이들 값은 측정된 너비 및 높이와 달라도 되지만 +꼭 달라야 하는 것은 아닙니다. 너비와 높이를 가져오려면 +{@link android.view.View#getWidth()} 및 {@link android.view.View#getHeight()}를 호출하면 됩니다. +

+ +

+ 보기의 치수를 측정하려면 보기는 자신의 안쪽 여백을 감안합니다. 안쪽 여백은 +보기의 왼쪽, 상단, 오른쪽 및 하단 부분에 대해 픽셀로 표시됩니다. + 안쪽 여백은 정해진 픽셀 수를 사용하여 보기의 콘텐츠를 오프셋하는 데 쓰일 수도 +있습니다. 예를 들어 왼쪽 안쪽 여백을 2로 설정하면 해당 보기의 콘텐츠를 왼쪽 가장자리에서 +오른쪽으로 2픽셀 밀어냅니다. 안쪽 여백을 설정할 때에는 +{@link android.view.View#setPadding(int, int, int, int)} 메서드를 사용하면 되고, 이를 쿼리하려면 +{@link android.view.View#getPaddingLeft()}, {@link android.view.View#getPaddingTop()}, +{@link android.view.View#getPaddingRight()} 및 {@link android.view.View#getPaddingBottom()}을 사용하면 됩니다. +

+ +

+ 보기가 안쪽 여백을 정의할 수는 있지만, 여백에 대한 지원은 전혀 제공하지 +않습니다. 다만 보기 그룹이 그와 같은 지원을 제공합니다. 자세한 정보는 +{@link android.view.ViewGroup} 및 +{@link android.view.ViewGroup.MarginLayoutParams}를 참조하십시오. +

+ +

치수에 대한 자세한 정보는 +치수 값을 참조하십시오. +

+ + + + + + + + + + + +

보편적인 레이아웃

+ +

{@link android.view.ViewGroup} 클래스의 각 하위 클래스는 각기 고유한 방식으로 자신 안에 +중첩한 보기를 표시합니다. 아래는 Android 플랫폼에서 기본 제공되는, 보다 보편적인 레이아웃 유형을 +몇 가지 나타낸 것입니다.

+ +

참고: 하나 이상의 레이아웃을 또 다른 레이아웃에 중첩하여 +UI 디자인을 이룰 수도 있지만, 레이아웃 계층을 가능한 한 얕게 유지하도록 +애써야 합니다. 중첩된 레이아웃이 적을수록 레이아웃이 더욱 빠르게 그려집니다(가로로 넓은 보기 계층이 +깊은 보기 계층보다 낫습니다).

+ + + + +
+

선형 레이아웃

+ +

여러 하위를 하나의 가로 방향 또는 세로 방향 행으로 정리하는 레이아웃. 이것은 +창의 길이가 화면 길이를 웃도는 경우 스크롤 막대를 만듭니다.

+
+ +
+

관계 레이아웃

+ +

여러 하위 객체의 위치를 서로 관련지어 나타내거나(하위 A가 +하위 B의 왼쪽), 상위와 관련지어 나타낼 수 있도록 해줍니다(상위의 맨 위에 맞춰 정렬).

+
+ +
+

웹 보기

+ +

웹 페이지를 표시합니다.

+
+ + + + +

어댑터로 레이아웃 구축하기

+ +

레이아웃의 콘텐츠가 동적이거나 미리 정의되지 않은 경우, +{@link android.widget.AdapterView}의 하위 클래스가 되는 레이아웃을 사용하여 런타임에 보기로 레이아웃을 채울 수 있습니다. +{@link android.widget.AdapterView} 클래스의 하위 클래스는 {@link android.widget.Adapter}를 +사용하여 자신의 레이아웃에 데이터를 바인딩합니다. {@link android.widget.Adapter}가 데이터 소스와 {@link android.widget.AdapterView} + 레이아웃 사이의 중개자 역할을 합니다. —{@link android.widget.Adapter}가 + 데이터를 검색하여(배열 또는 데이터베이스 쿼리와 같은 소스로부터) +각 항목을 보기로 변환해서 {@link android.widget.AdapterView} 레이아웃에 추가될 수 있도록 합니다.

+ +

어댑터로 지원되는 보편적인 레이아웃의 몇 가지 예는 다음과 같습니다.

+ +
+

목록 보기

+ +

스크롤로 내리는 하나의 열 목록을 표시합니다.

+
+ +
+

눈금 보기

+ +

스크롤로 내리는 열과 행의 눈금을 표시합니다.

+
+ + + +

데이터로 어댑터 보기 채우기

+ +

{@link android.widget.ListView} 또는 +{@link android.widget.GridView}와 같은 {@link android.widget.AdapterView}를 채우려면 {@link android.widget.AdapterView} 인스턴스를 +{@link android.widget.Adapter}에 바인딩하면 됩니다. 이는 외부 소스로부터 데이터를 검색하여 각 데이터 항목을 나타내는 {@link +android.view.View}를 생성합니다.

+ +

Android는 {@link android.widget.Adapter}의 하위 클래스를 여러 개 제공합니다. +이는 여러 가지 종류의 데이터를 검색하고 {@link android.widget.AdapterView}에 대한 보기를 구축하는 데 유용합니다. +가장 보편적인 어댑터 두 가지를 예로 들면 다음과 같습니다.

+ +
+
{@link android.widget.ArrayAdapter}
+
이 어댑터는 데이터 소스가 배열일 때 사용하십시오. 기본적으로 {@link +android.widget.ArrayAdapter}가 각 항목에서 {@link +java.lang.Object#toString()}를 호출하고 그 콘텐츠를 {@link +android.widget.TextView}에 배치함으로써 각 배열 항목에 대한 보기를 생성합니다. +

예를 들어, {@link +android.widget.ListView}로 문자열 배열을 표시하고자 하는 경우, 생성자를 사용하여 +새로운 {@link android.widget.ArrayAdapter}를 초기화해서 각 문자열과 문자열 배열에 대한 레이아웃을 지정하면 됩니다.

+
+ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
+        android.R.layout.simple_list_item_1, myStringArray);
+
+

이 생성자에 대한 인수는 다음과 같습니다.

+
    +
  • 개발자의 앱 {@link android.content.Context}
  • +
  • 배열에 있는 각 문자열에 대한 {@link android.widget.TextView}가 들어있는 레이아웃
  • +
  • 문자열 배열
  • +
+

그런 다음 {@link android.widget.ListView}에서 +{@link android.widget.ListView#setAdapter setAdapter()}를 호출하기만 하면 됩니다.

+
+ListView listView = (ListView) findViewById(R.id.listview);
+listView.setAdapter(adapter);
+
+ +

각 항목의 외관을 사용자 지정하려면 배열의 객체에 대한 {@link +java.lang.Object#toString()} 메서드를 재정의하면 됩니다. 아니면, 각 항목에 대하여 +{@link android.widget.TextView}가 아닌 다른 보기를 생성하고자 하는 경우(예를 들어 각 배열 항목에 +{@link android.widget.ImageView}를 원하는 경우), {@link +android.widget.ArrayAdapter} 클래스를 확장하고 {@link android.widget.ArrayAdapter#getView +getView()}를 재정의하여 각 항목에 대해 원하는 유형의 보기를 반환하도록 할 수 있습니다.

+ +
+ +
{@link android.widget.SimpleCursorAdapter}
+
이 어댑터는 데이터 출처가 {@link android.database.Cursor}일 때 사용하십시오. +{@link android.widget.SimpleCursorAdapter}를 사용하는 경우, +{@link android.database.Cursor}에 있는 각 행에 대하여 사용할 레이아웃을 지정해야 합니다. 또한 {@link android.database.Cursor} +의 어느 열이 레이아웃의 어느 보기에 삽입되어야 할지도 지정해야 합니다. 예를 들어 사람 이름과 +전화번호로 이루어진 목록을 생성하고자 하는 경우, 각 사람에 대해 행이 하나씩 있고 이름과 번호에 대해 열이 들어있는 {@link +android.database.Cursor}를 반환하는 쿼리를 수행하면 됩니다. + 그런 다음 레이아웃에서 각 결과에 대하여 {@link +android.database.Cursor}의 어느 열을 원하는지 지정하는 문자열 배열을 만들 수 있고, 각 열이 배치되어야 하는 +상응하는 보기를 지정하는 정수 배열을 만들면 됩니다.

+
+String[] fromColumns = {ContactsContract.Data.DISPLAY_NAME,
+                        ContactsContract.CommonDataKinds.Phone.NUMBER};
+int[] toViews = {R.id.display_name, R.id.phone_number};
+
+

{@link android.widget.SimpleCursorAdapter}를 인스턴트화하는 경우, 각 결과에 대해 사용할 레이아웃과 +결과가 들어있는 {@link android.database.Cursor}, 그리고 다음의 두 배열을 전달합니다.

+
+SimpleCursorAdapter adapter = new SimpleCursorAdapter(this,
+        R.layout.person_name_and_number, cursor, fromColumns, toViews, 0);
+ListView listView = getListView();
+listView.setAdapter(adapter);
+
+

그러면 {@link android.widget.SimpleCursorAdapter}가 +{@link android.database.Cursor}에 있는 각 행에 대한 보기를 하나씩 생성합니다. 이때 상응하는 {@code toViews} 보기 안에 각 {@code +fromColumns} 항목을 삽입함으로써 제공된 레이아웃을 사용합니다.

.
+
+ + +

애플리케이션의 수명이 진행되는 동안에 어댑터가 읽는 기본 데이터를 변경하는 경우, +{@link android.widget.ArrayAdapter#notifyDataSetChanged()}를 호출해야 합니다. +이렇게 하면 첨부된 보기에 데이터가 변경되었으며 스스로 새로 고쳐야 한다는 사실을 알려줍니다.

+ + + +

클릭 이벤트 처리

+ +

{@link android.widget.AdapterView}에 있는 각 항목에서의 클릭 이벤트에 응답하려면 +{@link android.widget.AdapterView.OnItemClickListener} 인터페이스를 구현하면 됩니다. 예:

+ +
+// Create a message handling object as an anonymous class.
+private OnItemClickListener mMessageClickedHandler = new OnItemClickListener() {
+    public void onItemClick(AdapterView parent, View v, int position, long id) {
+        // Do something in response to the click
+    }
+};
+
+listView.setOnItemClickListener(mMessageClickedHandler);
+
+ + + diff --git a/docs/html-intl/intl/ko/guide/topics/ui/dialogs.jd b/docs/html-intl/intl/ko/guide/topics/ui/dialogs.jd new file mode 100644 index 0000000000000000000000000000000000000000..23e92c9f855e95c2f4b0264ab87d77ded46958d4 --- /dev/null +++ b/docs/html-intl/intl/ko/guide/topics/ui/dialogs.jd @@ -0,0 +1,798 @@ +page.title=대화 +page.tags=alertdialog,dialogfragment + +@jd:body + + + + + +

대화는 사용자에게 결정을 내리거나 추가 정보를 입력하라는 +프롬프트를 보내는 작은 창입니다. 대화는 화면을 가득 채우지 않으며 보통 사용자가 +다음으로 계속 진행하기 전에 조치를 취해야 하는 모달 이벤트에 쓰입니다.

+ +
+

대화 디자인

+

언어 권장 사항을 비롯한 여러 가지 대화 디자인 방법에 관련된 정보는 +대화 디자인 가이드를 읽어보십시오.

+
+ + + +

{@link android.app.Dialog} 클래스가 대화의 기본 클래스이지만, +{@link android.app.Dialog}를 직접 인스턴트화하는 것은 삼가야 합니다. +대신 다음 하위 클래스 중 하나를 사용하십시오.

+
+
{@link android.app.AlertDialog}
+
제목 하나, 최대 세 개의 버튼, 선택 가능한 품목 목록 또는 +사용자 지정 레이아웃을 표시할 수 있는 대화입니다.
+
{@link android.app.DatePickerDialog} 또는 {@link android.app.TimePickerDialog}
+
미리 정의된 UI가 있는 대화로 사용자로 하여금 날짜 또는 시간을 선택할 수 있게 해줍니다.
+
+ + + +

이러한 클래스가 대화의 스타일과 구조를 정의하지만, 대화의 컨테이너로는 +{@link android.support.v4.app.DialogFragment}를 사용해야 합니다. +{@link android.support.v4.app.DialogFragment} + 클래스는 대화를 만들고 그 외관을 관리하는 데 필요한 모든 제어를 제공합니다. +{@link android.app.Dialog} 객체에서 메서드를 호출하는 것 대신입니다.

+ +

대화를 관리하기 위해 {@link android.support.v4.app.DialogFragment}를 사용하면 +사용자가 뒤로 버튼을 누르거나 화면을 돌릴 때 등 +수명 주기 이벤트를 올바르게 처리하도록 보장할 수 있습니다. {@link +android.support.v4.app.DialogFragment} 클래스를 사용하면 대화의 UI를 더 큰 UI에 +포함시킬 수 있는 구성 요소로 다시 사용할 수 있게 해주기도 합니다. 이것은 기존의 {@link +android.support.v4.app.Fragment}와 똑같습니다(대화 UI를 크고 작은 화면에서 서로 다르게 +나타나도록 하고자 하는 경우 등).

+ +

이 가이드의 다음 섹션에서는 {@link +android.support.v4.app.DialogFragment}를 {@link android.app.AlertDialog} + 객체와 함께 조합하여 사용하는 방법을 설명합니다. 날짜 또는 시간 선택기를 생성하고자 하는 경우, 대신 +선택기 가이드를 읽으십시오.

+ +

참고: +{@link android.app.DialogFragment} 클래스는 원래 +Android 3.0(API 레벨 11)에 추가되었기 때문에 이 문서에서는 지원 라이브러리와 함께 제공된 {@link +android.support.v4.app.DialogFragment} 클래스를 사용하는 법을 설명합니다. 이 라이브러리를 앱에 추가하면 Android 1.6 이상을 실행하는 기기에서 +{@link android.support.v4.app.DialogFragment}를 비롯하여 +다른 API도 다양하게 사용할 수 있습니다. 앱의 최소 버전이 +API 레벨 11 이상인 경우, {@link +android.app.DialogFragment}의 프레임워크 버전을 사용해도 되지만, 이 문서에 있는 링크는 +지원 라이브러리 API를 대상으로 한 것이라는 점을 유의하십시오. 지원 라이브러리를 사용할 때에는 +android.support.v4.app.DialogFragment + 클래스를 가져와야 합니다. android.app.DialogFragment아닙니다.

+ + +

대화 프래그먼트 생성

+ +

대단히 다양한 대화 디자인을 만들 수 있습니다. 사용자 지정 레이아웃은 물론 +대화 +디자인 가이드에서 설명한 것도 포함합니다. +{@link android.support.v4.app.DialogFragment}를 확장하고 {@link android.support.v4.app.DialogFragment#onCreateDialog +onCreateDialog()} 콜백 메서드에 {@link android.app.AlertDialog}를 + 생성하면 됩니다.

+ +

예를 들어 다음은 {@link android.app.AlertDialog}로, 이는 +{@link android.support.v4.app.DialogFragment} 내에서 관리되는 것입니다.

+ +
+public class FireMissilesDialogFragment extends DialogFragment {
+    @Override
+    public Dialog onCreateDialog(Bundle savedInstanceState) {
+        // Use the Builder class for convenient dialog construction
+        AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+        builder.setMessage(R.string.dialog_fire_missiles)
+               .setPositiveButton(R.string.fire, new DialogInterface.OnClickListener() {
+                   public void onClick(DialogInterface dialog, int id) {
+                       // FIRE ZE MISSILES!
+                   }
+               })
+               .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
+                   public void onClick(DialogInterface dialog, int id) {
+                       // User cancelled the dialog
+                   }
+               });
+        // Create the AlertDialog object and return it
+        return builder.create();
+    }
+}
+
+ +
+ +

그림 1. +메시지 하나와 작업 버튼 두 개가 있는 대화입니다.

+
+ +

이 클래스의 인스턴스를 생성하고 해당 객체에서 {@link +android.support.v4.app.DialogFragment#show show()}를 호출하면 대화는 +그림 1에 표시된 것처럼 나타납니다.

+ +

다음 섹션에서는 {@link android.app.AlertDialog.Builder} +API를 사용하여 대화를 생성하는 것에 대해 좀 더 자세히 설명합니다.

+ +

대화가 얼마나 복잡한지에 따라 +{@link android.support.v4.app.DialogFragment}에서 여러 가지 다른 콜백 메서드를 구현할 수 있습니다. 그중에는 기본적인 +조각 수명 주기 메서드도 포함됩니다. + + + + + +

경고 대화 구축

+ + +

{@link android.app.AlertDialog} 클래스를 사용하면 +여러 가지 대화 디자인을 구축할 수 있으며, 필요한 대화 클래스는 이것뿐인 경우도 많습니다. +그림 2에 표시된 것과 같이 경고 대화에는 세 가지 영역이 있습니다.

+ +
+ +

그림 2. 대화의 레이아웃입니다.

+
+ +
    +
  1. 제목 +

    이것은 선택 항목이며 콘텐츠 영역에 상세한 메시지, 목록 또는 +사용자 지정 레이아웃이 채워져 있는 경우에만 사용해야 합니다. 단순한 메시지 또는 +질문(그림 1의 대화처럼)을 진술해야 하는 경우, 제목은 없어도 됩니다.

  2. +
  3. 콘텐츠 영역 +

    이것은 메시지, 목록 또는 다른 사용자 지정 레이아웃을 표시할 수 있습니다.

  4. +
  5. 작업 버튼 +

    대화 하나에 작업 버튼은 세 개 이상 있으면 안 됩니다.

  6. +
+ +

{@link android.app.AlertDialog.Builder} + 클래스가 이와 같은 종류의 콘텐츠가 있는 {@link android.app.AlertDialog}를 + 생성할 수 있게 해주는 API를 제공하며, 여기에 사용자 지정 레이아웃도 포함됩니다.

+ +

{@link android.app.AlertDialog}를 구축하려면 다음과 같이 하면 됩니다.

+ +
+// 1. Instantiate an {@link android.app.AlertDialog.Builder} with its constructor
+AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+
+// 2. Chain together various setter methods to set the dialog characteristics
+builder.setMessage(R.string.dialog_message)
+       .setTitle(R.string.dialog_title);
+
+// 3. Get the {@link android.app.AlertDialog} from {@link android.app.AlertDialog.Builder#create()}
+AlertDialog dialog = builder.create();
+
+ +

다음 주제는 +{@link android.app.AlertDialog.Builder} 클래스를 사용하여 다양한 대화 속성을 정의하는 방법을 나타낸 것입니다.

+ + + + +

버튼 추가

+ +

그림 2에 표시된 것과 같은 작업 버튼을 추가하려면 +{@link android.app.AlertDialog.Builder#setPositiveButton setPositiveButton()} 및 +{@link android.app.AlertDialog.Builder#setNegativeButton setNegativeButton()} 메서드를 호출하면 됩니다.

+ +
+AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+// Add the buttons
+builder.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
+           public void onClick(DialogInterface dialog, int id) {
+               // User clicked OK button
+           }
+       });
+builder.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
+           public void onClick(DialogInterface dialog, int id) {
+               // User cancelled the dialog
+           }
+       });
+// Set other dialog properties
+...
+
+// Create the AlertDialog
+AlertDialog dialog = builder.create();
+
+ +

set...Button() 메서드에는 버튼의 제목이 필요하고( +문자열 리소스가 제공), 사용자가 버튼을 눌렀을 때 수행할 작업을 정의하는 +{@link android.content.DialogInterface.OnClickListener}가 +필요합니다.

+ +

추가할 수 있는 작업 버튼은 다음과 같은 세 가지가 있습니다.

+
+
긍정적
+
이것은 수락하고 작업을 계속하는 데 사용해야 합니다("확인(OK)" 작업).
+
부정적
+
이것은 작업을 취소하는 데 사용해야 합니다.
+
중립적
+
이것은 사용자가 작업을 계속하고 싶지 않을 수 있지만 +취소하고자 한다고 볼 수 없을 때 사용해야 합니다. 이것은 긍정적 버튼과 부정적 버튼 사이에 나타납니다. + 이런 작업을 예로 들면 "나중에 알림" 등이 있습니다.
+
+ +

{@link +android.app.AlertDialog}에는 각 버튼 유형을 하나씩만 추가할 수 있습니다. 다시 말해, "긍정적" 버튼이 한 개 이상 있으면 안 됩니다.

+ + + +
+ +

그림 3. +제목과 목록이 있는 대화입니다.

+
+ +

목록 추가

+ +

{@link android.app.AlertDialog} API와 함께 사용 가능한 목록은 세 가지 종류가 있습니다.

+
    +
  • 일반적인 단일 선택 목록
  • +
  • 영구적인 단일 선택 목록(무선 버튼)
  • +
  • 영구적인 다중 선택 목록(확인란)
  • +
+ +

그림 3에 표시된 것과 같은 단일 선택 목록을 생성하려면 +{@link android.app.AlertDialog.Builder#setItems setItems()} 메서드를 사용하면 됩니다.

+ +
+@Override
+public Dialog onCreateDialog(Bundle savedInstanceState) {
+    AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+    builder.setTitle(R.string.pick_color)
+           .setItems(R.array.colors_array, new DialogInterface.OnClickListener() {
+               public void onClick(DialogInterface dialog, int which) {
+               // The 'which' argument contains the index position
+               // of the selected item
+           }
+    });
+    return builder.create();
+}
+
+ +

목록은 대화의 콘텐츠 영역에 나타나므로, +대화는 메시지와 목록을 둘 다 표시할 수 없습니다. 대화에는 +{@link android.app.AlertDialog.Builder#setTitle setTitle()}로 제목을 설정해야 합니다. +목록에 대한 항목을 지정하려면 {@link +android.app.AlertDialog.Builder#setItems setItems()}를 호출하여 배열을 하나 전달합니다. +아니면 {@link +android.app.AlertDialog.Builder#setAdapter setAdapter()}를 사용하여 목록을 지정해도 됩니다. 이렇게 하면 동적인 데이터가 있는 목록(예: 데이터베이스에서 가져온 것)을 +{@link android.widget.ListAdapter}로 지원할 수 있게 해줍니다.

+ +

{@link android.widget.ListAdapter}로 목록을 지원하기로 선택하는 경우, +항상 {@link android.support.v4.content.Loader}를 사용해야 콘텐츠가 비동기식으로 +로딩됩니다. +이것은 어댑터로 레이아웃 +구축하기로더 + 가이드에 더 자세히 설명되어 있습니다.

+ +

참고: 기본적으로 목록 항목을 터치하면 대화를 무시하게 됩니다. +다만 다음과 같은 영구적인 선택 목록 중 하나를 사용하는 경우는 예외입니다.

+ +
+ +

그림 4. +다중 선택 항목의 목록입니다.

+
+ + +

영구적 다중 선택 또는 단일 선택 목록 추가

+ +

다중 선택 항목 목록을 추가하거나(확인란) +단일 선택 목록을 추가하려면(무선 버튼), 각각 +{@link android.app.AlertDialog.Builder#setMultiChoiceItems(Cursor,String,String, +DialogInterface.OnMultiChoiceClickListener) setMultiChoiceItems()} 또는 +{@link android.app.AlertDialog.Builder#setSingleChoiceItems(int,int,DialogInterface.OnClickListener) +setSingleChoiceItems()} 메서드를 사용합니다.

+ +

예를 들어 다음은 그림 4에 표시된 것과 같이 다중 선택 목록을 생성하는 방법입니다. +이것은 선택한 항목을 +{@link java.util.ArrayList}에 저장합니다.

+ +
+@Override
+public Dialog onCreateDialog(Bundle savedInstanceState) {
+    mSelectedItems = new ArrayList();  // Where we track the selected items
+    AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+    // Set the dialog title
+    builder.setTitle(R.string.pick_toppings)
+    // Specify the list array, the items to be selected by default (null for none),
+    // and the listener through which to receive callbacks when items are selected
+           .setMultiChoiceItems(R.array.toppings, null,
+                      new DialogInterface.OnMultiChoiceClickListener() {
+               @Override
+               public void onClick(DialogInterface dialog, int which,
+                       boolean isChecked) {
+                   if (isChecked) {
+                       // If the user checked the item, add it to the selected items
+                       mSelectedItems.add(which);
+                   } else if (mSelectedItems.contains(which)) {
+                       // Else, if the item is already in the array, remove it 
+                       mSelectedItems.remove(Integer.valueOf(which));
+                   }
+               }
+           })
+    // Set the action buttons
+           .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
+               @Override
+               public void onClick(DialogInterface dialog, int id) {
+                   // User clicked OK, so save the mSelectedItems results somewhere
+                   // or return them to the component that opened the dialog
+                   ...
+               }
+           })
+           .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
+               @Override
+               public void onClick(DialogInterface dialog, int id) {
+                   ...
+               }
+           });
+
+    return builder.create();
+}
+
+ +

일반적인 목록과 무선 버튼이 있는 목록 양쪽 모두 "단일 선택" 작업을 +제공하지만, 사용자의 선택을 유지하고자 하는 경우 {@link +android.app.AlertDialog.Builder#setSingleChoiceItems(int,int,DialogInterface.OnClickListener) +setSingleChoiceItems()}를 사용해야 합니다. +다시 말해, 대화를 나중에 다시 여는 경우 사용자의 현재 선택이 무엇인지 나타내야 하며, +그러면 무선 버튼으로 목록을 생성할 수 있습니다.

+ + + + + +

사용자 지정 레이아웃 생성

+ +
+ +

그림 5. 사용자 지정 대화 레이아웃입니다.

+
+ +

대화에서 사용자 지정 레이아웃을 원하는 경우, 레이아웃을 생성한 다음 이를 +{@link android.app.AlertDialog}에 추가하면 됩니다. 이때 {@link +android.app.AlertDialog.Builder#setView setView()} on your {@link +android.app.AlertDialog.Builder} 객체를 호출하는 방법을 씁니다.

+ +

기본적으로 사용자 지정 레이아웃이 대화창을 가득 채우지만, 여전히 +{@link android.app.AlertDialog.Builder} 메서드를 사용하여 버튼과 제목을 추가할 수 있습니다.

+ +

예를 들어 다음은 그림 5에 표시된 대화에 대한 레이아웃 파일입니다.

+ +

res/layout/dialog_signin.xml

+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content">
+    <ImageView
+        android:src="@drawable/header_logo"
+        android:layout_width="match_parent"
+        android:layout_height="64dp"
+        android:scaleType="center"
+        android:background="#FFFFBB33"
+        android:contentDescription="@string/app_name" />
+    <EditText
+        android:id="@+id/username"
+        android:inputType="textEmailAddress"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="16dp"
+        android:layout_marginLeft="4dp"
+        android:layout_marginRight="4dp"
+        android:layout_marginBottom="4dp"
+        android:hint="@string/username" />
+    <EditText
+        android:id="@+id/password"
+        android:inputType="textPassword"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="4dp"
+        android:layout_marginLeft="4dp"
+        android:layout_marginRight="4dp"
+        android:layout_marginBottom="16dp"
+        android:fontFamily="sans-serif"
+        android:hint="@string/password"/>
+</LinearLayout>
+
+ +

팁: 기본적으로 {@link android.widget.EditText} + 요소를 설정하여 {@code "textPassword"} 입력 유형을 사용하고자 하는 경우, 글꼴 패밀리가 고정 폭으로 설정되어 있으므로 +글꼴 패밀리를 {@code "sans-serif"}로 변경해야 합니다. 그래야 양쪽 텍스트 필드가 모두 일치하는 글꼴 스타일을 +사용할 수 있습니다.

+ +

{@link android.support.v4.app.DialogFragment} 안의 레이아웃을 팽창시키려면, +{@link android.view.LayoutInflater}를 +{@link android.app.Activity#getLayoutInflater()}로 가져와 +{@link android.view.LayoutInflater#inflate inflate()}를 호출합니다. +여기서 첫 번째 매개변수가 레이아웃 리소스 ID이고 두 번째 매개변수가 레이아웃의 상위 보기입니다. +그러므로 그런 다음 {@link android.app.AlertDialog#setView setView()}를 + 호출하여 레이아웃을 대화에 배치할 수 있습니다.

+ +
+@Override
+public Dialog onCreateDialog(Bundle savedInstanceState) {
+    AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+    // Get the layout inflater
+    LayoutInflater inflater = getActivity().getLayoutInflater();
+
+    // Inflate and set the layout for the dialog
+    // Pass null as the parent view because its going in the dialog layout
+    builder.setView(inflater.inflate(R.layout.dialog_signin, null))
+    // Add action buttons
+           .setPositiveButton(R.string.signin, new DialogInterface.OnClickListener() {
+               @Override
+               public void onClick(DialogInterface dialog, int id) {
+                   // sign in the user ...
+               }
+           })
+           .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
+               public void onClick(DialogInterface dialog, int id) {
+                   LoginDialogFragment.this.getDialog().cancel();
+               }
+           });      
+    return builder.create();
+}
+
+ +
+

팁: 사용자 지정 대화를 원하는 경우, +{@link android.app.Activity}를 대신 표시해도 됩니다. 이는 +{@link android.app.Dialog} API 대신 대화로 표시하는 것입니다. 단순히 액티비티를 하나 생성한 다음 그 테마를 {@code +<activity>} 매니페스트 요소에 있는 +{@link android.R.style#Theme_Holo_Dialog Theme.Holo.Dialog}로 + 설정하면 됩니다.

+ +
+<activity android:theme="@android:style/Theme.Holo.Dialog" >
+
+

완료되었습니다. 이제 액티비티가 대화를 전체 화면이 아니라 대화창으로 표시합니다.

+
+ + + +

이벤트를 대화의 호스트에 다시 전달

+ +

사용자가 대화의 작업 버튼 중 하나를 터치하거나 목록에서 항목을 하나 선택하면, +{@link android.support.v4.app.DialogFragment}가 +필요한 작업을 알아서 수행할 수도 있지만 대부분의 경우 이벤트를 대화를 연 액티비티 또는 프래그먼트에 직접 전달하고자 할 수 있습니다. + 이렇게 하려면 각 클릭 이벤트의 유형별로 메서드가 있는 인터페이스를 정의합니다. + 그런 다음 해당 인터페이스를 대화로부터 작업 이벤트를 수신할 +호스트 구성 요소에 구현하면 됩니다.

+ +

예를 들어 다음은 인터페이스를 정의하는 {@link android.support.v4.app.DialogFragment}입니다. +이 인터페이스를 통해 이벤트를 호스트 액티비티에 도로 전달하게 됩니다.

+ +
+public class NoticeDialogFragment extends DialogFragment {
+    
+    /* The activity that creates an instance of this dialog fragment must
+     * implement this interface in order to receive event callbacks.
+     * Each method passes the DialogFragment in case the host needs to query it. */
+    public interface NoticeDialogListener {
+        public void onDialogPositiveClick(DialogFragment dialog);
+        public void onDialogNegativeClick(DialogFragment dialog);
+    }
+    
+    // Use this instance of the interface to deliver action events
+    NoticeDialogListener mListener;
+    
+    // Override the Fragment.onAttach() method to instantiate the NoticeDialogListener
+    @Override
+    public void onAttach(Activity activity) {
+        super.onAttach(activity);
+        // Verify that the host activity implements the callback interface
+        try {
+            // Instantiate the NoticeDialogListener so we can send events to the host
+            mListener = (NoticeDialogListener) activity;
+        } catch (ClassCastException e) {
+            // The activity doesn't implement the interface, throw exception
+            throw new ClassCastException(activity.toString()
+                    + " must implement NoticeDialogListener");
+        }
+    }
+    ...
+}
+
+ +

대화를 호스팅하는 액티비티는 대화의 인스턴스를 만듭니다. +이때 대화 프래그먼트의 생성자를 사용하며, +{@code NoticeDialogListener} 인터페이스 구현을 통해 대화의 이벤트를 수신하게 됩니다.

+ +
+public class MainActivity extends FragmentActivity
+                          implements NoticeDialogFragment.NoticeDialogListener{
+    ...
+    
+    public void showNoticeDialog() {
+        // Create an instance of the dialog fragment and show it
+        DialogFragment dialog = new NoticeDialogFragment();
+        dialog.show(getSupportFragmentManager(), "NoticeDialogFragment");
+    }
+
+    // The dialog fragment receives a reference to this Activity through the
+    // Fragment.onAttach() callback, which it uses to call the following methods
+    // defined by the NoticeDialogFragment.NoticeDialogListener interface
+    @Override
+    public void onDialogPositiveClick(DialogFragment dialog) {
+        // User touched the dialog's positive button
+        ...
+    }
+
+    @Override
+    public void onDialogNegativeClick(DialogFragment dialog) {
+        // User touched the dialog's negative button
+        ...
+    }
+}
+
+ +

액티비티가 {@code NoticeDialogListener}를 구현하기 때문에—위에 표시된 {@link android.support.v4.app.Fragment#onAttach onAttach()} + 콜백 메서드가 강제 적용—해당 대화 프래그먼트는 +인터페이스 콜백 메서드를 사용하여 액티비티에 대한 클릭 이벤트를 +전달할 수 있습니다.

+ +
+public class NoticeDialogFragment extends DialogFragment {
+    ...
+
+    @Override
+    public Dialog onCreateDialog(Bundle savedInstanceState) {
+        // Build the dialog and set up the button click handlers
+        AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+        builder.setMessage(R.string.dialog_fire_missiles)
+               .setPositiveButton(R.string.fire, new DialogInterface.OnClickListener() {
+                   public void onClick(DialogInterface dialog, int id) {
+                       // Send the positive button event back to the host activity
+                       mListener.onDialogPositiveClick(NoticeDialogFragment.this);
+                   }
+               })
+               .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
+                   public void onClick(DialogInterface dialog, int id) {
+                       // Send the negative button event back to the host activity
+                       mListener.onDialogNegativeClick(NoticeDialogFragment.this);
+                   }
+               });
+        return builder.create();
+    }
+}
+
+ + + +

대화 표시

+ +

대화를 표시하고자 하는 경우, {@link +android.support.v4.app.DialogFragment}의 인스턴스를 생성한 다음 {@link android.support.v4.app.DialogFragment#show +show()}를 호출하여 {@link android.support.v4.app.FragmentManager}와 대화 프래그먼트에 대한 +태그 이름을 전달합니다.

+ +

{@link android.support.v4.app.FragmentManager}를 가져오려면 +{@link android.support.v4.app.FragmentActivity}에서 +{@link android.support.v4.app.FragmentActivity#getSupportFragmentManager()}를 호출하거나 {@link +android.support.v4.app.Fragment}로부터 {@link +android.support.v4.app.Fragment#getFragmentManager()}를 호출합니다. 예:

+ +
+public void confirmFireMissiles() {
+    DialogFragment newFragment = new FireMissilesDialogFragment();
+    newFragment.show(getSupportFragmentManager(), "missiles");
+}
+
+ +

두 번째 인수 {@code "missiles"}는 시스템이 +필요에 따라 프래그먼트의 상태를 저장하고 복원하는 데 사용하는 고유한 태그 이름입니다. 이 태그를 사용하면 {@link android.support.v4.app.FragmentManager#findFragmentByTag +findFragmentByTag()}를 호출하여 해당 프래그먼트를 파악할 수도 있습니다. +

+ + + + +

대화를 전체 화면으로 또는 포함된 프래그먼트로 표시

+ +

UI 디자인에서, 몇몇 상황 하에서는 UI의 한 조각을 대화로 나타내지만 +다른 상황에서는 전체 화면이나 포함된 프래그먼트로 나타내고자 하는 경우가 있을 수 +있습니다(이는 어쩌면 기기 화면이 대형인지 소형인지에 따라 달라질 수도 있습니다). {@link android.support.v4.app.DialogFragment} +클래스에서 이런 유연성을 제공하는 것은 이것이 여전히 포함 가능한 {@link +android.support.v4.app.Fragment} 역할을 할 수 있기 때문입니다.

+ +

그러나 이 경우에는 대화를 구축하는 데 {@link android.app.AlertDialog.Builder AlertDialog.Builder} +또는 다른 {@link android.app.Dialog} 객체를 사용하면 안 됩니다. +{@link android.support.v4.app.DialogFragment}를 포함 가능한 상태로 만들려면, +레이아웃 안에 있는 대화의 UI를 정의해야 합니다. 그런 다음 레이아웃을 +{@link android.support.v4.app.DialogFragment#onCreateView +onCreateView()} 콜백에 로딩합니다.

+ +

다음은 대화 또는 포함 가능한 프래그먼트 중 어느 쪽으로든 표시될 수 있는 +{@link android.support.v4.app.DialogFragment} 예시입니다(purchase_items.xml이라는 레이아웃 사용).

+ +
+public class CustomDialogFragment extends DialogFragment {
+    /** The system calls this to get the DialogFragment's layout, regardless
+        of whether it's being displayed as a dialog or an embedded fragment. */
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+            Bundle savedInstanceState) {
+        // Inflate the layout to use as dialog or embedded fragment
+        return inflater.inflate(R.layout.purchase_items, container, false);
+    }
+  
+    /** The system calls this only when creating the layout in a dialog. */
+    @Override
+    public Dialog onCreateDialog(Bundle savedInstanceState) {
+        // The only reason you might override this method when using onCreateView() is
+        // to modify any dialog characteristics. For example, the dialog includes a
+        // title by default, but your custom layout might not need it. So here you can
+        // remove the dialog title, but you must call the superclass to get the Dialog.
+        Dialog dialog = super.onCreateDialog(savedInstanceState);
+        dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
+        return dialog;
+    }
+}
+
+ +

그리고 다음은 프래그먼트를 대화로 표시할지 전체 화면 UI로 표시할지 화면 크기를 근거로 결정하는 몇 가지 코드입니다. +

+ +
+public void showDialog() {
+    FragmentManager fragmentManager = getSupportFragmentManager();
+    CustomDialogFragment newFragment = new CustomDialogFragment();
+    
+    if (mIsLargeLayout) {
+        // The device is using a large layout, so show the fragment as a dialog
+        newFragment.show(fragmentManager, "dialog");
+    } else {
+        // The device is smaller, so show the fragment fullscreen
+        FragmentTransaction transaction = fragmentManager.beginTransaction();
+        // For a little polish, specify a transition animation
+        transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
+        // To make it fullscreen, use the 'content' root view as the container
+        // for the fragment, which is always the root view for the activity
+        transaction.add(android.R.id.content, newFragment)
+                   .addToBackStack(null).commit();
+    }
+}
+
+ +

프래그먼트 트랜잭션을 수행하는 것에 대한 자세한 내용은 +프래그먼트 가이드를 참조하십시오.

+ +

이 예시에서는 mIsLargeLayout 부울이 현재 기기가 앱의 큰 레이아웃 디자인을 써야 할지를 + 나타냅니다(따라서 이 프래그먼트를 전체 화면보다는 대화로 표시). + 이런 종류의 부울을 설정하는 가장 좋은 방법은 +부울 리소스 값을 +여러 가지 화면 크기에 대한 대체 리소스 값으로 선언하는 것입니다. +예를 들어 다음은 여러 가지 화면 크기에 대한 두 가지 버전의 부울 리소스입니다.

+ +

res/values/bools.xml

+
+<!-- Default boolean values -->
+<resources>
+    <bool name="large_layout">false</bool>
+</resources>
+
+ +

res/values-large/bools.xml

+
+<!-- Large screen boolean values -->
+<resources>
+    <bool name="large_layout">true</bool>
+</resources>
+
+ +

그러면 액티비티의 +{@link android.app.Activity#onCreate onCreate()} 메서드 중에 {@code mIsLargeLayout} 값을 초기화할 수 있습니다.

+ +
+boolean mIsLargeLayout;
+
+@Override
+public void onCreate(Bundle savedInstanceState) {
+    super.onCreate(savedInstanceState);
+    setContentView(R.layout.activity_main);
+
+    mIsLargeLayout = getResources().getBoolean(R.bool.large_layout);
+}
+
+ + + +

액티비티를 큰 화면에 대화로 표시

+ +

작은 화면의 경우 대화를 전체 화면 UI로 표시하는 대신, 큰 화면에 있을 때에는 +{@link android.app.Activity}를 대화로 표시함으로써 같은 결과를 얻을 수 있습니다. + 어느 방식을 사용할 것인지는 앱 디자인에 따라 달라지지만, +액티비티를 대화로 표시하면 앱이 이미 작은 화면용으로 디자인된 상태에서 +태블릿에서의 환경을 개선하기 위해 일시적인 액티비티를 대화로 표시하는 경우 +유용할 때가 많습니다.

+ +

큰 화면의 경우 액티비티를 대화로만 표시하려면, +{@link android.R.style#Theme_Holo_DialogWhenLarge Theme.Holo.DialogWhenLarge} + 테마를 {@code +<activity>} 매니페스트 요소에 적용하면 됩니다.

+ +
+<activity android:theme="@android:style/Theme.Holo.DialogWhenLarge" >
+
+ +

액티비티를 여러 가지 테마로 스타일링하는 법에 대한 자세한 정보는 스타일 및 테마 가이드를 참조하십시오.

+ + + +

대화 무시

+ +

사용자가 +{@link android.app.AlertDialog.Builder}로 생성한 작업 버튼 중 하나라도 터치하면 시스템이 대화를 대신 무시합니다.

+ +

시스템은 사용자가 대화 목록에서 항목을 터치하는 경우에도 대화를 무시합니다. +다만 목록이 무선 버튼이나 확인란을 사용하는 경우에는 예외입니다. +그렇지 않으면 대화를 수동으로 무시할 수도 있습니다. {@link +android.support.v4.app.DialogFragment}에서 {@link android.support.v4.app.DialogFragment#dismiss()}를 호출하면 됩니다.

+ +

+대화가 사라질 때 특정 작업을 수행해야 하는 경우, {@link +android.support.v4.app.DialogFragment}에서 @link +android.support.v4.app.DialogFragment#onDismiss onDismiss()}를 구현하면 됩니다.

+ +

또한 대화를 취소할 수도 있습니다. 이것은 사용자가 작업을 완료하지 않고 대화를 +분명히 떠났다는 것을 나타내는 특수 이벤트입니다. 이것은 사용자가 +뒤로 버튼을 누르거나 대화 영역 바깥의 화면을 터치하거나, +개발자가 {@link +android.app.Dialog}에서 명시적으로 {@link android.app.Dialog#cancel()}을 호출한 경우 발생합니다(예: 대화의 "취소" 버튼에 대한 응답으로).

+ +

위의 예시에 나타난 바와 같이 취소 이벤트에 응답하려면 {@link +android.support.v4.app.DialogFragment} 클래스에서 +{@link android.support.v4.app.DialogFragment#onCancel onCancel()}을 구현하면 됩니다.

+ +

참고: 시스템은 +{@link android.support.v4.app.DialogFragment#onCancel onCancel()} 콜백을 불러오는 이벤트가 발생할 때마다 +{@link android.support.v4.app.DialogFragment#onDismiss onDismiss()}를 호출합니다. +그러나 {@link android.app.Dialog#dismiss Dialog.dismiss()} 또는 {@link +android.support.v4.app.DialogFragment#dismiss DialogFragment.dismiss()}를 호출하면 +시스템은 {@link android.support.v4.app.DialogFragment#onDismiss onDismiss()}는 호출하지만 {@link android.support.v4.app.DialogFragment#onCancel onCancel()}은 +호출하지 않습니다. 따라서 사용자가 대화를 보기에서 제거하기 위해 대화에 있는 +긍정적인 버튼을 누르는 경우, 일반적으로 {@link android.support.v4.app.DialogFragment#dismiss dismiss()}를 +사용해야 합니다.

+ + diff --git a/docs/html-intl/intl/ko/guide/topics/ui/menus.jd b/docs/html-intl/intl/ko/guide/topics/ui/menus.jd new file mode 100644 index 0000000000000000000000000000000000000000..c115c2a7c3badc8f6db23ecebf599176a092a64f --- /dev/null +++ b/docs/html-intl/intl/ko/guide/topics/ui/menus.jd @@ -0,0 +1,1031 @@ +page.title=메뉴 +parent.title=사용자 인터페이스 +parent.link=index.html +@jd:body + + + +

메뉴는 수많은 유형의 애플리케이션에서 사용되는 보편적인 사용자 인터페이스 구성 요소입니다. 친숙하고 일관적인 +사용자 경험을 제공하기 위해 액티비티에서 사용자 작업과 다른 옵션을 제시하는 {@link android.view.Menu} API를 +사용해야 합니다.

+ +

Android 3.0(API 레벨 11)부터 Android 구성 기기는 +전용 메뉴 버튼을 하지 않아도 됩니다. 이번 변경 사항부터 Android 앱은 +종래의 6항목 메뉴 패널에 의존하지 않고 작업 모음을 사용하여 +공통 사용자 작업을 표현합니다.

+ +

일부 메뉴 항목의 디자인과 사용자 경험이 변경되었지만, 작업과 옵션 세트를 정의하는 +의미 체계 세트는 여전히 {@link android.view.Menu} API를 기초로 사용합니다. 이 가이드는 +모든 버전의 Android에서 세 가지 기본적인 유형의 메뉴나 작업 표시를 +생성하는 법을 보여줍니다.

+ +
+
옵션 메뉴 및 작업 모음
+
옵션 메뉴는 액티비티에 대한 기본 메뉴 항목 컬렉션 +입니다. +이곳에 "검색", "이메일 작성" 및 "설정"과 같이 앱에 전체적인 영향을 미치는 작업을 배치해야 합니다. +

Android 2.3 이하를 대상으로 개발하는 경우 사용자는 +메뉴 버튼을 눌러서 옵션 메뉴 패널을 표시할 수 있습니다.

+

Android 3.0 이상의 경우, 옵션 메뉴의 항목은 작업 모음에서 화면 작업 항목과 오버플로 옵션의 조합으로 +표시됩니다. Android 3.0부터 메뉴 버튼의 사용이 중단되므로(일부 +기기는 +메뉴 버튼이 없습니다), 작업과 다른 옵션에 대한 액세스를 제공하려면 작업 모음을 +사용하기 시작해야 합니다.

+

옵션 메뉴 만들기 섹션을 참조하십시오.

+
+ +
컨텍스트 메뉴 및 상황별 작업 모드
+ +
컨텍스트 메뉴는 사용자가 요소를 길게 클릭하면 나타나는 부동 메뉴 +입니다. 이것은 선택한 콘텐츠나 컨텍스트 프레임에 +영향을 주는 작업을 제공합니다. +

Android 3.0 이상을 대상으로 개발하는 경우, 상황별 작업 모드를 사용하여 선택한 콘텐츠의 작업을 활성화해야 합니다. 이 모드는 +화면 위에 있는 막대에서 선택된 콘텐츠에 영향을 미치는 작업 항목을 표시하고 사용자가 +여러 항목을 선택할 수 있습니다.

+

상황별 메뉴 만들기에 관한 섹션을 참조하십시오.

+
+ +
팝업 메뉴
+
팝업 메뉴는 메뉴를 호출하는 보기에 고정된 수직 목록에서 항목 목록을 +표시합니다. 이것은 정 콘텐츠와 관련이 되는 작업의 오버플로를 제공하거나 +명령의 두 번째 부분에 대한 옵션을 제공하는 데 좋습니다. 팝업 메뉴의 작업은 +해당 콘텐츠에 직접적 영향을 미쳐서는 안 됩니다. 이는 상황별 작업의 역할입니다. + 팝업 메뉴는 그보다는 티비티의 콘텐츠 영역과 관련이 있는 +확장 작업을 위한 것입니다. +

팝업 메뉴 만들기 섹션을 참조하십시오.

+
+
+ + + +

XML로 메뉴 정의

+ +

모든 메뉴 유형에 대하여, Android는 표준 XML 형식으로 메뉴 항목을 정의합니다. +액티비티 코드에서 메뉴를 구축하는 대신 +XML 메뉴 리소스에서 메뉴와 모든 항목을 정의해야 합니다. 그러면 +액티비티나 프래그먼트에서 메뉴 리소스를 팽창시킬 수 있습니다({@link android.view.Menu} 개체로 로딩하면 됩니다). +

+ +

메뉴 리소스를 사용하는 것이 좋은 이유로 다음과 같은 몇 가지를 들 수 있습니다.

+
    +
  • 메뉴 구조를 XML로 시작화하면 다른 방식보다 쉽습니다.
  • +
  • 애플리케이션의 동작 코드에서 메뉴의 콘텐츠를 분리합니다.
  • +
  • 앱 리소스 프레임워크를 활용하여 다양한 플랫폼 버전, 화면 크기 및 기타 구성에 대한 +대체 메뉴 프레임워크를 생성할 수 있습니다.
  • +
+ +

메뉴를 정의하려면 프로젝트의 res/menu/ +디렉터리에서 XML 파일을 생성하고 다음 요소로 메뉴를 구축합니다.

+
+
<menu>
+
메뉴 항목의 컨테이너인 {@link android.view.Menu}를 정의합니다. +<menu> 요소는 파일의 루트 노드여야 하고 하나 이상의 +<item><group> 요소를 보유할 수 있습니다.
+ +
<item>
+
메뉴 안의 항목 하나를 나타내는 {@link android.view.MenuItem}을 생성합니다. +이 요소 안에는 하위 메뉴를 생성하기 위한 중첩 <menu> 요소가 들어있을 수 있습니다.
+ +
<group>
+
{@code <item>} 요소를 위한 선택적인 투명 컨테이너입니다. 이것을 사용하면 활성 상태와 가시성 등의 속성을 공유할 수 있도록 +메뉴 항목을 분류하도록 해줍니다. +자세한 정보를 보려면 메뉴 그룹 만들기를 참조하십시오.
+
+ + +

다음은 game_menu.xml이라는 메뉴의 예시입니다.

+
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:id="@+id/new_game"
+          android:icon="@drawable/ic_new_game"
+          android:title="@string/new_game"
+          android:showAsAction="ifRoom"/>
+    <item android:id="@+id/help"
+          android:icon="@drawable/ic_help"
+          android:title="@string/help" />
+</menu>
+
+ +

<item> 요소는 항목의 외관과 동작을 +정의하는 데 사용할 수 있는 여러 속성을 지원합니다. 위 메뉴의 항목에는 다음 속성이 포함되어 있습니다.

+ +
+
{@code android:id}
+
항목에 고유한 리소스 ID입니다. 사용자가 항목을 선택하면 +애플리케이션이 이것으로 해당 항목을 인식할 수 있게 해줍니다.
+
{@code android:icon}
+
항목의 아이콘으로 사용할 수 있는 드로어블에 대한 참조입니다.
+
{@code android:title}
+
항목의 제목으로 사용할 문자열에 대한 참조입니다.
+
{@code android:showAsAction}
+
작업 모음에서 작업 항목을 나타낼 시기와 방법을 나타냅니다.
+
+ +

이들이 여러분이 꼭 사용해야 할 가장 중요한 속성이지만, 사용할 수 있는 요소는 이외에도 많이 있습니다. +모든 지원 속성에 관한 정보는 메뉴 리소스 문서를 참조하십시오.

+ +

{@code <menu>} 요소를 +{@code <item>}의 요소로 추가하면 어떤 메뉴 항목이든 하위 메뉴를 추가할 수 있습니다(하위 메뉴 제외). 하위 메뉴는 애플리케이션에 PC 애플리케이션의 메뉴 막대(파일, +편집, 보기 등)의 항목과 같이 주제별로 체계화할 수 있는 기능이 많을 때 +유용합니다. 예:

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:id="@+id/file"
+          android:title="@string/file" >
+        <!-- "file" submenu -->
+        <menu>
+            <item android:id="@+id/create_new"
+                  android:title="@string/create_new" />
+            <item android:id="@+id/open"
+                  android:title="@string/open" />
+        </menu>
+    </item>
+</menu>
+
+ +

액티비티에서 메뉴를 사용하려면 +{@link android.view.MenuInflater#inflate(int,Menu) +MenuInflater.inflate()}를 사용하여 메뉴 리소스를 팽창해야 합니다(XML 리소스를 프로그램 가능 개체로 변환). 다음 섹션에서는 각 메뉴 유형에 대해 +메뉴를 팽창하는 방법을 보여줍니다.

+ + + +

옵션 메뉴 만들기

+ +
+ +

그림 1. Android 2.3에서 실행되는 +브라우저의 옵션 메뉴입니다.

+
+ +

옵션 메뉴에 "검색", "이메일 작성" 및 "설정"과 같이 +현재 액티비티 컨텍스트와 관련이 있는 작업과 다른 옵션을 포함해야 합니다.

+ +

화면의 옵션 메뉴에 항목이 나타나는 위치는 +개발자가 애플리케이션을 어느 버전에서 개발했는지에 따라 달라집니다.

+ +
    +
  • Android 2.3.x(API 레벨 10) +이하에서 애플리케이션을 개발했을 경우, +그림 1과 같이 사용자가 메뉴 버튼을 누르면 화면 아래에 옵션 메뉴의 콘텐츠가 나타납니다. 이것이 열렸을 때 가장 먼저 보이는 부분은 +아이콘 +메뉴이고, 이는 최대 여섯 개의 메뉴 항목을 보유합니다. 메뉴에 여섯 개를 넘는 항목이 포함되어 있는 경우, Android는 +여섯 번째 항목과 나머지 항목을 더보기 메뉴에 배치합니다. 이것은 사용자가 +더보기를 선택하면 열 수 있습니다.
  • + +
  • Android 3.0(API 레벨 11) +이상에서 애플리케이션을 개발했을 경우, 옵션 메뉴의 항목은 작업 모음에서 이용할 수 있습니다. 기본적으로 시스템은 모든 항목을 작업 더보기에 배치합니다. +사용자는 작업 모음 오른쪽에 있는 +작업 더보기 아이콘으로 이를 표시할 수 있습니다(또는 이용할 수 있을 경우 기기 메뉴 버튼을 누르면 됩니다). +중요한 작업에 대한 빠른 액세스를 + 활성화하려면 +{@code android:showAsAction="ifRoom"}을 해당 {@code <item>} 요소에 추가하여 몇 가지 항목이 작업 모음에 표시되도록 수준을 올립니다(그림 +2 참조).

    작업 항목과 다른 작업 모음 동작에 관한 자세한 정보는 작업 모음 가이드를 참조하십시오.

    +

    참고: Andoid 3.0 이상을 대상으로 개발하지 않더라도 +개발자 나름의 작업 모음 레이아웃을 구축하여 비슷한 효과를 낼 수 있습니다. 예를 들어, +작업 모음이 포함된 Android 이전 버전을 지원하는 방법은 작업 모음 호환성 +샘플을 참조하십시오.

    +
  • +
+ + +

그림 2. Honeycomb 갤러리 앱의 작업 모음으로 +탐색 탭과 카메라 작업 항목(및 작업 더보기 버튼)을 표시합니다.

+ +

{@link android.app.Activity} +하위 클래스나 {@link android.app.Fragment} 하위 클래스에서 옵션 메뉴용 항목을 선언할 수 있습니다. 액티비티와 프래그먼트가 모두 +옵션 메뉴용 항목을 선언할 경우, 이들은 UI에서 조합됩니다. 액티비티의 항목이 먼저 나타나고, +뒤이어 각 프래그먼트의 항목이 나타나며 이때 순서는 각 프래그먼트가 액티비티에 추가된 순서를 +따릅니다. 필요한 경우 이동해야 하는 각 {@code <item>}에서{@code android:orderInCategory} 속성이 포함된 +메뉴 항목을 다시 정렬할 수 있습니다.

+ +

액티비티에 대한 옵션 메뉴를 지정하려면 {@link +android.app.Activity#onCreateOptionsMenu(Menu) onCreateOptionsMenu()}를 재정의합니다(프래그먼트는 +자신만의 {@link android.app.Fragment#onCreateOptionsMenu onCreateOptionsMenu()} 콜백을 제공합니다). 이 메서드에서 +(XML에서 정의된) 메뉴 리소스를 콜백에서 제공된 {@link +android.view.Menu}로 팽창할 수 있습니다. 예:

+ +
+@Override
+public boolean onCreateOptionsMenu(Menu menu) {
+    MenuInflater inflater = {@link android.app.Activity#getMenuInflater()};
+    inflater.inflate(R.menu.game_menu, menu);
+    return true;
+}
+
+ +

또한, {@link android.view.Menu#add(int,int,int,int) +add()}를 사용하여 메뉴 항목을 추가하고 {@link android.view.Menu#findItem findItem()}로 항목을 검색하여 +{@link android.view.MenuItem} API로 속성을 수정할 수 있습니다.

+ +

Android 2.3.x 이하를 대상으로 애플리케이션을 개발했을 경우, 사용자가 처음으로 메뉴를 열었을 때 시스템이 {@link +android.app.Activity#onCreateOptionsMenu(Menu) onCreateOptionsMenu()}를 +호출합니다. Android 3.0 이상을 대상으로 개발할 경우, +액티비티를 시작할 때 작업 모음에 항목을 보여주기 위해 시스템이 {@link android.app.Activity#onCreateOptionsMenu(Menu) onCreateOptionsMenu()}를 +호출합니다.

+ + + +

클릭 이벤트 처리

+ +

사용자가 옵션 메뉴에서 항목을 선택하면(작업 모음의 작업 항목 포함), +시스템이 액티비티의 {@link android.app.Activity#onOptionsItemSelected(MenuItem) +onOptionsItemSelected()} 메서드를 호출합니다. 이 메서드가 선택한 {@link android.view.MenuItem}을 전달합니다. 항목을 식별하려면 +{@link android.view.MenuItem#getItemId()}을 호출하면 됩니다. 이 메서드는(메뉴 리소스의 {@code android:id} 속성으로 지정되거나 +{@link android.view.Menu#add(int,int,int,int) add()} 메서드에 제공된 정수가 포함된) 메뉴 항목에 대한 고유 ID를 반환합니다 +. 이 ID와 +알려진 메뉴 항목을 일치시켜 적절한 작업을 수행할 수 있습니다. 예:

+ +
+@Override
+public boolean onOptionsItemSelected(MenuItem item) {
+    // Handle item selection
+    switch (item.getItemId()) {
+        case R.id.new_game:
+            newGame();
+            return true;
+        case R.id.help:
+            showHelp();
+            return true;
+        default:
+            return super.onOptionsItemSelected(item);
+    }
+}
+
+ +

메뉴 항목을 성공적으로 처리하면 {@code true}를 반환합니다. 메뉴 항목을 +처리하지 않으면, {@link +android.app.Activity#onOptionsItemSelected(MenuItem) onOptionsItemSelected()}의 슈퍼 클래스 구현을 호출해야 합니다(기본 +구현은 '거짓'을 반환합니다).

+ +

액티비티에 프래그먼트가 포함되어 있으면, 액티비티에 대해 {@link +android.app.Activity#onOptionsItemSelected(MenuItem) onOptionsItemSelected()}를 호출하고, 그 다음에는 +하나의 프래그먼트가 +{@code true}를 반환하거나 모든 프래그먼트가 호출될 때까지 (각 프래그먼트가 추가된 순서대로) 각 프래그먼트에 대해 해당 메서드를 호출합니다.

+ +

팁: Android 3.0에는 +{@code android:onClick} 속성을 사용하여 XML에 있는 메뉴 항목에 대한 온-클릭 동작을 정의하는 기능이 추가됩니다. +속성 값은 메뉴를 사용하여 액티비티가 정의한 메서드의 이름이어야 합니다. 메서드는 +공개여야 하며 하나의 {@link android.view.MenuItem} 매개변수를 수락해야 합니다. 시스템이 이 메서드를 호출하면 +메서드가 선택한 메뉴 항목을 전달합니다. 자세한 정보와 예시는 메뉴 리소스 문서를 참조하십시오.

+ +

팁: 애플리케이션에 여러 액티비티가 포함되어 있고, 그 중 몇몇이 같은 옵션 메뉴를 제공할 경우, + +{@link android.app.Activity#onCreateOptionsMenu(Menu) +onCreateOptionsMenu()}와 {@link android.app.Activity#onOptionsItemSelected(MenuItem) +onOptionsItemSelected()} 메서드를 제외하고 아무것도 구현하지 않는 액티비티를 만드는 것을 고려해보십시오. 그런 다음 이 클래스를 같은 옵션 메뉴를 공유해야 하는 각 액티비티에 대해 +확장하면 됩니다. 이렇게 하면 메뉴 작업을 처리하는 코드 세트 하나를 관리할 수 있고, +각 하위 클래스가 메뉴 동작을 상속합니다. +이런 하위 액티비티 중 하나에 메뉴 항목을 추가하려면, +해당 액티비티에서 {@link android.app.Activity#onCreateOptionsMenu(Menu) +onCreateOptionsMenu()}를 재정의하십시오. {@code super.onCreateOptionsMenu(menu)}를 호출하여 +원래 메뉴 항목을 생성하고, {@link +android.view.Menu#add(int,int,int,int) menu.add()}이 포함된 새로운 메뉴 항목을 추가합니다. 각각의 메뉴 항목에 대한 슈퍼클래스의 동작을 +재정의할 수도 있습니다.

+ + +

런타임에 메뉴 항목 변경

+ +

시스템이 {@link android.app.Activity#onCreateOptionsMenu(Menu) +onCreateOptionsMenu()}를 호출하면, 개발자가 채우는 {@link android.view.Menu}의 인스턴스를 유지하고 +어떤 이유로 메뉴가 무효화되지 않으면{@link android.app.Activity#onCreateOptionsMenu(Menu) onCreateOptionsMenu()}를 +호출하지 않습니다. 그러나 {@link +android.app.Activity#onCreateOptionsMenu(Menu) onCreateOptionsMenu()}는 초기 +메뉴 상태를 생성할 때만 사용하고, 액티비티 수명 주기에서 변경할 때는 사용하지 않습니다.

+ +

액티비티 수명 주기에서 발생하는 이벤트에 +기반하여 옵션을 수정하고자 할 경우, 이는 +{@link android.app.Activity#onPrepareOptionsMenu(Menu) onPrepareOptionsMenu()} 메서드에서 할 수 있습니다. 이 +메서드는 현재 존재하는 상태대로 {@link android.view.Menu} 개체를 전달하므로, +항목을 추가, 삭제, 비활성화하는 등의 수정을 할 수 있습니다 (프래그먼트도 {@link +android.app.Fragment#onPrepareOptionsMenu onPrepareOptionsMenu()} 콜백을 제공합니다).

+ +

Android 2.3.x 이하에서는, 사용자가 옵션 메뉴를 열 때마다(메뉴 +버튼 누름) 시스템이{@link +android.app.Activity#onPrepareOptionsMenu(Menu) +onPrepareOptionsMenu()}를 호출합니다.

+ +

Android 3.0 이상에서는 메뉴 항목이 작업 모음에 표시되어 있을 때마다 +옵션 메뉴는 항상 열려 있는 것으로 간주됩니다. 이벤트가 발생하고 메뉴 업데이트를 수행하고자 하는 경우, +{@link android.app.Activity#invalidateOptionsMenu invalidateOptionsMenu()}를 호출하여 +시스템이 {@link android.app.Activity#onPrepareOptionsMenu(Menu) onPrepareOptionsMenu()}를 호출하도록 요청해야 합니다.

+ +

참고: +현재 초점이 맞춰져 있는 {@link android.view.View}를 기반으로 한 +옵션 메뉴의 항목을 절대로 변경해서는 안 됩니다. 터치 모드에서는(사용자가 트랙볼이나 d-패드를 사용하지 않는 경우), +보기가 초점을 취할 수 없으므로 옵션 메뉴에 있는 항목을 수정할 +근거로 초점을 사용해서는 결코 안 됩니다. {@link +android.view.View}의 컨텍스트에 민감한 메뉴를 제공하고자 하는 경우, 컨텍스트 메뉴를 사용하십시오.

+ + + + +

상황별 메뉴 만들기

+ +
+ +

그림 3. 부동 컨텍스트 메뉴(왼쪽) +와 상황별 작업 모음(오른쪽)의 스크린샷입니다.

+
+ +

상황별 메뉴는 UI에서 특정 항목이나 컨텍스트 프레임에 영향을 미치는 작업을 제공합니다. +컨텍스트 메뉴는 어느 보기에나 제공할 수 있지만, {@link +android.widget.ListView}나 {@link android.widget.GridView}, 사용자가 각 항목에서 직접 작업을 수행하는 기타 보기 컬렉션의 +항목에 가장 자주 사용합니다.

+ +

상황별 작업을 제공하는 방법에는 두 가지가 있습니다.

+
    +
  • 부동 컨텍스트 메뉴를 사용합니다. 사용자가 컨텍스트 메뉴에 대한 지원을 선언하는 보기를 길게 클릭하면 +(대화와 유사한) 메뉴 항목의 부동 목록이 +나타납니다. 사용자는 한 항목에서 한 번에 하나의 상황별 +작업을 수행할 수 있습니다.
  • + +
  • 상황별 작업 모드를 사용합니다. 이 모드는 화면 위에 있는 막대에서 선택된 항목에 영향을 미치는 작업 항목이 포함된 상황별 작업 막대를 표시하는 +{@link android.view.ActionMode}의 시스템 구현입니다. + 이 모드가 활성 상태이면 사용자는 +여러 개의 항목에서 한 작업을 한꺼번에 수행할 수 있습니다(앱이 이를 허용하는 경우).
  • +
+ +

참고: 상황별 작업 모드는 Android 3.0(API +레벨 11) 이상에서 이용할 수 있으며, 이용 가능할 때 컨텍스트 작업 표시용으로 기본 설정된 기술입니다. + 앱이 3.0 이하의 버전을 지원할 경우 해당 기기에서는 +부동 컨텍스트 메뉴로 돌아가야 합니다.

+ + +

부동 컨텍스트 메뉴 만들기

+ +

부동 컨텍스트 메뉴를 제공하려면 다음과 같이 합니다.

+
    +
  1. 컨텍스트 메뉴가 연관되어야 하는 {@link android.view.View}를 등록합니다. 그러려면 +{@link android.app.Activity#registerForContextMenu(View) registerForContextMenu()}를 호출하고 여기에 +{@link android.view.View}를 전달하면 됩니다. +

    액티비티가 {@link android.widget.ListView} 또는 {@link android.widget.GridView}를 사용하고 +각 항목이 같은 컨텍스트 메뉴를 제공하게 하고 싶을 경우, +{@link android.widget.ListView}나 {@link android.widget.GridView}를 {@link +android.app.Activity#registerForContextMenu(View) registerForContextMenu()}에 전달하여 컨텍스트 메뉴에 모든 항목을 등록합니다.

    +
  2. + +
  3. {@link android.app.Activity}나 {@link android.app.Fragment}에서 {@link +android.view.View.OnCreateContextMenuListener#onCreateContextMenu onCreateContextMenu()} 메서드를 +구현합니다. +

    등록된 보기가 롱-클릭 이벤트를 수신하면, 시스템이 {@link +android.view.View.OnCreateContextMenuListener#onCreateContextMenu onCreateContextMenu()} +메서드를 호출합니다. 이곳이 바로 메뉴 항목을 정의하는 곳입니다. 주로 메뉴 리소스를 팽창시키는 방법을 씁니다. 예: +

    +
    +@Override
    +public void onCreateContextMenu(ContextMenu menu, View v,
    +                                ContextMenuInfo menuInfo) {
    +    super.onCreateContextMenu(menu, v, menuInfo);
    +    MenuInflater inflater = getMenuInflater();
    +    inflater.inflate(R.menu.context_menu, menu);
    +}
    +
    + +

    {@link android.view.MenuInflater}를 사용하면 메뉴 리소스에서 컨텍스트 메뉴를 팽창하게 해줍니다. 콜백 메서드 +매개변수에는 사용자가 선택한 {@link android.view.View}와 +선택한 항목에 대한 추가 정보를 제공하는 {@link android.view.ContextMenu.ContextMenuInfo} 객체가 +포함됩니다. 액티비티에 여러 개의 보기가 있고 이들이 각각 서로 다른 컨텍스트 메뉴를 제공하는 경우, +이와 같은 매개변수를 사용하여 팽창할 컨텍스트 메뉴가 무엇인지 +판별할 수 있습니다.

    +
  4. + +
  5. {@link android.app.Activity#onContextItemSelected(MenuItem) +onContextItemSelected()}를 구현합니다. +

    사용자가 메뉴 항목을 선택할 경우, 시스템이 이 메서드를 호출하여 +적절한 작업을 수행하게 해줍니다. 예:

    + +
    +@Override
    +public boolean onContextItemSelected(MenuItem item) {
    +    AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo();
    +    switch (item.getItemId()) {
    +        case R.id.edit:
    +            editNote(info.id);
    +            return true;
    +        case R.id.delete:
    +            deleteNote(info.id);
    +            return true;
    +        default:
    +            return super.onContextItemSelected(item);
    +    }
    +}
    +
    + +

    {@link android.view.MenuItem#getItemId()} 메서드는 선택된 메뉴 항목의 ID를 쿼리하고, + +XML에서 메뉴 정의 섹션에 나타난 바와 같이 {@code +android:id} 속성을 사용하여 XML의 각 메뉴 항목에 이를 할당해야 합니다.

    + +

    메뉴 항목을 성공적으로 처리하면 {@code true}를 반환합니다. 메뉴 항목을 처리하지 않는 경우, +해당 메뉴 항목을 슈퍼클래스 구현에 전달해야 합니다. 액티비티에 프래그먼트가 포함되어 있는 경우 +해당 액티비티가 첫 번째로 이 콜백을 수신합니다. 처리되지 않을 때 슈퍼클래스를 호출하면 시스템이 +이벤트를 각 프래그먼트의 각 콜백 메서드에 전달합니다. +{@code true} 또는 {@code false}가 반환될 때까지 (각 프래그먼트가 추가된 순서대로) 한 번에 하나씩 전달됩니다 ( +{@link android.app.Activity}와 {@code android.app.Fragment}의 기본 구현이 {@code +false}를 반환하므로 처리되지 않을 때는 언제나 슈퍼 클래스를 호출해야 합니다).

    +
  6. +
+ + +

상황별 동작 모드 사용

+ +

상황별 작업 모드는 사용자 상호 작용을 컨텍스트 작업 수행에 집중시키는 {@link android.view.ActionMode}의 +시스템 구현입니다. 사용자가 항목을 선택하여 +이 모드를 활성화하면, 상황별 작업 모음이 화면 위에 나타나서 +사용자가 현재 선택된 항목에서 수행할 수 있는 작업을 표시합니다. 이 모드가 +활성화되면 사용자는 여러 항목을 선택하고(개발자가 이를 허용하는 경우), 항목을 선택 해제하고, 액티비티 내에서 +탐색을 계속합니다(개발자가 허용하고자 하는 만큼). 사용자가 모든 항목에서 선택을 해제하고, '뒤로' 버튼을 누르거나 + +작업 모음 왼쪽의 완료 작업을 누르면 작업 모드가 비활성화되고 상황별 작업 모음이 사라집니다.

+ +

참고: 상황별 작업 모음이 +반드시 작업 모음과 연관되어 있는 것은 아닙니다. 이들은 서로 +독립적으로 작동합니다. 이는 겉으로 보기에는 상황별 작업 모음이 작업 모음의 위치를 능가하는 것으로 +보이더라도 적용됩니다.

+ +

Android 3.0 (API level 11) 이상을 대상으로 개발하는 경우, +일반적으로 상황별 작업 모드를 사용하여 부동 컨텍스트 메뉴가 아닌 상황별 작업을 표시합니다.

+ +

상황별 작업을 제공하는 보기의 경우, 일반적으로 두 이벤트 중 하나(또는 두 가지 모두)에서 상황별 작업 모드를 +호출해야 합니다.

+
    +
  • 사용자가 보기에서 롱-클릭을 수행합니다.
  • +
  • 사용자가 보기에서 확인란 또는 그와 유사한 UI 구성 요소를 선택합니다.
  • +
+ +

애플리케이션이 상황별 작업 모드를 호출하고 디자인에 따라 각 작업의 동작을 +정의합니다. 기본적으로 두 가지 디자인이 있습니다.

+
    +
  • 개별적인 임의의 보기에 대한 상황별 작업의 경우.
  • +
  • {@link +android.widget.ListView} 또는 {@link android.widget.GridView}에서 항목 그룹의 일괄 상황별 작업의 경우(사용자가 여러 항목을 +선택하도록 허용하고 모든 항목에서 작업을 수행).
  • +
+ +

다음 섹션은 각 시나리오에 필요한 설정을 설명합니다.

+ + +

각각의 보기에 대한 상황별 작업 모드의 활성화

+ +

사용자가 특정 보기를 선택했을 때만 상황별 작업 모드를 호출하고자 하는 경우 +다음과 같이 해야 합니다.

+
    +
  1. {@link android.view.ActionMode.Callback} 인터페이스를 구현합니다. 콜백 메서드에서 +상황별 작업 모음의 작업을 지정하고, 작업 항목에 대한 클릭 이벤트에 응답하고, 작업 모드에 대한 +다른 수명 주기 이벤트를 처리합니다.
  2. +
  3. 모음을 표시하고자 하는 경우{@link android.app.Activity#startActionMode startActionMode()}를 호출합니다 +(사용자가 보기를 롱-클릭하는 경우).
  4. +
+ +

예:

+ +
    +
  1. {@link android.view.ActionMode.Callback ActionMode.Callback} 인터페이스를 구현합니다. +
    +private ActionMode.Callback mActionModeCallback = new ActionMode.Callback() {
    +
    +    // Called when the action mode is created; startActionMode() was called
    +    @Override
    +    public boolean onCreateActionMode(ActionMode mode, Menu menu) {
    +        // Inflate a menu resource providing context menu items
    +        MenuInflater inflater = mode.getMenuInflater();
    +        inflater.inflate(R.menu.context_menu, menu);
    +        return true;
    +    }
    +
    +    // Called each time the action mode is shown. Always called after onCreateActionMode, but
    +    // may be called multiple times if the mode is invalidated.
    +    @Override
    +    public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
    +        return false; // Return false if nothing is done
    +    }
    +
    +    // Called when the user selects a contextual menu item
    +    @Override
    +    public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
    +        switch (item.getItemId()) {
    +            case R.id.menu_share:
    +                shareCurrentItem();
    +                mode.finish(); // Action picked, so close the CAB
    +                return true;
    +            default:
    +                return false;
    +        }
    +    }
    +
    +    // Called when the user exits the action mode
    +    @Override
    +    public void onDestroyActionMode(ActionMode mode) {
    +        mActionMode = null;
    +    }
    +};
    +
    + +

    이벤트 콜백은 옵션 메뉴의 콜백과 거의 똑같다는 점을 눈여겨 보십시오. 단, 각 콜백은 이벤트와 연결된 {@link +android.view.ActionMode} 객체를 전달합니다. {@link +android.view.ActionMode} API를 사용하여 +{@link android.view.ActionMode#setTitle setTitle()}과 {@link +android.view.ActionMode#setSubtitle setSubtitle()}이 포함된 제목과 하위 제목을 수정하는 등과 같이 CAB를 다양하게 변경합니다(몇 개의 +항목이 선택되었는지 나타낼 때 유용합니다).

    + +

    또한, 위 샘플은 작업 모드가 소멸될 때 {@code mActionMode} 변수를 null로 +설정한다는 점도 유의하십시오. 다음 단계에서는 액티비티나 프래그먼트의 구성원 변수를 초기화하고 저장하는 방법을 +볼 수 있습니다.

    +
  2. + +
  3. {@link android.app.Activity#startActionMode startActionMode()}를 호출하여 +{@link +android.view.View}를 롱-클릭했을 경우와 같이 상황에 따라 상황별 작업 모드를 활성화합니다.

    + +
    +someView.setOnLongClickListener(new View.OnLongClickListener() {
    +    // Called when the user long-clicks on someView
    +    public boolean onLongClick(View view) {
    +        if (mActionMode != null) {
    +            return false;
    +        }
    +
    +        // Start the CAB using the ActionMode.Callback defined above
    +        mActionMode = getActivity().startActionMode(mActionModeCallback);
    +        view.setSelected(true);
    +        return true;
    +    }
    +});
    +
    + +

    {@link android.app.Activity#startActionMode startActionMode()}를 호출하면 시스템이 +생성된 {@link android.view.ActionMode}를 반환합니다. 이것을 구성원 변수에 저장하면 +다른 이벤트에 대한 응답으로 상황별 작업 모음을 변경할 수 있습니다. 위 샘플에서 +{@link android.view.ActionMode}를 사용하여 {@link android.view.ActionMode} 인스턴스가 이미 활성화되었을 경우 +작업 모드를 시작하기 전에 구성원이 null인지 여부를 점검하여 +해당 인스턴스가 재생성되지 않게 합니다.

    +
  4. +
+ + + +

ListView 또는 GridView에서 일괄 상황별 작업 활성화

+ +

{@link android.widget.ListView} 또는 {@link +android.widget.GridView}(또는 {@link android.widget.AbsListView}의 또 다른 확장)에 항목 컬렉션이 있고 +사용자가 일괄 작업을 수행하도록 허용하려면 다음과 같이 해야 합니다.

+ +
    +
  • {@link android.widget.AbsListView.MultiChoiceModeListener} 인터페이스를 구현하고 이를 +{@link android.widget.AbsListView#setMultiChoiceModeListener +setMultiChoiceModeListener()}가 있는 보기 그룹에 대해 설정합니다. 수신기 콜백 메서드에서 상황별 작업 모음에 대한 +작업을 지정하고, 작업 항목에 대한 클릭 이벤트에 대응하고, +{@link android.view.ActionMode.Callback} 인터페이스에서 상속한 다른 콜백을 처리할 수 있습니다.
  • + +
  • {@link +android.widget.AbsListView#CHOICE_MODE_MULTIPLE_MODAL} 인수로 {@link android.widget.AbsListView#setChoiceMode setChoiceMode()}를 호출합니다.
  • +
+ +

예:

+ +
+ListView listView = getListView();
+listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL);
+listView.setMultiChoiceModeListener(new MultiChoiceModeListener() {
+
+    @Override
+    public void onItemCheckedStateChanged(ActionMode mode, int position,
+                                          long id, boolean checked) {
+        // Here you can do something when items are selected/de-selected,
+        // such as update the title in the CAB
+    }
+
+    @Override
+    public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
+        // Respond to clicks on the actions in the CAB
+        switch (item.getItemId()) {
+            case R.id.menu_delete:
+                deleteSelectedItems();
+                mode.finish(); // Action picked, so close the CAB
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    @Override
+    public boolean onCreateActionMode(ActionMode mode, Menu menu) {
+        // Inflate the menu for the CAB
+        MenuInflater inflater = mode.getMenuInflater();
+        inflater.inflate(R.menu.context, menu);
+        return true;
+    }
+
+    @Override
+    public void onDestroyActionMode(ActionMode mode) {
+        // Here you can make any necessary updates to the activity when
+        // the CAB is removed. By default, selected items are deselected/unchecked.
+    }
+
+    @Override
+    public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
+        // Here you can perform updates to the CAB due to
+        // an {@link android.view.ActionMode#invalidate} request
+        return false;
+    }
+});
+
+ +

완료되었습니다. 이제 사용자가 롱-클릭하여 항목을 선택하면 시스템이 {@link +android.widget.AbsListView.MultiChoiceModeListener#onCreateActionMode onCreateActionMode()} +메서드를 호출하고 지정된 작업으로 상황별 작업 모음을 표시합니다. 상황별 작업 모음이 표시되는 동안 +사용자가 추가 항목을 선택할 수 있습니다.

+ +

상황별 작업이 공통 작업 항목을 제공하는 몇몇 경우, +확인란이나 그와 비슷한 UI요소를 추가하여 사용자가 항목을 선택할 수 있도록 해주는 것이 좋습니다. +사용자가 롱-클릭 동작을 발견하지 못할 수도 있기 때문입니다. 사용자가 확인란을 선택하면 +{@link android.widget.AbsListView#setItemChecked setItemChecked()}로 +확인된 상태에 각 목록 항목을 설정하여 상황별 작업 모드를 호출할 수 있습니다.

+ + + + +

팝업 메뉴 만들기

+ +
+ +

그림 4. Gmail 앱의 팝업 메뉴는 오른쪽 위에 있는 더보기 +버튼에 고정되어 있습니다.

+
+ +

{@link android.widget.PopupMenu}는 {@link android.view.View}에 고정된 모달 메뉴입니다. +이것은 앵커 보기 아래에 공간이 있으면 아래에, 없으면 보기 위에 나타납니다. 이것은 다음과 같은 상황에 유용합니다.

+
    +
  • 특정 콘텐츠와 관련된 작업에 대한 더보기 스타일 메뉴를 제공합니다(예: +그림 4의 Gmail 이메일 헤더 등). +

    참고: 이것은 컨텍스트 메뉴와는 다릅니다. 컨텍스트 메뉴는 +일반적으로 선택된 콘텐츠에 영향을 미치는 작업입니다. 선택된 +콘텐츠에 영향을 미치는 작업의 경우, 상황별 작업 모드 또는 부동 컨텍스트 메뉴를 사용하십시오.

  • +
  • 명령문의 두 번째 부분을 제공합니다(예: "추가"라고 표시된 버튼으로, +각기 다른 "추가" 옵션이 있는 팝업 메뉴를 발생시킵니다).
  • +
  • 영구적인 선택이 포함되지 않은 {@link android.widget.Spinner}와 유사한 드롭다운을 제공합니다. +
  • +
+ + +

참고: {@link android.widget.PopupMenu}는 API +레벨 11 이상에서 이용할 수 있습니다.

+ +

XML로 메뉴를 정의하는 경우, 팝업 메뉴를 표시하는 방법은 다음과 같습니다.

+
    +
  1. 자신의 생성자로 {@link android.widget.PopupMenu}를 인스턴트화합니다. 생성자는 +현재 애플리케이션 {@link android.content.Context} 및 {@link android.view.View}를 +메뉴를 고정시켜야 하는 곳에 가져갑니다.
  2. +
  3. {@link android.view.MenuInflater}를 사용하여{@link +android.widget.PopupMenu#getMenu() PopupMenu.getMenu()}가 반환한 {@link +android.view.Menu} 객체에 메뉴 리소스를 팽창합니다. API 레벨 14 이상에서는 이 대신 +{@link android.widget.PopupMenu#inflate PopupMenu.inflate()}를 사용할 수 있습니다.
  4. +
  5. {@link android.widget.PopupMenu#show() PopupMenu.show()}를 호출합니다.
  6. +
+ +

예를 들어, 다음은 팝업 메뉴를 표시하는 {@link android.R.attr#onClick android:onClick} 속성이 +있는 버튼입니다.

+ +
+<ImageButton
+    android:layout_width="wrap_content" 
+    android:layout_height="wrap_content" 
+    android:src="@drawable/ic_overflow_holo_dark"
+    android:contentDescription="@string/descr_overflow_button"
+    android:onClick="showPopup" />
+
+ +

그러면 액티비티가 이렇게 팝업 메뉴를 표시할 수 있습니다.

+ +
+public void showPopup(View v) {
+    PopupMenu popup = new PopupMenu(this, v);
+    MenuInflater inflater = popup.getMenuInflater();
+    inflater.inflate(R.menu.actions, popup.getMenu());
+    popup.show();
+}
+
+ +

API 레벨 14 이상의 경우, {@link +android.widget.PopupMenu#inflate PopupMenu.inflate()}로 메뉴를 팽창하는 두 개의 줄을 결합시킬 수 있습니다.

+ +

사용자가 항목을 선택하거나 메뉴 영역 바깥쪽을 터치하면 이 메뉴는 +무시됩니다. {@link +android.widget.PopupMenu.OnDismissListener}를 사용하여 무시 이벤트를 수신 대기할 수 있습니다.

+ +

클릭 이벤트 처리

+ +

사용자가 메뉴 항목을 선택할 때 작업을 수행하려면,{@link android.widget.PopupMenu#setOnMenuItemClickListener +setOnMenuItemclickListener()}를 호출하여 +{@link +android.widget.PopupMenu.OnMenuItemClickListener} 인터페이스를 구현하고 {@link +android.widget.PopupMenu}를 등록합니다. 사용자가 항목을 선택하면 시스템이 인터페이스에서 {@link +android.widget.PopupMenu.OnMenuItemClickListener#onMenuItemClick onMenuItemClick()} 콜백을 +호출합니다.

+ +

예:

+ +
+public void showMenu(View v) {
+    PopupMenu popup = new PopupMenu(this, v);
+
+    // This activity implements OnMenuItemClickListener
+    popup.setOnMenuItemClickListener(this);
+    popup.inflate(R.menu.actions);
+    popup.show();
+}
+
+@Override
+public boolean onMenuItemClick(MenuItem item) {
+    switch (item.getItemId()) {
+        case R.id.archive:
+            archive(item);
+            return true;
+        case R.id.delete:
+            delete(item);
+            return true;
+        default:
+            return false;
+    }
+}
+
+ + +

메뉴 그룹 만들기

+ +

메뉴 그룹은 특정한 특성을 공유하는 메뉴 항목의 컬렉션입니다. 그룹으로 다음과 같은 작업을 +할 수 있습니다.

+
    +
  • {@link android.view.Menu#setGroupVisible(int,boolean) +setGroupVisible()}로 모든 항목 표시 또는 숨기기
  • +
  • {@link android.view.Menu#setGroupEnabled(int,boolean) +setGroupEnabled()}로 모든 항목 활성화 또는 비활성화
  • +
  • {@link +android.view.Menu#setGroupCheckable(int,boolean,boolean) setGroupCheckable()}로 모든 항목이 확인 가능한지 여부 지정
  • +
+ +

메뉴 리소스의 {@code <group>} 요소 안에 {@code <item>} 요소를 중첩시키거나 +{@link +android.view.Menu#add(int,int,int,int) add()} 메서드로 그룹 ID를 지정하여 그룹을 생성할 수 있습니다.

+ +

다음은 그룹을 포함하는 메뉴 리소스의 예시입니다.

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:id="@+id/menu_save"
+          android:icon="@drawable/menu_save"
+          android:title="@string/menu_save" />
+    <!-- menu group -->
+    <group android:id="@+id/group_delete">
+        <item android:id="@+id/menu_archive"
+              android:title="@string/menu_archive" />
+        <item android:id="@+id/menu_delete"
+              android:title="@string/menu_delete" />
+    </group>
+</menu>
+
+ +

그룹에 있는 항목은 첫 항목과 같은 레벨에서 표시됩니다. 메뉴 안에 있는 세 가지 항목은 모두 +형제입니다. 그러나 이 그룹에 있는 항목 두 개의 특성을 개발자가 수정할 수 있습니다. +그룹 ID를 참조하고 위에 나령된 메서드를 사용하면 됩니다. 시스템 또한 +그룹화된 항목은 절대 분리하지 않습니다. 예를 들어, 각 항목에 대해 {@code +android:showAsAction="ifRoom"}을 선언하면, 두 가지 모두 작업 모음에 나타나거나 +작업 더보기에 나타납니다.

+ + +

확인 가능한 메뉴 항목 사용

+ +
+ +

그림 5. 확인 가능한 +항목이 있는 하위 메뉴의 스크린샷입니다.

+
+ +

메뉴는 옵션을 켜고 끄거나, 독립적 옵션에 대한 확인란으로 사용하거나, +상호 배타적인 옵션의 그룹에 대한 무선 버튼으로 사용하기 위한 인터페이스로 +유용합니다. 그림 5는 무선 버튼이 있으며 확인 가능한 항목이 포함된 하위 메뉴를 +표시합니다.

+ +

참고: (옵션 메뉴의) 아이콘 메뉴의 메뉴 항목은 +확인란이나 무선 버튼을 표시할 수 없습니다. 확인 가능한 아이콘 메뉴에서 항목을 만들기로 선택하는 경우, +상태가 변경될 때마다 아이콘 및/또는 텍스트를 교체하여 +확인된 상태를 수동으로 나타내야 합니다.

+ +

{@code <item>} 요소의 {@code +android:checkable} 속성을 사용하여 개별 메뉴 항목에 대한 확인 가능한 동작을 정의하거나 +{@code <group>} 요소에서 {@code android:checkableBehavior} 속성으로 전체 그룹에 대한 확인 가능한 동작을 사용할 수 있습니다. +예를 들어, 이 메뉴 그룹의 모든 항목은 무선 버튼으로 확인할 수 있습니다.

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+    <group android:checkableBehavior="single">
+        <item android:id="@+id/red"
+              android:title="@string/red" />
+        <item android:id="@+id/blue"
+              android:title="@string/blue" />
+    </group>
+</menu>
+
+ +

{@code android:checkableBehavior} 속성은 다음 중 하나를 수락합니다. +

+
{@code single}
+
그룹의 한 항목만 확인할 수 있습니다(무선 버튼).
+
{@code all}
+
모든 항목을 확인할 수 있습니다(확인란).
+
{@code none}
+
확인할 수 있는 항목이 없습니다.
+
+ +

{@code <item>} 요소의 {@code android:checked} 속성을 이용하여 항목에 기본 확인된 상태를 적용하고 +{@link +android.view.MenuItem#setChecked(boolean) setChecked()} 메서드로 코드 내에서 이를 변경할 수 있습니다.

+ +

확인 가능한 항목이 선택되면, 시스템이 각 항목이 선택된 콜백 메서드를 호출합니다 +(예: {@link android.app.Activity#onOptionsItemSelected(MenuItem) onOptionsItemSelected()}). 바로 여기에서 +확인란의 상태를 설정해야 합니다. 확인란이나 무선 버튼은 자신의 상태를 자동으로 +변경하지 않기 때문입니다. +{@link android.view.MenuItem#isChecked()}로 항목의 현재 상태를 (사용자가 이를 선택하기 전 상태 그대로) 쿼리하고 그런 다음 +{@link android.view.MenuItem#setChecked(boolean) setChecked()}로 확인된 상태를 설정할 수 있습니다. 예:

+ +
+@Override
+public boolean onOptionsItemSelected(MenuItem item) {
+    switch (item.getItemId()) {
+        case R.id.vibrate:
+        case R.id.dont_vibrate:
+            if (item.isChecked()) item.setChecked(false);
+            else item.setChecked(true);
+            return true;
+        default:
+            return super.onOptionsItemSelected(item);
+    }
+}
+
+ +

이런 방식으로 확인된 상태를 설정하지 않으면, 사용자가 선택했을 때 항목의 가시적 상태(확인란 또는 무선 버튼)가 +변경되지 않습니다. + 상태를 설정할 경우, 액티비티는 항목의 확인된 상태를 보존해서 +사용자가 나중에 메뉴를 열었을 때 개발자가 설정한 확인된 상태가 +보이게 합니다.

+ +

참고: +확인 가능한 메뉴 항목은 세션별 기준으로만 사용하도록 만들어져 있으며 애플리케이션이 소멸된 후에는 +저장되지 않습니다. 사용자에 대해 저장하고자 하는 애플리케이션 설정이 있으면, +공유 기본 설정으로 해당 데이터를 저장해야 합니다.

+ + + +

인텐트에 기반한 메뉴 항목 추가

+ +

{@link android.content.Intent}를 이용하여 +액티비티를 시작하는 메뉴 항목을 원할 수도 있습니다(액티비티가 본인의 애플리케이션 안에 있는 것이든 또 다른 애플리케이션에 있는 것이든 무관합니다). 사용하고자 하는 인텐트를 알고 +인텐트를 시작해야 하는 특정 메뉴 항목이 있을 경우, +항목에 대해 선택된 적절한 콜백 메서드에서 {@link android.app.Activity#startActivity(Intent) startActivity()}가 +포함된 인텐트를 실행합니다(예: {@link +android.app.Activity#onOptionsItemSelected(MenuItem) onOptionsItemSelected()} 콜백).

+ +

그러나 사용자 기기에 +해당 인텐트를 처리하는 애플리케이션이 있는지 모르는 경우, 이를 호출하는 메뉴 항목을 추가하면 +해당 인텐트가 액티비티에 대해 확인되지 못해서 메뉴 항목이 기능하지 못할 수도 있습니다. + 이것을 해결하기 위해 Android는 개발자가 동적으로 자신의 메뉴에 메뉴 항목을 추가할 수 있도록 허용합니다. +이는 Android가 기기에서 개발자의 인텐트를 처리하는 액티비티를 찾을 경우에 해당됩니다.

+ +

인텐트를 수락하는 이용 가능한 액티비티에 기반하여 메뉴 항목을 추가하려면 다음과 같이 합니다.

+
    +
  1. +카테고리 {@link android.content.Intent#CATEGORY_ALTERNATIVE} 및/또는 +{@link android.content.Intent#CATEGORY_SELECTED_ALTERNATIVE}, 기타 요구 사항으로 인텐트를 정의합니다.
  2. +
  3. {@link +android.view.Menu#addIntentOptions(int,int,int,ComponentName,Intent[],Intent,int,MenuItem[]) +Menu.addIntentOptions()}을 호출합니다. 그러면 Android가 인텐트를 수행하는 애플리케이션을 검색하고 +이들을 개발자의 메뉴에 추가합니다.
  4. +
+ +

인텐트를 만족하는 애플리케이션이 설치되어 있지 않으면, +메뉴 항목이 추가되지 않습니다.

+ +

참고: +{@link android.content.Intent#CATEGORY_SELECTED_ALTERNATIVE} 를 사용하여 화면에서 현재 선택된 +요소를 처리합니다. 그러므로 이것은 {@link +android.app.Activity#onCreateContextMenu(ContextMenu,View,ContextMenuInfo) +onCreateContextMenu()}에서 메뉴를 생성할 때만 사용해야 합니다.

+ +

예:

+ +
+@Override
+public boolean onCreateOptionsMenu(Menu menu){
+    super.onCreateOptionsMenu(menu);
+
+    // Create an Intent that describes the requirements to fulfill, to be included
+    // in our menu. The offering app must include a category value of Intent.CATEGORY_ALTERNATIVE.
+    Intent intent = new Intent(null, dataUri);
+    intent.addCategory(Intent.CATEGORY_ALTERNATIVE);
+
+    // Search and populate the menu with acceptable offering applications.
+    menu.addIntentOptions(
+         R.id.intent_group,  // Menu group to which new items will be added
+         0,      // Unique item ID (none)
+         0,      // Order for the items (none)
+         this.getComponentName(),   // The current activity name
+         null,   // Specific items to place first (none)
+         intent, // Intent created above that describes our requirements
+         0,      // Additional flags to control items (none)
+         null);  // Array of MenuItems that correlate to specific items (none)
+
+    return true;
+}
+ +

정의된 인텐트와 일치하는 인텐트 필터를 제공하는 것으로 발견된 각 액티비티에 +인텐트 필터의 android:label를 +메뉴 항목 제목으로, 애플리케이션 아이콘을 메뉴 항목 아이콘으로 사용하여 메뉴 항목을 추가합니다. +{@link android.view.Menu#addIntentOptions(int,int,int,ComponentName,Intent[],Intent,int,MenuItem[]) +addIntentOptions()} 메서드는 추가된 메뉴 항목 개수를 반환합니다.

+ +

참고: {@link +android.view.Menu#addIntentOptions(int,int,int,ComponentName,Intent[],Intent,int,MenuItem[]) +addIntentOptions()}을 호출할 때 첫 번째 인수에서 지정된 메뉴 그룹이 모든 메뉴 항목을 +재정의합니다.

+ + +

다른 메뉴에 액티비티 추가 허용

+ +

본인의 액티비티의 서비스를 다른 애플리케이션에 제공하여 다른 +애플리케이션의 메뉴에 본인의 애플리케이션이 추가되도록 할 수도 있습니다(위에서 설명한 것과 역할이 반대입니다).

+ +

다른 애플리케이션 메뉴에 추가되려면, 인텐트 필터는 평소와 같이 +정의해야 하지만, 인텐트 필터 카테고리에 {@link android.content.Intent#CATEGORY_ALTERNATIVE} +및/또는{@link android.content.Intent#CATEGORY_SELECTED_ALTERNATIVE} 값을 +반드시 포함해야 합니다. 예:

+
+<intent-filter label="@string/resize_image">
+    ...
+    <category android:name="android.intent.category.ALTERNATIVE" />
+    <category android:name="android.intent.category.SELECTED_ALTERNATIVE" />
+    ...
+</intent-filter>
+
+ +

인텐트 필터 작성에 관한 자세한 내용은 +인텐트와 인텐트 필터 문서를 참조하십시오.

+ +

이 기법을 사용하는 샘플 애플리케이션은 +Note +Pad 샘플 코드를 참조하십시오.

diff --git a/docs/html-intl/intl/ko/guide/topics/ui/notifiers/notifications.jd b/docs/html-intl/intl/ko/guide/topics/ui/notifiers/notifications.jd new file mode 100644 index 0000000000000000000000000000000000000000..db55424d8965574954a417991927b452821540de --- /dev/null +++ b/docs/html-intl/intl/ko/guide/topics/ui/notifiers/notifications.jd @@ -0,0 +1,979 @@ +page.title=알림 +@jd:body + + +

+ 알림은 애플리케이션의 정상 UI 외부에서 사용자에게 표시할 수 있는 메시지입니다. +시스템에 알림을 실행하라고 명령하면 +처음에 알림 영역에서 아이콘으로 나타납니다. 알림 세부 정보를 보려면 사용자는 +알림 창을 열어야 합니다. 알림 영역과 알림 창은 +사용자가 언제든 볼 수 있는, 시스템이 제어하는 영역입니다. +

+ +

+ 그림 1. 알림 영역에 있는 알림입니다. +

+ +

+ 그림 2. 알림 창에 있는 알림입니다. +

+ +

참고: 따로 언급된 부분을 제외하고 이 가이드는 +버전 4 지원 라이브러리의 {@link android.support.v4.app.NotificationCompat.Builder NotificationCompat.Builder} 클래스를 +참조합니다. +클래스 {@link android.app.Notification.Builder Notification.Builder}는 Android +3.0(API 레벨 11)에 추가되었습니다.

+ +

디자인 고려 사항

+ +

Android 사용자 인터페이스의 중요한 부분인 알림에는 자체적인 디자인 지침이 있습니다. +Android 5.0(API 레벨 21)부터 도입된 머티어리얼 디자인 변경 사항은 +특히 중요하며, 자세한 정보를 보려면 재료 디자인 +교육을 검토해야 합니다. 알림과 그 상호작용을 디자인하는 방법을 알아보려면 +알림 디자인 가이드를 읽어보십시오.

+ +

알림 생성

+ +

+{@link android.support.v4.app.NotificationCompat.Builder NotificationCompat.Builder} 개체에서 알림에 대한 UI 정보와 작업을 지정합니다. +알림 자체를 생성하려면 +{@link android.support.v4.app.NotificationCompat.Builder#build NotificationCompat.Builder.build()}를 호출합니다. +이는 사양이 포함된 {@link android.app.Notification} 객체를 반환합니다. 알림을 발행하려면 + +{@link android.app.NotificationManager#notify NotificationManager.notify()}를 호출해서 시스템에 {@link android.app.Notification} 객체를 전달합니다.

+ +

필수 알림 콘텐츠

+

+ {@link android.app.Notification} 객체는 다음을 반드시 포함해야 합니다. +

+
    +
  • + +{@link android.support.v4.app.NotificationCompat.Builder#setSmallIcon setSmallIcon()}이 설정한 작은 아이콘 +
  • +
  • + +{@link android.support.v4.app.NotificationCompat.Builder#setContentTitle setContentTitle()}이 설정한 제목 +
  • +
  • + +{@link android.support.v4.app.NotificationCompat.Builder#setContentText setContentText()}이 설정한 세부 텍스트 +
  • +
+

선택적 알림 콘텐츠 및 설정

+

+ 다른 모든 알림 설정 및 콘텐츠는 선택 사항입니다. 이들에 관해 자세히 알아보려면 +{@link android.support.v4.app.NotificationCompat.Builder}의 참조 문서를 참조하십시오. +

+ +

알림 작업

+

+ 선택 항목이기는 하지만 알림에 작업을 하나 이상 추가해야 합니다. + 작업은 사용자가 알림에서 +애플리케이션의 {@link android.app.Activity}로 바로 갈 수 있게 하고, 여기에서 사용자는 하나 이상의 이벤트를 보거나 +더 많은 작업을 할 수 있습니다. +

+

+ 하나의 알림은 여러 개의 작업을 제공할 수 있습니다. 사용자가 알림을 클릭했을 때 트리거되는 작업을 항상 정의해야 합니다. +일반적으로 작업은 +애플리케이션의 {@link android.app.Activity}를 엽니다. 또한, 알람 다시 알림이나 텍스트 메시지에 즉시 답장 등과 같은 추가 작업을 수행하는 +알림 버튼을 추가할 수 있습니다. +이 기능은 Android 4.1부터 사용할 수 있습니다. 추가 작업 버튼을 사용할 경우, +앱의 {@link android.app.Activity}에서 해당 기능을 사용할 수 있게 해야 합니다. +자세한 정보는 처리 호환성 섹션을 참조하십시오. +

+

+ {@link android.app.Notification}에서 작업 자체는 +애플리케이션에서 {@link android.app.Activity}를 시작하는 +{@link android.content.Intent}가 포함된 +{@link android.app.PendingIntent}가 정의합니다. +{@link android.app.PendingIntent}를 동작과 연관시키려면 +{@link android.support.v4.app.NotificationCompat.Builder}의 적절한 메서드를 호출합니다. 예를 들어, +사용자가 알림 창의 알림 텍스트를 클릭했을 때 {@link android.app.Activity}를 시작하려면, + +{@link android.support.v4.app.NotificationCompat.Builder#setContentIntent setContentIntent()}를 호출하여 {@link android.app.PendingIntent}를 추가합니다. +

+

+ 사용자가 알림을 클릭했을 때 {@link android.app.Activity}를 시작하는 동작이 가장 보편적인 작업 +시나리오입니다. 또한, 사용자가 알림을 무시했을 때 {@link android.app.Activity}를 +시작할 수도 있습니다. Android 4.1 이후부터는 +{@link android.app.Activity}를 작업 버튼에서 시작할 수 있습니다. 자세한 내용을 알아보려면 +{@link android.support.v4.app.NotificationCompat.Builder} 참조 가이드를 읽어보십시오. +

+ +

알림 우선 순위

+

+ 원한다면, 알림에 우선 순위를 설정할 수 있습니다. 우선 순위는 +기기 UI에 알림 표시 방식을 암시하는 역할을 합니다. + 알림 우선 순위를 설정하려면, {@link +android.support.v4.app.NotificationCompat.Builder#setPriority(int) +NotificationCompat.Builder.setPriority()}를 호출하고 {@link +android.support.v4.app.NotificationCompat} 우선 순위 상수 중 하나에 전달합니다. +우선 순위 수준은 {@link +android.support.v4.app.NotificationCompat#PRIORITY_MIN}(-2)에서 {@link +android.support.v4.app.NotificationCompat#PRIORITY_MAX}(2)까지 다섯 개가 있습니다. 별도의 설정이 없을 경우, +우선 순위 기본값은 {@link +android.support.v4.app.NotificationCompat#PRIORITY_DEFAULT}(0)으로 설정됩니다. +

+

적절한 우선 순위 수준 설정에 관한 정보는 알림 디자인 가이드에서 "알림 우선 순위 올바르게 설정하고 관리하기"를 +참조하십시오. + +

+ +

단순 알림 만들기

+

+ 다음 조각은 사용자가 알림을 클릭하면 알리는 +액티비티를 지정하는 단순한 알림을 나타냅니다. 이 코드는 +{@link android.support.v4.app.TaskStackBuilder} 객체를 생성하고 이를 사용하여 +해당 작업의 {@link android.app.PendingIntent}를 생성합니다. 이 패턴은 + +액티비티를 시작할 때 탐색 보존 섹션에서 자세히 설명합니다. +

+
+NotificationCompat.Builder mBuilder =
+        new NotificationCompat.Builder(this)
+        .setSmallIcon(R.drawable.notification_icon)
+        .setContentTitle("My notification")
+        .setContentText("Hello World!");
+// Creates an explicit intent for an Activity in your app
+Intent resultIntent = new Intent(this, ResultActivity.class);
+
+// The stack builder object will contain an artificial back stack for the
+// started Activity.
+// This ensures that navigating backward from the Activity leads out of
+// your application to the Home screen.
+TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);
+// Adds the back stack for the Intent (but not the Intent itself)
+stackBuilder.addParentStack(ResultActivity.class);
+// Adds the Intent that starts the Activity to the top of the stack
+stackBuilder.addNextIntent(resultIntent);
+PendingIntent resultPendingIntent =
+        stackBuilder.getPendingIntent(
+            0,
+            PendingIntent.FLAG_UPDATE_CURRENT
+        );
+mBuilder.setContentIntent(resultPendingIntent);
+NotificationManager mNotificationManager =
+    (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+// mId allows you to update the notification later on.
+mNotificationManager.notify(mId, mBuilder.build());
+
+

완료되었습니다. 이제 사용자에게 알림이 전달되었습니다.

+ +

알림에 확장 레이아웃 적용

+

+ 확장된 보기에 알림을 나타나게 하려면, +먼저 원하는 일반 보기 옵션으로 {@link android.support.v4.app.NotificationCompat.Builder} 객체를 +생성합니다. 다음에는 확장된 레이아웃 객체의 인수로 {@link android.support.v4.app.NotificationCompat.Builder#setStyle +Builder.setStyle()}을 호출합니다. +

+

+ 확장 알림은 Android 4.1 이전 플랫폼에서 사용할 수 없다는 것을 명심하십시오. +Android 4.1 이하 플랫폼에서 알림을 처리하는 방법은 +처리 호환성 섹션을 참조하십시오. +

+

+ 예를 들어, 다음 코드 조각은 +이전 조각에서 생성된 알림을 변경하여 확장 레이아웃을 사용하는 방법을 나타냅니다. +

+
+NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(this)
+    .setSmallIcon(R.drawable.notification_icon)
+    .setContentTitle("Event tracker")
+    .setContentText("Events received")
+NotificationCompat.InboxStyle inboxStyle =
+        new NotificationCompat.InboxStyle();
+String[] events = new String[6];
+// Sets a title for the Inbox in expanded layout
+inboxStyle.setBigContentTitle("Event tracker details:");
+...
+// Moves events into the expanded layout
+for (int i=0; i < events.length; i++) {
+
+    inboxStyle.addLine(events[i]);
+}
+// Moves the expanded layout object into the notification object.
+mBuilder.setStyle(inBoxStyle);
+...
+// Issue the notification here.
+
+ +

처리 호환성

+ +

+ +알림 기능을 설정하는 메서드가 +지원 라이브러리 클래스 {@link android.support.v4.app.NotificationCompat.Builder NotificationCompat.Builder}에 있더라도 모든 알림 기능을 특정 버전에서 사용할 수 있는 것은 아닙니다. + 예를 들어, 확장 알림에 따라 달라지는 작업 버튼은 Android +4.1 이상에만 나타납니다. 확장 알림 자체를 +Android 4.1 이상에서만 이용할 수 있기 때문입니다. +

+

+ 최상의 호환성을 보장하기 위해 +알림은 {@link android.support.v4.app.NotificationCompat NotificationCompat}와 하위 클래스, +특히 {@link android.support.v4.app.NotificationCompat.Builder +NotificationCompat.Builder}를 이용해서 알림을 생성합니다. 또한, 알림을 구현할 때 이 절차를 따릅니다. +

+
    +
  1. + 사용하는 버전에 관계없이 +모든 사용자에게 모든 알림 기능을 제공합니다. 이를 위해서 +앱의 {@link android.app.Activity}에서 모든 기능을 이용할 수 있는지 검증합니다. 그러려면 +새로운 {@link android.app.Activity}를 추가해야 할 수도 있습니다. +

    + 예를 들어, +여러분이{@link android.support.v4.app.NotificationCompat.Builder#addAction addAction()}을 사용하여 +미디어 재생을 중지하고 시작하는 제어를 제공하고 싶다면 +먼저 앱의 {@link android.app.Activity}에 이 제어를 구현합니다. +

    +
  2. +
  3. + 사용자가 알림을 클릭하면 알림을 시작시키는 방식으로 모든 사용자에게 {@link android.app.Activity}에서 알림 기능을 +사용할 수 있게 합니다. 이를 위해, + +{@link android.app.Activity}를 위한 {@link android.app.PendingIntent}를 생성합니다. +{@link android.support.v4.app.NotificationCompat.Builder#setContentIntent +setContentIntent()}를 호출하여 알림에 {@link android.app.PendingIntent}를 추가합니다. +
  4. +
  5. + 이제 알림에서 사용하고자 하는 확장 알림 기능을 추가합니다. 또한, 사용자가 알림을 클릭하면 시작되는 +{@link android.app.Activity}에서 개발자가 추가한 모든 기능을 +사용할 수 있어야 합니다. +
  6. +
+ + + + +

알림 관리

+

+ 같은 유형의 이벤트에서 알림을 여러 번 발행해야 할 경우, +완전히 새로운 알림을 만드는 것은 삼가야 합니다. 대신, 일부 값을 변경하거나 추가하거나, 두 가지 조치를 모두 취하여 +이전 알림을 업데이트하는 것이 좋습니다. +

+

+ 예를 들어, Gmail은 읽지 않은 메시지 개수를 올리고 각 이메일의 요약을 알림에 추가하여 +새 이메일 도착을 알립니다. 이것을 일명 +알림을 "쌓는다"고 하며, 이는 +알림 디자인 가이드에 자세히 설명되어 있습니다. +

+

+ 참고: 이 Gmail 기능에는 "받은편지함" 확장 레이아웃이 필요한데, +이것은 Android 4.1부터 이용할 수 있는 확장 알림 기능의 일부입니다. +

+

+ 다음 섹션은 알림 업데이트 방법과 삭제 방법을 설명합니다. +

+

알림 업데이트

+

+ 알림이 업데이트되도록 설정하려면, +{@link android.app.NotificationManager#notify(int, android.app.Notification) NotificationManager.notify()}를 호출하여 알림 ID와 함께 발행합니다. + 알림을 발행한 후에 업데이트하려면, +{@link android.support.v4.app.NotificationCompat.Builder} 객체를 업데이트하거나 생성하고, +{@link android.app.Notification} 객체를 구축하고, +이전에 사용한 것과 같은 ID로 {@link android.app.Notification}을 발행합니다. 이전 알림이 +여전히 표시되는 경우, 시스템은 +{@link android.app.Notification} 객체의 콘텐츠에서 알림을 업데이트합니다. 이전 알림을 무시할 경우, +대신 새로운 알림이 생성됩니다. +

+

+ 다음 코드 조각은 발생한 이벤트 개수를 반영하여 +업데이트된 알림을 나타낸 것입니다. 이것은 알림을 쌓아 요약을 표시합니다. +

+
+mNotificationManager =
+        (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+// Sets an ID for the notification, so it can be updated
+int notifyID = 1;
+mNotifyBuilder = new NotificationCompat.Builder(this)
+    .setContentTitle("New Message")
+    .setContentText("You've received new messages.")
+    .setSmallIcon(R.drawable.ic_notify_status)
+numMessages = 0;
+// Start of a loop that processes data and then notifies the user
+...
+    mNotifyBuilder.setContentText(currentText)
+        .setNumber(++numMessages);
+    // Because the ID remains unchanged, the existing notification is
+    // updated.
+    mNotificationManager.notify(
+            notifyID,
+            mNotifyBuilder.build());
+...
+
+ + +

알림 제거

+

+ 알림은 다음 중 하나가 발생할 때까지 계속 표시된 상태로 유지됩니다. +

+
    +
  • + 사용자가 개별적으로 삭제하거나 "모두 삭제"를 사용하여 알림을 무시합니다( +알림을 지울 수 있는 경우). +
  • +
  • + 사용자가 알림을 클릭하고, 알림을 생성했을 때 +{@link android.support.v4.app.NotificationCompat.Builder#setAutoCancel setAutoCancel()} 을 호출했을 경우입니다. + +
  • +
  • + 특정 알림 ID에 대해 {@link android.app.NotificationManager#cancel(int) cancel()}을 호출합니다. +이 메서드도 현재 진행 중인 알림을 삭제합니다. +
  • +
  • + {@link android.app.NotificationManager#cancelAll() cancelAll()}을 호출합니다. +이것은 이전에 발행한 알림을 모두 제거합니다. +
  • +
+ + +

액티비티를 시작할 때 탐색 보존

+

+ 알림에서 {@link android.app.Activity}를 시작할 때는 사용자의 예상 탐색 경험을 +보존해야 합니다. '뒤로'를 클릭하면 사용자를 애플리케이션의 정상 작업 흐름을 거쳐 메인 스크린으로 보내고, + '최근'을 클릭하면 +{@link android.app.Activity}를 별개의 작업으로 표시합니다. 탐색 경험을 보존하려면 +새 작업에서 {@link android.app.Activity}를 시작해야 합니다. 새로운 작업을 부여하기 위한 +{@link android.app.PendingIntent} 설정 방법은 시작하는 +{@link android.app.Activity}의 성격에 따라 달라집니다. 여기에는 두 가지 일반적인 상황이 있습니다. +

+
+
+ 정규 액티비티 +
+
+ 애플리케이션의 정상적 작업 흐름의 일부인 {@link android.app.Activity}를 +시작합니다. 이 상황에서 {@link android.app.PendingIntent}를 설정하여 +새 작업을 시작하고 애플리케이션의 +정상적인 '뒤로' 동작을 재현하는 백 스택으로 {@link android.app.PendingIntent}를 제공합니다. +

+ Gmail 앱에서 보낸 알림이 이것을 잘 보여줍니다. 하나의 이메일 메시지에 대한 +알림을 클릭하면 메시지 자체를 보게 됩니다. 뒤로를 터치하면 +알림에서 들어간 것이 아니라 메인 스크린에서 +Gmail에 들어간 것처럼 Gmail을 통해 메인 스크린으로 돌아갑니다. +

+

+ 이것은 알림을 터치하기만 하면 어느 애플리케이션에 있든 관계 없이 발생하는 +일입니다. 예를 들어, Gmail에서 메시지를 작성하다가 +한 이메일에 대한 알림을 클릭하면 해당 이메일로 바로 이동합니다. 뒤로 +를 터치하면 +작성 중인 메시지가 아니라 받은편지함과 메인 스크린으로 돌아갑니다. +

+
+
+ 특수 액티비티 +
+
+ {@link android.app.Activity}가 알림에서 시작될 경우 사용자에게는 이것만 보입니다. + {@link android.app.Activity}는 알림 자체에서 표시하기 어려운 정보를 제공하므로 +어떤 면에서는 알림을 확장하는 셈입니다. 이 상황에서는, +{@link android.app.PendingIntent}를 설정하고 새로운 작업에서 시작합니다. +시작된 {@link android.app.Activity}는 +애플리케이션 액티비티 흐름의 일부가 아니므로 백 스택을 생성하지 않아도 됩니다. 뒤로를 클릭하면 사용자는 여전히 +메인 스크린으로 돌아갑니다. +
+
+ +

정규 액티비티 PendingIntent 설정

+

+ 직접 진입 +{@link android.app.Activity}를 시작하는 {@link android.app.PendingIntent}를 설정하려면 다음과 같은 단계를 따르십시오. +

+
    +
  1. + 매니페스트에서 애플리케이션의 {@link android.app.Activity} 계층을 정의합니다. +
      +
    1. + Android 4.0.3 이전에 대한 지원을 추가합니다. 이렇게 하려면 + +<meta-data> +요소를 +<activity>의 하위 요소로 추가하여 개발자가 시작하는 {@link android.app.Activity}의 상위 요소를 지정합니다. +

      + 이 요소의 경우, +android:name="android.support.PARENT_ACTIVITY"를 설정합니다. + <parent_activity_name>가 +상위 요소 <activity> +에 대한 +android:name의 값인 + +android:value="<parent_activity_name>" +를 시작합니다. 다음 XML을 예시로 참조하십시오. +

      +
    2. +
    3. + Android 4.1 이후에 대한 지원도 추가합니다. 이렇게 하려면 개발자가 시작하는 {@link android.app.Activity}의 +<activity> + 요소에 +android:parentActivityName +속성을 추가합니다. +
    4. +
    +

    + 최종 XML은 이런 모습을 띠는 것이 정상입니다. +

    +
    +<activity
    +    android:name=".MainActivity"
    +    android:label="@string/app_name" >
    +    <intent-filter>
    +        <action android:name="android.intent.action.MAIN" />
    +        <category android:name="android.intent.category.LAUNCHER" />
    +    </intent-filter>
    +</activity>
    +<activity
    +    android:name=".ResultActivity"
    +    android:parentActivityName=".MainActivity">
    +    <meta-data
    +        android:name="android.support.PARENT_ACTIVITY"
    +        android:value=".MainActivity"/>
    +</activity>
    +
    +
  2. +
  3. + {@link android.app.Activity}를 시작하는 {@link android.content.Intent}에 기초하여 +백 스택을 생성합니다. +
      +
    1. + {@link android.content.Intent}를 생성하여 {@link android.app.Activity}를 생성합니다. +
    2. +
    3. + {@link android.app.TaskStackBuilder#create +TaskStackBuilder.create()}를 호출하여 스택 빌더를 생성합니다. +
    4. +
    5. + +{@link android.support.v4.app.TaskStackBuilder#addParentStack addParentStack()}을 호출하여 스택 빌더를 백 스택에 추가합니다. + 매니페스트에서 정의한 계층의 각 {@link android.app.Activity}의 경우, +백 스택에 +{@link android.app.Activity}를 시작하는 {@link android.content.Intent} 객체가 포함됩니다. 이 메서드는 새로운 작업에서 +스택을 시작하는 플래그도 추가합니다. +

      + 참고: +{@link android.support.v4.app.TaskStackBuilder#addParentStack addParentStack()}에 대한 인수가 +시작된 {@link android.app.Activity}의 참조이기는 하지만, 이 메서드 호출은 + +{@link android.app.Activity}를 시작하는 {@link android.content.Intent}를 추가하지 않습니다. 대신, 그 부분은 다음 단계에서 해결합니다. +

      +
    6. +
    7. + +{@link android.support.v4.app.TaskStackBuilder#addNextIntent addNextIntent()}를 호출하여 {@link android.app.Activity}를 시작하는 {@link android.content.Intent}를 +추가합니다. + 첫 번째 단계에서 생성한 {@link android.content.Intent}를 + +{@link android.support.v4.app.TaskStackBuilder#addNextIntent addNextIntent()}에 대한 인수로 전달합니다. +
    8. +
    9. + 필요할 경우 +{@link android.support.v4.app.TaskStackBuilder#editIntentAt +TaskStackBuilder.editIntentAt()}을 호출하여 스택에서{@link android.content.Intent} 객체에 대한 인수를 추가합니다. 이것은 사용자가 + +'뒤로'를 사용하여 탐색할 때 대상{@link android.app.Activity}가 의미 있는 데이터를 표시하도록 보장하기 위해 때때로 필요한 절차입니다. +
    10. +
    11. + 이 백 스택에 대한 {@link android.app.PendingIntent}를 가져옵니다. 이때 +{@link android.support.v4.app.TaskStackBuilder#getPendingIntent getPendingIntent()}를 호출하는 방법을 씁니다. + 그러면 이 {@link android.app.PendingIntent}를 +{@link android.support.v4.app.NotificationCompat.Builder#setContentIntent +setContentIntent()}에 대한 인수로 사용할 수 있습니다. +
    12. +
    +
  4. +
+

+ 다음 코드 조각은 이 과정을 나타낸 것입니다. +

+
+...
+Intent resultIntent = new Intent(this, ResultActivity.class);
+TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);
+// Adds the back stack
+stackBuilder.addParentStack(ResultActivity.class);
+// Adds the Intent to the top of the stack
+stackBuilder.addNextIntent(resultIntent);
+// Gets a PendingIntent containing the entire back stack
+PendingIntent resultPendingIntent =
+        stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
+...
+NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
+builder.setContentIntent(resultPendingIntent);
+NotificationManager mNotificationManager =
+    (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+mNotificationManager.notify(id, builder.build());
+
+ +

특수 액티비티 PendingIntent 설정

+

+ 다음 섹션에서는 특수 액티비티 +{@link android.app.PendingIntent}를 설정하는 법을 설명합니다. +

+

+ 특수 {@link android.app.Activity}에는 백 스택이 필요하지 않습니다. 따라서 매니페스트에서 이것의 +{@link android.app.Activity} 계층을 정의하지 않아도 되고, +백 스택을 구축하기 위해 +{@link android.support.v4.app.TaskStackBuilder#addParentStack addParentStack()}을 +호출하지 않아도 됩니다. 대신 매니페스트를 사용하여 {@link android.app.Activity} 작업 옵션을 설정하고, +{@link android.app.PendingIntent}를 생성하십시오. 이때 +{@link android.app.PendingIntent#getActivity getActivity()}를 호출하는 방법을 씁니다. +

+
    +
  1. + 매니페스트에서 다음 속성을 {@link android.app.Activity}에 대한 +<activity> +요소에 추가합니다. +
    +
    +android:name="activityclass" +
    +
    + 액티비티의 완전히 정규화된 클래스 이름입니다. +
    +
    +android:taskAffinity="" +
    +
    + 이것은 개발자가 코드에서 설정하는 +{@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK FLAG_ACTIVITY_NEW_TASK} 플래그와 더불어 +이 {@link android.app.Activity}가 +애플리케이션의 기본 작업으로 들어가지 않게 보장합니다. 애플리케이션의 기본 유사성을 +가지고 있는 기존 작업은 모두 영향을 받지 않습니다. +
    +
    +android:excludeFromRecents="true" +
    +
    + 새 작업을 최근에서 배제하여 사용자가 우연히 +이곳으로 다시 이동하지 못하게 합니다. +
    +
    +

    + 이 조각은 해당 요소를 나타낸 것입니다. +

    +
    +<activity
    +    android:name=".ResultActivity"
    +...
    +    android:launchMode="singleTask"
    +    android:taskAffinity=""
    +    android:excludeFromRecents="true">
    +</activity>
    +...
    +
    +
  2. +
  3. + 알림을 구축 및 발행합니다. +
      +
    1. + +{@link android.app.Activity}를 시작하는 {@link android.content.Intent}를 생성합니다. +
    2. +
    3. + {@link android.app.Activity}가 새로운, 빈 작업에서 시작되도록 설정합니다. 이때 +{@link android.content.Intent#setFlags setFlags()}를 +{@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK FLAG_ACTIVITY_NEW_TASK} + 및 +{@link android.content.Intent#FLAG_ACTIVITY_CLEAR_TASK FLAG_ACTIVITY_CLEAR_TASK} 플래그와 함께 호출하면 됩니다. +
    4. +
    5. + {@link android.content.Intent}에 필요한 다른 모든 옵션을 설정합니다. +
    6. +
    7. + {@link android.app.PendingIntent}를 {@link android.content.Intent}로부터 +생성합니다. 이때 {@link android.app.PendingIntent#getActivity getActivity()}를 호출하면 됩니다. + 그러면 이 {@link android.app.PendingIntent}를 +{@link android.support.v4.app.NotificationCompat.Builder#setContentIntent +setContentIntent()}에 대한 인수로 사용할 수 있습니다. +
    8. +
    +

    + 다음 코드 조각은 이 과정을 나타낸 것입니다. +

    +
    +// Instantiate a Builder object.
    +NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
    +// Creates an Intent for the Activity
    +Intent notifyIntent =
    +        new Intent(this, ResultActivity.class);
    +// Sets the Activity to start in a new, empty task
    +notifyIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
    +                        | Intent.FLAG_ACTIVITY_CLEAR_TASK);
    +// Creates the PendingIntent
    +PendingIntent notifyPendingIntent =
    +        PendingIntent.getActivity(
    +        this,
    +        0,
    +        notifyIntent,
    +        PendingIntent.FLAG_UPDATE_CURRENT
    +);
    +
    +// Puts the PendingIntent into the notification builder
    +builder.setContentIntent(notifyPendingIntent);
    +// Notifications are issued by sending them to the
    +// NotificationManager system service.
    +NotificationManager mNotificationManager =
    +    (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
    +// Builds an anonymous Notification object from the builder, and
    +// passes it to the NotificationManager
    +mNotificationManager.notify(id, builder.build());
    +
    +
  4. +
+ + +

알림에서 진행 상태 표시

+

+ 알림에는 사용자에게 진행 중인 작업의 상태를 보여주는 +애니메이션 진행 표시기를 포함할 수 있습니다. 작업이 얼마나 걸릴지, 주어진 시점에 어느 정도 완료되었는지를 추정할 수 있는 경우 +표시기의 "확정적" 형태(진행률 표시줄)를 +사용하십시오. 작업의 길이를 추정할 수 없으면, 표시기의 +"비확정적" 형태(액티비티 표시기)를 사용하십시오. +

+

+ 진행 상태 표시기는 +{@link android.widget.ProgressBar} 클래스의 플랫폼 구현으로 표시됩니다. +

+

+ Android 4.0부터 시작되는 플랫폼에서 진행 상태 표시기를 사용하려면, +{@link android.support.v4.app.NotificationCompat.Builder#setProgress setProgress()}를 호출하십시오. 이전 +버전의 경우, 개발자 나름의 사용자 지정 알림 레이아웃을 생성해야 하며 여기에 +{@link android.widget.ProgressBar} 보기가 포함되어 있어야 합니다. +

+

+ 다음 섹션은 +{@link android.support.v4.app.NotificationCompat.Builder#setProgress setProgress()}를 사용하여 알림의 진행 상태를 표시하는 법을 설명합니다. +

+ +

고정 기간 진행 상태 표시기 표시

+

+ 확정적인 진행률 표시줄을 표시하려면 +{@link android.support.v4.app.NotificationCompat.Builder#setProgress +setProgress(max, progress, false)}를 호출하여 표시줄을 알림에 추가하고, 그 다음에 알림을 발행합니다. 작업이 진행되는 동안 +progress를 증가시키고 알림을 업데이트합니다. 작업이 끝날 무렵 +progressmax와 같아야 합니다. +{@link android.support.v4.app.NotificationCompat.Builder#setProgress setProgress()}를 호출하는 보편적인 방법은 +max를 100에 설정하고 작업에 대한 "완료 비율" 값에 따라 progress를 +증가시키는 것입니다. +

+

+ 작업이 완료되면 진행률 표시줄이 표시되는 상태로 둘 수도 있고, 제거할 수도 있습니다. 어느 경우를 택하더라도 +알림 텍스트를 업데이트하여 작업이 완료되었다고 표시하는 것을 잊지 마십시오. + 진행률 표시줄을 제거하려면, +{@link android.support.v4.app.NotificationCompat.Builder#setProgress +setProgress(0, 0, false)}를 호출합니다. 예: +

+
+...
+mNotifyManager =
+        (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+mBuilder = new NotificationCompat.Builder(this);
+mBuilder.setContentTitle("Picture Download")
+    .setContentText("Download in progress")
+    .setSmallIcon(R.drawable.ic_notification);
+// Start a lengthy operation in a background thread
+new Thread(
+    new Runnable() {
+        @Override
+        public void run() {
+            int incr;
+            // Do the "lengthy" operation 20 times
+            for (incr = 0; incr <= 100; incr+=5) {
+                    // Sets the progress indicator to a max value, the
+                    // current completion percentage, and "determinate"
+                    // state
+                    mBuilder.setProgress(100, incr, false);
+                    // Displays the progress bar for the first time.
+                    mNotifyManager.notify(0, mBuilder.build());
+                        // Sleeps the thread, simulating an operation
+                        // that takes time
+                        try {
+                            // Sleep for 5 seconds
+                            Thread.sleep(5*1000);
+                        } catch (InterruptedException e) {
+                            Log.d(TAG, "sleep failure");
+                        }
+            }
+            // When the loop is finished, updates the notification
+            mBuilder.setContentText("Download complete")
+            // Removes the progress bar
+                    .setProgress(0,0,false);
+            mNotifyManager.notify(ID, mBuilder.build());
+        }
+    }
+// Starts the thread by calling the run() method in its Runnable
+).start();
+
+ + +

지속적 액티비티 표시기 표시

+

+ 비확정적 액티비티 표시기를 표시하려면, +{@link android.support.v4.app.NotificationCompat.Builder#setProgress setProgress(0, 0, true)}으로 알림에 표시기를 추가하고(처음의 인수 두 개는 무시합니다) +, 알림을 발행합니다. 그 결과로 +진행률 표시줄과 같은 스타일의 표시기가 나타납니다. 다만 이것은 애니메이션이 계속 진행 중입니다. +

+

+ 작업을 시작할 때 알림을 발행합니다. 애니메이션은 +알림을 수정할 때까지 실행됩니다. 작업이 완료되면, +{@link android.support.v4.app.NotificationCompat.Builder#setProgress setProgress(0, 0, false)}를 호출하고 +알림을 업데이트하여 액티비티 표시기를 제거합니다. + 이 작업은 항상 해야 합니다. 하지 않으면, 작업이 완료되더라도 애니메이션이 계속 실행됩니다. 또한, +알림 텍스트를 변경하여 작업이 완료되었음을 나타내는 것을 잊지 마십시오. +

+

+ 액티비티 표시기의 작동 원리를 알아보려면 이어지는 코드 조각을 참조하십시오. 다음 줄을 찾을 수 있습니다. +

+
+// Sets the progress indicator to a max value, the current completion
+// percentage, and "determinate" state
+mBuilder.setProgress(100, incr, false);
+// Issues the notification
+mNotifyManager.notify(0, mBuilder.build());
+
+

+ 찾은 줄을 다음 줄로 교체하십시오. +

+
+ // Sets an activity indicator for an operation of indeterminate length
+mBuilder.setProgress(0, 0, true);
+// Issues the notification
+mNotifyManager.notify(0, mBuilder.build());
+
+ +

알림 메타데이터

+ +

알림은 +다음 {@link android.support.v4.app.NotificationCompat.Builder} 메서드로 할당된 메타데이터에 따라 정렬할 수 있습니다.

+ +
    +
  • {@link android.support.v4.app.NotificationCompat.Builder#setCategory(java.lang.String) setCategory()}는 +기기가 우선 순위 모드일 때 앱 알림을 처리하는 방법을 시스템에 전달합니다 +(예를 들어, 알림이 수신 전화나 채팅 메시지, 알람 등을 나타낼 경우).
  • +
  • {@link android.support.v4.app.NotificationCompat.Builder#setPriority(int) setPriority()}는 +우선 순위 필드가 포함된 알림을 {@code PRIORITY_MAX} 또는 {@code PRIORITY_HIGH}로 설정하고, +알림에 소리나 진동이 포함되어 있을 경우 작은 부동 창에 나타나게 합니다.
  • +
  • {@link android.support.v4.app.NotificationCompat.Builder#addPerson(java.lang.String) addPerson()}을 +사용하면 알림에 사람 목록을 추가할 수 있게 해줍니다. 개발자의 앱은 이 신호를 사용하여 +시스템에 지정된 사람들로부터 받은 알림을 함께 그룹화해야 한다고 알리거나, 이런 사람들로부터 받은 알림을 +더욱 중요한 것으로 순위를 높일 수 있습니다.
  • +
+ +
+ +

+ 그림 3. 헤드업 알림을 표시하고 있는 전체 화면 액티비티 +

+
+ +

헤드업 알림

+ +

Android 5.0(API 레벨 21)에서는 알림을 작은 부동 창에 나타낼 수 있습니다 +(다른 말로 헤드업 알림이라고 부릅니다). 이것은 기기가 활성 상태일 때(즉, +기기가 잠금 해제 상태이며 화면에 켜져 있는 경우) 해당됩니다. 이와 같은 알림은 +외견상 일반적인 알림의 소형 형태와 비슷해 보이지만, +해드업 알림에서는 작업 버튼도 표시한다는 점이 다릅니다. 사용자는 현재 앱을 떠나지 않고도 +헤드업 알림에 조치를 취하거나 이를 무시할 수 있습니다.

+ +

헤드업 알림을 트리거할 수 있는 조건의 예시를 몇 가지 소개하면 다음과 같습니다.

+ +
    +
  • 사용자 액티비티가 전체 화면 모드이거나(앱이 +{@link android.app.Notification#fullScreenIntent}를 사용할 경우)
  • +
  • 알림의 우선 순위가 높고 +벨소리나 진동을 사용할 경우
  • +
+ +

잠금 화면 알림

+ +

Android 5.0 (API 레벨 21) 릴리스부터 알림이 잠금 화면에도 +나타날 수 있게 되었습니다. 앱은 이 기능을 사용하면 미디어 재생 제어와 다른 보편적인 작업을 +제공할 수 있습니다. 사용자는 설정을 통해 잠금 화면에 알림 표시 여부를 선택할 수 있고, +개발자는 앱의 알림이 잠금 화면에 표시될지 여부를 지정할 수 있습니다.

+ +

가시성 설정

+ +

보안 잠금 화면에 알림이 얼마나 상세하게 표시될 것인지 그 수준을 앱이 제어할 수 +있습니다. {@link android.support.v4.app.NotificationCompat.Builder#setVisibility(int) setVisibility()}를 호출하고 +다음 값 중 하나를 지정합니다.

+ +
    +
  • {@link android.support.v4.app.NotificationCompat#VISIBILITY_PUBLIC}은 +알림의 전체 콘텐츠를 표시합니다.
  • +
  • {@link android.support.v4.app.NotificationCompat#VISIBILITY_SECRET}은 +이 알림의 어떤 부분도 화면에 표시하지 않습니다.
  • +
  • {@link android.support.v4.app.NotificationCompat#VISIBILITY_PRIVATE}은 +알림 아이콘과 콘텐츠 제목 등의 기본 정보는 표시하지만 알림의 전체 콘텐츠는 숨깁니다.
  • +
+ +

{@link android.support.v4.app.NotificationCompat#VISIBILITY_PRIVATE}으로 설정하면 , +알림 콘텐츠의 대체 버전을 제공할 수도 있습니다. 이렇게 하면 특정 세부 사항만 숨깁니다. 예를 들어, +SMS 앱에서 3개의 새 문자 메시지가 있습니다.라고 표시하면서도 +문자 메시지의 내용과 발신자는 숨길 수 있습니다. 이 대체 알림을 제공하려면, 우선 대체 알림을 생성합니다. 이때 +{@link android.support.v4.app.NotificationCompat.Builder}를 사용합니다. 비공개 알림 객체를 +생성하는 경우, 대체 알림을 이에 첨부할 때 +{@link android.support.v4.app.NotificationCompat.Builder#setPublicVersion(android.app.Notification) setPublicVersion()} +메서드를 통합니다.

+ +

잠금 화면에서 미디어 재생 제어

+ +

Android 5.0(API 레벨 21)의 잠금 화면에서는 더 이상 +{@link android.media.RemoteControlClient}를 기반으로 한 미디어 제어를 표시하지 않습니다. 이는 사용되지 않고 있기 때문입니다. 대신, +{@link android.app.Notification.MediaStyle} 템플릿을 +{@link android.app.Notification.Builder#addAction(android.app.Notification.Action) addAction()} +메서드와 함께 사용하십시오. 이 메서드는 작업을 클릭할 수 있는 아이콘으로 변환해줍니다.

+ +

참고: 이 템플릿과 {@link android.app.Notification.Builder#addAction(android.app.Notification.Action) addAction()} +메서드는 지원 라이브러리에 포함되어 있지 않으므로 이 기능은 Android 5.0 이상에서만 +실행됩니다.

+ +

Android 5.0의 잠금 화면에서 미디어 재생 제어를 표시하려면, +위에 설명한 바와 같이 가시성을 {@link android.support.v4.app.NotificationCompat#VISIBILITY_PUBLIC}으로 설정합니다. 그런 다음 다음 샘플 코드에서 설명한 바와 같이 작업을 추가하고 +{@link android.app.Notification.MediaStyle} 템플릿을 설정합니다. +

+ +
+Notification notification = new Notification.Builder(context)
+    // Show controls on lock screen even when user hides sensitive content.
+    .setVisibility(Notification.VISIBILITY_PUBLIC)
+    .setSmallIcon(R.drawable.ic_stat_player)
+    // Add media control buttons that invoke intents in your media service
+    .addAction(R.drawable.ic_prev, "Previous", prevPendingIntent) // #0
+    .addAction(R.drawable.ic_pause, "Pause", pausePendingIntent)  // #1
+    .addAction(R.drawable.ic_next, "Next", nextPendingIntent)     // #2
+    // Apply the media style template
+    .setStyle(new Notification.MediaStyle()
+    .setShowActionsInCompactView(1 /* #1: pause button */)
+    .setMediaSession(mMediaSession.getSessionToken())
+    .setContentTitle("Wonderful music")
+    .setContentText("My Awesome Band")
+    .setLargeIcon(albumArtBitmap)
+    .build();
+
+ +

참고: {@link android.media.RemoteControlClient}를 +사용하지 않게 된 것은 미디어 제어에 이외에도 더 많은 영향을 미칩니다. 미디어 세션을 관리하고 재생을 제어하기 위한 새 API에 관한 자세한 정보는 +미디어 재생 제어를 + 참조하십시오.

+ + + +

사용자 지정 알림 레이아웃

+

+ 알림 프레임워크를 사용하면 사용자 지정 레이아웃을 정의할 수 있습니다. +사용자 지정 레이아웃은 {@link android.widget.RemoteViews} 객체에서 알림의 외관을 정의합니다. + 사용자 지정 레이아웃 알림은 일반적인 알림과 비슷하지만, 이들은 XML 레이아웃 파일에서 + 정의한 {@link android.widget.RemoteViews}에 기초합니다. +

+

+ 사용자 지정 알림 레이아웃에 사용할 수 있는 높이는 알림 보기에 따라 다릅니다. 일반 +보기 레이아웃은 64dp로 제한되어 있으며 확장 보기 레이아웃은 256dp로 제한되어 있습니다. +

+

+ 사용자 지정 레이아웃을 정의하려면 +XML 레이아웃 파일을 팽창하는 {@link android.widget.RemoteViews} 객체를 인스턴트화하는 것부터 시작합니다. 그런 다음, + +{@link android.support.v4.app.NotificationCompat.Builder#setContentTitle setContentTitle()}과 같은 메서드를 호출하는 대신 +{@link android.support.v4.app.NotificationCompat.Builder#setContent setContent()}를 호출합니다. 사용자 지정 알림에서 +콘텐츠 세부 정보를 설정하려면 +{@link android.widget.RemoteViews}의 메서드를 사용하여 보기의 하위 요소에 대한 값을 설정합니다. +

+
    +
  1. + 알림에 대한 XML 레이아웃은 별도의 파일에 생성하십시오. 파일 이름은 원하는 대로 +아무 것이나 사용해도 좋지만, 확장자는 .xml을 사용해야 합니다. +
  2. +
  3. + 앱에서 {@link android.widget.RemoteViews} 메서드를 사용하여 알림의 아이콘과 텍스트를 +정의합니다. 이 {@link android.widget.RemoteViews} 객체를 +{@link android.support.v4.app.NotificationCompat.Builder} 안에 넣으십시오. +{@link android.support.v4.app.NotificationCompat.Builder#setContent setContent()}를 호출하면 됩니다. 배경 +{@link android.graphics.drawable.Drawable}을 +{@link android.widget.RemoteViews} 객체에서 설정하는 것은 삼가하십시오. 텍스트 색상을 읽을 수 없게 될 수도 있습니다. +
  4. +
+

+ {@link android.widget.RemoteViews} 클래스에도 개발자가 손쉽게 사용할 수 있는 여러 가지 메서드가 포함되어 있습니다. 이를 이용해 +{@link android.widget.Chronometer} 또는 {@link android.widget.ProgressBar}를 +알림의 레이아웃에 추가하면 됩니다. 알림의 사용자 지정 레이아웃 생성에 관한 자세한 정보는 +{@link android.widget.RemoteViews} 참조 문서를 참조하십시오. +

+

+ 주의: 사용자 지정 레이아웃을 사용하는 경우, +사용자 지정 레이아웃이 다양한 기기 방향과 해상도에서 작동하는지 각별히 주의를 기울여 확인하십시오. 이 조언은 +모든 보기 레이아웃에 공통적으로 적용되지만, 특히 알림에 중요한 의미를 지닙니다. +알림 창에서는 공간이 굉장히 제한되어 있기 때문입니다. 사용자 지정 레이아웃을 너무 복잡하게 만들지 마시고, +여러 가지 구성에서 테스트하는 것을 잊지 마십시오. +

+ +

사용자 지정 알림 텍스트에 스타일 리소스 사용

+

+ 사용자 지정 알림의 텍스트에는 항상 스타일 리소스를 사용하십시오. 알림의 배경 색상은 기기와 버전별로 다를 수 있습니다. +스타일 리소스를 사용하면 이러한 차이를 +감안하는 데 도움이 됩니다. Android 2.3부터 시스템은 +표준 알림 레이아웃 텍스트의 스타일을 정의했습니다. Android 2.3 이상을 대상으로 하는 +애플리케이션에서와 같은 스타일을 사용하면 텍스트가 디스플레이 배경에서도 잘 보이도록 할 수 있습니다. +

diff --git a/docs/html-intl/intl/ko/guide/topics/ui/overview.jd b/docs/html-intl/intl/ko/guide/topics/ui/overview.jd new file mode 100644 index 0000000000000000000000000000000000000000..eb288f1532aeb034a95444bc271f11f29db89074 --- /dev/null +++ b/docs/html-intl/intl/ko/guide/topics/ui/overview.jd @@ -0,0 +1,71 @@ +page.title=UI 개요 +@jd:body + + +

Android 앱의 모든 사용자 인터페이스 요소는 {@link android.view.View}와 +{@link android.view.ViewGroup} 개체를 사용하여 구축합니다. {@link android.view.View}는 사용자가 상호 작용할 수 있는 무언가를 +화면에 그리는 객체입니다. {@link android.view.ViewGroup}은 +인터페이스 레이아웃을 정의하기 위해 다른 {@link android.view.View}(및{@link android.view.ViewGroup}) 객체를 +보유하는 객체입니다.

+ +

Android는 공통 입력 제어(버튼 및 텍스트 필드)와 다양한 레이아웃 모델(선형 또는 관계 레이아웃)을 제공하는 {@link android.view.View}와 {@link +android.view.ViewGroup} 하위 클래스의 +컬렉션을 제공합니다.

+ + +

사용자 인터페이스 레이아웃

+ +

앱의 각 구성 요소에 대한 사용자 인터페이스는 그림 1에서 나타난 바와 같이 {@link +android.view.View}와 {@link android.view.ViewGroup} 객체의 계층으로 정의됩니다. 각 보기 그룹은 +하위 보기를 체계화하는 투명한 컨테이너이고, +하위 보기는 UI의 일부분을 그리는 제어나 다른 위젯일 수 있습니다. +이 계층 트리는 개발자에게 필요한 만큼 단순하거나 복잡하게 +만들 수 있습니다(다만 단순한 것이 성능에는 가장 좋습니다).

+ + +

그림 1. 보기 계층을 나타낸 것으로, 이것이 +UI 레이아웃을 정의합니다.

+ +

레이아웃을 선언하려면 코드의 {@link android.view.View} 객체를 인스턴트화하고 트리를 구축하기 시작하면 되지만, +레이아웃을 정의하는 가장 쉽고 효과적인 방법은 XML 파일을 사용하는 것입니다. +XML은 HTML과 유사한, 인간이 읽을 수 있는 레이아웃 구조를 제공합니다.

+ +

보기의 XML 요소 이름은 해당 요소가 나타내는 각각의 Android 클래스를 따릅니다. 말하자면 +<TextView> 요소가 UI에서 {@link android.widget.TextView} 위젯을 생성하고, +<LinearLayout> 요소는 {@link android.widget.LinearLayout} 보기 +그룹을 생성하는 것입니다.

+ +

예를 들어, 텍스트 보기와 버튼 하나가 있는 단순한 수직 레이아웃은 이런 모습을 띱니다.

+
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:layout_width="fill_parent" 
+              android:layout_height="fill_parent"
+              android:orientation="vertical" >
+    <TextView android:id="@+id/text"
+              android:layout_width="wrap_content"
+              android:layout_height="wrap_content"
+              android:text="I am a TextView" />
+    <Button android:id="@+id/button"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="I am a Button" />
+</LinearLayout>
+
+ +

앱에 레이아웃 리소스를 로드하면 Android가 레이아웃의 각 노드를 초기화하여 +추가 동작을 정의하거나, 객체 상태를 쿼리 또는 레이아웃을 수정하는 데 쓸 수 있는 런타임 객체로 +초기화합니다.

+ +

UI 레이아웃 생성에 대한 완전한 가이드는 XML +레이아웃을 참조하십시오. + + +

사용자 인터페이스 구성 요소

+ +

UI를 구축할 때 모두 {@link android.view.View} 및 {@link +android.view.ViewGroup} 객체를 사용해야 하는 것은 아닙니다. Android가 표준형 UI 레이아웃을 제공하는 앱 구성 요소를 여러 개 제공하고 있으니, +개발자 여러분은 이에 대한 콘텐츠만 정의하면 됩니다. 이와 같은 UI 구성 요소에는 각각 +고유한 API 세트가 있습니다. 이들은 작업 모음, 대화상태 알림 등 각각 다른 문서에서 설명하였습니다.

+ + diff --git a/docs/html-intl/intl/ko/guide/topics/ui/settings.jd b/docs/html-intl/intl/ko/guide/topics/ui/settings.jd new file mode 100644 index 0000000000000000000000000000000000000000..36204e03ecf5efbdf65db34b2189e755581bff00 --- /dev/null +++ b/docs/html-intl/intl/ko/guide/topics/ui/settings.jd @@ -0,0 +1,1202 @@ +page.title=설정 +page.tags=preference,preferenceactivity,preferencefragment + +@jd:body + + + + + + + +

애플리케이션에는 종종 설정이 포함되어 있어 사용자가 앱 기능과 행동을 수정할 수 있게 해줍니다. 예를 들어 +몇몇 앱은 사용자에게 알림을 활성화할지 여부를 지정하거나 애플리케이션이 +클라우드와 데이터를 동기화할 빈도를 지정할 수 있게 해줍니다.

+ +

자신의 앱에 설정을 제공하고자 하는 경우, Android의 +{@link android.preference.Preference} API를 사용하여 다른 Android 앱(시스템 설정 포함)의 사용자 환경과 +일관성을 유지하는 인터페이스를 구축할 수 있게 해야 합니다. 이 문서에서는 +{@link android.preference.Preference} API를 사용하여 앱 설정을 구축하는 방법을 설명합니다.

+ +
+

설정 디자인

+

설정을 디자인하는 방법에 관련된 정보는 설정 디자인 가이드를 읽어보십시오.

+
+ + + +

그림 1. Android 메시지 앱의 설정에서 가져온 +스크린샷입니다. {@link android.preference.Preference}가 정의한 항목을 선택하면 +인터페이스가 열려 설정을 변경할 수 있게 됩니다.

+ + + + +

개요

+ +

사용자 인터페이스를 구축할 때에는 {@link android.view.View} 객체를 사용하지만, 설정은 그 대신 +{@link android.preference.Preference} 클래스의 다양한 하위 클래스를 사용하여 구축합니다. +이와 같은 하위 클래스는 XML 파일에서 선언합니다.

+ +

{@link android.preference.Preference} 객체는 하나의 설정을 이루는 기본 +단위입니다. 각각의 {@link android.preference.Preference}는 목록의 항목으로 +나타나며 사용자가 설정을 수정하기에 적절한 UI를 제공합니다. 예를 들어 {@link +android.preference.CheckBoxPreference}는 확인란을 표시하는 목록 항목을 만들고, {@link +android.preference.ListPreference}는 선택 목록이 있는 대화를 여는 항목을 만듭니다.

+ +

각각의 {@link android.preference.Preference}를 추가할 때마다 상응하는 키-값 쌍이 있어 +시스템이 이를 사용하여 해당 설정을 앱의 설정에 대한 기본 {@link android.content.SharedPreferences} +파일에 저장합니다. 사용자가 설정을 변경하면 시스템이 +{@link android.content.SharedPreferences} 파일에 있는 상응하는 값을 개발자 대신 업데이트합니다. 개발자가 직접 +연관된 {@link android.content.SharedPreferences} 파일과 상호 작용을 해야 하는 경우는 +사용자의 설정을 기반으로 앱의 동작을 결정하기 위해 값을 읽어야 할 때뿐입니다.

+ +

각 설정에 대하여 {@link android.content.SharedPreferences}에 저장된 값은 다음과 같은 +데이터 유형 중 한 가지를 취할 수 있습니다.

+ +
    +
  • Boolean
  • +
  • Float
  • +
  • Int
  • +
  • Long
  • +
  • String
  • +
  • String {@link java.util.Set}
  • +
+ +

앱의 설정 UI는 +{@link android.view.View} 객체 대신 +{@link android.preference.Preference} 객체를 사용하여 구축되기 때문에, 목록 설정을 표시하려면 특수 {@link android.app.Activity} 또는 +{@link android.app.Fragment} 하위 클래스를 사용해야 합니다.

+ +
    +
  • 앱이 Android 3.0 이전 버전(API 레벨 10 이하)을 지원하는 경우, 액티비티를 구축할 때 +{@link android.preference.PreferenceActivity} 클래스의 확장으로 구축해야 합니다.
  • +
  • Android 3.0 이후의 경우에는 대신 기존의 {@link android.app.Activity}를 +사용해야 합니다. 이것은 앱 설정을 표시하는 {@link android.preference.PreferenceFragment}를 호스팅합니다. +하지만, 여러 개의 설정 그룹이 있는 경우 {@link android.preference.PreferenceActivity}를 사용하여 +대형 화면에 맞는 창 두 개짜리 레이아웃을 만들 수도 있습니다.
  • +
+ +

{@link android.preference.PreferenceActivity}와 {@link +android.preference.PreferenceFragment}의 인스턴스를 설정하는 방법은 기본 설정 액티비티 만들기기본 설정 +프래그먼트 사용하기에 관련된 섹션에서 논합니다.

+ + +

기본 설정

+ +

앱에 대한 설정은 모두 {@link +android.preference.Preference} 클래스의 특정 하위 클래스로 표현됩니다. 각 하위 클래스에 핵심 속성이 한 세트씩 포함되어 있어 +설정의 제목과 기본 값 등과 같은 것을 지정할 수 있게 해줍니다. 각 하위 클래스는 또한 자신만의 +특수 속성과 사용자 인터페이스도 제공합니다. 예를 들어, 그림 1에서는 메시지 앱의 설정에서 +가져온 스크린샷을 나타낸 것입니다. 설정 화면에 있는 각 목록 항목은 각기 서로 다른 {@link +android.preference.Preference} 객체로 지원됩니다.

+ +

가장 보편적인 기본 설정을 몇 가지만 소개하면 다음과 같습니다.

+ +
+
{@link android.preference.CheckBoxPreference}
+
활성화되었거나 비활성화된 설정에 대한 확인란이 있는 항목을 표시합니다. 저장된 값은 +부울입니다(확인란이 선택된 경우 true).
+ +
{@link android.preference.ListPreference}
+
무선 버튼 목록이 있는 대화를 엽니다. 저장된 값은 +지원되는 값 유형(위에 목록으로 나열) 중 어느 것이라도 될 수 있습니다.
+ +
{@link android.preference.EditTextPreference}
+
{@link android.widget.EditText} 위젯이 있는 대화를 엽니다. 저장된 값은 {@link +java.lang.String}입니다.
+
+ +

다른 모든 하위 클래스와 이에 상응하는 속성의 목록을 보려면 {@link android.preference.Preference} + 클래스를 참조하십시오.

+ +

물론 기본 제공 클래스만으로는 필요한 것을 모두 충족할 수 없고 앱에 무언가 좀 더 특수한 것이 +필요할 수도 있습니다. 예를 들어 플랫폼은 현재 숫자나 날짜를 선택할 수 있는 {@link +android.preference.Preference} 클래스를 제공하지 않습니다. 따라서 개발자 나름대로 +{@link android.preference.Preference} 하위 클래스를 정의해야 할 수도 있습니다. 이 작업을 수행하는 데 유용한 내용인 사용자 지정 기본 설정 구축하기에 관한 섹션을 참조하십시오.

+ + + +

XML로 기본 설정 정의하기

+ +

새로운 {@link android.preference.Preference} 객체를 런타임에 인스턴트화하는 것도 가능하지만, +설정 목록을 정의할 때에는 {@link android.preference.Preference} +객체의 계층과 함께 XML을 사용해야 합니다. 설정 컬렉션을 정의하는 데 XM 파일을 사용하는 것이 선호되는 이유는 이 파일이 +읽기 쉬운 구조를 제공하여 업데이트가 단순하기 때문입니다. 또한, 앱의 설정은 보통 +미리 정의되어 있습니다. 다만 개발자도 여전히 런타임에 설정 컬렉션을 수정할 수 있습니다.

+ +

각 {@link android.preference.Preference} 하위 클래스는 클래스 이름에 일치하는 XML 요소로 +선언하면 됩니다. 예를 들면 {@code <CheckBoxPreference>}가 이에 해당됩니다.

+ +

이 XML 파일은 반드시 {@code res/xml/} 디렉터리에 저장해야 합니다. 파일의 이름은 무엇이든 원하는 대로 지정할 수 있지만, +일반적으로는 {@code preferences.xml}이라고 명명합니다. 파일은 하나만 필요한 것이 보통입니다. +왜냐하면 계층에 있는 분기(자신만의 설정 목록을 엶)는 +{@link android.preference.PreferenceScreen}의 중첩된 인스턴스를 사용하여 선언되기 때문입니다.

+ +

참고: 설정에 다중 창 레이아웃을 만들고자 하는 경우, +각 프래그먼트에 대해 별도의 XML 파일이 필요합니다.

+ +

XML 파일의 루트 노드는 반드시 {@link android.preference.PreferenceScreen +<PreferenceScreen>} 요소여야 합니다. 바로 이 요소 내에 각 {@link +android.preference.Preference}를 추가하는 것입니다. +{@link android.preference.PreferenceScreen <PreferenceScreen>} 요소 내에 추가하는 각 하위는 설정 목록에서 +각기 항목 하나씩으로 나타납니다.

+ +

예:

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <CheckBoxPreference
+        android:key="pref_sync"
+        android:title="@string/pref_sync"
+        android:summary="@string/pref_sync_summ"
+        android:defaultValue="true" />
+    <ListPreference
+        android:dependency="pref_sync"
+        android:key="pref_syncConnectionType"
+        android:title="@string/pref_syncConnectionType"
+        android:dialogTitle="@string/pref_syncConnectionType"
+        android:entries="@array/pref_syncConnectionTypes_entries"
+        android:entryValues="@array/pref_syncConnectionTypes_values"
+        android:defaultValue="@string/pref_syncConnectionTypes_default" />
+</PreferenceScreen>
+
+ +

이 예시에서는 {@link android.preference.CheckBoxPreference}와 {@link +android.preference.ListPreference}가 하나씩 있습니다. 두 항목 모두 다음과 같은 세 가지 속성을 포함하고 있습니다.

+ +
+
{@code android:key}
+
이 속성은 데이터 값을 유지하는 기본 설정에 필수입니다. 이것은 고유키(문자)를 +나타내며, 시스템이 이것을 사용하여 이 설정의 값을 {@link +android.content.SharedPreferences}에 저장합니다. +

이 속성이 필요하지 않은 인스턴스는 기본 설정이 +{@link android.preference.PreferenceCategory} 또는 {@link android.preference.PreferenceScreen}인 경우, 또는 +기본 설정이 {@link android.content.Intent}를 호출할 것을 나타내거나({@code <intent>} 요소로) {@link android.app.Fragment}를 표시하도록 지정하는 경우({@code +android:fragment} 속성으로)뿐입니다.

+
+
{@code android:title}
+
이것은 설정에 대하여 사용자가 볼 수 있는 이름을 제공합니다.
+
{@code android:defaultValue}
+
이것은 시스템이 {@link +android.content.SharedPreferences} 파일에 설정해야 하는 초기 값을 나타냅니다. 모든 설정에 기본 값을 제공해야 +합니다.
+
+ +

다른 모든 지원되는 속성에 대해서는 {@link +android.preference.Preference}(및 각각의 하위 클래스) 관련 문서를 참조하십시오.

+ + +
+ +

그림 2. 제목이 있는 설정 +카테고리입니다.
1. 카테고리는 {@link +android.preference.PreferenceCategory <PreferenceCategory>} 요소가 지정합니다.
2. 제목은 +{@code android:title} 속성으로 지정합니다.

+
+ + +

설정 목록이 약 10개 항목을 초과하면 제목을 추가하여 +설정 그룹을 정의하거나, 해당 그룹을 별도의 +화면에 표시하는 것이 좋을 수도 있습니다. 이러한 옵션은 다음 섹션에 설명되어 있습니다.

+ + +

설정 그룹 만들기

+ +

10개 이상의 설정 목록을 제시하는 경우, 사용자가 +이들을 둘러보고 이해하며 처리하는 데 어려움을 겪을 수 있습니다. 이 문제를 해결하려면 +설정의 일부 또는 모두를 그룹으로 나누어 사실상 하나의 긴 목록을 여러 개의 더 짧은 목록으로 +바꿔주면 됩니다. 관련된 설정 그룹 하나를 나타낼 때에는 다음과 같은 두 가지 방식 중 하나를 택하면 됩니다.

+ + + +

이와 같은 그룹화 기법 중 하나 또는 둘 모두를 사용하여 앱의 설정을 조직화할 수 있습니다. 어느 것을 +사용할지, 설정을 어떻게 나눌지 결정할 때에는 Android +디자인의 설정 가이드에 있는 지침을 따라야 합니다.

+ + +

제목 사용하기

+ +

여러 개의 설정 그룹 사이에 구분선과 제목을 제공하고자 하는 경우(그림 2에 표시된 것과 같이), +각 {@link android.preference.Preference} 객체 그룹을 {@link +android.preference.PreferenceCategory} 내부에 배치합니다.

+ +

예:

+ +
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <PreferenceCategory 
+        android:title="@string/pref_sms_storage_title"
+        android:key="pref_key_storage_settings">
+        <CheckBoxPreference
+            android:key="pref_key_auto_delete"
+            android:summary="@string/pref_summary_auto_delete"
+            android:title="@string/pref_title_auto_delete"
+            android:defaultValue="false"... />
+        <Preference 
+            android:key="pref_key_sms_delete_limit"
+            android:dependency="pref_key_auto_delete"
+            android:summary="@string/pref_summary_delete_limit"
+            android:title="@string/pref_title_sms_delete"... />
+        <Preference 
+            android:key="pref_key_mms_delete_limit"
+            android:dependency="pref_key_auto_delete"
+            android:summary="@string/pref_summary_delete_limit"
+            android:title="@string/pref_title_mms_delete" ... />
+    </PreferenceCategory>
+    ...
+</PreferenceScreen>
+
+ + +

보조 화면 사용하기

+ +

설정 그룹 여러 개를 보조 화면에 배치하고자 하는 경우(그림 3에 표시된 것과 같이), 해당 +{@link android.preference.Preference} 객체 그룹을 {@link +android.preference.PreferenceScreen} 안에 배치합니다.

+ + +

그림 3. 설정 보조 화면입니다. {@code +<PreferenceScreen>} 요소가 +항목을 만들며, 이 항목이 선택되면 별도의 목록이 열려 중첩된 설정을 표시합니다.

+ +

예:

+ +
+<PreferenceScreen  xmlns:android="http://schemas.android.com/apk/res/android">
+    <!-- opens a subscreen of settings -->
+    <PreferenceScreen
+        android:key="button_voicemail_category_key"
+        android:title="@string/voicemail"
+        android:persistent="false">
+        <ListPreference
+            android:key="button_voicemail_provider_key"
+            android:title="@string/voicemail_provider" ... />
+        <!-- opens another nested subscreen -->
+        <PreferenceScreen
+            android:key="button_voicemail_setting_key"
+            android:title="@string/voicemail_settings"
+            android:persistent="false">
+            ...
+        </PreferenceScreen>
+        <RingtonePreference
+            android:key="button_voicemail_ringtone_key"
+            android:title="@string/voicemail_ringtone_title"
+            android:ringtoneType="notification" ... />
+        ...
+    </PreferenceScreen>
+    ...
+</PreferenceScreen>
+
+ + +

인텐트 사용하기

+ +

어떤 경우에는 기본 설정 항목을 사용하여 설정 화면 대신 여러 가지 액티비티를 +열고자 할 수도 있습니다. 예를 들어 웹 브라우저를 열어 웹 페이지를 보는 것이 이에 해당됩니다. 사용자가 기본 설정 항목을 선택할 때 {@link +android.content.Intent}를 호출하도록 하려면, {@code <intent>} +요소를 상응하는 {@code <Preference>} 요소의 하위로 추가하면 됩니다.

+ +

예를 들어 다음은 기본 설정 항목을 사용하여 웹 페이지를 열도록 하는 방법입니다.

+ +
+<Preference android:title="@string/prefs_web_page" >
+    <intent android:action="android.intent.action.VIEW"
+            android:data="http://www.example.com" />
+</Preference>
+
+ +

다음 속성을 사용하여 명시적 인텐트와 암시적 인텐트 두 가지를 모두 만들 수 있습니다.

+ +
+
{@code android:action}
+
할당할 작업이며, {@link android.content.Intent#setAction setAction()} +메서드를 따릅니다.
+
{@code android:data}
+
할당할 데이터이며, {@link android.content.Intent#setData setData()} 메서드를 따릅니다.
+
{@code android:mimeType}
+
할당할 MIME 유형이며, {@link android.content.Intent#setType setType()} +메서드를 따릅니다.
+
{@code android:targetClass}
+
구성 요소 이름의 클래스 부분이며, {@link android.content.Intent#setComponent +setComponent()} 메서드를 따릅니다.
+
{@code android:targetPackage}
+
구성 요소 이름의 패키지 부분이며, {@link +android.content.Intent#setComponent setComponent()} 메서드를 따릅니다.
+
+ + + +

기본 설정 액티비티 만들기

+ +

설정을 액티비티에서 표시하려면 {@link +android.preference.PreferenceActivity} 클래스를 확장하면 됩니다. 이것은 일반적인 {@link +android.app.Activity} 클래스 확장의 일종입니다. 이는 {@link +android.preference.Preference} 객체의 계층에 기반한 설정 목록을 표시합니다. {@link android.preference.PreferenceActivity}는 +사용자가 변경 작업을 하면 각 {@link +android.preference.Preference}와 연관된 설정을 유지합니다.

+ +

참고: Android 3.0 이상에 맞춰 애플리케이션을 개발하는 경우, +대신 {@link android.preference.PreferenceFragment}를 사용해야 합니다. 다음 섹션의 +기본 설정 프래그먼트 사용하기 관련 내용을 참조하십시오.

+ +

여기서 기억해야 할 가장 중요한 점은 {@link +android.preference.PreferenceActivity#onCreate onCreate()} 콜백 중에 보기 레이아웃을 로딩해서는 안 된다는 것입니다. 그 대신 {@link +android.preference.PreferenceActivity#addPreferencesFromResource addPreferencesFromResource()}를 호출하여 +XML 파일에서 선언한 기본 설정을 액티비티에 추가합니다. 예를 들어 다음은 기능적인 +{@link android.preference.PreferenceActivity}에 필요한 가장 최소한의 코드를 나타낸 것입니다.

+ +
+public class SettingsActivity extends PreferenceActivity {
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        addPreferencesFromResource(R.xml.preferences);
+    }
+}
+
+ +

사실 이 코드만으로 몇몇 앱에는 충분합니다. 사용자가 기본 설정을 수정하자마자 +시스템이 해당 변경을 기본 {@link android.content.SharedPreferences} 파일에 저장하여, +사용자의 설정을 확인해야 할 때 다른 애플리케이션 구성 요소가 이를 읽을 수 있도록 하기 때문입니다. 하지만 +대다수의 앱은 기본 설정에 일어나는 변경을 수신 대기하기 위해 약간의 코드가 더 필요합니다. +{@link android.content.SharedPreferences} 파일에 일어나는 변경을 수신 대기하는 데 관한 +자세한 정보는 기본 설정 읽기에 관한 섹션을 참조하십시오.

+ + + + +

기본 설정 프래그먼트 사용하기

+ +

Android 3.0(API 레벨 11) 이상에 맞춰 개발하는 경우, {@link +android.preference.PreferenceFragment}를 사용하여 {@link android.preference.Preference} +객체 목록을 표시해야 합니다. {@link android.preference.PreferenceFragment}는 모든 액티비티에 추가할 수 있습니다. 즉, +{@link android.preference.PreferenceActivity}를 사용하지 않아도 됩니다.

+ +

프래그먼트는 액티비티만 +사용하는 것에 비해 애플리케이션에 보다 유연한 아키텍처를 제공하며, 이는 구축하는 +액티비티의 종류와 무관하게 적용됩니다. 따라서 설정 표시를 제어하는 데에는 {@link +android.preference.PreferenceFragment}를 {@link +android.preference.PreferenceActivity} 대신 사용하는 방안을 권장합니다(가능한 경우).

+ +

{@link android.preference.PreferenceFragment} 구현은 매우 간단합니다. +{@link android.preference.PreferenceFragment#onCreate onCreate()} 메서드를 정의하여 기본 설정 파일을 +{@link android.preference.PreferenceFragment#addPreferencesFromResource +addPreferencesFromResource()}로 로딩하도록 하기만 하면 됩니다. 예:

+ +
+public static class SettingsFragment extends PreferenceFragment {
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        // Load the preferences from an XML resource
+        addPreferencesFromResource(R.xml.preferences);
+    }
+    ...
+}
+
+ +

그런 다음 이 프래그먼트를 {@link android.app.Activity}에 추가하기만 하면 되고, 이는 다른 모든 +{@link android.app.Fragment}에서와 마찬가지입니다. 예:

+ +
+public class SettingsActivity extends Activity {
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        // Display the fragment as the main content.
+        getFragmentManager().beginTransaction()
+                .replace(android.R.id.content, new SettingsFragment())
+                .commit();
+    }
+}
+
+ +

참고: {@link android.preference.PreferenceFragment}에는 자신만의 +{@link android.content.Context} 객체가 없습니다. {@link android.content.Context} +객체가 필요한 경우, {@link android.app.Fragment#getActivity()}를 호출하면 됩니다. 하지만, +{@link android.app.Fragment#getActivity()}를 호출하는 것은 프래그먼트가 액티비티에 첨부되어 있는 경우만으로 국한시켜야 한다는 점을 유의하십시오. 프래그먼트가 +아직 첨부되지 않았거나 수명 주기가 끝날 무렵 분리된 경우, {@link +android.app.Fragment#getActivity()}가 null을 반환합니다.

+ + +

설정 기본 값

+ +

여러분이 만드는 기본 설정은 애플리케이션에 중요한 동작을 정의하는 경우가 많을 것입니다. 따라서 +연관된 {@link android.content.SharedPreferences} 파일을 +각 {@link android.preference.Preference}에 대한 기본 값으로 초기화하여 사용자가 애플리케이션을 처음 열 때 +적용하는 것이 중요합니다.

+ +

가장 먼저 해야 할 일은 XML 파일 내의 각 {@link +android.preference.Preference} +객체에 대해 기본 값을 지정하는 것입니다. 이때 {@code android:defaultValue} 속성을 사용합니다. 이 값은 상응하는 +{@link android.preference.Preference} 객체에 대해 적절한 어느 데이터 유형이라도 될 수 있습니다. 예: +

+ +
+<!-- default value is a boolean -->
+<CheckBoxPreference
+    android:defaultValue="true"
+    ... />
+
+<!-- default value is a string -->
+<ListPreference
+    android:defaultValue="@string/pref_syncConnectionTypes_default"
+    ... />
+
+ +

그런 다음, 애플리케이션의 기본 액티비티에 있는 {@link android.app.Activity#onCreate onCreate()} +메서드로부터—또한 사용자가 애플리케이션에 처음으로 들어올 통로가 될 수 있는 +다른 모든 액티비티도 포함—{@link android.preference.PreferenceManager#setDefaultValues +setDefaultValues()}를 호출합니다.

+ +
+PreferenceManager.setDefaultValues(this, R.xml.advanced_preferences, false);
+
+ +

이것을 {@link android.app.Activity#onCreate onCreate()} 중에 호출하면 +애플리케이션이 기본 설정으로 적절히 초기화되도록 보장할 수 있습니다. 이것은 애플리케이션이 +몇 가지 동작을 결정하기 위해 읽어야 할 수도 있습니다(예를 들어 셀룰러 네트워크에서 데이터를 +다운로드할지 여부 등).

+ +

이 메서드는 다음과 같은 세 개의 인수를 취합니다.

+
    +
  • 애플리케이션 {@link android.content.Context}.
  • +
  • 기본 값을 설정하고자 하는 기본 설정 XML 파일에 대한 리소스 ID입니다.
  • +
  • 기본 값을 한 번 이상 설정해야 하는지 여부를 나타내는 부울 값입니다. +

    false인 경우, 시스템은 이 메서드가 전에 한 번도 호출된 적이 없을 경우에만 +기본 값을 설정합니다(아니면 기본 값을 공유한 기본 설정 파일에 있는 {@link android.preference.PreferenceManager#KEY_HAS_SET_DEFAULT_VALUES} +가 안전합니다).

  • +
+ +

세 번째 인수를 false로 설정해 두는 한 이 메서드를 액티비티가 시작될 때마다 +안전하게 호출할 수 있으며, 그렇게 해도 사용자의 저장된 기본 설정을 기본값으로 초기화하여 +재정의하지 않습니다. 하지만 이를 true로 설정하면, 이전의 모든 값을 +기본 값으로 재정의하게 됩니다.

+ + + +

기본 설정 헤더 사용하기

+ +

드문 경우지만 설정을 디자인할 때 첫 화면에는 +보조 화면 목록만 표시하도록 하고자 할 수도 있습니다(예: 시스템 설정 앱, +그림 4와 5 참조). 그러한 디자인을 Android 3.0 이상을 대상으로 개발하는 경우, Android 3.0에 있는 +새로운 "헤더" 기능을 사용해야 합니다. 이것이 중첩된 +{@link android.preference.PreferenceScreen} 요소를 사용하여 보조 화면을 구축하는 방안을 대신합니다.

+ +

헤더를 사용하여 설정을 구축하려면 다음과 같이 해야 합니다.

+
    +
  1. 각 설정 그룹을 별개의 {@link +android.preference.PreferenceFragment} 인스턴스로 구분합니다. 다시 말해, 설정 그룹마다 별도의 XML 파일이 하나씩 있어야 한다는 +뜻입니다.
  2. +
  3. 각 설정 그룹을 목록으로 나열하는 XML 헤더 파일을 생성하고 어느 프래그먼트에 +상응하는 설정 목록이 들어있는지 선언합니다.
  4. +
  5. {@link android.preference.PreferenceActivity} 클래스를 확장하여 설정을 호스팅하도록 합니다.
  6. +
  7. {@link +android.preference.PreferenceActivity#onBuildHeaders onBuildHeaders()} 콜백을 구현하여 헤더 파일을 +나타냅니다.
  8. +
+ +

이 디자인을 사용하는 데 있어 커다란 이점은 {@link android.preference.PreferenceActivity}가 +(앱이) 대형 화면에서 실행될 때 그림 4에서 나타낸 것과 같이 창 두 개짜리 레이아웃을 자동으로 표시한다는 것입니다.

+ +

애플리케이션이 Android 3.0 이전 버전을 지원한다 하더라도 애플리케이션이 +{@link android.preference.PreferenceFragment}를 사용하여 +신형 기기에서 창 두 개짜리 표시를 지원하도록 하면서도 구형 기기에서는 일반적인 다중 화면 계층을 +여전히 지원하도록 할 수도 있습니다(기본 설정 헤더로 +이전 버전 지원하기를 참조하십시오).

+ + +

그림 4. 헤더가 있는 창 두 개짜리 레이아웃입니다.
1. 헤더는 +XML 헤더 파일로 정의됩니다.
2. 각 설정 그룹은 +{@link android.preference.PreferenceFragment}가 정의하며, 이는 헤더 파일에 있는 {@code <header>} 요소가 +지정합니다.

+ + +

그림 5. 설정 헤더가 있는 핸드셋 기기입니다. 항목을 선택하면 +연관된 {@link android.preference.PreferenceFragment}가 헤더를 +대체합니다.

+ + +

헤더 파일 만들기

+ +

헤더 목록에 있는 각 설정 그룹은 루트 {@code <preference-headers>} +요소 안에 있는 {@code <header>} 요소 하나로 나타냅니다. 예:

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<preference-headers xmlns:android="http://schemas.android.com/apk/res/android">
+    <header 
+        android:fragment="com.example.prefs.SettingsActivity$SettingsFragmentOne"
+        android:title="@string/prefs_category_one"
+        android:summary="@string/prefs_summ_category_one" />
+    <header 
+        android:fragment="com.example.prefs.SettingsActivity$SettingsFragmentTwo"
+        android:title="@string/prefs_category_two"
+        android:summary="@string/prefs_summ_category_two" >
+        <!-- key/value pairs can be included as arguments for the fragment. -->
+        <extra android:name="someKey" android:value="someHeaderValue" />
+    </header>
+</preference-headers>
+
+ +

각 헤더는 {@code android:fragment} 속성으로 {@link +android.preference.PreferenceFragment} 예를 선언하며 이는 사용자가 헤더를 선택하면 열려야 합니다.

+ +

{@code <extras>} 요소를 사용하면 키-값 쌍을 {@link +android.os.Bundle} 내의 프래그먼트에 전달할 수 있게 해줍니다. 이 프래그먼트가 인수를 검색하려면 {@link +android.app.Fragment#getArguments()}를 호출하면 됩니다. 인수를 프래그먼트에 전달하는 데에는 여러 가지 이유가 있을 수 있지만, +한 가지 중요한 이유를 예로 들면 각 그룹에 대해 {@link +android.preference.PreferenceFragment}의 같은 하위 클래스를 재사용하고, 이 인수를 사용하여 해당 프래그먼트가 로딩해야 하는 +기본 설정 XML 파일이 무엇인지 나타낼 수 있다는 점입니다.

+ +

예를 들어 다음은 여러 가지 설정 그룹에 재사용할 수 있는 프래그먼트입니다. 이것은 +각 헤더가 {@code "settings"} 키로 {@code <extra>} 인수를 정의하는 경우를 나타낸 것입니다.

+ +
+public static class SettingsFragment extends PreferenceFragment {
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        String settings = getArguments().getString("settings");
+        if ("notifications".equals(settings)) {
+            addPreferencesFromResource(R.xml.settings_wifi);
+        } else if ("sync".equals(settings)) {
+            addPreferencesFromResource(R.xml.settings_sync);
+        }
+    }
+}
+
+ + + +

헤더 표시하기

+ +

기본 설정 헤더를 표시하려면 {@link +android.preference.PreferenceActivity#onBuildHeaders onBuildHeaders()} 콜백 메서드를 구현하고 +{@link android.preference.PreferenceActivity#loadHeadersFromResource +loadHeadersFromResource()}를 호출해야 합니다. 예:

+ +
+public class SettingsActivity extends PreferenceActivity {
+    @Override
+    public void onBuildHeaders(List<Header> target) {
+        loadHeadersFromResource(R.xml.preference_headers, target);
+    }
+}
+
+ +

사용자가 헤더 목록에서 항목을 하나 선택하면 시스템이 연관된 {@link +android.preference.PreferenceFragment}를 엽니다.

+ +

참고: 기본 설정 헤더를 사용하는 경우, {@link +android.preference.PreferenceActivity}의 하위 클래스가 {@link +android.preference.PreferenceActivity#onCreate onCreate()} 메서드를 구현하지 않아도 됩니다. 액티비티에 대한 필수 작업은 +헤더를 로딩하는 것뿐이기 때문입니다.

+ + +

기본 설정 헤더로 이전 버전 지원하기

+ +

애플리케이션이 Android 3.0 이전 버전을 지원하는 경우에도 여전히 헤더를 사용하여 +Android 3.0 이상에서 창 두 개짜리 레이아웃을 제공하도록 할 수 있습니다. 개발자가 해야 할 일은 추가로 기본 설정 XML 파일을 +생성하는 것뿐입니다. 이 파일은 마치 헤더 항목처럼 동작하는 기본적인 {@link android.preference.Preference +<Preference>} 요소를 사용합니다(이것을 이전 Android 버전이 사용하도록 +할 예정).

+ +

하지만 새로운 {@link android.preference.PreferenceScreen}을 여는 대신 각 {@link +android.preference.Preference <Preference>} 요소가 {@link android.content.Intent}를 하나씩 +{@link android.preference.PreferenceActivity}에 전송합니다. 이것이 로딩할 XML 파일이 무엇인지를 +나타냅니다.

+ +

예를 들어 다음은 Android 3.0 이상에서 사용되는 기본 설정 헤더에 대한 +XML 파일입니다({@code res/xml/preference_headers.xml}).

+ +
+<preference-headers xmlns:android="http://schemas.android.com/apk/res/android">
+    <header 
+        android:fragment="com.example.prefs.SettingsFragmentOne"
+        android:title="@string/prefs_category_one"
+        android:summary="@string/prefs_summ_category_one" />
+    <header 
+        android:fragment="com.example.prefs.SettingsFragmentTwo"
+        android:title="@string/prefs_category_two"
+        android:summary="@string/prefs_summ_category_two" />
+</preference-headers>
+
+ +

그리고 다음은, Android 3.0 이전 버전에 같은 헤더를 제공하는 기본 설정 +파일입니다({@code res/xml/preference_headers_legacy.xml}).

+ +
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <Preference 
+        android:title="@string/prefs_category_one"
+        android:summary="@string/prefs_summ_category_one"  >
+        <intent 
+            android:targetPackage="com.example.prefs"
+            android:targetClass="com.example.prefs.SettingsActivity"
+            android:action="com.example.prefs.PREFS_ONE" />
+    </Preference>
+    <Preference 
+        android:title="@string/prefs_category_two"
+        android:summary="@string/prefs_summ_category_two" >
+        <intent 
+            android:targetPackage="com.example.prefs"
+            android:targetClass="com.example.prefs.SettingsActivity"
+            android:action="com.example.prefs.PREFS_TWO" />
+    </Preference>
+</PreferenceScreen>
+
+ +

{@code <preference-headers>}에 대한 지원이 Android 3.0에서 추가되었기 때문에 시스템이 +{@link android.preference.PreferenceActivity#onBuildHeaders onBuildHeaders()}를 {@link +android.preference.PreferenceActivity}에서 호출하는 것은 Android 3.0 이상에서 실행될 때뿐입니다. "레거시" 헤더 파일을 +로딩하려면({@code preference_headers_legacy.xml}) 반드시 Android +버전을 확인해야 하며, 해당 버전이 Android 3.0 이전인 경우({@link +android.os.Build.VERSION_CODES#HONEYCOMB}), {@link +android.preference.PreferenceActivity#addPreferencesFromResource addPreferencesFromResource()}를 호출하여 +레거시 헤더 파일을 로딩해야 합니다. 예:

+ +
+@Override
+public void onCreate(Bundle savedInstanceState) {
+    super.onCreate(savedInstanceState);
+    ...
+
+    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
+        // Load the legacy preferences headers
+        addPreferencesFromResource(R.xml.preference_headers_legacy);
+    }
+}
+
+// Called only on Honeycomb and later
+@Override
+public void onBuildHeaders(List<Header> target) {
+   loadHeadersFromResource(R.xml.preference_headers, target);
+}
+
+ +

이제 남은 할 일이라고는 {@link android.content.Intent}를 처리하는 것뿐입니다. 이것은 +액티비티로 전달되어 어느 기본 설정 파일을 로딩해야 하는지 식별하는 데 쓰입니다. 그럼 이제 인텐트의 작업을 검색하여 기본 설정 XML의 +{@code <intent>} 태그에서 사용한 알려진 작업 문자열에 비교해보겠습니다.

+ +
+final static String ACTION_PREFS_ONE = "com.example.prefs.PREFS_ONE";
+...
+
+@Override
+public void onCreate(Bundle savedInstanceState) {
+    super.onCreate(savedInstanceState);
+
+    String action = getIntent().getAction();
+    if (action != null && action.equals(ACTION_PREFS_ONE)) {
+        addPreferencesFromResource(R.xml.preferences);
+    }
+    ...
+
+    else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
+        // Load the legacy preferences headers
+        addPreferencesFromResource(R.xml.preference_headers_legacy);
+    }
+}
+
+ +

{@link +android.preference.PreferenceActivity#addPreferencesFromResource addPreferencesFromResource()}를 연이어 호출하면 +모든 기본 설정을 하나의 목록에 쌓게 된다는 점을 유의하십시오. 따라서 이것은 'Else-if' 문이 있는 조건을 변경하여 딱 한 번만 +호출하도록 주의해야 합니다.

+ + + + + +

기본 설정 읽기

+ +

기본적으로 앱의 기본 설정은 모두 +애플리케이션 내의 어디서든 정적 메서드 {@link +android.preference.PreferenceManager#getDefaultSharedPreferences +PreferenceManager.getDefaultSharedPreferences()}를 호출하면 액세스할 수 있는 파일에 저장됩니다. 이것은 {@link +android.content.SharedPreferences} 객체를 반환하며, 여기에 {@link +android.preference.PreferenceActivity}에서 사용한 {@link android.preference.Preference} 객체와 +연관된 모든 키-값 쌍이 들어있습니다.

+ +

예를 들어 다음은 기본 설정 값 중 하나를 애플리케이션 내의 다른 모든 액티비티에서 읽는 방법을 +나타낸 것입니다.

+ +
+SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this);
+String syncConnPref = sharedPref.getString(SettingsActivity.KEY_PREF_SYNC_CONN, "");
+
+ + + +

기본 설정 변경 수신 대기

+ +

사용자가 기본 설정 중 하나를 변경하자마자 이에 대해 알림을 받는 것이 좋은 데에는 몇 가지 +이유가 있습니다. 기본 설정 중 어느 하나에라도 변경이 발생했을 때 콜백을 받으려면, +{@link android.content.SharedPreferences.OnSharedPreferenceChangeListener +SharedPreference.OnSharedPreferenceChangeListener} 인터페이스를 구현하고 +{@link android.content.SharedPreferences} 객체에 대한 수신기를 등록합니다. 이때 {@link +android.content.SharedPreferences#registerOnSharedPreferenceChangeListener +registerOnSharedPreferenceChangeListener()}를 호출하면 됩니다.

+ +

이 인터페이스에는 콜백 메서드가 {@link +android.content.SharedPreferences.OnSharedPreferenceChangeListener#onSharedPreferenceChanged +onSharedPreferenceChanged()} 하나뿐이며, 인터페이스를 액티비티의 일부분으로 구현하는 것이 +가장 쉬운 방법일 공산이 큽니다. 예:

+ +
+public class SettingsActivity extends PreferenceActivity
+                              implements OnSharedPreferenceChangeListener {
+    public static final String KEY_PREF_SYNC_CONN = "pref_syncConnectionType";
+    ...
+
+    public void onSharedPreferenceChanged(SharedPreferences sharedPreferences,
+        String key) {
+        if (key.equals(KEY_PREF_SYNC_CONN)) {
+            Preference connectionPref = findPreference(key);
+            // Set summary to be the user-description for the selected value
+            connectionPref.setSummary(sharedPreferences.getString(key, ""));
+        }
+    }
+}
+
+ +

이 예시에서 메서드는 변경된 설정이 알려진 기본 설정 키에 대한 것인지 여부를 확인합니다. 이것은 +{@link android.preference.PreferenceActivity#findPreference findPreference()}를 호출하여 +변경된 {@link android.preference.Preference} 객체를 가져오는데, 이렇게 해야 항목의 요약을 수정하여 +사용자의 선택에 대한 설명이 되도록 할 수 있습니다. 다시 말해, 설정이 {@link +android.preference.ListPreference} 또는 다른 다중 선택 설정인 경우, 설정이 변경되어 현재 상태를 표시하도록 하면 {@link +android.preference.Preference#setSummary setSummary()}를 호출해야 한다는 뜻입니다(예를 들어 +그림 5에 표시된 절전 모드 설정과 같음).

+ +

참고: Android 디자인 문서의 설정 관련 내용에서 설명한 바와 같이, 사용자가 기본 설정을 변경할 때마다 +{@link android.preference.ListPreference}의 요약을 업데이트하는 것을 권장합니다. 이렇게 하여 현재 설정을 +나타내는 것입니다.

+ +

액티비티에서 적절한 수명 주기 관리를 수행하려면 +{@link android.content.SharedPreferences.OnSharedPreferenceChangeListener}를 등록하고 등록 해제하는 작업은 각각 {@link +android.app.Activity#onResume} 및 {@link android.app.Activity#onPause} 콜백 중에 수행하는 것을 권장합니다.

+ +
+@Override
+protected void onResume() {
+    super.onResume();
+    getPreferenceScreen().getSharedPreferences()
+            .registerOnSharedPreferenceChangeListener(this);
+}
+
+@Override
+protected void onPause() {
+    super.onPause();
+    getPreferenceScreen().getSharedPreferences()
+            .unregisterOnSharedPreferenceChangeListener(this);
+}
+
+ +

주의: {@link +android.content.SharedPreferences#registerOnSharedPreferenceChangeListener +registerOnSharedPreferenceChangeListener()}를 호출하면 +현재의 경우, 기본 설정 관리자가 수신기에 대한 강력한 참조를 저장하지 않습니다. 반드시 수신기에 대한 강력한 +참조를 저장해야 합니다. 그렇지 않으면 가비지 수집의 대상이 될 가능성이 높습니다. 권장 사항으로는 +수신기를 객체의 인스턴스 데이터 안에 보관하는 것을 추천합니다. 이 객체는 +수신기를 필요로 하는 기간만큼 오래 존재할 것이 확실해야 합니다.

+ +

예를 들어 다음 코드에서 발신자는 수신기에 대한 참조를 +보관하지 않습니다. 그 결과 해당 수신기가 가비지 수집의 대상이 되며 +향후 언젠가 알 수 없는 시점에 고장을 일으키게 될 것입니다.

+ +
+prefs.registerOnSharedPreferenceChangeListener(
+  // Bad! The listener is subject to garbage collection!
+  new SharedPreferences.OnSharedPreferenceChangeListener() {
+  public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
+    // listener implementation
+  }
+});
+
+ +

대신, 수신기에 대한 참조를 수신기가 필요한 기간만큼 오래 존재할 것이 확실한 객체의 +인스턴스 데이터 필드에 저장하십시오.

+ +
+SharedPreferences.OnSharedPreferenceChangeListener listener =
+    new SharedPreferences.OnSharedPreferenceChangeListener() {
+  public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
+    // listener implementation
+  }
+};
+prefs.registerOnSharedPreferenceChangeListener(listener);
+
+ +

네트워크 사용량 관리하기

+ + +

Android 4.0부터 시스템의 설정 애플리케이션을 사용하면 사용자가 +애플리케이션이 전경과 배경에 있는 동안 각각 얼마나 많은 네트워크 데이터를 사용하는지 알아볼 수 있게 되었습니다. 그런 다음 +사용자는 각각의 앱에 대해 배경 데이터 사용을 비활성화할 수 있습니다. 사용자가 여러분의 앱이 배경에서 +데이터에 액세스하는 기능을 비활성화하는 사태를 피하려면 데이터 연결을 효율적으로 사용하고 +사용자가 애플리케이션 설정을 통하여 앱의 데이터 사용량을 미세 조정할 수 있도록 허용해야 합니다.

+ +

예를 들어 사용자에게 앱의 데이터 동기화 빈도를 제어하도록 허용할 수 있습니다. 앱이 Wi-Fi에 있을 때에만 +업로드/다운로드를 수행하도록 할지 여부, 앱이 로밍 중에 데이터를 사용하도록 할지 여부 등을 이렇게 조절합니다. 사용자가 +이러한 제어 기능을 사용할 수 있게 되면 시스템 설정에서 설정한 한도에 가까워지고 +있을 때 앱의 데이터 액세스를 비활성화할 가능성이 낮아집니다. 그 대신 앱이 사용하는 데이터 양을 +정밀하게 제어할 수 있기 때문입니다.

+ +

일단 필요한 기본 설정을 {@link android.preference.PreferenceActivity}에 +추가하여 앱의 데이터 습관을 제어하도록 했으면, 다음으로 매니페스트 파일에 있는 {@link +android.content.Intent#ACTION_MANAGE_NETWORK_USAGE}에 대한 인텐트 필터를 추가해야 합니다. 예:

+ +
+<activity android:name="SettingsActivity" ... >
+    <intent-filter>
+       <action android:name="android.intent.action.MANAGE_NETWORK_USAGE" />
+       <category android:name="android.intent.category.DEFAULT" />
+    </intent-filter>
+</activity>
+
+ +

이 인텐트 필터는 이것이 애플리케이션의 데이터 사용량을 제어하는 액티비티라는 +사실을 시스템에 나타내는 역할을 합니다. 따라서, 사용자가 시스템의 설정 앱에서 여러분의 앱이 +얼마나 많은 데이터를 사용하는지 알아볼 때면 애플리케이션 설정 보기 버튼을 사용할 수 있어 +{@link android.preference.PreferenceActivity}를 시작하게 됩니다. 그러면 사용자는 +앱이 사용할 데이터 양을 미세하게 조정할 수 있습니다.

+ + + + + + + +

사용자 지정 기본 설정 구축하기

+ +

Android 프레임워크에는 다양한 {@link android.preference.Preference} 하위 클래스가 포함되어 있어 +여러 가지 설정 유형에 맞게 UI를 구축할 수 있습니다. +하지만, 기본 제공 솔루션이 없는 설정이 필요하게 되는 경우도 있습니다. 예를 들어 숫자 선택기 또는 +날짜 선택기 등이 이에 해당됩니다. 그러한 경우에는 사용자 지정 기본 설정을 만들어야 합니다. 이때 +{@link android.preference.Preference} 클래스 또는 다른 하위 클래스 중 하나를 확장하는 방법을 씁니다.

+ +

{@link android.preference.Preference} 클래스를 확장하는 경우, 다음과 같이 +몇 가지 중요한 해야 할 일이 있습니다.

+ +
    +
  • 사용자가 설정을 선택하면 나타나는 사용자 인터페이스를 지정합니다.
  • +
  • 필요에 따라 설정의 값을 저장합니다.
  • +
  • {@link android.preference.Preference}가 보이게 되면 +이를 현재(또는 기본) 값으로 초기화합니다.
  • +
  • 시스템이 요청하는 경우 기본 값을 제공합니다.
  • +
  • {@link android.preference.Preference}가 나름의 UI(예: 대화)를 제공하는 경우, 상태를 +저장하고 복원하여 수명 주기 변경을 처리할 수 있도록 합니다(예: 사용자가 화면을 돌리는 경우).
  • +
+ +

다음 섹션에서는 이와 같은 각각의 작업을 수행하는 방법을 설명합니다.

+ + + +

사용자 인터페이스 지정하기

+ +

{@link android.preference.Preference} 클래스를 직접 확장하는 경우, +{@link android.preference.Preference#onClick()}을 구현하여 사용자가 +항목을 선택할 때 일어날 동작을 정의해야 합니다. 그러나, 대부분의 사용자 지정 설정은 {@link android.preference.DialogPreference}를 확장하여 +대화를 표시하도록 합니다. 이렇게 하면 절차가 단순해집니다. {@link +android.preference.DialogPreference}를 확장하는 경우에는 클래스 생성자 중에 반드시 {@link +android.preference.DialogPreference#setDialogLayoutResource setDialogLayoutResourcs()}를 호출하여 +대화에 대한 레이아웃을 지정해야 합니다.

+ +

예를 들어 다음은 레이아웃을 선언하는 사용자 지정 {@link +android.preference.DialogPreference}와 기본 +긍정적 및 부정적 대화 버튼에 대한 텍스트를 지정하는 생성자입니다.

+ +
+public class NumberPickerPreference extends DialogPreference {
+    public NumberPickerPreference(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        
+        setDialogLayoutResource(R.layout.numberpicker_dialog);
+        setPositiveButtonText(android.R.string.ok);
+        setNegativeButtonText(android.R.string.cancel);
+        
+        setDialogIcon(null);
+    }
+    ...
+}
+
+ + + +

설정의 값 저장하기

+ +

설정에 대한 값은 언제든 저장할 수 있습니다. {@link +android.preference.Preference} 클래스의 {@code persist*()} 메서드 중 하나를 호출하기만 하면 됩니다. 예를 들어 설정의 값이 정수인 경우 {@link +android.preference.Preference#persistInt persistInt()}를, 부울을 저장하려면 +{@link android.preference.Preference#persistBoolean persistBoolean()}을 호출하십시오.

+ +

참고: 각각의 {@link android.preference.Preference}는 데이터 유형 하나씩만 +저장할 수 있으므로, 사용자 지정 +{@link android.preference.Preference}에서 사용한 데이터 유형에 적절한 {@code persist*()} 메서드를 사용해야 합니다.

+ +

설정을 유지하기로 선택하는 시점은 확장하는 지점이 {@link +android.preference.Preference} 클래스인지에 좌우될 수 있습니다. {@link +android.preference.DialogPreference}를 확장하면 값을 유지하는 것은 대화가 긍정적인 결과로 인해 +닫히는 경우만으로 국한해야 합니다(사용자가 "확인(OK)" 버튼을 선택하는 경우).

+ +

{@link android.preference.DialogPreference}가 닫히면 시스템이 {@link +android.preference.DialogPreference#onDialogClosed onDialogClosed()} 메서드를 호출합니다. 이 메서드에는 +부울 인수가 포함되어 있어 사용자의 결과가 "긍정적"인지 아닌지를 나타냅니다. 이 값이 +true인 경우, 사용자가 긍정적 버튼을 선택한 것이고 새 값을 저장해야 합니다. 예: +

+ +
+@Override
+protected void onDialogClosed(boolean positiveResult) {
+    // When the user selects "OK", persist the new value
+    if (positiveResult) {
+        persistInt(mNewValue);
+    }
+}
+
+ +

이 예시에서 mNewValue는 설정의 현재 값을 보유한 클래스 +구성원입니다. {@link android.preference.Preference#persistInt persistInt()}를 호출하면 +{@link android.content.SharedPreferences} 파일에 대한 값을 저장합니다(이 +{@link android.preference.Preference}에 대하여 XML 파일에 지정된 키를 자동으로 사용합니다).

+ + +

현재 값 초기화하기

+ +

시스템이 {@link android.preference.Preference}를 화면에 추가하는 경우, 이는 +{@link android.preference.Preference#onSetInitialValue onSetInitialValue()}를 호출하여 +설정에 유지된 값이 있는지 없는지를 알립니다. 유지된 값이 없는 경우, 이 호출은 기본 값을 +제공합니다.

+ +

{@link android.preference.Preference#onSetInitialValue onSetInitialValue()} 메서드는 +부울 값 restorePersistedValue를 전달하여 해당 설정에 대해 이미 어떤 값이 유지되었는지 +아닌지를 나타냅니다. 만일 이것이 true라면, 유지된 값을 검색하되 +{@link +android.preference.Preference} 클래스의 {@code getPersisted*()} 메서드 중 하나를 호출하는 방법을 써야 합니다. 예를 들어 정수 값이라면 {@link +android.preference.Preference#getPersistedInt getPersistedInt()}를 사용합니다. 보통은 +유지된 값을 검색하여, UI에 이전에 저장된 값을 반영하여 이를 적절하게 업데이트할 수 +있도록 하는 것이 좋습니다.

+ +

restorePersistedValuefalse인 경우, +두 번째 인수로 전달된 기본 값을 사용해야 합니다.

+ +
+@Override
+protected void onSetInitialValue(boolean restorePersistedValue, Object defaultValue) {
+    if (restorePersistedValue) {
+        // Restore existing state
+        mCurrentValue = this.getPersistedInt(DEFAULT_VALUE);
+    } else {
+        // Set default state from the XML attribute
+        mCurrentValue = (Integer) defaultValue;
+        persistInt(mCurrentValue);
+    }
+}
+
+ +

각 {@code getPersisted*()} 메서드는 기본 값을 나타내는 인수를 취하여 +사실은 유지된 값이 전혀 없거나 키 자체가 존재하지 않는 경우 사용하도록 합니다. 위의 +예시에서는 혹시 {@link +android.preference.Preference#getPersistedInt getPersistedInt()}가 유지된 값을 반환할 수 없는 경우에 사용하도록 기본 값을 나타내는 데 로컬 상수를 사용하였습니다.

+ +

주의: {@code getPersisted*()} 메서드에서는 +defaultValue를 기본 값으로 사용하면 안 됩니다. 이것의 값은 +restorePersistedValuetrue이면 항상 null이기 때문입니다.

+ + +

기본 값 제공하기

+ +

{@link android.preference.Preference} 클래스의 인스턴스가 기본 값을 나타내는 경우 +({@code android:defaultValue} 속성으로), 시스템은 +값을 검색하기 위해 객체를 인스턴트화할 때 {@link android.preference.Preference#onGetDefaultValue +onGetDefaultValue()}를 호출합니다. 이 메서드를 구현해야 +시스템이 {@link +android.content.SharedPreferences}에 있는 기본 값을 저장할 수 있습니다. 예:

+ +
+@Override
+protected Object onGetDefaultValue(TypedArray a, int index) {
+    return a.getInteger(index, DEFAULT_VALUE);
+}
+
+ +

이 메서드 인수가 여러분에게 필요한 모든 것을 제공합니다. 즉 속성 배열과 +{@code android:defaultValue}의 위치로, 이는 반드시 검색해야 합니다. 이 메서드를 +반드시 구현하여 속성에서 기본 값을 추출해야만 하는 이유는 값이 정의되지 않은 경우, 속성에 대한 +로컬 기본 값을 꼭 지정해야 하기 때문입니다.

+ + + +

기본 설정의 상태 저장 및 복원하기

+ +

레이아웃에서의 {@link android.view.View}와 마찬가지로 {@link android.preference.Preference} +하위 클래스가 액티비티 또는 프래그먼트가 재시작했을 때 +그 상태를 저장하고 복원하는 역할을 맡습니다(예를 들어 사용자가 화면을 돌리는 경우 등). +{@link android.preference.Preference} 클래스의 상태를 적절하게 저장하고 복원하려면, +수명 주기 콜백 메서드 {@link android.preference.Preference#onSaveInstanceState +onSaveInstanceState()} 및 {@link +android.preference.Preference#onRestoreInstanceState onRestoreInstanceState()}를 구현해야 합니다.

+ +

{@link android.preference.Preference}의 상태를 정의하는 것은 +{@link android.os.Parcelable} 인터페이스를 구현하는 객체입니다. Android 프레임워크는 +그러한 객체를 제공하여 상태 객체를 정의하는 데 일종의 시작 지점으로 사용하도록 하고 있습니다. 즉 {@link +android.preference.Preference.BaseSavedState} 클래스가 이에 해당됩니다.

+ +

{@link android.preference.Preference} 클래스가 자신의 상태를 저장하는 방법을 정의하려면 +{@link android.preference.Preference.BaseSavedState} 클래스를 확장해야 합니다. 아주 약간의 메서드를 재정의하고 +{@link android.preference.Preference.BaseSavedState#CREATOR} +객체를 정의해야 합니다.

+ +

대부분의 앱에서는 다음과 같은 구현을 복사한 다음, +{@code value}를 처리하는 줄만 변경하면 됩니다. 이는 {@link android.preference.Preference} 하위 클래스가 정수보다는 데이터 +유형을 저장하는 경우 해당됩니다.

+ +
+private static class SavedState extends BaseSavedState {
+    // Member that holds the setting's value
+    // Change this data type to match the type saved by your Preference
+    int value;
+
+    public SavedState(Parcelable superState) {
+        super(superState);
+    }
+
+    public SavedState(Parcel source) {
+        super(source);
+        // Get the current preference's value
+        value = source.readInt();  // Change this to read the appropriate data type
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        super.writeToParcel(dest, flags);
+        // Write the preference's value
+        dest.writeInt(value);  // Change this to write the appropriate data type
+    }
+
+    // Standard creator object using an instance of this class
+    public static final Parcelable.Creator<SavedState> CREATOR =
+            new Parcelable.Creator<SavedState>() {
+
+        public SavedState createFromParcel(Parcel in) {
+            return new SavedState(in);
+        }
+
+        public SavedState[] newArray(int size) {
+            return new SavedState[size];
+        }
+    };
+}
+
+ +

위의 {@link android.preference.Preference.BaseSavedState} 구현을 앱에 +추가하고 나면(주로 {@link android.preference.Preference} 하위 클래스의 하위 클래스로), 이제 +{@link android.preference.Preference#onSaveInstanceState +onSaveInstanceState()} 및 {@link +android.preference.Preference#onRestoreInstanceState onRestoreInstanceState()} 메서드를 구현해야 합니다. 이것은 +{@link android.preference.Preference} 하위 클래스를 위한 것입니다.

+ +

예:

+ +
+@Override
+protected Parcelable onSaveInstanceState() {
+    final Parcelable superState = super.onSaveInstanceState();
+    // Check whether this Preference is persistent (continually saved)
+    if (isPersistent()) {
+        // No need to save instance state since it's persistent,
+        // use superclass state
+        return superState;
+    }
+
+    // Create instance of custom BaseSavedState
+    final SavedState myState = new SavedState(superState);
+    // Set the state's value with the class member that holds current
+    // setting value
+    myState.value = mNewValue;
+    return myState;
+}
+
+@Override
+protected void onRestoreInstanceState(Parcelable state) {
+    // Check whether we saved the state in onSaveInstanceState
+    if (state == null || !state.getClass().equals(SavedState.class)) {
+        // Didn't save the state, so call superclass
+        super.onRestoreInstanceState(state);
+        return;
+    }
+
+    // Cast state to custom BaseSavedState and pass to superclass
+    SavedState myState = (SavedState) state;
+    super.onRestoreInstanceState(myState.getSuperState());
+    
+    // Set this Preference's widget to reflect the restored state
+    mNumberPicker.setValue(myState.value);
+}
+
+ diff --git a/docs/html-intl/intl/ko/guide/topics/ui/ui-events.jd b/docs/html-intl/intl/ko/guide/topics/ui/ui-events.jd new file mode 100644 index 0000000000000000000000000000000000000000..b059bd24de48a07c770a94d0c06b579f3ddf4902 --- /dev/null +++ b/docs/html-intl/intl/ko/guide/topics/ui/ui-events.jd @@ -0,0 +1,291 @@ +page.title=입력 이벤트 +parent.title=사용자 인터페이스 +parent.link=index.html +@jd:body + +
+ +
+ +

Android에는 사용자와 애플리케이션의 상호 작용으로부터 이벤트를 가로채는 방법이 여러 가지 있습니다. +사용자 인터페이스 내의 이벤트가 관련된 경우, 이러한 방식은 이벤트를 사용자가 상호 작용하는 +특정 보기 객체로부터 캡처하는 것입니다. 이에 필요한 수단은 보기 클래스가 제공합니다.

+ +

레이아웃을 작성하는 데 사용하게 되는 여러 가지 보기 클래스 안을 보면 UI 이벤트에 유용해 보이는 공개 콜백 +메서드가 여러 개 있는 것이 눈에 띕니다. 이러한 메서드는 해당 객체에서 각각의 작업이 발생할 때 Android 프레임워크가 +호출하는 것입니다. 예를 들어 보기(예: 버튼)를 하나 터치하면 +해당 객체에서 onTouchEvent() 메서드가 호출됩니다. 그러나 이것을 가로채려면 클래스를 확장하고 +메서드를 재정의해야 합니다. 다만 그런 이벤트를 처리하기 위해 모든 보기 객체를 +다 확장하는 것은 타당성이 없습니다. 이 때문에 보기 클래스에 +일련의 중첩된 인터페이스가 있고 거기에 훨씬 쉽게 정의할 수 있는 콜백에 있습니다. 이와 같은 +인터페이스를 일명 이벤트 수신기라고 하는데, 이것이 UI와 사용자 상호 작용을 캡처하는 데 아주 적합합니다.

+ +

사용자 상호 작용을 수신 대기하는 데에는 이벤트 수신기를 사용하는 것이 좀 더 보편적이지만, 사용자 지정 +구성 요소를 구축하기 위해 보기 클래스를 확장하고자 하는 상황이 올 수도 있습니다. +어쩌면 {@link android.widget.Button} +클래스를 확장하여 무언가 더 복잡한 것을 만들고자 할 수도 있습니다. 이런 경우, 클래스에 대한 기본 이벤트 행동을 클래스 +이벤트 처리기를 사용하여 정의할 수 있습니다.

+ + +

이벤트 수신기

+ +

이벤트 수신기란 {@link android.view.View} 클래스 내에 있는 일종의 인터페이스로, 이 안에 하나의 +콜백 메서드가 들어있습니다. 이러한 메서드는 수신기가 등록된 보기가 UI 안의 항목과 사용자의 상호 작용으로 인하여 트리거되었을 때 +Android 프레임워크가 호출하는 것입니다.

+ +

이벤트 수신기 인터페이스에 포함된 콜백 메서드는 다음과 같습니다.

+ +
+
onClick()
+
{@link android.view.View.OnClickListener}에서 온 것입니다. +이것이 호출되는 것은 사용자가 항목을 터치하거나 +(터치 모드에 있을 때), 탐색 키 또는 트랙볼을 사용하여 해당 항목에 초점을 맞추고 있으면서 +적절한 "엔터" 키를 누르거나 트랙볼을 꾹 누를 때입니다.
+
onLongClick()
+
{@link android.view.View.OnLongClickListener}에서 온 것입니다. +이것이 호출되는 것은 사용자가 항목을 길게 누르거나(터치 모드에 있을 때), +탐색 키 또는 트랙볼을 사용하여 해당 항목에 초점을 맞추고 있으면서 +적절한 "엔터" 키를 누르거나 트랙볼을 꾹 누를 때입니다(일 초간).
+
onFocusChange()
+
{@link android.view.View.OnFocusChangeListener}에서 온 것입니다. +이것이 호출되는 것은 사용자가 탐색 키 또는 트랙볼을 사용하여 항목 쪽으로 이동하거나 항목에서 멀어질 때입니다.
+
onKey()
+
{@link android.view.View.OnKeyListener}에서 온 것입니다. +이것이 호출되는 것은 사용자가 항목에 초점을 맞추고 있으면서 기기에 있는 하드웨어 키를 누르거나 키에서 손을 떼는 경우입니다.
+
onTouch()
+
{@link android.view.View.OnTouchListener}에서 온 것입니다. +이것이 호출되는 것은 사용자가 터치 이벤트로서의 자격을 만족하는 작업을 수행하는 경우로, 여기에 +누르기, 손 떼기와 화면에서 이루어지는 모든 움직임 동작(항목의 경계 내에서)이 포함됩니다.
+
onCreateContextMenu()
+
{@link android.view.View.OnCreateContextMenuListener}에서 온 것입니다. +이것을 호출하는 것은 컨텍스트 메뉴가 구축되는 중일 때입니다(정체된 "롱 클릭"의 결과로). +메뉴 + 개발자 가이드에 있는 컨텍스트 메뉴 관련 논의를 참조하십시오.
+
+ +

이러한 메서드는 각자의 인터페이스 안에 거주하는 유일한 주민입니다. 이러한 메서드 중 하나를 +정의하고 이벤트를 처리하려면 액티비티 내의 중첩된 인터페이스를 구현하거나 익명의 클래스로 정의하면 됩니다. +그런 다음 구현의 인스턴스 하나를 +각각의 View.set...Listener() 메서드에 전달하십시오 (예: +{@link android.view.View#setOnClickListener(View.OnClickListener) setOnClickListener()}를 +호출한 다음 이를 {@link android.view.View.OnClickListener OnClickListener}의 구현에 전달합니다).

+ +

아래의 예시는 버튼에 대하여 온-클릭 수신기를 등록하는 방법을 나타낸 것입니다.

+ +
+// Create an anonymous implementation of OnClickListener
+private OnClickListener mCorkyListener = new OnClickListener() {
+    public void onClick(View v) {
+      // do something when the button is clicked
+    }
+};
+
+protected void onCreate(Bundle savedValues) {
+    ...
+    // Capture our button from layout
+    Button button = (Button)findViewById(R.id.corky);
+    // Register the onClick listener with the implementation above
+    button.setOnClickListener(mCorkyListener);
+    ...
+}
+
+ +

OnClickListener를 액티비티의 일부로 구현하는 것이 더 편리할 수도 있습니다. +이렇게 하면 클래스 부하와 객체 할당이 추가되는 것을 피할 수 있습니다. 예:

+
+public class ExampleActivity extends Activity implements OnClickListener {
+    protected void onCreate(Bundle savedValues) {
+        ...
+        Button button = (Button)findViewById(R.id.corky);
+        button.setOnClickListener(this);
+    }
+
+    // Implement the OnClickListener callback
+    public void onClick(View v) {
+      // do something when the button is clicked
+    }
+    ...
+}
+
+ +

위의 예시에서 onClick() 콜백에는 +반환 값이 없지만 다른 이벤트 수신기 메서드 중에는 부울 값을 반환해야만 하는 것도 있다는 점을 유의하십시오. 그 이유는 이벤트에 따라 +다릅니다. 이런 필수 사항이 적용되는 몇몇 메서드의 경우, 이유는 다음과 같습니다.

+
    +
  • {@link android.view.View.OnLongClickListener#onLongClick(View) onLongClick()} - +이것은 부울 값을 반환하여 이벤트를 완전히 사용하였으며 더 이상 이를 담지 않아도 되는지 여부를 나타냅니다. +다시 말해, 을 반환하면 이벤트를 처리했으며 여기에서 중단해야 한다는 것을 의미하며 +거짓을 반환하면 이벤트가 아직 미처리 상태이며/거나 이 이벤트를 다른 +온-클릭 수신기로 계속 진행해야 할지 나타내는 것입니다.
  • +
  • {@link android.view.View.OnKeyListener#onKey(View,int,KeyEvent) onKey()} - +이것은 부울 값을 반환하여 이벤트를 완전히 사용하였으며 더 이상 이를 담지 않아도 되는지 여부를 나타냅니다. + 다시 말해, 을 반환하면 이벤트를 처리했으며 여기에서 중단해야 한다는 것을 의미하며 +거짓을 반환하면 이벤트가 아직 미처리 상태이며/거나 이 이벤트를 다른 +온-키 수신기로 계속 진행해야 할지 나타내는 것입니다.
  • +
  • {@link android.view.View.OnTouchListener#onTouch(View,MotionEvent) onTouch()} - +이것은 부울 값을 반환하여 수신기가 이 이벤트를 사용하는지 아닌지를 나타냅니다. 여기서 중요한 점은 +이 이벤트에는 서로 연달아 발생하는 여러 개의 작업이 있을 수 있다는 것입니다. 그러므로 '아래로' 작업 이벤트를 수신했을 때 거짓을 반환하면, +해당 이벤트를 사용하지 않았으며 이 이벤트로부터 이어지는 이후의 작업에 +흥미가 없음을 나타내는 것이 됩니다. 따라서 이 이벤트 내의 다른 모든 작업에 대해 +호출되지 않습니다(예: 손가락 동작 또는 최종적인 '위로' 작업 이벤트 등).
  • +
+ +

하드웨어 키 이벤트는 항상 현재 초점의 중심에 있는 보기로 전달된다는 점을 명심하십시오. 이들은 보기 계층의 맨 위에서 시작하여 +아래 방향으로 발송되어 적절한 목적지에 도달할 때까지 계속합니다. 보기(또는 보기의 하위)에 +현재 초점이 맞춰져 있으면, 이벤트가 {@link android.view.View#dispatchKeyEvent(KeyEvent) +dispatchKeyEvent()} 메서드를 통과하여 이동하는 것을 확인할 수 있습니다. 보기를 통해 키 이벤트를 캡처하는 대신, 액티비티 내부의 모든 이벤트를 {@link android.app.Activity#onKeyDown(int,KeyEvent) onKeyDown()}및 + +{@link android.app.Activity#onKeyUp(int,KeyEvent) onKeyUp()}을 사용하여 수신할 수도 있습니다.

+ +

또한, 애플리케이션에 대한 텍스트 입력의 경우 대다수의 기기에는 소프트웨어 입력 메서드만 있다는 사실을 +명심하십시오. 그러한 메서드는 반드시 키 기반이 아니어도 됩니다. 음성 입력, 손글씨 등을 사용하는 것도 있습니다. 입력 +메서드가 키보드 같은 인터페이스를 표시하더라도 일반적으로 +{@link android.app.Activity#onKeyDown(int,KeyEvent) onKeyDown()} 이벤트군을 트리거하지는 않습니다. 특정 키 누름을 +제어해야만 하는 UI는 절대 구축하면 안 됩니다. 이렇게 하면 애플리케이션이 하드웨어 키보드가 있는 +기기에만 한정됩니다. 특히, 사용자가 리턴 키를 누르면 입력의 유효성을 검사하는 데 이와 같은 메서드에 +의존해서는 안 됩니다. 대신, {@link android.view.inputmethod.EditorInfo#IME_ACTION_DONE}과 같은 작업을 사용하여 애플리케이션이 반응할 것으로 기대되는 방식에 해당되는 +입력 메서드를 신호하여 의미 있는 방식으로 UI를 변경할 수 있게 하는 것이 좋습니다. 소프트웨어 입력 메서드가 +어떻게 작동할지 임의로 추정하지 마시고, 이미 서식 지정된 텍스트를 애플리케이션에 제공해줄 것이라 믿으면 됩니다.

+ +

참고: Androids는 우선 이벤트 처리기부터 호출하고, 그 다음에 클래스 정의로부터 가져온 +적절한 기본 처리기를 두 번째로 호출합니다. 따라서, 이와 같은 이벤트 수신기에서 을 반환하면 이벤트가 +다른 이벤트 수신기로 전파되는 것을 중지시킬 뿐만 아니라 보기에 있는 +기본 이벤트 처리기로의 콜백도 차단하게 됩니다. 따라서 을 반환하는 경우 해당 이벤트를 종료하고 싶은 것인지 확신해야 합니다.

+ + +

이벤트 처리기

+ +

보기에서 사용자 지정 구성 요소를 구축하는 경우, 기본 이벤트 처리기로 사용될 콜백 메서드를 +여러 개 정의할 수 있게 됩니다. +사용자 지정 +구성 요소에 대한 문서를 보면 이벤트 처리에 사용되는 몇 가지 보편적인 콜백을 확인할 수 있습니다. +다음은 그 몇 가지 예입니다.

+
    +
  • {@link android.view.View#onKeyDown} - 새로운 키 이벤트가 발생하면 호출합니다.
  • +
  • {@link android.view.View#onKeyUp} - 새로운 키 '위로' 이벤트가 발생하면 호출합니다.
  • +
  • {@link android.view.View#onTrackballEvent} - 트랙볼 동작 이벤트가 발생하면 호출합니다.
  • +
  • {@link android.view.View#onTouchEvent} - 터치 스크린 동작 이벤트가 발생하면 호출합니다.
  • +
  • {@link android.view.View#onFocusChanged} - 보기가 초점을 취하거나 이를 잃으면 호출합니다.
  • +
+

개발자 여러분이 알아두어야 하는 다른 메서드가 몇 가지 더 있습니다. 이들은 보기 클래스의 일부분이 아니지만, +이벤트를 처리할 수 있는 방식에 직접적으로 영향을 미칠 수 있는 것들입니다. 그러니, 레이아웃 안에서 좀 더 복잡한 이벤트를 관리하는 경우, +이와 같은 다른 메서드도 고려하십시오.

+
    +
  • {@link android.app.Activity#dispatchTouchEvent(MotionEvent) + Activity.dispatchTouchEvent(MotionEvent)} - 이것을 사용하면 {@link + android.app.Activity}로 하여금 모든 터치 이벤트가 창으로 발송되기 전에 이들을 가로채도록 할 수 있습니다.
  • +
  • {@link android.view.ViewGroup#onInterceptTouchEvent(MotionEvent) + ViewGroup.onInterceptTouchEvent(MotionEvent)} - 이것을 사용하면 {@link + android.view.ViewGroup}으로 하여금 이벤트가 하위 보기로 발송되는 것을 지켜보도록 할 수 있습니다.
  • +
  • {@link android.view.ViewParent#requestDisallowInterceptTouchEvent(boolean) + ViewParent.requestDisallowInterceptTouchEvent(boolean)} - 이것을 +호출하는 것은 상위 보기에 {@link + android.view.ViewGroup#onInterceptTouchEvent(MotionEvent)}가 있는 터치 이벤트를 가로채면 안 된다고 나타낼 때입니다.
  • +
+ +

터치 모드

+

+사용자가 방향 키 또는 트랙볼을 사용하여 사용자 인터페이스를 탐색하고 있는 경우, +조치 가능한 항목(예: 버튼)에 초점을 맞춰 어느 것이 입력을 허용할지 사용자가 +볼 수 있도록 해야 합니다. 하지만 기기에 터치 기능이 있고 사용자가 +인터페이스를 터치하여 인터페이스와의 상호 작용을 시작하는 경우라면, 더 이상 항목을 강조 표시하거나 +특정 보기에 초점을 맞추지 않아도 됩니다. 따라서 "터치 모드"라고 불리는 상호 작용에 대한 +모드가 따로 있습니다. +

+

+터치 기능이 있는 기기의 경우, 사용자가 화면을 터치하면 기기가 터치 모드에 +진입하게 됩니다. 이 시점부터는 +{@link android.view.View#isFocusableInTouchMode}가 참인 보기에만 초점을 맞출 수 있습니다. 예를 들어 텍스트 편집 위젯이 이에 해당됩니다. +버튼처럼 터치할 수 있는 다른 보기의 경우 터치해도 주의를 끌 수 없으며 이를 누르면 그저 +온-클릭 수신기를 실행시키기만 합니다. +

+

+사용자가 방향 키를 누르거나 트랙볼로 스크롤 동작을 할 때마다 기기가 +터치 모드를 종료하고 초점을 맞출 보기를 찾습니다. 이제 사용자는 사용자 인터페이스와 +상호 작용을 재개해도 되며, 화면을 터치하지 않아도 됩니다. +

+

+터치 모드 상태는 시스템 전체를 통틀어 유지됩니다(모든 창과 액티비티 포함). +현재 상태를 쿼리하려면 +{@link android.view.View#isInTouchMode}를 호출하여 기기가 현재 터치 모드에 있는지 확인하면 됩니다. +

+ + +

초점 처리하기

+ +

프레임워크가 사용자 입력에 응답하여 일상적인 초점 이동을 처리합니다. +여기에는 보기가 제거되거나 숨겨지는 것, 또는 새 보기를 사용할 수 있게 됨에 따라 +초점을 변경하는 것이 포함됩니다. 보기는 초점을 취하고자 하는 의향을 +{@link android.view.View#isFocusable()} 메서드를 통해 나타냅니다. 보기가 초점을 취할 수 있는지 여부를 변경하려면 +{@link android.view.View#setFocusable(boolean) setFocusable()}을 호출합니다. 터치 모드에 있는 경우, +어느 보기가 {@link android.view.View#isFocusableInTouchMode()}로 초점을 취하는 것을 허용하는지 여부를 쿼리할 수 있습니다. +이것은 {@link android.view.View#setFocusableInTouchMode(boolean) setFocusableInTouchMode()}로 변경하면 됩니다. +

+ +

초점 이동은 주어진 방향에서 가장 가까운 이웃을 찾아내는 알고리즘을 기반으로 +합니다. 드문 일이지만 기본 알고리즘이 개발자가 의도한 행동과 일치하지 않는 +경우도 있습니다. 이러한 상황이라면, 레이아웃 파일에서 다음과 같은 XML 속성을 +사용하여 명시적 재정의를 제공하면 됩니다. +nextFocusDown, nextFocusLeft, nextFocusRight, 및 +nextFocusUp입니다. 이와 같은 속성 중 한 가지를 초점이 떠나고 있는 보기에 +추가합니다. 속성의 값을 초점을 +맞춰야 할 보기의 ID가 되도록 정의합니다. 예:

+
+<LinearLayout
+    android:orientation="vertical"
+    ... >
+  <Button android:id="@+id/top"
+          android:nextFocusUp="@+id/bottom"
+          ... />
+  <Button android:id="@+id/bottom"
+          android:nextFocusDown="@+id/top"
+          ... />
+</LinearLayout>
+
+ +

보통은 이런 수직 레이아웃에서 첫 버튼부터 위로 이동하면 아무 데도 갈 수 없고, +두 번째 버튼에서 아래로 이동해도 마찬가지입니다. 이제 맨 위 버튼이 맨 아래 버튼을 다음과 같이 +정의했습니다. nextFocusUp (반대쪽도 마찬가지) 따라서 이동 초점은 위에서 아래로 갔다가 +아래에서 위로 순환하게 됩니다.

+ +

보기를 UI에서 초점을 맞출 수 있는 것으로 선언하고자 하는 경우(일반적으로는 그렇지 않음), +보기에 레이아웃 선언에서 android:focusable XML 속성을 추가합니다. +이 값을 으로 설정합니다. 터치 모드에 있을 때에도 보기를 초점을 맞출 수 있는 것으로 +선언할 수 있습니다. android:focusableInTouchMode를 사용하면 됩니다.

+

특정 보기에 초점을 맞추기를 요청하려면, {@link android.view.View#requestFocus()}를 호출하십시오.

+

초점 이벤트를 수신 대기하려면(어떤 보기에 초점이 맞춰지거나 이를 잃는 경우 알림을 받으려면), +{@link android.view.View.OnFocusChangeListener#onFocusChange(View,boolean) onFocusChange()}를 사용하면 됩니다. +이는 위의 이벤트 수신기 섹션에서 이야기한 바와 같습니다.

+ + + + diff --git a/docs/html-intl/intl/ko/sdk/index.jd b/docs/html-intl/intl/ko/sdk/index.jd new file mode 100644 index 0000000000000000000000000000000000000000..4586e9f15b11c7a7b5e8b91cf21ddeab8ce08c3a --- /dev/null +++ b/docs/html-intl/intl/ko/sdk/index.jd @@ -0,0 +1,487 @@ +page.title=Android Studio 및 SDK 도구 다운로드 +page.tags=sdk, android studio +page.template=sdk +header.hide=1 +page.metaDescription=Android 휴대폰, 태블릿, 웨어러블, TV 등을 위한 앱을 구축하는 데 사용할 수 있는 공식 Android IDE 및 개발자 도구를 다운로드하세요. + +studio.version=1.1.0 + +studio.linux_bundle_download=android-studio-ide-135.1740770-linux.zip +studio.linux_bundle_bytes=259336386 +studio.linux_bundle_checksum=e8d166559c50a484f83ebfec6731cc0e3f259208 + +studio.mac_bundle_download=android-studio-ide-135.1740770-mac.dmg +studio.mac_bundle_bytes=261303345 +studio.mac_bundle_checksum=f9745d0fec1eefd498f6160a2d6a1b5247d4cda3 + +studio.win_bundle_exe_download=android-studio-bundle-135.1740770-windows.exe +studio.win_bundle_exe_bytes=856233768 +studio.win_bundle_exe_checksum=7484b9989d2914e1de30995fbaa97a271a514b3f + +studio.win_notools_exe_download=android-studio-ide-135.1740770-windows.exe +studio.win_notools_exe_bytes=242135128 +studio.win_notools_exe_checksum=5ea77661cd2300cea09e8e34f4a2219a0813911f + +studio.win_bundle_download=android-studio-ide-135.1740770-windows.zip +studio.win_bundle_bytes=261667054 +studio.win_bundle_checksum=e903f17cc6a57c7e3d460c4555386e3e65c6b4eb + + + +sdk.linux_download=android-sdk_r24.1.2-linux.tgz +sdk.linux_bytes=168121693 +sdk.linux_checksum=68980e4a26cca0182abb1032abffbb72a1240c51 + +sdk.mac_download=android-sdk_r24.1.2-macosx.zip +sdk.mac_bytes=89151287 +sdk.mac_checksum=00e43ff1557e8cba7da53e4f64f3a34498048256 + +sdk.win_download=android-sdk_r24.1.2-windows.zip +sdk.win_bytes=159778618 +sdk.win_checksum=704f6c874373b98e061fe2e7eb34f9fcb907a341 + +sdk.win_installer=installer_r24.1.2-windows.exe +sdk.win_installer_bytes=111364285 +sdk.win_installer_checksum=e0ec864efa0e7449db2d7ed069c03b1f4d36f0cd + + + + + + +@jd:body + + + + + + + +
+ + + + + + + + + +
+ +
 
+ + + +
+ +

Android Studio

+ +

공식 Android IDE

+ +
    +
  • Android Studio IDE
  • +
  • Android SDK Tools
  • +
  • Android 5.0(Lollipop) 플랫폼
  • +
  • Google API를 사용하는 Android 5.0 에뮬레이터 시스템 이미지
  • +
+ + +Android Studio 다운로드 + + +

+Android Studio 또는 독립 실행형 SDK 도구를 얻으려면 developer.android.com/sdk/ 페이지를 방문하십시오. +

+ + + +
+ + + + +

지능형 코드 편집기

+ +
+ +
+ +
+

Android Studio의 핵심에는 고급 코드 작성, 리팩터링, 코드 분석이 가능한 지능형 +코드 편집기가 있습니다.

+

이 강력한 코드 편집기를 사용하면 여러분이 더욱 생산적인 Android 앱 개발자가 되는 데 도움이 됩니다.

+
+ + + + + +

코드 템플릿 및 GitHub 통합

+ +
+ +
+ +
+

새 프로젝트 마법사를 통해 새로운 프로젝트를 더욱 쉽게 시작할 수 있습니다.

+ +

탐색 드로어 및 보기 호출기와 같은 패턴에 대한 템플릿 코드를 사용해 프로젝트를 시작하고, +GitHub에서 Google 코드 샘플을 가져올 수도 있습니다.

+
+ + + + +

다중 화면 앱 개발

+ +
+ +
+ +
+

Android 휴대폰, 태블릿, Android Wear, Android TV, +Android Auto 그리고 Google Glass용 앱을 구축할 수 있습니다.

+

Android Studio의 새로운 Android 프로젝트 보기 및 모듈 지원 기능을 통해 +앱 프로젝트 및 리소스 관리를 더욱 쉽게 수행할 수 있습니다. +

+ + + + +

모든 모양 및 크기를 지원하는 가상 기기

+ +
+ +
+ +
+

Android Studio는 최적화된 에뮬레이터 이미지로 사전 구성된 형태로 제공됩니다.

+

업데이트되고 간소화된 Virtual Device Manager는 일반 Android 기기에 대해 +사전 정의된 기기 프로필을 제공합니다.

+
+ + + + +

+Gradle을 통해 진화된 Android 빌드

+ +
+ +
+ +
+

동일한 프로젝트를 사용하여 다양한 기능을 갖춘 Android 앱을 위한 여러 APK를 생성할 수 있습니다.

+

Maven을 통해 앱 종속관계를 관리할 수 있습니다.

+

Android Studio 또는 명령줄에서 APK를 구축할 수 있습니다.

+
+ + + + +

Android Studio에 대한 추가 정보

+
+ +Android Studio 다운로드 + +
    +
  • JetBrains에서 제공하는 인기 있는 Java IDE인 IntelliJ IDEA Community Edition 기반
  • +
  • 유연한 Gradle 기반 빌드 시스템
  • +
  • 빌드 변형 및 여러 APK 생성
  • +
  • 템플릿 지원 확대를 통해 Google Services 및 다양한 기기 유형 지원
  • +
  • 테마 편집을 지원하는 고급 레이아웃 편집기
  • +
  • 성능, 유용성, 버전 호환성 및 기타 문제를 파악하는 Lint 도구
  • +
  • ProGuard 및 앱 서명 기능
  • +
  • Google Cloud Messaging 및 App Engine의 통합을 용이하게 하는 Google Cloud Platform에 대한 +기본 지원 기능
  • +
+ +

+Android Studio에서 사용할 수 있는 기능에 대한 자세한 내용은 +Android Studio 개요 가이드를 참조하세요.

+
+ + +

ADT와 함께 Eclipse를 사용하고 있었다면 현재 Android의 공식 IDE는 +Android Studio이므로, 모든 최신 IDE 업데이트를 받을 수 있도록 Android Studio로 +마이그레이션해야 합니다. 프로젝트 이동에 대한 도움말은 +Migrating to Android +Studio를 참조하세요.

+ + + + + + + +

시스템 요건

+ +

Windows

+ +
    +
  • Microsoft® Windows® 8/7/Vista/2003(32비트 또는 64비트)
  • +
  • 최소 2GB RAM, 4GB RAM 권장
  • +
  • 400MB 하드 디스크 공간
  • +
  • Android SDK, 에뮬레이터 시스템 이미지 및 캐시용 최소 1GB
  • +
  • 1280 x 800 이상의 화면 해상도
  • +
  • JDK(Java Development Kit) 7
  • +
  • 가속 에뮬레이터를 위한 선택 사항: Intel® VT-x, Intel® EM64T(Intel® 64) 및 XD(Execute Disable) Bit +기능 지원 Intel® 프로세서
  • +
+ + +

Mac OS X

+ +
    +
  • Mac® OS X® 10.8.5 이상, 최대 10.9(Mavericks)
  • +
  • 최소 2GB RAM, 4GB RAM 권장
  • +
  • 400MB 하드 디스크 공간
  • +
  • Android SDK, 에뮬레이터 시스템 이미지 및 캐시용 최소 1GB
  • +
  • 1280 x 800 이상의 화면 해상도
  • +
  • JRE(Java Runtime Environment) 6
  • +
  • JDK(Java Development Kit) 7
  • +
  • 가속 에뮬레이터를 위한 선택 사항: Intel® VT-x, Intel® EM64T(Intel® 64) 및 XD(Execute Disable) Bit +기능 지원 Intel® 프로세서
  • +
+ +

Mac OS에서는 최적화된 글꼴 렌더링을 위해 JRE(Java Runtime Environment) 6에서 Android Studio를 +실행하십시오. 그런 다음, JDK(Java Development Kit) 6 또는 JDK 7을 사용하여 프로젝트를 구성할 수 있습니다.

+ + + +

Linux

+ +
    +
  • GNOME 또는 KDE 데스크톱
  • +
  • GNU C Library(glibc) 2.15 이상
  • +
  • 최소 2GB RAM, 4GB RAM 권장
  • +
  • 400MB 하드 디스크 공간
  • +
  • Android SDK, 에뮬레이터 시스템 이미지 및 캐시용 최소 1GB
  • +
  • 1280 x 800 이상의 화면 해상도
  • +
  • Oracle® JDK(Java Development Kit) 7
  • +
+

Ubuntu® 14.04, Precise Pangolin(32비트 애플리케이션 실행 가능 64비트 버전)에서 +테스트됨

+ + + + +

기타 다운로드 옵션

+ + diff --git a/docs/html-intl/intl/ko/sdk/installing/adding-packages.jd b/docs/html-intl/intl/ko/sdk/installing/adding-packages.jd new file mode 100644 index 0000000000000000000000000000000000000000..b70d39edbd7439c360c78fb1b2d5db64acd73be0 --- /dev/null +++ b/docs/html-intl/intl/ko/sdk/installing/adding-packages.jd @@ -0,0 +1,227 @@ +page.title=SDK 패키지 추가하기 + +page.tags=sdk manager +helpoutsWidget=true + +@jd:body + + + + +

+기본적으로 Android SDK에는 개발을 시작하는 데 필요한 모든 것이 포함되어 있지 않습니다. +SDK는 도구, 플랫폼과 기타 구성요소를 여러 개의 +패키지로 구분하여 필요에 따라 +Android SDK Manager를 사용하여 다운로드할 수 있습니다. +따라서 시작하기 전에 Android SDK에 추가해야 하는 패키지가 몇 개 있습니다.

+ +

패키지를 추가하려면, 다음 중 한 가지 방법을 사용하여 Android SDK Manager를 시작합니다.

+
    +
  • Android Studio의 도구 모음에서 SDK Manager +를 클릭합니다.
  • +
  • Android Studio를 사용하지 않는 경우: +
      +
    • Windows: Android + SDK 디렉터리의 루트에 있는 SDK Manager.exe 파일을 더블 클릭합니다.
    • +
    • Mac/Linux: 터미널을 열고 Android SDK가 설치된 tools/ 디렉터리로 +이동한 후 android sdk를 실행합니다.
    • +
    +
  • +
+ +

SDK Manager를 처음 열 때에는, 여러 개의 패키지가 기본적으로 선택되어 +있습니다. 이들은 선택된 상태로 두십시오. 다만 다음 단계를 따라 시작하는 데 필요한 모든 것을 +갖추고 있는지 확인해야 합니다.

+ + +
    +
  1. +

    최신 SDK 도구 다운로드

    + + + +

    Android SDK를 설정할 때는 +최신 도구와 Android 플랫폼 다운로드가 최소한의 준비 사항입니다.

    +
      +
    1. 도구 디렉터리를 열고 다음을 선택합니다. +
        +
      • Android SDK Tools
      • +
      • Android SDK Platform-tools
      • +
      • Android SDK Build-tools(최상위 버전)
      • +
      +
    2. +
    3. 첫 번째 Android X.X 폴더(최신 버전)를 열고 다음을 선택합니다. +
        +
      • SDK Platform
      • +
      • 에뮬레이터용 시스템 이미지(다음 예시 참조)
        + ARM EABI v7a 시스템 이미지
      • +
      +
    4. +
    +
  2. + +
  3. +

    추가 API를 위한 지원 라이브러리 가져오기

    + + + +

    Android 지원 라이브러리는 + 대부분의 Android 버전과 호환되는 광범위한 집합의 API를 제공합니다.

    + +

    Extras 디렉터리를 열고 다음을 선택합니다.

    +
      +
    • Android 지원 리포지토리
    • +
    • Android 지원 라이브러리
    • +
    + +

     

    +

     

    + +
  4. + + +
  5. +

    더 많은 API를 위해 Google Play 서비스 가져오기

    + + + +

    Google API로 개발하려면 Google Play 서비스 패키지가 필요합니다.

    +

    Extras 디렉터리를 열고 다음을 선택합니다.

    +
      +
    • Google Repository
    • +
    • Google Play 서비스
    • +
    + +

    참고: Google Play 서비스를 모든 Android 구동 기기에서 +사용할 수 있는 것은 아니지만, Google Play Store가 있는 기기에서는 모두 사용할 수 있습니다. 이와 같은 API를 +Android 에뮬레이터에서 사용하려면, SDK Manager의 최신 Android X.X 디렉터리에서 Google API + 시스템 이미지도 설치해야 합니다.

    +
  6. + + +
  7. +

    패키지 설치

    +

    원하는 패키지를 모두 선택하면 계속 진행하여 설치합니다.

    +
      +
    1. X 패키지 설치를 클릭합니다.
    2. +
    3. 다음 창에서, 왼쪽에 있는 각 패키지 이름을 더블 클릭하여 각각의 +라이선스 동의서를 수락합니다.
    4. +
    5. 설치를 클릭합니다.
    6. +
    +

    다운로드 진행률이 SDK Manager 창 맨 아래에 표시됩니다. + SDK Manager를 종료하지 마십시오. 다운로드가 취소됩니다.

    +
  8. + +
  9. +

    뭐든 구축하세요!

    + +

    이제 Android SDK에 위의 패키지를 설치했으니 Android용 앱을 구축할 준비가 끝난 +셈입니다. 새로운 도구와 다른 API를 사용할 수 있게 되면, SDK Manager를 시작하여 +사용자의 SDK에 새 패키지를 다운로드할 수 있습니다.

    + +

    진행 방법에 대한 몇 가지 옵션을 소개합니다.

    + +
    +
    +

    시작하기

    +

    Android 개발이 처음인 경우, Android 앱의 기초를 배울 수 있는 +첫 앱 구축하기 가이드를 참조하십시오.

    + +
    +
    +

    웨어러블용 앱 구축하기

    +

    Android 웨어러블용 앱을 구축할 준비가 되었다면 +Android Wear용 앱 구축하기 가이드를 참조하십시오.

    + +
    +
    +

    Google API 사용

    +

    Maps 또는 Play Game 서비스와 같은 +Google API를 사용하려면 +Google Play +서비스 설정하기 가이드를 참조하십시오.

    + +
    +
    + + +
  10. + +
+ + diff --git a/docs/html-intl/intl/pt-br/guide/components/activities.jd b/docs/html-intl/intl/pt-br/guide/components/activities.jd new file mode 100644 index 0000000000000000000000000000000000000000..71986abe649bf508fa380c200e439875405b2dbb --- /dev/null +++ b/docs/html-intl/intl/pt-br/guide/components/activities.jd @@ -0,0 +1,756 @@ +page.title=Atividades +page.tags=atividade,intenção +@jd:body + + + + + +

{@link android.app.Activity} é um componente de aplicativo que fornece uma tela com a qual +os usuários podem interagir para fazer algo, como discar um número no telefone, tirar uma foto, enviar um e-mail +ou ver um mapa. Cada atividade recebe uma janela que exibe a interface do usuário. Geralmente, a janela +preenche a tela, mas pode ser menor que a tela e flutuar +sobre outras janelas.

+ +

Aplicativos geralmente possuem várias atividades pouco vinculadas +entre si. Normalmente, uma atividade em um aplicativo é especificada como "principal", +que é a apresentada ao usuário ao iniciar o aplicativo pela primeira vez. Cada +atividade pode, então, iniciar outra atividade para executar diferentes ações. Ao iniciar uma nova +atividade, a atividade anterior é interrompida, mas o sistema conserva a atividade +em uma pilha (a "pilha de retorno"). Quando uma atividade inicia, ela é enviada para a pilha de retorno +e obtém o foco do usuário. A pilha de retorno segue o mecanismo básico de pilha UEPS (o último que entra é o primeiro que sai). +Assim, quando o usuário terminar a atividade atual e apertar o botão Voltar, +ela sairá da pilha (é destruída) e a atividade anterior será retomada (a pilha de retorno +é discutida em mais detalhes no documento Tarefas +e Pilha de Retorno).

+ +

Quando uma atividade é interrompida devido ao início de uma nova atividade, ela é notificada acerca dessa alteração de estado +por meio de métodos de retorno de chamada do ciclo de vida da atividade. +Há diversos métodos de retorno de chamada que uma atividade pode receber devido a uma alteração +em seu estado — quando o sistema a está criando, interrompendo, retomando ou destruindo — e cada +retorno de chamada oferece uma oportunidade de executar trabalhos específicos +adequados a essa alteração de estado. Por exemplo: quando interrompida, a atividade deve liberar +todos os objetos grandes, como conexões com a rede ou com um banco de dados. Quando a atividade for retomada, será possível +readquirir os recursos necessários e retomar as ações interrompidas. Essas transições de estado +são parte do ciclo de vida da atividade.

+ +

O restante deste documento discute o básico sobre a compilação e o uso de uma atividade, +incluindo uma discussão completa sobre o funcionamento do ciclo de vida da atividade para gerenciar adequadamente +a transição entre os diversos estados da atividade.

+ + + +

Criação de uma atividade

+ +

Para criar uma atividade, é preciso criar uma subclasse de {@link android.app.Activity} +(ou uma respectiva subclasse existente). Na subclasse, é preciso implementar um método de retorno de chamada +que o sistema chama quando ocorre a transição entre os diversos estados de seu ciclo de vida, +como na criação, interrupção, retomada ou destruição da atividade. Os dois métodos mais importantes +de retorno de chamada são:

+ +
+
{@link android.app.Activity#onCreate onCreate()}
+
É preciso implementar esse método. O sistema o chama ao criar +a atividade. Na implementação, é preciso inicializar os componentes essenciais +da atividade. + E, fundamentalmente, é quando se deve chamar {@link android.app.Activity#setContentView +setContentView()} para definir o layout da interface do usuário da atividade.
+
{@link android.app.Activity#onPause onPause()}
+
O sistema chama esse método como o primeiro indício de que o usuário está saindo +da atividade (embora não seja sempre uma indicação de que a atividade será destruída). É quando geralmente +se deve confirmar qualquer alteração que deva persistir além da sessão do usuário atual (porque +o usuário pode não retornar).
+
+ +

Há outros métodos de retorno de chamada do ciclo de vida que se pode usar para oferecer +uma experiência fluida ao usuário entre atividades e manipular interrupções inesperadas que venham a parar +ou até a destruir a atividade. Todos os métodos de retorno de chamada do ciclo de vida serão discutidos mais adiante +na seção sobre Gerenciamento do ciclo de vida da atividade.

+ + + +

Implementação de uma interface do usuário

+ +

A interface do usuário de uma atividade é fornecida por uma hierarquia de objetos — de vistas derivados +da classe {@link android.view.View}. Cada vista controla um espaço retangular específico +dentro da janela da atividade e pode responder à interação com o usuário. Por exemplo: uma vista pode ser +um botão que inicia uma ação quando o usuário toca nele.

+ +

O Android oferece algumas vistas prontas que podem ser usadas para projetar e organizar +o layout. "Widgets" são vistas que fornecem elementos visuais (e interativos) à tela, +como um botão, um campo de texto, uma caixa de seleção ou apenas uma imagem. "Layouts" são vistas derivadas de {@link +android.view.ViewGroup} que oferecem um modelo de layout exclusivo para suas vistas filhas, +como um layout linear, um layout em grade ou um layout relativo. Também é possível definir como subclasse as classes +{@link android.view.View} e {@link android.view.ViewGroup} (ou subclasses existentes) para criar widgets e layouts próprios +e aplicá-los no layout da atividade.

+ +

A forma mais comum de definir um layout usando vistas é com um arquivo de layout XML salvo +nos recursos do aplicativo. Assim, é possível manter o projeto da interface do usuário separado +do código fonte que define o comportamento da atividade. É possível definir o layout como a IU da atividade +com {@link android.app.Activity#setContentView(int) setContentView()}, passando +o ID de recurso do layout. No entanto, também é possível criar novas {@link android.view.View}s +no código da atividade e compilar uma hierarquia de vistas inserindo novas {@link +android.view.View}s em um {@link android.view.ViewGroup} e, em seguida, usar esse layout passando a raiz +{@link android.view.ViewGroup} para {@link android.app.Activity#setContentView(View) +setContentView()}.

+ +

Para obter mais informações sobre a criação de uma interface do usuário, consulte a documentação Interface do Usuário.

+ + + +

Declaração de uma atividade no manifesto

+ +

É preciso declarar a atividade no arquivo de manifesto para torná-la +acessível para o sistema. Para declarar a atividade, abra o arquivo de manifesto e adicione um elemento {@code <activity>} +como filho do elemento +{@code <application>}. Por exemplo:

+ +
+<manifest ... >
+  <application ... >
+      <activity android:name=".ExampleActivity" />
+      ...
+  </application ... >
+  ...
+</manifest >
+
+ +

Existem outros atributos que podem ser incluídos nesse elemento para definir propriedades +como o rótulo da atividade, um ícone para a atividade ou um tema para estilizar a IU da atividade. + O atributo {@code android:name} + é o único atributo obrigatório — ele especifica o nome de classe da atividade. Depois +de publicar o aplicativo, não se deve alterar-lhe o nome porque, se isso acontecer, podem ocorrer danos +em algumas funcionalidades, como os atalhos do aplicativo (leia a publicação do blogue Coisas +que não podem mudar).

+ +

Consulte a referência do elemento {@code <activity>} +para obter mais informações sobre como declarar a atividade no manifesto.

+ + +

Uso de filtros de intenções

+ +

Um elemento {@code +<activity>} também pode especificar vários filtros de intenções — usando o elemento {@code +<intent-filter>} — para declarar o modo com que os componentes de aplicativo +podem ativá-lo.

+ +

Ao criar um novo aplicativo com as ferramentas do Android SDK, o esboço da atividade +criado contém automaticamente um filtro de intenção que declara que a atividade responde +à ação "main" (principal) e deve ser colocada na categoria "launcher” (inicializador). O filtro de intenção +tem a seguinte aparência:

+ +
+<activity android:name=".ExampleActivity" android:icon="@drawable/app_icon">
+    <intent-filter>
+        <action android:name="android.intent.action.MAIN" />
+        <category android:name="android.intent.category.LAUNCHER" />
+    </intent-filter>
+</activity>
+
+ +

O elemento {@code +<action>} especifica que este é o “principal” ponto de entrada do aplicativo. O elemento {@code +<category>} especifica que essa atividade deve ser listada no inicializador do aplicativo +do sistema (para permitir que os usuários iniciem essa atividade).

+ +

Se pretende-se que o aplicativo seja autônomo e que não permita que outros aplicativos ativem +suas atividades, não será necessário nenhum outro filtro de intenção. Só uma atividade deve ter +a ação "main" e a categoria "launcher", como no exemplo anterior. As atividades +que não devem estar disponíveis a outros aplicativos não devem ter filtros de intenção, já que é possível +iniciá-las por meio de intenções explícitas (conforme discutido na seção a seguir).

+ +

No entanto, se a atividade deve responder a intenções implícitas derivadas de outros aplicativos +(e do aplicativo em questão), é preciso definir filtros de intenções adicionais +para a atividade. Para cada tipo de intenção que deve responder, é preciso incluir um {@code +<intent-filter>} que contenha um elemento +{@code +<action>} e, opcionalmente, um elemento {@code +<category>} e/ou um elemento {@code +<data>}. Esses elementos especificam o tipo de intenção a que a atividade pode +responder.

+ +

Para obter mais informações sobre a forma com que as atividades podem responder a intenções, consulte o documento +Intenções e filtros de intenções.

+ + + +

Início de uma atividade

+ +

Para iniciar outra atividade, é possível chamar {@link android.app.Activity#startActivity +startActivity()} passando uma {@link android.content.Intent} que descreva a atividade +que se deseja iniciar. A intenção especifica a atividade exata que deve ser iniciada ou descreve o tipo +de ação que ela deve executar (e o sistema seleciona a atividade adequada, +que +pode ser até de um outro aplicativo). Uma intenção também pode portar pequenas quantidades de dados +a serem usados pela atividade iniciada.

+ +

Ao trabalhar no aplicativo, frequentemente será necessário iniciar uma atividade conhecida. + Para isso, pode-se criar uma intenção que defina explicitamente a atividade que deve ser iniciada +por meio de um nome de classe. Por exemplo, a seguir é demonstrado como uma atividade inicia outra atividade de nome {@code +SignInActivity}:

+ +
+Intent intent = new Intent(this, SignInActivity.class);
+startActivity(intent);
+
+ +

No entanto, o aplicativo também pode ter que executar algumas ações, como enviar um e-mail, +mensagem de texto ou atualização de status usando os dados da atividade em questão. Nesse caso, o aplicativo +pode não ter suas próprias atividades para executar tais ações; para isso, pode-se aproveitar as atividades +fornecidas por outros aplicativos do dispositivo que podem executar essas ações. Esses são os casos +em que as intenções são muito importantes — é possível criar uma intenção que descreva +uma ação a executar para que o sistema +inicie a atividade apropriada de outro aplicativo. Se houver +mais de uma atividade que possa manipular a intenção, o usuário poderá escolher qual usará. Por exemplo: + se quiser que o usuário envie uma mensagem de e-mail, é possível criar +a seguinte intenção:

+ +
+Intent intent = new Intent(Intent.ACTION_SEND);
+intent.putExtra(Intent.EXTRA_EMAIL, recipientArray);
+startActivity(intent);
+
+ +

O {@link android.content.Intent#EXTRA_EMAIL} adicionado à intenção é uma matriz de sequência +de endereços de e-mail para os quais o e-mail poderá ser enviado. Quando um aplicativo de e-mail responde à essa intenção, +ele lê a matriz de sequência fornecida no extra e a coloca no campo "para" +do formulário de composição do e-mail. Nessa situação, a atividade do aplicativo de e-mail inicia e, quando o usuário termina o trabalho, +sua atividade é retomada.

+ + + + +

Início de uma atividade para um resultado

+ +

Às vezes, é necessário receber um resultado de alguma atividade iniciada. Nesse caso, + inicie a atividade chamando {@link android.app.Activity#startActivityForResult +startActivityForResult()} (em vez de {@link android.app.Activity#startActivity + startActivity()}). Para receber o resultado de uma atividade +subsequente, implemente o método de retorno de chamada +{@link android.app.Activity#onActivityResult onActivityResult()}. Quando a atividade subsequente estiver concluída, ela retornará um resultado em uma {@link +android.content.Intent} para o método +{@link android.app.Activity#onActivityResult onActivityResult()}.

+ +

Por exemplo, talvez você queira que o usuário escolha um dos contatos dele, deste modo, a atividade poderá +fazer algo com as informações naquele contato. A seguir expõe-se como criar uma intenção desse tipo +e manipular o resultado:

+ +
+private void pickContact() {
+    // Create an intent to "pick" a contact, as defined by the content provider URI
+    Intent intent = new Intent(Intent.ACTION_PICK, Contacts.CONTENT_URI);
+    startActivityForResult(intent, PICK_CONTACT_REQUEST);
+}
+
+@Override
+protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+    // If the request went well (OK) and the request was PICK_CONTACT_REQUEST
+    if (resultCode == Activity.RESULT_OK && requestCode == PICK_CONTACT_REQUEST) {
+        // Perform a query to the contact's content provider for the contact's name
+        Cursor cursor = getContentResolver().query(data.getData(),
+        new String[] {Contacts.DISPLAY_NAME}, null, null, null);
+        if (cursor.moveToFirst()) { // True if the cursor is not empty
+            int columnIndex = cursor.getColumnIndex(Contacts.DISPLAY_NAME);
+            String name = cursor.getString(columnIndex);
+            // Do something with the selected contact's name...
+        }
+    }
+}
+
+ +

Esse exemplo mostra a lógica básica que deve ser usada no método {@link +android.app.Activity#onActivityResult onActivityResult()} para manipular +o resultado de uma atividade. A primeira condição verifica se a solicitação foi bem-sucedida — se for, +o {@code resultCode} será {@link android.app.Activity#RESULT_OK} — e se a solicitação +a que esse resultado responderá for desconhecida —, nesse caso, o {@code requestCode} corresponderá +ao segundo parâmetro com {@link android.app.Activity#startActivityForResult +startActivityForResult()}. A partir daí, o código manipula o resultado da atividade com uma consulta +dos dados retornados em uma {@link android.content.Intent} (o parâmetro {@code data}).

+ +

Nesse momento, um {@link +android.content.ContentResolver} executa uma consulta em um provedor de conteúdo, que retorna +um {@link android.database.Cursor} que permite a leitura dos dados consultados. Para obter mais informações, +consulte o documento Provedores de conteúdo.

+ +

Para obter mais informações sobre intenções, consulte o documento +Intenções e filtros de intenções.

+ + +

Encerramento de uma atividade

+ +

Para encerrar uma atividade, chame o método {@link android.app.Activity#finish +finish()}. Também é possível encerrar uma atividade separada iniciada anteriormente chamando +{@link android.app.Activity#finishActivity finishActivity()}.

+ +

Observação: na maioria dos casos, não se deve finalizar explicitamente +uma atividade usando esses métodos. Conforme discutido na seção anterior sobre o ciclo de vida da atividade, +o sistema Android gerencia a vida de uma atividade, portanto não é necessário finalizar +as atividades. Chamar esses métodos poderia afetar negativamente a experiência +do usuário esperada e isso só deve ser usado quando realmente não se desejar que o usuário +retorne a essa instância da atividade.

+ + +

Gerenciamento do ciclo de vida da atividade

+ +

O gerenciamento do ciclo de vida das atividades por meio da implementação +de métodos de retorno de chamada +é essencial para desenvolver um aplicativo flexível. O ciclo de vida de uma atividade é diretamente afetado +por sua associação a outras atividades, sua tarefa e sua pilha de retorno.

+ +

Uma atividade pode existir essencialmente em três estados:

+ +
+
Retomada
+
A atividade está em primeiro plano na tela e tem o foco do usuário (em geral, +chama-se esse estado de "em execução”).
+ +
Pausada
+
A atividade ainda está visível, mas outra atividade está em primeiro plano e tem o foco. Ou seja, +outra atividade está visível por cima desta e está parcialmente transparente +ou não cobre inteiramente a tela. Uma atividade pausada está totalmente ativa (o objeto +{@link android.app.Activity} está retido na memória, mantém todas as informações de estado e do membro e permanece anexado +ao gerenciador de janela), mas pode ser eliminada pelo sistema em situações de memória extremamente baixa.
+ +
Interrompida
+
A atividade está totalmente suplantada por outra (a atividade passa para +"segundo plano"). Uma atividade interrompida ainda está ativa (o objeto +{@link android.app.Activity} está retido na memória, mantém todas as informações de estado e do membro, mas não está +anexado ao gerenciador de janelas). No entanto, ela não fica mais visível para o usuário +e pode ser eliminada pelo sistema se a memória for necessária em outro processo.
+
+ +

Se uma atividade estiver pausada ou interrompida, o sistema poderá descartá-la da memória solicitando a +finalização do processo (chamando seu método {@link android.app.Activity#finish finish()}) ou simplesmente +eliminando-o. Quando a atividade for reaberta (depois de finalizada ou eliminada), ele deverá ser +totalmente recriada.

+ + + +

Implementação de retornos de chamada do ciclo de vida

+ +

Quando uma atividade transita entre os diferentes estados descritos acima, ela é notificada +por meio de vários métodos de retorno de chamada. Todos os métodos de retorno de chamada são ganchos +que podem ser substituídos para executar um trabalho adequado quando o estado da atividade muda. O esqueleto de atividade +a seguir contém cada um dos métodos do ciclo de vida fundamentais:

+ + +
+public class ExampleActivity extends Activity {
+    @Override
+    public void {@link android.app.Activity#onCreate onCreate}(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        // The activity is being created.
+    }
+    @Override
+    protected void {@link android.app.Activity#onStart onStart()} {
+        super.onStart();
+        // The activity is about to become visible.
+    }
+    @Override
+    protected void {@link android.app.Activity#onResume onResume()} {
+        super.onResume();
+        // The activity has become visible (it is now "resumed").
+    }
+    @Override
+    protected void {@link android.app.Activity#onPause onPause()} {
+        super.onPause();
+        // Another activity is taking focus (this activity is about to be "paused").
+    }
+    @Override
+    protected void {@link android.app.Activity#onStop onStop()} {
+        super.onStop();
+        // The activity is no longer visible (it is now "stopped")
+    }
+    @Override
+    protected void {@link android.app.Activity#onDestroy onDestroy()} {
+        super.onDestroy();
+        // The activity is about to be destroyed.
+    }
+}
+
+ +

Observação: a implementação desses métodos do ciclo de vida +deve sempre chamar a implementação da superclasse antes de realizar qualquer trabalho, conforme ilustrado no exemplo acima.

+ +

Juntos, esses métodos definem todo o ciclo de vida da atividade. Ao implementá-los, +é possível monitorar três loops aninhados no ciclo de vida da atividade:

+ +
    +
  • Todo o tempo de vida de uma atividade acontece entre a chamada de {@link +android.app.Activity#onCreate onCreate()} e a chamada de {@link +android.app.Activity#onDestroy}. A atividade deve executar configuração +de estado "global" (como definindo layout) em {@link android.app.Activity#onCreate onCreate()} +e liberar todos os recursos restantes em {@link android.app.Activity#onDestroy}. Por exemplo: se a sua atividade +tiver um encadeamento em execução em segundo plano para baixar dados da rede, ela pode +criá-lo em {@link android.app.Activity#onCreate onCreate()} e, em seguida, interrompê-lo em {@link +android.app.Activity#onDestroy}.
  • + +
  • O tempo de vida visível de uma atividade acontece entre a chamada de {@link +android.app.Activity#onStart onStart()} e a chamada de {@link +android.app.Activity#onStop onStop()}. Durante esse tempo, o usuário pode ver a atividade +na tela e interagir com ela. Por exemplo: {@link android.app.Activity#onStop onStop()} é chamado +quando uma nova atividade inicia e esta não fica mais visível. Entre esses dois métodos, é possível +manter os recursos necessários para exibir a atividade ao usuário. Por exemplo: você pode registrar +um {@link android.content.BroadcastReceiver} em {@link +android.app.Activity#onStart onStart()} para monitorar as alterações que afetem a IU e cancelar o registro + em {@link android.app.Activity#onStop onStop()} quando o usuário não puder mais ver +o que você está exibindo. O sistema pode chamar {@link android.app.Activity#onStart onStart()} e {@link +android.app.Activity#onStop onStop()} várias vezes durante todo o tempo de vida de uma atividade +enquanto ela alterna entre visível e oculta ao usuário.

  • + +
  • O tempo de vida em primeiro plano de uma atividade ocorre entre a chamada de {@link +android.app.Activity#onResume onResume()} e a chamada de {@link android.app.Activity#onPause +onPause()}. Durante esse tempo, a atividade está na frente de todas as outras atividades na tela +e tem o foco de interação do usuário. Frequentemente, uma atividade pode transitar entre o primeiro e +o segundo plano — por exemplo, {@link android.app.Activity#onPause onPause()} é chamado quando o dispositivo está em suspensão +ou quando uma caixa de diálogo é exibida. Como esse estado pode transitar frequentemente, o código nesses dois métodos deve +ser bem leve para evitar transições lentas que façam o usuário esperar.

  • +
+ +

A figura 1 ilustra esses loops e os caminhos que uma atividade pode tomar entre os estados. +Os retângulos representam os métodos de retorno de chamada que podem ser implementados para executar operações +quando a atividade transita entre estados.

+ + +

Figura 1. Ciclo de vida da atividade.

+ +

Os mesmos métodos de retorno de chamada do ciclo de vida são listados na tabela 1, que descreve cada um deles +em mais detalhes e localiza cada um dentro +do ciclo de vida geral da atividade, inclusive se o sistema puder eliminar a atividade depois +da conclusão do método de retorno de chamada.

+ +

Tabela 1. Resumo dos métodos de retorno de chamada +do ciclo de vida da atividade.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Método Descrição Eliminável depois de? Próximo
{@link android.app.Activity#onCreate onCreate()}Chamado quando a atividade é criada pela primeira vez. + É quando deve-se fazer toda a configuração estática normal — +criar vistas, vincular dados a listas etc. Esse método recebe +um objeto Bundle (pacote) contendo o estado anterior da atividade, se esse +estado for capturado (consulte Gravação do estado da atividade +mais adiante). +

Sempre seguido de {@code onStart()}.

Não{@code onStart()}
    {@link android.app.Activity#onRestart +onRestart()}Chamado depois que atividade tiver sido interrompida, logo antes de ser +reiniciada. +

Sempre seguido de {@code onStart()}.

Não{@code onStart()}
{@link android.app.Activity#onStart onStart()}Chamado logo antes de a atividade se tornar visível ao usuário. +

Seguido de {@code onResume()} se a atividade +for para segundo plano ou {@code onStop()} se ficar oculta.

Não{@code onResume()}
ou
{@code onStop()}
    {@link android.app.Activity#onResume onResume()}Chamado logo antes de a atividade iniciar +a interação com o usuário. Nesse ponto, a atividade estará +no topo da pilha de atividades com a entrada do usuário direcionada a ela. +

Sempre seguido de {@code onPause()}.

Não{@code onPause()}
{@link android.app.Activity#onPause onPause()}Chamado quando o sistema está prestes a retomar +outra atividade. Esse método normalmente é usado para confirmar alterações +não salvas a dados persistentes, animações interrompidas e outras coisas que talvez +estejam consumindo CPU e assim por diante. Ele sempre deve fazer tudo bem rapidamente porque +a próxima atividade não será retomada até ela retornar. +

Seguido de {@code onResume()} se a atividade +retornar para a frente ou de {@code onStop()} se ficar +invisível ao usuário.

Sim{@code onResume()}
ou
{@code onStop()}
{@link android.app.Activity#onStop onStop()}Chamado quando a atividade não está mais visível ao usuário. Isso +pode acontecer porque ela está sendo destruída ou porque outra atividade +(uma existente ou uma nova) foi retomada e está cobrindo-a. +

Seguido de {@code onRestart()} se a atividade +estiver voltando a interagir com o usuário +ou {@code onDestroy()} se estiver saindo.

Sim{@code onRestart()}
ou
{@code onDestroy()}
{@link android.app.Activity#onDestroy +onDestroy()}Chamado antes de a atividade ser destruída. É a última chamada +que a atividade receberá. Pode ser chamado porque a atividade +está finalizando (alguém chamou {@link android.app.Activity#finish + finish()} nela) ou porque o sistema está destruindo temporariamente essa instância +da atividade para poupar espaço. É possível distinguir +entre essas duas situações com o método {@link + android.app.Activity#isFinishing isFinishing()}.Simnada
+ +

A coluna de nome "Eliminável depois de?" indica se o sistema pode ou não +eliminar o processo que hospeda a atividade a qualquer momento após o método retornar +sem executar outra linha de código da atividade. Estes três métodos são marcados como "sim": ({@link +android.app.Activity#onPause +onPause()}, {@link android.app.Activity#onStop onStop()} e {@link android.app.Activity#onDestroy +onDestroy()}). Como {@link android.app.Activity#onPause onPause()} é o primeiro +dos três, assim que a atividade é criada, {@link android.app.Activity#onPause onPause()} é +o último método que certamente será chamado antes que o processo possa ser eliminado — +se o sistema precisar recuperar memória em uma emergência, {@link +android.app.Activity#onStop onStop()} e {@link android.app.Activity#onDestroy onDestroy()} poderão +não ser chamados. Portanto, deve-se usar {@link android.app.Activity#onPause onPause()} para gravar +dados persistentes cruciais (como edições do usuário) no armazenamento. No entanto, deve-se sempre ser seletivo +acerca das informações que devem ser retidas durante {@link android.app.Activity#onPause onPause()} porque +qualquer procedimento de bloqueio nesse método bloqueará a transição para a próxima atividade e retardará +a experiência do usuário.

+ +

Os métodos marcados como "Não" na coluna Elimináveis protegem o processo que hospeda +a atividade. evitando a eliminação dele no momento em que é chamado. Assim, uma atividade é eliminável +do momento em que {@link android.app.Activity#onPause onPause()} retorna ao momento em que +{@link android.app.Activity#onResume onResume()} é chamado. Ela não será eliminável novamente até que +{@link android.app.Activity#onPause onPause()} seja chamado e retorne novamente.

+ +

Observação: uma atividade tecnicamente não "eliminável”, por essa definição +na tabela 1, ainda pode ser eliminada pelo sistema — mas isso só ocorreria +em circunstâncias extremas, quando não houvesse outra opção. A possibilidade de uma atividade ser eliminada +é discutida em mais detalhes no documento Processos +e encadeamentos.

+ + +

Gravação do estado da atividade

+ +

A introdução a Gerenciamento do ciclo de vida da atividade menciona brevemente +que, +quando uma atividade é pausada ou interrompida, o estado da atividade é retido. Isso acontece porque +o objeto {@link android.app.Activity} continua mantido na memória quando a atividade está pausada +ou interrompida — todas as informações sobre seus membros e estado atual ainda estão ativas. Assim, todas as alterações +feitas pelo usuário dentro da atividade são retidas, de forma que, quando a atividade retornar +ao primeiro plano (quando é "retomada"), essas alterações ainda estejam lá.

+ +

No entanto, quando o sistema destrói uma atividade para recuperar memória, o objeto {@link +android.app.Activity} é destruído, por isso o sistema não pode simplesmente retomá-la com seu estado +intacto. Em vez disso, o sistema tem que recriar o objeto {@link android.app.Activity} se o usuário +navegar de volta a ele. Ainda assim, o usuário não estará ciente +de que o sistema destruiu a atividade e recriou-a e, assim, provavelmente +esperará que a atividade esteja exatamente como antes. Nessa situação, para garantir que +as informações importantes sobre o estado da atividade sejam preservadas, implementa-se um método +adicional de retorno de chamada que permite salvar as informações sobre o estado da atividade: {@link +android.app.Activity#onSaveInstanceState onSaveInstanceState()}.

+ +

O sistema chama {@link android.app.Activity#onSaveInstanceState onSaveInstanceState()} +antes de deixar a mensagem vulnerável à destruição. O sistema passa a esse método +um {@link android.os.Bundle} no qual é possível salvar +informações de estado acerca da atividade, como pares nome-valor, usando métodos como {@link +android.os.Bundle#putString putString()} e {@link +android.os.Bundle#putInt putInt()}. Em seguida, se o sistema eliminar o processo +do aplicativo e o usuário voltar à atividade, o sistema recriará a atividade e passará +o {@link android.os.Bundle} a {@link android.app.Activity#onCreate onCreate()} e a {@link +android.app.Activity#onRestoreInstanceState onRestoreInstanceState()}. Usando qualquer um desses métodos, +é possível extrair o estado salvo de {@link android.os.Bundle} e restaurar +o estado da atividade. Se não houver informações de estado a restaurar, o {@link +android.os.Bundle} passado será nulo (que é o caso quando a atividade é criada +pela primeira vez).

+ + +

Figura 2. As duas formas pelas quais uma atividade retorna ao foco +do usuário com seu estado intacto: ou a atividade é destruída e recriada em seguida — e ela deve restaurar +o estado salvo anteriormente —, ou a atividade é interrompida e retomada em seguida — e o estado dela +permanece intacto.

+ +

Observação: não há garantia nenhuma de que {@link +android.app.Activity#onSaveInstanceState onSaveInstanceState()} será chamado antes de a atividade +ser destruída porque há casos em que não será necessário salvar o estado +(como quando o usuário sai da atividade usando o botão Voltar) porque o usuário está fechando +explicitamente +a atividade). Se o sistema chamar {@link android.app.Activity#onSaveInstanceState +onSaveInstanceState()}, ele o fará antes de {@link +android.app.Activity#onStop onStop()} e possivelmente antes de {@link android.app.Activity#onPause +onPause()}.

+ +

No entanto, mesmo se você não fizer nada e não implementar {@link +android.app.Activity#onSaveInstanceState onSaveInstanceState()}, parte do estado da atividade +será restaurada pela implementação padrão da classe {@link android.app.Activity} de {@link +android.app.Activity#onSaveInstanceState onSaveInstanceState()}. Especificamente, a implementação +padrão chama o método {@link +android.view.View#onSaveInstanceState onSaveInstanceState()} correspondente para cada {@link +android.view.View} no layout, o que permite que cada vista forneça informações próprias sobre o que +deve ser salvo. Quase todo widget na estrutura do Android implementa esse método +conforme o necessário, de forma que qualquer alteração visível na IU seja automaticamente salva e restaurada +ao criar a atividade. Por exemplo, o widget {@link android.widget.EditText} salva qualquer texto +inserido pelo usuário e o widget {@link android.widget.CheckBox} salva independente +de verificação. O único trabalho necessário será fornecer um ID exclusivo (com +o atributo {@code android:id}) para cada widget que for salvar seu estado. Se o widget não salvar nenhum ID, o sistema +não poderá salvar seu estado.

+ + + +

Embora a implementação padrão de {@link +android.app.Activity#onSaveInstanceState onSaveInstanceState()} salve informações úteis sobre +a IU da atividade, talvez ainda seja necessário substituí-la para salvar informações adicionais. +Por exemplo: pode ser necessário salvar valores de membro alterados durante a vida da atividade (possivelmente +correlacionados a valores restaurados na IU, mas os membros que retêm esses valores de IU, por padrão, +não são restaurados).

+ +

Como a implementação padrão de {@link +android.app.Activity#onSaveInstanceState onSaveInstanceState()} ajuda a salvar o estado da IU, +se o método for substituído para salvar informações de estado adicionais, deve-se sempre chamar a implementação +da superclasse de {@link android.app.Activity#onSaveInstanceState onSaveInstanceState()} +antes de fazer qualquer trabalho. Da mesma forma, deve-se também chamar a implementação da superclasse de {@link +android.app.Activity#onRestoreInstanceState onRestoreInstanceState()} se ela for substituída para que +a implementação padrão possa restaurar estados da vista.

+ +

Observação: Como nem sempre {@link android.app.Activity#onSaveInstanceState +onSaveInstanceState()} é chamado, +deve-se usá-lo somente para registrar o estado temporário da atividade (o estado +da IU) — nunca se deve usá-lo para armazenar dados persistentes. Em vez disso, deve-se usar {@link +android.app.Activity#onPause onPause()} para armazenar dados persistentes (como dados que devem ser salvos +em um banco de dados) quando o usuário sair da atividade.

+ +

Uma boa forma de testar a capacidade do aplicativo de restaurar seu estado é girar +o dispositivo para alterar a orientação da tela. Quando a orientação de tela muda, o sistema +destrói e recria a atividade para aplicar recursos alternativos que podem ser disponibilizados +para a nova configuração de tela. Por esse motivo somente, é muito importante que a atividade +restaure completamente seu estado quando for recriada porque os usuários normalmente giram a tela +ao usar aplicativos.

+ + +

Manipulação de alterações de configuração

+ +

Algumas configurações do dispositivo podem mudar em tempo de execução (como A orientação da tela, disponibilidade +do teclado e idioma). Quando ocorre uma alteração, o Android recria a atividade em execução +(o sistema chama {@link android.app.Activity#onDestroy} e, em seguida, chama {@link +android.app.Activity#onCreate onCreate()} imediatamente). Esse comportamento foi projetado +para ajudar o aplicativo a se adaptar a novas configurações recarregando-o automaticamente +com recursos alternativos fornecidos pelo programador (como diferentes layouts +para orientações e tamanhos de telas diferentes).

+ +

Se você projetar adequadamente a atividade para manipular um reinício devido a uma alteração na orientação da tela +e restaurar o estado da atividade conforme descrito acima, o aplicativo será mais resiliente a outros +eventos inesperados no ciclo de vida da atividade.

+ +

A melhor forma de manipular um reinício desse tipo +é salvar e restaurar o estado da atividade com {@link + android.app.Activity#onSaveInstanceState onSaveInstanceState()} e {@link +android.app.Activity#onRestoreInstanceState onRestoreInstanceState()} (ou com {@link +android.app.Activity#onCreate onCreate()}), conforme abordado na seção anterior.

+ +

Para obter mais informações sobre alterações de configuração que podem ocorrer em tempo de execução e como manipulá-las, +leia o guia em Tratamento de +alterações em tempo de execução.

+ + + +

Coordenação de atividades

+ +

Quando uma atividade inicia outra, ambas passam por transições no ciclo de vida. A primeira atividade +é pausada e interrompida (embora ela não seja interrompida se ainda estiver visível em segundo plano) enquanto a outra +atividade é criada. Caso essas atividades compartilhem dados salvos em disco ou em outro lugar, é importante +compreender que a primeira atividade não é totalmente interrompida antes da criação da segunda. +Em vez disso, o processo de iniciar a segunda se sobrepõe ao processo de interromper +a primeira.

+ +

A ordem dos retornos de chamada do ciclo de vida é bem definida, especialmente quando as duas atividades estão +no mesmo processo e uma está iniciando a outra. A seguir há a ordem das operações que ocorrem quando a atividade A +inicia a atividade B:

+ +
    +
  1. O método {@link android.app.Activity#onPause onPause()} da atividade A é executado.
  2. + +
  3. Os métodos {@link android.app.Activity#onCreate onCreate()}, {@link +android.app.Activity#onStart onStart()} e {@link android.app.Activity#onResume onResume()} +da atividade B são executados em sequência (a atividade B agora tem o foco do usuário).
  4. + +
  5. Em seguida, se a atividade A não estiver mais visível na tela, seu método {@link +android.app.Activity#onStop onStop()} é executado.
  6. +
+ +

Essa sequência previsível de retornos de chamada do ciclo de vida permite gerenciar a transição +de informações de uma atividade para outra. Por exemplo: se você for gravar em um banco de dados o momento em que +a primeira atividade é interrompida para que a atividade a seguir possa lê-lo, é preciso realizar a gravação +no banco de dados durante {@link android.app.Activity#onPause onPause()} e não durante {@link +android.app.Activity#onStop onStop()}.

+ + diff --git a/docs/html-intl/intl/pt-br/guide/components/bound-services.jd b/docs/html-intl/intl/pt-br/guide/components/bound-services.jd new file mode 100644 index 0000000000000000000000000000000000000000..aa024943160f9609acda72f5ae8aeec4fb68fa9c --- /dev/null +++ b/docs/html-intl/intl/pt-br/guide/components/bound-services.jd @@ -0,0 +1,658 @@ +page.title=Serviços vinculados +parent.title=Serviços +parent.link=services.html +@jd:body + + +
+
    +

    Neste documento

    +
      +
    1. Conceitos básicos
    2. +
    3. Criação de um serviço vinculado +
        +
      1. Extensão da classe Binder
      2. +
      3. Uso de um mensageiro
      4. +
      +
    4. +
    5. Vinculação a um serviço
    6. +
    7. Gerenciamento do ciclo de vida de um serviço vinculado
    8. +
    + +

    Classes principais

    +
      +
    1. {@link android.app.Service}
    2. +
    3. {@link android.content.ServiceConnection}
    4. +
    5. {@link android.os.IBinder}
    6. +
    + +

    Exemplos

    +
      +
    1. {@code + RemoteService}
    2. +
    3. {@code + LocalService}
    4. +
    + +

    Veja também

    +
      +
    1. Serviços
    2. +
    +
+ + +

Um serviço vinculado é o servidor em uma interface servidor-cliente. Um serviço vinculado permite que componentes +(como atividades) sejam vinculados ao serviço, enviem solicitações, recebam respostas e até estabeleçam +comunicação entre processos (IPC). Um serviço vinculado, geralmente, vive somente enquanto serve +outro componente do aplicativo e não é executado em segundo plano indefinidamente.

+ +

Este documento mostra como criar um serviço vinculado, inclusive como criar vínculos +com o serviço a partir de outros componentes do aplicativo. No entanto, você também deve consultar a documentação Serviços para obter +informações adicionais sobre serviços de forma geral, por exemplo: como enviar notificações de um serviço, +como definir o serviço a ser executado em primeiro plano etc.

+ + +

Conceitos básicos

+ +

Um serviço vinculado é uma implementação da classe {@link android.app.Service} que permite +que outros aplicativos sejam vinculados e interajam com ele. Para fornecer a vinculação +a um serviço, você deve implementar o método de retorno de chamada {@link android.app.Service#onBind onBind()}. Este método +retorna um objeto {@link android.os.IBinder} que define a interface de programação +que os clientes podem usar para interagir com o serviço.

+ + + +

Um cliente pode vincular-se ao serviço chamando {@link android.content.Context#bindService +bindService()}. Quando isto ocorre, é preciso fornecer uma implementação de {@link +android.content.ServiceConnection}, que monitora a conexão com o serviço. O método {@link +android.content.Context#bindService bindService()} retorna imediatamente sem um valor, +mas quando o sistema Android cria a conexão entre +o cliente e o serviço, ele chama {@link +android.content.ServiceConnection#onServiceConnected onServiceConnected()} em {@link +android.content.ServiceConnection} para fornecer o {@link android.os.IBinder} +que o cliente pode usar para comunicar-se com o serviço.

+ +

Vários clientes podem conectar-se ao serviço de uma vez. No entanto, o sistema chama o método +{@link android.app.Service#onBind onBind()} do serviço para recuperar o {@link android.os.IBinder} +somente quando o primeiro cliente é vinculado. Em seguida, o sistema entrega o mesmo {@link android.os.IBinder} +para quaisquer clientes adicionais que vincularem-se, sem chamar {@link android.app.Service#onBind onBind()} novamente.

+ +

Quando o último cliente desvincular-se do serviço, o sistema destruirá o serviço +(a não ser que ele também seja iniciado por {@link android.content.Context#startService startService()}).

+ +

Ao implementar o serviço vinculado, o mais importante é definir a interface +que o método de retorno de chamada {@link android.app.Service#onBind onBind()} retornará. Há poucas maneiras diferentes +de definir a interface {@link android.os.IBinder} do serviço e a +seção a seguir discute cada técnica.

+ + + +

Criação de um serviço vinculado

+ +

Ao criar um serviço que fornece vinculação, você deve fornecer um {@link android.os.IBinder} +que ofereça a interface de programação que os clientes podem usar para interagir com o serviço. Há três maneiras +possíveis de definir a interface:

+ +
+
Extensão da classe Binder
+
Se o serviço for privado para o próprio aplicativo e for executado no mesmo processo que o cliente +(o que é comum), deve-se criar a interface estendendo-se a classe {@link android.os.Binder} +e retornando uma instância dela a partir de +{@link android.app.Service#onBind onBind()}. O cliente receberá {@link android.os.Binder} +e poderá usá-lo para acessar os métodos públicos disponíveis na implementação de {@link android.os.Binder} +ou até em {@link android.app.Service} diretamente. +

Esta é a técnica preferencial quando o serviço é meramente um trabalhador de segundo plano +para o aplicativo. O único motivo pelo qual não se criaria a interface desta maneira +é porque o serviço está sendo usado por outros aplicativos ou em processos separados.

+ +
Uso de um mensageiro
+
Caso precise que a interface funcione em diferentes processos, é possível +criar uma interface para o serviço com {@link android.os.Messenger}. Desta maneira, o serviço +define um {@link android.os.Handler} que responde a diferentes tipos de objetos {@link +android.os.Message}. Este {@link android.os.Handler} +é a base para {@link android.os.Messenger}, que pode então compartilhar um {@link android.os.IBinder} +com o cliente, permitindo que ele envie comandos ao serviço usando objetos {@link +android.os.Message}. Além disso, o cliente pode definir o próprio {@link android.os.Messenger} +para que o serviço possa enviar as mensagens de volta. +

Esta é a maneira mais simples de estabelecer comunicação entre processos (IPC), pois o {@link +android.os.Messenger} coloca todas as solicitações em fila em um único encadeamento para que você não precise +projetar o serviço de modo que seja seguro para encadeamentos.

+
+ +
Uso de AIDL
+
O AIDL (Android Interface Definition Language, linguagem de definição de interface do Android) realiza todo o trabalho de decomposição de objetos +em primitivos para que o sistema operacional possa entendê-los e dispô-los em processos +para realizar a IPC. A técnica anterior, usando o {@link android.os.Messenger}, tem base em AIDL +como a estrutura fundamental. Como mencionado acima, o {@link android.os.Messenger} cria uma fila +de todas as solicitações de cliente em um único encadeamento para que o serviço receba uma solicitação por vez. Se, no entanto, +você quiser que o serviço lide com várias solicitações simultaneamente, é possível usar a AIDL +diretamente. Neste caso, o serviço deverá ser capaz de realizar vários encadeamentos e ser programado de forma segura para encadeamentos. +

Para usar a AIDL diretamente, deve-se +criar um arquivo {@code .aidl} que defina a interface de programação. As ferramentas SDK do Android +usam este arquivo para gerar uma classe abstrata que implemente a interface e lide com a IPC, +que pode ser estendida dentro do serviço.

+
+
+ +

Observação: a maioria dos aplicativos não deve usar a AIDL +para criar um serviço vinculado, pois isto requer capacidade de se trabalhar com encadeamentos múltiplos +e pode resultar em uma implementação mais complicada. Portanto, a AIDL não é adequada para a maioria dos aplicativos +e este documento não discute o seu uso para o serviço. Caso tenha certeza de que +precisa usar a AIDL diretamente, consulte a documentação +AIDL.

+ + + + +

Extensão da classe Binder

+ +

Se o serviço for usado somente pelo aplicativo local e não precisar trabalhar entre processos, +então será possível implementar a própria classe {@link android.os.Binder} que fornece ao cliente +acesso direto aos métodos públicos no serviço.

+ +

Observação: isto funciona somente se o cliente e o serviço +estiverem no mesmo aplicativo e processo, o que é muito comum. Por exemplo, isto funcionaria bem para um +aplicativo de música que precise vincular uma atividade ao próprio serviço que está +reproduzindo música em segundo plano.

+ +

Como configurar:

+
    +
  1. No serviço, crie uma instância de {@link android.os.Binder} que: +
      +
    • contenha métodos públicos que o cliente possa chamar
    • +
    • retorne ao cliente a instância {@link android.app.Service}, que tenha métodos públicos +que ele possa chamar
    • +
    • ou retorne uma instância de outra classe hospedada pelo serviço com métodos públicos +que o cliente possa chamar
    • +
    +
  2. Retorne esta instância de {@link android.os.Binder} a partir do método de retorno de chamada {@link +android.app.Service#onBind onBind()}.
  3. +
  4. No cliente, receba o {@link android.os.Binder} a partir do método de retorno de chamada {@link +android.content.ServiceConnection#onServiceConnected onServiceConnected()} +e faça chamadas para o serviços vinculados usando os métodos fornecidos.
  5. +
+ +

Observação: o motivo pelo qual o serviço e o cliente devem estar no mesmo aplicativo +é para que o cliente possa lançar o objeto retornado e chamar as APIs adequadamente. O serviço e o cliente +também devem estar no mesmo processo, pois esta técnica não possui +nenhuma ingerência entre processos.

+ +

Por exemplo, a seguir há um serviço que fornece aos clientes acesso aos métodos no serviço +por meio de uma implementação de {@link android.os.Binder}:

+ +
+public class LocalService extends Service {
+    // Binder given to clients
+    private final IBinder mBinder = new LocalBinder();
+    // Random number generator
+    private final Random mGenerator = new Random();
+
+    /**
+     * Class used for the client Binder.  Because we know this service always
+     * runs in the same process as its clients, we don't need to deal with IPC.
+     */
+    public class LocalBinder extends Binder {
+        LocalService getService() {
+            // Return this instance of LocalService so clients can call public methods
+            return LocalService.this;
+        }
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return mBinder;
+    }
+
+    /** method for clients */
+    public int getRandomNumber() {
+      return mGenerator.nextInt(100);
+    }
+}
+
+ +

O {@code LocalBinder} fornece o método {@code getService()} para que os clientes +recuperem a instância atual de {@code LocalService}. Isto permite que os clientes chamem métodos públicos +no serviço. Por exemplo, eles podem chamar {@code getRandomNumber()} a partir do serviço.

+ +

Abaixo há uma atividade que vincula-se a {@code LocalService} e chama {@code getRandomNumber()} +quando um botão é pressionado:

+ +
+public class BindingActivity extends Activity {
+    LocalService mService;
+    boolean mBound = false;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.main);
+    }
+
+    @Override
+    protected void onStart() {
+        super.onStart();
+        // Bind to LocalService
+        Intent intent = new Intent(this, LocalService.class);
+        bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
+    }
+
+    @Override
+    protected void onStop() {
+        super.onStop();
+        // Unbind from the service
+        if (mBound) {
+            unbindService(mConnection);
+            mBound = false;
+        }
+    }
+
+    /** Called when a button is clicked (the button in the layout file attaches to
+      * this method with the android:onClick attribute) */
+    public void onButtonClick(View v) {
+        if (mBound) {
+            // Call a method from the LocalService.
+            // However, if this call were something that might hang, then this request should
+            // occur in a separate thread to avoid slowing down the activity performance.
+            int num = mService.getRandomNumber();
+            Toast.makeText(this, "number: " + num, Toast.LENGTH_SHORT).show();
+        }
+    }
+
+    /** Defines callbacks for service binding, passed to bindService() */
+    private ServiceConnection mConnection = new ServiceConnection() {
+
+        @Override
+        public void onServiceConnected(ComponentName className,
+                IBinder service) {
+            // We've bound to LocalService, cast the IBinder and get LocalService instance
+            LocalBinder binder = (LocalBinder) service;
+            mService = binder.getService();
+            mBound = true;
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName arg0) {
+            mBound = false;
+        }
+    };
+}
+
+ +

O exemplo acima mostra como o cliente vincula um serviço usando uma implementação +de {@link android.content.ServiceConnection} e o retorno de chamada {@link +android.content.ServiceConnection#onServiceConnected onServiceConnected()}. A próxima seção +fornece mais informações sobre este processo de vinculação ao serviço.

+ +

Observação: o exemplo acima não desfaz a vinculação explicitamente a partir do serviço, +mas todos os clientes devem fazê-lo no momento adequado (como quando a atividade pausa).

+ +

Para obter mais códigos de exemplo, consulte a classe {@code +LocalService.java} e a classe {@code +LocalServiceActivities.java} em ApiDemos.

+ + + + + +

Uso de um mensageiro

+ + + +

Caso precise que o serviço comunique-se com processos remotos, é possível usar +o {@link android.os.Messenger} para fornecer a interface ao serviço. Esta técnica permite +estabelecer comunicação entre processos (IPC) sem precisar usar a AIDL.

+ +

A seguir há um resumo sobre como usar um {@link android.os.Messenger}:

+ +
    +
  • O serviço implementa um {@link android.os.Handler} que recebe um retorno de chamada +para cada chamada de um cliente.
  • +
  • O {@link android.os.Handler} é usado para criar um objeto {@link android.os.Messenger} +(que é uma referência para o {@link android.os.Handler}).
  • +
  • O {@link android.os.Messenger} cria um {@link android.os.IBinder} que o serviço +retorna aos clientes a partir de {@link android.app.Service#onBind onBind()}.
  • +
  • Os clientes usam {@link android.os.IBinder} para instanciar o {@link android.os.Messenger} +(que menciona o {@link android.os.Handler} do serviço), que usam para enviar objetos +{@link android.os.Message} para o serviço.
  • +
  • O serviço recebe cada {@link android.os.Message} em seu {@link +android.os.Handler} — especificamente, no método {@link android.os.Handler#handleMessage +handleMessage()}.
  • +
+ + +

Desta forma, não há "métodos" para o cliente chamar no serviço. Em vez disso, o cliente +envia "mensagens" (objetos {@link android.os.Message}) que o serviço recebe +no {@link android.os.Handler}.

+ +

Abaixo há um exemplo simples de serviço que usa uma interface {@link android.os.Messenger}:

+ +
+public class MessengerService extends Service {
+    /** Command to the service to display a message */
+    static final int MSG_SAY_HELLO = 1;
+
+    /**
+     * Handler of incoming messages from clients.
+     */
+    class IncomingHandler extends Handler {
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_SAY_HELLO:
+                    Toast.makeText(getApplicationContext(), "hello!", Toast.LENGTH_SHORT).show();
+                    break;
+                default:
+                    super.handleMessage(msg);
+            }
+        }
+    }
+
+    /**
+     * Target we publish for clients to send messages to IncomingHandler.
+     */
+    final Messenger mMessenger = new Messenger(new IncomingHandler());
+
+    /**
+     * When binding to the service, we return an interface to our messenger
+     * for sending messages to the service.
+     */
+    @Override
+    public IBinder onBind(Intent intent) {
+        Toast.makeText(getApplicationContext(), "binding", Toast.LENGTH_SHORT).show();
+        return mMessenger.getBinder();
+    }
+}
+
+ +

Observe que o método {@link android.os.Handler#handleMessage handleMessage()} +no {@link android.os.Handler} é onde o serviço recebe a {@link android.os.Message} +e decide o que fazer, com base no membro {@link android.os.Message#what}.

+ +

Tudo que o cliente precisa fazer é criar um {@link android.os.Messenger} com base no {@link +android.os.IBinder} retornado pelo serviço e enviar uma mensagem usando {@link +android.os.Messenger#send send()}. Por exemplo, a seguir há uma atividade simples que vincula-se +ao serviço e envia a mensagem {@code MSG_SAY_HELLO} a ele:

+ +
+public class ActivityMessenger extends Activity {
+    /** Messenger for communicating with the service. */
+    Messenger mService = null;
+
+    /** Flag indicating whether we have called bind on the service. */
+    boolean mBound;
+
+    /**
+     * Class for interacting with the main interface of the service.
+     */
+    private ServiceConnection mConnection = new ServiceConnection() {
+        public void onServiceConnected(ComponentName className, IBinder service) {
+            // This is called when the connection with the service has been
+            // established, giving us the object we can use to
+            // interact with the service.  We are communicating with the
+            // service using a Messenger, so here we get a client-side
+            // representation of that from the raw IBinder object.
+            mService = new Messenger(service);
+            mBound = true;
+        }
+
+        public void onServiceDisconnected(ComponentName className) {
+            // This is called when the connection with the service has been
+            // unexpectedly disconnected -- that is, its process crashed.
+            mService = null;
+            mBound = false;
+        }
+    };
+
+    public void sayHello(View v) {
+        if (!mBound) return;
+        // Create and send a message to the service, using a supported 'what' value
+        Message msg = Message.obtain(null, MessengerService.MSG_SAY_HELLO, 0, 0);
+        try {
+            mService.send(msg);
+        } catch (RemoteException e) {
+            e.printStackTrace();
+        }
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.main);
+    }
+
+    @Override
+    protected void onStart() {
+        super.onStart();
+        // Bind to the service
+        bindService(new Intent(this, MessengerService.class), mConnection,
+            Context.BIND_AUTO_CREATE);
+    }
+
+    @Override
+    protected void onStop() {
+        super.onStop();
+        // Unbind from the service
+        if (mBound) {
+            unbindService(mConnection);
+            mBound = false;
+        }
+    }
+}
+
+ +

Observe que este exemplo não mostra como o serviço pode responder ao cliente. Caso queira que o serviço responda, +é necessário criar um {@link android.os.Messenger} no cliente também. Em seguida, +quando o cliente receber o retorno de chamada de {@link android.content.ServiceConnection#onServiceConnected +onServiceConnected()}, ele enviará uma {@link android.os.Message} para o serviço que inclui +o {@link android.os.Messenger} no parâmetro {@link android.os.Message#replyTo} +do método {@link android.os.Messenger#send send()}.

+ +

É possível ver um exemplo de como fornecer mensagens de duas vias nos exemplos {@code +MessengerService.java} (serviço) e {@code +MessengerServiceActivities.java} (cliente).

+ + + + + +

Vinculação a um serviço

+ +

Um componente de aplicativo (cliente) pode vincular-se a um serviço chamando +{@link android.content.Context#bindService bindService()}. O sistema Android, em seguida, +chama o método {@link android.app.Service#onBind +onBind()} do serviço, que retorna um {@link android.os.IBinder} para interagir com o serviço.

+ +

A vinculação é assíncrona. {@link android.content.Context#bindService +bindService()} retorna imediatamente e não retorna o {@link android.os.IBinder} +ao cliente. Para receber {@link android.os.IBinder}, o cliente deve criar uma instância de {@link +android.content.ServiceConnection} e passá-la para {@link android.content.Context#bindService +bindService()}. O {@link android.content.ServiceConnection} inclui um método de retorno de chamada +que o sistema chama para entregar o {@link android.os.IBinder}.

+ +

Observação: somente atividades, serviços e provedores de conteúdo +podem vincular-se a um serviço — você não pode fazer isto a partir de um receptor de transmissão.

+ +

Portanto, para vincular-se a um serviço a partir do cliente, deve-se:

+
    +
  1. Implementar {@link android.content.ServiceConnection}. +

    Sua implementação deve substituir dois métodos de retorno de chamada:

    +
    +
    {@link android.content.ServiceConnection#onServiceConnected onServiceConnected()}
    +
    O sistema chama isto para entregar o {@link android.os.IBinder} retornado +pelo método {@link android.app.Service#onBind onBind()} do serviço.
    +
    {@link android.content.ServiceConnection#onServiceDisconnected +onServiceDisconnected()}
    +
    O sistema Android chama isto quando a conexão ao serviço é perdida inesperadamente, + como quando o serviço apresenta problemas ou é fechado repentinamente. Isto não é chamado quando +o cliente desfaz o vínculo.
    +
    +
  2. +
  3. Chamar {@link +android.content.Context#bindService bindService()}, passando a implementação {@link +android.content.ServiceConnection}.
  4. +
  5. Quando o sistema chamar o método de retorno de chamada {@link android.content.ServiceConnection#onServiceConnected +onServiceConnected()}, é possível fazer chamadas para o serviço +usando os métodos definidos pela interface.
  6. +
  7. Para desconectar-se de um serviço, chamar {@link +android.content.Context#unbindService unbindService()}. +

    Quando um cliente é destruído, o vínculo com o serviço acaba. No entanto, sempre desvincule-se +ao terminar a interação com o serviço ou quando a atividade pausar para que o serviço +possa ser encerrado enquanto não estiver em uso (os momentos adequados para vincular ou desvincular +são abordados mais abaixo).

    +
  8. +
+ +

Por exemplo, o fragmento a seguir conecta o cliente ao serviço criado acima +estendendo a classe Binder para que tudo que ele tenha que fazer seja lançar +o {@link android.os.IBinder} retornado para a classe {@code LocalService} e solicitar a instância de {@code +LocalService}:

+ +
+LocalService mService;
+private ServiceConnection mConnection = new ServiceConnection() {
+    // Called when the connection with the service is established
+    public void onServiceConnected(ComponentName className, IBinder service) {
+        // Because we have bound to an explicit
+        // service that is running in our own process, we can
+        // cast its IBinder to a concrete class and directly access it.
+        LocalBinder binder = (LocalBinder) service;
+        mService = binder.getService();
+        mBound = true;
+    }
+
+    // Called when the connection with the service disconnects unexpectedly
+    public void onServiceDisconnected(ComponentName className) {
+        Log.e(TAG, "onServiceDisconnected");
+        mBound = false;
+    }
+};
+
+ +

Com este {@link android.content.ServiceConnection}, o cliente pode vincular-se a um serviço +passando-o para {@link android.content.Context#bindService bindService()}. Por exemplo:

+ +
+Intent intent = new Intent(this, LocalService.class);
+bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
+
+ +
    +
  • O primeiro parâmetro de {@link android.content.Context#bindService bindService()} +é uma {@link android.content.Intent} que nomeia explicitamente o serviço que será vinculado (apesar de a intenção +poder ser implícita).
  • +
  • O segundo parâmetro é o objeto {@link android.content.ServiceConnection}.
  • +
  • O terceiro parâmetro é um sinalizador que indica as opções de vinculação. Geralmente, ele deve ser {@link +android.content.Context#BIND_AUTO_CREATE} para criar o serviço se não estiver ativo ainda. +Outros possíveis valores são {@link android.content.Context#BIND_DEBUG_UNBIND} +e {@link android.content.Context#BIND_NOT_FOREGROUND}, ou {@code 0} para nada.
  • +
+ + +

Observações adicionais

+ +

A seguir há algumas observações importantes sobre a vinculação com um serviço:

+
    +
  • Deve-se sempre capturar exceções{@link android.os.DeadObjectException}, que são lançadas +quando a conexão apresenta erros. Esta é a única exceção lançada por métodos remotos.
  • +
  • Objetos são referências contadas em todos os processos.
  • +
  • Geralmente, alterna-se entre a vinculação e a desvinculação +durante os momentos crescentes e decrescentes do ciclo de vida do cliente. Por exemplo: +
      +
    • Caso precise interagir com o serviço enquanto a atividade estiver visível, +deve-se vincular durante {@link android.app.Activity#onStart onStart()} e desvincular durante {@link +android.app.Activity#onStop onStop()}.
    • +
    • Caso queira que a atividade receba mensagens mesmo quando for interrompida +em segundo plano, é possível vincular durante {@link android.app.Activity#onCreate onCreate()} +e desvincular durante {@link android.app.Activity#onDestroy onDestroy()}. Cuidado, pois isto significa que a atividade +precisa usar o serviço durante todo o tempo de execução (mesmo em segundo plano). +Portanto, se o serviço estiver em outro processo, o peso do processo será aumentado +e é mais provável que o sistema elimine-o.
    • +
    +

    Observação: geralmente, não se realiza o vínculo e o seu rompimento +durante o {@link android.app.Activity#onResume onResume()} e o {@link +android.app.Activity#onPause onPause()} da atividade, pois esses retornos de chamada ocorrem em todas as transições do ciclo de vida +e deve-se usar menos processamento possível nessas transações. Além disso, +se várias atividades no aplicativo vincularem-se ao mesmo serviço e houver uma transação entre duas +dessas atividades, o serviço poderá ser destruído e recriado à medida que a atividade desvincula-se, +durante a pausa, antes do próximo vínculo, durante a retomada (esta transição de atividade +para como as atividades coordenam os ciclos de vida é descrita no documento Atividades +).

    +
+ +

Para obter mais códigos de exemplo mostrando como vincular a um serviço, consulte a classe {@code +RemoteService.java} no ApiDemos.

+ + + + + +

Gerenciamento do ciclo de vida de um serviço vinculado

+ +

Quando um serviço é desvinculado de todos os clientes, o sistema Android o destrói (a não ser que ele +também tenha sido inicializado com {@link android.app.Service#onStartCommand onStartCommand()}). Portanto, não é necessário +gerenciar o ciclo de vida do serviço se ele for puramente um serviço vinculado +— o sistema Android o gerencia com base nos vínculos com os clientes.

+ +

No entanto, se você escolher implementar o método de retorno de chamada {@link android.app.Service#onStartCommand +onStartCommand()}, interrompa o serviço explicitamente, pois o serviço +já terá sido considerado como iniciado. Neste caso, o serviço permanece em execução até +ser interrompido com {@link android.app.Service#stopSelf()} ou outras chamadas de componente {@link +android.content.Context#stopService stopService()}, independente de vínculo +com qualquer cliente.

+ +

Além disso, se o serviço for iniciado e aceitar vínculos, quando o sistema chamar +o método {@link android.app.Service#onUnbind onUnbind()}, será possível retornar +{@code true} opcionalmente se você quiser receber uma chamada de {@link android.app.Service#onRebind +onRebind()} na próxima vez em que um cliente vincular-se ao serviço (em vez de receber uma chamada de {@link +android.app.Service#onBind onBind()}). {@link android.app.Service#onRebind +onRebind()} retorna vazio, mas o cliente ainda recebe {@link android.os.IBinder} +no retorno de chamada {@link android.content.ServiceConnection#onServiceConnected onServiceConnected()}. +Abaixo, a figura 1 ilustra a lógica para este tipo de ciclo de vida.

+ + + +

Figura 1. O ciclo de vida para um serviço que é iniciado +e também permite vínculos.

+ + +

Para obter mais informações sobre o ciclo de vida de um serviço iniciado, consulte o documento Serviços.

+ + + + diff --git a/docs/html-intl/intl/pt-br/guide/components/fragments.jd b/docs/html-intl/intl/pt-br/guide/components/fragments.jd new file mode 100644 index 0000000000000000000000000000000000000000..7b1acf9e3439ce87687f833fe15c64a2e661f4cb --- /dev/null +++ b/docs/html-intl/intl/pt-br/guide/components/fragments.jd @@ -0,0 +1,812 @@ +page.title=Fragmentos +parent.title=Atividades +parent.link=activities.html +@jd:body + + + +

Um {@link android.app.Fragment} representa o comportamento ou uma parte da interface do usuário em um +{@link android.app.Activity}. É possível combinar vários fragmentos em uma única atividade para compilar +uma IU de painéis múltiplos e reutilizar um fragmento em diversas atividades. Um fragmento +é como uma seção modular de uma atividade, que tem o próprio ciclo de vida, recebe os próprios eventos de entrada +e que pode ser adicionado ou removido com a atividade em execução (uma espécie de "sub-atividade" +que pode ser reutilizada em diferentes atividades).

+ +

Um fragmento deve sempre ser embutido em uma atividade e o ciclo de vida dele +é diretamente impactado pelo ciclo de vida da atividade do host. Por exemplo, quando a atividade é pausada, todos os fragmentos +também são e, quando a atividade é destruída, todos os fragmentos também são. No entanto, enquanto +uma atividade estiver em execução (estiver no estado do ciclo de vida retomado), é possível +manipular cada fragmento independentemente, como adicionar ou removê-los. Ao realizar tal operação com fragmentos, +também é possível adicioná-los a uma pilha de retorno que é gerenciada pela +atividade — cada entrada da pilha de retorno na atividade é um registro da operação de fragmento +que ocorreu. A pilha de retorno permite que o usuário inverta uma operação de fragmento (navegue ao contrário), +pressionando o botão Voltar.

+ +

Ao adicionar um fragmento como parte do layout da atividade, ele viverá em um {@link +android.view.ViewGroup} dentro da hierarquia de vistas e o fragmento definirá o próprio +layout da vista. +É possível inserir um fragmento no layout, declarando-o no arquivo de layout +da atividade, como um elemento {@code <fragment>} ou a partir do código do aplicativo adicionando-o +a um {@link android.view.ViewGroup} existente. No entanto, não é necessário que um fragmento faça parte +do layout da atividade; também é possível usar um fragmento sem a IU como um trabalhador invisível +da atividade.

+ +

Este documento descreve como compilar o aplicativo para usar fragmentos, +incluindo como os fragmentos podem manter seu estado ao serem adicionados à pilha de retorno da atividade, +como compartilhar eventos com a atividade e com outros fragmentos da atividade, como contribuir para a barra de ação +da atividade e muito mais.

+ + +

Filosofia do projeto

+ +

O Android introduziu os fragmentos no Android 3.0 (API de nível 11), principalmente para suportar +mais projetos de IU flexíveis e dinâmicos em telas grandes, como em tablets. Como a tela de um tablet +é muito maior que a de um celular, há mais espaço para combinar +e alternar componentes da IU. Os fragmentos permitem tais projetos sem haver a necessidade de gerenciar +alterações complexas na hierarquia de vistas. Ao dividir o layout de uma atividade em fragmentos, é possível +modificar a aparência da atividade em tempo de execução e preservar essas alterações na pilha de retorno, +que é gerenciada por esta atividade.

+ +

Por exemplo, um aplicativo de notícias pode usar um fragmento para exibir uma lista de artigos +na esquerda e outro fragmento para exibir um artigo na direita — ambos os fragmentos aparecem +em uma atividade, lado a lado, e cada fragmento possui o próprio conjunto de métodos de retorno de chamada do ciclo de vida e lidam +com os próprios eventos de entrada do usuário. Portanto, em vez de usar uma atividade para selecionar um artigo +e outra atividade para lê-lo, o usuário pode selecionar um artigo e lê-lo por completo dentro +da mesma atividade, como ilustrado no layout do tablet na figura 1.

+ +

Você deve projetar cada fragmento como um componente modular e reutilizável da atividade. Ou seja, como cada fragmento +define seu próprio layout e comportamento com os próprios retornos de chamada do ciclo de vida, é possível +incluir um fragmento em várias atividades para poder projetá-lo para reutilização e evitar +a manipulação direta de um fragmento a partir de outro fragmento. Isto é especialmente importante porque +um fragmento modular permite alterar as combinações de fragmentos para tamanhos de tela diferentes. Ao projetar o aplicativo +para ser compatível com tablets e celulares, você poderá reutilizar os fragmentos em diferentes configurações +de layout para otimizar a experiência do usuário com base no espaço de tela disponível. Por exemplo, +em um celular, talvez seja necessário separar os fragmentos para fornecer uma IU de painel único +quando mais de um não se encaixar dentro da mesma atividade.

+ + +

Figura 1. Um exemplo de como dois módulos de IU definidos +pelos fragmentos podem ser combinados em uma atividade de um projeto para tablet mas separados +em um projeto para celular.

+ +

Por exemplo, — continuando com o exemplo do aplicativo de notícias — o aplicativo pode embutir +dois fragmentos na atividade A, quando executado em um dispositivo do tamanho de um tablet. No entanto, +em uma tela de tamanho de celular, não há espaço suficiente para ambos os fragmentos, então a atividade A inclui +somente o fragmento da lista de artigos e, quando o usuário seleciona um artigo, ele inicia +a atividade B, que inclui o segundo fragmento para ler o artigo. Portanto, o aplicativo +é compatível com tablets e celulares através da reutilização dos fragmentos em combinações diferentes, como ilustrado +na figura 1.

+ +

Para obter mais informações sobre o projeto de aplicativos com diferentes combinações de fragmentos +para configurações de tela diferentes, consulte o guia Compatibilidade com tablets e celulares.

+ + + +

Criação de um fragmento

+ +
+ +

Figura 2. O ciclo de vida de um fragmento +(enquanto sua atividade está em execução).

+
+ +

Para criar um fragmento, é preciso criar uma subclasse de {@link android.app.Fragment} (ou uma respectiva +subclasse existente). A classe {@link android.app.Fragment} tem um código que é muito parecido +com o de uma {@link android.app.Activity}. Ele contém métodos de retorno de chamada semelhantes aos de uma atividade, +como {@link android.app.Fragment#onCreate onCreate()}, {@link android.app.Fragment#onStart onStart()}, +{@link android.app.Fragment#onPause onPause()} e {@link android.app.Fragment#onStop onStop()}. Na verdade, +caso esteja convertendo um aplicativo do Android existente para usar os fragmentos, basta mover +o código dos métodos de retorno de chamada da atividade para os respectivos métodos de retorno de chamada +do fragmento.

+ +

Geralmente, deve-se implementar pelo menos os seguintes métodos de ciclo de vida:

+ +
+
{@link android.app.Fragment#onCreate onCreate()}
+
O sistema o chama ao criar o fragmento. Dentro da implementação, +deve-se inicializar os componentes essenciais do fragmento que deseja-se reter quando o fragmento +for pausado ou interrompido e, em seguida, retomado.
+
{@link android.app.Fragment#onCreateView onCreateView()}
+
O sistema chama isto quando é o momento de o fragmento desenhar a interface do usuário +pela primeira vez. Para desenhar uma IU para o fragmento, você deve retornar uma {@link android.view.View} +deste método, que é a raiz do layout do fragmento. É possível retornar como nulo se o fragmento +não fornecer uma IU.
+
{@link android.app.Activity#onPause onPause()}
+
O sistema chama esse método como o primeiro indício de que o usuário está saindo +do fragmento (embora não seja sempre uma indicação de que o fragmento está sendo destruído). É quando geralmente +deve-se confirmar qualquer alteração que deva se manter além da sessão atual do usuário (porque +o usuário pode não retornar).
+
+ +

A maioria dos aplicativos deve implementar pelo menos três destes métodos para cada fragmento, +mas há vários outros métodos de retorno de chamada que você deve usar para lidar com diversos estágios +do ciclo de vida do fragmento. Todos os métodos de retorno de chamada do ciclo de vida são abordados com mais detalhes na seção +Tratamento do ciclo de vida dos fragmentos.

+ + +

Há também algumas subclasses que você pode querer estender, em vez de a classe {@link +android.app.Fragment} de base:

+ +
+
{@link android.app.DialogFragment}
+
Exibe uma caixa de diálogo flutuante. Usar esta classe para criar uma caixa de diálogo é uma boa alternativa +para usar métodos auxiliares das caixas de diálogo na classe {@link android.app.Activity}, +pois é possível incorporar uma caixa de diálogo de fragmento na pilha de retorno dos fragmentos gerenciados pela atividade, +permitindo que o usuário retorne a um fragmento dispensado.
+ +
{@link android.app.ListFragment}
+
Exibe uma lista de itens que são gerenciados por um adaptador (como um {@link +android.widget.SimpleCursorAdapter}), semelhante à {@link android.app.ListActivity}. Ele fornece vários métodos +para gerenciar uma vista de lista, como o retorno de chamada {@link +android.app.ListFragment#onListItemClick(ListView,View,int,long) onListItemClick()} +para lidar com eventos de clique.
+ +
{@link android.preference.PreferenceFragment}
+
Exibe uma hierarquia de objetos {@link android.preference.Preference} como uma lista, +parecido com {@link android.preference.PreferenceActivity}. Isto é útil ao criar uma atividade +de "configurações" para o aplicativo.
+
+ + +

Adição de uma interface do usuário

+ +

Um fragmento é geralmente usado como parte de uma interface do usuário da atividade e contribui +para a atividade com seu próprio layout.

+ +

Para fornecer um layout para um fragmento, você deve implementar o método de retorno de chamada {@link +android.app.Fragment#onCreateView onCreateView()}, que o sistema Android chama +no momento em que o fragmento deve desenhar o layout. A implementação deste método deve retornar +uma {@link android.view.View}, que é a raiz do layout do fragmento.

+ +

Observação: se o fragmento for uma subclasse de {@link +android.app.ListFragment}, a implementação padrão retornará uma {@link android.widget.ListView} +de {@link android.app.Fragment#onCreateView onCreateView()}, então não será necessário implementá-lo.

+ +

Para retornar um layout de {@link +android.app.Fragment#onCreateView onCreateView()}, é possível inflá-lo a partir de um recurso de layout definido no XML. Para ajudar +a fazer isto, o {@link android.app.Fragment#onCreateView onCreateView()} fornece +um objeto {@link android.view.LayoutInflater}.

+ +

Por exemplo, a seguir há uma subclasse de {@link android.app.Fragment} que carrega um layout +do arquivo {@code example_fragment.xml}:

+ +
+public static class ExampleFragment extends Fragment {
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+                             Bundle savedInstanceState) {
+        // Inflate the layout for this fragment
+        return inflater.inflate(R.layout.example_fragment, container, false);
+    }
+}
+
+ + + +

O parâmetro {@code container} passado para {@link android.app.Fragment#onCreateView +onCreateView()} é o pai de {@link android.view.ViewGroup} (do layout da atividade) +em que o layout do fragmento +será inserido. O parâmetro {@code savedInstanceState} é um {@link android.os.Bundle} +que fornece dados sobre a instância anterior do fragmento, se o fragmento estiver sendo retomado +(a restauração de estado é abordada mais detalhadamente na seção Tratamento do ciclo +de vida dos fragmentos).

+ +

O método {@link android.view.LayoutInflater#inflate(int,ViewGroup,boolean) inflate()} +usa três argumentos:

+
    +
  • O ID de recurso do layout que você quer inflar.
  • +
  • O {@link android.view.ViewGroup} que será pai do layout inflado. Passar o {@code +container} é importante para que o sistema aplique os parâmetros de layout à vista raiz +do layout inflado, especificado pela vista pai em que está ocorrendo.
  • +
  • Um booleano que indica se o layout inflado deve ser anexado a {@link +android.view.ViewGroup} (o segundo parâmetro) durante a inflação (neste caso, +isto é falso, pois o sistema já está inserindo o layout inflado no {@code +container} — retornar como verdadeiro criaria um grupo de vistas redundante no layout final).
  • +
+ +

Este é o modo de criar um fragmento que fornece um layout. A seguir, +é preciso adicionar o fragmento à atividade.

+ + + +

Adição de um fragmento a uma atividade

+ +

Geralmente, um fragmento contribui com a atividade do host com uma parte da IU, que é embutida como parte +da hierarquia de vistas geral da atividade. Há duas formas de adicionar um fragmento ao layout +da atividade:

+ +
    +
  • Declarar o fragmento dentro do arquivo de layout da atividade. +

    Neste caso, +é possível especificar as propriedades do layout para o fragmento como se fosse uma vista. Por exemplo, a seguir há o arquivo de layout +para uma atividade com dois fragmentos:

    +
    +<?xml version="1.0" encoding="utf-8"?>
    +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    +    android:orientation="horizontal"
    +    android:layout_width="match_parent"
    +    android:layout_height="match_parent">
    +    <fragment android:name="com.example.news.ArticleListFragment"
    +            android:id="@+id/list"
    +            android:layout_weight="1"
    +            android:layout_width="0dp"
    +            android:layout_height="match_parent" />
    +    <fragment android:name="com.example.news.ArticleReaderFragment"
    +            android:id="@+id/viewer"
    +            android:layout_weight="2"
    +            android:layout_width="0dp"
    +            android:layout_height="match_parent" />
    +</LinearLayout>
    +
    +

    O atributo {@code android:name} em {@code <fragment>} especifica a classe {@link +android.app.Fragment} a instanciar no layout.

    + +

    Quando o sistema cria este layout de atividade, ele instancia cada fragmento especificado +no layout e chama o método {@link android.app.Fragment#onCreateView onCreateView()} para cada um +para recuperar o layout de cada fragmento. O sistema insere a {@link android.view.View} retornada +pelo fragmento diretamente no lugar do elemento {@code <fragment>}.

    + +
    +

    Observação: cada fragmento requer um identificador único +que o sistema possa usar para restaurá-lo se a atividade for reiniciada (e que possa ser usado +para capturar o fragmento para realizar operações, como a remoção). Há três maneiras de fornecer +um ID para um fragmento:

    +
      +
    • Fornecer o atributo {@code android:id} com um ID único.
    • +
    • Fornecer o atributo {@code android:tag} com uma string única.
    • +
    • Caso não forneça nenhuma das alternativas anteriores, o sistema usará o ID da vista +do recipiente.
    • +
    +
    +
  • + +
  • Ou adicionar programaticamente o fragmento a um {@link android.view.ViewGroup} existente. +

    A qualquer momento, enquanto a atividade está em execução, é possível adicionar fragmentos ao layout da atividade. Você precisa +apenas especificar um {@link +android.view.ViewGroup} para posicionar o fragmento.

    +

    Para realizar operações de fragmentos na atividade (como adicionar, remover ou substituir +um fragmento), você deve usar APIs de {@link android.app.FragmentTransaction}. É possível adquirir +uma instância de {@link android.app.FragmentTransaction} da {@link android.app.Activity} desta maneira:

    + +
    +FragmentManager fragmentManager = {@link android.app.Activity#getFragmentManager()}
    +FragmentTransaction fragmentTransaction = fragmentManager.{@link android.app.FragmentManager#beginTransaction()};
    +
    + +

    É possível adicionar um fragmento usando o método {@link +android.app.FragmentTransaction#add(int,Fragment) add()}, especificando o fragmento que será adicionado +e a vista em que será inserido. Por exemplo:

    + +
    +ExampleFragment fragment = new ExampleFragment();
    +fragmentTransaction.add(R.id.fragment_container, fragment);
    +fragmentTransaction.commit();
    +
    + +

    O primeiro argumento passado para {@link android.app.FragmentTransaction#add(int,Fragment) add()} +é {@link android.view.ViewGroup}, onde o fragmento deve ser colocado, especificado +pelo ID de recurso e o segundo parâmetro é o fragmento a ser adicionado.

    +

    Ao realizar as alterações +com {@link android.app.FragmentTransaction}, +deve-se chamar{@link android.app.FragmentTransaction#commit} para que as alterações entrem em vigor.

    +
  • +
+ + +

Adição de um fragmento sem IU

+ +

O exemplo acima mostra como adicionar um fragmento à atividade para fornecer uma IU. No entanto, +é possível também usar um fragmento para fornecer o comportamento de segundo plano para a atividade sem apresentar +IU adicional.

+ +

Para adicionar um fragmento sem uma IU, adicione o fragmento da atividade usando {@link +android.app.FragmentTransaction#add(Fragment,String)} (fornecendo uma "tag" de string única +para o fragmento, em vez de um ID de vista). Isto adiciona o fragmento, mas, como ele não está associado +a uma vista no layout da atividade, não recebe uma chamada de {@link +android.app.Fragment#onCreateView onCreateView()}. Portanto, não é necessário implementar este método.

+ +

Fornecer uma tag de string para o fragmento não é algo estrito para fragmentos que não sejam de IU — é possível também +fornecer tags de string para fragmentos que possuam uma IU — mas, se o fragmento não possuir uma IU, +a tag de string é a única maneira de identificá-lo. Caso queira obter o fragmento da atividade posteriormente, +você precisará usar {@link android.app.FragmentManager#findFragmentByTag +findFragmentByTag()}.

+ +

Para ver uma atividade de exemplo que usa um fragmento como um trabalhador de segundo plano, sem uma IU, consulte o exemplo de {@code +FragmentRetainInstance.java}, incluso nos exemplos do SDK (disponibilizados pelo +Android SDK Manager) e localizado no sistema como +<sdk_root>/APIDemos/app/src/main/java/com/example/android/apis/app/FragmentRetainInstance.java.

+ + + +

Gerenciamento de fragmentos

+ +

Para gerenciar os fragmentos na atividade, você precisa usar {@link android.app.FragmentManager}. Para adquiri-lo, +chame {@link android.app.Activity#getFragmentManager()} a partir da atividade.

+ +

Algumas das coisas que você pode fazer com {@link android.app.FragmentManager} incluem:

+ +
    +
  • Adquirir fragmentos existentes na atividade, com {@link +android.app.FragmentManager#findFragmentById findFragmentById()} (para fragmentos que forneçam uma IU +no layout da atividade) ou {@link android.app.FragmentManager#findFragmentByTag +findFragmentByTag()} (para fragmentos que forneçam ou não uma IU).
  • +
  • Retire os fragmentos da pilha de retorno com {@link +android.app.FragmentManager#popBackStack()} (simulando um comando de Voltar do usuário).
  • +
  • Registre uma escuta para as alterações na pilha de retorno com {@link +android.app.FragmentManager#addOnBackStackChangedListener addOnBackStackChangedListener()}.
  • +
+ +

Para obter mais informações sobre esses e outros métodos, consulte a documentação da classe {@link +android.app.FragmentManager}.

+ +

Como demonstrado na seção anterior, é possível usar o {@link android.app.FragmentManager} +para abrir uma {@link android.app.FragmentTransaction}, que permite realizar operações, +como adicionar e remover fragmentos.

+ + +

Realização de operações com fragmentos

+ +

Um grande recurso fornecido por fragmentos em atividades é a possibilidade de adicionar, remover, substituir +e realizar outras ações com eles em resposta à interação do usuário. Cada conjunto de alterações que forem realizadas +na atividade é chamado de operação e podem ser feitas usando APIs em {@link +android.app.FragmentTransaction}. Também é possível salvar cada operação em uma pilha de retorno gerenciada pela atividade, +permitindo que o usuário navegue inversamente por meio de alterações de fragmento (semelhante à navegação +inversa por meio de atividades).

+ +

É possível adquirir uma instância de {@link android.app.FragmentTransaction} a partir do {@link +android.app.FragmentManager} da seguinte forma:

+ +
+FragmentManager fragmentManager = {@link android.app.Activity#getFragmentManager()};
+FragmentTransaction fragmentTransaction = fragmentManager.{@link android.app.FragmentManager#beginTransaction()};
+
+ +

Cada operação é um conjunto de alterações que você deseja realizar ao mesmo tempo. É possível definir +todas as alterações desejadas para uma operação usando métodos como {@link +android.app.FragmentTransaction#add add()}, {@link android.app.FragmentTransaction#remove remove()} +e {@link android.app.FragmentTransaction#replace replace()}. Em seguida, para aplicar a operação +à atividade, deve-se chamar {@link android.app.FragmentTransaction#commit()}.

+ + +

Antes de chamar {@link +android.app.FragmentTransaction#commit()}, no entanto, você pode querer chamar {@link +android.app.FragmentTransaction#addToBackStack addToBackStack()} para adicionar a operação +a uma pilha de retorno de operações de fragmentos. A pilha de retorno é gerenciada pela atividade +e permite que o usuário retorne ao estado anterior do fragmento, ao pressionar o botão Voltar.

+ +

Por exemplo, a seguir é apresentado o modo de substituir um fragmento por outro e preservar +o estado anterior da pilha de retorno:

+ +
+// Create new fragment and transaction
+Fragment newFragment = new ExampleFragment();
+FragmentTransaction transaction = getFragmentManager().beginTransaction();
+
+// Replace whatever is in the fragment_container view with this fragment,
+// and add the transaction to the back stack
+transaction.replace(R.id.fragment_container, newFragment);
+transaction.addToBackStack(null);
+
+// Commit the transaction
+transaction.commit();
+
+ +

Neste exemplo, {@code newFragment} substitui qualquer fragmento (se houver) que estiver +no recipiente do layout identificado pelo ID {@code R.id.fragment_container}. Ao chamar {@link +android.app.FragmentTransaction#addToBackStack addToBackStack()}, a operação de substituição +é salva na pilha de retorno para que o usuário possa reverter a operação +e voltar ao fragmento anterior pressionando o botão Voltar.

+ +

Se você adicionar várias alterações à operação (como outro {@link +android.app.FragmentTransaction#add add()} ou {@link android.app.FragmentTransaction#remove +remove()}) e chamar {@link +android.app.FragmentTransaction#addToBackStack addToBackStack()}, todas as alterações aplicadas +antes de chamar {@link android.app.FragmentTransaction#commit commit()} serão adicionadas +à pilha de retorno como uma única operação e o botão Voltar reverterá todas elas juntas.

+ +

A ordem em que você adicionar as alterações em {@link android.app.FragmentTransaction} não importa, +exceto que:

+
    +
  • Você deve chamar {@link android.app.FragmentTransaction#commit()} por último
  • +
  • Caso esteja adicionando vários fragmentos ao mesmo recipiente, a ordem em que +adicioná-los determina a ordem em que eles aparecerão na hierarquia de vistas
  • +
+ +

Caso você não chame {@link android.app.FragmentTransaction#addToBackStack(String) +addToBackStack()} ao realizar uma operação que remove um fragmento, este fragmento +será destruído quando a operação for realizada e o usuário não poderá navegar de volta a ele. Considerando que, +se você não chamar {@link android.app.FragmentTransaction#addToBackStack(String) addToBackStack()} +ao remover um fragmento, o fragmento será interrompido e será retomado se o usuário +navegar de volta.

+ +

Dica: para cada operação de fragmento, é possível aplicar uma animação +de transição, chamando {@link android.app.FragmentTransaction#setTransition setTransition()} +antes da realização.

+ +

Chamar {@link android.app.FragmentTransaction#commit()} não realiza a operação +imediatamente. Em vez disso, o parâmetro agenda a execução no encadeamento da IU da atividade (o encadeamento “main”, ou principal) +assim que possível. Se necessário, no entanto, é possível chamar {@link +android.app.FragmentManager#executePendingTransactions()} a partir do encadeamento da IU para executar imediatamente +as operações enviadas por {@link android.app.FragmentTransaction#commit()}. Tal medida, geralmente, +não é necessária, a não ser que a operação represente uma dependência para trabalhos em outros encadeamentos.

+ +

Atenção: É possível realizar uma operação usando {@link +android.app.FragmentTransaction#commit commit()} somente antes da atividade salvar +seu estado (quando o usuário deixa a atividade). Caso tente efetivas as alterações após este ponto, +uma exceção será lançada. Isto acontece porque o estado após a efetivação pode ser perdido se a atividade +precisar ser restaurada. Para situações em que não haja problema em perder a efetivação, use {@link +android.app.FragmentTransaction#commitAllowingStateLoss()}.

+ + + + +

Comunicação com a atividade

+ +

Apesar de {@link android.app.Fragment} ser implementado como um objeto independente +de uma {@link android.app.Activity} e poder ser usado dentro de várias atividades, uma dada instância +de um fragmento é diretamente vinculada à atividade que o contém.

+ +

Especificamente, o fragmento pode acessar a instância {@link android.app.Activity} com {@link +android.app.Fragment#getActivity()} e realizar facilmente tarefas como encontrar uma vista +no layout da atividade:

+ +
+View listView = {@link android.app.Fragment#getActivity()}.{@link android.app.Activity#findViewById findViewById}(R.id.list);
+
+ +

Do mesmo modo, a atividade pode chamar métodos no fragmento adquirindo uma referência +para o {@link android.app.Fragment} a partir do {@link android.app.FragmentManager} usando {@link +android.app.FragmentManager#findFragmentById findFragmentById()} ou {@link +android.app.FragmentManager#findFragmentByTag findFragmentByTag()}. Por exemplo:

+ +
+ExampleFragment fragment = (ExampleFragment) getFragmentManager().findFragmentById(R.id.example_fragment);
+
+ + +

Criação de retornos de chamada de evento para a atividade

+ +

Em alguns casos, um fragmento que compartilhe eventos com a atividade pode ser necessário. Uma boa maneira de fazer isto +é definir uma interface de retorno de chamada dentro do fragmento e solicitar que a atividade do host +implemente-a. Quando a atividade recebe um retorno de chamada por meio da interface, ela pode compartilhar as informações +com outros fragmentos no layout conforme necessário.

+ +

Por exemplo, se um aplicativo de notícias em uma atividade tiver dois fragmentos — um para exibir uma lista +de artigos (fragmento A) e outro para exibir um artigo (fragmento B) — então o fragmento A +deve informar à atividade quando um item de lista é selecionado para que ela possa instruir o fragmento B a exibir o artigo. Neste caso, + a interface {@code OnArticleSelectedListener} é declarada dentro do fragmento A:

+ +
+public static class FragmentA extends ListFragment {
+    ...
+    // Container Activity must implement this interface
+    public interface OnArticleSelectedListener {
+        public void onArticleSelected(Uri articleUri);
+    }
+    ...
+}
+
+ +

Portanto, a atividade que hospeda o fragmento implementa a interface {@code OnArticleSelectedListener} +e suspende +{@code onArticleSelected()} para notificar o fragmento B do evento do fragmento A. Para garantir +que a atividade do host implemente esta interface, o método de retorno de chamada de {@link +android.app.Fragment#onAttach onAttach()} do fragmento A (que o sistema chama ao adicionar +o fragmento à atividade) instanciará {@code OnArticleSelectedListener} +lançando a {@link android.app.Activity}, que é passada para {@link android.app.Fragment#onAttach +onAttach()}:

+ +
+public static class FragmentA extends ListFragment {
+    OnArticleSelectedListener mListener;
+    ...
+    @Override
+    public void onAttach(Activity activity) {
+        super.onAttach(activity);
+        try {
+            mListener = (OnArticleSelectedListener) activity;
+        } catch (ClassCastException e) {
+            throw new ClassCastException(activity.toString() + " must implement OnArticleSelectedListener");
+        }
+    }
+    ...
+}
+
+ +

Se a atividade não implementar a interface, o fragmento lançará +{@link java.lang.ClassCastException}. +Se for bem-sucedida, o membro {@code mListener} reterá uma referência da implementação da atividade +de {@code OnArticleSelectedListener}, para que o fragmento A possa compartilhar os eventos com a atividade +chamando métodos definidos pela interface {@code OnArticleSelectedListener}. Por exemplo, se o fragmento A +for uma extensão de {@link android.app.ListFragment}, sempre +que o usuário clicar em um item de lista, o sistema chamará {@link android.app.ListFragment#onListItemClick +onListItemClick()} no fragmento que, em seguida, chamará {@code onArticleSelected()} para compartilhar +o evento com a atividade:

+ +
+public static class FragmentA extends ListFragment {
+    OnArticleSelectedListener mListener;
+    ...
+    @Override
+    public void onListItemClick(ListView l, View v, int position, long id) {
+        // Append the clicked item's row ID with the content provider Uri
+        Uri noteUri = ContentUris.{@link android.content.ContentUris#withAppendedId withAppendedId}(ArticleColumns.CONTENT_URI, id);
+        // Send the event and Uri to the host activity
+        mListener.onArticleSelected(noteUri);
+    }
+    ...
+}
+
+ +

O parâmetro {@code id} passado para {@link +android.app.ListFragment#onListItemClick onListItemClick()} é o ID da linha do item clicado +que a atividade (ou outro fragmento) usa para resgatar o artigo do {@link +android.content.ContentProvider} do aplicativo.

+ +

Mais informações sobre +como usar o provedor de conteúdo estão disponíveis na documentação Provedores de conteúdo.

+ + + +

Adição de itens à barra de ação

+ +

Os fragmentos podem contribuir com itens de menu para o menu de opções da atividade (e, consequentemente, para a barra de ação) implementando +{@link android.app.Fragment#onCreateOptionsMenu(Menu,MenuInflater) onCreateOptionsMenu()}. Para que este método +receba chamadas, no entanto, você deve chamar {@link +android.app.Fragment#setHasOptionsMenu(boolean) setHasOptionsMenu()} durante {@link +android.app.Fragment#onCreate(Bundle) onCreate()} para indicar que o fragmento +gostaria de adicionar itens ao menu de opções (caso contrário, o fragmento não receberá uma chamada +para {@link android.app.Fragment#onCreateOptionsMenu onCreateOptionsMenu()}).

+ +

Quaisquer itens adicionados ao menu de opções do fragmento são anexados +aos itens de menu existentes. O fragmento também recebe retornos de chamada para {@link +android.app.Fragment#onOptionsItemSelected(MenuItem) onOptionsItemSelected()} quando um item de menu +é selecionado.

+ +

Também é possível registrar uma vista no layout do fragmento para fornecer um menu de contexto chamando {@link +android.app.Fragment#registerForContextMenu(View) registerForContextMenu()}. Quando o usuário +abre o menu de contexto, o fragmento recebe uma chamada de {@link +android.app.Fragment#onCreateContextMenu(ContextMenu,View,ContextMenu.ContextMenuInfo) +onCreateContextMenu()}. Quando o usuário seleciona um item, o fragmento recebe uma chamada de {@link +android.app.Fragment#onContextItemSelected(MenuItem) onContextItemSelected()}.

+ +

Observação: apesar de o fragmento receber um retorno de chamada selecionado no item +para cada item de menu que adiciona, a atividade é a primeira a receber o respectivo retorno de chamada quando o usuário +seleciona um item de menu. Se a implementação da atividade do retorno de chamada selecionado no item não +lida com o item selecionado, o evento é passado para o retorno de chamada do fragmento. Isto é verdadeiro +para o menu de opções e os menus de contexto.

+ +

Para obter mais informações sobre menus, consulte os guias do desenvolvedor Menus e Barra de ação.

+ + + + +

Tratamento do ciclo de vida dos fragmentos

+ +
+ +

Figura 3. O efeito do ciclo de vida da atividade no ciclo de vida +do fragmento.

+
+ +

Gerenciar o ciclo de vida de um fragmento é muito parecido com gerenciar o ciclo de vida de uma atividade. Como uma atividade, +um fragmento pode existir em três estados:

+ +
+
Retomado
+
O fragmento é visível na atividade em execução.
+ +
Pausado
+
Outra atividade está em primeiro plano, mas a atividade em que este fragmento +vive ainda está visível (a atividade de primeiro plano é parcialmente transparente +ou não cobre a tela inteira).
+ +
Interrompido
+
O fragmento não é visível. A atividade do host foi interrompida +ou o fragmento foi removido da atividade mas adicionado à pilha de retorno. Um fragmento interrompido +ainda está vivo (todas as informações do membro e de estado estão retidas no sistema). No entanto, +não está mais visível e será eliminado se a atividade também for.
+
+ +

Além disso, como uma atividade, é possível reter o estado de um fragmento usando um {@link +android.os.Bundle}, caso o processo da atividade seja eliminado e você precise restaurar +o estado do fragmento quando a atividade for recriada. É possível salvar o estado durante o retorno de chamada {@link +android.app.Fragment#onSaveInstanceState onSaveInstanceState()} do fragmento e restaurá-lo +durante {@link android.app.Fragment#onCreate onCreate()}, {@link +android.app.Fragment#onCreateView onCreateView()} ou {@link +android.app.Fragment#onActivityCreated onActivityCreated()}. Para obter mais informações sobre +como salvar o estado, consulte a documentação Atividades +.

+ +

A diferença mais significante entre o ciclo de vida de uma atividade e de um fragmento +é o armazenamento em suas respectivas pilhas de retorno. Por padrão, uma atividade é colocada de volta na pilha de retorno de atividades, +que é gerenciada pelo sistema, quando interrompida (para que o usuário possa navegar +a ela com o botão Voltar, como discutido em Tarefas e pilha de retorno). +No entanto, um fragmento é posicionado em uma pilha de retorno gerenciada pela atividade do host somente +ao solicitar explicitamente que a instância seja salva chamando {@link +android.app.FragmentTransaction#addToBackStack(String) addToBackStack()} durante uma operação +que remove o fragmento.

+ +

Caso contrário, gerenciar o ciclo de vida do fragmento é muito semelhante a gerenciar +o ciclo de vida da atividade. Portanto, as mesmas práticas para gerenciar o ciclo de vida +da atividade aplicam-se aos fragmentos. O que você também precisa entender, no entanto, é como a vida +da atividade afeta a vida do fragmento.

+ +

Atenção: caso precise de um objeto {@link android.content.Context} +dentro do {@link android.app.Fragment}, é possível chamar {@link android.app.Fragment#getActivity()}. +No entanto, tome cuidado para chamar {@link android.app.Fragment#getActivity()} somente quando o fragmento +estiver anexado a uma atividade. Quando o fragmento ainda não estiver anexado, ou tiver sido desvinculado durante o fim +do seu ciclo de vida, {@link android.app.Fragment#getActivity()} retornará como nulo.

+ + +

Coordenação do ciclo de vida da atividade

+ +

O ciclo de vida da atividade em que o fragmento vive afeta diretamente o ciclo de vida +do fragmento, da mesma forma que um retorno de chamada de cada ciclo de vida da atividade resulta em um retorno de chamada semelhante +de cada fragmento. Por exemplo, quando a atividade receber {@link android.app.Activity#onPause}, +cada fragmento na atividade receberá {@link android.app.Fragment#onPause}.

+ +

Os fragmentos têm alguns retornos de chamada do ciclo de vida extras. No entanto, isto trata da interação única +com a atividade para realizar ações como compilar e destruir a IU do fragmento. Esses +métodos adicionais de retorno de chamada são:

+ +
+
{@link android.app.Fragment#onAttach onAttach()}
+
Chamado quando o fragmento tiver sido associado à atividade ({@link +android.app.Activity} é passado aqui).
+
{@link android.app.Fragment#onCreateView onCreateView()}
+
Chamado para criar a hierarquia de vistas associada ao fragmento.
+
{@link android.app.Fragment#onActivityCreated onActivityCreated()}
+
Chamado quando o método {@link android.app.Activity#onCreate +onCreate()} da atividade tiver retornado.
+
{@link android.app.Fragment#onDestroyView onDestroyView()}
+
Chamado quando a hierarquia de vistas associada ao fragmento estiver sendo removida.
+
{@link android.app.Fragment#onDetach onDetach()}
+
Chamado quando o fragmento estiver sendo desassociado da atividade.
+
+ +

O fluxo do ciclo de vida do fragmento, afetado pela atividade do host, como ilustrado +pela figura 3. Nesta figura, é possível ver como cada estado sucessivo da atividade determina +qual método de retorno de chamada um fragmento pode receber. Por exemplo, quando a atividade recebe o retorno de chamada {@link +android.app.Activity#onCreate onCreate()}, um fragmento na atividade recebe nada mais +do que o retorno de chamada {@link android.app.Fragment#onActivityCreated onActivityCreated()}.

+ +

Quando a atividade atinge o estado retomado, é possível adicionar e remover fragmentos +dela livremente. Portanto, somente quando a atividade estiver no estado retomado, o ciclo de vida +de um fragmento poderá alterar-se de forma independente.

+ +

No entanto, quando a atividade deixa o estado retomado, o fragmento é novamente forçado +a passar pelo ciclo de vida pela atividade.

+ + + + +

Exemplo

+ +

Para juntar tudo que foi discutido neste documento, abaixo há um exemplo de uma atividade +que usa dois fragmentos para criar um layout de dois painéis. A atividade abaixo inclui um fragmento +para exibir uma lista de títulos de peças de Shakespeare e outra para exibir um resumo da peça, +quando selecionada na lista. Ela também determina como fornecer diferentes configurações de fragmentos, +com base na configuração da tela.

+ +

Observação: O código fonte completo desta atividade está disponível em +{@code +FragmentLayout.java}.

+ +

A atividade principal aplica-se de maneira comum, durante {@link +android.app.Activity#onCreate onCreate()}:

+ +{@sample development/samples/ApiDemos/src/com/example/android/apis/app/FragmentLayout.java main} + +

O layout aplicado é {@code fragment_layout.xml}:

+ +{@sample development/samples/ApiDemos/res/layout-land/fragment_layout.xml layout} + +

Usando este layout, o sistema instancia {@code TitlesFragment} (que lista os títulos +das peças) assim que a atividade carrega o layout, enquanto que {@link android.widget.FrameLayout} +(onde o fragmento para exibir o resumo da peça aparecerá) consome o espaço do lado direito da tela, + mas primeiramente permanece vazio. Como verá abaixo, ele não estará visível até que o usuário selecione um item +na lista em que um fragmento esteja posicionado no {@link android.widget.FrameLayout}.

+ +

No entanto, nem todas as configurações de tela são grandes o suficiente para exibir a lista +de peças e o resumo, lado a lado. Portanto, o layout acima é usado somente para configuração +de tela de paisagem, salvando-o em {@code res/layout-land/fragment_layout.xml}.

+ +

Por isso, quando a tela está na orientação de retrato, o sistema aplica o seguinte layout, +que é salvo em {@code res/layout/fragment_layout.xml}:

+ +{@sample development/samples/ApiDemos/res/layout/fragment_layout.xml layout} + +

Este layout inclui somente {@code TitlesFragment}. Isto significa que, quando o dispositivo +está na orientação de retrato, somente a lista de título das peças está disponível. Portanto, quando o usuário clicar +em um item da lista nesta configuração, o aplicativo iniciará uma nova atividade para exibir o resumo, +em vez de carregar um segundo fragmento.

+ +

A seguir, é possível ver como isto é realizado com classes de fragmento. Primeiro, {@code +TitlesFragment}, que exibe a lista de peças de Shakespeare. Este fragmento estende {@link +android.app.ListFragment} e confia nele para lidar com grande parte do trabalho da vista de lista.

+ +

Enquanto inspeciona este código, observe que há dois possíveis comportamentos quando um usuário +clica em um item de lista: dependendo de qual dos dois layouts está ativo, ele pode criar e exibir +um novo fragmento para exibir os detalhes na mesma atividade (adicionando o fragmento {@link +android.widget.FrameLayout}), ou iniciar uma nova atividade (onde o fragmento possa ser exibido).

+ +{@sample development/samples/ApiDemos/src/com/example/android/apis/app/FragmentLayout.java titles} + +

O segundo fragmento, {@code DetailsFragment}, exibe o resumo da peça para o item selecionado +na lista de {@code TitlesFragment}:

+ +{@sample development/samples/ApiDemos/src/com/example/android/apis/app/FragmentLayout.java details} + +

Uma nova chamada da classe {@code TitlesFragment}, ou seja, se o usuário clicar em um item de lista +e o layout original não incluir a vista {@code R.id.details} (a que +{@code DetailsFragment} pertence), o aplicativo iniciará a atividade {@code DetailsActivity} +para exibir o conteúdo do item.

+ +

A seguir há {@code DetailsActivity}, que simplesmente embute {@code DetailsFragment} para exibir +o resumo da peça selecionada quando a tela está na orientação de retrato:

+ +{@sample development/samples/ApiDemos/src/com/example/android/apis/app/FragmentLayout.java +details_activity} + +

Observe que esta atividade finaliza-se se a configuração for de paisagem, +pois a atividade principal pode tomar o controle e exibir {@code DetailsFragment} juntamente com {@code TitlesFragment}. +Isto pode acontecer se o usuário iniciar {@code DetailsActivity} enquanto estiver na orientação de retrato, +mas alternar para orientação de paisagem (o que reinicia a atividade atual).

+ + +

Para obter mais exemplos do uso de fragmentos (e arquivos fonte completos deste exemplo), +consulte o aplicativo de exemplo API Demos disponível em +ApiDemos (disponível para download em Exemplos de componentes do SDK).

+ + diff --git a/docs/html-intl/intl/pt-br/guide/components/fundamentals.jd b/docs/html-intl/intl/pt-br/guide/components/fundamentals.jd new file mode 100644 index 0000000000000000000000000000000000000000..47b98458e6429755bb32db773853014f557382c2 --- /dev/null +++ b/docs/html-intl/intl/pt-br/guide/components/fundamentals.jd @@ -0,0 +1,480 @@ +page.title=Fundamentos de aplicativos +@jd:body + + + +

Os aplicativos do Android são programados em linguagem de programação Java. As ferramentas Android SDK compilam +o código — em conjunto com todos os arquivos de dados e recursos — em um APK, que é o pacote do Android, +um arquivo de arquivamento com sufixo {@code .apk}. Os arquivos de APK contêm todo o conteúdo +de um aplicativo do Android e são os arquivos que os dispositivos desenvolvidos para Android usam para instalar o aplicativo.

+ +

Depois de instalado em um dispositivo, cada aplicativo do Android é ativado em sua própria área de segurança:

+ +
    +
  • O sistema operacional Android é um sistema Linux multiusuário em que cada aplicativo +é um usuário diferente.
  • + +
  • Por padrão, o sistema atribui a cada aplicativo um ID de usuário do Linux exclusivo (o ID é usado somente +pelo sistema e é desconhecido para o aplicativo). O sistema define permissões para todos os arquivos +em um aplicativo, de modo que somente o ID de usuário atribuído àquele aplicativo pode acessá-los.
  • + +
  • Cada processo tem sua própria máquina virtual (VM), portanto o código de um aplicativo é executado isoladamente +de outros aplicativos.
  • + +
  • Por padrão, cada aplicativo é executado em seu próprio processo Linux. O Android inicia o processo quando é preciso +executar algum componente do aplicativo; em seguida, encerra-o quando não mais é +necessário ou quando o sistema precisa recuperar memória para outros aplicativos.
  • +
+ +

Assim, o sistema Android implementa o princípio do privilégio mínimo. Ou seja, +cada aplicativo, por padrão, tem acesso somente aos componentes necessários para a execução do seu trabalho +e nada mais. Isso cria um ambiente muito seguro em que o aplicativo não pode acessar partes +do sistema para o qual não tem permissão.

+ +

No entanto, sempre existe uma maneira de um aplicativo compartilhar dados com outros aplicativos +e acessar serviços do sistema:

+ +
    +
  • É impossível fazer com que dois aplicativos compartilhem o mesmo ID de usuário do Linux, caso em que +eles são capazes de acessar os arquivos um do outro. Para preservar os recursos do sistema, os aplicativos com o mesmo +ID de usuário também podem ser combinados para serem executados no mesmo processo Linux e compartilhar a mesma VM +(também é preciso atribuir o mesmo certificado aos aplicativos).
  • +
  • Um aplicativo pode solicitar permissão para acessar dados de dispositivo como +contatos do usuário, mensagens SMS, o sistema montável (cartão SD), câmera, Bluetooth etc. Todas +as permissões de aplicativo devem ser concedidas pelo usuário no momento da instalação.
  • +
+ +

Essas são as informações básicas de como um aplicativo do Android existe dentro do sistema. O restante +deste documento apresenta o leitor a:

+
    +
  • Componentes fundamentais de estrutura que definem o aplicativo.
  • +
  • O arquivo de manifesto em que os componentes são declarados e os recursos de dispositivo necessários +ao aplicativo.
  • +
  • Recursos separados do código do aplicativo que permitem +otimizar o comportamento de uma variedade de configurações de dispositivo.
  • +
+ + + +

Componentes de aplicativo

+ +

Os componentes de aplicativo são os blocos de construção fundamentais de um aplicativo do Android. +Cada componente é um ponto diferente pelo qual o sistema pode entrar no aplicativo. Nem todos +os componentes são pontos de entrada reais para o usuário e alguns dependem uns dos outros, mas cada um existe +como uma entidade independente e desempenha uma função específica — cada um é um bloco de construção exclusivo +que ajuda a definir o comportamento geral do aplicativo.

+ +

Há quatro tipos diferentes de componentes de aplicativo. Cada tipo tem uma finalidade distinta +e tem um ciclo de vida específico que define a forma pela qual o componente é criado e destruído.

+ +

A seguir apresentam-se os quatro tipos de componentes de aplicativos:

+ +
+ +
Atividades
+ +
As atividades representam uma tela única com uma interface do usuário. Por exemplo: +um aplicativo de e-mails pode ter uma atividade que mostra uma lista de novos +e-mails, outra atividade que compõe um e-mail e outra ainda que lê e-mails. Embora +essas atividades funcionem juntas para formar uma experiência de usuário coesa no aplicativo de e-mails, +elas são independentes entre si. Portanto, um aplicativo diferente pode iniciar qualquer uma +dessas atividades (se o aplicativo de e-mails permitir). Por exemplo: um aplicativo de câmera pode iniciar +a atividade no aplicativo de e-mail que compõe o novo e-mail para que o usuário compartilhe uma foto. + +

Uma atividade é implementada como uma subclasse de {@link android.app.Activity} — saiba mais sobre isso +no guia do desenvolvedor +Atividades.

+
+ + +
Serviços
+ +
Os serviços são componentes executados em segundo plano para realizar operações +de execução longa ou para realizar trabalho para processos remotos. Eles +não apresentam uma interface do usuário. Por exemplo: um serviço pode tocar música em segundo plano +enquanto o usuário está em um aplicativo diferente ou buscar dados na rede sem bloquear +a interação do usuário com uma atividade. Outro componente, como uma atividade, pode iniciar +o serviço e deixá-lo executar ou vincular-se a ele para interagir. + +

Um serviço é implementado como uma subclasse de {@link android.app.Service} — saiba mais sobre isso +no guia do desenvolvedor +Serviços.

+
+ + +
Provedores de conteúdo
+ +
Os provedores de conteúdo gerenciam um conjunto compartilhado de dados do aplicativo. É possível armazenar os dados +no sistema de arquivos, em um banco de dados SQLite ou em qualquer local de armazenamento persistente +que o aplicativo possa acessar. Por meio do provedor de conteúdo, outros aplicativos podem consultar ou até modificar +os dados (se o provedor de conteúdo permitir). Por exemplo: o sistema Android oferece um provedor +de conteúdo que gerencia as informações de contato do usuário. Assim, qualquer aplicativo +com as permissões adequadas pode consultar parte do provedor de conteúdo (como {@link +android.provider.ContactsContract.Data}) para ler e gravar informações sobre uma pessoa específica. + +

Os provedores de conteúdo são úteis para ler e gravar dados privados +no aplicativo e não compartilhados. Por exemplo: o exemplo de aplicativo Note Pad usa +um provedor de conteúdo para salvar notas.

+ +

Um provedor de conteúdo é implementado como uma subclasse de {@link android.content.ContentProvider} +e precisa implementar um conjunto padrão de APIs que permitem a outros aplicativos +realizar transações. Para obter mais informações, consulte o guia de desenvolvedor +Provedores de conteúdo.

+
+ + +
Receptores de transmissão
+ +
Os receptores de transmissão são componentes que respondem a anúncios de transmissão +por todo o sistema. Muitas transmissões se originam do sistema — por exemplo, uma transmissão que anuncia +que uma tela foi desligada, a bateria está baixa ou uma tela foi capturada. +Os aplicativos também podem iniciar transmissões — por exemplo, para comunicar a outros dispositivos +que alguns dados foram baixados no dispositivo e estão disponíveis para uso. Embora os receptores +de transmissão não exibam nenhuma interface do usuário, eles podem criar uma notificação na barra de status +para alertar ao usuário quando ocorre uma transmissão. Mais comumente, no entanto, um receptor de transmissão +é somente um "portal" para outros componentes e realiza uma quantidade mínima de trabalho. Por +exemplo: ele pode iniciar um serviço para executar um trabalho baseado no evento. + +

Os receptores de transmissão são implementados como subclasses de {@link android.content.BroadcastReceiver} +e cada transmissão é entregue como um objeto {@link android.content.Intent}. Para obter mais informações, +consulte a classe {@link android.content.BroadcastReceiver}.

+
+ +
+ + + +

Um aspecto exclusivo do projeto do sistema Android é que qualquer aplicativo pode iniciar +um componente de outro aplicativo. Por exemplo: se você quiser que o usuário capture +uma foto com a câmera do dispositivo, provavelmente haverá outro aplicativo que faz isso +e o seu aplicativo poderá usá-lo, ou seja, não será necessário desenvolver uma atividade para capturar uma foto. Não é +necessário incorporar nem mesmo vinculá-lo do aplicativo da câmera ao código. +Em vez disso, basta iniciar a atividade no aplicativo da câmera que captura +uma foto. Quando concluída, a foto até retorna ao aplicativo em questão para ser usada. Para o usuário, +parece que a câmera é realmente parte do aplicativo.

+ +

Quando o sistema inicia um componente, ele inicia o processo daquele aplicativo (se ele já +não estiver em execução) e instancia as classes necessárias para o componente. Por exemplo: se o aplicativo +iniciar a atividade no aplicativo da câmera que captura uma foto, aquele aplicativo +é executado no processo que pertence ao aplicativo da câmera e não no processo do aplicativo. +Portanto, ao contrário dos aplicativos na maioria dos outros sistemas, os aplicativos do Android não têm um ponto +de entrada único (não há a função {@code main()}, por exemplo).

+ +

Como o sistema executa cada aplicativo em um processo separado com permissões de arquivo +que restringem o acesso a outros aplicativos, o aplicativo não pode ativar diretamente um componente +a partir de outro aplicativo. No entanto, o sistema Android pode. Portanto, para ativar um componente +em outro aplicativo, é preciso enviar uma mensagem ao sistema que especifique a intenção +de iniciar um componente específico. Em seguida, o sistema ativa o componente.

+ + +

Ativação de componentes

+ +

Três dos quatro tipos de componente — atividades, serviços +e receptores de transmissão — são ativados por uma mensagem assíncrona chamada intenção. +As intenções vinculam componentes individuais entre si em tempo de execução (como +mensageiros que solicitam uma ação de outros componentes), seja o componente pertencente +ao aplicativo ou não.

+ +

A intenção é criada com um objeto {@link android.content.Intent}, que define uma mensagem +para ativar um componente específico ou um tipo específico de componente — as intenções +podem ser explícitas ou implícitas, respectivamente.

+ +

Para atividades e serviços, as intenções definem a ação a executar (por exemplo, "exibir" ou +"enviar" algo) e podem especificar a URI dos dados usados na ação (entre outras coisas +que o componente a iniciar precisa saber). Por exemplo: uma intenção pode transmitir uma solicitação +de uma atividade para exibir uma imagem ou abrir uma página da web. Em alguns casos, é preciso iniciar +uma atividade para receber um resultado; nesse caso, a atividade também retorna +o resultado em um {@link android.content.Intent} (por exemplo, é possível emitir uma intenção para +que o usuário selecione um contato pessoal e o retorne a você — a intenção de retorno contém +uma URI que aponta para o contato selecionado).

+ +

Para receptores de transmissão, a intenção simplesmente define +o anúncio que está sendo transmitido (por exemplo, uma transmissão para indicar que a bateria do dispositivo está acabando +contém uma string de ação conhecida que indica "nível baixo da bateria").

+ +

O outro tipo de componente, o provedor de conteúdo, não é ativado por intenções. Em vez disso, +ele é ativado por uma solicitação de um {@link android.content.ContentResolver}. O resolvedor +de conteúdo manipula todas as transações diretas com o provedor de conteúdo para que o componente +que executa as transações com o provedor não precise e, em vez disso, chama os métodos no objeto {@link +android.content.ContentResolver}. Isso deixa uma camada de abstração entre o provedor +de conteúdo e o componente que solicita informações (por segurança).

+ +

Há dois métodos separados para ativar cada tipo de componente:

+
    +
  • É possível iniciar uma atividade (ou dar-lhe algo novo para fazer) +passando uma {@link android.content.Intent} a {@link android.content.Context#startActivity +startActivity()} ou {@link android.app.Activity#startActivityForResult startActivityForResult()} +(para que, quando desejado, a atividade retorne um resultado).
  • +
  • É possível iniciar um dispositivo (ou dar novas instruções a um serviço em andamento) +passando uma {@link android.content.Intent} a {@link android.content.Context#startService +startService()}. Ou então, é possível vincular ao serviço passando uma{@link android.content.Intent} +a {@link android.content.Context#bindService bindService()}.
  • +
  • É possível iniciar uma transmissão passando uma{@link android.content.Intent} a métodos como +{@link android.content.Context#sendBroadcast(Intent) sendBroadcast()}, {@link +android.content.Context#sendOrderedBroadcast(Intent, String) sendOrderedBroadcast()} ou {@link +android.content.Context#sendStickyBroadcast sendStickyBroadcast()}.
  • +
  • É possível iniciar uma consulta a um provedor de conteúdo chamando {@link +android.content.ContentProvider#query query()} em um {@link android.content.ContentResolver}.
  • +
+ +

Para obter mais informações sobre intenções, consulte o documento +Intenções e filtros de intenções. Veja mais informações sobre a ativação de componentes específicos +nos seguintes documentos: Atividades, Serviços, {@link +android.content.BroadcastReceiver} e Provedores de conteúdo.

+ + +

O arquivo de manifesto

+ +

Antes de o sistema Android iniciar um componente de aplicativo, é preciso ler o arquivo {@code AndroidManifest.xml} +(o arquivo de "manifesto") +do aplicativo para que o sistema saiba se o componente existe. O aplicativo precisa declarar todos os seus componentes nesse arquivo, que deve estar na raiz +do diretório do projeto do aplicativo.

+ +

O manifesto faz outras coisas além de declarar os componentes do aplicativo, +por exemplo:

+
    +
  • Identifica todas as permissões do usuário de que o aplicativo precisa, como acesso à internet +ou acesso de somente leitura aos contatos do usuário.
  • +
  • Declara o nível de API mínimo +exigido pelo aplicativo com base nas APIs que o aplicativo usa.
  • +
  • Declara os recursos de hardware e software usados ou exigidos pelo aplicativo, como câmera, +serviços de bluetooth ou tela multitoque.
  • +
  • As bibliotecas de API às quais o aplicativo precisa vincular-se (outras além das APIs +de estrutura do Android), como a biblioteca +Google Maps.
  • +
  • E outros.
  • +
+ + +

Declaração de componentes

+ +

A principal tarefa do manifesto é informar ao sistema os componentes do aplicativo. Por +exemplo: um arquivo de manifesto pode declarar uma atividade da seguinte forma:

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<manifest ... >
+    <application android:icon="@drawable/app_icon.png" ... >
+        <activity android:name="com.example.project.ExampleActivity"
+                  android:label="@string/example_label" ... >
+        </activity>
+        ...
+    </application>
+</manifest>
+ +

No elemento <application> +, o atributo {@code android:icon} aponta para recursos de um ícone que identifica +o aplicativo.

+ +

No elemento <activity>, +o atributo {@code android:name} especifica o nome da classe totalmente qualificada da subclasse de {@link +android.app.Activity} e o atributo {@code android:label} especifica uma string +a usar como a etiqueta da atividade visível ao usuário.

+ +

É preciso declarar todos os componentes desta forma:

+ + +

Atividades, serviços e provedores de conteúdo incluídos na fonte, mas não declarados +no manifesto, não ficam visíveis para o sistema e, consequentemente, podem não ser executados. No entanto, receptores +de transmissão +podem ser declarados no manifesto dinamicamente no código (como objetos +{@link android.content.BroadcastReceiver}) e registrados no sistema chamando-se +{@link android.content.Context#registerReceiver registerReceiver()}.

+ +

Para obter mais informações sobre a estrutura do arquivo de manifesto de aplicativos, consulte a documentação +O arquivo AndroidManifest.xml.

+ + + +

Declaração de recursos de componentes

+ +

Conforme abordado acima, em Ativação de componentes, é possível usar +uma {@link android.content.Intent} para iniciar atividades, serviços e receptores de transmissão. Isso pode ser feito +nomeando-se explicitamente o componente-alvo (usando o nome da classe do componente) na intenção. No entanto, +a verdadeira força das intenções reside no conceito de intenções implícitas. As intenções implícitas +descrevem simplesmente o tipo de ação a executar (e, opcionalmente, os dados em que +a ação deve ser executada) e permitem ao sistema encontrar e iniciar um componente no dispositivo que pode executar +a ação. Se houver mais de um componente que possa executar a ação descrita +pela intenção, o usuário selecionará qual deles usar.

+ +

Para o sistema identificar os componentes que podem responder a uma intenção, compara-se +a intenção recebida com os filtros de intenções fornecidos no arquivo de manifesto de outros aplicativos +do dispositivo.

+ +

Ao declarar uma atividade no manifesto do aplicativo, pode-se incluir +filtros de intenções que declarem os recursos da atividade para que ela responda +a intenções de outros aplicativos. Para declarar um filtro de intenções no componentes, +adiciona-se um elemento {@code +<intent-filter>} como filho do elemento de declaração do componente.

+ +

Por exemplo: se você estiver programando um aplicativo de e-mail com uma atividade de compor um novo e-mail, +é possível declarar um filtro de intenções para responder a intenções "enviar" (para enviar um novo e-mail), assim:

+
+<manifest ... >
+    ...
+    <application ... >
+        <activity android:name="com.example.project.ComposeEmailActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.SEND" />
+                <data android:type="*/*" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </activity>
+    </application>
+</manifest>
+
+ +

Em seguida, se outro aplicativo criar uma intenção com a ação {@link +android.content.Intent#ACTION_SEND} e passá-la para {@link android.app.Activity#startActivity +startActivity()}, o sistema poderá iniciar a atividade de forma que o usuário possa rascunhar e enviar +um e-mail.

+ +

Para obter mais informações sobre filtros de intenções, consulte o documento Intenções e filtros de intenções. +

+ + + +

Declaração de requisitos do aplicativo

+ +

Existem vários dispositivos desenvolvidos para Android e nem todos apresentam os mesmos +recursos e características. Para evitar que o aplicativo seja instalado em dispositivos +que não contenham os recursos que o aplicativo necessita, é importante definir um perfil +para os tipos de dispositivo compatíveis com o aplicativo. É preciso declarar os requisitos de dispositivo e software +no arquivo de manifesto. A maior parte dessas declarações é somente informativa e o sistema não as lê, +mas serviços externos, como o Google Play, as leem para oferecer uma filtragem +aos usuários quando buscam esses aplicativos para seu dispositivo.

+ +

Por exemplo: se o aplicativo exige uma câmera e usa APIs introduzidas no Android 2.1 (API de nível 7), +deve-se declarar esses requisitos no arquivo de manifesto da seguinte forma:

+ +
+<manifest ... >
+    <uses-feature android:name="android.hardware.camera.any"
+                  android:required="true" />
+    <uses-sdk android:minSdkVersion="7" android:targetSdkVersion="19" />
+    ...
+</manifest>
+
+ +

Assim, dispositivos que não tenham câmera e tenham +versão Android anterior a 2.1 não poderão instalar o aplicativo a partir do Google Play.

+ +

No entanto, também é possível declarar que o aplicativo usa a câmera como recurso +não obrigatório. Nesse caso, o aplicativo precisa definir o atributo {@code required} + como {@code "false"} e verificar em tempo de execução +se o dispositivo tem câmera e desativar os recursos da câmera conforme o necessário.

+ +

Veja mais informações sobre o gerenciamento da compatibilidade do aplicativo com diferentes dispositivos +no documento Compatibilidade +do dispositivo.

+ + + +

Recursos do aplicativo

+ +

Os aplicativos Android são compostos por mais do que somente códigos — eles requerem recursos +separados do código-fonte, como imagens, arquivos de áudio e tudo o que se relaciona +com a apresentação visual do aplicativo. Por exemplo: deve-se definir animações, menus, estilos, cores +e o layout das interfaces do usuário da atividade com arquivos XML. O uso de recursos de aplicativo facilita +a atualização de diversas características do aplicativo sem a necessidade de modificar +o código e — fornecendo conjuntos de recursos alternativos — permite otimizar o aplicativo +para diversas configurações de dispositivo (como idiomas e tamanhos de tela diferentes).

+ +

Para todo recurso incluído no projeto Android, as ferramentas de programação SDK definem +um ID inteiro exclusivo que o programador pode usar para referenciar o recurso do código do aplicativo +ou de outros recursos definidos no XML. Por exemplo: se o aplicativo contiver um arquivo de imagem de nome {@code +logo.png} (salvo no diretório {@code res/drawable/}), as ferramentas de SDK gerarão um ID de recurso +chamado {@code R.drawable.logo}, que pode ser usado para referenciar a imagem e inseri-la +na interface do usuário.

+ +

Um dos aspectos mais importantes de fornecer recursos separados do código-fonte +é a capacidade de fornecer recursos alternativos para diferentes configurações +de dispositivo. Por exemplo: ao definir strings de IU em XML, é possível converter as strings em outros +idiomas e salvá-las em arquivos separados. Em seguida, com base em um qualificador de idioma +acrescentado ao nome do diretório do recurso (como {@code res/values-fr/} para valores +de string em francês) e a configuração de idioma do usuário, o sistema Android aplica as strings de idioma adequadas +à IU.

+ +

O Android aceita vários qualificadores para recursos alternativos. O qualificador +é uma string curta incluída no nome dos diretórios de recurso para definir +a configuração de dispositivo em que esses recursos serão usados. Outro exemplo: +deve-se criar diferentes layouts para as atividades conforme a orientação +e o tamanho da tela do dispositivo. Por exemplo: quando a tela do dispositivo está em orientação +retrato (vertical), pode ser desejável um layout com botões na vertical, mas, quando a tela está em orientação +paisagem (horizontal), os botões devem estar alinhados horizontalmente. Para alterar o layout +conforme a orientação, pode-se definir dois layouts diferentes e aplicar o qualificador +adequado ao nome do diretório de cada layout. Em seguida, o sistema aplica automaticamente o layout adequado +conforme a orientação atual do dispositivo.

+ +

Para obter mais informações sobre os diferentes tipos de recursos a incluir no aplicativo +e como criar recursos alternativos para diferentes configurações de dispositivo, leia Como fornecer recursos.

+ + + +
+
+

Continue lendo sobre:

+
+
Intenções e filtros de intenções +
+
Informações sobre o uso de APIs {@link android.content.Intent} para +ativar componentes de aplicativos, como atividades e serviços, e como disponibilizar componentes +de aplicativo para uso em outros aplicativos.
+
Atividades
+
Informações sobre a criação de uma instância da classe {@link android.app.Activity}, +que permite uma tela diferente no aplicativo com uma interface do usuário.
+
Como fornecer recursos
+
Informações sobre a estrutura de aplicativos Android para recursos de aplicativo separados do código-fonte +, inclusive como fornecer recursos alternativos para configurações +de dispositivo específicas. +
+
+
+
+

Você também pode se interessar por:

+
+
Compatibilidade do dispositivo
+
Informações sobre como o Android funciona em diferentes tipos de dispositivo e uma introdução +a como otimizar o aplicativo para cada dispositivo ou restringir a disponibilidade +a diferentes dispositivos.
+
Permissões do sistema
+
Informações sobre como o aplicativo restringe o acesso do Android a determinadas APIs sem um +sistema de permissão que exija o consentimento do usuário para que o aplicativo use essas APIs.
+
+
+
+ diff --git a/docs/html-intl/intl/pt-br/guide/components/index.jd b/docs/html-intl/intl/pt-br/guide/components/index.jd new file mode 100644 index 0000000000000000000000000000000000000000..02fcaa63c63a78d5b3982a429e71ee11f6037561 --- /dev/null +++ b/docs/html-intl/intl/pt-br/guide/components/index.jd @@ -0,0 +1,57 @@ +page.title=Componentes do aplicativo +page.landing=true +page.landing.intro=A estrutura de aplicativo do Android permite criar aplicativos ricos e inovadores usando um conjunto de componentes reutilizáveis. Esta seção explica como criar os componentes que definem os blocos de construção do aplicativo e como conectá-los usando intenções. +page.metaDescription=A estrutura de aplicativo do Android permite criar aplicativos ricos e inovadores usando um conjunto de componentes reutilizáveis. Esta seção mostra como criar os componentes que definem os blocos de construção do aplicativo e como conectá-los usando intenções. +page.landing.image=images/develop/app_components.png +page.image=images/develop/app_components.png + +@jd:body + +
+ + + + + +
diff --git a/docs/html-intl/intl/pt-br/guide/components/intents-filters.jd b/docs/html-intl/intl/pt-br/guide/components/intents-filters.jd new file mode 100644 index 0000000000000000000000000000000000000000..80e3f08d375d40e3e9504d897fb16182ac2984d5 --- /dev/null +++ b/docs/html-intl/intl/pt-br/guide/components/intents-filters.jd @@ -0,0 +1,899 @@ +page.title=Intenções e filtros de intenções +page.tags="IntentFilter" +@jd:body + + + + + + +

A {@link android.content.Intent} é um objeto de mensagem que pode ser usado para solicitar uma ação +de outro componente de aplicativo. +Embora as intenções facilitem a comunicação entre componentes de diversos modos, há três +casos de uso fundamentais:

+ +
    +
  • Para iniciar uma atividade: +

    A {@link android.app.Activity} representa uma única tela em um aplicativo. É possível iniciar uma nova +instância de uma {@link android.app.Activity} passando uma {@link android.content.Intent} +a {@link android.content.Context#startActivity startActivity()}. A {@link android.content.Intent} +descreve a atividade a iniciar e carrega todos os dados necessários.

    + +

    Se você deseja receber um resultado da atividade quando ela finalizar, +chame {@link android.app.Activity#startActivityForResult +startActivityForResult()}. Sua atividade recebe o resultado +como um objeto {@link android.content.Intent} separado no retorno de chamada de {@link +android.app.Activity#onActivityResult onActivityResult()} da atividade. +Para obter mais informações, consulte o guia Atividades.

  • + +
  • Para iniciar um serviço: +

    O {@link android.app.Service} é um componente que realiza operações em segundo plano +sem interface de usuário. É possível iniciar um serviço para realizar uma operação que acontece uma vez +(como baixar um arquivo) passando uma {@link android.content.Intent} +a {@link android.content.Context#startService startService()}. A {@link android.content.Intent} +descreve o serviço a iniciar e carrega todos os dados necessários.

    + +

    Se o serviço for projetado com uma interface servidor-cliente, é possível vincular ao serviço +a partir de outro componente passando uma {@link android.content.Intent} a {@link +android.content.Context#bindService bindService()}. Para obter mais informações, consulte o guia Serviços.

  • + +
  • Para fornecer uma transmissão: +

    Transmissão é uma mensagem que qualquer aplicativo pode receber. O sistema fornece diversas +transmissões para eventos do sistema, como quando o sistema inicializa ou o dispositivo inicia o carregamento. +Você pode fornecer uma transmissão a outros aplicativos passando uma {@link android.content.Intent} +a {@link android.content.Context#sendBroadcast(Intent) sendBroadcast()}, +{@link android.content.Context#sendOrderedBroadcast(Intent, String) +sendOrderedBroadcast()} ou {@link +android.content.Context#sendStickyBroadcast sendStickyBroadcast()}.

    +
  • +
+ + + + +

Tipos de intenções

+ +

Há dois tipos de intenções:

+ +
    +
  • As intenções explícitas especificam o componente a iniciar pelo nome +(o nome de classe totalmente qualificado). Normalmente, usa-se uma intenção explícita para iniciar um componente +no próprio aplicativo porque se sabe o nome de classe da atividade ou serviço que se deseja iniciar. +Por exemplo, iniciar uma nova atividade em resposta a uma ação do usuário ou iniciar um serviço para baixar +um arquivo em segundo plano.
  • + +
  • As intenções implícitas não nomeiam nenhum componente específico, mas declaram uma ação geral +a realizar, o que permite que um componente de outro aplicativo a trate. Por exemplo: se você deseja +exibir ao usuário uma localização em um mapa, pode usar uma intenção implícita para solicitar que outro aplicativo +capaz exiba uma localização especificada no mapa.
  • +
+ +

Ao criar uma intenção explícita para iniciar uma atividade ou serviço, o sistema inicia imediatamente +o componente do aplicativo especificado no objeto {@link android.content.Intent} .

+ +
+ +

Figura 1. Ilustração de como uma intenção implícita +é fornecida pelo sistema para iniciar outra atividade: [1] A atividade A cria uma +{@link android.content.Intent} com uma descrição de ação e passa-a para {@link +android.content.Context#startActivity startActivity()}. [2] O sistema Android busca, em todos +os aplicativos, um filtro de intenções que corresponda à intenção. Ao encontrar uma correspondência, [3] o sistema +inicia a atividade correspondente (atividade B) chamando seu método {@link +android.app.Activity#onCreate onCreate()} e passando a {@link android.content.Intent} para ele. +

+
+ +

Ao criar uma intenção implícita, o sistema Android encontra o componente adequado para iniciar, +comparando o conteúdo da intenção aos filtros de intenções declarados no arquivo de manifesto de outros aplicativos +no dispositivo. Se a intenção corresponder a um filtro de intenções, o sistema iniciará esse componente e entregará +o objeto {@link android.content.Intent}. Se diversos filtros de intenções corresponderem, o sistema +exibirá uma caixa de diálogo para que o usuário selecione o aplicativo que deseja usar.

+ +

O filtro de intenções é uma expressão em um arquivo de manifesto do aplicativo +que especifica o tipo de intenções que o componente +gostaria de receber. Por exemplo, ao declarar um filtro de intenções para uma atividade, +outros aplicativos se tornam capazes de iniciar diretamente sua atividade com o determinado tipo de intenção. +Do mesmo modo, se você não declarar nenhum filtro de intenções para uma atividade, ela poderá ser iniciada +somente com uma intenção explícita.

+ +

Atenção: para garantir a segurança do seu aplicativo, sempre use uma intenção +explícita ao iniciar um {@link android.app.Service} e não +declare filtros de intenções para os serviços. O uso de uma intenção implícita para iniciar um serviço representa +um risco de segurança porque não é possível determinar qual serviço responderá à intenção +e o usuário não poderá ver que serviço é iniciado. A partir do Android 5.0 (API de nível 21), o sistema +lança uma exceção ao chamar {@link android.content.Context#bindService bindService()} +com uma intenção implícita.

+ + + + + +

Criação de uma intenção

+ +

Um objeto {@link android.content.Intent} carrega informações que o sistema Android usa +para determinar o componente a iniciar (como o nome exato do componente ou categoria +do componente que deve receber a intenção), além de informações que o componente receptor usa para +realizar a ação adequadamente (como a ação a tomar e os dados a usar).

+ + +

As informações principais contidas em uma {@link android.content.Intent} são as seguintes:

+ +
+ +
Nome do componente
+
O nome do componente a iniciar. + +

É opcional, mas é a informação fundamental que torna uma intenção +explícita, o que significa que a intenção deve ser entregue somente ao componente do aplicativo +definido pelo nome do componente. Sem nome de componente, a intenção será implícita +e o sistema decidirá qual componente deve receber a intenção, com base nas informações de outra intenção +(como a ação, os dados e a categoria — descritos abaixo). Portanto, se for necessário iniciar +um componente específico no seu aplicativo, deve-se especificar o nome do componente.

+ +

Observação: ao iniciar um {@link android.app.Service}, deve-se +sempre especificar o nome do componente. Caso contrário, não será possível determinar qual serviço +responderá à intenção e o usuário não poderá ver que serviço é iniciado.

+ +

Esse campo da {@link android.content.Intent} é um +objeto {@link android.content.ComponentName} que pode ser especificado usando um nome +de classe totalmente qualificado do componente-alvo, inclusive o nome do pacote do aplicativo. Por exemplo, +{@code com.example.ExampleActivity}. É possível definir o nome do componente com {@link +android.content.Intent#setComponent setComponent()}, {@link android.content.Intent#setClass +setClass()}, {@link android.content.Intent#setClassName(String, String) setClassName()} ou com +o construtor de {@link android.content.Intent}.

+ +
+ +

Ação
+
String que especifica a ação genérica a realizar (como exibir ou selecionar). + +

No caso de uma intenção de transmissão, essa é a ação que entrou em vigor e que está sendo relatada. +A ação determina amplamente como o resto da intenção é estruturado — especificamente, +o que está contido nos dados e em extras. + +

É possível especificar as ações para uso por intenções dentro do aplicativo (ou para uso por outros +aplicativos para chamar componentes no seu aplicativo), mas normalmente usam-se constantes de ação +definidas pela classe {@link android.content.Intent} ou por outras classes de estrutura. A seguir há algumas +ações comuns para iniciar uma atividade:

+ +
+
{@link android.content.Intent#ACTION_VIEW}
+
Use essa ação em uma intenção com {@link + android.content.Context#startActivity startActivity()} quando houver informações que + uma atividade possa exibir ao usuário, como uma foto para exibição em um aplicativo de galeria ou um endereço + para exibição em um aplicativo de mapa.
+ +
{@link android.content.Intent#ACTION_SEND}
+
Também conhecida como a intenção de "compartilhamento", ela deve ser usada em uma intenção com {@link + android.content.Context#startActivity startActivity()} quando houver alguns dados que o usuário possa + compartilhar por meio de outro aplicativo, como um aplicativo de e-mail ou de compartilhamento social.
+
+ +

Consulte a referência da classe {@link android.content.Intent} para obter mais +constantes que definem ações genéricas. Outras ações são definidas +em outros locais na estrutura do Android, como nas {@link android.provider.Settings} para ações +que abrem telas específicas no aplicativo de Configurações do sistema.

+ +

É possível especificar a ação para uma intenção com {@link android.content.Intent#setAction +setAction()} ou com um construtor de {@link android.content.Intent}.

+ +

Se você definir as próprias ações, certifique-se de incluir o nome do pacote do seu aplicativo +como prefixo. Por exemplo:

+
static final String ACTION_TIMETRAVEL = "com.example.action.TIMETRAVEL";
+
+ +
Dados
+
A URI (um objeto {@link android.net.Uri}) que referencia os dados a serem aproveitados e/ou o +tipo MIME desses dados. O tipo dos dados fornecidos geralmente é determinado pela ação da intenção. +Por exemplo: se a ação for {@link android.content.Intent#ACTION_EDIT}, os dados devem conter +a URI do documento a editar. + +

Ao criar uma intenção, +em geral, é importante especificar o tipo de dados (seu tipo MIME) em adição à URI. +Por exemplo: uma atividade capaz de exibir imagens provavelmente não será capaz +de reproduzir um arquivo de áudio, mesmo que os formatos da URI sejam similares. +Portanto, especificar o tipo MIME dos dados ajuda o sistema +Android a encontrar o melhor componente para receber a intenção. +Contudo, o tipo MIME, às vezes, pode ser inferido a partir da URI — especificamente quando os dados são +uma URI de {@code content:}, indicando que os dados se localizam no dispositivo e são controlados +por um {@link android.content.ContentProvider}, que torna o tipo MIME dos dados visíveis para o sistema.

+ +

Para definir somente a URI de dados, chame {@link android.content.Intent#setData setData()}. +Para definir somente o tipo MIME, chame {@link android.content.Intent#setType setType()}. Se necessário, +é possível definir ambos explicitamente com {@link +android.content.Intent#setDataAndType setDataAndType()}.

+ +

Atenção: se você deseja definir a URI e o tipo MIME, +não chame {@link android.content.Intent#setData setData()} +e {@link android.content.Intent#setType setType()}, pois eles anulam seus valores mutuamente. +Sempre use {@link android.content.Intent#setDataAndType setDataAndType()} para definir +a URI e o tipo MIME juntos.

+
+ +

Categoria
+
String que contém informações adicionais sobre o tipo de componente +que deve tratar da intenção. Qualquer número de descrições de categoria pode ser +inserido em uma intenção, mas a maioria das intenções não requer nenhuma categoria. +A seguir há algumas categorias comuns: + +
+
{@link android.content.Intent#CATEGORY_BROWSABLE}
+
A atividade-alvo permite seu início por um navegador da web para exibir dados + referenciados por um link — como uma imagem ou uma mensagem de e-mail. +
+
{@link android.content.Intent#CATEGORY_LAUNCHER}
+
A atividade é a atividade inicial de uma tarefa e é listada + no inicializador do aplicativo do sistema. +
+
+ +

Consulte a descrição da classe {@link android.content.Intent} para obter a lista completa +de categorias.

+ +

É possível especificar uma categoria com {@link android.content.Intent#addCategory addCategory()}.

+
+
+ + +

As propriedades listadas abaixo (nome do componente, ação, dados e categoria) representam +as características de definição de uma intenção. Ao ler estas propriedades, o sistema Android +será capaz de definir o componente de aplicativo que ele deve iniciar.

+ +

Contudo, uma intenção pode carregar informações adicionais que não afetam +o modo com que é tratada para um componente do aplicativo. As intenções também podem fornecer:

+ +
+
Extras
+
Pares de valores-chave que carregam informações adicionais exigidas para realizar a ação solicitada. +Assim, como algumas ações usam determinados tipos de URIs de dados, outras também usam determinados extras. + +

É possível adicionar dados extras com diversos métodos {@link android.content.Intent#putExtra putExtra()}, +cada um aceitando dois parâmetros: o nome principal e o valor. +Também é possível criar um objeto {@link android.os.Bundle} com todos os dados extras e, em seguida, inserir +o {@link android.os.Bundle} na {@link android.content.Intent} com {@link +android.content.Intent#putExtras putExtras()}.

+ +

Por exemplo: ao criar uma intenção para enviar um e-mail com +{@link android.content.Intent#ACTION_SEND}, é possível especificar o recipiente "para" com +a chave {@link android.content.Intent#EXTRA_EMAIL} e especificar o "assunto" com +a chave {@link android.content.Intent#EXTRA_SUBJECT}.

+ +

A classe {@link android.content.Intent} especifica diversas constantes {@code EXTRA_*} +para tipos de dados padronizados. Se for necessário declarar chaves extras (para intenções que +seu aplicativo receba), certifique-se de incluir o nome do pacote do aplicativo +como prefixo. Por exemplo:

+
static final String EXTRA_GIGAWATTS = "com.example.EXTRA_GIGAWATTS";
+
+ +
Sinalizadores
+
Sinalizadores definidos na classe {@link android.content.Intent} que funcionam como metadados +para a intenção. Os sinalizadores podem instruir o sistema Android sobre como inicializar uma atividade (por exemplo, a qual +tarefa a atividade deve +pertencer) e como tratá-la após sua inicialização (por exemplo, se ela pertencer a uma lista de atividades +recentes). + +

Para obter mais informações, consulte o método {@link android.content.Intent#setFlags setFlags()}.

+
+ +
+ + + + +

Exemplo de intenção explícita

+ +

A intenção explícita é usada para inicializar um componente específico de um aplicativo, como +uma atividade ou serviço em particular, no seu aplicativo. Para criar uma intenção explícita, defina +o nome do componente para o objeto {@link android.content.Intent} — todas +as outras propriedades da intenção são opcionais.

+ +

Por exemplo: se você criar um serviço no aplicativo, chamado {@code DownloadService} e +projetado para baixar um arquivo da web, poderá iniciá-lo com o código a seguir:

+ +
+// Executed in an Activity, so 'this' is the {@link android.content.Context}
+// The fileUrl is a string URL, such as "http://www.example.com/image.png"
+Intent downloadIntent = new Intent(this, DownloadService.class);
+downloadIntent.setData({@link android.net.Uri#parse Uri.parse}(fileUrl));
+startService(downloadIntent);
+
+ +

O construtor {@link android.content.Intent#Intent(Context,Class)} + fornece {@link android.content.Context} ao aplicativo +e um objeto {@link java.lang.Class} ao componente. Assim, +essa intenção inicia explicitamente a classe {@code DownloadService} no aplicativo.

+ +

Para obter mais informações sobre a criação e inicialização de um serviço, consulte +o guia Serviços.

+ + + + +

Exemplo de intenção implícita

+ +

A intenção implícita especifica uma ação que possa chamar qualquer aplicativo no dispositivo capaz +de realizar a ação. A intenção implícita é útil quando o aplicativo não pode realizar +a ação, mas outros aplicativos provavelmente podem e o usuário seleciona que aplicativo usar.

+ +

Por exemplo: se você tem o conteúdo que deseja que o usuário compartilhe com outras pessoas, crie uma intenção +com a ação {@link android.content.Intent#ACTION_SEND} +e adicione extras que especifiquem o conteúdo a compartilhar. Ao chamar +{@link android.content.Context#startActivity startActivity()} com esta intenção, o usuário poderá +selecionar um aplicativo pelo qual deseja compartilhar o conteúdo.

+ +

Atenção: é possível que um usuário não tenha nenhum +aplicativo que trate da intenção implícita enviada a {@link android.content.Context#startActivity +startActivity()}. Se isso acontecer, a chamada e seu aplicativo falharão. Para verificar +se uma atividade receberá a intenção, chame {@link android.content.Intent#resolveActivity +resolveActivity()} no objeto {@link android.content.Intent}. Se o resultado não for nulo, +há pelo menos um aplicativo que pode tratar da intenção e será seguro chamar +{@link android.content.Context#startActivity startActivity()}. Se o resultado for nulo, +você não deve usar a intenção e, se possível, deve desativar o recurso que emite +a intenção.

+ + +
+// Create the text message with a string
+Intent sendIntent = new Intent();
+sendIntent.setAction(Intent.ACTION_SEND);
+sendIntent.putExtra(Intent.EXTRA_TEXT, textMessage);
+sendIntent.setType("text/plain");
+
+// Verify that the intent will resolve to an activity
+if (sendIntent.resolveActivity(getPackageManager()) != null) {
+    startActivity(sendIntent);
+}
+
+ +

Observação: nesse caso, não será usada nenhuma URI, mas o tipo de dados da intenção +será declarado para especificar o conteúdo carregado pelos extras.

+ + +

Quando {@link android.content.Context#startActivity startActivity()} é chamada, o sistema +avalia todos os aplicativos instalados para determinar quais deles podem tratar esse tipo de intenção +(uma intenção com a ação {@link android.content.Intent#ACTION_SEND} e que carrega dados +de "texto/simples". Se houver somente um aplicativo que possa tratá-la, o aplicativo abre imediatamente e recebe +a intenção. Se diversas atividades aceitarem a intenção, o sistema +exibirá uma caixa de diálogo para que o usuário selecione que aplicativo usar.

+ + +
+ +

Figura 2. Caixa de diálogo seletora.

+
+ +

Forçar um seletor de aplicativo

+ +

Quando há mais de um aplicativo que responde à intenção implícita, +o usuário pode selecionar o aplicativo que deseja usar e tornar este aplicativo a escolha padrão para +a ação. Isso é positivo ao executar uma ação para a qual o usuário +deseja usar o mesmo aplicativo todas as vezes, como quando abre uma página da web (os usuários +geralmente usam apenas um navegador).

+ +

Contudo, se diversos aplicativos podem responder à intenção e o usuário deve ficar livre para usar um aplicativo +diferente em cada vez, é preciso exibir uma caixa de diálogo seletora explicitamente. A caixa de diálogo seletora pede que +o usuário selecione o aplicativo desejado para a ação todas as vezes (o usuário não pode selecionar um aplicativo padrão para +a ação). Por exemplo: quando o aplicativo realiza "compartilhar" com a ação {@link +android.content.Intent#ACTION_SEND}, os usuários podem querer compartilhar usando um aplicativo diferente +conforme a situação, portanto deve-se sempre usar a caixa de diálogo seletora, como ilustrado na figura 2.

+ + + + +

Para exibir o seletor, crie uma {@link android.content.Intent} usando {@link +android.content.Intent#createChooser createChooser()} e passe-a para {@link +android.app.Activity#startActivity startActivity()}. Por exemplo:

+ +
+Intent sendIntent = new Intent(Intent.ACTION_SEND);
+...
+
+// Always use string resources for UI text.
+// This says something like "Share this photo with"
+String title = getResources().getString(R.string.chooser_title);
+// Create intent to show the chooser dialog
+Intent chooser = Intent.createChooser(sendIntent, title);
+
+// Verify the original intent will resolve to at least one activity
+if (sendIntent.resolveActivity(getPackageManager()) != null) {
+    startActivity(chooser);
+}
+
+ +

Isso exibe uma caixa de diálogo com uma lista de aplicativos que respondem à intenção transmitida ao método {@link +android.content.Intent#createChooser createChooser()} e utiliza o texto fornecido como +título da caixa de diálogo.

+ + + + + + + + + +

Recepção de uma intenção implícita

+ +

Para anunciar quais intenções implícitas o aplicativo pode receber, declare um ou mais filtros de intenções +para cada um dos componentes do aplicativo com um elemento +{@code <intent-filter>} no arquivo de manifesto. +Cada filtro de intenções especifica o tipo de intenções aceito com base na ação +nos dados e na categoria da intenção. O sistema fornecerá uma intenção implícita ao componente do seu aplicativo somente se ela +puder passar por um dos filtros de intenções.

+ +

Observação: a intenção explícita é sempre entregue ao alvo +independentemente dos filtros de intenções que o componente declare.

+ +

Os componentes de um aplicativo devem declarar filtros separados para cada trabalho exclusivo que podem fazer. +Por exemplo, uma atividade em um aplicativo de galeria de imagens pode ter dois filtros: um filtro +para visualizar uma imagem e outro para editar uma imagem. Quando a atividade inicia, +ela inspeciona a {@link android.content.Intent} e decide como se comportar com base nas informações +na {@link android.content.Intent} (como para exibir ou não os controles do editor).

+ +

Cada filtro de intenções é definido por um elemento {@code <intent-filter>} +no arquivo de manifesto do aplicativo, aninhado no componente correspondente do aplicativo (como +um elemento +{@code <activity>}). Dentro de {@code <intent-filter>}, +é possível especificar o tipo de intenções aceitas usando um ou mais +dos três elementos a seguir:

+ +
+
{@code <action>}
+
Declara a ação da intenção aceita, no atributo {@code name}. O valor + deve ser o valor literal da string de uma ação, e não a constante da classe.
+
{@code <data>}
+
Declara o tipo de dados aceitos usando um ou mais atributos que especificam diversos + aspectos da URI de dados (scheme, host, port, +path etc.) e do tipo MIME.
+
{@code <category>}
+
Declara a categoria da intenção aceita, no atributo {@code name}. O valor + deve ser o valor literal da string de uma ação, e não a constante da classe. + +

Observação: para receber intenções implícitas, + é preciso incluir a + categoria {@link android.content.Intent#CATEGORY_DEFAULT} no filtro de intenções. Os métodos +{@link android.app.Activity#startActivity startActivity()} e +{@link android.app.Activity#startActivityForResult startActivityForResult()} tratam de todas as intenções + como se eles declarassem a categoria {@link android.content.Intent#CATEGORY_DEFAULT}. + Se você não a declarar no filtro de intenções, nenhuma intenção implícita retomará + a sua atividade.

+
+
+ +

Por exemplo, abaixo há uma declaração de atividade com um filtro de intenções para receber +uma intenção {@link android.content.Intent#ACTION_SEND} quando o tipo de dados for texto:

+ +
+<activity android:name="ShareActivity">
+    <intent-filter>
+        <action android:name="android.intent.action.SEND"/>
+        <category android:name="android.intent.category.DEFAULT"/>
+        <data android:mimeType="text/plain"/>
+    </intent-filter>
+</activity>
+
+ +

Não há problemas em criar um filtro que inclua mais de uma instância de +{@code <action>}, +{@code <data>} ou +{@code <category>}. +Se você o fizer, basta certificar-se de que o componente possa tratar todas e quaisquer combinações +daqueles elementos do filtro.

+ +

Para tratar de diversos tipos de intenções, mas somente em combinações específicas +de ações, dados e tipos de categoria, será necessário criar diversos filtros de intenções.

+ + + + +

As intenções implícitas são testadas em relação a um filtro por meio da comparação da intenção com cada um +dos três elementos. Para ser entregue ao componente, a intenção deve passar por todos os três testes. +Se ela falhar em algum deles, o sistema Android não entregará a intenção +ao componente. No entanto, como um componente poder ter diversos filtros de intenções, uma intenção que não +passe por um dos filtros de um componente pode passar por outro filtro. +Veja mais informações sobre como o sistema resolve intenções na seção abaixo +sobre Resolução de intenções.

+ +

Atenção: para evitar a execução involuntária de um {@link android.app.Service} +diferente do aplicativo, sempre use uma intenção explícita para iniciar o próprio serviço +e não declare filtros de intenções para ele.

+ +

Observação: +para todas as atividades, é necessário declarar os filtros de intenções no arquivo de manifesto. +Contudo, os filtros para receptores de transmissão podem ser registrados dinamicamente chamando +{@link android.content.Context#registerReceiver(BroadcastReceiver, IntentFilter, String, +Handler) registerReceiver()}. Assim, será possível cancelar o registro do receptor com {@link +android.content.Context#unregisterReceiver unregisterReceiver()}. Isso permitirá que o aplicativo +receba transmissões específicas durante um período de tempo especificado apenas quando o aplicativo +estiver em execução.

+ + + + + + + +

Exemplos de filtros

+ +

Para compreender melhor alguns dos comportamentos do filtro de intenções, veja o fragmento a seguir +do arquivo de manifesto de um aplicativo de compartilhamento social.

+ +
+<activity android:name="MainActivity">
+    <!-- This activity is the main entry, should appear in app launcher -->
+    <intent-filter>
+        <action android:name="android.intent.action.MAIN" />
+        <category android:name="android.intent.category.LAUNCHER" />
+    </intent-filter>
+</activity>
+
+<activity android:name="ShareActivity">
+    <!-- This activity handles "SEND" actions with text data -->
+    <intent-filter>
+        <action android:name="android.intent.action.SEND"/>
+        <category android:name="android.intent.category.DEFAULT"/>
+        <data android:mimeType="text/plain"/>
+    </intent-filter>
+    <!-- This activity also handles "SEND" and "SEND_MULTIPLE" with media data -->
+    <intent-filter>
+        <action android:name="android.intent.action.SEND"/>
+        <action android:name="android.intent.action.SEND_MULTIPLE"/>
+        <category android:name="android.intent.category.DEFAULT"/>
+        <data android:mimeType="application/vnd.google.panorama360+jpg"/>
+        <data android:mimeType="image/*"/>
+        <data android:mimeType="video/*"/>
+    </intent-filter>
+</activity>
+
+ +

A primeira atividade, {@code MainActivity}, é o ponto de entrada principal do aplicativo — a atividade +que abre quando o usuário inicializa o aplicativo pela primeira vez com o ícone de inicialização:

+
    +
  • A ação {@link android.content.Intent#ACTION_MAIN} + indica que este é o ponto de entrada principal e não espera nenhum dado de intenção.
  • +
  • A categoria {@link android.content.Intent#CATEGORY_LAUNCHER} indica que esse ícone + da atividade deve ser colocado no inicializador de aplicativo do sistema. Se o elemento {@code <activity>} + não especificar um ícone com {@code icon}, o sistema usará o ícone do elemento + {@code <application>}.
  • +
+

Esses dois devem ser pareados juntos para que a atividade apareça no inicializador do aplicativo.

+ +

A segunda atividade, {@code ShareActivity}, destina-se a facilitar o compartilhamento de conteúdo de texto +e mídia. Apesar de os usuários poderem acessar essa atividade pela {@code MainActivity}, +eles também podem acessar {@code ShareActivity} diretamente de outro aplicativo que emita uma intenção +implícita que corresponda a um dos dois filtros de intenções.

+ +

Observação: o tipo MIME, +{@code +application/vnd.google.panorama360+jpg}, é um tipo de dados especial que especifica +fotos panorâmicas que podem ser tratadas com as APIs do Google +Panorama.

+ + + + + + + + + + + + + +

Uso de uma intenção pendente

+ +

Um objeto {@link android.app.PendingIntent} é um agrupador em torno de um objeto {@link +android.content.Intent}. A principal finalidade de uma {@link android.app.PendingIntent} + é conceder permissão a um aplicativo externo +para usar a {@link android.content.Intent} contida como se ela fosse executada a partir +do processo do próprio aplicativo.

+ +

Os principais casos de uso de uma intenção pendente são:

+
    +
  • Declarar uma intenção a ser executada quando o usuário realiza uma ação com a Notificação + (o {@link android.app.NotificationManager} do sistema Android + executa a {@link android.content.Intent}). +
  • Declarar uma intenção a ser executada quando o usuário realiza uma ação com o + Widget do aplicativo + (o aplicativo de tela inicial executa a {@link android.content.Intent}). +
  • Declarar uma intenção a ser executada em um momento específico no futuro (o {@link android.app.AlarmManager} + do sistema Android executa a {@link android.content.Intent}). +
+ +

Como cada objeto {@link android.content.Intent} é projetado para ser tratado por um tipo +específico de componentes do aplicativo (uma {@link android.app.Activity}, um {@link android.app.Service} +ou um {@link android.content.BroadcastReceiver}), uma {@link android.app.PendingIntent} +deve ser criada com a mesma consideração. Ao usar uma intenção pendente, o aplicativo +não executará a intenção com uma chamada como de {@link android.content.Context#startActivity +startActivity()}. Em vez disso, deve-se declarar o tipo do componente pretendido ao criar +a {@link android.app.PendingIntent} chamando o respectivo método criador:

+ +
    +
  • {@link android.app.PendingIntent#getActivity PendingIntent.getActivity()} para uma + {@link android.content.Intent} que inicia uma {@link android.app.Activity}.
  • +
  • {@link android.app.PendingIntent#getService PendingIntent.getService()} para uma + {@link android.content.Intent} que inicia um {@link android.app.Service}.
  • +
  • {@link android.app.PendingIntent#getBroadcast PendingIntent.getBroadcast()} para uma + {@link android.content.Intent} que inicia um {@link android.content.BroadcastReceiver}.
  • +
+ +

A menos que o aplicativo esteja recebendo intenções pendentes de outros aplicativos, +os métodos acima para criar uma {@link android.app.PendingIntent} são provavelmente +os únicos métodos de {@link android.app.PendingIntent} necessários.

+ +

Cada método toma o {@link android.content.Context} do aplicativo atual, +a {@link android.content.Intent} que você deseja agrupar e um ou mais sinalizadores que especificam +como a intenção deve ser usada (como se a intenção pudesse ser usada mais de uma vez).

+ +

Veja mais informações sobre o uso de intenções pendentes na documentação +dos respectivos casos de uso, como nos guias das APIs de Notificações +e Widget do aplicativo.

+ + + + + + + +

Resolução de intenções

+ + +

Quando o sistema recebe uma intenção implícita para iniciar uma atividade, ele busca +as melhores atividades para a intenção comparando-a com os filtros de intenções com base em três aspectos:

+ +
    +
  • A ação da intenção +
  • Os dados da intenção (URI e tipo de dados) +
  • A categoria da intenção +
+ +

As seções a seguir descrevem como uma intenção é combinada com os componentes apropriados +em termos de como o filtro de intenções é declarado no arquivo de manifesto de um aplicativo.

+ + +

Teste de ação

+ +

Para especificar ações de intenções aceitas, um filtro de intenções pode declarar zero ou mais +elementos {@code +<action>}. Por exemplo:

+ +
+<intent-filter>
+    <action android:name="android.intent.action.EDIT" />
+    <action android:name="android.intent.action.VIEW" />
+    ...
+</intent-filter>
+
+ +

Para passar por este filtro, a ação especificada na {@link android.content.Intent} + deve corresponder a uma das ações listadas no filtro.

+ +

Se o filtro não listar nenhuma ação, não há nada a que +uma intenção corresponda, portanto todas as intenções falharão no teste. Contudo, se uma {@link android.content.Intent} +não especificar nenhuma ação, ela passará no teste (desde que o filtro +contenha pelo menos uma ação).

+ + + +

Teste de categoria

+ +

Para especificar as categorias de intenção aceitas, um filtro de intenções pode declarar zero ou mais +elementos {@code +<category>}. Por exemplo:

+ +
+<intent-filter>
+    <category android:name="android.intent.category.DEFAULT" />
+    <category android:name="android.intent.category.BROWSABLE" />
+    ...
+</intent-filter>
+
+ +

Para que uma intenção passe no teste de categoria, cada categoria na {@link android.content.Intent} +deve corresponder a uma categoria no filtro. O inverso não é necessário — o filtro de intenções pode +declarar mais categorias das especificadas na {@link android.content.Intent} e +a {@link android.content.Intent} ainda passará no teste. Portanto, uma intenção sem categorias sempre +passará nesse teste independentemente das categorias declaradas no filtro.

+ +

Observação: +O Android aplica automaticamente a categoria {@link android.content.Intent#CATEGORY_DEFAULT} +para todas as intenções implícitas passadas a {@link +android.content.Context#startActivity startActivity()} e {@link +android.app.Activity#startActivityForResult startActivityForResult()}. +Por isso, se você deseja que a atividade receba intenções implícitas, ela deve +conter uma categoria de {@code "android.intent.category.DEFAULT"} nos filtros de intenções (como +exibido no exemplo de {@code <intent-filter>} anterior).

+ + + +

Teste de dados

+ +

Para especificar dados de intenções aceitas, um filtro de intenções pode declarar zero ou mais +elementos {@code +<data>}. Por exemplo:

+ +
+<intent-filter>
+    <data android:mimeType="video/mpeg" android:scheme="http" ... />
+    <data android:mimeType="audio/mpeg" android:scheme="http" ... />
+    ...
+</intent-filter>
+
+ +

Cada elemento +<data> pode especificar uma estrutura de URI e um tipo de dados (tipo de mídia MIME). Há atributos +separados — {@code scheme}, {@code host}, {@code port} e +{@code path} — para cada parte da URI: +

+ +

{@code <scheme>://<host>:<port>/<path>}

+ +

+Por exemplo: +

+ +

{@code content://com.example.project:200/folder/subfolder/etc}

+ +

Nessa URI, o esquema é {@code content}, o host é {@code com.example.project}, +a porta é {@code 200} e o caminho é {@code folder/subfolder/etc}. +

+ +

Cada um desses atributos é opcional em um elemento {@code <data>}, +mas há dependências lineares:

+
    +
  • Se não houver esquema especificado, o host será ignorado.
  • +
  • Se não houver host especificado, a porta será ignorada.
  • +
  • Se não houver esquema nem host especificado, o caminho será ignorado.
  • +
+ +

Quando a URI em uma intenção é comparada a uma especificação de URI em um filtro, +a comparação é feita somente com as partes da URI incluídas no filtro. Por exemplo:

+
    +
  • Se um filtro especificar somente um esquema, todas as URIs com esse esquema atenderão +ao filtro.
  • +
  • Se um filtro especificar um esquema e uma autoridade, mas não um caminho, todas as URIs +com o mesmo esquema e autoridade passarão pelo filtro independentemente dos caminhos.
  • +
  • Se um filtro especificar um esquema, uma autoridade e um caminho, somente URIs com o mesmo esquema, +autoridade e caminho passarão pelo filtro.
  • +
+ +

Observação: a especificação de caminho pode +conter um asterisco especial (*) para exigir somente uma correspondência parcial do nome do caminho.

+ +

O teste de dados compara a URI e o tipo MIME da intenção com uma URI +e um tipo MIME especificados no filtro. As regras são as seguintes: +

+ +
    +
  1. A intenção que não contiver URI nem tipo MIME passará +no teste somente se o filtro não especificar nenhuma URI nem tipo MIME.
  2. + +
  3. A intenção que contiver URI mas nenhum tipo MIME (nem explícito, nem inferível a partir +da URI) passará pelo teste somente se a URI corresponder ao formato de URI do filtro +e se o filtro, igualmente, não especificar um tipo MIME.
  4. + +
  5. A intenção que contiver tipo MIME mas nenhuma URI passará pelo teste +somente se o filtro listar o mesmo tipo MIME e não especificar nenhum formato de URI.
  6. + +
  7. A intenção que contiver URI e tipo MIME (explícito ou inferível a partir +da URI) passará a parte do tipo MIME do teste somente se esse +tipo corresponder a um tipo listado no filtro. A parte da URI passará no teste +se corresponder a uma URI no filtro ou se tiver uma URI de {@code content:} +ou {@code file:} e se o filtro não especificar nenhuma URI. Em outras palavras, +presume-se que um componente seja compatível com dados de {@code content:} e de {@code file:} se +o filtro listar somente um tipo MIME.

  8. +
+ +

+Essa última regra (d) reflete a expectativa +de que os componentes sejam capazes de obter dados de local de um arquivo ou provedor de conteúdo. +Portanto, os filtros podem listar somente um tipo de dados e não precisam nomear +explicitamente os esquemas {@code content:} e {@code file:}. +Este é um caso típico. Um elemento {@code <data>} +como o seguinte, por exemplo, informa ao Android que o componente pode obter dados de imagem de um provedor +de conteúdo e exibi-los: +

+ +
+<intent-filter>
+    <data android:mimeType="image/*" />
+    ...
+</intent-filter>
+ +

+Como a maioria dos dados disponíveis é dispensada pelos provedores de conteúdo, os filtros +que especificam um tipo de dados mas não uma URI são, talvez, os mais comuns. +

+ +

+Outra configuração comum é: filtros com um esquema e um tipo de dados. Por +exemplo, um elemento +{@code <data>}, como o seguinte, informa ao Android +que o componente pode recuperar dados de vídeo da rede para realizar a ação: +

+ +
+<intent-filter>
+    <data android:scheme="http" android:type="video/*" />
+    ...
+</intent-filter>
+ + + +

Correspondência de intenções

+ +

Intenções são correspondidas a filtros de intenções não somente para descobrir +um componente-alvo a ativar, mas também para descobrir algo sobre o conjunto +de componentes do dispositivo. Por exemplo: o aplicativo Home preenche o inicializador do aplicativo +encontrando todas as atividades com filtros de intenções que especifiquem +a ação {@link android.content.Intent#ACTION_MAIN} e +a categoria {@link android.content.Intent#CATEGORY_LAUNCHER}.

+ +

O aplicativo pode usar a correspondência de intenções de modo similar. +O {@link android.content.pm.PackageManager} tem um conjunto de métodos +{@code query...()}, que retornam todos os componentes que podem aceitar uma determinada intenção +e uma série de métodos {@code resolve...()} similares, que determinam o melhor +componente para responder a uma intenção. Por exemplo: +{@link android.content.pm.PackageManager#queryIntentActivities +queryIntentActivities()} retorna uma lista de todas as atividades que podem realizar +a intenção passada como um argumento e {@link +android.content.pm.PackageManager#queryIntentServices +queryIntentServices()} retorna uma lista de serviços semelhantes. +Nenhum dos métodos ativa os componentes — eles apenas listam aqueles que +podem responder. Há um método semelhante +para receptores de transmissão — o {@link android.content.pm.PackageManager#queryBroadcastReceivers +queryBroadcastReceivers()}. +

+ + + + diff --git a/docs/html-intl/intl/pt-br/guide/components/loaders.jd b/docs/html-intl/intl/pt-br/guide/components/loaders.jd new file mode 100644 index 0000000000000000000000000000000000000000..f3c42094729db5224090075ffa53744402e7229b --- /dev/null +++ b/docs/html-intl/intl/pt-br/guide/components/loaders.jd @@ -0,0 +1,494 @@ +page.title=Carregadores +parent.title=Atividades +parent.link=activities.html +@jd:body +
+
+

Neste documento

+
    +
  1. Resumo de API de carregador
  2. +
  3. Uso de carregadores em um aplicativo +
      +
    1. +
    2. Início de um carregador
    3. +
    4. Reinício de um carregador
    5. +
    6. Uso dos retornos de chamada de LoaderManager
    7. +
    +
  4. +
  5. Exemplo +
      +
    1. Mais exemplos
    2. +
    +
  6. +
+ +

Classes principais

+
    +
  1. {@link android.app.LoaderManager}
  2. +
  3. {@link android.content.Loader}
  4. + +
+ +

Exemplos relacionados

+
    +
  1. +LoaderCursor
  2. +
  3. +LoaderThrottle
  4. +
+
+
+ +

Introduzidos no Android 3.0, os carregadores facilitam o carregamento assíncrono de dados +em uma atividade ou fragmento. Os carregadores têm as seguintes características:

+
    +
  • Eles estão disponíveis para cada {@link android.app.Activity} e {@link +android.app.Fragment}.
  • +
  • Eles fornecem carregamento assíncrono de dados.
  • +
  • Eles monitoram a origem dos dados e fornecem novos resultados +quando o conteúdo é alterado.
  • +
  • Eles reconectam-se automaticamente ao cursor do último carregador +quando são recriados após uma alteração de configuração. Portanto, eles não precisam reconsultar +os dados.
  • +
+ +

Resumo da API de carregador

+ +

Há várias classes e interfaces que podem ser envolvidas no uso +de carregadores em um aplicativo. Elas são resumidas nesta tabela:

+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Classe/InterfaceDescrição
{@link android.app.LoaderManager}Uma classe abstrata associada a {@link android.app.Activity} ou +{@link android.app.Fragment} para gerenciar uma ou mais instâncias de {@link +android.content.Loader}. Isto ajuda um aplicativo a gerenciar +operações executadas por longos períodos juntamente com o ciclo de vida de {@link android.app.Activity} +ou {@link android.app.Fragment}; o uso mais comum disto é com +{@link android.content.CursorLoader}. No entanto, os aplicativos têm a liberdade de criar +os próprios carregadores para outros tipos de dados. +
+
+ Há apenas um {@link android.app.LoaderManager} por atividade ou fragmento. No entanto, um {@link android.app.LoaderManager} pode ter +vários carregadores.
{@link android.app.LoaderManager.LoaderCallbacks}Uma interface de retorno de chamada para um cliente interagir com {@link +android.app.LoaderManager}. Por exemplo, usa-se o método de retorno de chamada {@link +android.app.LoaderManager.LoaderCallbacks#onCreateLoader onCreateLoader()} +para criar um novo carregador.
{@link android.content.Loader}Uma classe abstrata que realiza carregamento assíncrono de dados. Esta é a classe de base +de um carregador. Geralmente, você usaria {@link +android.content.CursorLoader}, mas é possível implementar subclasse própria. Enquanto os carregadores +estiverem ativos, devem monitorar a origem dos dados e fornecer +novos resultados quando o conteúdo for alterado.
{@link android.content.AsyncTaskLoader}Um carregador abstrato que fornece uma {@link android.os.AsyncTask} para realizar o trabalho.
{@link android.content.CursorLoader}Uma subclasse de {@link android.content.AsyncTaskLoader} que consulta +{@link android.content.ContentResolver} e retorna um {@link +android.database.Cursor}. Esta classe implementa o protocolo {@link +android.content.Loader} de forma padrão para cursores de consulta, +compilando em {@link android.content.AsyncTaskLoader} para realizar a consulta de cursor +em um encadeamento de segundo plano para que a IU do aplicativo não seja bloqueada. Usar +este carregador é a melhor maneira de carregar dados de forma assíncrona a partir de um {@link +android.content.ContentProvider}, em vez de realizar uma consulta gerenciada pelas +APIs da atividade ou do fragmento.
+ +

As classes e interfaces na tabela acima são os componentes essenciais +que você usará para implementar um carregador no aplicativo. Você não precisará de todos eles +para cada carregador criado, mas sempre precisará de uma referência a {@link +android.app.LoaderManager} para inicializar um carregador e uma implementação +de uma classe {@link android.content.Loader}, como {@link +android.content.CursorLoader}. As seguintes seções mostram como usar +essas classes e interfaces em um aplicativo.

+ +

Uso de carregadores em um aplicativo

+

Esta seção descreve como usar os carregadores em um aplicativo do Android. Um aplicativo +que usa os carregadores, geralmente, inclui o seguinte:

+
    +
  • Uma {@link android.app.Activity} ou um {@link android.app.Fragment}.
  • +
  • Uma instância de {@link android.app.LoaderManager}.
  • +
  • Um {@link android.content.CursorLoader} para carregar dados baseados em um {@link +android.content.ContentProvider}. Alternativamente, é possível implementar a própria subclasse +de {@link android.content.Loader} ou {@link android.content.AsyncTaskLoader} +para carregar dados de outra origem.
  • +
  • Uma implementação de {@link android.app.LoaderManager.LoaderCallbacks}. +É aqui que é possível criar novos carregadores e gerenciar as referências +a carregadores existentes.
  • +
  • Uma maneira de exibir os dados do carregador, como um {@link +android.widget.SimpleCursorAdapter}.
  • +
  • Uma origem de dados, como um {@link android.content.ContentProvider}, ao usar +{@link android.content.CursorLoader}.
  • +
+

Início de um carregador

+ +

O {@link android.app.LoaderManager} gerencia uma ou mais instâncias de {@link +android.content.Loader} dentro de uma {@link android.app.Activity} +ou um {@link android.app.Fragment}. Há apenas um {@link +android.app.LoaderManager} por atividade ou fragmento.

+ +

Geralmente, +inicializa-se um {@link android.content.Loader} dentro do método {@link +android.app.Activity#onCreate onCreate()} da atividade, ou dentro do método +{@link android.app.Fragment#onActivityCreated onActivityCreated()} do fragmento. Faça +isso da seguinte maneira:

+ +
// Prepare the loader.  Either re-connect with an existing one,
+// or start a new one.
+getLoaderManager().initLoader(0, null, this);
+ +

O método {@link android.app.LoaderManager#initLoader initLoader()} +recebe os seguintes parâmetros:

+
    +
  • Um ID único que identifica o carregador. Neste exemplo, o ID é 0.
  • +
  • Argumentos opcionais para fornecer ao carregador +em construção (null neste exemplo).
  • + +
  • Uma implementação de {@link android.app.LoaderManager.LoaderCallbacks}, +que {@link android.app.LoaderManager} chama para relatar eventos do carregador. Nesse exemplo, + a classe local implementa a interface de {@link +android.app.LoaderManager.LoaderCallbacks}, para que ela passe uma referência +para si, {@code this}.
  • +
+

A chamada de {@link android.app.LoaderManager#initLoader initLoader()} garante que o carregador +foi inicializado e que está ativo. Ela possui dois possíveis resultados:

+
    +
  • Se o carregador especificado pelo ID já existir, o último carregador +criado será usado novamente.
  • +
  • Se o carregador especificado pelo ID não existir, +{@link android.app.LoaderManager#initLoader initLoader()} ativará o método +{@link android.app.LoaderManager.LoaderCallbacks} {@link android.app.LoaderManager.LoaderCallbacks#onCreateLoader onCreateLoader()}. +É aqui que você implementa o código para instanciar e retornar um novo carregador. +Para obter mais informações, consulte a seção onCreateLoader.
  • +
+

Em qualquer um dos casos, a implementação de {@link android.app.LoaderManager.LoaderCallbacks} +fornecida é associada ao carregador e será chamada quando o estado +do carregador mudar. Se, no momento desta chamada, o autor dela +estiver no estado inicializado e o carregador solicitado já existir e tiver +gerado seus dados, o sistema chamará {@link +android.app.LoaderManager.LoaderCallbacks#onLoadFinished onLoadFinished()} +imediatamente (durante{@link android.app.LoaderManager#initLoader initLoader()}), +então prepare-se para tais situações. Consulte +onLoadFinished para obter mais informações sobre este retorno de chamada

+ +

Observe que o método {@link android.app.LoaderManager#initLoader initLoader()} +retorna o {@link android.content.Loader} que é criado, mas você não precisará +capturar uma referência para ele. O {@link android.app.LoaderManager} gerencia +a vida do carregador automaticamente. O {@link android.app.LoaderManager} +inicia e interrompe o carregamento quando necessário, além de manter o estado do carregador +e do conteúdo associado. À medida que isso ocorre, você raramente interage com os carregadores +diretamente (para ver um exemplo de métodos para aprimorar o comportamento +de um carregador, consulte o exemplo de LoaderThrottle). +Geralmente, usam-se os métodos {@link +android.app.LoaderManager.LoaderCallbacks} para intervir no processo de carregamento +quando determinados eventos ocorrem. Para obter mais informações sobre este assunto, consulte Uso dos retornos de chamada de LoaderManager.

+ +

Reinício de um carregador

+ +

Ao usar {@link android.app.LoaderManager#initLoader initLoader()}, +como mostrado acima, ele usará um carregador existente com o ID especificado, se houver um. +Caso contrário, um carregador será criado. No entanto, às vezes, você quer descartar os dados antigos +e começar do início.

+ +

Para descartar os dados antigos, use {@link +android.app.LoaderManager#restartLoader restartLoader()}. Por exemplo, +esta implementação de {@link android.widget.SearchView.OnQueryTextListener} reinicia +o carregador quando a consulta do usuário é alterada. O carregador precisa ser reiniciado +para que possa usar o filtro de busca revisado para realizar uma nova consulta:

+ +
+public boolean onQueryTextChanged(String newText) {
+    // Called when the action bar search text has changed.  Update
+    // the search filter, and restart the loader to do a new query
+    // with this filter.
+    mCurFilter = !TextUtils.isEmpty(newText) ? newText : null;
+    getLoaderManager().restartLoader(0, null, this);
+    return true;
+}
+ +

Uso dos retornos de chamada de LoaderManager

+ +

{@link android.app.LoaderManager.LoaderCallbacks} é uma interface de retorno de chamada +que permite que um cliente interaja com o {@link android.app.LoaderManager}.

+

Carregadores, em determinado {@link android.content.CursorLoader}, devem +reter os dados após serem interrompidos. Isto permite que os aplicativos +mantenham os dados dos métodos {@link android.app.Activity#onStop +onStop()} e {@link android.app.Activity#onStart onStart()} do fragmento ou da atividade +para que, quando os usuários voltarem a um aplicativo, não tenham que esperar +o recarregamento dos dados. Você usa os métodos {@link android.app.LoaderManager.LoaderCallbacks} +para saber quando deve criar um novo carregador, e para dizer ao aplicativo quando + deve interromper o uso dos dados de um carregador.

+ +

{@link android.app.LoaderManager.LoaderCallbacks} inclui +esses métodos:

+
    +
  • {@link android.app.LoaderManager.LoaderCallbacks#onCreateLoader onCreateLoader()} — +instancia e retorna um novo {@link android.content.Loader} para o ID fornecido. +
+
    +
  • {@link android.app.LoaderManager.LoaderCallbacks#onLoadFinished onLoadFinished()} +— chamado quando um carregador anteriormente criado termina o seu carregamento. +
+
    +
  • {@link android.app.LoaderManager.LoaderCallbacks#onLoaderReset onLoaderReset()} + — chamado quando um carregador anteriormente criado é reiniciado, +tornando os dados indisponíveis. +
  • +
+

Esses métodos são descritos com mais informações nas seguintes seções.

+ +

onCreateLoader

+ +

Ao tentar acessar um carregador (por exemplo, por meio de {@link +android.app.LoaderManager#initLoader initLoader()}), ele verifica +se o carregador especificado pelo ID existe. Se não existir, ele ativa o método {@link +android.app.LoaderManager.LoaderCallbacks} {@link +android.app.LoaderManager.LoaderCallbacks#onCreateLoader onCreateLoader()}. É aqui +que você pode criar um novo carregador. Geralmente, será um {@link +android.content.CursorLoader}, mas é possível implementar a própria subclasse de {@link +android.content.Loader}.

+ +

Nesse exemplo, o método de retorno de chamada de {@link +android.app.LoaderManager.LoaderCallbacks#onCreateLoader onCreateLoader()} +cria um {@link android.content.CursorLoader}. Você deve compilar +{@link android.content.CursorLoader} usando o método construtor, +que exige um conjunto completo de informações necessárias para realizar uma consulta ao {@link +android.content.ContentProvider}. Especificamente, ele precisa de:

+
    +
  • uri — a URI do conteúdo a ser recuperado.
  • +
  • projection — uma lista de quais colunas devem ser retornadas. Passar +null retornará todas as colunas, o que é ineficiente.
  • +
  • selection — um filtro que declara quais linhas devem retornar, +formatado por uma cláusula SQL WHERE (excluindo WHERE). Passar +null retornará todas as linhas da URI em questão.
  • +
  • selectionArgs — é possível incluir interrogações na seleção, +que serão substituídas pelos valores de selectionArgs, na ordem em que aparecem +na seleção. Os valores serão vinculados como Strings.
  • +
  • sortOrder — como ordenar as linhas, formatadas em uma cláusula SQL +ORDER BY (excluindo ORDER BY). Passar null +usará a ordem de classificação padrão, que pode ser desordenada.
  • +
+

Por exemplo:

+
+ // If non-null, this is the current filter the user has provided.
+String mCurFilter;
+...
+public Loader<Cursor> onCreateLoader(int id, Bundle args) {
+    // This is called when a new Loader needs to be created.  This
+    // sample only has one Loader, so we don't care about the ID.
+    // First, pick the base URI to use depending on whether we are
+    // currently filtering.
+    Uri baseUri;
+    if (mCurFilter != null) {
+        baseUri = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI,
+                  Uri.encode(mCurFilter));
+    } else {
+        baseUri = Contacts.CONTENT_URI;
+    }
+
+    // Now create and return a CursorLoader that will take care of
+    // creating a Cursor for the data being displayed.
+    String select = "((" + Contacts.DISPLAY_NAME + " NOTNULL) AND ("
+            + Contacts.HAS_PHONE_NUMBER + "=1) AND ("
+            + Contacts.DISPLAY_NAME + " != '' ))";
+    return new CursorLoader(getActivity(), baseUri,
+            CONTACTS_SUMMARY_PROJECTION, select, null,
+            Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC");
+}
+

onLoadFinished

+ +

Este método é chamado quando um carregador anteriormente criado terminar o seu carregamento. +Este método certamente será chamado antes da liberação dos últimos dados +que forem fornecidos por este carregador. Neste ponto, você deve remover todo o uso +dos dados antigos (já que serão liberados em breve), mas não deve fazer +a liberação dos dados, já que pertencem ao carregador e ele lidará com isso.

+ + +

O carregador liberará os dados quando souber +que o aplicativo não está mais usando-os. Por exemplo, se os dados forem um cursor de um {@link +android.content.CursorLoader}, você não deve chamar {@link +android.database.Cursor#close close()} por conta própria. Se o cursor estiver +sendo colocado em um {@link android.widget.CursorAdapter}, você deve usar o método {@link +android.widget.SimpleCursorAdapter#swapCursor swapCursor()} para que o antigo +{@link android.database.Cursor} não seja fechado. Por exemplo:

+ +
+// This is the Adapter being used to display the list's data.
SimpleCursorAdapter mAdapter; +... + +public void onLoadFinished(Loader<Cursor> loader, Cursor data) { + // Swap the new cursor in.  (The framework will take care of closing the + // old cursor once we return.) + mAdapter.swapCursor(data); +}
+ +

onLoaderReset

+ +

Este método é chamado quando um carregador anteriormente criado é reiniciado, +tornando os dados indisponíveis. Este retorno de chamada permite que você descubra quando os dados +estão prestes a serem liberados para que seja possível remover a referência a eles.  

+

Esta implementação chama +{@link android.widget.SimpleCursorAdapter#swapCursor swapCursor()} +com um valor de null:

+ +
+// This is the Adapter being used to display the list's data.
+SimpleCursorAdapter mAdapter;
+...
+
+public void onLoaderReset(Loader<Cursor> loader) {
+    // This is called when the last Cursor provided to onLoadFinished()
+    // above is about to be closed.  We need to make sure we are no
+    // longer using it.
+    mAdapter.swapCursor(null);
+}
+ + +

Exemplo

+ +

Como exemplo, a seguir há uma implementação completa de um {@link +android.app.Fragment} que exibe uma {@link android.widget.ListView} contendo +os resultados de uma consulta aos provedores de conteúdo de contatos. Ela usa um {@link +android.content.CursorLoader} para gerenciar a consulta no provedor.

+ +

Para um aplicativo acessar os contatos de um usuário, como neste exemplo, +o manifesto deverá incluir a permissão +{@link android.Manifest.permission#READ_CONTACTS READ_CONTACTS}.

+ +
+public static class CursorLoaderListFragment extends ListFragment
+        implements OnQueryTextListener, LoaderManager.LoaderCallbacks<Cursor> {
+
+    // This is the Adapter being used to display the list's data.
+    SimpleCursorAdapter mAdapter;
+
+    // If non-null, this is the current filter the user has provided.
+    String mCurFilter;
+
+    @Override public void onActivityCreated(Bundle savedInstanceState) {
+        super.onActivityCreated(savedInstanceState);
+
+        // Give some text to display if there is no data.  In a real
+        // application this would come from a resource.
+        setEmptyText("No phone numbers");
+
+        // We have a menu item to show in action bar.
+        setHasOptionsMenu(true);
+
+        // Create an empty adapter we will use to display the loaded data.
+        mAdapter = new SimpleCursorAdapter(getActivity(),
+                android.R.layout.simple_list_item_2, null,
+                new String[] { Contacts.DISPLAY_NAME, Contacts.CONTACT_STATUS },
+                new int[] { android.R.id.text1, android.R.id.text2 }, 0);
+        setListAdapter(mAdapter);
+
+        // Prepare the loader.  Either re-connect with an existing one,
+        // or start a new one.
+        getLoaderManager().initLoader(0, null, this);
+    }
+
+    @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+        // Place an action bar item for searching.
+        MenuItem item = menu.add("Search");
+        item.setIcon(android.R.drawable.ic_menu_search);
+        item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
+        SearchView sv = new SearchView(getActivity());
+        sv.setOnQueryTextListener(this);
+        item.setActionView(sv);
+    }
+
+    public boolean onQueryTextChange(String newText) {
+        // Called when the action bar search text has changed.  Update
+        // the search filter, and restart the loader to do a new query
+        // with this filter.
+        mCurFilter = !TextUtils.isEmpty(newText) ? newText : null;
+        getLoaderManager().restartLoader(0, null, this);
+        return true;
+    }
+
+    @Override public boolean onQueryTextSubmit(String query) {
+        // Don't care about this.
+        return true;
+    }
+
+    @Override public void onListItemClick(ListView l, View v, int position, long id) {
+        // Insert desired behavior here.
+        Log.i("FragmentComplexList", "Item clicked: " + id);
+    }
+
+    // These are the Contacts rows that we will retrieve.
+    static final String[] CONTACTS_SUMMARY_PROJECTION = new String[] {
+        Contacts._ID,
+        Contacts.DISPLAY_NAME,
+        Contacts.CONTACT_STATUS,
+        Contacts.CONTACT_PRESENCE,
+        Contacts.PHOTO_ID,
+        Contacts.LOOKUP_KEY,
+    };
+    public Loader<Cursor> onCreateLoader(int id, Bundle args) {
+        // This is called when a new Loader needs to be created.  This
+        // sample only has one Loader, so we don't care about the ID.
+        // First, pick the base URI to use depending on whether we are
+        // currently filtering.
+        Uri baseUri;
+        if (mCurFilter != null) {
+            baseUri = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI,
+                    Uri.encode(mCurFilter));
+        } else {
+            baseUri = Contacts.CONTENT_URI;
+        }
+
+        // Now create and return a CursorLoader that will take care of
+        // creating a Cursor for the data being displayed.
+        String select = "((" + Contacts.DISPLAY_NAME + " NOTNULL) AND ("
+                + Contacts.HAS_PHONE_NUMBER + "=1) AND ("
+                + Contacts.DISPLAY_NAME + " != '' ))";
+        return new CursorLoader(getActivity(), baseUri,
+                CONTACTS_SUMMARY_PROJECTION, select, null,
+                Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC");
+    }
+
+    public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
+        // Swap the new cursor in.  (The framework will take care of closing the
+        // old cursor once we return.)
+        mAdapter.swapCursor(data);
+    }
+
+    public void onLoaderReset(Loader<Cursor> loader) {
+        // This is called when the last Cursor provided to onLoadFinished()
+        // above is about to be closed.  We need to make sure we are no
+        // longer using it.
+        mAdapter.swapCursor(null);
+    }
+}
+

Mais exemplos

+ +

Há alguns exemplos variados na ApiDemos +que ilustra o uso de carregadores:

+
    +
  • +LoaderCursor — uma versão completa do +fragmento exibido acima.
  • +
  • LoaderThrottle — um exemplo de como usar o regulador +para reduzir o número de consultas que o provedor de conteúdo realiza quando os dados são alterados.
  • +
+ +

Para obter mais informações sobre o download e a instalação de exemplos de SDK, consulte Obtenção +dos exemplos.

+ diff --git a/docs/html-intl/intl/pt-br/guide/components/processes-and-threads.jd b/docs/html-intl/intl/pt-br/guide/components/processes-and-threads.jd new file mode 100644 index 0000000000000000000000000000000000000000..c8e636dacce834f848b8435bc8d49b05adb8db3d --- /dev/null +++ b/docs/html-intl/intl/pt-br/guide/components/processes-and-threads.jd @@ -0,0 +1,411 @@ +page.title=Processos e encadeamentos +page.tags=lifecycle,background + +@jd:body + + + +

Quando um componente de aplicativo inicia e o aplicativo não tem nenhum outro componente em execução, +o sistema Android inicia um novo processo no Linux para o aplicativo com um único encadeamento +de execução. Por padrão, todos os componentes do mesmo aplicativo são executados no mesmo processo +e encadeamento (chamado de encadeamento "principal"). Se um componente de aplicativo iniciar e já existir um processo +para este aplicativo (pois outro componente do aplicativo já existe), ele será iniciado +dentro deste processo e usará o mesmo encadeamento de execução. No entanto, é possível organizar para que vários componentes +no aplicativo sejam executados em processos separados e criar encadeamentos +adicionais para qualquer processo.

+ +

Este documento discute como os processos e os encadeamentos funcionam em um aplicativo do Android.

+ + +

Processos

+ +

Por padrão, todos os componentes do mesmo aplicativo são executados no mesmo processo +e a maioria dos aplicativos não deve alterar isso. No entanto, se achar que precisa de controle sobre o processo a que um certo +componente pertence, é possível fazer isto no arquivo de manifesto.

+ +

A entrada do manifesto para cada tipo de elemento de componente — {@code +<activity>}, {@code +<service>}, {@code +<receiver>} e {@code +<provider>} — é compatível com um atributo {@code android:process} que pode especificar +um processo em que este componente deverá ser executado. É possível definir este atributo para que cada componente +seja executado em seu próprio processo ou para que alguns componentes compartilhem um processo, enquanto que outros não. Também é possível definir +{@code android:process} para que os componentes de aplicativos diferentes sejam executados no mesmo +processo — fornecido para que os aplicativos compartilhem o mesmo ID de usuário do Linux e sejam assinados +com os mesmos certificados.

+ +

O elemento {@code +<application>} também suporta um atributo {@code android:process} +para definir um valor padrão que se aplique a todos os elementos.

+ +

O Android pode decidir desativar um processo em certo ponto, quando a memória estiver baixa e for necessária +para outros processos que atendem o usuário de forma mais imediata. Os componentes +de aplicativo em execução no processo que é eliminado são consequentemente eliminados. Um processo é iniciado +novamente para aqueles componentes quando houver trabalho para eles.

+ +

Ao decidir que processo eliminar, o sistema Android avalia a importância relativa dele +para o usuário. Por exemplo, ele fechará mais prontamente um processo que hospede atividades que não estejam mais +visíveis na tela, comparado a um processo que hospede atividades visíveis. A decisão +é exterminar processo ou não, portanto, depende do estado dos componentes em execução neste processo. As regras +usadas para decidir quais processos serão exterminados são discutidas abaixo.

+ + +

Ciclo de vida dos processos

+ +

O sistema Android tenta manter um processo do aplicativo pelo maior período possível, +mas eventualmente precisa remover processos antigos para recuperar memória para processos novos e mais importantes. Para determinar +quais processos manter +e quais exterminar, o sistema posiciona cada um em uma "hierarquia de importância" com base +nos componentes em execução e no estado deles. Processos com menos importância +serão eliminados primeiro e, em seguida, aqueles com a menor importância depois deles também serão, consecutivamente, até que os recursos +do sistema sejam recuperados.

+ +

Há cinco níveis na hierarquia de importância. A lista a seguir mostra os tipos +de processo em ordem de importância (o primeiro processo é o mais importante +e o eliminado por último):

+ +
    +
  1. Processos em primeiro plano +

    Um processo necessário para o que o usuário está fazendo. Considera-se + um processo como em primeiro plano se qualquer uma das condições for verdadeira:

    + +
      +
    • Se ele hospedar uma {@link android.app.Activity} com a qual o usuário esteja interagindo (se o método {@link android.app.Activity#onResume onResume()} de {@link +android.app.Activity} +for chamado).
    • + +
    • Se ele hospedar um {@link android.app.Service} vinculado à atividade com a qual o usuário +esteja interagindo.
    • + +
    • Se ele hospedar um {@link android.app.Service} em execução "em primeiro plano"— se o serviço +tiver chamado {@link android.app.Service#startForeground startForeground()}. + +
    • Se ele hospedar um {@link android.app.Service} que esteja executando um de seus retornos de chamada +do ciclo de vida ({@link android.app.Service#onCreate onCreate()}, {@link android.app.Service#onStart +onStart()}, ou {@link android.app.Service#onDestroy onDestroy()}).
    • + +
    • Se ele hospedar um {@link android.content.BroadcastReceiver} que esteja executando seu método {@link + android.content.BroadcastReceiver#onReceive onReceive()}.
    • +
    + +

    Geralmente, somente poucos processos em primeiro plano existem em um determinado momento. Eles serão eliminados +somente como último recurso — se a memória estiver baixa demais para suportar a execução de todos. Geralmente, neste ponto, +o dispositivo atingiu o estado de paginação de memória. Portanto, eliminar alguns processos em primeiro plano +é necessário para que a interface permaneça responsiva.

  2. + +
  3. Processos visíveis +

    Um processo que não tenha nenhum componente em primeiro plano, + mas que ainda possa afetar o que o usuário vê na tela. Um processo é considerado visível + se uma das condições a seguir for verdadeira:

    + +
      +
    • Se ele hospedar um {@link android.app.Activity} que não esteja em primeiro plano, +mas ainda seja visível para o usuário (o seu método {@link android.app.Activity#onPause onPause()} tiver sido chamado). +Isto poderá ocorrer, por exemplo, se a atividade em primeiro plano iniciar um diálogo, o que permitirá +que a atividade anterior seja vista por trás dela.
    • + +
    • Se ele hospedar um {@link android.app.Service} que esteja vinculado a uma atividade visível +(ou em primeiro plano).
    • +
    + +

    Um processo visível é considerado extremamente importante e não será eliminado a não ser +que seja necessário para manter em execução os processos em primeiro plano.

    +
  4. + +
  5. Processos de serviço +

    Um processo que esteja executando um serviço que tenha sido iniciado com o método {@link +android.content.Context#startService startService()} e não +esteja em uma das duas categorias mais altas. Apesar de processos de serviço não estarem diretamente ligados a tudo o que os usuários veem, +eles estão geralmente fazendo coisas com que o usuário se importa (como reproduzindo música em segundo plano +ou baixando dados na rede), então o sistema os mantém em execução a não ser que haja memória suficiente +para retê-los juntamente com todos os processos visíveis e em primeiro plano.

    +
  6. + +
  7. Processos em segundo plano +

    Um processo que mantenha uma atividade que não esteja atualmente visível para o usuário (se o método +{@link android.app.Activity#onStop onStop()} da atividade tiver sido chamado). Esses processos não têm impacto direto +na experiência do usuário e o sistema poderá eliminá-los a qualquer momento para recuperar memória para processos +em primeiro plano, +visíveis ou de serviço. Geralmente há vários processos em segundo plano em execução e eles são mantidos +em uma lista LRU (least recently used - menos usados recentemente) para garantir que o processo com a atividade +que foi mais vista recentemente pelo usuário seja a última a ser eliminada. Se uma atividade implementar o seu método de ciclo de vida +corretamente e salvar o estado atual, eliminar o seu processo não terá efeito visível +na experiência do usuário, pois quando ele navegar de volta à atividade, ela restaurará +todo o estado visível. Consulte o documento Atividades +para obter informações sobre os estados de restauração e de gravação.

    +
  8. + +
  9. Processos vazios +

    Um processo que não possui nenhum componente de aplicativo ativo. O único motivo para manter este tipo de processo +ativo é para armazenamento em cache, para aprimorar o tempo de inicialização +na próxima vez em que um componente precisar executá-lo. O sistema frequentemente elimina esses processos para equilibrar os recursos do sistema em geral +entre os armazenamentos em cache do processo e os de núcleo subjacente.

    +
  10. +
+ + +

O Android coloca o processo no maior nível possível, com base na importância dos componentes +atualmente ativos no processo. Por exemplo, se um processo hospedar um serviço e uma atividade visível +, ele terá a classificação de um processo visível, e não a de um processo de serviço.

+ +

Além disso, a classificação de um processo pode ser elevada porque outros processos +dependem dele — um processo que está servindo a outro processo nunca pode ter classificação menor +do que a do processo que está sendo servido. Por exemplo, se um provedor de conteúdo no processo A estiver servindo um cliente no processo B, +ou se um serviço no processo A estiver vinculado a um componente no processo B, o processo A será sempre considerado +menos importante do que o processo B.

+ +

Como um processo que executa um serviço tem classificação maior do que um processo com atividades em segundo plano, +uma atividade que iniciar uma operação de longo prazo poderá também iniciar um serviço para esta operação, em vez de simplesmente +criar um encadeamento de trabalho — especificamente se a operação exceder a atividade. +Por exemplo, uma atividade que esteja fazendo upload de uma imagem para um site deverá iniciar um serviço +para fazer o upload para que ele possa continuar em segundo plano mesmo se o usuário deixar a atividade. +Usar um serviço garante que a operação tenha pelo menos a prioridade "processo de serviço", +independente do que acontecer à atividade. Este é o mesmo motivo pelo qual os receptores de transmissão devem +empregar serviços em vez de simplesmente usar operações que consomem tempo em um encadeamento.

+ + + + +

Encadeamentos

+ +

Quando um aplicativo é executado, o sistema cria um encadeamento de execução para ele, +chamado de "principal". Este encadeamento é muito importante, pois está encarregado de despachar eventos +para os widgets adequados da interface do usuário, incluindo eventos de desenho. É também o encadeamento +em que o aplicativo interage com componentes do kit de ferramentas da IU do Android (componentes dos pacotes {@link +android.widget} e {@link android.view}). Portanto, o encadeamento principal, às vezes, +também chama o encadeamento da IU.

+ +

O sistema não cria um encadeamento separado para cada instância de um componente. Todos os componentes +executados no mesmo processo são instanciados no encadeamento da IU, e as chamadas do sistema +para cada componente são despachadas deste encadeamento. Consequentemente, métodos que respondam aos retornos de chamada +(como {@link android.view.View#onKeyDown onKeyDown()} para informar ações de usuário +ou um método de retorno de chamada do ciclo de vida) sempre serão executados no encadeamento da IU do processo.

+ +

Por exemplo, quando o usuário toca em um botão na tela, o encadeamento da IU do aplicativo despacha +o evento de toque para o widget que, em troca, ativa o seu estado pressionado e publica uma solicitação de invalidação +à fila do evento. O encadeamento da IU retira a solicitação da fila e notifica o widget de que ele deve +desenhar novamente por conta própria.

+ +

Quando o aplicativo realiza trabalho intenso em resposta à interação do usuário, este modelo de único encadeamento +pode produzir desempenho inferior a não ser que você implemente o aplicativo adequadamente. Especificamente, se tudo estiver +acontecendo no encadeamento da IU, realizar operações longas, como acesso à rede +ou consultas a banco de dados, bloqueará toda a IU. Quando o encadeamento é bloqueado, nenhum evento pode ser despachado, +incluindo eventos de desenho. Da perspectiva do usuário, +o aplicativo parece travar. Pior ainda, se o encadeamento da IU for bloqueado por mais do que alguns segundos +(cerca de 5 segundos atualmente), o usuário receberá a vergonhosa mensagem "aplicativo +não respondendo" (ANR). O usuário poderá, então, decidir fechar o aplicativo e desinstalá-lo +se estiver descontente.

+ +

Além disso, o kit de ferramentas de IU do Android não é seguro para encadeamentos. Portanto, você não deve manipular a IU +a partir de um encadeamento de trabalho — deve-se realizar toda a manipulação para a interface do usuário +a partir do encadeamento da IU. Com isso dito, há duas regras simples para o modelo de encadeamento único do Android:

+ +
    +
  1. Não bloquear o encadeamento da IU +
  2. Não acessar o kit de ferramentas de IU do Android fora do encadeamento da IU +
+ +

Encadeamentos de trabalho

+ +

Por causa do modelo de encadeamento único descrito acima, é essencial para a capacidade de resposta da IU do aplicativo +que você não bloqueie o encadeamento da IU. Caso tenha operações a realizar +que não sejam instantâneas, deverá certificar-se de fazê-las em encadeamentos separados (encadeamentos de "segundo plano" +ou "de trabalho").

+ +

Por exemplo, abaixo apresenta-se o código de uma escuta de clique que baixa uma imagem de um encadeamento separado +e exibe-a em uma {@link android.widget.ImageView}:

+ +
+public void onClick(View v) {
+    new Thread(new Runnable() {
+        public void run() {
+            Bitmap b = loadImageFromNetwork("http://example.com/image.png");
+            mImageView.setImageBitmap(b);
+        }
+    }).start();
+}
+
+ +

À primeira vista, parece não apresentar problemas, pois ele cria um novo encadeamento para lidar +com a operação de rede. No entanto, ele viola a segunda regra do modelo de encadeamento único: não acessar o kit de ferramentas de IU do Android +de fora do encadeamento da IU — este exemplo modifica o {@link +android.widget.ImageView} a partir do encadeamento de trabalho em vez de o encadeamento da IU. Isto pode resultar em comportamentos +inesperados e indefinidos, que podem ser difíceis de rastrear e consumir bastante tempo.

+ +

Para resolver este problema, o Android oferece várias maneiras de acessar o encadeamento da IU a partir +de outros encadeamentos. Abaixo há uma lista dos métodos que podem ajudar:

+ +
    +
  • {@link android.app.Activity#runOnUiThread(java.lang.Runnable) +Activity.runOnUiThread(Runnable)}
  • +
  • {@link android.view.View#post(java.lang.Runnable) View.post(Runnable)}
  • +
  • {@link android.view.View#postDelayed(java.lang.Runnable, long) View.postDelayed(Runnable, +long)}
  • +
+ +

Por exemplo, é possível resolver o código acima usando o método {@link +android.view.View#post(java.lang.Runnable) View.post(Runnable)}:

+ +
+public void onClick(View v) {
+    new Thread(new Runnable() {
+        public void run() {
+            final Bitmap bitmap = loadImageFromNetwork("http://example.com/image.png");
+            mImageView.post(new Runnable() {
+                public void run() {
+                    mImageView.setImageBitmap(bitmap);
+                }
+            });
+        }
+    }).start();
+}
+
+ +

Agora esta implementação é segura para encadeamentos: a operação de rede é concluída a partir de um encadeamento separado +enquanto que a {@link android.widget.ImageView} é manipulada a partir do encadeamento da IU.

+ +

No entanto, à medida que a complexidade da operação aumenta, este tipo de código pode tornar-se complexo +e difícil demais para ser mantido. Para lidar com interações mais complexas com um encadeamento de trabalho, considere +usar um {@link android.os.Handler} nele para processar as mensagens entregues +pelo encadeamento da IU. Apesar de que, talvez, a melhor solução seja estender a classe {@link android.os.AsyncTask}, +que simplifica a execução das tarefas do encadeamento de trabalho que precisam interagir com a IU.

+ + +

Uso de AsyncTask

+ +

{@link android.os.AsyncTask} permite que você trabalhe de forma assíncrona +na interface do usuário. Ela realiza operações de bloqueio em um encadeamento de trabalho e, em seguida, publica os resultados +no encadeamento da IU, sem que você precise lidar com os encadeamentos e/ou os manipuladores.

+ +

Para usá-la, você deve atribuir a subclasse {@link android.os.AsyncTask} e implementar o método de retorno de chamada {@link +android.os.AsyncTask#doInBackground doInBackground()}, que executa uma série +de encadeamentos de segundo plano. Para atualizar a IU, deve-se implementar {@link +android.os.AsyncTask#onPostExecute onPostExecute()}, que entrega o resultado de {@link +android.os.AsyncTask#doInBackground doInBackground()} e é executado no encadeamento da IU para que seja possível +atualizar a IU de forma segura. É possível, em seguida, executar a tarefa chamando {@link android.os.AsyncTask#execute execute()} +a partir do encadeamento da IU.

+ +

Por exemplo, é possível implementar o exemplo anterior usando {@link android.os.AsyncTask} +da seguinte forma:

+ +
+public void onClick(View v) {
+    new DownloadImageTask().execute("http://example.com/image.png");
+}
+
+private class DownloadImageTask extends AsyncTask<String, Void, Bitmap> {
+    /** The system calls this to perform work in a worker thread and
+      * delivers it the parameters given to AsyncTask.execute() */
+    protected Bitmap doInBackground(String... urls) {
+        return loadImageFromNetwork(urls[0]);
+    }
+    
+    /** The system calls this to perform work in the UI thread and delivers
+      * the result from doInBackground() */
+    protected void onPostExecute(Bitmap result) {
+        mImageView.setImageBitmap(result);
+    }
+}
+
+ +

Agora a IU está segura e o código está mais simples, pois ele separa o trabalho +em uma parte que deve ser feita em um encadeamento de trabalho e outra parte que deve ser feita no encadeamento da IU.

+ +

Leia a referência {@link android.os.AsyncTask} para compreender melhor +o uso desta classe, mas a seguir há uma visão geral rápida sobre como ela funciona:

+ +
    +
  • É possível especificar o tipo dos parâmetros, valores de progresso e valor final +da tarefa, usando genéricos
  • +
  • O método {@link android.os.AsyncTask#doInBackground doInBackground()} é executado automaticamente +em um encadeamento de trabalho
  • +
  • {@link android.os.AsyncTask#onPreExecute onPreExecute()}, {@link +android.os.AsyncTask#onPostExecute onPostExecute()} e {@link +android.os.AsyncTask#onProgressUpdate onProgressUpdate()} são invocados no encadeamento da IU
  • +
  • O valor retornado por {@link android.os.AsyncTask#doInBackground doInBackground()} +é enviado para {@link android.os.AsyncTask#onPostExecute onPostExecute()}
  • +
  • É possível chamar {@link android.os.AsyncTask#publishProgress publishProgress()} a qualquer momento em {@link +android.os.AsyncTask#doInBackground doInBackground()} para executar {@link +android.os.AsyncTask#onProgressUpdate onProgressUpdate()} no encadeamento da IU
  • +
  • É possível cancelar a tarefa a qualquer momento, a partir de qualquer encadeamento
  • +
+ +

Atenção: outro problema que pode ocorrer ao usar +um encadeamento de trabalho são reinicializações inesperadas da atividade devido a alterações na configuração em tempo de execução +(como quando o usuário altera a orientação da tela), que podem eliminar o encadeamento de trabalho. Para ver como é possível +manter uma tarefa durante uma dessas reinicializações e como cancelar adequadamente a tarefa +quando a atividade for eliminada, consulte o código fonte do aplicativo Prateleiras de exemplo.

+ + +

Métodos seguros de encadeamento

+ +

Em algumas situações, os métodos implementados podem ser chamados a partir de mais de um encadeamento e, portanto, +devem ser programados para serem seguros para encadeamento.

+ +

Isto é especialmente verdadeiro para métodos que podem ser cancelados remotamente — como métodos em um serviço vinculado. Quando uma chamada +de um método implementado em um {@link android.os.IBinder} tiver origem no mesmo processo +em que {@link android.os.IBinder IBinder} estiver em execução, o método será executado no encadeamento do autor da chamada. +No entanto, quando a chamada tiver origem em outro processo, o método será executado em um encadeamento +escolhido a partir de uma série de encadeamentos que o sistema mantém no mesmo processo que {@link android.os.IBinder +IBinder} (ele não será executado no encadeamento da IU do processo). Por exemplo, enquanto o método +{@link android.app.Service#onBind onBind()} de um serviço seria chamado a partir de um encadeamento da IU do processo +de um serviço, os métodos implementados no objeto que {@link android.app.Service#onBind +onBind()} retorna (por exemplo, uma subclasse que implementa métodos de RPC) seriam chamados +a partir dos encadeamentos no conjunto. Como um serviço pode ter mais de um cliente, mais de um encadeamento no conjunto +pode envolver o mesmo método {@link android.os.IBinder IBinder} ao mesmo tempo. Os métodos {@link android.os.IBinder +IBinder} devem, portanto, ser implementados para serem seguros para encadeamentos.

+ +

De forma semelhante, um provedor de conteúdo pode receber solicitações de dados que tenham origem em outros processos. +Apesar de as classes {@link android.content.ContentResolver} e {@link android.content.ContentProvider} +ocultarem os detalhes de como a comunicação entre processos é gerenciada, os métodos {@link +android.content.ContentProvider} que respondem a essas solicitações — os métodos {@link +android.content.ContentProvider#query query()}, {@link android.content.ContentProvider#insert +insert()}, {@link android.content.ContentProvider#delete delete()}, {@link +android.content.ContentProvider#update update()} e {@link android.content.ContentProvider#getType +getType()} — serão chamados de um conjunto de encadeamentos no processo do provedor de conteúdo, não no encadeamento da IU +para o processo. Como esses métodos podem ser chamados a partir de qualquer quantidade de encadeamentos +ao mesmo tempo, eles também devem ser implementados para serem seguros para encadeamento.

+ + +

Comunicação entre processos

+ +

O Android oferece um mecanismo para comunicação entre processos (IPC) usando chamadas de procedimento remoto (RPCs), +onde um método é chamado por uma atividade ou outro componente de aplicativo, mas é executado +remotamente (em outro processo), com qualquer resultado retornado +de volta ao autor da chamada. Isto acarreta na decomposição de uma chamada de método e de seus dados para um nível em que o sistema operacional +possa entender, transmitindo-a do processo local e do espaço de endereço ao processo remoto +e ao espaço de endereço e, em seguida, remontando e restabelecendo a chamada lá. Os valores de retorno +são transmitidos na direção oposta. O Android fornece todo o código para realizar essas operações +de IPC para que você possa concentrar-se em definir e implementar a interface de programação de RPC.

+ +

Para realizar o IPC, o aplicativo deve vincular-se a um serviço usando {@link +android.content.Context#bindService bindService()}. Para obter mais informações, consulte o guia do desenvolvedor Serviços.

+ + + diff --git a/docs/html-intl/intl/pt-br/guide/components/recents.jd b/docs/html-intl/intl/pt-br/guide/components/recents.jd new file mode 100644 index 0000000000000000000000000000000000000000..467f62067a546d5286ffa8ae5ce5180bfd918d7a --- /dev/null +++ b/docs/html-intl/intl/pt-br/guide/components/recents.jd @@ -0,0 +1,256 @@ +page.title=Tela de visão geral +page.tags="recents","overview" + +@jd:body + +
+
+ +

Neste documento

+
    +
  1. Adição de tarefas à tela de visão geral +
      +
    1. Uso do sinalizador Intent para adicionar uma tarefa
    2. +
    3. Uso do atributo Activity para adicionar uma tarefa
    4. +
    +
  2. +
  3. Remoção de tarefas +
      +
    1. Uso da classe AppTask para remover tarefas
    2. +
    3. Retenção de tarefas terminadas
    4. +
    +
  4. +
+ +

Classes principais

+
    +
  1. {@link android.app.ActivityManager.AppTask}
  2. +
  3. {@link android.content.Intent}
  4. +
+ +

Exemplo de código

+
    +
  1. Aplicativos centralizados em documentos
  2. +
+ +
+
+ +

A tela de visão geral (também chamada de tela de recentes, lista de tarefas recentes ou aplicativos recentes) +é uma IU de nível de sistema que lista +atividades e tarefas acessadas recentemente. O +usuário pode navegar pela lista e selecionar uma tarefa a retomar ou remover uma tarefa da +lista deslizando-a para fora. Com a versão 5.0 do Android (API de nível 21), várias instâncias da +mesma atividade contendo diferentes documentos podem aparecer como tarefas na tela de visão geral. Por exemplo, o +Google Drive pode ter uma tarefa para cada um dos vários documentos do Google. Cada documento aparece como uma +tarefa na tela de visão geral.

+ + +

Figura 1. A tela de visão geral mostrando três documentos do Google Drive, +cada um representado como uma tarefa separada.

+ +

Normalmente, você deve permitir que o sistema defina como as tarefas e as +atividades são representadas na tela de visão geral e não precisa modificar esse comportamento. +No entanto, o seu aplicativo pode determinar como e quando as atividades aparecem na tela de visão geral. A +classe {@link android.app.ActivityManager.AppTask} permite gerenciar tarefas e os sinalizadores de atividade da classe +{@link android.content.Intent} permitem especificar quando uma atividade é adicionada ou removida da +tela de visão geral. Além disso, os atributos +<activity> permitem definir o comportamento no manifesto.

+ +

Adição de tarefas à tela de visão geral

+ +

Usar os sinalizadores da classe {@link android.content.Intent} para adicionar uma tarefa permite maior controle sobre +quando e como um documento é aberto ou reaberto na tela de visão geral. Ao usar os atributos +<activity>, +é possível escolher entre sempre abrir o documento em uma nova tarefa ou reutilizar uma +tarefa existente para o documento.

+ +

Uso do sinalizador Intent para adicionar uma tarefa

+ +

Ao criar um novo documento para a atividade, você chama o método +{@link android.app.ActivityManager.AppTask#startActivity(android.content.Context, android.content.Intent, android.os.Bundle) startActivity()} +da classe {@link android.app.ActivityManager.AppTask}, passando a ele a intenção que +inicia a atividade. Para inserir uma quebra lógica para que o sistema trate a atividade como uma nova +tarefa na tela de visão geral, passe o sinalizador {@link android.content.Intent#FLAG_ACTIVITY_NEW_DOCUMENT} +no método {@link android.content.Intent#addFlags(int) addFlags()} da {@link android.content.Intent} +que inicia a atividade.

+ +

Observação: o sinalizador {@link android.content.Intent#FLAG_ACTIVITY_NEW_DOCUMENT} +substitui o sinalizador {@link android.content.Intent#FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET}, +obsoleto a partir do Android 5.0 (API de nível 21).

+ +

Se você usar o sinalizador {@link android.content.Intent#FLAG_ACTIVITY_MULTIPLE_TASK} ao criar +o novo documento, o sistema sempre criará uma nova tarefa com a atividade-alvo como raiz. +Essa configuração permite que o mesmo documento seja aberto em mais de uma tarefa. O código a seguir demonstra +como a atividade principal faz isso:

+ +

+DocumentCentricActivity.java

+
+public void createNewDocument(View view) {
+      final Intent newDocumentIntent = newDocumentIntent();
+      if (useMultipleTasks) {
+          newDocumentIntent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
+      }
+      startActivity(newDocumentIntent);
+  }
+
+  private Intent newDocumentIntent() {
+      boolean useMultipleTasks = mCheckbox.isChecked();
+      final Intent newDocumentIntent = new Intent(this, NewDocumentActivity.class);
+      newDocumentIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
+      newDocumentIntent.putExtra(KEY_EXTRA_NEW_DOCUMENT_COUNTER, incrementAndGet());
+      return newDocumentIntent;
+  }
+
+  private static int incrementAndGet() {
+      Log.d(TAG, "incrementAndGet(): " + mDocumentCounter);
+      return mDocumentCounter++;
+  }
+}
+
+ +

Observação: Atividades iniciadas com o sinalizador {@code FLAG_ACTIVITY_NEW_DOCUMENT} +devem ter o valor do atributo {@code android:launchMode="standard"} (o padrão) definido no +manifesto.

+ +

Quando a atividade principal inicia uma nova atividade, o sistema procura nas tarefas existentes uma +cuja intenção corresponda ao nome do componente da intenção e aos dados de Intent para a atividade. Se a tarefa +não for encontrada ou se a intenção continha o sinalizador {@link android.content.Intent#FLAG_ACTIVITY_MULTIPLE_TASK}, +uma nova tarefa será criada com a atividade como raiz. Se o sistema encontrar uma tarefa, ele a trará +para a frente e passará a nova intenção para {@link android.app.Activity#onNewIntent onNewIntent()}. +A nova atividade receberá a intenção e criará um novo documento na tela de visão geral, como no +exemplo a seguir:

+ +

+NewDocumentActivity.java

+
+@Override
+protected void onCreate(Bundle savedInstanceState) {
+    super.onCreate(savedInstanceState);
+    setContentView(R.layout.activity_new_document);
+    mDocumentCount = getIntent()
+            .getIntExtra(DocumentCentricActivity.KEY_EXTRA_NEW_DOCUMENT_COUNTER, 0);
+    mDocumentCounterTextView = (TextView) findViewById(
+            R.id.hello_new_document_text_view);
+    setDocumentCounterText(R.string.hello_new_document_counter);
+}
+
+@Override
+protected void onNewIntent(Intent intent) {
+    super.onNewIntent(intent);
+    /* If FLAG_ACTIVITY_MULTIPLE_TASK has not been used, this activity
+    is reused to create a new document.
+     */
+    setDocumentCounterText(R.string.reusing_document_counter);
+}
+
+ + +

Uso do atributo Activity para adicionar uma tarefa

+ +

Uma atividade também pode especificar em seu manifesto que sempre iniciará uma nova tarefa usando +o atributo <activity>, + +{@code android:documentLaunchMode}. Esse atributo tem quatro valores que produzem os seguintes +efeitos quando o usuário abre um documento com o aplicativo:

+ +
+
"{@code intoExisting}"
+
A atividade reutiliza uma tarefa existente para o documento. Isso é o mesmo que configurar o + sinalizador {@link android.content.Intent#FLAG_ACTIVITY_NEW_DOCUMENT} sem configurar + o sinalizador {@link android.content.Intent#FLAG_ACTIVITY_MULTIPLE_TASK}, como descrito em + Uso do sinalizador Intent para adicionar uma tarefa acima.
+ +
"{@code always}"
+
A atividade cria uma nova tarefa para o documento, mesmo se o mesmo já estiver aberto. Usar + esse valor é o mesmo que configurar os sinalizadores {@link android.content.Intent#FLAG_ACTIVITY_NEW_DOCUMENT} + e {@link android.content.Intent#FLAG_ACTIVITY_MULTIPLE_TASK}.
+ +
"{@code none”}"
+
A atividade não cria uma nova tarefa para o documento. A tela de visão geral trata a + atividade como aconteceria por padrão: ela exibe uma tarefa para o aplicativo, que + retoma a atividade invocada por último pelo usuário.
+ +
"{@code never}"
+
A atividade não cria uma nova tarefa para o documento. Definir esse valor substitui o + comportamento dos sinalizadores {@link android.content.Intent#FLAG_ACTIVITY_NEW_DOCUMENT} + e {@link android.content.Intent#FLAG_ACTIVITY_MULTIPLE_TASK}, caso um deles esteja definido + na intenção, e a tela de visão geral exibe uma tarefa para o aplicativo, que retoma a + atividade invocada por último pelo usuário.
+
+ +

Observação: para valores diferentes de {@code none} e {@code never}, a +atividade deve ser definida com {@code launchMode="standard"}. Se esse atributo não for especificado, +{@code documentLaunchMode="none"} será usado.

+ +

Remoção de tarefas

+ +

Por padrão, uma tarefa de documento é automaticamente removida da tela de visão geral quando a atividade +termina. Esse comportamento pode ser substituído com a classe {@link android.app.ActivityManager.AppTask}, +com um sinalizador {@link android.content.Intent} ou com um atributo +<activity>.

+ +

É possível excluir inteiramente uma tarefa da tela de visão geral definindo o +atributo <activity>, + +{@code android:excludeFromRecents} como {@code true}.

+ +

É possível definir o número máximo de tarefas que o aplicativo pode incluir na tela de visão geral definindo +o atributo <activity> +{@code android:maxRecents} + como um valor inteiro. O padrão é 16. Quando o número máximo de tarefas é atingido, a tarefa usada menos +recentemente é removida da tela de visão geral. O valor máximo de {@code android:maxRecents} +é 50 (25 em dispositivos com pouca memória); valores menores que 1 não são válidos.

+ +

Uso da classe AppTask para remover tarefas

+ +

Na atividade que cria uma nova tarefa na tela de visão geral, é possível +especificar quando remover a tarefa e terminar todas as atividades associadas a ela chamando +o método {@link android.app.ActivityManager.AppTask#finishAndRemoveTask() finishAndRemoveTask()}.

+ +

+NewDocumentActivity.java

+
+public void onRemoveFromRecents(View view) {
+    // The document is no longer needed; remove its task.
+    finishAndRemoveTask();
+}
+
+ +

Observação: o uso +do método {@link android.app.ActivityManager.AppTask#finishAndRemoveTask() finishAndRemoveTask()} +substitui o uso do sinalizador {@link android.content.Intent#FLAG_ACTIVITY_RETAIN_IN_RECENTS} +discutido abaixo.

+ +

Retenção de tarefas terminadas

+ +

Se você deseja reter uma tarefa na tela de visão geral, mesmo que a atividade tenha terminado, passe +o sinalizador {@link android.content.Intent#FLAG_ACTIVITY_RETAIN_IN_RECENTS} no +método {@link android.content.Intent#addFlags(int) addFlags()} da Intent que inicia a atividade.

+ +

+DocumentCentricActivity.java

+
+private Intent newDocumentIntent() {
+    final Intent newDocumentIntent = new Intent(this, NewDocumentActivity.class);
+    newDocumentIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT |
+      android.content.Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS);
+    newDocumentIntent.putExtra(KEY_EXTRA_NEW_DOCUMENT_COUNTER, incrementAndGet());
+    return newDocumentIntent;
+}
+
+ +

Para obter o mesmo efeito, defina o +atributo <activity> + +{@code android:autoRemoveFromRecents} como {@code false}. O valor padrão é {@code true} +para atividades de documentos e {@code false} para atividades comuns. Usar esse atributo substitui +o sinalizador {@link android.content.Intent#FLAG_ACTIVITY_RETAIN_IN_RECENTS} discutido anteriormente.

+ + + + + + + diff --git a/docs/html-intl/intl/pt-br/guide/components/services.jd b/docs/html-intl/intl/pt-br/guide/components/services.jd new file mode 100644 index 0000000000000000000000000000000000000000..123d90ac9cb32554292672cfa0bb870b43475b42 --- /dev/null +++ b/docs/html-intl/intl/pt-br/guide/components/services.jd @@ -0,0 +1,813 @@ +page.title=Serviços +@jd:body + + + + +

Um {@link android.app.Service} é um componente do aplicativo que pode realizar +operações longas e não fornece uma interface do usuário. Outro componente do aplicativo +pode iniciar um serviço e ele continuará em execução em segundo plano mesmo que o usuário +alterne para outro aplicativo. Além disso, um componente poderá vincular-se a um serviço +para interagir com ele e até estabelecer comunicação entre processos (IPC). Por exemplo, um serviço pode lidar +com operações de rede, reproduzir música, executar E/S de arquivos, ou interagir com um provedor de conteúdo, +tudo a partir do segundo plano.

+ +

Um serviço pode essencialmente ter duas formas:

+ +
+
Iniciado
+
Um serviço é "iniciado" quando um componente do aplicativo (como um atividade) +inicia-o chamando {@link android.content.Context#startService startService()}. Quando iniciado, um serviço +pode ficar em execução em segundo plano indefinidamente, mesmo que o componente que o iniciou seja destruído. Geralmente, +um serviço iniciado realiza uma única operação e não retorna um resultado para o autor da chamada. +Por exemplo, ele pode fazer download ou upload de um arquivo pela rede. Quando a operação for concluída, +o serviço deverá ser interrompido.
+
Vinculado
+
Um serviço é "vinculado" quando um componente do aplicativo o vincula chamando {@link +android.content.Context#bindService bindService()}. Um serviço vinculado oferece uma interface +servidor-cliente que permite que os componentes interajam com a interface, enviem solicitações, obtenham resultados, +mesmo em processos com comunicação interprocessual (IPC). Um serviço vinculado permanece em execução somente enquanto +outro componente do aplicativo estiver vinculado a ele. Vários componentes podem ser vinculados ao serviço de uma só vez, +mas quando todos desfizerem o vínculo, o serviço será destruído.
+
+ +

Apesar de esta documentação discutir os dois tipos de serviços separadamente, +um serviço pode funcionar das duas maneiras — ele pode ser iniciado (para permanecer em execução indefinidamente) e também permitir a vinculação. +Basta você implementar alguns métodos de retorno de chamada: {@link +android.app.Service#onStartCommand onStartCommand()} para permitir que os componentes iniciem o serviço e {@link +android.app.Service#onBind onBind()} para permitir a vinculação.

+ +

Se o aplicativo for iniciado, vinculado ou ambos, qualquer componente do aplicativo +poderá usar o serviço (mesmo a partir de um aplicativo separado), da mesma forma que qualquer componente poderá usar +uma atividade — iniciando com uma {@link android.content.Intent}. No entanto, é possível declarar +o serviço como privado, no arquivo do manifesto, e bloquear o acesso de outros aplicativos. Isto é discutido com mais detalhes +na seção Declaração do serviço +no manifesto.

+ +

Atenção: um serviço é executado +no encadeamento principal em seu processo de hospedagem — o serviço não cria seu próprio encadeamento +e não é executado em um processo separado (salvo se especificado). Isso significa que, + se o serviço for realizar qualquer trabalho intensivo de CPU ou operações de bloqueio (como reprodução +de MP3 ou rede), você deve criar um novo encadeamento dentro do serviço. Usando um encadeamento separado, +você reduzirá o risco da ocorrência de erros de Aplicativo não respondendo (ANR) +e o encadeamento principal do aplicativo poderá permanecer dedicado à interação do usuário com as atividades.

+ + +

Conceitos básicos

+ + + +

Para criar um serviço, você deve criar uma subclasse de {@link android.app.Service} +(ou uma das subclasses existentes). Na implementação, será necessário substituir alguns métodos de retorno de chamada +que lidem com aspectos essenciais do ciclo de vida do serviço e forneçam um mecanismo para vincular +componentes ao serviço, se apropriado. Os dois métodos mais importantes de retorno de chamada que você deve substituir são:

+ +
+
{@link android.app.Service#onStartCommand onStartCommand()}
+
O sistema chama este método quando outro componente, como uma atividade, +solicita que o serviço seja iniciado, chamando {@link android.content.Context#startService +startService()}. Quando este método é executado, o serviço é iniciado e pode ser executado +em segundo plano indefinidamente. Se implementar isto, é de sua responsabilidade interromper o serviço +quando o trabalho for concluído, chamando {@link android.app.Service#stopSelf stopSelf()} ou {@link +android.content.Context#stopService stopService()} (caso queira somente fornecer a vinculação, +não é necessário implementar este método).
+
{@link android.app.Service#onBind onBind()}
+
O sistema chama este método quando outro componente quer vincular-se +ao serviço (como para realizações de RPC) chamando {@link android.content.Context#bindService +bindService()}. Na implementação deste método, você deve fornecer uma interface que os clientes +usem para comunicar-se com o serviço, retornando um {@link android.os.IBinder}. Você sempre deve implementar este método, +mas, se não quiser permitir a vinculação, retorne nulo.
+
{@link android.app.Service#onCreate()}
+
O sistema chama este método quando o serviço é criado pela primeira vez para realizar procedimentos únicos +de configuração (antes de chamar {@link android.app.Service#onStartCommand onStartCommand()} ou +{@link android.app.Service#onBind onBind()}). Se o serviço já estiver em execução, este método +não é chamado.
+
{@link android.app.Service#onDestroy()}
+
O sistema chama este método quando o serviço não é mais usado e está sendo destruído. +O serviço deve implementar isto para limpar quaisquer recursos, como encadeamentos, escutas +registradas, receptores etc. Esta é a última chamada que o serviço recebe.
+
+ +

Se um componente iniciar o serviço chamando {@link +android.content.Context#startService startService()} (o que resulta em uma chamada para {@link +android.app.Service#onStartCommand onStartCommand()}), +o serviço permanecerá em execução até ser interrompido por conta própria com {@link android.app.Service#stopSelf()} ou por outro componente +chamando {@link android.content.Context#stopService stopService()}.

+ +

Se um componente +chamar {@link android.content.Context#bindService bindService()} para criar o serviço (e {@link +android.app.Service#onStartCommand onStartCommand()} não for chamado), o serviço será executado +somente enquanto o componente estiver vinculado a ele. Quando o serviço for desvinculado de todos os clientes, +o sistema o destruirá.

+ +

O sistema Android forçará a interrupção de um serviço somente quando a memória estiver baixa e precisar +recuperar os recursos do sistema para a atividade com que o usuário estiver interagindo. Se o serviço estiver vinculado a uma atividade que o usuário +tenha atribuído foco, é menos provável que ele seja eliminado. E, se o serviço for declarado para ser executado em primeiro plano (discutido posteriormente), então ele quase nunca será eliminado. +Caso contrário, se o serviço for iniciado e estiver em uma longa execução, o sistema reduzirá sua posição +na lista de tarefas de segundo plano no decorrer do tempo e o serviço se tornará altamente suscetível +à eliminação — se o serviço for iniciado, então você deve programá-lo para lidar +com reinicializações pelo sistema. Se o sistema eliminar o seu serviço, ele reiniciará assim que os recursos +estiverem disponíveis novamente (apesar de isto também depender do valor que você retornar de {@link +android.app.Service#onStartCommand onStartCommand()}, como discutido a seguir). Para obter mais informações sobre +quando o sistema deve destruir um serviço, consulte o documento Processos e encadeamentos +.

+ +

Nas seguintes seções, você verá como é possível criar cada tipo de serviço e como +usá-los a partir de outros componentes do aplicativo.

+ + + +

Declaração de serviço no manifesto

+ +

Como atividades (e outros componentes), você deve declarar todos os serviços no arquivo de manifesto +do aplicativo.

+ +

Para declarar o serviço, adicione um elemento {@code <service>} +como filho do elemento {@code <application>} +. Por exemplo:

+ +
+<manifest ... >
+  ...
+  <application ... >
+      <service android:name=".ExampleService" />
+      ...
+  </application>
+</manifest>
+
+ +

Consulte a referência de elemento {@code <service>} +para obter mais informações sobre como declarar o serviço no manifesto.

+ +

É possível incluir outros atributos no elemento {@code <service>} +para definir propriedades como permissões necessárias para iniciar o serviço e o processo +em que o serviço deve ser executado. O atributo {@code android:name} +é o único necessário — ele especifica o nome da classe do serviço. Ao publicar o aplicativo, + não deve-se alterar-lhe o nome porque, se isso acontecer, há riscos de ocorrência de erros +de código devido à dependência de intenções explícitas para iniciar ou vincular o serviço (leia a publicação do blogue, Coisas +que não podem mudar). + +

Para garantir a segurança do aplicativo, sempre use uma intenção explícita ao iniciar ou vincular +{@link android.app.Service} e não declare filtros de intenção para o serviço. Caso seja essencial permitir alguma ambiguidade, como qual serviço deve ser usado para iniciar, +é possível fornecer filtros de intenções +para os serviços e excluir o nome do componente de {@link +android.content.Intent}, mas é preciso definir o pacote para a intenção com {@link +android.content.Intent#setPackage setPackage()}, que fornece desambiguidade suficiente para +o serviço alvo.

+ +

Além disso, é possível garantir que o serviço esteja disponível somente para o aplicativo +incluindo o atributo {@code android:exported} +e definindo-o como {@code "false"}. Isto impede efetivamente que outros aplicativos iniciem o serviço, + mesmo usando uma intenção explícita.

+ + + + +

Criação de um serviço iniciado

+ +

Um serviço iniciado é o que outro componente inicia chamando {@link +android.content.Context#startService startService()}, resultando em uma chamada para o método +{@link android.app.Service#onStartCommand onStartCommand()} do serviço.

+ +

Quando um serviço é iniciado, ele tem um ciclo de vida que não depende do componente +que o iniciou. Além disso, o serviço pode ser executado em segundo plano indefinidamente, +mesmo se o componente que o iniciou for eliminado. Além disso, o serviço deve ser interrompido quando o seu trabalho +for realizado, chamando {@link android.app.Service#stopSelf stopSelf()}. Outros componentes podem interrompê-lo +chamando {@link android.content.Context#stopService stopService()}.

+ +

Um componente do aplicativo, como uma atividade, pode iniciar o serviço chamando {@link +android.content.Context#startService startService()} e passando {@link android.content.Intent}, +que especifica o serviço e inclui os dados para o serviço usar. O serviço +recebe esta {@link android.content.Intent} no método {@link android.app.Service#onStartCommand +onStartCommand()}.

+ +

Por exemplo, presuma que uma atividade precise salvar alguns dados em um banco de dados on-line. A atividade pode iniciar +um serviço de acompanhamento e entregar a ele os dados a salvar passando uma intenção para {@link +android.content.Context#startService startService()}. O serviço recebe a intenção em {@link +android.app.Service#onStartCommand onStartCommand()}, conecta-se à Internet e realiza +a transação no banco de dados. Quando a operação é concluída, o serviço é interrompido +e destruído.

+ +

Atenção: um serviço é executado no mesmo processo que o aplicativo +em que ele é declarado e no encadeamento principal do aplicativo, por padrão. Portanto, se o serviço +realizar operações intensivas ou de bloqueio quando o usuário interagir com uma atividade do mesmo aplicativo, +diminuirá o desempenho da atividade. Para evitar impacto no desempenho do aplicativo, +deve-se iniciar um novo encadeamento dentro do serviço.

+ +

Geralmente, há duas classes que podem ser estendidas para criar um serviço iniciado:

+
+
{@link android.app.Service}
+
Esta é a classe de base para todos os serviços. Ao estender esta classe, é importante +criar um novo encadeamento para fazer todo o trabalho do serviço, pois o serviço usa +o encadeamento principal do aplicativo, por padrão, o que deve diminuir o desempenho de qualquer atividade +que o aplicativo estiver executando.
+
{@link android.app.IntentService}
+
Esta é uma subclasse de {@link android.app.Service} que usa um encadeamento de trabalho para lidar +com todas as solicitações de inicialização, uma por vez. Esta é a melhor opção se não quiser que o serviço +lide com várias solicitações simultaneamente. Tudo que precisa fazer é implementar {@link +android.app.IntentService#onHandleIntent onHandleIntent()}, que recebe a intenção para cada solicitação +de início para que você possa realizar o trabalho de segundo plano.
+
+ +

As seguintes seções descrevem como é possível implementar o serviço usando +uma dessas classes.

+ + +

Extensão da classe IntentService

+ +

Como a maioria dos serviços não precisam lidar com várias solicitações simultaneamente +(o que pode ser perigoso para situações de vários encadeamentos), é melhor +se o serviço for implementado usando a classe {@link android.app.IntentService}.

+ +

O {@link android.app.IntentService} faz o seguinte:

+ +
    +
  • Cria um encadeamento de trabalho padrão que executa todas as intenções entregues a {@link +android.app.Service#onStartCommand onStartCommand()} separado do encadeamento principal +do aplicativo.
  • +
  • Cria uma fila de trabalho que passa uma intenção por vez à implementação {@link +android.app.IntentService#onHandleIntent onHandleIntent()}, para que nunca seja necessário +preocupar-se com vários encadeamentos.
  • +
  • Interrompe o serviço depois que todas as solicitações forem resolvidas para que não seja necessário chamar +{@link android.app.Service#stopSelf}.
  • +
  • Fornece implementações padrão de {@link android.app.IntentService#onBind onBind()} +que retornam como nulo.
  • +
  • Fornece uma implementação padrão de {@link android.app.IntentService#onStartCommand +onStartCommand()} que envia a intenção para a fila de trabalho e, em seguida, para a implementação de {@link +android.app.IntentService#onHandleIntent onHandleIntent()}.
  • +
+ +

Tudo isso adiciona-se ao fato de que basta implementar {@link +android.app.IntentService#onHandleIntent onHandleIntent()} para fazer o trabalho fornecido +pelo cliente (embora também seja necessário fornecer um pequeno construtor para o serviço).

+ +

A seguir há um exemplo de implementação de {@link android.app.IntentService}:

+ +
+public class HelloIntentService extends IntentService {
+
+  /**
+   * A constructor is required, and must call the super {@link android.app.IntentService#IntentService}
+   * constructor with a name for the worker thread.
+   */
+  public HelloIntentService() {
+      super("HelloIntentService");
+  }
+
+  /**
+   * The IntentService calls this method from the default worker thread with
+   * the intent that started the service. When this method returns, IntentService
+   * stops the service, as appropriate.
+   */
+  @Override
+  protected void onHandleIntent(Intent intent) {
+      // Normally we would do some work here, like download a file.
+      // For our sample, we just sleep for 5 seconds.
+      long endTime = System.currentTimeMillis() + 5*1000;
+      while (System.currentTimeMillis() < endTime) {
+          synchronized (this) {
+              try {
+                  wait(endTime - System.currentTimeMillis());
+              } catch (Exception e) {
+              }
+          }
+      }
+  }
+}
+
+ +

É tudo que você precisa: um construtor e uma implementação de {@link +android.app.IntentService#onHandleIntent onHandleIntent()}.

+ +

Caso decida substituir outros métodos de retorno de chamada, como {@link +android.app.IntentService#onCreate onCreate()}, {@link +android.app.IntentService#onStartCommand onStartCommand()} ou {@link +android.app.IntentService#onDestroy onDestroy()}, certifique-se de chamar a super-implementação +para que o {@link android.app.IntentService} possa lidar adequadamente com a vida do encadeamento de trabalho.

+ +

Por exemplo, {@link android.app.IntentService#onStartCommand onStartCommand()} deve retornar +a implementação padrão (que é como a intenção é entregue para {@link +android.app.IntentService#onHandleIntent onHandleIntent()}):

+ +
+@Override
+public int onStartCommand(Intent intent, int flags, int startId) {
+    Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show();
+    return super.onStartCommand(intent,flags,startId);
+}
+
+ +

Além de {@link android.app.IntentService#onHandleIntent onHandleIntent()}, o único método +que não precisa ser usado para chamar a superclasse é {@link android.app.IntentService#onBind +onBind()} (mas é necessário implementá-lo se o serviço permitir vinculações).

+ +

Na próxima seção, você verá como o mesmo tipo de serviço é implementado ao estender +a classe de base {@link android.app.Service}, que possui muito mais códigos, mas que pode +ser apropriada para lidar com solicitações de inicialização simultâneas.

+ + +

Extensão da classe Service

+ +

Como você viu na seção anterior, o uso de {@link android.app.IntentService} +torna a implementação de um serviço iniciado muito simples. Se, no entanto, você precisa do serviço +para realizar vários encadeamentos (em vez de processar as solicitações de inicialização por meio de uma fila de trabalho), +é possível estender a classe {@link android.app.Service} para lidar com cada intenção.

+ +

Para efeito de comparação, o seguinte exemplo de código é uma implementação da classe {@link +android.app.Service} que realiza exatamente o mesmo trabalho que o exemplo acima usando {@link +android.app.IntentService}. Ou seja, para cada solicitação de inicialização, ela usa um encadeamento +de trabalho para realizar o trabalho e processa apenas uma solicitação por vez.

+ +
+public class HelloService extends Service {
+  private Looper mServiceLooper;
+  private ServiceHandler mServiceHandler;
+
+  // Handler that receives messages from the thread
+  private final class ServiceHandler extends Handler {
+      public ServiceHandler(Looper looper) {
+          super(looper);
+      }
+      @Override
+      public void handleMessage(Message msg) {
+          // Normally we would do some work here, like download a file.
+          // For our sample, we just sleep for 5 seconds.
+          long endTime = System.currentTimeMillis() + 5*1000;
+          while (System.currentTimeMillis() < endTime) {
+              synchronized (this) {
+                  try {
+                      wait(endTime - System.currentTimeMillis());
+                  } catch (Exception e) {
+                  }
+              }
+          }
+          // Stop the service using the startId, so that we don't stop
+          // the service in the middle of handling another job
+          stopSelf(msg.arg1);
+      }
+  }
+
+  @Override
+  public void onCreate() {
+    // Start up the thread running the service.  Note that we create a
+    // separate thread because the service normally runs in the process's
+    // main thread, which we don't want to block.  We also make it
+    // background priority so CPU-intensive work will not disrupt our UI.
+    HandlerThread thread = new HandlerThread("ServiceStartArguments",
+            Process.THREAD_PRIORITY_BACKGROUND);
+    thread.start();
+
+    // Get the HandlerThread's Looper and use it for our Handler
+    mServiceLooper = thread.getLooper();
+    mServiceHandler = new ServiceHandler(mServiceLooper);
+  }
+
+  @Override
+  public int onStartCommand(Intent intent, int flags, int startId) {
+      Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show();
+
+      // For each start request, send a message to start a job and deliver the
+      // start ID so we know which request we're stopping when we finish the job
+      Message msg = mServiceHandler.obtainMessage();
+      msg.arg1 = startId;
+      mServiceHandler.sendMessage(msg);
+
+      // If we get killed, after returning from here, restart
+      return START_STICKY;
+  }
+
+  @Override
+  public IBinder onBind(Intent intent) {
+      // We don't provide binding, so return null
+      return null;
+  }
+
+  @Override
+  public void onDestroy() {
+    Toast.makeText(this, "service done", Toast.LENGTH_SHORT).show();
+  }
+}
+
+ +

Como pode ver, é muito mais trabalhoso do que usar {@link android.app.IntentService}.

+ +

No entanto, como você lida com cada chamada de {@link android.app.Service#onStartCommand +onStartCommand()}, é possível realizar várias solicitações simultaneamente. Isso não é o que este exemplo faz, +mas, se for o que quer, é possível criar um novo encadeamento +para cada solicitação e executá-las na hora (em vez de esperar que a solicitação anterior seja concluída).

+ +

Observe que o método {@link android.app.Service#onStartCommand onStartCommand()} deve retornar +um número inteiro. O número inteiro é um valor que descreve como o sistema deve continuar o serviço +no evento em que o sistema o eliminar (como discutido acima, a implementação padrão de {@link +android.app.IntentService} lida com isto, apesar de ser possível modificá-lo). O valor de retorno +de {@link android.app.Service#onStartCommand onStartCommand()} deve ser uma das seguintes +constantes:

+ +
+
{@link android.app.Service#START_NOT_STICKY}
+
Se o sistema eliminar o serviço após o retorno de {@link android.app.Service#onStartCommand +onStartCommand()}, não recrie o serviço, a não ser que tenha +intenções pendentes para entregar. Esta é a opção mais segura para evitar executar o serviço quando desnecessário +e quando o aplicativo puder simplesmente reiniciar qualquer trabalho incompleto.
+
{@link android.app.Service#START_STICKY}
+
Se o sistema eliminar o serviço após o retorno de {@link android.app.Service#onStartCommand +onStartCommand()}, recrie o serviço e chame {@link +android.app.Service#onStartCommand onStartCommand()}, mas não entregue novamente a última intenção. +Em vez disso, o sistema chama {@link android.app.Service#onStartCommand onStartCommand()} com intenção nula, +a não ser que tenha intenções pendentes para iniciar o serviço e, +nesse caso, essas intenções são entregues. Isto é adequado para reprodutores de mídia (ou serviços semelhantes) +que não estejam executando comandos, mas estejam em execução indefinidamente e aguardando um trabalho.
+
{@link android.app.Service#START_REDELIVER_INTENT}
+
Se o sistema eliminar o serviço após o retorno de {@link android.app.Service#onStartCommand +onStartCommand()}, recrie o serviço e chame {@link +android.app.Service#onStartCommand onStartCommand()} com a última intenção que foi entregue +ao serviço. Quaisquer intenções pendentes são entregues, por sua vez. Isto é adequado para serviços que estejam realizando +um trabalho ativamente que deva ser retomado imediatamente, como o download de um arquivo.
+
+

Para obter mais informações sobre esses valores de retorno, veja a documentação de referência vinculada +a cada constante.

+ + + +

Início de um serviço

+ +

É possível iniciar um dispositivo de uma atividade ou outro componente do aplicativo passando uma +{@link android.content.Intent} a {@link +android.content.Context#startService startService()}. O sistema Android chama o método {@link +android.app.Service#onStartCommand onStartCommand()} do serviço e passa a ele a {@link +android.content.Intent} (nunca deve-se chamar {@link android.app.Service#onStartCommand +onStartCommand()} diretamente).

+ +

Por exemplo, uma atividade pode iniciar o serviço de exemplo na seção anterior ({@code +HelloSevice}) usando uma intenção explícita com {@link android.content.Context#startService +startService()}:

+ +
+Intent intent = new Intent(this, HelloService.class);
+startService(intent);
+
+ +

O método {@link android.content.Context#startService startService()} retorna imediatamente +e o sistema Android chama o método {@link android.app.Service#onStartCommand +onStartCommand()} do serviço. Se o serviço não estiver em execução, o sistemo primeiro chama {@link +android.app.Service#onCreate onCreate()} e, em seguida, chama {@link android.app.Service#onStartCommand +onStartCommand()}.

+ +

Se o serviço não fornecer vinculação, a intenção entregue com {@link +android.content.Context#startService startService()} é o único modo de comunicação entre +o componente do aplicativo e o serviço. No entanto, se quiser que o serviço envie um resultado de volta, +o cliente que iniciar o serviço poderá criar um {@link android.app.PendingIntent} para uma transmissão +(com {@link android.app.PendingIntent#getBroadcast getBroadcast()}) e entregá-la ao serviço +na {@link android.content.Intent} que iniciar o serviço. O serviço pode então +usar a transmissão para entregar um resultado.

+ +

Várias solicitações para iniciar o serviço resultam em diversas chamadas correspondentes +ao {@link android.app.Service#onStartCommand onStartCommand()} do serviço. No entanto, somente uma solicitação para interromper +o serviço (com {@link android.app.Service#stopSelf stopSelf()} ou {@link +android.content.Context#stopService stopService()}) é necessária para interrompê-lo.

+ + +

Interrupção de um serviço

+ +

Um serviço iniciado deve gerenciar seu próprio ciclo de vida. Ou seja, o sistema não interrompe +ou elimina o serviço, a não ser que tenha que recuperar a memória do sistema e o serviço +continuar em execução depois que {@link android.app.Service#onStartCommand onStartCommand()} retornar. Portanto, +o serviço deve ser interrompido chamando {@link android.app.Service#stopSelf stopSelf()} ou outro +componente pode interrompê-lo chamando {@link android.content.Context#stopService stopService()}.

+ +

Ao solicitar o interrompimento com {@link android.app.Service#stopSelf stopSelf()} ou {@link +android.content.Context#stopService stopService()}, o sistema elimina o serviço +assim que possível.

+ +

No entanto, se o serviço lida com várias solicitações para {@link +android.app.Service#onStartCommand onStartCommand()} ao mesmo tempo, não se deve interromper +o serviço ao terminar o processamento de uma solicitação de inicialização, pois é possível +que uma nova solicitação de inicialização tenha ocorrido (interromper no final da primeira solicitação eliminaria a segunda). Para evitar este problema, +é possível usar {@link android.app.Service#stopSelf(int)} para garantir que a solicitação +que interrompe o serviço sempre se baseie na solicitação de inicialização mais recente. Ou seja, ao chamar {@link +android.app.Service#stopSelf(int)}, você passa o ID da solicitação de inicialização (o startId +entregue a {@link android.app.Service#onStartCommand onStartCommand()}) para aquele que solicitação de interrompimento +corresponde. Em seguida, se o serviço receber uma nova solicitação de inicialização antes de ser possível chamar {@link +android.app.Service#stopSelf(int)}, o ID não corresponderá e o serviço não será interrompido.

+ +

Atenção: é importante que um aplicativo interrompa seus serviços +quando terminar os trabalhos para evitar o desperdício dos recursos do sistema e consumo da bateria. Se necessário, +outros componentes podem interromper o serviço chamando {@link +android.content.Context#stopService stopService()}. Mesmo se for possível vincular-se ao serviço, +deve-se sempre interrompê-lo por conta própria se ele tiver recebido uma chamada de {@link +android.app.Service#onStartCommand onStartCommand()}.

+ +

Para obter mais informações sobre o ciclo de vida de um serviço, consulte a seção Gerenciamento do ciclo de vida de um serviço abaixo.

+ + + +

Criação de um serviço vinculado

+ +

Um serviço vinculado permite que componentes de aplicativo sejam vinculados chamando {@link +android.content.Context#bindService bindService()} para criar uma conexão de longo prazo +(e, geralmente, não permite que os componentes o iniciem chamando {@link +android.content.Context#startService startService()}).

+ +

Deve-se criar um serviço vinculado quando se deseja interagir com o serviço a partir de atividades +e outros componentes no aplicativo ou para expor algumas das funcionalidades do aplicativo +para outros aplicativos, por meio de comunicação entre processos (IPC).

+ +

Para criar um serviço vinculado, você deve implementar o método de retorno de chamada {@link +android.app.Service#onBind onBind()} para retornar um {@link android.os.IBinder} +que define a interface para a comunicação com o serviço. Outros componentes de aplicativo podem chamar +{@link android.content.Context#bindService bindService()} para recuperar a interface +e começar a chamar métodos no serviço. O serviço vive somente para servir o componente do aplicativo +ao qual ele está vinculado. Portanto, quando não houver componentes vinculados ao serviço, o sistema o eliminará +(não é necessário interromper um serviço vinculado da mesma maneira que quando o serviço é iniciado +por meio de {@link android.app.Service#onStartCommand onStartCommand()}).

+ +

Para criar um serviço vinculado, a primeira coisa a se fazer é definir a interface que especifica +como um cliente pode comunicar-se com o servidor. Esta interface entre o serviço +e um cliente deve ser uma implementação de {@link android.os.IBinder} e é o que o serviço deve retornar +a partir do método de retorno de chamada {@link android.app.Service#onBind +onBind()}. Quando o cliente receber {@link android.os.IBinder}, ele poderá começar +a interagir com o serviço por meio da interface.

+ +

Vários clientes podem vincular-se ao serviço por vez. Quando um cliente terminar de interagir com o serviço, + ele chamará {@link android.content.Context#unbindService unbindService()} para desvincular-se. Quando não houver +clientes vinculados ao serviço, o sistema o eliminará.

+ +

Há várias maneiras de implementar um serviço vinculado e a implementação é mais complicada +que um serviço iniciado. Logo, a discussão sobre serviços vinculados aparece em um documento separado +sobre Serviços vinculados.

+ + + +

Enviar notificações ao usuário

+ +

Quando em execução, um serviço pode notificar o usuário sobre eventos usando Notificações de aviso ou Notificações da barra de status.

+ +

Uma notificação de aviso é uma mensagem que aparece na superfície da janela atual +por um breve momento antes de desaparecer, enquanto que uma notificação da barra de status fornece um ícone na barra de status +com uma mensagem que o usuário pode selecionar para realizar uma ação (como iniciar uma atividade).

+ +

Geralmente, uma notificação da barra de status é a melhor técnica quando um trabalho de segundo plano é concluído +(como download +de arquivo completo) e o usuário pode agir a partir dele. Quando o usuário seleciona a notificação a partir +da vista expandida, ela pode iniciar uma atividade (como a vista do arquivo baixado).

+ +

Consulte os guias de desenvolvedor Notificações de aviso ou Notificações da barra de status +para obter mais informações.

+ + + +

Execução de serviço em primeiro plano

+ +

Um serviço de primeiro plano é aquele +com que o usuário está ativamente interagindo e não é uma opção para o sistema eliminá-lo quando a memória estiver baixa. Um serviço de primeiro plano +deve fornecer uma notificação para a barra de status, que é colocada sob +"o cabeçalho "Em andamento", o que significa que a notificação não pode ser dispensada a não ser que o serviço +seja interrompido ou removido do primeiro plano.

+ +

Por exemplo, um reprodutor de música que reproduz a partir de um serviço deve ser configurado +para permanecer em execução em primeiro plano, pois o usuário está +prestando atenção em sua operação explicitamente. A notificação na barra de status pode indicar a música atual +e permitir que o usuário inicie uma atividade para interagir com o reprodutor de música.

+ +

Para solicitar que o serviço seja executado em primeiro plano, chame {@link +android.app.Service#startForeground startForeground()}. Este método usa dois parâmetros: um número inteiro +que identifica unicamente a notificação e {@link +android.app.Notification} para a barra de status. Por exemplo:

+ +
+Notification notification = new Notification(R.drawable.icon, getText(R.string.ticker_text),
+        System.currentTimeMillis());
+Intent notificationIntent = new Intent(this, ExampleActivity.class);
+PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);
+notification.setLatestEventInfo(this, getText(R.string.notification_title),
+        getText(R.string.notification_message), pendingIntent);
+startForeground(ONGOING_NOTIFICATION_ID, notification);
+
+ +

Atenção: O ID do número inteiro fornecido para {@link +android.app.Service#startForeground startForeground()} não deve ser zero.

+ + +

Para remover o serviço do primeiro plano, chame {@link +android.app.Service#stopForeground stopForeground()}. Este método usa um booleano, +indicando se deve remover também a notificação da barra de status. Este método não interrompe +o serviço. No entanto, se você interromper o serviço enquanto estiver em execução em primeiro plano, +a notificação também será removida.

+ +

Para obter mais informações sobre notificações, consulte Criação de notificações +da barra de status.

+ + + +

Gerenciamento do ciclo de vida de um serviço

+ +

O ciclo de vida de um serviço é muito mais simples do que o de uma atividade. No entanto, é ainda mais importante +que você preste muita atenção em como o serviço é criado e eliminado, pois ele +pode ser executado em primeiro plano sem que o usuário esteja ciente.

+ +

O ciclo de vida do serviço — desde quando é criado até ser eliminado — pode seguir +dois caminhos:

+ +
    +
  • Um serviço iniciado +

    O serviço é criado quando outro componente chama {@link +android.content.Context#startService startService()}. Depois, o serviço permanece em execução indefinidamente e deve +interromper-se chamando {@link +android.app.Service#stopSelf() stopSelf()}. Outro componente também pode interromper +o serviço chamando {@link +android.content.Context#stopService stopService()}. Quando o serviço é interrompido, o sistema o elimina.

  • + +
  • Um serviço vinculado +

    O serviço é criado quando outro componente (um cliente) chama {@link +android.content.Context#bindService bindService()}. O cliente comunica-se com o serviço +pela interface {@link android.os.IBinder}. O cliente pode escolher encerrar a conexão chamando +{@link android.content.Context#unbindService unbindService()}. Vários clientes podem ser vinculados +ao mesmo serviço e, quando todos os vínculos terminam, o sistema destrói o serviço (o serviço +não precisa ser interrompido).

  • +
+ +

Esses dois caminhos não são inteiramente separados. Ou seja, é possível vincular um serviço +que já foi iniciado com {@link android.content.Context#startService startService()}. Por exemplo, um serviço de música +de segundo plano pode ser iniciado chamando {@link android.content.Context#startService +startService()} com uma {@link android.content.Intent} que identifica a música reproduzida. Depois, +possivelmente quando o usuário quiser exercer mais controle sobre o reprodutor ou obter informações +sobre a música em reprodução, uma atividade pode ser vinculada ao serviço chamando {@link +android.content.Context#bindService bindService()}. Em casos como esse, {@link +android.content.Context#stopService stopService()} ou {@link android.app.Service#stopSelf +stopSelf()} não interrompe o serviço até que todos os clientes sejam desvinculados.

+ + +

Implementação de retornos de chamada do ciclo de vida

+ +

Como uma atividade, um serviço tem um método de ciclo de vida que é possível implementar +para monitorar as alterações no estado do serviço e realizar trabalhos em momentos adequados. O seguinte serviço de esqueleto +demonstra cada um dos métodos de ciclo de vida:

+ +
+public class ExampleService extends Service {
+    int mStartMode;       // indicates how to behave if the service is killed
+    IBinder mBinder;      // interface for clients that bind
+    boolean mAllowRebind; // indicates whether onRebind should be used
+
+    @Override
+    public void {@link android.app.Service#onCreate onCreate}() {
+        // The service is being created
+    }
+    @Override
+    public int {@link android.app.Service#onStartCommand onStartCommand}(Intent intent, int flags, int startId) {
+        // The service is starting, due to a call to {@link android.content.Context#startService startService()}
+        return mStartMode;
+    }
+    @Override
+    public IBinder {@link android.app.Service#onBind onBind}(Intent intent) {
+        // A client is binding to the service with {@link android.content.Context#bindService bindService()}
+        return mBinder;
+    }
+    @Override
+    public boolean {@link android.app.Service#onUnbind onUnbind}(Intent intent) {
+        // All clients have unbound with {@link android.content.Context#unbindService unbindService()}
+        return mAllowRebind;
+    }
+    @Override
+    public void {@link android.app.Service#onRebind onRebind}(Intent intent) {
+        // A client is binding to the service with {@link android.content.Context#bindService bindService()},
+        // after onUnbind() has already been called
+    }
+    @Override
+    public void {@link android.app.Service#onDestroy onDestroy}() {
+        // The service is no longer used and is being destroyed
+    }
+}
+
+ +

Observação: diferentemente dos métodos de retorno de chamada do ciclo de vida da atividade, +você não precisa chamar a implementação da superclasse.

+ + +

Figura 2. Ciclo de vida do serviço. O diagrama à esquerda +mostra o ciclo de vida quando o serviço é criado com {@link android.content.Context#startService +startService()} e o diagrama à direita mostra o ciclo de vida quando o serviço +é criado com {@link android.content.Context#bindService bindService()}.

+ +

Ao implementar esses métodos, é possível monitorar dois retornos (loops) aninhados no ciclo de vida do serviço:

+ +
    +
  • Todo o ciclo de vida de um serviço acontece entre o momento em que {@link +android.app.Service#onCreate onCreate()} é chamado e em que {@link +android.app.Service#onDestroy} retorna. Como uma atividade, um serviço faz a sua configuração inicial +em {@link android.app.Service#onCreate onCreate()} e libera todos os recursos restantes em {@link +android.app.Service#onDestroy onDestroy()}. Por exemplo, +um serviço de reprodução de música pode criar um encadeamento onde a música será reproduzida em {@link +android.app.Service#onCreate onCreate()}, e interromperá o encadeamento em {@link +android.app.Service#onDestroy onDestroy()}. + +

    Os métodos {@link android.app.Service#onCreate onCreate()} e {@link android.app.Service#onDestroy +onDestroy()} são chamados para todos os serviços, +se tiverem sido criados por {@link android.content.Context#startService startService()} ou {@link +android.content.Context#bindService bindService()}.

  • + +
  • O ciclo de vida ativo de um serviço começa com uma chamada de {@link +android.app.Service#onStartCommand onStartCommand()} ou {@link android.app.Service#onBind onBind()}. +Cada método entrega a {@link +android.content.Intent} que foi passada para {@link android.content.Context#startService +startService()} ou {@link android.content.Context#bindService bindService()}, respectivamente. +

    Se o serviço for iniciado, o ciclo de vida ativo terminará no mesmo momento +em que o ciclo de vida inteiro terminar (o serviço permanece ativo mesmo após o retorno de {@link android.app.Service#onStartCommand +onStartCommand()}). Se o serviço estiver vinculado, o ciclo de ida ativo acabará quando {@link +android.app.Service#onUnbind onUnbind()} retornar.

    +
  • +
+ +

Observação: apesar de um serviço iniciado ser interrompido com uma chamada +de {@link android.app.Service#stopSelf stopSelf()} ou {@link +android.content.Context#stopService stopService()}, não há um retorno de chamada respectivo +para o serviço (não há retorno de chamada de {@code onStop()}). Portanto, a não ser que o serviço esteja vinculado a um cliente, +o sistema o eliminará quando for interrompido — {@link +android.app.Service#onDestroy onDestroy()} será o único retorno de chamada recebido.

+ +

A figura 2 ilustra os métodos de retorno de chamada tradicionais para um serviço. Apesar de a figura separar +os serviços que são criados por {@link android.content.Context#startService startService()} +daqueles que são criados por {@link android.content.Context#bindService bindService()}, +observe que qualquer serviço, não importa como foi iniciado, pode permitir a vinculação de clientes. +Portanto, um serviço que já foi iniciado com {@link android.app.Service#onStartCommand +onStartCommand()} (por um cliente chamando {@link android.content.Context#startService startService()}) +ainda pode receber uma chamada de {@link android.app.Service#onBind onBind()} (quando um cliente chama +{@link android.content.Context#bindService bindService()}).

+ +

Para obter mais informações sobre como criar um serviço que forneça vinculação, consulte o documento Serviços vinculados, +que aborda mais profundamente o método de retorno de chamada {@link android.app.Service#onRebind onRebind()} +na seção sobre Gerenciamento do ciclo de vida +de um serviço vinculado.

+ + + diff --git a/docs/html-intl/intl/pt-br/guide/components/tasks-and-back-stack.jd b/docs/html-intl/intl/pt-br/guide/components/tasks-and-back-stack.jd new file mode 100644 index 0000000000000000000000000000000000000000..d309c6704f3765f19332f60021d1e5c20f08ffbe --- /dev/null +++ b/docs/html-intl/intl/pt-br/guide/components/tasks-and-back-stack.jd @@ -0,0 +1,578 @@ +page.title=Tarefas e pilhas de retorno +parent.title=Atividades +parent.link=activities.html +@jd:body + + + + +

Os aplicativos normalmente contêm diversas atividades. Cada atividade +deve ser projetada com relação a tipos específicos de ações que o usuário pode realizar e que podem iniciar outras +atividades. Por exemplo: um aplicativo de e-mail pode ter uma atividade para exibir uma lista de novas mensagens. +Quando o usuário seleciona uma mensagem, uma nova atividade abre para exibir essa mensagem.

+ +

As atividades podem também iniciar atividades existentes em outros aplicativos no dispositivo. Por +exemplo: se o seu aplicativo deseja enviar um e-mail, é possível definir uma intenção para realizar +uma ação de "enviar" e incluir alguns dados, como um endereço de e-mail e uma mensagem. Uma atividade de outro +aplicativo que se declara para tratar deste tipo de intenções, então, abre-se. Nesse caso, a intenção +destina-se ao envio de e-mails, portanto inicia-se uma atividade de “composição" do aplicativo de e-mail (se diversas atividades +forem compatíveis com a mesma intenção, o sistema permitirá que o usuário selecione qual usar). Quando o e-mail é +enviado, sua atividade reinicia, parecendo que a atividade de e-mail faz parte do seu aplicativo. Apesar +de as atividades serem de aplicativos diferentes, o Android mantém essa experiência +do usuário retilínea mantendo ambas as atividades na mesma tarefa.

+ +

Tarefas são coleções de atividades com as quais os usuários interagem +ao realizar determinado trabalho. As atividades são organizadas em uma pilha (a pilha de retorno) +na ordem em que cada atividade é aberta.

+ + + +

A tela inicial do dispositivo é o ponto de partida para a maioria das tarefas. Quando o usuário toca em um ícone no inicializador do +aplicativo + (ou em um atalho na tela inicial), essa tarefa do aplicativo acontece em primeiro plano. Se não +existir nenhuma tarefa para o aplicativo (se o aplicativo não tiver sido usado recentemente), uma nova tarefa +será criada e a atividade "principal" daquele aplicativo abrirá como a atividade raiz na pilha.

+ +

Quando a atividade atual inicia outra, a nova atividade é colocada no topo da pilha +e recebe foco. A atividade anterior permanece na pilha, mas é interrompida. Quando uma atividade +para, o sistema retém o estado atual da interface do usuário. Quando o usuário pressiona o botão +Voltar +, a atividade atual é retirada do topo da pilha (a atividade é destruída) +e a atividade anterior reinicia (o estado anterior da IU é restaurado). Atividades na pilha nunca +são reorganizadas, somente colocadas e retiradas da pilha — colocadas na pilha quando iniciadas +pela atividade atual e retiradas quando o usuário se retira dela usando o botão Voltar. Desse modo, a pilha +de retorno +opera como uma estrutura de objeto UEPS (último que entra, primeiro que sai). A figura 1 +ilustra esse comportamento com uma linha cronológica exibindo o progresso entre atividades junto com a pilha de retorno +atual em cada ponto no tempo.

+ + +

Figura 1. Representação de como cada nova atividade +em uma tarefa adiciona um item à pilha de retorno. Quando o usuário pressiona o botão Voltar, +a atividade atual é +destruída e a atividade anterior reinicia.

+ + +

Se o usuário continua pressionando Voltar, cada atividade na pilha é retirada para +revelar +a anterior até que o usuário retorne à tela inicial (ou a qualquer atividade que estivesse em execução +no começo da tarefa). Quando todas as atividades forem removidas da pilha, a tarefa não existirá mais.

+ +
+

Figura 2. Duas tarefas: a tarefa B recebe a interação do usuário +em primeiro plano enquanto a tarefa A está em segundo plano aguardando para ser retomada.

+
+
+

Figura 3. Uma única atividade é instanciada diversas vezes.

+
+ +

As tarefas são unidades coesas que podem mover-se para "segundo plano" quando usuário inicia uma nova tarefa +ou ir para a tela inicial por meio do botão Página inicial. Quando em segundo plano, todas as atividades +da tarefa são +interrompidas, mas a pilha de retorno das tarefas continua intacta — a tarefa simplesmente perdeu o foco, +que foi para outra tarefa, como ilustrado na figura 2. Uma tarefa pode, então, retornar ao "primeiro plano" para que os usuários +continuem de onde pararam. Suponha, por exemplo, que a tarefa atual (tarefa A) tenha três +atividades na sua pilha — duas sob a atividade atual. O usuário pressiona o botão Página inicial e, +em seguida, +inicia um novo aplicativo no inicializador do aplicativo. Quando a tela inicial aparece, a tarefa A +vai para segundo plano. Quando o novo aplicativo inicia, o sistema inicia uma tarefa para este aplicativo +(tarefa B) com sua própria pilha de atividades. Após interagir +com este aplicativo, o usuário retorna à Página inicial novamente e seleciona o aplicativo que originalmente +iniciou a tarefa A. Agora, a tarefa A fica +em primeiro plano — todas as três atividades nas pilhas estão intactas e a atividade no topo +da pilha reinicia. Nesse +momento, o usuário pode alternar para a tarefa B acessando a Página inicial e selecionando o ícone do aplicativo +que iniciou essa tarefa (ou selecionando a tarefa do aplicativo +na tela de visão geral). +Esse é um exemplo de multitarefas no Android.

+ +

Observação: várias tarefas podem ser mantidas em segundo plano simultaneamente. +Contudo, se o usuário estiver executando diversas tarefas em segundo plano ao mesmo tempo, o sistema pode começar +a destruir atividades de segundo plano para recuperar memória, fazendo com que o estado das atividades seja perdido. +Consulte a seção a seguir sobre Estado de atividades.

+ +

Como as atividades na pilha de retorno nunca são reorganizadas, se o seu aplicativo permitir que +usuários iniciem uma determinada atividade a partir de mais de uma atividade, uma nova instância +dela será criada e colocada na pilha (em vez de trazer qualquer instância anterior +dela para o topo). Assim, uma atividade no aplicativo pode ser instanciada diversas +vezes (mesmo a partir de diferentes tarefas), como ilustrado na figura 3. Assim, se o usuário navegar inversamente +usando o botão Voltar, as instâncias da atividade serão revelada na ordem em que foram +abertas (cada uma +com o próprio estado da IU). No entanto, é possível modificar esse comportamento se você não deseja que uma atividade seja +instanciada mais de uma vez. Esse assunto é abordado na próxima seção sobre Gerenciamento de tarefas.

+ + +

Para resumir o comportamento padrão de atividades e tarefas:

+ +
    +
  • Quando a atividade A inicia a atividade B, a atividade A é interrompida, mas o sistema retém seu estado +(como posição de rolagem e texto inserido em formulários). +Se o usuário pressionar o botão Voltar na atividade B, a atividade A reiniciará com seu estado +restaurado.
  • +
  • Quando o usuário se retira de uma tarefa pressionando o botão Página inicial, a atividade atual é +interrompida +e sua tarefa fica em segundo plano. O sistema retém o estado de cada atividade na tarefa. Se, +mais tarde, o usuário reiniciar a tarefa selecionando o ícone de inicialização que a inicia, ela ficará +em primeiro plano e reiniciará a atividade no topo da pilha.
  • +
  • Se o usuário pressionar o botão Voltar, a atividade atual será retirada da pilha +e +destruída. A atividade anterior na pilha é retomada. Quando uma atividade é destruída, o sistema +não retém seu estado.
  • +
  • As atividades podem ser instanciadas diversas vezes mesmo a partir de outras tarefas.
  • +
+ + +
+

Projeto de navegação

+

Para saber mais sobre o funcionamento da navegação de aplicativos no Android, leia o guia Navegação do Projeto do Android.

+
+ + +

Gravação do estado da atividade

+ +

Como discutido acima, o comportamento padrão do sistema preserva o estado de uma atividade quando ela é +interrompida. Desse modo, quando usuários navegam inversamente a uma atividade anterior, a interface do usuário aparece +na forma em que foi deixada. Entretanto, é possível — e recomendável — reter proativamente +o estado das atividades usando métodos de retorno de chamada nos casos em que a atividade é destruída e deve +ser recriada.

+ +

Quando o sistema interrompe uma das atividades (como quando uma nova atividade inicia ou a tarefa +se move para segundo plano), o sistema pode destruir esta atividade completamente se precisar recuperar +a memória do sistema. Quando isso ocorre, as informações sobre o estado da atividade são perdidas. Se isso acontecer, +o sistema ainda +saberá que a atividade tem um lugar na pilha de retorno, mas quando a atividade for levada +ao topo da pilha, o sistema precisará recriá-la (em vez de reiniciá-la). Para +evitar perder o trabalho do usuário, deve-se retê-la proativamente implementando +métodos de retorno de chamada {@link android.app.Activity#onSaveInstanceState onSaveInstanceState()} +na atividade.

+ +

Para obter mais informações sobre a gravação do estado da atividade, consulte o documento +Atividades.

+ + + +

Gerenciamento de tarefas

+ +

O modo com que o Android gerencia tarefas e a pilha de retorno, como descrito abaixo — posicionando todas +as atividades iniciadas em sucessão na mesma tarefa em uma pilha UEPS (último que entra, primeiro que sai) — funciona +muito bem para a maioria dos aplicativo e não é preciso preocupar-se com a associação das atividades +a tarefas ou como elas estão organizadas na pilha de retorno. No entanto, pode-se decidir interromper +o comportamento normal. Talvez você deseje que uma atividade inicie uma nova tarefa no aplicativo ao ser +iniciada (em vez de ser colocada dentro da tarefa atual); ou, quando você inicia uma atividade, deseja +apresentar uma instância dela existente (em vez de criar uma nova +instância no topo da pilha de retorno); ou talvez você deseje que a pilha apague +todas as atividades, exceto a atividade raiz, quando o usuário sai da tarefa.

+ +

É possível fazer tudo isso e muito mais com atributos +no elemento {@code <activity>} do manifesto + e com sinalizadores na intenção passada +a {@link android.app.Activity#startActivity startActivity()}.

+ +

Com relação a isso, os principais atributos +{@code <activity>} que podem ser usados são:

+ + + +

E os principais sinalizadores de intenção que podem ser usados são:

+ +
    +
  • {@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK}
  • +
  • {@link android.content.Intent#FLAG_ACTIVITY_CLEAR_TOP}
  • +
  • {@link android.content.Intent#FLAG_ACTIVITY_SINGLE_TOP}
  • +
+ +

Nas seções a seguir, você verá como usar esses atributos de manifesto e sinalizadores +de intenção para definir como as atividades são associadas a tarefas e como elas se comportam na pilha de retorno.

+ +

Além disso, são discutidas separadamente as considerações sobre como tarefas e atividades podem ser representadas +e gerenciadas na tela de visão geral. Consulte Tela de visão geral +para obter mais informações. Normalmente, é preciso permitir que o sistema defina como as tarefas +e as atividades são representadas na tela de visão geral e não é necessário modificar esse comportamento.

+ +

Atenção: a maioria dos aplicativos não deve interromper o comportamento +padrão de atividades e tarefas. Se você determinar que é necessário modificar os comportamentos padrão +da atividade, seja prudente e certifique-se de testar a capacidade de uso da atividade durante +a inicialização e ao navegar de volta para ela de outras atividades e tarefas com o botão Voltar. +Certifique-se de testar os comportamentos de navegação que podem entrar em conflito com o comportamento esperado pelo usuário.

+ + +

Definição de modos de inicialização

+ +

Os modos de inicialização permitem definir a forma com que uma nova instância de uma atividade é associada +à tarefa atual. Pode-se definir diferentes modos de inicialização de duas maneiras:

+
    +
  • Usando o arquivo de manifesto +

    Ao declarar uma atividade em um arquivo de manifesto, pode-se especificar como a atividade +deve associar-se a tarefas quando ela inicia.

  • +
  • Usando sinalizadores de intenção +

    Ao chamar {@link android.app.Activity#startActivity startActivity()}, +é possível incluir um sinalizador na {@link android.content.Intent} que declara como +(ou se) a nova atividade deve associar-se à tarefa atual.

  • +
+ +

Assim, se a atividade A iniciar a atividade B, a atividade B poderá definir no manifesto como +deve associar-se à tarefa atual (se ocorrer) e a atividade A poderá solicitar o modo pelo qual a atividade +B deverá associar-se à tarefa atual. Se ambas as atividades definem como a atividade B +deve associar-se a uma tarefa, a solicitação da atividade A (como definido na intenção) sobrepõe +a solicitação da atividade B (como definido no seu manifesto).

+ +

Observação: Alguns modos de inicialização disponíveis para o arquivo de manifesto +não estão disponíveis como sinalizadores para uma intenção e, do mesmo modo, alguns modos de inicialização disponíveis como sinalizadores +de uma intenção não podem ser definidos no manifesto.

+ + +

Uso do arquivo de manifesto

+ +

Ao declarar uma atividade no arquivo de manifesto, pode-se especificar como a atividade +deve associar-se a tarefas usando o atributo {@code +launchMode} do elemento +{@code <activity>}.

+ +

O atributo {@code +launchMode} especifica uma instrução sobre como a atividade deve ser inicializada +em uma tarefa. Há quatro modos diferentes de inicialização que podem ser designados ao atributo +launchMode: +

+ +
+
{@code "standard"} (o modo padrão)
+
Padrão. O sistema cria uma nova instância da atividade na tarefa +pela qual foi iniciada e encaminha-lhe a intenção. A atividade pode ser instanciada diversas vezes; +cada instância pode pertencer a diferentes tarefas e uma tarefa pode ter diversas instâncias.
+
{@code "singleTop"}
+
Se uma instância da atividade já existir no topo da tarefa atual, o sistema +encaminhará a intenção àquela instância por meio de uma chamada do método {@link +android.app.Activity#onNewIntent onNewIntent()} em vez de criar uma nova instância +da atividade. A atividade pode ser instanciada diversas vezes; cada instância pode +pertencer a diferentes tarefas e uma tarefa pode ter diversas instâncias (mas somente se +a atividade no topo da pilha de retorno não for uma instância existente da atividade). +

Por exemplo: suponhamos que uma pilha de retorno da tarefa consista na atividade raiz A com as atividades B, C e +D no topo (a pilha é A-B-C-D, com D no topo). Uma intenção chega de uma atividade de tipo D. +Se D for o modo de inicialização {@code "standard"} padrão, uma nova instância da classe será inicializada +e a tarefa se tornará A-B-C-D-D. Entretanto, se o modo de inicialização de D for {@code "singleTop"}, a instância existente +de D receberá a intenção por {@link +android.app.Activity#onNewIntent onNewIntent()} porque ela está no topo da pilha — +a pilha permanece A-B-C-D. Contudo, se uma intenção chegar de uma atividade de tipo B, uma nova +instância de B será adicionada à pilha, mesmo que o modo de inicialização seja {@code "singleTop"}.

+

Observação: quando uma nova instância de uma atividade é criada, +o usuário pode pressionar o botão Voltar para retornar à atividade anterior. Porém, quando uma +instância +existente de uma atividade trata de uma nova intenção, o usuário não pode pressionar o botão Voltar para retornar +ao estado +da atividade antes que a nova intenção chegue em {@link android.app.Activity#onNewIntent +onNewIntent()}.

+
+ +
{@code "singleTask"}
+
O sistema cria uma nova tarefa e instancia a atividade em sua raiz. +Entretanto, se uma instância da atividade já existir em uma tarefa separada, o sistema encaminhará +a intenção àquela instância por meio de uma chamada do método {@link +android.app.Activity#onNewIntent onNewIntent()} em vez de criar uma nova instância. Somente +uma instância da atividade pode existir por vez. +

Observação: embora a atividade inicie em uma nova tarefa, o botão +Voltar ainda direciona o usuário à atividade anterior.

+
{@code "singleInstance"}.
+
Igual à {@code "singleTask"}, exceto que o sistema não inicializa nenhuma outra atividade +na tarefa que contém a instância. A atividade é sempre o único e exclusivo membro de sua tarefa; +toda atividade iniciada por ela abre em uma tarefa separada.
+
+ + +

Outro exemplo: o aplicativo Navegador do Android declara que a atividade do navegador da web deve +sempre abrir na própria tarefa — especificando o modo de inicialização {@code singleTask} no elemento {@code <activity>}. +Isso significa que, se o aplicativo emite uma +intenção para abrir o navegador do Android, sua atividade não é colocada na mesma +tarefa do aplicativo. Em vez disso, uma nova tarefa inicia para o navegador ou, se o navegador +já possui uma tarefa em execução em segundo plano, essa tarefa é colocada em primeiro plano para tratar a nova +intenção.

+ +

Se uma atividade inicia em uma nova tarefa ou na mesma tarefa que a atividade que +a iniciou, o botão Voltar sempre direciona o usuário à atividade anterior. Porém, se você +iniciar uma atividade que especifica o modo de inicialização {@code singleTask} e se uma instância +dessa atividade existir em uma tarefa de segundo plano, toda a tarefa será colocada em primeiro plano. Nesse +momento, a pilha de retorno conterá todas as atividades da tarefa colocada em primeiro plano +no topo. A figura 4 ilustra essa situação.

+ + +

Figura 4. Representação de como uma atividade +com modo de inicialização "singleTask" é adicionada à pilha de retorno. Se a atividade já for parte de uma tarefa +de segundo plano com a própria pilha de retorno, toda a pilha também virá +para primeiro plano, no topo da tarefa atual.

+ +

Para obter mais informações sobre o uso de modos de inicialização no arquivo de manifesto, consulte a documentação do elemento +<activity> +, onde o atributo {@code launchMode} e os valores aceitos +são discutidos mais aprofundadamente.

+ +

Observação: os comportamentos a especificar para a atividade com o atributo {@code launchMode} + podem ser neutralizados por sinalizadores incluídos na intenção que inicia a atividade, conforme abordado +na próxima seção.

+ + + +

Uso de sinalizadores de intenção

+ +

Ao iniciar uma atividade, é possível modificar a associação padrão de uma atividade à tarefa +incluindo sinalizadores na intenção fornecida a {@link +android.app.Activity#startActivity startActivity()}. Os sinalizadores que podem ser usados para modificar +o comportamento padrão são:

+ +

+

{@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK}
+
Inicia a atividade em uma nova tarefa. Se uma tarefa já estiver em execução para a atividade que você está +iniciando agora, ela será colocada em primeiro plano com o último estado restaurado e a atividade +receberá a nova intenção em {@link android.app.Activity#onNewIntent onNewIntent()}. +

Isso produz o mesmo comportamento que o valor {@code "singleTask"} do {@code launchMode}, +abordado na seção anterior.

+
{@link android.content.Intent#FLAG_ACTIVITY_SINGLE_TOP}
+
Se a atividade iniciada for a atual (no topo da pilha de retorno), +a instância existente receberá uma chamada de {@link android.app.Activity#onNewIntent onNewIntent()} +em vez de criar uma nova instância da atividade. +

Isso produz o mesmo comportamento que o valor {@code "singleTop"} do {@code launchMode} +abordado na seção anterior.

+
{@link android.content.Intent#FLAG_ACTIVITY_CLEAR_TOP}
+
Se a atividade iniciada já estiver em execução na tarefa atual, em vez +de lançar uma nova instância daquela atividade, todas as outras atividades no topo dela serão +destruídas e essa intenção será entregue à instância reiniciada da atividade (agora no topo) +por {@link android.app.Activity#onNewIntent onNewIntent()}. +

Não há nenhum valor para o atributo {@code launchMode} + que produza esse comportamento.

+

{@code FLAG_ACTIVITY_CLEAR_TOP} é mais usado em conjunto com + {@code FLAG_ACTIVITY_NEW_TASK}. +Quando usados juntos, estes sinalizadores são o modo de localizar uma atividade existente +em outra tarefa e colocá-la em uma posição em que possa responder à intenção.

+

Observação: se o modo de inicialização da atividade designada for +{@code "standard"}, +ela também será removida da pilha e uma nova instância será iniciada em seu lugar para tratar +a intenção recebida. Isso se deve ao fato de que uma nova instância é sempre criada para uma nova intenção quando o +modo de inicialização é {@code "standard"}.

+
+ + + + + + +

Tratamento de afinidades

+ +

A afinidade indica a que tarefa uma atividade prefere pertencer. Por padrão, todas +as atividades do mesmo aplicativo têm afinidade entre si. Assim, por padrão, todas +as atividades no mesmo aplicativo preferem estar na mesma tarefa. Contudo, é possível modificar +a afinidade padrão de uma atividade. Atividades definidas +em aplicativos diferentes podem compartilhar uma afinidade, ou atividades definidas no mesmo aplicativo podem ter +diferentes afinidades de tarefa atribuídas.

+ +

É possível modificar a afinidade de qualquer atividade com o atributo {@code taskAffinity} + do elemento +{@code <activity>}.

+ +

O atributo {@code taskAffinity} + recebe um valor de string que deve ser exclusivo do nome do pacote padrão +declarado no elemento +{@code <manifest>} + porque o sistema usa esse nome para identificar a afinidade +de tarefa padrão do aplicativo.

+ +

A afinidade tem relevância em duas circunstâncias:

+
    +
  • Quando a intenção que inicializa uma atividade contém + o sinalizador + {@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK}. + +

    Uma nova atividade é, por padrão, inicializada na tarefa da atividade +que chamou {@link android.app.Activity#startActivity startActivity()}. Ela é colocada na mesma +pilha de retorno do autor da chamada. Contudo, se a intenção passada a +{@link android.app.Activity#startActivity startActivity()} +contiver o sinalizador {@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK} +, o sistema procurará uma tarefa diferente para comportar a nova atividade. Na maioria das vezes, é uma nova tarefa. +Porém, isso não é obrigatório. Se já houver uma tarefa com a mesma afinidade +da nova atividade, ela será inicializada naquela tarefa. Caso contrário, ela iniciará uma nova tarefa.

    + +

    Se esse sinalizador fizer com que uma atividade inicie uma nova tarefa e se o usuário pressionar o botão Página inicial +para sair dela, + será necessário ter um modo de o usuário navegar de volta à tarefa. Algumas entidades (como +o gerenciador de notificação) sempre iniciam atividades em tarefas externas, nunca como parte de si mesmas, por isso +elas sempre colocam {@code FLAG_ACTIVITY_NEW_TASK} nas intenções que passam +a {@link android.app.Activity#startActivity startActivity()}. +Se você tiver uma atividade que possa ser chamada +por uma entidade externa que possa usar este sinalizador, certifique-se de que o usuário tenha um modo independente +de voltar à tarefa iniciada, como com um ícone de inicialização (a atividade raiz da tarefa +tem um filtro de intenções {@link android.content.Intent#CATEGORY_LAUNCHER}; consulte a seção Início de uma tarefa abaixo).

    +
  • + +
  • Quando uma atividade tem o atributo +{@code allowTaskReparenting} definido como {@code "true"}. +

    Nesse caso, a atividade pode mover-se da tarefa que iniciou para a tarefa afim +quando for colocada em primeiro plano.

    +

    Por exemplo: suponhamos que uma atividade que relate condições do clima em cidades selecionadas seja +definida como parte de um aplicativo de viagens. Ela tem a mesma afinidade que outras atividades no mesmo +aplicativo (a afinidade padrão do aplicativo) e permite a redefinição da hierarquia com esse atributo. +Quando uma das atividades inicia a atividade de notificação de clima, ela inicialmente pertence à mesma +tarefa de sua atividade. Porém, quando a tarefa do aplicativo de viagens é colocada em primeiro plano, +a atividade de notificação de clima é reatribuída a essa tarefa e exibida dentro dela.

    +
  • +
+ +

Dica: se um arquivo {@code .apk} contiver mais de um "aplicativo" +do ponto de vista do usuário, você provavelmente desejará usar o atributo {@code taskAffinity} + para designar diferentes afinidades às atividades associadas a cada "aplicativo".

+ + + +

Apagar a pilha de retorno

+ +

Se o usuário sair de uma tarefa por muito tempo, o sistema apagará a tarefa de todas as atividades exceto +a da atividade raiz. Quando o usuário retornar à tarefa, somente a atividade raiz será restaurada. +O sistema comporta-se dessa maneira porque, após longo tempo de uso, os usuários provavelmente abandonaram +o que estavam fazendo antes e são direcionados de volta à tarefa para começar algo novo.

+ +

Há alguns atributos de atividade que podem ser usados para modificar esse comportamento:

+ +
+
alwaysRetainTaskState +
+
Se esse atributo for definido como {@code "true"} na atividade raiz de uma tarefa, +o comportamento padrão descrito não acontecerá. +A tarefa reterá todas as atividades em sua pilha mesmo após um longo período.
+ +
clearTaskOnLaunch
+
Se esse atributo for definido como {@code "true"} na atividade raiz de uma tarefa, +a pilha será apagada da atividade raiz sempre que o usuário sair da tarefa +e retornar a ela. Em outras palavras, é o oposto de + +{@code alwaysRetainTaskState}. O usuário sempre retorna à tarefa +no estado inicial, mesmo ao retirar-se da tarefa somente por um momento.
+ +
finishOnTaskLaunch +
+
Esse atributo é como {@code clearTaskOnLaunch}, +mas opera +em uma única atividade, e não em uma tarefa inteira. Ele também apaga todas as atividades, +inclusive a atividade raiz. Quando definido como {@code "true"}, +a atividade permanece parte da tarefa somente para a sessão atual. Se o usuário +retirar-se e, em seguida, retornar à tarefa, ela não estará mais presente.
+
+ + + + +

Início de uma tarefa

+ +

É possível configurar uma atividade como ponto de entrada de uma tarefa fornecendo-lhe um filtro de intenções +com {@code "android.intent.action.MAIN"} como a ação especificada +e {@code "android.intent.category.LAUNCHER"} +como a categoria especificada. Por exemplo:

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

Um filtro de intenções desse tipo faz com que um ícone e o rótulo +da atividade sejam exibidos no inicializador do aplicativo, fornecendo aos usuários um modo de inicializar a atividade +e de retornar à tarefa criada a qualquer tempo após sua inicialização. +

+ +

Este segundo recurso é importante: é preciso que os usuários possam sair de uma tarefa e voltar a ela +mais tarde usando esse inicializador de atividades. Por isso, os dois modos +de inicialização que marcam atividades como sempre iniciando uma tarefa ({@code "singleTask"} e +{@code "singleInstance"}) devem ser usados somente quando a atividade tiver um filtro +{@link android.content.Intent#ACTION_MAIN} +e um {@link android.content.Intent#CATEGORY_LAUNCHER}. Imagine, por exemplo, o que +aconteceria se o filtro não estivesse presente: uma intenção inicializaria uma atividade {@code "singleTask"}, iniciando uma +nova tarefa, e o usuário perderia algum tempo trabalhando nessa tarefa. O usuário, então, pressiona o botão +Página inicial. A tarefa é enviada para segundo plano e não fica mais visível. O usuário não tem como voltar +à tarefa porque ela não é representada no inicializador do aplicativo.

+ +

Para esses casos em que se deseja que o usuário não seja capaz de retornar a uma atividade, defina +{@code finishOnTaskLaunch} + do elemento +<activity> +como {@code "true"} (consulte Apagar a pilha).

+ +

Veja mais informações sobre a representação e o gerenciamento de atividades +na tela de visão geral em +Tela de visão geral.

+ + diff --git a/docs/html-intl/intl/pt-br/guide/index.jd b/docs/html-intl/intl/pt-br/guide/index.jd new file mode 100644 index 0000000000000000000000000000000000000000..ab396477e2506007b41254f3dd50403a56439c30 --- /dev/null +++ b/docs/html-intl/intl/pt-br/guide/index.jd @@ -0,0 +1,74 @@ +page.title=Introdução ao Android + +@jd:body + + + + +

O Android fornece uma estrutura de aplicativo rica que permite criar aplicativos e jogos inovadores +para dispositivos móveis em um ambiente de linguagem Java. Os documentos listados na navegação +à esquerda fornecem detalhes da criação de aplicativos usando as várias APIs do Android.

+ +

Se você é novo no desenvolvimento para Android, é importante que entenda +os seguintes conceitos fundamentais sobre a estrutura de aplicativos do Android:

+ + +
+ +
+ +

Aplicativos oferecem vários pontos de entrada

+ +

Aplicativos para Android são criados como uma combinação de componentes distintos que podem ser invocados +individualmente. Por exemplo, uma atividade individual fornece uma única +tela para a interface de usuário e um serviço realiza trabalho +em segundo plano de forma independente.

+ +

De um componente, é possível executar outro componente usando uma intenção. É possível até mesmo +iniciar um componente em um aplicativo diferente, como uma atividade em um aplicativo de mapas para mostrar um endereço. Esse modelo +fornece vários pontos de entrada para um único aplicativo e permite que qualquer aplicativo se comporte como o "padrão" de um usuário +para uma ação que outros aplicativos podem invocar.

+ + +

Saiba mais:

+ + +
+ + +
+ +

Os aplicativos se adaptam a diferentes dispositivos

+ +

O Android fornece uma estrutura de aplicativo adaptativa que permite fornecer recursos exclusivos para +diferentes configurações de dispositivos. Por exemplo, é possível criar diferentes arquivos XML +de layout para diversos tamanhos de tela e o sistema +determina qual layout deverá aplicar com base no tamanho da tela do dispositivo atual.

+ +

Você pode consultar a disponibilidade dos recursos do dispositivo em tempo de execução se qualquer recurso do +aplicativo exigir hardware específico, como uma câmera. Se necessário, também é possível declarar recursos que o aplicativo exige, +para que mercados como a Google Play Store não permitam a instalação em dispositivos que não sejam compatíveis +com aquele recurso.

+ + +

Saiba mais:

+ + +
+ +
+ + + diff --git a/docs/html-intl/intl/pt-br/guide/topics/manifest/manifest-intro.jd b/docs/html-intl/intl/pt-br/guide/topics/manifest/manifest-intro.jd new file mode 100644 index 0000000000000000000000000000000000000000..e33779684a5c0a5e577f325234540821ae73bee6 --- /dev/null +++ b/docs/html-intl/intl/pt-br/guide/topics/manifest/manifest-intro.jd @@ -0,0 +1,517 @@ +page.title=Manifesto do aplicativo +@jd:body + + + +

+ Todo aplicativo tem que ter um arquivo AndroidManifest.xml (precisamente +com esse nome) no seu diretório raiz. O arquivo de manifesto +apresenta informações essenciais sobre o aplicativo ao sistema Android, +necessárias para o sistema antes que ele possa executar o código +do aplicativo. Entre outras coisas, o manifesto serve para: +

+ +
    +
  • Nomear o pacote Java para o aplicativo. +O nome do pacote serve como identificador exclusivo para o aplicativo.
  • + +
  • Descrever os componentes do aplicativo — as atividades, +os serviços, os receptores de transmissão e os provedores de conteúdo que compõem +o aplicativo. Nomear as classes que implementam os componentes +e publicam seus recursos (por exemplo, que mensagens {@link android.content.Intent +Intent} eles podem tratar). Essas declarações permitem ao sistema Android +saber quais são os componentes e em que condições eles podem ser iniciados.
  • + +
  • Determinar que processos hospedarão componentes de aplicativo.
  • + +
  • Declarar as permissões que o aplicativo deve ter para acessar +partes protegidas da API e interagir com outros aplicativos.
  • + +
  • Declarar também as permissões que outros devem ter +para interagir com os componentes do aplicativo.
  • + +
  • Listar as classes {@link android.app.Instrumentation} que fornecem +geração de perfil e outras informações durante a execução do aplicativo. Essas declarações +estão presentes no manifesto somente enquanto o aplicativo está em desenvolvimento +e teste — elas são removidas antes da publicação do aplicativo.
  • + +
  • Declarar o nível mínimo da API do Android que o aplicativo +exige.
  • + +
  • Listar as bibliotecas às quais o aplicativo deve se vincular.
  • +
+ + +

Estrutura do arquivo de manifesto

+ +

+O diagrama ilustra a estrutura geral do arquivo de manifesto e cada +elemento que ele pode conter. Cada elemento e seus atributos +são documentados na totalidade em um arquivo separado. Para exibir informações detalhadas +sobre cada elemento, clique no nome do elemento no diagrama, +na lista de elementos em ordem alfabética que acompanha o diagrama +ou em qualquer outra menção ao nome do elemento. +

+ +
+<?xml version="1.0" encoding="utf-8"?>
+
+<manifest>
+
+    <uses-permission />
+    <permission />
+    <permission-tree />
+    <permission-group />
+    <instrumentation />
+    <uses-sdk />
+    <uses-configuration />  
+    <uses-feature />  
+    <supports-screens />  
+    <compatible-screens />  
+    <supports-gl-texture />  
+
+    <application>
+
+        <activity>
+            <intent-filter>
+                <action />
+                <category />
+                <data />
+            </intent-filter>
+            <meta-data />
+        </activity>
+
+        <activity-alias>
+            <intent-filter> . . . </intent-filter>
+            <meta-data />
+        </activity-alias>
+
+        <service>
+            <intent-filter> . . . </intent-filter>
+            <meta-data/>
+        </service>
+
+        <receiver>
+            <intent-filter> . . . </intent-filter>
+            <meta-data />
+        </receiver>
+
+        <provider>
+            <grant-uri-permission />
+            <meta-data />
+            <path-permission />
+        </provider>
+
+        <uses-library />
+
+    </application>
+
+</manifest>
+
+ +

+Todos os elementos que podem aparecer no arquivo de manifesto estão +relacionados abaixo em ordem alfabética. Estes são os únicos elementos legais. Não é possível +adicionar elementos ou atributos próprios. +

+ +

+<action> +
<activity> +
<activity-alias> +
<application> +
<category> +
<data> +
<grant-uri-permission> +
<instrumentation> +
<intent-filter> +
<manifest> +
<meta-data> +
<permission> +
<permission-group> +
<permission-tree> +
<provider> +
<receiver> +
<service> +
<supports-screens> +
<uses-configuration> +
<uses-feature> +
<uses-library> +
<uses-permission> +
<uses-sdk> +

+ + + + +

Convenções de arquivos

+ +

+Algumas convenções e regras se aplicam geralmente a todos os elementos e atributos +no manifesto: +

+ +
+
Elementos
+
Somente os elementos +<manifest> +e <application> +são necessários — eles devem estar presentes e ocorrer somente uma vez. +A maioria dos outros pode ocorrer diversas vezes ou nunca — embora +pelo menos alguns deles devam estar presentes para que o manifesto +realize algo significativo. + +

+Se um elemento contiver qualquer coisa, ele conterá outros elementos. +Todos os valores são definidos por meio de atributos, e não como dados de caracteres dentro de um elemento. +

+ +

+Elementos de mesmo nível geralmente não são ordenados. Por exemplo: os elementos +<activity>, +<provider> +e <service> +podem ser combinados entre si em qualquer sequência. (O elemento +<activity-alias> +é uma exceção a essa regra: ele deve seguir o +<activity> +para o qual é alias.) +

+ +
Atributos
+
Formalmente, todos os atributos são opcionais. No entanto, alguns deles +devem ser especificados para um elemento para cumprir a sua finalidade. Use +a documentação como guia. Para atributos verdadeiramente opcionais, ele menciona +um valor padrão ou declara o que acontece na ausência de uma especificação. + +

Exceto por alguns atributos do elemento +<manifest> +raiz, todos os nomes de atributo têm um prefixo {@code android:} — +por exemplo, {@code android:alwaysRetainTaskState}. Como o prefixo é universal, +a documentação geralmente o omite ao referir-se a atributos +pelo nome.

+ +
Declaração de nomes de classe
+
Muitos elementos correspondem a objetos Java, inclusive elementos do próprio +aplicativo (o elemento +<application> +) e seus componentes principais — atividades +(<activity>), +serviços +(<service>), +receptores de transmissão +(<receiver>) +e provedores de conteúdo +(<provider>). + +

+Se uma subclasse for definida, como quase sempre acontece para classes de componentes +({@link android.app.Activity}, {@link android.app.Service}, +{@link android.content.BroadcastReceiver} e {@link android.content.ContentProvider}), +a subclasse será declarada por meio de um atributo {@code name}. O nome deve conter +toda a designação do pacote. +Por exemplo: uma subclasse {@link android.app.Service} pode ser declarada assim: +

+ +
<manifest . . . >
+    <application . . . >
+        <service android:name="com.example.project.SecretService" . . . >
+            . . .
+        </service>
+        . . .
+    </application>
+</manifest>
+ +

+No entanto, para encurtar, se o primeiro caractere da string for um ponto, +a string será acrescentada ao nome do pacote do aplicativo (conforme especificado pelo atributo +package + do elemento +<manifest> +). A seguinte atribuição é igual à atribuição acima: +

+ +
<manifest package="com.example.project" . . . >
+    <application . . . >
+        <service android:name=".SecretService" . . . >
+            . . .
+        </service>
+        . . .
+    </application>
+</manifest>
+ +

+Ao iniciar um componente, o Android cria uma instância da subclasse nomeada. +Se nenhuma subclasse for especificada, ele criará uma instância da classe base. +

+ +
Vários valores
+
Se for especificado mais de um valor, o elemento sempre será repetido +em vez de listar os vários valores dentro de um único elemento. +Por exemplo, um filtro de intenção pode listar algumas ações: + +
<intent-filter . . . >
+    <action android:name="android.intent.action.EDIT" />
+    <action android:name="android.intent.action.INSERT" />
+    <action android:name="android.intent.action.DELETE" />
+    . . .
+</intent-filter>
+ +
Valores de recurso
+
Alguns atributos têm valores que podem ser exibidos aos usuários — por exemplo, +uma etiqueta e um ícone de uma atividade. Os valores desses atributos +devem ser localizados e, por tanto, definidos a partir de um recurso ou tema. Os valores +de recurso são expressos no formato a seguir:

+ +

{@code @[pacote:]tipo:nome}

+ +

+em que o nome do pacote pode ser omitido se o recurso estiver no mesmo pacote +que o aplicativo tipo é um tipo de recurso — como uma "string" ou +"drawable" (desenhável) — e nome é o nome que identifica o recurso específico. +Por exemplo: +

+ +
<activity android:icon="@drawable/smallPic" . . . >
+ +

+Os valores de um tema são expressos de forma semelhante, mas, com um '{@code ?}' +em vez de '{@code @}': +

+ +

{@code ?[pacote:]tipo:nome} +

+ +
Valores de string
+
Quando o valor de um atributo é uma string, devem-se usar duas barras invertidas ('{@code \\}') +para caracteres de escape — por exemplo, '{@code \\n}' para +uma nova linha ou '{@code \\uxxxx}' para um caractere Unicode.
+
+ + +

Características do arquivo

+ +

+As seções a seguir descrevem como alguns recursos do Android se refletem +no arquivo de manifesto. +

+ + +

Filtros de intenções

+ +

+Os componentes fundamentais de um aplicativo (suas atividades, serviços e receptores +de transmissão) são ativados por intenções. Intenções são +pacotes de informações (objetos {@link android.content.Intent}) que descrevem +uma ação desejada — inclusive os dados usados em ações, a categoria +do componente que deve executar a ação e outras instruções pertinentes. +O Android localiza um componente adequado para responder à intenção, inicia +uma nova instância do componente se necessário e passa-o +ao objeto da intenção. +

+ +

+Os componentes anunciam seus recursos — os tipos de intenção aos quais eles podem +responder — por meio de filtros de intenções. Como o sistema Android +precisa saber que intenções um componente pode tratar antes de iniciá-lo, os filtros +de intenções são especificados no manifesto como elementos +<intent-filter> +. Os componentes podem ter qualquer quantidade de filtros, em que cada um descreve +um recurso diferente. +

+ +

+A intenção que nomeia explicitamente um componente alvo ativará +aquele componente — o filtro não desempenha nenhuma função. Porém, uma intenção que não especifica nenhum alvo +pelo nome pode ativar um componente somente se ele puder passar por um dos filtros +do componente. +

+ +

+Para ver como os objetos de intenção são testados em relação aos filtros de intenções, +consulte o documento +Intenções +e filtros de intenções em separado. +

+ + +

Ícones e etiquetas

+ +

+Alguns elementos têm atributos {@code icon} e {@code label} de um pequeno ícone +e uma etiqueta de texto que pode ficar visível para os usuários. Alguns deles também têm um atributo +{@code description} para um texto explicativo mais longo que também pode ser +exibido na tela. Por exemplo: o elemento +<permission> +tem todos os três atributos; assim, quando o usuário é consultado para dar +permissão a um aplicativo que a solicitou, serão apresentados ao usuário um ícone +que representa a permissão, o nome da permissão e uma descrição +de tudo o que está envolvido. +

+ +

+Em todo caso, o ícone e a etiqueta definidos em um elemento recipiente se tornam as configurações +{@code icon} e {@code label} padrão de todos os subelementos do contêiner. +Assim, o ícone e a etiqueta definidos no elemento +<application> +são o ícone e a etiqueta padrão para cada um dos componentes do aplicativo. +Da mesma forma, o ícone e a etiqueta definidos para um componente — por exemplo, um elemento +<activity> + — são as configurações padrão para cada um dos elementos +<intent-filter> +do componente. Se um elemento +<application> +define uma etiqueta, mas uma atividade e seu filtro de intenção não definem, +a etiqueta do aplicativo é tratada como a etiqueta de atividade +e do filtro de intenção. +

+ +

+O ícone e a etiqueta definidos para um filtro de intenção são usados para representar um componente +apresentado para o usuário para preencher a função +anunciada pelo filtro. Por exemplo: um filtro com as configurações +"{@code android.intent.action.MAIN}" e +"{@code android.intent.category.LAUNCHER}" anuncia uma atividade +como uma que inicia um aplicativo — ou seja, +que deve ser exibida no inicializador do aplicativo. O ícone e a etiqueta +definidos no filtro são, portanto, as exibidas no inicializador. +

+ + +

Permissões

+ +

+As permissões são restrições que limitam o acesso a parte do código +ou aos dados de um dispositivo. A limitação é imposta para proteger dados +essenciais que podem sofrer mau uso e distorções ou prejudicar a experiência do usuário. +

+ +

+Cada permissão é identificada por uma etiqueta exclusiva. Geralmente a etiqueta indica +a ação que foi restringida. A seguir há alguns exemplos de permissões definidas +pelo Android: +

+ +

{@code android.permission.CALL_EMERGENCY_NUMBERS} +
{@code android.permission.READ_OWNER_DATA} +
{@code android.permission.SET_WALLPAPER} +
{@code android.permission.DEVICE_POWER}

+ +

+Um recurso pode ser protegido por, no máximo, uma permissão. +

+ +

+Se um aplicativo precisar de acesso a um recurso protegido por uma permissão, +ele deve declarar que precisa da permissão com um elemento +<uses-permission> +no manifesto. Assim, quando o aplicativo é instalado +no dispositivo, o instalador determina se concederá ou não a permissão +solicitada, marcando as autoridades que assinaram os certificados +do aplicativo e, em alguns casos, perguntando ao usuário. +Se a permissão for concedida, o aplicativo será capaz de usar os recursos +protegidos. Caso contrário, sua tentativa de acessar esses recursos simplesmente falhará +sem nenhuma notificação ao usuário. +

+ +

+Um aplicativo também pode proteger seus componentes (atividades, serviços, +receptores de transmissão e provedores de conteúdo) com permissões. Ele pode empregar +qualquer uma das permissões definidas pelo Android (listadas em +{@link android.Manifest.permission android.Manifest.permission}) ou declaradas +por outros aplicativos. Ou então, ele pode definir as suas próprias. As novas permissões são declaradas +com o elemento +<permission>. + Por exemplo: uma atividade pode ser protegida da seguinte forma: +

+ +
+<manifest . . . >
+    <permission android:name="com.example.project.DEBIT_ACCT" . . . />
+    <uses-permission android:name="com.example.project.DEBIT_ACCT" />
+    . . .
+    <application . . .>
+        <activity android:name="com.example.project.FreneticActivity"
+                  android:permission="com.example.project.DEBIT_ACCT"
+                  . . . >
+            . . .
+        </activity>
+    </application>
+</manifest>
+
+ +

+Observe que, nesse exemplo, a permissão {@code DEBIT_ACCT}, além de declarada +com o elemento +<permission> +, tem seu uso solicitado com o elemento +<uses-permission>. + Ela deve ser solicitada para que outros componentes do aplicativo +iniciem a atividade protegida, mesmo que a proteção +seja imposta pelo próprio aplicativo. +

+ +

+Se, no mesmo exemplo, o atributo {@code permission} fosse definido +como uma permissão declarada em outro lugar +(como {@code android.permission.CALL_EMERGENCY_NUMBERS}), não seria +necessário declará-la novamente com um elemento +<permission>. + No entanto, ainda seria necessário solicitar seu uso com +<uses-permission>. +

+ +

+O elemento +<permission-tree> +declara um espaço de nome de um grupo de permissões que será definido +no código. E +<permission-group> +define um etiqueta de um conjunto de permissões (os dois declarados no manifesto com elementos +<permission> +e as declaradas em outro lugar). Ele afeta somente a forma com que as permissões estão +agrupadas quando apresentadas ao usuário. O elemento +<permission-group> +não especifica que permissões pertencem ao grupo; +ele só dá um nome ao grupo. Para incluir uma permissão no grupo, +designa-se o nome do grupo ao atributo +permissionGroup + do elemento +<permission>. + +

+ + +

Bibliotecas

+ +

+Todo aplicativo está vinculado à biblioteca Android padrão, que +contém os pacotes básicos para programar aplicativos (com classes comuns +tais como Activity, Service, Intent, View, Button, Application, ContentProvider +etc.). +

+ +

+No entanto, alguns pacotes residem em suas próprias bibliotecas. Se o aplicativo +usar código de algum desses pacotes, ele deve receber solicitação explícita para ser +vinculado a eles. O manifesto deve conter um elemento +<uses-library> + separado para nomear cada uma das bibliotecas (o nome da biblioteca se encontra +na documentação do pacote). +

diff --git a/docs/html-intl/intl/pt-br/guide/topics/providers/calendar-provider.jd b/docs/html-intl/intl/pt-br/guide/topics/providers/calendar-provider.jd new file mode 100644 index 0000000000000000000000000000000000000000..ce72b7db5cff09ac92303b605426f7efd2e7f692 --- /dev/null +++ b/docs/html-intl/intl/pt-br/guide/topics/providers/calendar-provider.jd @@ -0,0 +1,1184 @@ +page.title=Provedor de agenda +@jd:body + +
+
+

Neste documento

+
    +
  1. Conceitos básicos
  2. +
  3. Permissões do usuário
  4. +
  5. Tabela de agenda +
      +
    1. Consulta em uma agenda
    2. +
    3. Modificação de uma agenda
    4. +
    5. Inserção de uma agenda
    6. +
    +
  6. +
  7. Tabelas de eventos +
      +
    1. Adição de eventos
    2. +
    3. Atualização de eventos
    4. +
    5. Exclusão de eventos
    6. +
    +
  8. +
  9. Tabela de participantes +
      +
    1. Adição de participantes
    2. +
    +
  10. +
  11. Tabela de lembretes +
      +
    1. Adição de lembretes
    2. +
    +
  12. +
  13. Tabela de instâncias +
      +
    1. Consulta na tabela de instâncias
    2. +
  14. +
  15. Intenções do Agenda +
      +
    1. Uso de uma intenção para inserir um evento
    2. +
    3. Uso de uma intenção para editar um evento
    4. +
    5. Uso de intenções para exibir dados de agenda
    6. +
    +
  16. + +
  17. Adaptadores de sincronização
  18. +
+ +

Classes principais

+
    +
  1. {@link android.provider.CalendarContract.Calendars}
  2. +
  3. {@link android.provider.CalendarContract.Events}
  4. +
  5. {@link android.provider.CalendarContract.Attendees}
  6. +
  7. {@link android.provider.CalendarContract.Reminders}
  8. +
+
+
+ +

O Provedor de agenda é um repositório para eventos da agenda do usuário. A +API do Provedor de Agenda permite consultar, inserir, atualizar e excluir +operações em agendas, eventos, participantes, lembretes etc.

+ + +

A API do Provedor de Agenda pode ser usada por aplicativos e adaptadores de sincronização. As regras +variam conforme o tipo de programa que realiza as chamadas. Este documento +se concentra principalmente no uso da API do Provedor de Agenda como um aplicativo. Veja +uma discussão sobre as diferenças entre adaptadores de sincronização em +Adaptadores de sincronização.

+ + +

Normalmente, para ler ou programar dados da agenda, o manifesto de um aplicativo deve +conter as permissões adequadas descritas em Permissões +do usuário. Para facilitar a realização de operações comuns, o Provedor +de Agenda fornece um conjunto de intenções conforme descrito em Intenções +do Agenda. Essas intenções levam os usuários ao aplicativo Agenda para inserir, exibir +e editar eventos. O usuário interage com o aplicativo Agenda e, em seguida, +retorna ao aplicativo original. Assim, o aplicativo não precisa solicitar permissões +nem fornecer uma interface gráfica para exibir ou criar eventos.

+ +

Conceitos básicos

+ +

Os provedores de conteúdo armazenam dados e disponibilizam-nos +para aplicativos. Os provedores de conteúdo oferecidos pela plataforma do Android (inclusive o Provedor de Agenda) normalmente expõem dados como um conjunto de tabelas baseadas em +um modelo de banco de dados relacional, em que cada linha é um registro e cada coluna são dados +de um tipo e significado específico. Por meio da API do Provedor de Agenda, aplicativos +e adaptadores de sincronização podem obter acesso de leitura/gravação às tabelas do banco de dados que armazenam +dados da agenda do usuário.

+ +

Cada provedor de conteúdo expõe uma URI pública (agrupada como +um objeto {@link android.net.Uri} +) que identifica exclusivamente seu conjunto de dados. Um provedor de conteúdo que controla +diversos conjuntos de dados (diversas tabelas) expõe uma URI separada para cada um. Todas as +URIs de provedores começam com a string "content://". Ela +identifica os dados como controlados por um provedor de conteúdo. O Provedor +de Agenda define constantes para as URIs de cada uma das classes (tabelas). Essas +URIs têm o formato <class>.CONTENT_URI. Por exemplo: + {@link android.provider.CalendarContract.Events#CONTENT_URI Events.CONTENT_URI}.

+ +

A figura 1 exibe uma representação gráfica do modelo de dados do Provedor de Agenda. Ela ilustra +as tabelas e os campos principais que as vinculam entre si.

+ +Calendar Provider Data Model +

Figura 1. Modelo de dados do Provedor de Agenda.

+ +

Cada usuário pode ter diversas agendas e diferentes agendas podem ser associadas a diferentes tipos de conta (Google Agenda, Exchange etc.).

+ +

O {@link android.provider.CalendarContract} define o modelo de dados de informações relacionadas a eventos e agendas. Esses dados são armazenados em diversas tabelas, que são listadas a seguir.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tabela (classe)Descrição

{@link android.provider.CalendarContract.Calendars}

Essa tabela contém +as informações específicas da agenda. Cada linha nessa tabela contém os detalhes +de uma única agenda, como nome, cor, informações de sincronização etc.
{@link android.provider.CalendarContract.Events}Essa tabela contém +as informações específicas do evento. Cada linha nessa tabela tem as informações de um único +evento — por exemplo: título do evento, local, horário de início, horário +de término etc. O evento pode ocorrer uma vez ou diversas vezes. Os participantes, +lembretes e propriedades estendidas são armazenados em tabelas separadas. +Cada um deles tem um {@link android.provider.CalendarContract.AttendeesColumns#EVENT_ID} +que referencia o {@link android.provider.BaseColumns#_ID} na tabela de eventos.
{@link android.provider.CalendarContract.Instances}Essa tabela contém os +horários de início e término para cada ocorrência em um evento. Cada linha nessa tabela +representa uma única ocorrência do evento. Para eventos de ocorrência única, há um mapeamento 1:1 +de instâncias para eventos. Para eventos recorrentes, diversas linhas correspondentes +a diversas ocorrências daquele evento são geradas automaticamente.
{@link android.provider.CalendarContract.Attendees}Essa tabela contém +as informações dos participantes (convidados) do evento. Cada linha representa um único convidado +de um evento. Ela especifica o tipo de convidado e a resposta quanto à participação do convidado +no evento.
{@link android.provider.CalendarContract.Reminders}Essa tabela contém os +dados de alerta/notificação. Cada linha representa um único alerta de um evento. Um evento +pode ter vários lembretes. O número máximo de lembretes por evento +é especificado em +{@link android.provider.CalendarContract.CalendarColumns#MAX_REMINDERS}, +que é definido pelo adaptador de sincronização +que possui a agenda fornecida. Os lembretes são especificados em minutos antes do evento +e têm um método que determina a forma de alertar o usuário.
+ +

A API do Provedor de Agenda é projetada para ser flexível e poderosa. Ao mesmo tempo, +é importante fornecer uma boa experiência ao usuário final +e proteger a integridade da agenda e de seus dados. Para isso, a seguir apresentam-se +alguns pontos a considerar ao usar a API:

+ +
    + +
  • Inserção, atualização e exibição de eventos da agenda. Para inserir, modificar e ler eventos diretamente do provedor de agenda, é necessário ter as permissões apropriadas. Contudo, se o aplicativo em criação não for um aplicativo de agenda totalmente desenvolvido nem um adaptador de sincronização, não será necessário solicitar essas permissões. Em vez disso, podem-se usar intenções compatíveis com o aplicativo Agenda do Android para entregar operações de leitura e gravação a esse aplicativo. Ao usar as intenções, o aplicativo envia usuários ao aplicativo Agenda para realizar a operação desejada +em um formulário pré-preenchido. Após finalizarem a operação, eles serão direcionados de volta ao aplicativo. +Ao projetar seu aplicativo para realizar operações comuns através do Agenda, +os usuários têm uma experiência em uma interface robusta e consistente. Essa é +a abordagem recomendada. Para obter mais informações, consulte Intenções +do Agenda.

    + + +
  • Adaptadores de sincronização. Os adaptadores de sincronização sincronizam os dados da agenda +em um dispositivo do usuário com outro servidor ou fonte de dados. Nas tabelas +{@link android.provider.CalendarContract.Calendars} +e {@link android.provider.CalendarContract.Events}, +há colunas reservadas para o uso dos adaptadores de sincronização. +O provedor e os aplicativos não devem modificá-las. De fato, elas não são +visíveis a menos que sejam acessadas como um adaptador de sincronização. Para obter mais informações sobre +adaptadores de sincronização, consulte Adaptadores de sincronização.
  • + +
+ + +

Permissões do usuário

+ +

Para ler dados da agenda, o aplicativo deve conter a permissão {@link +android.Manifest.permission#READ_CALENDAR} no arquivo de manifesto. Ele +deve conter a permissão {@link android.Manifest.permission#WRITE_CALENDAR} +para excluir, inserir ou atualizar dados da agenda:

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"...>
+    <uses-sdk android:minSdkVersion="14" />
+    <uses-permission android:name="android.permission.READ_CALENDAR" />
+    <uses-permission android:name="android.permission.WRITE_CALENDAR" />
+    ...
+</manifest>
+
+ + +

Tabela de agendas

+ +

A tabela {@link android.provider.CalendarContract.Calendars} contém detalhes +de agendas individuais. As colunas +Agendas a seguir são graváveis tanto por aplicativos quanto por adaptadores de sincronização. +Para obter uma lista completa de campos compatíveis, consulte +a referência {@link android.provider.CalendarContract.Calendars}

+ + + + + + + + + + + + + + + + + + + + + + + + + +
ConstanteDescrição
{@link android.provider.CalendarContract.Calendars#NAME}O nome da agenda.
{@link android.provider.CalendarContract.Calendars#CALENDAR_DISPLAY_NAME}O nome desta agenda que é exibido ao usuário.
{@link android.provider.CalendarContract.Calendars#VISIBLE}Um booleano indicando se a agenda foi selecionada para ser exibida. Um valor +de 0 indica que eventos associados a essa agenda não devem ser +exibidos. Um valor de 1 indica que eventos associados a essa agenda devem +ser exibidos. Esse valor afeta a geração de linhas na tabela {@link +android.provider.CalendarContract.Instances}.
{@link android.provider.CalendarContract.CalendarColumns#SYNC_EVENTS}Um booleano que indica se a agenda deve ser sincronizada e ter +os eventos armazenados no dispositivo. Um valor de 0 indica a não sincronização dessa agenda +e o não armazenamento dos eventos no dispositivo. Um valor de 1 indica a sincronização dos eventos dessa agenda +e o armazenamento dos eventos no dispositivo.
+ +

Consulta em uma agenda

+ +

A seguir há um exemplo que mostra como obter as agendas de propriedade de determinado +usuário. Para simplificar o exemplo, a operação de consulta é exibida no +encadeamento da interface do usuário ("encadeamento principal"). Na prática, isso deve ser feito em um encadeamento +assíncrono em vez de no encadeamento principal. Para ver mais discussões, consulte +Carregadores. Se você não estiver somente +lendo dados, mas modificando-os, consulte {@link android.content.AsyncQueryHandler}. +

+ + +
+// Projection array. Creating indices for this array instead of doing
+// dynamic lookups improves performance.
+public static final String[] EVENT_PROJECTION = new String[] {
+    Calendars._ID,                           // 0
+    Calendars.ACCOUNT_NAME,                  // 1
+    Calendars.CALENDAR_DISPLAY_NAME,         // 2
+    Calendars.OWNER_ACCOUNT                  // 3
+};
+  
+// The indices for the projection array above.
+private static final int PROJECTION_ID_INDEX = 0;
+private static final int PROJECTION_ACCOUNT_NAME_INDEX = 1;
+private static final int PROJECTION_DISPLAY_NAME_INDEX = 2;
+private static final int PROJECTION_OWNER_ACCOUNT_INDEX = 3;
+ + + + + +

Na próxima parte do exemplo, você construirá a consulta. A seleção +especifica os critérios da consulta. Neste exemplo, a consulta busca +agendas que tenham o ACCOUNT_NAME +"sampleuser@google.com", o ACCOUNT_TYPE +"com.google" e o OWNER_ACCOUNT +"sampleuser@google.com". Para ver todas as agendas que um usuário +tenha exibido, não somente as que ele possui, omita o OWNER_ACCOUNT. +A consulta retorna um objeto {@link android.database.Cursor} +que pode ser usado para cruzar o conjunto de resultados retornado pela consulta +do banco de dados. Para ver mais informações sobre o uso de consultas em provedores de conteúdo, +consulte Provedores de conteúdo.

+ + +
// Run query
+Cursor cur = null;
+ContentResolver cr = getContentResolver();
+Uri uri = Calendars.CONTENT_URI;   
+String selection = "((" + Calendars.ACCOUNT_NAME + " = ?) AND (" 
+                        + Calendars.ACCOUNT_TYPE + " = ?) AND ("
+                        + Calendars.OWNER_ACCOUNT + " = ?))";
+String[] selectionArgs = new String[] {"sampleuser@gmail.com", "com.google",
+        "sampleuser@gmail.com"}; 
+// Submit the query and get a Cursor object back. 
+cur = cr.query(uri, EVENT_PROJECTION, selection, selectionArgs, null);
+ +

Essa próxima seção usa o cursor para avançar pelo conjunto de resultados. Ele usa +as constantes definidas no início do exemplo para retornar os valores +de cada campo.

+ +
// Use the cursor to step through the returned records
+while (cur.moveToNext()) {
+    long calID = 0;
+    String displayName = null;
+    String accountName = null;
+    String ownerName = null;
+      
+    // Get the field values
+    calID = cur.getLong(PROJECTION_ID_INDEX);
+    displayName = cur.getString(PROJECTION_DISPLAY_NAME_INDEX);
+    accountName = cur.getString(PROJECTION_ACCOUNT_NAME_INDEX);
+    ownerName = cur.getString(PROJECTION_OWNER_ACCOUNT_INDEX);
+              
+    // Do something with the values...
+
+   ...
+}
+
+ +

Modificação de uma agenda

+ +

Para realizar uma atualização de uma agenda, é possível fornecer o {@link +android.provider.BaseColumns#_ID} da agenda como um ID anexado à +URI + +({@link android.content.ContentUris#withAppendedId(android.net.Uri,long) withAppendedId()}) +ou como o primeiro item de seleção. A seleção +deve iniciar com "_id=?" e o primeiro +selectionArg deve ser o {@link +android.provider.BaseColumns#_ID} da agenda. +Também é possível realizar atualizações com codificações do ID na URI. Este exemplo altera +o nome de exibição de uma agenda usando a +abordagem +({@link android.content.ContentUris#withAppendedId(android.net.Uri,long) withAppendedId()}):

+ +
private static final String DEBUG_TAG = "MyActivity";
+...
+long calID = 2;
+ContentValues values = new ContentValues();
+// The new display name for the calendar
+values.put(Calendars.CALENDAR_DISPLAY_NAME, "Trevor's Calendar");
+Uri updateUri = ContentUris.withAppendedId(Calendars.CONTENT_URI, calID);
+int rows = getContentResolver().update(updateUri, values, null, null);
+Log.i(DEBUG_TAG, "Rows updated: " + rows);
+ +

Inserção de uma agenda

+ +

Agendas são projetadas para serem gerenciadas principalmente por um adaptador de sincronização, por isso +deve-se inserir somente novas agendas como um adaptador de sincronização. Para a maior parte, +aplicativos só podem efetuar mudanças superficiais em agendas, como mudar o nome de exibição. Se +um aplicativo precisa criar uma agenda local, pode fazê-lo realizando +a inserção da agenda como um adaptador de sincronização usando um {@link +android.provider.CalendarContract.SyncColumns#ACCOUNT_TYPE} de {@link +android.provider.CalendarContract#ACCOUNT_TYPE_LOCAL}. +{@link android.provider.CalendarContract#ACCOUNT_TYPE_LOCAL} +é um tipo de conta especial para agendas +não associado a nenhuma conta do dispositivo. Agendas desse tipo não são sincronizadas com um servidor. Para +ver discussões sobre adaptadores de sincronização, consulte Adaptadores de sincronização.

+ +

Tabela de eventos

+ +

A tabela {@link android.provider.CalendarContract.Events} contém detalhes +de eventos individuais. Para adicionar, atualizar ou excluir eventos, um aplicativo deve +conter a permissão {@link android.Manifest.permission#WRITE_CALENDAR} +no arquivo de manifesto.

+ +

As colunas de Eventos a seguir são graváveis tanto por um aplicativo quanto por um adaptador +de sincronização. Para obter uma lista completa de campos compatíveis, consulte a referência de {@link +android.provider.CalendarContract.Events}.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ConstanteDescrição
{@link android.provider.CalendarContract.EventsColumns#CALENDAR_ID}O {@link android.provider.BaseColumns#_ID} da agenda à qual o evento pertence.
{@link android.provider.CalendarContract.EventsColumns#ORGANIZER}E-mail do organizador (dono) do evento.
{@link android.provider.CalendarContract.EventsColumns#TITLE}O título do evento.
{@link android.provider.CalendarContract.EventsColumns#EVENT_LOCATION}Onde o evento acontece.
{@link android.provider.CalendarContract.EventsColumns#DESCRIPTION}A descrição do evento.
{@link android.provider.CalendarContract.EventsColumns#DTSTART}O horário de início do evento em milissegundos em UTC desde a época.
{@link android.provider.CalendarContract.EventsColumns#DTEND}O horário de término do evento em milissegundos em UTC desde a época.
{@link android.provider.CalendarContract.EventsColumns#EVENT_TIMEZONE}O fuso horário do evento.
{@link android.provider.CalendarContract.EventsColumns#EVENT_END_TIMEZONE}O fuso horário do horário de término do evento.
{@link android.provider.CalendarContract.EventsColumns#DURATION}A duração do evento em formato RCF5545. +Por exemplo, um valor de "PT1H" indica que o evento +deve durar uma hora, e um valor de "P2W" indica +uma duração de 2 semanas.
{@link android.provider.CalendarContract.EventsColumns#ALL_DAY}Um valor de 1 indica que esse evento ocupa o dia inteiro, como definido +pelo fuso horário local. Um valor de 0 indica que é um evento comum que pode iniciar +e terminar a qualquer momento durante um dia.
{@link android.provider.CalendarContract.EventsColumns#RRULE}A regra de recorrência do formato do evento. Por +exemplo, "FREQ=WEEKLY;COUNT=10;WKST=SU". Veja +mais exemplos aqui.
{@link android.provider.CalendarContract.EventsColumns#RDATE}As datas de recorrência do evento. + Normalmente, usa-se {@link android.provider.CalendarContract.EventsColumns#RDATE} + em conjunto com {@link android.provider.CalendarContract.EventsColumns#RRULE} + para definir um conjunto agregado +de ocorrências repetidas. Para ver mais discussões, consulte Especificação RFC5545.
{@link android.provider.CalendarContract.EventsColumns#AVAILABILITY}Se esse evento considera tempo ocupado ou se há tempo livre que pode ser +reagendado.
{@link android.provider.CalendarContract.EventsColumns#GUESTS_CAN_MODIFY}Se convidados podem modificar o evento.
{@link android.provider.CalendarContract.EventsColumns#GUESTS_CAN_INVITE_OTHERS}Se convidados podem convidar outros.
{@link android.provider.CalendarContract.EventsColumns#GUESTS_CAN_SEE_GUESTS}Se convidados podem ver a lista de participantes.
+ +

Adição de eventos

+ +

Quando o aplicativo insere um novo evento, recomenda-se usar +uma intenção {@link android.content.Intent#ACTION_INSERT INSERT}, como descrito em Uso de uma intenção para inserir um evento. Contudo, se for +necessário, é possível inserir eventos diretamente. Esta seção descreve como +fazê-lo.

+ + +

Abaixo apresentam-se as regras para inserção de um novo evento:

+
    + +
  • É necessário incluir {@link +android.provider.CalendarContract.EventsColumns#CALENDAR_ID} e {@link +android.provider.CalendarContract.EventsColumns#DTSTART}.
  • + +
  • É necessário incluir um {@link +android.provider.CalendarContract.EventsColumns#EVENT_TIMEZONE}. Para obter uma lista +dos IDs de fuso horário instalados do sistema, use {@link +java.util.TimeZone#getAvailableIDs()}. Observe que essa regra não se aplica +a inserções de evento pela intenção {@link +android.content.Intent#ACTION_INSERT INSERT} descrita em Uso de uma intenção para inserir um evento — nesta +situação, é fornecido um fuso horário padrão.
  • + +
  • Para eventos não recorrentes, é preciso incluir {@link +android.provider.CalendarContract.EventsColumns#DTEND}.
  • + + +
  • Para eventos recorrentes, é necessário incluir uma {@link +android.provider.CalendarContract.EventsColumns#DURATION} além de uma {@link +android.provider.CalendarContract.EventsColumns#RRULE} ou {@link +android.provider.CalendarContract.EventsColumns#RDATE}. Observe que essa regra não se aplica +a inserções de evento pela intenção {@link +android.content.Intent#ACTION_INSERT INSERT} descrita em Uso de uma intenção para inserir um evento — nessa situação, +é possível usar uma {@link +android.provider.CalendarContract.EventsColumns#RRULE} em conjunto com {@link android.provider.CalendarContract.EventsColumns#DTSTART} e {@link android.provider.CalendarContract.EventsColumns#DTEND}. Desta forma, o aplicativo Agenda +a converte em uma duração automaticamente.
  • + +
+ +

A seguir há um exemplo de inserção de um evento: para simplificar, isso está sendo realizado +no encadeamento da IU. Na prática, inserções e atualizações devem ser feitas +em um encadeamento assíncrono para mover a ação para um encadeamento de segundo plano. Para obter +mais informações, consulte {@link android.content.AsyncQueryHandler}.

+ + +
+long calID = 3;
+long startMillis = 0; 
+long endMillis = 0;     
+Calendar beginTime = Calendar.getInstance();
+beginTime.set(2012, 9, 14, 7, 30);
+startMillis = beginTime.getTimeInMillis();
+Calendar endTime = Calendar.getInstance();
+endTime.set(2012, 9, 14, 8, 45);
+endMillis = endTime.getTimeInMillis();
+...
+
+ContentResolver cr = getContentResolver();
+ContentValues values = new ContentValues();
+values.put(Events.DTSTART, startMillis);
+values.put(Events.DTEND, endMillis);
+values.put(Events.TITLE, "Jazzercise");
+values.put(Events.DESCRIPTION, "Group workout");
+values.put(Events.CALENDAR_ID, calID);
+values.put(Events.EVENT_TIMEZONE, "America/Los_Angeles");
+Uri uri = cr.insert(Events.CONTENT_URI, values);
+
+// get the event ID that is the last element in the Uri
+long eventID = Long.parseLong(uri.getLastPathSegment());
+// 
+// ... do something with event ID
+//
+//
+ +

Observação: veja como este exemplo captura o ID +do evento depois que o evento é criado. Este é o modo mais fácil de obter um ID de evento. Muitas vezes o ID do evento +é necessário para realizar outras operações de agenda — por exemplo, adicionar +participantes ou lembretes a um evento.

+ + +

Atualização de eventos

+ +

Quando o aplicativo deseja permitir que o usuário edite um evento, é recomendável +usar uma intenção {@link android.content.Intent#ACTION_EDIT EDIT}, como +descrito em Uso de uma intenção para editar um evento. +Contudo, se for necessário, é possível editar eventos diretamente. Para realizar uma atualização +de um evento, é possível fornecer o _ID +do evento como um ID anexado à URI ({@link +android.content.ContentUris#withAppendedId(android.net.Uri,long) withAppendedId()}) +ou como o primeiro item de seleção. +A seleção deve iniciar com "_id=?" e o primeiro +selectionArg deve ser o _ID do evento. Você também +pode realizar atualizações usando uma seleção sem ID. A seguir há um exemplo de como atualizar +um evento. É possível mudar o título do evento usando +a abordagem +{@link android.content.ContentUris#withAppendedId(android.net.Uri,long) withAppendedId()}:

+ + +
private static final String DEBUG_TAG = "MyActivity";
+...
+long eventID = 188;
+...
+ContentResolver cr = getContentResolver();
+ContentValues values = new ContentValues();
+Uri updateUri = null;
+// The new title for the event
+values.put(Events.TITLE, "Kickboxing"); 
+updateUri = ContentUris.withAppendedId(Events.CONTENT_URI, eventID);
+int rows = getContentResolver().update(updateUri, values, null, null);
+Log.i(DEBUG_TAG, "Rows updated: " + rows);  
+ +

Exclusão de eventos

+ +

Pode-se excluir um evento tanto pelo {@link +android.provider.BaseColumns#_ID} como um ID anexado na URI quanto usando-se +a seleção padrão. Ao usar um ID anexado, não é possível fazer seleções. +Há duas versões de exclusão: como aplicativo e como adaptador de sincronização. +A exclusão por um aplicativo define as colunas excluídas como 1. Esse sinalizador é que diz +ao adaptador de sincronização que a linha foi excluída e que essa exclusão deve ser +propagada para o servidor. A exclusão por um adaptador de sincronização remove o evento +do banco de dados junto com todos os dados associados. A seguir há um exemplo de um aplicativo +excluindo um evento pelo seu {@link android.provider.BaseColumns#_ID}:

+ + +
private static final String DEBUG_TAG = "MyActivity";
+...
+long eventID = 201;
+...
+ContentResolver cr = getContentResolver();
+ContentValues values = new ContentValues();
+Uri deleteUri = null;
+deleteUri = ContentUris.withAppendedId(Events.CONTENT_URI, eventID);
+int rows = getContentResolver().delete(deleteUri, null, null);
+Log.i(DEBUG_TAG, "Rows deleted: " + rows);  
+
+ +

Tabela de participantes

+ +

Cada linha da tabela {@link android.provider.CalendarContract.Attendees} +representa um único participante ou convidado de um evento. Chamar +{@link android.provider.CalendarContract.Reminders#query(android.content.ContentResolver, long, java.lang.String[]) query()} +retorna uma lista de participantes para +o evento com o {@link android.provider.CalendarContract.AttendeesColumns#EVENT_ID} dado. +Esse {@link android.provider.CalendarContract.AttendeesColumns#EVENT_ID} +deve corresponder ao {@link +android.provider.BaseColumns#_ID} de determinado evento.

+ +

A tabela a seguir lista +os campos graváveis. Ao inserir um novo participante, é necessário incluir todos eles +exceto ATTENDEE_NAME. +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ConstanteDescrição
{@link android.provider.CalendarContract.AttendeesColumns#EVENT_ID}O ID do evento.
{@link android.provider.CalendarContract.AttendeesColumns#ATTENDEE_NAME}O nome do participante.
{@link android.provider.CalendarContract.AttendeesColumns#ATTENDEE_EMAIL}O endereço de e-mail do participante.
{@link android.provider.CalendarContract.AttendeesColumns#ATTENDEE_RELATIONSHIP}

A relação do participante com o evento. Uma das seguintes:

+
    +
  • {@link android.provider.CalendarContract.AttendeesColumns#RELATIONSHIP_ATTENDEE}
  • +
  • {@link android.provider.CalendarContract.AttendeesColumns#RELATIONSHIP_NONE}
  • +
  • {@link android.provider.CalendarContract.AttendeesColumns#RELATIONSHIP_ORGANIZER}
  • +
  • {@link android.provider.CalendarContract.AttendeesColumns#RELATIONSHIP_PERFORMER}
  • +
  • {@link android.provider.CalendarContract.AttendeesColumns#RELATIONSHIP_SPEAKER}
  • +
+
{@link android.provider.CalendarContract.AttendeesColumns#ATTENDEE_TYPE}

O tipo de participante. Uma das seguintes:

+
    +
  • {@link android.provider.CalendarContract.AttendeesColumns#TYPE_REQUIRED}
  • +
  • {@link android.provider.CalendarContract.AttendeesColumns#TYPE_OPTIONAL}
  • +
{@link android.provider.CalendarContract.AttendeesColumns#ATTENDEE_STATUS}

O status de participação do participante. Uma das seguintes:

+
    +
  • {@link android.provider.CalendarContract.AttendeesColumns#ATTENDEE_STATUS_ACCEPTED}
  • +
  • {@link android.provider.CalendarContract.AttendeesColumns#ATTENDEE_STATUS_DECLINED}
  • +
  • {@link android.provider.CalendarContract.AttendeesColumns#ATTENDEE_STATUS_INVITED}
  • +
  • {@link android.provider.CalendarContract.AttendeesColumns#ATTENDEE_STATUS_NONE}
  • +
  • {@link android.provider.CalendarContract.AttendeesColumns#ATTENDEE_STATUS_TENTATIVE}
  • +
+ +

Adição de participantes

+ +

A seguir há um exemplo que adiciona um único participante a um evento. Observe que +o {@link android.provider.CalendarContract.AttendeesColumns#EVENT_ID} +é obrigatório:

+ +
+long eventID = 202;
+...
+ContentResolver cr = getContentResolver();
+ContentValues values = new ContentValues();
+values.put(Attendees.ATTENDEE_NAME, "Trevor");
+values.put(Attendees.ATTENDEE_EMAIL, "trevor@example.com");
+values.put(Attendees.ATTENDEE_RELATIONSHIP, Attendees.RELATIONSHIP_ATTENDEE);
+values.put(Attendees.ATTENDEE_TYPE, Attendees.TYPE_OPTIONAL);
+values.put(Attendees.ATTENDEE_STATUS, Attendees.ATTENDEE_STATUS_INVITED);
+values.put(Attendees.EVENT_ID, eventID);
+Uri uri = cr.insert(Attendees.CONTENT_URI, values);
+
+ +

Tabela de lembretes

+ +

Cada linha da tabela {@link android.provider.CalendarContract.Reminders} +representa um único lembrete de um evento. Chamar +{@link android.provider.CalendarContract.Reminders#query(android.content.ContentResolver, long, java.lang.String[]) query()} retorna uma lista de lembretes para +o evento com o dado +{@link android.provider.CalendarContract.AttendeesColumns#EVENT_ID}.

+ + +

A tabela a seguir relaciona os campos graváveis de lembretes. Todos eles devem +ser incluídos ao inserir um novo lembrete. Observe que adaptadores de sincronização especificam +os tipos de lembretes compatíveis na tabela {@link +android.provider.CalendarContract.Calendars}. Consulte +{@link android.provider.CalendarContract.CalendarColumns#ALLOWED_REMINDERS} +para obter detalhes.

+ + + + + + + + + + + + + + + + + + + +
ConstanteDescrição
{@link android.provider.CalendarContract.RemindersColumns#EVENT_ID}O ID do evento.
{@link android.provider.CalendarContract.RemindersColumns#MINUTES}Os minutos antes do evento em que o lembrete deve disparar.
{@link android.provider.CalendarContract.RemindersColumns#METHOD}

O método de alarme, como definido no servidor. Uma das seguintes:

+
    +
  • {@link android.provider.CalendarContract.RemindersColumns#METHOD_ALERT}
  • +
  • {@link android.provider.CalendarContract.RemindersColumns#METHOD_DEFAULT}
  • +
  • {@link android.provider.CalendarContract.RemindersColumns#METHOD_EMAIL}
  • +
  • {@link android.provider.CalendarContract.RemindersColumns#METHOD_SMS}
  • +
+ +

Adição de lembretes

+ +

Este exemplo adiciona um lembrete para um evento. O lembrete dispara +15 minutos antes do evento.

+
+long eventID = 221;
+...
+ContentResolver cr = getContentResolver();
+ContentValues values = new ContentValues();
+values.put(Reminders.MINUTES, 15);
+values.put(Reminders.EVENT_ID, eventID);
+values.put(Reminders.METHOD, Reminders.METHOD_ALERT);
+Uri uri = cr.insert(Reminders.CONTENT_URI, values);
+ +

Tabela de instâncias

+ +

A tabela +{@link android.provider.CalendarContract.Instances} contém +os horários de início e término das ocorrência de um evento. Cada linha nessa tabela +representa uma única ocorrência do evento. A tabela de instâncias não é gravável e fornece +somente um modo de consultar ocorrências de eventos.

+ +

A tabela a seguir relaciona alguns dos campos passíveis de consulta de uma instância. Observe +que o fuso horário é definido por +{@link android.provider.CalendarContract.CalendarCache#KEY_TIMEZONE_TYPE} +e +{@link android.provider.CalendarContract.CalendarCache#KEY_TIMEZONE_INSTANCES}.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ConstanteDescrição
{@link android.provider.CalendarContract.Instances#BEGIN}O horário de início da instância, em milissegundos UTC.
{@link android.provider.CalendarContract.Instances#END}O horário de término da instância, em milissegundos UTC.
{@link android.provider.CalendarContract.Instances#END_DAY}O dia final juliano da instância relativo ao fuso horário +do Agenda. + +
{@link android.provider.CalendarContract.Instances#END_MINUTE}O minuto final da instância calculado a partir de meia-noite +no fuso horário do Agenda.
{@link android.provider.CalendarContract.Instances#EVENT_ID}O _ID do evento para essa instância.
{@link android.provider.CalendarContract.Instances#START_DAY}O dia inicial juliano da instância relativo ao fuso horário do Agenda. +
{@link android.provider.CalendarContract.Instances#START_MINUTE}O minuto inicial da instância calculado a partir de meia-noite, relativo +ao fuso horário do Agenda. +
+ +

Consulta na tabela de instâncias

+ +

Para consultar a Tabela de instâncias, é necessário especificar um intervalo de tempo para a consulta +na URI. Neste exemplo, {@link android.provider.CalendarContract.Instances} +obtém acesso ao campo {@link +android.provider.CalendarContract.EventsColumns#TITLE} por meio +da sua implementação da interface {@link android.provider.CalendarContract.EventsColumns}. +Em outras palavras, {@link +android.provider.CalendarContract.EventsColumns#TITLE} é retornado por uma +vista do banco de dados, não pela consulta da tabela {@link +android.provider.CalendarContract.Instances} bruta.

+ +
+private static final String DEBUG_TAG = "MyActivity";
+public static final String[] INSTANCE_PROJECTION = new String[] {
+    Instances.EVENT_ID,      // 0
+    Instances.BEGIN,         // 1
+    Instances.TITLE          // 2
+  };
+  
+// The indices for the projection array above.
+private static final int PROJECTION_ID_INDEX = 0;
+private static final int PROJECTION_BEGIN_INDEX = 1;
+private static final int PROJECTION_TITLE_INDEX = 2;
+...
+
+// Specify the date range you want to search for recurring
+// event instances
+Calendar beginTime = Calendar.getInstance();
+beginTime.set(2011, 9, 23, 8, 0);
+long startMillis = beginTime.getTimeInMillis();
+Calendar endTime = Calendar.getInstance();
+endTime.set(2011, 10, 24, 8, 0);
+long endMillis = endTime.getTimeInMillis();
+  
+Cursor cur = null;
+ContentResolver cr = getContentResolver();
+
+// The ID of the recurring event whose instances you are searching
+// for in the Instances table
+String selection = Instances.EVENT_ID + " = ?";
+String[] selectionArgs = new String[] {"207"};
+
+// Construct the query with the desired date range.
+Uri.Builder builder = Instances.CONTENT_URI.buildUpon();
+ContentUris.appendId(builder, startMillis);
+ContentUris.appendId(builder, endMillis);
+
+// Submit the query
+cur =  cr.query(builder.build(), 
+    INSTANCE_PROJECTION, 
+    selection, 
+    selectionArgs, 
+    null);
+   
+while (cur.moveToNext()) {
+    String title = null;
+    long eventID = 0;
+    long beginVal = 0;    
+    
+    // Get the field values
+    eventID = cur.getLong(PROJECTION_ID_INDEX);
+    beginVal = cur.getLong(PROJECTION_BEGIN_INDEX);
+    title = cur.getString(PROJECTION_TITLE_INDEX);
+              
+    // Do something with the values. 
+    Log.i(DEBUG_TAG, "Event:  " + title); 
+    Calendar calendar = Calendar.getInstance();
+    calendar.setTimeInMillis(beginVal);  
+    DateFormat formatter = new SimpleDateFormat("MM/dd/yyyy");
+    Log.i(DEBUG_TAG, "Date: " + formatter.format(calendar.getTime()));    
+    }
+ }
+ +

Intenções do Agenda

+

O aplicativo não precisa de permissões para ler e gravar dados de agenda. Em vez disso, ele pode usar intenções compatíveis com o aplicativo Agenda do Android para entregar operações de leitura e gravação. A tabela a seguir lista as intenções compatíveis com o Provedor de Agenda.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AçãoURIDescriçãoExtras

+ {@link android.content.Intent#ACTION_VIEW VIEW}

content://com.android.calendar/time/<ms_since_epoch>

+ Também pode-se consultar a URI com +{@link android.provider.CalendarContract#CONTENT_URI CalendarContract.CONTENT_URI}. +Para ver um exemplo do uso dessa intenção, consulte Uso de intenções para exibir dados de calendários. + +
Abre a agenda no horário especificado por <ms_since_epoch>.Nenhum.

{@link android.content.Intent#ACTION_VIEW VIEW}

+ +

content://com.android.calendar/events/<event_id>

+ + Também é possível consultar a URI com +{@link android.provider.CalendarContract.Events#CONTENT_URI Events.CONTENT_URI}. +Para ver um exemplo do uso dessa intenção, consulte Uso de intenções para exibir dados de calendários. + +
Exibe o evento especificado por <event_id>.{@link android.provider.CalendarContract#EXTRA_EVENT_BEGIN_TIME CalendarContract.EXTRA_EVENT_BEGIN_TIME}
+
+
+ {@link android.provider.CalendarContract#EXTRA_EVENT_END_TIME CalendarContract.EXTRA_EVENT_END_TIME}
{@link android.content.Intent#ACTION_EDIT EDIT}

content://com.android.calendar/events/<event_id>

+ + Também é possível consultar a URI com +{@link android.provider.CalendarContract.Events#CONTENT_URI Events.CONTENT_URI}. +Para ver um exemplo do uso dessa intenção, consulte Uso de uma intenção para editar um evento. + + +
Edita o evento especificado por <event_id>.{@link android.provider.CalendarContract#EXTRA_EVENT_BEGIN_TIME CalendarContract.EXTRA_EVENT_BEGIN_TIME}
+
+
+ {@link android.provider.CalendarContract#EXTRA_EVENT_END_TIME CalendarContract.EXTRA_EVENT_END_TIME}
{@link android.content.Intent#ACTION_EDIT EDIT}
+
+ {@link android.content.Intent#ACTION_INSERT INSERT}

content://com.android.calendar/events

+ + Também é possível consultar a URI com +{@link android.provider.CalendarContract.Events#CONTENT_URI Events.CONTENT_URI}. +Para ver um exemplo do uso dessa intenção, consulte Uso de uma intenção para inserir um evento. + +
Cria um evento.Qualquer um dos extras listados na tabela abaixo.
+ +

A tabela a seguir lista os extras de intenção compatíveis com o Provedor de Agenda: +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Extra de intençãoDescrição
{@link android.provider.CalendarContract.EventsColumns#TITLE Events.TITLE}Nome do evento.
{@link android.provider.CalendarContract#EXTRA_EVENT_BEGIN_TIME +CalendarContract.EXTRA_EVENT_BEGIN_TIME}Horário de início do evento em milissegundos a partir da época.
{@link android.provider.CalendarContract#EXTRA_EVENT_END_TIME +CalendarContract.EXTRA_EVENT_END_TIME}Horário de término do evento em milissegundos a partir da época.
{@link android.provider.CalendarContract#EXTRA_EVENT_ALL_DAY +CalendarContract.EXTRA_EVENT_ALL_DAY}Um booleano que indica que um evento acontece o dia inteiro. O valor pode ser +true ou false.
{@link android.provider.CalendarContract.EventsColumns#EVENT_LOCATION +Events.EVENT_LOCATION}Local do evento.
{@link android.provider.CalendarContract.EventsColumns#DESCRIPTION +Events.DESCRIPTION}Descrição do evento.
+ {@link android.content.Intent#EXTRA_EMAIL Intent.EXTRA_EMAIL}Endereços de e-mail daqueles a convidar em forma de lista com termos separados por vírgula.
+ {@link android.provider.CalendarContract.EventsColumns#RRULE Events.RRULE}A regra de recorrência do evento.
+ {@link android.provider.CalendarContract.EventsColumns#ACCESS_LEVEL +Events.ACCESS_LEVEL}Se o evento é privado ou público.
{@link android.provider.CalendarContract.EventsColumns#AVAILABILITY +Events.AVAILABILITY}Se esse evento considera tempo ocupado na contagem ou se há tempo livre que pode ser reagendado.
+

As seções a seguir descrevem como usar estas intenções.

+ + +

Uso de uma intenção para inserir um evento

+ +

A intenção {@link android.content.Intent#ACTION_INSERT INSERT} +permite que o aplicativo entregue a tarefa de inserção de eventos ao próprio aplicativo Agenda. +Com essa abordagem, o aplicativo não precisará ter a permissão {@link +android.Manifest.permission#WRITE_CALENDAR} contida no arquivo de manifesto.

+ + +

Quando usuários executam um aplicativo que usa essa abordagem, ele os direciona +ao Agenda para finalizar a adição do evento. A intenção {@link +android.content.Intent#ACTION_INSERT INSERT} usa campos extras para +pré-preencher um formulário com os detalhes do evento na Agenda. Os usuários podem, +então, cancelar o evento, editar o formulário conforme o necessário ou salvar o evento nas suas +agendas.

+ + + +

A seguir há um fragmento de código que agenda um evento em 19 de janeiro de 2012, que acontece +das 7h30 às 8h30. Observe o exposto a seguir sobre esse fragmento de código:

+ +
    +
  • Ele especifica {@link android.provider.CalendarContract.Events#CONTENT_URI Events.CONTENT_URI} + como a URI.
  • + +
  • Ele usa os campos extras {@link +android.provider.CalendarContract#EXTRA_EVENT_BEGIN_TIME +CalendarContract.EXTRA_EVENT_BEGIN_TIME} e {@link +android.provider.CalendarContract#EXTRA_EVENT_END_TIME +CalendarContract.EXTRA_EVENT_END_TIME} para pré-preencher o formulário +com o horário do evento. Os valores desses horários devem estar em milissegundos UTC +da época.
  • + +
  • Ele usa o campo extra {@link android.content.Intent#EXTRA_EMAIL Intent.EXTRA_EMAIL} + para fornecer uma lista de termos separados por vírgula de convidados, especificados por endereço de e-mail.
  • + +
+
+Calendar beginTime = Calendar.getInstance();
+beginTime.set(2012, 0, 19, 7, 30);
+Calendar endTime = Calendar.getInstance();
+endTime.set(2012, 0, 19, 8, 30);
+Intent intent = new Intent(Intent.ACTION_INSERT)
+        .setData(Events.CONTENT_URI)
+        .putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, beginTime.getTimeInMillis())
+        .putExtra(CalendarContract.EXTRA_EVENT_END_TIME, endTime.getTimeInMillis())
+        .putExtra(Events.TITLE, "Yoga")
+        .putExtra(Events.DESCRIPTION, "Group class")
+        .putExtra(Events.EVENT_LOCATION, "The gym")
+        .putExtra(Events.AVAILABILITY, Events.AVAILABILITY_BUSY)
+        .putExtra(Intent.EXTRA_EMAIL, "rowan@example.com,trevor@example.com");
+startActivity(intent);
+
+ +

Uso de uma intenção para editar um evento

+ +

É possível atualizar um evento diretamente, como descrito em Atualização de eventos. Porém, o uso da intenção {@link +android.content.Intent#ACTION_EDIT EDIT} permite que um aplicativo +sem permissão forneça a edição de eventos ao aplicativo Agenda. +Quando usuários finalizam a edição do evento no Agenda, retornam +ao aplicativo original.

A seguir há um exemplo de uma intenção que define um novo +título para o evento especificado e permite aos usuários editar o evento no Agenda.

+ + +
long eventID = 208;
+Uri uri = ContentUris.withAppendedId(Events.CONTENT_URI, eventID);
+Intent intent = new Intent(Intent.ACTION_EDIT)
+    .setData(uri)
+    .putExtra(Events.TITLE, "My New Title");
+startActivity(intent);
+ +

Uso de intenções para exibir dados de agenda

+

O Provedor de Agenda oferece dois modos diferentes de usar a intenção {@link android.content.Intent#ACTION_VIEW VIEW}:

+
    +
  • Para abrir o Agenda em uma data específica.
  • +
  • Para exibir um evento.
  • + +
+

A seguir há um exemplo que mostra como abrir o Agenda em uma data específica:

+
// A date-time specified in milliseconds since the epoch.
+long startMillis;
+...
+Uri.Builder builder = CalendarContract.CONTENT_URI.buildUpon();
+builder.appendPath("time");
+ContentUris.appendId(builder, startMillis);
+Intent intent = new Intent(Intent.ACTION_VIEW)
+    .setData(builder.build());
+startActivity(intent);
+ +

Abaixo há um exemplo que mostra como abrir um evento para exibição:

+
long eventID = 208;
+...
+Uri uri = ContentUris.withAppendedId(Events.CONTENT_URI, eventID);
+Intent intent = new Intent(Intent.ACTION_VIEW)
+   .setData(uri);
+startActivity(intent);
+
+ + +

Adaptadores de sincronização

+ + +

Há pequenas diferenças apenas nos modos de acesso ao Provedor de Agenda +via aplicativo e via adaptador de sincronização:

+ +
    +
  • Um adaptador de sincronização precisa especificar que é um adaptador de sincronização que define {@link android.provider.CalendarContract#CALLER_IS_SYNCADAPTER} como true.
  • + + +
  • Os adaptadores de sincronização precisam fornecer um {@link +android.provider.CalendarContract.SyncColumns#ACCOUNT_NAME} e um {@link +android.provider.CalendarContract.SyncColumns#ACCOUNT_TYPE} como parâmetros da consulta na URI.
  • + +
  • Os adaptadores de sincronização têm acesso de gravação a mais colunas do que um aplicativo ou widget. + Por exemplo: um aplicativo só pode modificar algumas características de uma agenda, +como nome, nome de exibição, configurações de visibilidade e se a agenda está +sincronizada. Por comparação, um adaptador de sincronização pode acessar não somente essas colunas, mas muitas outras, +como cores da agenda, fuso horário, nível de acesso, local etc. +No entanto, um adaptador de sincronização é restrito ao ACCOUNT_NAME +e ao ACCOUNT_TYPE que especificou.
+ +

A seguir há um método auxiliar que pode ser usado para retornar uma URI para uso com um adaptador de sincronização:

+
 static Uri asSyncAdapter(Uri uri, String account, String accountType) {
+    return uri.buildUpon()
+        .appendQueryParameter(android.provider.CalendarContract.CALLER_IS_SYNCADAPTER,"true")
+        .appendQueryParameter(Calendars.ACCOUNT_NAME, account)
+        .appendQueryParameter(Calendars.ACCOUNT_TYPE, accountType).build();
+ }
+
+

Para obter uma implementação de exemplo de um adaptador de sincronização (não especificamente relacionada ao Agenda), consulte +SampleSyncAdapter. diff --git a/docs/html-intl/intl/pt-br/guide/topics/providers/contacts-provider.jd b/docs/html-intl/intl/pt-br/guide/topics/providers/contacts-provider.jd new file mode 100644 index 0000000000000000000000000000000000000000..0d42d2daed530d74530c8bce80ac736a297ee3a9 --- /dev/null +++ b/docs/html-intl/intl/pt-br/guide/topics/providers/contacts-provider.jd @@ -0,0 +1,2356 @@ +page.title=Provedor de Contatos +@jd:body +

+
+

Visualização rápida

+
    +
  • Repositório de informações sobre pessoas do Android.
  • +
  • + Sincronização com a web. +
  • +
  • + Integração de dados de fluxos sociais. +
  • +
+

Neste documento

+
    +
  1. + Organização do Provedor de Contatos +
  2. +
  3. + Contatos brutos +
  4. +
  5. + Dados +
  6. +
  7. + Contatos +
  8. +
  9. + Dados de adaptadores de sincronização +
  10. +
  11. + Permissões necessárias +
  12. +
  13. + O perfil do usuário +
  14. +
  15. + Metadados do Provedor de Contatos +
  16. +
  17. + Acesso ao Provedor de Contatos +
  18. +
  19. +
  20. + Adaptadores de sincronização do Provedor de Contatos +
  21. +
  22. + Dados de fluxos sociais +
  23. +
  24. + Recursos adicionais do Provedor de Contatos +
  25. +
+

Classes principais

+
    +
  1. {@link android.provider.ContactsContract.Contacts}
  2. +
  3. {@link android.provider.ContactsContract.RawContacts}
  4. +
  5. {@link android.provider.ContactsContract.Data}
  6. +
  7. {@code android.provider.ContactsContract.StreamItems}
  8. +
+

Exemplos relacionados

+
    +
  1. + + Gerente de contatos + +
  2. +
  3. + + Exemplo de adaptador de sincronização +
  4. +
+

Veja também

+
    +
  1. + + Preceitos do provedor de conteúdo + +
  2. +
+
+
+

+ O Provedor de Contatos é um componente poderoso e flexível do Android que gerencia +o principal repositório de dados sobre pessoas do dispositivo. O Provedor de Contatos é a fonte dos dados + vistos nos aplicativos de contatos do dispositivo e, por ele, também é possível acessar os dados + no próprio aplicativo e transferi-los entre o dispositivo e serviços on-line. O provedor fornece + uma grande variedade de fontes de dados e tenta gerenciar o máximo de dados possíveis para cada pessoa, + uma vez que organizá-los é algo complexo. Por isso, a API do provedor contém + um conjunto extensivo de classes e interfaces de contrato que facilitam a recuperação e a modificação + dos dados. +

+

+ Este guia descreve o seguinte: +

+
    +
  • + A estrutura básica do provedor. +
  • +
  • + Como recuperar dados por um provedor. +
  • +
  • + Como modificar dados no provedor. +
  • +
  • + Como criar um adaptador de sincronização para sincronizar dados do servidor com +o Provedor de Contatos. +
  • +
+

+ Este guia considera que o leitor conhece os preceitos dos provedores de conteúdo do Android. Para saber mais + sobre provedores de conteúdo do Android, leia o guia + + Preceitos do provedor de conteúdo. + O aplicativo Exemplo de adaptador de sincronização + é um exemplo de uso de um adaptador de sincronização que transfere dados entre o Provedor + de contatos e um aplicativo de amostra hospedado pelo Google Web Services. +

+

Organização do Provedor de Contatos

+

+ O Provedor de Contatos é um componente do provedor de conteúdo do Android. Ele mantém três tipos de + dados sobre uma pessoa, sendo cada um deles correspondente a uma tabela fornecida pelo provedor, como + ilustrado na figura 1: +

+ +

+ Figura 1. Estrutura da tabela do Provedor de Contatos. +

+

+ As três tabelas são comumente identificadas pelo nome de suas classes de contrato. As classes + definem constantes para URIs de conteúdo e nomes e valores de colunas usados pela tabela: +

+
+
+ Tabela {@link android.provider.ContactsContract.Contacts} +
+
+ As linhas representam pessoas diferentes com base em agregações de linhas de contatos brutos. +
+
+ Tabela {@link android.provider.ContactsContract.RawContacts} +
+
+ As linhas contêm um resumo dos dados de uma pessoa, específicos a um tipo e uma conta de usuário. +
+
+ Tabela {@link android.provider.ContactsContract.Data} +
+
+ As linhas contêm os detalhes dos contatos brutos, como endereços de e-mail ou números de telefone. +
+
+

+ As outras tabelas representadas pelas classes de contrato em {@link android.provider.ContactsContract} + são tabelas auxiliares que o Provedor de Contatos usa para gerenciar suas operações ou compatibilizar + funções específicas nos contatos do dispositivo ou em aplicativos de telefonia. +

+

Contatos brutos

+

+ Os contatos brutos representam os dados de uma pessoa provenientes de um tipo único de conta e um nome + de conta. Como o Provedor de Contatos permite mais de um serviço on-line como fonte +de dados de uma pessoa, ele permite diversos contatos brutos para a mesma pessoa. + Diversos contatos brutos também permitem que um usuário combine os dados de uma pessoa de mais de uma conta + a partir do mesmo tipo de conta. +

+

+ A maioria dos dados de um contato bruto não é armazenada + na tabela {@link android.provider.ContactsContract.RawContacts}. Em vez disso, é armazenada em uma ou mais + linhas na tabela {@link android.provider.ContactsContract.Data}. Cada linha de dados tem uma coluna + {@link android.provider.ContactsContract.DataColumns#RAW_CONTACT_ID Data.RAW_CONTACT_ID} que + contém o valor {@code android.provider.BaseColumns#_ID RawContacts._ID} de sua + linha {@link android.provider.ContactsContract.RawContacts} pai. +

+

Colunas importantes de contatos brutos

+

+ As colunas importantes na tabela {@link android.provider.ContactsContract.RawContacts} são + listadas na tabela 1. Leia as observações que se seguem após a tabela: +

+

+ Tabela 1. Importantes colunas de contatos brutos. +

+ + + + + + + + + + + + + + + + + + + + +
Nome da colunaUsoObservações
+ {@link android.provider.ContactsContract.SyncColumns#ACCOUNT_NAME} + + O nome da conta para o tipo de conta que é a fonte desse contato bruto. + Por exemplo: o nome da conta de uma conta da Google é um dos endereços do Gmail do proprietário + do dispositivo. Para obter mais informações, + acesse o próximo item + {@link android.provider.ContactsContract.SyncColumns#ACCOUNT_TYPE}. + + O formato desse nome é específico deste tipo de conta. + Não se trata necessariamente de um endereço de e-mail. +
+ {@link android.provider.ContactsContract.SyncColumns#ACCOUNT_TYPE} + + O tipo de conta que é a fonte desse contato bruto. Por exemplo: o tipo + de conta de uma conta da Google é com.google. Sempre qualifique o tipo de conta + com um identificador de domínio para um domínio que você controla ou possui. Isso garantirá que seu + tipo de conta seja único. + + Os tipos de conta que fornecem dados de contatos normalmente têm um adaptador de sincronização associado que + sincroniza-se com o Provedor de Contatos. +
+ {@link android.provider.ContactsContract.RawContactsColumns#DELETED} + + O sinalizador "excluído" de um contato bruto. + + Esse sinalizador permite que o Provedor de Contatos mantenha a linha internamente até + que os adaptadores de sincronização possam excluí-la dos servidores e, em seguida, excluí-la + do repositório. +
+

Observações

+

+ As observações a seguir sobre a tabela + {@link android.provider.ContactsContract.RawContacts} são importantes: +

+
    +
  • + O nome de um contato bruto não é armazenado em sua linha em + {@link android.provider.ContactsContract.RawContacts}. Em vez disso, é armazenado na + tabela {@link android.provider.ContactsContract.Data}, em uma + linha {@link android.provider.ContactsContract.CommonDataKinds.StructuredName}. Os contatos brutos + têm apenas uma linha desse tipo na tabela {@link android.provider.ContactsContract.Data}. +
  • +
  • + Atenção: para usar os dados da própria conta em uma linha de contato bruto, é necessário + registrá-la primeiro com o {@link android.accounts.AccountManager}. Para isso, faça com que + os usuários adicionem o tipo e o nome da conta à lista de contas. Caso + contrário, o Provedor de Contatos excluirá a linha do contato bruto automaticamente. +

    + Por exemplo: se quiser que o aplicativo mantenha dados de contato do seu serviço baseado em web + com o {@code com.example.dataservice} de domínio e com a conta do usuário do serviço + {@code becky.sharp@dataservice.example.com}, o usuário precisa, primeiramente, adicionar o "tipo" + de conta ({@code com.example.dataservice}) e o "nome" da conta + ({@code becky.smart@dataservice.example.com}) antes que o aplicativo adicione linhas de contato bruto. + Você pode explicar esse requisito ao usuário em documentações ou pode exigir que o + usuário adicione o tipo ou o nome ou ambos. Tipos e nomes de conta + são descritos com mais detalhes na próxima seção. +

  • +
+

Fontes de dados de contatos brutos

+

+ Para compreender como os contatos brutos funcionam, considere a usuária "Emily Dickinson" que tem as seguintes + três contas de usuário definidas no seu dispositivo: +

+
    +
  • emily.dickinson@gmail.com
  • +
  • emilyd@gmail.com
  • +
  • Conta do twitter "belle_of_amherst"
  • +
+

+ Essa usuária ativou Sincronizar contatos para todas as três contas nas + Configurações da conta. +

+

+ Suponhamos que Emily Dickinson abra uma janela do navegador, acesse o Gmail como + emily.dickinson@gmail.com, abra + Contatos e adicione "Thomas Higginson". Mais tarde, ela acessa o Gmail como + emilyd@gmail.com e envia um e-mail para "Thomas Higginson", o que automaticamente + o adiciona como um contato. Ela também segue "colonel_tom" (ID do twitter de Thomas Higginson) no + Twitter. +

+

+ O Provedor de Contatos cria três contatos brutos como resultado desse trabalho: +

+
    +
  1. + Um contato bruto de "Thomas Higginson" associado a emily.dickinson@gmail.com. + O tipo de conta do usuário é Google. +
  2. +
  3. + Um segundo contato bruto de "Thomas Higginson" associado a emilyd@gmail.com. + O tipo de conta do usuário também é Google. Há um segundo contato bruto, + embora o nome seja idêntico a um nome anterior porque a pessoa foi adicionada + a uma conta de usuário diferente. +
  4. +
  5. + Um terceiro contato bruto de "Thomas Higginson" associado a "belle_of_amherst". O tipo + de conta de usuário é Twitter. +
  6. +
+

Dados

+

+ Como observado anteriormente, os dados de um contato bruto são armazenados + em uma linha {@link android.provider.ContactsContract.Data} vinculada ao valor _ID + do contato bruto. Isso permite que um único contato bruto tenha diversas instâncias do mesmo + tipo de dados, como endereços de e-mail ou números de telefone. Por exemplo: se + "Thomas Higginson" para {@code emilyd@gmail.com} (a linha do contato bruto de Thomas Higginson + associada à conta Google emilyd@gmail.com) tem um endereço de e-mail pessoal + thigg@gmail.com e um de trabalho + thomas.higginson@gmail.com, o Provedor de Contatos armazena as duas linhas de endereço de e-mail + e vincula ambas ao contato bruto. +

+

+ Observe que diferentes tipos de dados são armazenados nessa única tabela. Linhas de nome de exibição, + número de telefone, e-mail, endereço postal, foto e detalhes de site são encontradas + na tabela {@link android.provider.ContactsContract.Data}. Para ajudar a gerenciar isso, + a tabela {@link android.provider.ContactsContract.Data} tem algumas colunas com nomes descritivos + e outras com nomes genéricos. O conteúdo de uma coluna de nome descritivo tem o mesmo significado + independente do tipo de dado da linha, enquanto o conteúdo de uma coluna de nome genérico tem + diferentes significados dependendo do tipo de dados. +

+

Nomes de coluna descritiva

+

+ A seguir há alguns exemplos de nomes descritivos de colunas: +

+
+
+ {@link android.provider.ContactsContract.Data#RAW_CONTACT_ID} +
+
+ O valor da coluna _ID do contato bruto para estes dados. +
+
+ {@link android.provider.ContactsContract.Data#MIMETYPE} +
+
+ O tipo de dado armazenado nessa linha, expresso como um tipo MIME personalizado. O Provedor de Contatos + usa os tipos MIME definidos nas subclasses de + {@link android.provider.ContactsContract.CommonDataKinds}. Esses tipos MIME têm código aberto + e podem ser usados por qualquer aplicativo ou adaptador de sincronização que funcione com o Provedor de Contatos. +
+
+ {@link android.provider.ContactsContract.DataColumns#IS_PRIMARY} +
+
+ Se esse tipo de linha de dados puder ocorrer mais do que uma vez em um contato bruto, + a coluna {@link android.provider.ContactsContract.DataColumns#IS_PRIMARY} sinaliza + a linha de dados que contém os dados primários do tipo. Por exemplo: se + o usuário pressionar por algum tempo um número de telefone de um contato e selecionar Definir padrão, + a linha {@link android.provider.ContactsContract.Data} que contém esse número + tem sua coluna {@link android.provider.ContactsContract.DataColumns#IS_PRIMARY} definida + como um valor diferente de zero. +
+
+

Nomes de coluna genérica

+

+ Há 15 colunas genéricas de nome DATA1 a + DATA15 que estão, em geral, disponíveis e quatro colunas genéricas + adicionais SYNC1 a SYNC4 que devem ser usadas somente por adaptadores + de sincronização. As constantes de nome da coluna genérica sempre funcionam independentemente do tipo + de dados que a linha contenha. +

+

+ A coluna DATA1 é indexada. O Provedor de Contatos sempre usa essa coluna para + os dados que o provedor espera serem os alvos mais frequentes de uma consulta. Por exemplo: + em uma linha de e-mail, essa coluna contém o endereço de e-mail atual. +

+

+ Por convenção, a coluna DATA15 é reservada para armazenar dados de BLOBs + (Binary Large Object), como miniaturas de fotos. +

+

Nomes de coluna de tipo específico

+

+ Para facilitar o trabalho com as colunas para um tipo específico de linha, o Provedor de Contatos + também fornece constantes de nome de colunas de tipo específico, definidas em subclasses de + {@link android.provider.ContactsContract.CommonDataKinds}. As constantes simplesmente dão + um nome de constante diferente para o mesmo nome de coluna, o que ajuda no acesso aos dados em uma linha + de um tipo específico. +

+

+ Por exemplo: a classe {@link android.provider.ContactsContract.CommonDataKinds.Email} define + constantes de nome de coluna de tipo específico para uma linha {@link android.provider.ContactsContract.Data} + que tem o tipo MIME + {@link android.provider.ContactsContract.CommonDataKinds.Email#CONTENT_ITEM_TYPE + Email.CONTENT_ITEM_TYPE}. A classe contém a constante + {@link android.provider.ContactsContract.CommonDataKinds.Email#ADDRESS} para a coluna de endereço + de e-mail. O valor atual + de {@link android.provider.ContactsContract.CommonDataKinds.Email#ADDRESS} é "data1", que é + igual ao nome genérico da coluna. +

+

+ Atenção: não adicione seus dados personalizados + à tabela {@link android.provider.ContactsContract.Data} usando uma linha que tenha um dos + tipos de MIME predefinidos do provedor. Caso contrário, há o risco de perda de dados ou mau funcionamento + do provedor. Por exemplo: não se deve adicionar uma linha com o tipo MIME + {@link android.provider.ContactsContract.CommonDataKinds.Email#CONTENT_ITEM_TYPE + Email.CONTENT_ITEM_TYPE} que contenha um nome de usuário em vez de um endereço de e-mail na + coluna DATA1. Se for usado um tipo MIME personalizado para a linha, será possível + definir os próprios nomes de coluna de tipo específico e usar as colunas como quiser. +

+

+ A figura 2 mostra como colunas descritivas e colunas de dados aparecem + em uma linha {@link android.provider.ContactsContract.Data} e como os nomes de coluna de tipo específico se "sobrepõem" + aos nomes de coluna genérica. +

+How type-specific column names map to generic column names +

+ Figura 2. Nomes de coluna de tipo específico e nomes de coluna genérica. +

+

Classes de nome de coluna de tipo específico

+

+ A tabela 2 lista as classes de nome de coluna de tipo específico mais usadas: +

+

+ Tabela 2. Classes de nome de coluna de tipo específico

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Classes de mapeamentoTipo de dadosObservações
{@link android.provider.ContactsContract.CommonDataKinds.StructuredName}Os dados de nome do contato bruto associados a essa linha de dados.Os contatos brutos têm apenas uma dessas linhas.
{@link android.provider.ContactsContract.CommonDataKinds.Photo}A foto principal do contato bruto associada a essa linha de dados.Os contatos brutos têm apenas uma dessas linhas.
{@link android.provider.ContactsContract.CommonDataKinds.Email}Um endereço de e-mail do contato bruto associado a essa linha de dados.Os contatos brutos podem ter diversos endereços de e-mail.
{@link android.provider.ContactsContract.CommonDataKinds.StructuredPostal}Um endereço postal do contato bruto associado a essa linha de dados.Os contatos brutos podem ter diversos endereços postais.
{@link android.provider.ContactsContract.CommonDataKinds.GroupMembership}Um identificador que vincula o contato bruto a um dos grupos no Provedor de Contatos. + Grupos são um recurso opcional de um tipo e um nome de conta. Eles são descritos + mais detalhadamente na seção Grupos de contato. +
+

Contatos

+

+ O Provedor de Contatos combina as linhas do contato bruto entre todos os tipos e nomes de conta + para formar um contato. Isso facilita a exibição e modificação de todos os dados de uma pessoa que um + usuário tenha coletado. O Provedor de Contatos gerencia a criação de linhas + de novos contatos e a agregação de contatos brutos a uma linha de contato existente. Nem os aplicativos, nem + os adaptadores de sincronização têm permissão para adicionar contatos, e algumas colunas em uma linha de contato são de somente leitura. +

+

+ Observação: a tentativa de adicionar um contato ao Provedor de Contatos com um + {@link android.content.ContentResolver#insert(Uri,ContentValues) insert()} gerará + uma exceção {@link java.lang.UnsupportedOperationException}. A tentativa de atualizar uma coluna + listada como "somente leitura" será ignorada. +

+

+ O Provedor de Contatos cria um novo contato em resposta à adição de um novo contato bruto + que não corresponda a nenhum contato existente. O provedor também faz isso se os dados de um + contato bruto existente mudam de modo a não corresponder mais ao contato + inserido anteriormente. Se um aplicativo ou um adaptador de sincronização criar um novo contato bruto que + corresponda a um contato existente, o novo contato bruto será agregado ao contato + existente. +

+

+ O Provedor de Contatos liga uma linha do contato às linhas do contato bruto com a coluna _ID + da linha do contato na tabela + {@link android.provider.ContactsContract.Contacts Contacts}. A coluna CONTACT_ID da tabela de contatos brutos + {@link android.provider.ContactsContract.RawContacts} contém valores _ID para + a linha dos contatos associados a cada linha dos contatos brutos. +

+

+ A tabela {@link android.provider.ContactsContract.Contacts} também tem a coluna + {@code android.provider.ContactsContract.ContactsColumns#LOOKUP_KEY}, que é + um vínculo "permanente" com a linha do contato. Como o Provedor de Contatos mantém contatos + automaticamente, ele pode mudar o valor de {@code android.provider.BaseColumns#_ID} da linha do contato + em resposta a uma agregação ou sincronização. Mesmo que isso aconteça, a URI de conteúdo + {@link android.provider.ContactsContract.Contacts#CONTENT_LOOKUP_URI} combinada com + {@code android.provider.ContactsContract.ContactsColumns#LOOKUP_KEY} do contato continuará + apontado para a linha do contato para permitir o uso de + {@code android.provider.ContactsContract.ContactsColumns#LOOKUP_KEY} + e manter ligações com contatos "favoritos" e assim por diante. Essa coluna tem o próprio formato, que + não tem nenhuma relação com o formato da coluna {@code android.provider.BaseColumns#_ID}. +

+

+ A figura 3 mostra como as três tabelas principais se relacionam entre si. +

+Contacts provider main tables +

+ Figura 3. Contatos, contatos brutos e relacionamentos da tabela de detalhes. +

+

Dados de adaptadores de sincronização

+

+ Os usuários inserem dados de contato diretamente no dispositivo, mas os dados também são direcionados ao Provedor + de Contatos a partir de serviços web via adaptadores de sincronização, que automatizam + a transferência de dados entre dispositivo e serviços. Adaptadores de sincronização funcionam em segundo plano, + controlados pelo sistema, e chamam métodos {@link android.content.ContentResolver} + para gerenciar os dados. +

+

+ No Android, o serviço web com o qual um adaptador de sincronização trabalha é identificado por um tipo de conta. + Cada adaptador de sincronização funciona com um tipo de conta, mas é compatível com diversos nomes de conta + para o tipo em questão. Tipos e nomes de conta são descritos brevemente na seção + Fontes de dados de contatos brutos. As definições a seguir fornecem + mais detalhes e descrevem como o tipo e o nome de conta se relacionam com adaptadores de sincronização e serviços. +

+
+
+ Tipo de conta +
+
+ Identifica um serviço em que o usuário tenha armazenado dados. Na maior parte do tempo, o usuário deve + autenticar com o serviço. Por exemplo: Google Contacts é um tipo de conta, identificado + pelo código google.com. Esse valor corresponde ao tipo de conta usado pelo + {@link android.accounts.AccountManager}. +
+
+ Nome da conta +
+
+ Identifica uma conta ou login específico de um tipo de conta. As contas Google Contacts + são idênticas às contas Google, que têm um endereço de e-mail como nome da conta. + Outros serviços podem usar um nome de usuário com só uma palavra ou ID numérico. +
+
+

+ Os tipos de conta não precisam ser exclusivos. Um usuário pode configurar diversas contas Google Contacts + e baixar os dados dela para o Provedor de Contatos. Isso pode acontecer se o usuário tiver um grupo de + contatos pessoais para um nome de conta pessoal e outro grupo para um de conta de trabalho. Nomes de conta normalmente + são exclusivos. Juntos, eles identificam um fluxo de dados específicos entre o Provedor de Contatos e + um serviço externo. +

+

+ Se você desejar transferir os dados do serviço ao Provedor de Contatos, precisará criar seu + próprio adaptador de sincronização. Isso é descrito com mais detalhes na seção + Adaptadores de sincronização do Provedor de Contatos. +

+

+ A figura 4 mostra como o Provedor de Contatos se insere no fluxo de dados + sobre pessoas. Na caixa marcada "adaptadores de sincronização", cada adaptador é etiquetado pelo tipo de conta. +

+Flow of data about people +

+ Figura 4. Fluxo de dados do Provedor de Contatos. +

+

Permissões necessárias

+

+ Os aplicativos que queiram acessar o Provedor de Contatos devem solicitar as seguintes + permissões: +

+
+
Acesso de leitura a uma ou mais tabelas
+
+ {@link android.Manifest.permission#READ_CONTACTS}, especificado em + AndroidManifest.xml com + o elemento + <uses-permission> + como <uses-permission android:name="android.permission.READ_CONTACTS">. +
+
Acesso de gravação a uma ou mais tabelas
+
+ {@link android.Manifest.permission#WRITE_CONTACTS}, especificado em + AndroidManifest.xml com + o elemento + <uses-permission> + como <uses-permission android:name="android.permission.WRITE_CONTACTS">. +
+
+

+ Essas permissões não se estendem aos dados do perfil do usuário. O perfil do usuário + e suas permissões necessárias são abordados na seção a seguir: + O perfil do usuário. +

+

+ Lembre-se de que os dados de contato do usuário são pessoais e confidenciais. Os usuários se preocupam + com a privacidade e, por isso, não querem aplicativos que coletem dados sobre eles ou seus contatos. + Se não for óbvio o motivo da necessidade de permissões para acessar os dados de contato de um usuário, eles podem atribuir + avaliações ruins ao seu aplicativo ou simplesmente não instalá-lo. +

+

O perfil do usuário

+

+ A tabela {@link android.provider.ContactsContract.Contacts} tem uma única linha contendo + os dados do perfil do usuário do dispositivo. Esses dados descrevem o user do dispositivo em vez + de um dos contatos do usuário. A linha de contatos do perfil é vinculada a uma linha + de contatos brutos para cada sistema que usa um perfil. + Cada linha de contato bruto de perfil pode ter diversas linhas de dados. Constantes de acesso ao perfil + do usuário estão disponíveis na classe {@link android.provider.ContactsContract.Profile}. +

+

+ O acesso ao perfil do usuário exige permissões especiais. Além das permissões + {@link android.Manifest.permission#READ_CONTACTS} e + {@link android.Manifest.permission#WRITE_CONTACTS} necessárias para ler e gravar, o acesso + ao perfil do usuário requer as permissões {@code android.Manifest.permission#READ_PROFILE} e + {@code android.Manifest.permission#WRITE_PROFILE}, respectivamente, para ler e + gravar. +

+

+ Lembre-se de que é preciso considerar a confidencialidade de um perfil do usuário. A permissão + {@code android.Manifest.permission#READ_PROFILE} permite o acesso aos dados de identificação + pessoal do usuário do dispositivo. Certifique-se de informar ao usuário o motivo + da necessidade de permissões de acesso ao perfil do usuário na descrição do aplicativo. +

+

+ Para recuperar a linha de contato que contém o perfil do usuário, + chame {@link android.content.ContentResolver#query(Uri,String[], String, String[], String) + ContentResolver.query()}. Defina a URI de conteúdo como + {@link android.provider.ContactsContract.Profile#CONTENT_URI} e não forneça nenhum + critério de seleção. Também é possível usar essa URI de conteúdo como base para recuperar + contatos brutos ou dados do perfil. Por exemplo, esse fragmento recupera dados do perfil: +

+
+// Sets the columns to retrieve for the user profile
+mProjection = new String[]
+    {
+        Profile._ID,
+        Profile.DISPLAY_NAME_PRIMARY,
+        Profile.LOOKUP_KEY,
+        Profile.PHOTO_THUMBNAIL_URI
+    };
+
+// Retrieves the profile from the Contacts Provider
+mProfileCursor =
+        getContentResolver().query(
+                Profile.CONTENT_URI,
+                mProjection ,
+                null,
+                null,
+                null);
+
+

+ Observação: se você recuperar diversas linhas de contato e quiser determinar se uma delas + é o perfil do usuário, teste a coluna + {@link android.provider.ContactsContract.ContactsColumns#IS_USER_PROFILE} da linha. Essa coluna + é definida como "1" se o contato for o perfil do usuário. +

+

Metadados do Provedor de Contatos

+

+ O Provedor de Contatos gerencia dados que acompanham o estado dos dados de contatos + no repositório. Esses metadados sobre o repositório são armazenados em vários locais, inclusive + os contatos brutos, os dados e as linhas da tabela de contatos, + a tabela {@link android.provider.ContactsContract.Settings} + e a tabela {@link android.provider.ContactsContract.SyncState}. A tabela a seguir mostra + o efeito de cada uma dessas partes de metadados: +

+

+ Tabela 3. Metadados no Provedor de Contatos

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TabelaColunaValoresSignificado
{@link android.provider.ContactsContract.RawContacts}{@link android.provider.ContactsContract.SyncColumns#DIRTY}"0" - sem modificação desde a última sincronização. + Sinaliza contatos brutos que não mudaram no dispositivo e devem ser sincronizados com + o servidor. O valor é definido automaticamente pelo Provedor de Contatos quando os aplicativos + do Android atualizam uma linha. +

+ Adaptadores de sincronização que modificam o contato bruto ou as tabelas de dados sempre devem anexar a + string {@link android.provider.ContactsContract#CALLER_IS_SYNCADAPTER} + à URI de conteúdo usada. Isso evita que o provedor sinalize alguma linha como suja. + Caso contrário, as modificações do adaptador de sincronização parecem ser modificações locais e são + enviadas ao servidor, mesmo que o servidor seja a origem da modificação. +

+
"1" - modificado desde a última sincronização, precisa ser sincronizado com o servidor.
{@link android.provider.ContactsContract.RawContacts}{@link android.provider.ContactsContract.SyncColumns#VERSION}O número da versão dessa linha. + O Provedor de Contatos incrementa esse valor automaticamente sempre que a linha ou + os dados relacionados a ela mudam. +
{@link android.provider.ContactsContract.Data}{@link android.provider.ContactsContract.DataColumns#DATA_VERSION}O número da versão dessa linha. + O Provedor de Contatos incrementa esse valor automaticamente sempre que a linha de dados + é modificada. +
{@link android.provider.ContactsContract.RawContacts}{@link android.provider.ContactsContract.SyncColumns#SOURCE_ID} + Valor de string que identifica exclusivamente esse contato bruto para a conta em + que foi criado. + + Quando um adaptador de sincronização cria um novo contato bruto, essa coluna deve ser definida como + o ID exclusivo do servidor para o contato bruto. Quando um aplicativo do Android cria um novo + contato bruto, o aplicativo deve deixar essa coluna vazia. Isso sinaliza ao adaptador + de sincronização que ele deve criar um novo contato bruto no servidor e obter + um valor para o {@link android.provider.ContactsContract.SyncColumns#SOURCE_ID}. +

+ Em particular, o ID de origem deve ser exclusivo de cada tipo + de conta e estável em sincronizações: +

+
    +
  • + Exclusivo: cada contato bruto de uma conta deve ter o próprio ID de origem. Se isso + não for aplicado, haverá problemas no aplicativo de contatos. + Observe que dois contatos brutos do mesmo tipo de conta podem ter + o mesmo ID de origem. Por exemplo: o contato bruto "Thomas Higginson" da + conta {@code emily.dickinson@gmail.com} pode ter o mesmo ID + de origem do contato bruto "Thomas Higginson" da conta + {@code emilyd@gmail.com}. +
  • +
  • + Estável: IDs de origem são uma parte permanente dos dados do serviço on-line para + o contato bruto. Por exemplo: se o usuário apaga o Armazenamento de Contatos + nas configurações dos aplicativos e ressincroniza, os contatos brutos restaurados devem ter os mesmos + IDs de origem de antes. Se isso não for aplicado, os atalhos + deixarão de funcionar. +
  • +
+
{@link android.provider.ContactsContract.Groups}{@link android.provider.ContactsContract.GroupsColumns#GROUP_VISIBLE}"0" - os contatos nesse grupo não devem ser visíveis em IUs do aplicativo do Android. + Essa coluna se destina à compatibilidade com servidores, que permitem que um usuário oculte contatos + em determinados grupos. +
"1" - os contatos nesse grupo podem ser visíveis nas IUs do aplicativo.
{@link android.provider.ContactsContract.Settings} + {@link android.provider.ContactsContract.SettingsColumns#UNGROUPED_VISIBLE} + "0" - para essa conta e esse tipo de conta, os contatos que não pertencem a um grupo são + invisíveis nas IUs do aplicativo do Android. + + Por padrão, os contatos são invisíveis se nenhum dos contatos brutos pertencer a algum grupo + (a associação de grupo de um contato bruto é indicada por uma ou mais + linhas {@link android.provider.ContactsContract.CommonDataKinds.GroupMembership} + na tabela {@link android.provider.ContactsContract.Data}). + Por padrão, é possível fazer com que contatos sem grupos sejam visíveis + por meio desse sinalizador na linha da tabela {@link android.provider.ContactsContract.Settings} para um tipo de conta e uma conta. + Esse sinalizador serve para exibir contatos de servidores que não usam grupos. +
+ "1" - para essa conta e esse tipo de conta, os contatos que não pertencem a um grupo são + visíveis nas IUs do aplicativo. +
{@link android.provider.ContactsContract.SyncState}(todos) + Use essa tabela para armazenar metadados do seu adaptador de sincronização. + + Com essa tabela, é possível armazenar o estado de sincronização e outros dados relacionados à sincronização persistentes + no dispositivo. +
+

Acesso ao Provedor de Contatos

+

+ Esta seção descreve orientações quanto ao acesso a dados do Provedor de Contatos, com foco + no seguinte: +

+
    +
  • + Consultas de entidade. +
  • +
  • + Modificação em lote. +
  • +
  • + Recuperação e modificação com intenções. +
  • +
  • + Integridade dos dados. +
  • +
+

+ A realização de modificações de um adaptador de sincronização também é abordada na seção + Adaptadores de sincronização do Provedor de Contatos. +

+

Consultas de entidades

+

+ A organização hierárquica das tabelas do Provedor de Contatos é muito útil + para recuperar uma linha e todas as linhas "filhas" vinculadas. Por exemplo: para exibir + todas as informações de uma pessoa, você pode querer recuperar todas + as linhas {@link android.provider.ContactsContract.RawContacts} de uma única + linha {@link android.provider.ContactsContract.Contacts} ou todas + as linhas {@link android.provider.ContactsContract.CommonDataKinds.Email} de uma única + linha {@link android.provider.ContactsContract.RawContacts}. Para facilitar isso, o Provedor + de contatos oferece a ideia de entidade, que atua como uma junção dos bancos de dados entre + tabelas. +

+

+ Entidade é como uma tabela composta de colunas selecionadas de uma tabela pai e uma tabela filha. + Ao consultar uma entidade, fornece-se uma projeção e buscam-se critérios com base nas colunas + disponíveis da entidade. O resultado é um {@link android.database.Cursor} que contém + uma linha para cada linha recuperada da tabela filha. Por exemplo: ao consultar + {@link android.provider.ContactsContract.Contacts.Entity} de um nome de contato + e todas as linhas {@link android.provider.ContactsContract.CommonDataKinds.Email} de todos os + contatos brutos por esse nome, você obterá um {@link android.database.Cursor} contendo uma linha + para cada linha {@link android.provider.ContactsContract.CommonDataKinds.Email}. +

+

+ As entidades simplificam as consultas. Usando uma entidade, é possível recuperar todos os dados de contatos + de um contato ou contato bruto de uma vez, sem precisar consultar primeiro a tabela pai para obter + um ID e, em seguida, ter que consultar a tabela filha com esse ID. Além disso, o Provedor de Contatos processa + uma consulta com uma entidade em uma transação única, o que garante que os dados recuperados + sejam consistentes internamente. +

+

+ Observação: uma entidade normalmente não contém todas as colunas da tabela pai e + da tabela filha. Se você tentar trabalhar com um nome de coluna que não esteja na lista de constantes de nomes de coluna + da entidade, será gerada uma {@link java.lang.Exception}. +

+

+ O fragmento a seguir mostra como recuperar todas as linhas de contato bruto de um contato. O fragmento + é parte de um aplicativo maior que tem duas atividades: "principal" e “de detalhes". A atividade principal + exibe uma lista de linhas de contato. Quando um usuário seleciona uma delas, a atividade envia o ID correspondente à atividade + de detalhes. A atividade de detalhes usa {@link android.provider.ContactsContract.Contacts.Entity} + para exibir todas as linhas de dados de todos os contatos brutos associados ao contato + selecionado. +

+

+ Esse fragmento é extraído da atividade "de detalhes": +

+
+...
+    /*
+     * Appends the entity path to the URI. In the case of the Contacts Provider, the
+     * expected URI is content://com.google.contacts/#/entity (# is the ID value).
+     */
+    mContactUri = Uri.withAppendedPath(
+            mContactUri,
+            ContactsContract.Contacts.Entity.CONTENT_DIRECTORY);
+
+    // Initializes the loader identified by LOADER_ID.
+    getLoaderManager().initLoader(
+            LOADER_ID,  // The identifier of the loader to initialize
+            null,       // Arguments for the loader (in this case, none)
+            this);      // The context of the activity
+
+    // Creates a new cursor adapter to attach to the list view
+    mCursorAdapter = new SimpleCursorAdapter(
+            this,                        // the context of the activity
+            R.layout.detail_list_item,   // the view item containing the detail widgets
+            mCursor,                     // the backing cursor
+            mFromColumns,                // the columns in the cursor that provide the data
+            mToViews,                    // the views in the view item that display the data
+            0);                          // flags
+
+    // Sets the ListView's backing adapter.
+    mRawContactList.setAdapter(mCursorAdapter);
+...
+@Override
+public Loader<Cursor> onCreateLoader(int id, Bundle args) {
+
+    /*
+     * Sets the columns to retrieve.
+     * RAW_CONTACT_ID is included to identify the raw contact associated with the data row.
+     * DATA1 contains the first column in the data row (usually the most important one).
+     * MIMETYPE indicates the type of data in the data row.
+     */
+    String[] projection =
+        {
+            ContactsContract.Contacts.Entity.RAW_CONTACT_ID,
+            ContactsContract.Contacts.Entity.DATA1,
+            ContactsContract.Contacts.Entity.MIMETYPE
+        };
+
+    /*
+     * Sorts the retrieved cursor by raw contact id, to keep all data rows for a single raw
+     * contact collated together.
+     */
+    String sortOrder =
+            ContactsContract.Contacts.Entity.RAW_CONTACT_ID +
+            " ASC";
+
+    /*
+     * Returns a new CursorLoader. The arguments are similar to
+     * ContentResolver.query(), except for the Context argument, which supplies the location of
+     * the ContentResolver to use.
+     */
+    return new CursorLoader(
+            getApplicationContext(),  // The activity's context
+            mContactUri,              // The entity content URI for a single contact
+            projection,               // The columns to retrieve
+            null,                     // Retrieve all the raw contacts and their data rows.
+            null,                     //
+            sortOrder);               // Sort by the raw contact ID.
+}
+
+

+ Quando o carregamento é finalizado, {@link android.app.LoaderManager} invoca um retorno de chamada para + {@link android.app.LoaderManager.LoaderCallbacks#onLoadFinished(Loader, D) + onLoadFinished()}. Um dos argumentos de entrada nesse método é um + {@link android.database.Cursor} com os resultados da consulta. No seu aplicativo, você pode obter os + dados desse {@link android.database.Cursor} para exibi-los ou trabalhar com eles posteriormente. +

+

Modificação em lote

+

+ Sempre que possível, deve-se inserir, atualizar e excluir dados no Provedor de Contatos + em "modo de lote", criando um {@link java.util.ArrayList} de + objetos {@link android.content.ContentProviderOperation} e chamando + {@link android.content.ContentResolver#applyBatch(String, ArrayList) applyBatch()}. Como + o Provedor de Contatos realiza todas as operações em um + {@link android.content.ContentResolver#applyBatch(String, ArrayList) applyBatch()} em uma única + transação, as modificações nunca deixarão o repositório de contatos em um estado + inconsistente. As modificações em lote também facilitam a inserção de um contato bruto e seus dados de detalhe + ao mesmo tempo. +

+

+ Observação: para modificar um contato bruto exclusivo, considere enviar uma intenção ao + aplicativo de contatos do dispositivo em vez de realizar a modificação no aplicativo. + Na seção Recuperação e modificação com intenções há mais detalhes + sobre como fazer isso. +

+

Pontos de rendimento

+

+ As modificações em lote que contiverem muitas operações podem bloquear outros processos, + resultando em uma experiência geral ruim para o usuário. Para organizar todas as modificações + a realizar no menor número de listas separadas possível e, ao mesmo tempo, evitar + o bloqueio do sistema, é preciso definir pontos de rendimento para uma ou mais operações. + Pontos de rendimento são objetos {@link android.content.ContentProviderOperation} que têm seu + valor {@link android.content.ContentProviderOperation#isYieldAllowed()} definido como + true. Quando o Provedor de Contatos encontra um ponto de rendimento, ele pausa o trabalho + para permitir a execução de outros processos e encerra a transação em curso. Quando o provedor retorna, ele + continua na próxima operação da {@link java.util.ArrayList} e inicia + uma nova transação. +

+

+ Os pontos de rendimento resultam em mais de uma transação por chamada de + {@link android.content.ContentResolver#applyBatch(String, ArrayList) applyBatch()}. Por isso, + é preciso definir um ponto de rendimento para a última operação de um conjunto de linhas relacionadas. + Por exemplo: é preciso definir um ponto de rendimento para a última operação em um conjunto que adicione + linhas de um contato bruto e linhas de dados associados a ele, ou para a última operação de um conjunto de linhas relacionadas + a um único contato. +

+

+ Os pontos de rendimento também são uma unidade de operação atômica. Todos os acessos entre dois pontos de rendimento + terão sucesso ou fracasso como uma unidade. Se não houver pontos de rendimento definidos, a menor + operação atômica será todo o lote de operações. Se forem usados pontos de rendimento, eles evitarão + que as operações prejudiquem o desempenho do sistema e, ao mesmo tempo, garantirá que o subconjunto + de operações seja atômico. +

+

Referências de retorno da modificação

+

+ Ao inserir uma nova linha de contato bruto e as linhas de dados associados como um conjunto + de objetos {@link android.content.ContentProviderOperation}, é preciso vincular as linhas de dados + à linha de contato bruto pela inserção do valor + {@code android.provider.BaseColumns#_ID} do contato bruto como + o valor {@link android.provider.ContactsContract.DataColumns#RAW_CONTACT_ID}. Contudo, esse + valor não está disponível ao criar a {@link android.content.ContentProviderOperation} + para a linha de dados porque + {@link android.content.ContentProviderOperation} ainda não foi aplicada à linha de contato bruto. Para trabalhar com isso, + a classe {@link android.content.ContentProviderOperation.Builder} tem o método + {@link android.content.ContentProviderOperation.Builder#withValueBackReference(String, int) withValueBackReference()}. + Esse método permite a inserção ou modificação de uma coluna com + o resultado de uma operação anterior. +

+

+ O método {@link android.content.ContentProviderOperation.Builder#withValueBackReference(String, int) withValueBackReference()} + tem dois argumentos: +

+
+
+ key +
+
+ O principal de um par de valores principais. O valor desse argumento deve ser o nome de uma coluna + na tabela que será modificada. +
+
+ previousResult +
+
+ O índice com base 0 de um valor em uma matriz + de objetos {@link android.content.ContentProviderResult} +de {@link android.content.ContentResolver#applyBatch(String, ArrayList) applyBatch()}. Quando + as operações em lote são aplicadas, o resultado de cada operação é armazenado + em uma matriz intermediária de resultados. O valor previousResult é o índice + de um desses resultados, que é recuperado e armazenado com o valor + key. Isso permite a inserção de um novo registro de contato bruto e a recuperação do seu + valor {@code android.provider.BaseColumns#_ID}, seguido de uma "referência de retorno" ao + valor ao adicionar uma linha {@link android.provider.ContactsContract.Data}. +

+ Toda a matriz de resultados é criada ao chamar + {@link android.content.ContentResolver#applyBatch(String, ArrayList) applyBatch()} pela primeira vez, + com tamanho igual ao da {@link java.util.ArrayList} + de objetos {@link android.content.ContentProviderOperation} fornecidos. No entanto, todos + os elementos na matriz de resultados são definidos como null e, se houver uma tentativa + de referência de retorno a um resultado de uma operação ainda não realizada, +{@link android.content.ContentProviderOperation.Builder#withValueBackReference(String, int) withValueBackReference()} + gera uma {@link java.lang.Exception}. + +

+
+
+

+ Os fragmentos a seguir mostram como inserir um novo contato bruto e dados em lote. Eles + contém o código que estabelece um ponto de rendimento e usam uma referência de retorno. Os fragmentos são + uma versão expandida do método createContacEntry(), que é parte + da classe ContactAdder + no aplicativo de exemplo de + Contact Manager. +

+

+ O primeiro fragmento recupera dados de contato da IU. Nesse momento, o usuário já + selecionou a conta na qual o novo contato bruto deve ser adicionado. +

+
+// Creates a contact entry from the current UI values, using the currently-selected account.
+protected void createContactEntry() {
+    /*
+     * Gets values from the UI
+     */
+    String name = mContactNameEditText.getText().toString();
+    String phone = mContactPhoneEditText.getText().toString();
+    String email = mContactEmailEditText.getText().toString();
+
+    int phoneType = mContactPhoneTypes.get(
+            mContactPhoneTypeSpinner.getSelectedItemPosition());
+
+    int emailType = mContactEmailTypes.get(
+            mContactEmailTypeSpinner.getSelectedItemPosition());
+
+

+ O próximo fragmento cria uma operação para inserir a linha de contato bruto + na tabela {@link android.provider.ContactsContract.RawContacts}: +

+
+    /*
+     * Prepares the batch operation for inserting a new raw contact and its data. Even if
+     * the Contacts Provider does not have any data for this person, you can't add a Contact,
+     * only a raw contact. The Contacts Provider will then add a Contact automatically.
+     */
+
+     // Creates a new array of ContentProviderOperation objects.
+    ArrayList<ContentProviderOperation> ops =
+            new ArrayList<ContentProviderOperation>();
+
+    /*
+     * Creates a new raw contact with its account type (server type) and account name
+     * (user's account). Remember that the display name is not stored in this row, but in a
+     * StructuredName data row. No other data is required.
+     */
+    ContentProviderOperation.Builder op =
+            ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI)
+            .withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, mSelectedAccount.getType())
+            .withValue(ContactsContract.RawContacts.ACCOUNT_NAME, mSelectedAccount.getName());
+
+    // Builds the operation and adds it to the array of operations
+    ops.add(op.build());
+
+

+ Em seguida, o código cria linhas de dados para as linhas de nome de exibição, telefone e e-mail. +

+

+ Cada objeto construtor de operações usa + {@link android.content.ContentProviderOperation.Builder#withValueBackReference(String, int) withValueBackReference()} + para obter + o {@link android.provider.ContactsContract.DataColumns#RAW_CONTACT_ID}. Os pontos de referência + voltam ao objeto {@link android.content.ContentProviderResult} a partir da primeira operação, + que adiciona a linha de contato bruto e retorna seu novo valor + {@code android.provider.BaseColumns#_ID}. Como resultado, cada linha de dados é automaticamente vinculada por meio do + {@link android.provider.ContactsContract.DataColumns#RAW_CONTACT_ID} + à nova linha {@link android.provider.ContactsContract.RawContacts} à qual ela pertence. +

+

+ O objeto {@link android.content.ContentProviderOperation.Builder} que adiciona a linha de e-mail é + sinalizado com {@link android.content.ContentProviderOperation.Builder#withYieldAllowed(boolean) + withYieldAllowed()}, que define um ponto de rendimento: +

+
+    // Creates the display name for the new raw contact, as a StructuredName data row.
+    op =
+            ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
+            /*
+             * withValueBackReference sets the value of the first argument to the value of
+             * the ContentProviderResult indexed by the second argument. In this particular
+             * call, the raw contact ID column of the StructuredName data row is set to the
+             * value of the result returned by the first operation, which is the one that
+             * actually adds the raw contact row.
+             */
+            .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
+
+            // Sets the data row's MIME type to StructuredName
+            .withValue(ContactsContract.Data.MIMETYPE,
+                    ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE)
+
+            // Sets the data row's display name to the name in the UI.
+            .withValue(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, name);
+
+    // Builds the operation and adds it to the array of operations
+    ops.add(op.build());
+
+    // Inserts the specified phone number and type as a Phone data row
+    op =
+            ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
+            /*
+             * Sets the value of the raw contact id column to the new raw contact ID returned
+             * by the first operation in the batch.
+             */
+            .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
+
+            // Sets the data row's MIME type to Phone
+            .withValue(ContactsContract.Data.MIMETYPE,
+                    ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE)
+
+            // Sets the phone number and type
+            .withValue(ContactsContract.CommonDataKinds.Phone.NUMBER, phone)
+            .withValue(ContactsContract.CommonDataKinds.Phone.TYPE, phoneType);
+
+    // Builds the operation and adds it to the array of operations
+    ops.add(op.build());
+
+    // Inserts the specified email and type as a Phone data row
+    op =
+            ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
+            /*
+             * Sets the value of the raw contact id column to the new raw contact ID returned
+             * by the first operation in the batch.
+             */
+            .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
+
+            // Sets the data row's MIME type to Email
+            .withValue(ContactsContract.Data.MIMETYPE,
+                    ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE)
+
+            // Sets the email address and type
+            .withValue(ContactsContract.CommonDataKinds.Email.ADDRESS, email)
+            .withValue(ContactsContract.CommonDataKinds.Email.TYPE, emailType);
+
+    /*
+     * Demonstrates a yield point. At the end of this insert, the batch operation's thread
+     * will yield priority to other threads. Use after every set of operations that affect a
+     * single contact, to avoid degrading performance.
+     */
+    op.withYieldAllowed(true);
+
+    // Builds the operation and adds it to the array of operations
+    ops.add(op.build());
+
+

+ O último fragmento exibe a chamada + a {@link android.content.ContentResolver#applyBatch(String, ArrayList) applyBatch()} que + insere as novas linhas de contato bruto e de dados. +

+
+    // Ask the Contacts Provider to create a new contact
+    Log.d(TAG,"Selected account: " + mSelectedAccount.getName() + " (" +
+            mSelectedAccount.getType() + ")");
+    Log.d(TAG,"Creating contact: " + name);
+
+    /*
+     * Applies the array of ContentProviderOperation objects in batch. The results are
+     * discarded.
+     */
+    try {
+
+            getContentResolver().applyBatch(ContactsContract.AUTHORITY, ops);
+    } catch (Exception e) {
+
+            // Display a warning
+            Context ctx = getApplicationContext();
+
+            CharSequence txt = getString(R.string.contactCreationFailure);
+            int duration = Toast.LENGTH_SHORT;
+            Toast toast = Toast.makeText(ctx, txt, duration);
+            toast.show();
+
+            // Log exception
+            Log.e(TAG, "Exception encountered while inserting contact: " + e);
+    }
+}
+
+

+ As operações de lote também permitem a implementação de controle otimista de simultaneidade, + um método de aplicar transações de modificação sem precisar bloquear o repositório subjacente. + Para usar esse método, aplique a transação e, em seguida, verifique se há outras modificações que + possam ter sido realizadas ao mesmo tempo. Se ficar determinado que houve uma modificação incoerente, + reverte-se a transação e tenta-se novamente. +

+

+ O controle otimista de simultaneidade é útil para dispositivos móveis em que haja somente um usuário + de uma vez e que sejam raros os acessos simultâneos a um repositório de dados. Como os bloqueios não são usados, + não há tempo gasto em bloqueios de configuração nem espera para que outras transações liberem os respectivos bloqueios. +

+

+ Para usar o controle otimista de simultaneidade ao atualizar uma única + linha {@link android.provider.ContactsContract.RawContacts}, siga estas etapas: +

+
    +
  1. + Recupere a coluna {@link android.provider.ContactsContract.SyncColumns#VERSION} + do contato bruto em conjunto com os outros dados recuperados. +
  2. +
  3. + Crie um objeto {@link android.content.ContentProviderOperation.Builder} adequado para + forçar uma restrição com o método + {@link android.content.ContentProviderOperation#newAssertQuery(Uri)}. Para a URI de conteúdo, + use {@link android.provider.ContactsContract.RawContacts#CONTENT_URI + RawContacts.CONTENT_URI} + com o {@code android.provider.BaseColumns#_ID} do contato bruto anexado a ela. +
  4. +
  5. + Para o objeto {@link android.content.ContentProviderOperation.Builder}, chame + {@link android.content.ContentProviderOperation.Builder#withValue(String, Object) + withValue()} para comparar a coluna + {@link android.provider.ContactsContract.SyncColumns#VERSION} com o número da versão recém-recuperado. +
  6. +
  7. + Para o mesmo {@link android.content.ContentProviderOperation.Builder}, chame + {@link android.content.ContentProviderOperation.Builder#withExpectedCount(int) + withExpectedCount()} para garantir que somente uma linha seja testada por essa instrução. +
  8. +
  9. + Chame {@link android.content.ContentProviderOperation.Builder#build()} para criar + o objeto {@link android.content.ContentProviderOperation}; em seguida, adicione esse objeto como + o primeiro objeto na {@link java.util.ArrayList} passada para + {@link android.content.ContentResolver#applyBatch(String, ArrayList) applyBatch()}. +
  10. +
  11. + Aplique a transação em lote. +
  12. +
+

+ Se a linha de contato bruto for atualizada por outra operação entre o momento da leitura da linha + e o momento de sua modificação, a "assert" {@link android.content.ContentProviderOperation} + falhará e todo o lote de operações será cancelado. Depois disso, as opções são tentar novamente + ou tomar outra medida. +

+

+ O fragmento a seguir demonstra como criar uma "assert" + {@link android.content.ContentProviderOperation} após consultar um contato bruto exclusivo usando + um {@link android.content.CursorLoader}: +

+
+/*
+ * The application uses CursorLoader to query the raw contacts table. The system calls this method
+ * when the load is finished.
+ */
+public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
+
+    // Gets the raw contact's _ID and VERSION values
+    mRawContactID = cursor.getLong(cursor.getColumnIndex(BaseColumns._ID));
+    mVersion = cursor.getInt(cursor.getColumnIndex(SyncColumns.VERSION));
+}
+
+...
+
+// Sets up a Uri for the assert operation
+Uri rawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, mRawContactID);
+
+// Creates a builder for the assert operation
+ContentProviderOperation.Builder assertOp = ContentProviderOperation.netAssertQuery(rawContactUri);
+
+// Adds the assertions to the assert operation: checks the version and count of rows tested
+assertOp.withValue(SyncColumns.VERSION, mVersion);
+assertOp.withExpectedCount(1);
+
+// Creates an ArrayList to hold the ContentProviderOperation objects
+ArrayList ops = new ArrayList<ContentProviderOperationg>;
+
+ops.add(assertOp.build());
+
+// You would add the rest of your batch operations to "ops" here
+
+...
+
+// Applies the batch. If the assert fails, an Exception is thrown
+try
+    {
+        ContentProviderResult[] results =
+                getContentResolver().applyBatch(AUTHORITY, ops);
+
+    } catch (OperationApplicationException e) {
+
+        // Actions you want to take if the assert operation fails go here
+    }
+
+

Recuperação e modificação com intenções

+

+ Enviar uma intenção ao aplicativo de contatos do dispositivo permite o acesso indireto ao Provedor + de contatos. A intenção inicia a IU do aplicativo de contatos do dispositivo, na qual os usuários podem + fazer tarefas relacionadas a contatos. Com esse tipo de acesso, os usuários podem: +

    +
  • Selecionar um contato de uma lista e retorná-lo ao aplicativo para trabalhos futuros.
  • +
  • Editar dados de um contato existente.
  • +
  • Inserir um novo contato bruto para quaisquer das suas contas.
  • +
  • Excluir um contato ou dados dos contatos.
  • +
+

+ Se o usuário estiver inserindo ou atualizando dados, é possível coletar os dados antes e enviá-los como + parte da intenção. +

+

+ Ao usar intenções para acessar o Provedor de Contatos por meio do aplicativo de contatos do dispositivo, + não é necessário criar a própria IU ou código para acessar o provedor. Também não é necessário + solicitar permissão de leitura e gravação ao provedor. O aplicativo de contatos do dispositivo pode + delegar a você permissões de leitura de um contato e, pelo fato de você fazer modificações + no provedor por meio de outro aplicativo, não é necessário ter permissões de gravação. +

+

+ O processo geral de envio de uma intenção para acessar um provedor é descrito detalhadamente + no guia +Preceitos do provedor de conteúdo, seção "Acesso a dados via intenções". Os valores + de ação, do tipo MIME e dos dados usados para as tarefas disponíveis são resumidos na tabela 4 + e os valores extras que podem ser usados com + {@link android.content.Intent#putExtra(String, String) putExtra()} são listados na + documentação de referência de {@link android.provider.ContactsContract.Intents.Insert}: +

+

+ Tabela 4. Intenções do Provedor de Contatos +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TarefaAçãoDadosTipo MIMEObservações
Selecionar um contato de uma lista{@link android.content.Intent#ACTION_PICK} + Um dos seguintes: +
    +
  • +{@link android.provider.ContactsContract.Contacts#CONTENT_URI Contacts.CONTENT_URI}, + que exibe uma lista de contatos. +
  • +
  • +{@link android.provider.ContactsContract.CommonDataKinds.Phone#CONTENT_URI Phone.CONTENT_URI}, + que exibe uma lista de números de telefone de um contato bruto. +
  • +
  • +{@link android.provider.ContactsContract.CommonDataKinds.StructuredPostal#CONTENT_URI +StructuredPostal.CONTENT_URI}, + que exibe uma lista de endereços postais de um contato bruto. +
  • +
  • +{@link android.provider.ContactsContract.CommonDataKinds.Email#CONTENT_URI Email.CONTENT_URI}, + que exibe uma lista de endereços de e-mail de um contato bruto. +
  • +
+
+ Não usado + + Exibe uma lista de contatos brutos ou uma lista de dados de um contato bruto conforme + o tipo da URI de conteúdo fornecido. +

+ Chame + {@link android.app.Activity#startActivityForResult(Intent, int) startActivityForResult()}, + que retorna a URI de conteúdo da linha selecionada. O formulário da URI é a + URI de conteúdo da tabela com o LOOKUP_ID da linha anexado a ela. + O aplicativo de contatos do dispositivo delega permissões de leitura e gravação a essa URI de conteúdo + pelo tempo que a atividade durar. Consulte + o guia + Preceitos do provedor de conteúdo para obter mais detalhes. +

+
Inserir um novo contato bruto{@link android.provider.ContactsContract.Intents.Insert#ACTION Insert.ACTION}Não aplicável + {@link android.provider.ContactsContract.RawContacts#CONTENT_TYPE + RawContacts.CONTENT_TYPE}, tipo MIME para um grupo de contatos brutos. + + Exibe a tela Adicionar contato do aplicativo de contatos do dispositivo. + São exibidos os valores extras adicionados à intenção. Se enviada com + {@link android.app.Activity#startActivityForResult(Intent, int) startActivityForResult()}, + a URI de conteúdo do contato bruto recentemente adicionado é passada de volta + ao método de retorno de chamada + {@link android.app.Activity#onActivityResult(int, int, Intent) onActivityResult()}no argumento {@link android.content.Intent}, +no campo "dados". Para obter o valor, chame {@link android.content.Intent#getData()}. +
Editar um contato{@link android.content.Intent#ACTION_EDIT} + {@link android.provider.ContactsContract.Contacts#CONTENT_LOOKUP_URI} + do contato. A atividade do editor permitirá que o usuário edite os dados associados + a esse contato. + + {@link android.provider.ContactsContract.Contacts#CONTENT_ITEM_TYPE + Contacts.CONTENT_ITEM_TYPE}, um único contato. + Exibe a tela Editar contato no aplicativo de contatos. São exibidos os valores extras + adicionados à intenção. Quando o usuário clica em Concluído para salvar + as edições, a atividade retorna ao primeiro plano. +
Exibir um seletor que também pode adicionar dados.{@link android.content.Intent#ACTION_INSERT_OR_EDIT} + Não aplicável + + {@link android.provider.ContactsContract.Contacts#CONTENT_ITEM_TYPE} + + Essa intenção sempre exibe a tela do seletor do aplicativo de contatos. O usuário pode + selecionar um contato para editar ou adicionar um novo. É exibida a tela de edição ou de adição, + conforme a escolha do usuário e são exibidos os dados extras passados + para a intenção. Se o aplicativo exibe dados de contato como um e-mail ou número de telefone, use + essa intenção para permitir que o usuário adicione os dados a um contato existente. + +

+ Observação: Não é necessário enviar nenhum valor de nome dos extras da intenção + porque o usuário sempre seleciona um nome existente ou adiciona um novo. Além disso, + se você enviar um nome e o usuário escolher editar, o aplicativo de contatos + exibirá o nome enviado, substituindo o valor anterior. Se o usuário + não notar isso e salvar a edição, o valor antigo será perdido. +

+
+

+ O aplicativo de contatos do dispositivo não permite a exclusão de um contato bruto ou de seus dados + com uma intenção. Em vez disso, para excluir um contato bruto, use + {@link android.content.ContentResolver#delete(Uri, String, String[]) ContentResolver.delete()} + ou {@link android.content.ContentProviderOperation#newDelete(Uri) + ContentProviderOperation.newDelete()}. +

+

+ O fragmento a seguir mostra como construir e enviar uma intenção que insira um novo + contato bruto e dados: +

+
+// Gets values from the UI
+String name = mContactNameEditText.getText().toString();
+String phone = mContactPhoneEditText.getText().toString();
+String email = mContactEmailEditText.getText().toString();
+
+String company = mCompanyName.getText().toString();
+String jobtitle = mJobTitle.getText().toString();
+
+// Creates a new intent for sending to the device's contacts application
+Intent insertIntent = new Intent(ContactsContract.Intents.Insert.ACTION);
+
+// Sets the MIME type to the one expected by the insertion activity
+insertIntent.setType(ContactsContract.RawContacts.CONTENT_TYPE);
+
+// Sets the new contact name
+insertIntent.putExtra(ContactsContract.Intents.Insert.NAME, name);
+
+// Sets the new company and job title
+insertIntent.putExtra(ContactsContract.Intents.Insert.COMPANY, company);
+insertIntent.putExtra(ContactsContract.Intents.Insert.JOB_TITLE, jobtitle);
+
+/*
+ * Demonstrates adding data rows as an array list associated with the DATA key
+ */
+
+// Defines an array list to contain the ContentValues objects for each row
+ArrayList<ContentValues> contactData = new ArrayList<ContentValues>();
+
+
+/*
+ * Defines the raw contact row
+ */
+
+// Sets up the row as a ContentValues object
+ContentValues rawContactRow = new ContentValues();
+
+// Adds the account type and name to the row
+rawContactRow.put(ContactsContract.RawContacts.ACCOUNT_TYPE, mSelectedAccount.getType());
+rawContactRow.put(ContactsContract.RawContacts.ACCOUNT_NAME, mSelectedAccount.getName());
+
+// Adds the row to the array
+contactData.add(rawContactRow);
+
+/*
+ * Sets up the phone number data row
+ */
+
+// Sets up the row as a ContentValues object
+ContentValues phoneRow = new ContentValues();
+
+// Specifies the MIME type for this data row (all data rows must be marked by their type)
+phoneRow.put(
+        ContactsContract.Data.MIMETYPE,
+        ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE
+);
+
+// Adds the phone number and its type to the row
+phoneRow.put(ContactsContract.CommonDataKinds.Phone.NUMBER, phone);
+
+// Adds the row to the array
+contactData.add(phoneRow);
+
+/*
+ * Sets up the email data row
+ */
+
+// Sets up the row as a ContentValues object
+ContentValues emailRow = new ContentValues();
+
+// Specifies the MIME type for this data row (all data rows must be marked by their type)
+emailRow.put(
+        ContactsContract.Data.MIMETYPE,
+        ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE
+);
+
+// Adds the email address and its type to the row
+emailRow.put(ContactsContract.CommonDataKinds.Email.ADDRESS, email);
+
+// Adds the row to the array
+contactData.add(emailRow);
+
+/*
+ * Adds the array to the intent's extras. It must be a parcelable object in order to
+ * travel between processes. The device's contacts app expects its key to be
+ * Intents.Insert.DATA
+ */
+insertIntent.putParcelableArrayListExtra(ContactsContract.Intents.Insert.DATA, contactData);
+
+// Send out the intent to start the device's contacts app in its add contact activity.
+startActivity(insertIntent);
+
+

Integridade dos dados

+

+ Como o repositório de contatos contém dados importantes e confidenciais que usuários esperam que estejam + corretos e atualizados, o Provedor de Contatos tem regras bem definidas para a integridade dos dados. É de + sua responsabilidade adequar-se a essas regras ao modificar dados de contatos. As regras + importantes são listadas a seguir: +

+
+
+ Sempre adicione uma linha {@link android.provider.ContactsContract.CommonDataKinds.StructuredName} + para cada linha {@link android.provider.ContactsContract.RawContacts} adicionada. +
+
+ Uma linha {@link android.provider.ContactsContract.RawContacts} sem uma + linha {@link android.provider.ContactsContract.CommonDataKinds.StructuredName} + na tabela {@link android.provider.ContactsContract.Data} pode causar problemas durante + a agregação. +
+
+ Sempre conecte novas linhas {@link android.provider.ContactsContract.Data} às respectivas + linhas pai {@link android.provider.ContactsContract.RawContacts}. +
+
+ Uma linha {@link android.provider.ContactsContract.Data} que não esteja conectada a um + {@link android.provider.ContactsContract.RawContacts} não será visível no aplicativo + de contatos do dispositivo, o que pode causar problemas com adaptadores de sincronização. +
+
+ Altere dados somente para os seus contatos brutos. +
+
+ Lembre-se de que o Provedor de Contatos normalmente gerencia dados de diversos tipos de conta + e serviços on-line diferentes. É preciso garantir que o aplicativo modifique + ou exclua somente dados de linhas que pertencem a você e que ele insira dados apenas + com um tipo e nome de conta que você controla. +
+
+ Sempre use as constantes definidas em {@link android.provider.ContactsContract} e suas + subclasses de autoridades, URIs de conteúdo, caminhos de URI, nomes de coluna, tipos MIME e + valores {@link android.provider.ContactsContract.CommonDataKinds.CommonColumns#TYPE}. +
+
+ O uso dessas constantes ajuda a evitar erros. Você também será notificado com avisos + do compilador se uma das constantes não for aprovada. +
+
+

Linhas de dados personalizados

+

+ Ao criar e usar seus próprios tipos MIME personalizados, você pode inserir, editar, excluir e recuperar + suas linhas de dados na tabela {@link android.provider.ContactsContract.Data}. As linhas + são limitadas a usar a coluna definida em + {@link android.provider.ContactsContract.DataColumns}, embora seja possível mapear + os nomes de coluna de tipo específico para os nomes de coluna padrão. No aplicativo de contatos do dispositivo, + os dados das linhas são exibidos, mas não podem ser editados nem excluídos e os usuários não podem inserir + dados adicionais. Para permitir que usuários modifiquem suas linhas de dados personalizados, é necessário fornecer + uma atividade do editor no próprio aplicativo. +

+

+ Para exibir os dados personalizados, forneça um arquivo contacts.xml contendo + um elemento <ContactsAccountType> ou um ou mais + dos elementos filho <ContactsDataKind>. Isso é abordado com mais detalhes na + seção <ContactsDataKind> element. +

+

+ Para saber mais sobre tipos MIME personalizados, leia o + guia + Criação de um provedor de conteúdo. +

+

Adaptadores de sincronização do Provedor de Contatos

+

+ O Provedor de Contatos foi projetado especificamente para tratar a sincronização + de dados de contatos entre um dispositivo e um serviço on-line. Isso permite que usuários baixem + dados existentes em um novo dispositivo e transfiram dados existentes a uma nova conta. + A sincronização também garante que usuários tenham os dados mais recentes à mão, independentemente + da origem de adições e alterações. Outra vantagem da sincronização é + a disponibilidade dos dados dos contatos mesmo quando o dispositivo não está conectado à rede. +

+

+ Embora seja possível implementar a sincronização de diversos modos, o sistema Android fornece + uma estrutura de sincronização de extensão que automatiza as seguintes tarefas: +

    + +
  • + Verificação da disponibilidade da rede. +
  • +
  • + Agendamento e execução de sincronizações, com base nas preferências do usuário. +
  • +
  • + Reinicialização de sincronizações que foram interrompidas. +
  • +
+

+ Para usar essa estrutura, deve-se fornecer uma extensão do adaptador de sincronização. Cada adaptador de sincronização é exclusivo + de um serviço e um provedor de conteúdo, mas pode tratar diversos nomes de conta do mesmo serviço. + A estrutura também permite diversos adaptadores de sincronização para o mesmo serviço e provedor. +

+

Classes e arquivos do adaptador de sincronização

+

+ O adaptador de sincronização é implementado como uma subclasse + de {@link android.content.AbstractThreadedSyncAdapter} e instalado como parte do aplicativo + do Android. O sistema coleta dados do adaptador de sincronização a partir de elementos no manifesto + do aplicativo e de um arquivo XML especial direcionado pelo manifesto. O arquivo XML define + o tipo de conta do serviço on-line e a autoridade do provedor de conteúdo que, juntos, + identificam exclusivamente o adaptador. O adaptador de sincronização não fica ativo até que o usuário adicione + uma conta para o tipo de conta do adaptador de sincronização e habilite a sincronização para + o provedor de conteúdo com o qual o adaptador se sincroniza. Nesse ponto, o sistema começa a gerenciar o adaptador, + chamando-o quando necessário para estabelecer a sincronização entre o provedor de conteúdo e o servidor. +

+

+ Observação: usar um tipo de conta como parte da identificação do adaptador de sincronização permite + que o sistema detecte e agrupe adaptadores de sincronização que acessam diferentes serviços + da mesma organização. Por exemplo, todos os adaptadores de sincronização dos serviços on-line da Google têm o mesmo + tipo de conta com.google. Quando o usuário adiciona uma conta Google aos dispositivos, todos + os adaptadores de sincronização dos serviços Google instalados são listados juntos; cada um + dos listados sincroniza-se com um provedor de conteúdo diferente no dispositivo. +

+

+ Como a maioria dos serviços exige que usuários verifiquem a identidade antes de acessar + os dados, o sistema Android oferece uma estrutura de autenticação similar à estrutura + dos adaptadores de sincronização e muitas vezes usada junto com eles. A estrutura de autenticação usa + autenticadores de extensão que são subclasses + de {@link android.accounts.AbstractAccountAuthenticator}. Um autenticador verifica + a identidade do usuário nas seguintes etapas: +

    +
  1. + Coleta nome, senha ou informação similar do usuário (as credenciais + do usuário). +
  2. +
  3. + Envia as credenciais para o serviço +
  4. +
  5. + Avalia a resposta do serviço. +
  6. +
+

+ Se o serviço aceitar as credenciais, o autenticador pode + armazená-las para uso futuro. Devido à estrutura do autenticador de extensão, + o {@link android.accounts.AccountManager} pode conferir acesso a quaisquer tokens de autenticação compatíveis com + um autenticador que escolha expô-los, como os tokens de autenticação OAuth2. +

+

+ Embora as autenticações não sejam necessárias, a maioria dos serviços de contato as usam. + Contudo, não é obrigatório usar a estrutura de autenticação do Android para efetuá-las. +

+

Implementação do adaptador de sincronização

+

+ Para implementar um adaptador de sincronização para o Provedor de Contatos, primeiramente é necessário criar + um aplicativo do Android que contenha o seguinte: +

+
+
+ Um componente {@link android.app.Service} que responda a solicitações do sistema para + vincular ao adaptador de sincronização. +
+
+ Quando o sistema quer executar uma sincronização, ele chama o método + {@link android.app.Service#onBind(Intent) onBind()} do serviço para obter + um {@link android.os.IBinder} para o adaptador de sincronização. Isso permite que o sistema efetue chamadas + entre processos aos métodos do adaptador. +

+ No aplicativo + Exemplo de adaptador de sincronização, o nome de classe desse serviço é + com.example.android.samplesync.syncadapter.SyncService. +

+
+
+ O adaptador de sincronização atual, implementado como uma subclasse concreta de + {@link android.content.AbstractThreadedSyncAdapter}. +
+
+ Essa classe realiza o trabalho de baixar dados do servidor, atualizar dados + do dispositivo e resolver conflitos. A principal tarefa do adaptador é + feita no método {@link android.content.AbstractThreadedSyncAdapter#onPerformSync( + Account, Bundle, String, ContentProviderClient, SyncResult) + onPerformSync()}. Essa classe deve ser instanciada como um singleton. +

+ No aplicativo + Exemplo de adaptador de sincronização, o adaptador de sincronização é definido na classe + com.example.android.samplesync.syncadapter.SyncAdapter. +

+
+
+ Uma subclasse de {@link android.app.Application}. +
+
+ Essa classe atua como uma fábrica para o singleton do adaptador de sincronização. Use + o método {@link android.app.Application#onCreate()} para instanciar o adaptador de sincronização + e fornecer um método "coletor" estático para retornar o singleton ao + método {@link android.app.Service#onBind(Intent) onBind()} do serviço do adaptador + de sincronização. +
+
+ Opcional: um componente {@link android.app.Service} que responda a + solicitações do sistema para autenticação do usuário. +
+
+ O {@link android.accounts.AccountManager} ativa esse serviço para iniciar o processo + de autenticação. O método {@link android.app.Service#onCreate()} do serviço instancia + um objeto autenticador. Quando o sistema quer autenticar uma conta de usuário para + o adaptador de sincronização do aplicativo, ele chama o método + {@link android.app.Service#onBind(Intent) onBind()} do serviço para obter um + {@link android.os.IBinder} para o autenticador. Isso permite que o sistema efetue chamadas + entre processos aos métodos do autenticador. +

+ No aplicativo + Exemplo de adaptador de sincronização, o nome de classe desse serviço é + com.example.android.samplesync.authenticator.AuthenticationService. +

+
+
+ Opcional: uma subclasse concreta de + {@link android.accounts.AbstractAccountAuthenticator} que trate de solicitações de + autenticação. +
+
+ Essa classe fornece métodos que chamam {@link android.accounts.AccountManager} + para autenticar as credenciais do usuário no servidor. Os detalhes + desse processo de autenticação variam amplamente, com base na tecnologia em uso no servidor. Consulte + a documentação do software do servidor para ver mais informações sobre autenticação. +

+ No aplicativo + Exemplo de adaptador de sincronização, o autenticador é definido na classe + com.example.android.samplesync.authenticator.Authenticator. +

+
+
+ Os arquivos XML que definem o adaptador de sincronização e o autenticador para o sistema. +
+
+ Os componentes de serviço do adaptador de sincronização e do autenticador descritos anteriormente são + definidos em +elementos <service> + no manifesto do aplicativo. Esses elementos + contêm +elementos filho <meta-data> +que fornecem dados específicos ao + sistema: +
    +
  • + O +elemento <meta-data> + de serviço do adaptador de sincronização direciona-se + ao res/xml/syncadapter.xml do arquivo XML. Em troca, esse arquivo especifica + uma URI para o serviço web que será sincronizado com o Provedor de Contatos + e um tipo de conta. +
  • +
  • + Opcional: o +elemento <meta-data> + do autenticador aponta para o arquivo XML + res/xml/authenticator.xml. Em troca, esse arquivo especifica + o tipo de conta compatível com esse autenticador, bem como recursos de IU + que aparecem durante o processo de autenticação. O tipo de conta especificado + nesse elemento deve ser o mesmo que o especificado para + o adaptador de sincronização. +
  • +
+
+
+

Dados de fluxos sociais

+

+ As tabelas {@code android.provider.ContactsContract.StreamItems} + e {@code android.provider.ContactsContract.StreamItemPhotos} + gerenciam dados advindos de redes sociais. É possível criar um adaptador de sincronização que adicione dados de fluxo + da sua própria rede a essas tabelas ou ler dados de fluxo dessas tabelas + e exibi-los no seu aplicativo, ou ambos. Com esses recursos, os serviços e aplicativos + de redes sociais podem ser integrados na experiência das redes sociais no Android. +

+

Textos de fluxos sociais

+

+ Itens de fluxo sempre são associados a um contato bruto. + O {@code android.provider.ContactsContract.StreamItemsColumns#RAW_CONTACT_ID} conecta-se ao + valor _ID do contato bruto. O tipo e o nome da conta do contato + bruto também são armazenados na linha do item de fluxo. +

+

+ Armazene os dados do seu fluxo nas colunas a seguir: +

+
+
+ {@code android.provider.ContactsContract.StreamItemsColumns#ACCOUNT_TYPE} +
+
+ Obrigatório. O tipo de conta do usuário do contato bruto associado a esse + item de fluxo. Lembre-se de definir esse valor ao inserir um item de fluxo. +
+
+ {@code android.provider.ContactsContract.StreamItemsColumns#ACCOUNT_NAME} +
+
+ Obrigatório. O nome de conta do usuário do contato bruto associado + a esse item de fluxo. Lembre-se de definir esse valor ao inserir um item de fluxo. +
+
+ Colunas identificadoras +
+
+ Obrigatório. É necessário inserir as colunas identificadoras a seguir + ao inserir um item de fluxo: +
    +
  • + {@code android.provider.ContactsContract.StreamItemsColumns#CONTACT_ID}: + o valor {@code android.provider.BaseColumns#_ID} do contato ao qual esse item + de fluxo está associado. +
  • +
  • + {@code android.provider.ContactsContract.StreamItemsColumns#CONTACT_LOOKUP_KEY}: + o valor{@code android.provider.ContactsContract.ContactsColumns#LOOKUP_KEY} do + contato ao qual esse item de fluxo está associado. +
  • +
  • + {@code android.provider.ContactsContract.StreamItemsColumns#RAW_CONTACT_ID}: + o valor {@code android.provider.BaseColumns#_ID} do contato bruto ao qual esse item + de fluxo está associado. +
  • +
+
+
+ {@code android.provider.ContactsContract.StreamItemsColumns#COMMENTS} +
+
+ Opcional. Armazena informações resumidas que podem ser exibidas no início do item de fluxo. +
+
+ {@code android.provider.ContactsContract.StreamItemsColumns#TEXT} +
+
+ O texto do item de fluxo, o conteúdo que foi publicado pela fonte do item + ou uma descrição de alguma ação que gerou o item de fluxo. Essa coluna pode conter + imagens de recurso incorporadas e de qualquer formato que possam ser renderizadas + {@link android.text.Html#fromHtml(String) fromHtml()}. O provedor pode truncar + ou abreviar conteúdos longos, mas ele tentará evitar quebrar as tags. +
+
+ {@code android.provider.ContactsContract.StreamItemsColumns#TIMESTAMP} +
+
+ Uma string de texto contendo o tempo em que o item de fluxo foi inserido ou atualizado, em forma + de milissegundos desde a época. Aplicativos que inserem ou atualizam itens de fluxo são + responsáveis por manter essa coluna, ela não é mantida automaticamente pelo + Provedor de Contatos. +
+
+

+ Para exibir informações de identificação para os itens de fluxo, use + {@code android.provider.ContactsContract.StreamItemsColumns#RES_ICON}, + {@code android.provider.ContactsContract.StreamItemsColumns#RES_LABEL} e + {@code android.provider.ContactsContract.StreamItemsColumns#RES_PACKAGE} para vincular a recursos + no aplicativo. +

+

+ A tabela {@code android.provider.ContactsContract.StreamItems} também contém as colunas + {@code android.provider.ContactsContract.StreamItemsColumns#SYNC1} por meio de + {@code android.provider.ContactsContract.StreamItemsColumns#SYNC4} para uso exclusivo + dos adaptadores de sincronização. +

+

Fotos de fluxos sociais

+

+ A tabela {@code android.provider.ContactsContract.StreamItemPhotos} armazena fotos associadas + a um item de fluxo. A coluna + {@code android.provider.ContactsContract.StreamItemPhotosColumns#STREAM_ITEM_ID} da tabela + vincula-se a valores na coluna {@code android.provider.BaseColumns#_ID} + da tabela {@code android.provider.ContactsContract.StreamItems}. Referências de fotografias são armazenadas + na tabela nessas colunas: +

+
+
+ Coluna {@code android.provider.ContactsContract.StreamItemPhotos#PHOTO} (um BLOB). +
+
+ Representação binária da foto, redimensionada pelo provedor para armazenar e exibir. + Essa coluna está disponível para compatibilidade retroativa com versões anteriores do Provedor + de Contatos que a usavam para armazenar fotos. Contudo, na versão atual, + não se deve usar essa coluna para armazenar fotos. Em vez disso, use + {@code android.provider.ContactsContract.StreamItemPhotosColumns#PHOTO_FILE_ID} ou + {@code android.provider.ContactsContract.StreamItemPhotosColumns#PHOTO_URI} (ambas são + descritas nos pontos a seguir) para armazenar fotos em um arquivo. Essa coluna + passa a conter uma miniatura da foto, que estará disponível para leitura. +
+
+ {@code android.provider.ContactsContract.StreamItemPhotosColumns#PHOTO_FILE_ID} +
+
+ Identificador numérico de uma foto de um contato bruto. Anexe esse valor à constante + {@link android.provider.ContactsContract.DisplayPhoto#CONTENT_URI DisplayPhoto.CONTENT_URI} + para obter uma URI de conteúdo direcionada para um único arquivo de foto e, em seguida, chame + {@link android.content.ContentResolver#openAssetFileDescriptor(Uri, String) + openAssetFileDescriptor()} para obter um identificador para o arquivo de foto. +
+
+ {@code android.provider.ContactsContract.StreamItemPhotosColumns#PHOTO_URI} +
+
+ URI de conteúdo direcionada diretamente para o arquivo de foto da foto representada por essa linha. + Chame {@link android.content.ContentResolver#openAssetFileDescriptor(Uri, String) + openAssetFileDescriptor()} com essa URI para obter um identificador para o arquivo de foto. +
+
+

Uso de tabelas de fluxos sociais

+

+ Essas tabelas funcionam como as outras tabelas principais do Provedor de Contatos, exceto que: +

+
    +
  • + Exigem permissões de acesso adicionais. Para ler o conteúdo delas, o aplicativo + deve ter a permissão {@code android.Manifest.permission#READ_SOCIAL_STREAM}. Para + modificá-las, o aplicativo precisa ter a permissão + {@code android.Manifest.permission#WRITE_SOCIAL_STREAM}. +
  • +
  • + Para a tabela {@code android.provider.ContactsContract.StreamItems}, o número de linhas + armazenadas para cada contato bruto é limitado. Ao atingir o limite, + o Provedor de Contatos abre espaço para novas linhas de itens de fluxo excluindo automaticamente + as linhas que têm + o {@code android.provider.ContactsContract.StreamItemsColumns#TIMESTAMP} mais antigo. Para conhecer + o limite, faça uma consulta à URI de conteúdo + {@code android.provider.ContactsContract.StreamItems#CONTENT_LIMIT_URI}. É possível deixar + todos os argumentos, exceto a URI de conteúdo, definidos como null. A consulta + retorna um Cursor contendo uma linha única com a coluna única + {@code android.provider.ContactsContract.StreamItems#MAX_ITEMS}. +
  • +
+ +

+ A classe {@code android.provider.ContactsContract.StreamItems.StreamItemPhotos} define + uma subtabela de {@code android.provider.ContactsContract.StreamItemPhotos} que contém as linhas + de foto de um único item de fluxo. +

+

Interações de fluxos sociais

+

+ Os dados de fluxos sociais gerenciados pelo Provedor de Contatos, em conjunto com o + aplicativo de contatos do dispositivo, oferecem um método poderoso de conexão ao sistema de redes sociais + com contatos existentes. Os seguintes recursos estão disponíveis: +

+
    +
  • + Ao sincronizar seu serviço de rede social com o Provedor de Contatos com um adaptador + de sincronização, é possível recuperar atividades recentes de contatos de um usuário e armazená-las + nas tabelas {@code android.provider.ContactsContract.StreamItems} + e {@code android.provider.ContactsContract.StreamItemPhotos} para uso posterior. +
  • +
  • + Além da sincronização regular, é possível ativar o adaptador de sincronização para recuperar + dados adicionais quando o usuário seleciona um contato para exibir. Isso permite que o adaptador de sincronização + recupere fotos de alta resolução e os itens de fluxo mais recentes do contato. +
  • +
  • + Ao registrar uma notificação com o aplicativo de contatos do dispositivo e o Provedor + de Contatos, é possível recuperar uma intenção quando um contato é exibido e, nesse ponto, + atualizar o status do contato pelo serviço. Essa abordagem pode ser mais rápida e usar menos + largura de banda do que realizar uma sincronização completa com um adaptador de sincronização. +
  • +
  • + Os usuários podem adicionar um contato ao seu serviço de rede social enquanto exibem-no + no aplicativo de contatos do dispositivo. É possível ativar isso com o recurso "convidar contato", + que é ativado com uma combinação de uma atividade que adiciona um contato existente à sua + rede, um arquivo XML que fornece o aplicativo de contatos do dispositivo + e o Provedor de Contatos com os detalhes do aplicativo. +
  • +
+

+ A sincronização regular de itens de fluxo com o Provedor de Contatos é igual + a outras sincronizações. Para saber mais sobre sincronizações, consulte a seção + Adaptadores de sincronização do Provedor de Contatos. O registro de notificações + e o convite a contatos são abordados nas próximas duas seções. +

+

Registro para manipular exibições de redes sociais

+

+ Para registrar o adaptador de sincronização para receber notificações quando o usuário exibe um contato que é + gerenciado pelo adaptador de sincronização: +

+
    +
  1. + Crie um arquivo chamado contacts.xml no diretório res/xml/ + do projeto. Se você já tiver esse arquivo, pule esta etapa. +
  2. +
  3. + Nesse arquivo, adicione o elemento +<ContactsAccountType xmlns:android="http://schemas.android.com/apk/res/android">. + Se esse elemento já existir, pule esta etapa. +
  4. +
  5. + Para registrar um serviço que seja notificado quando o usuário abre uma página de detalhes do contato + no aplicativo de contatos do dispositivo, adicione o atributo + viewContactNotifyService="serviceclass" ao elemento, em que + serviceclass é o nome de classe totalmente qualificado do serviço + que deve receber a intenção do aplicativo de contatos do dispositivo. Para o serviço + de notificação, use a classe que estende {@link android.app.IntentService} para permitir que o serviço + receba intenções. Os dados nas intenções recebidas contêm a URI de conteúdo do contato + bruto em que o usuário clicou. No serviço de notificação, é possível vincular e chamar + o adaptador de sincronização para atualizar os dados do contato bruto. +
  6. +
+

+ Para registrar uma atividade a ser chamada quando o usuário clica em um item de fluxo ou foto, ou ambos: +

+
    +
  1. + Crie um arquivo chamado contacts.xml no diretório res/xml/ + do projeto. Se você já tiver esse arquivo, pule esta etapa. +
  2. +
  3. + Nesse arquivo, adicione o elemento +<ContactsAccountType xmlns:android="http://schemas.android.com/apk/res/android">. + Se esse elemento já existir, pule esta etapa. +
  4. +
  5. + Para registrar uma das atividades para tratar do usuário que clica em um item de fluxo + no aplicativo de contatos do dispositivo, adicione o atributo + viewStreamItemActivity="activityclass" ao elemento, em que + activityclass é o nome de classe totalmente qualificado da atividade + que deve receber a intenção do aplicativo de contatos do dispositivo. +
  6. +
  7. + Para registrar uma das atividades para tratar do usuário que clica em uma foto de fluxo + no aplicativo de contatos do dispositivo, adicione o atributo + viewStreamItemPhotoActivity="activityclass" ao elemento, em que + activityclass é o nome de classe totalmente qualificado da atividade + que deve receber a intenção do aplicativo de contatos do dispositivo. +
  8. +
+

+ O elemento <ContactsAccountType> é descrito em mais detalhes + na seção Elemento <ContactsAccountType>. +

+

+ A intenção recebida contém a URI de conteúdo do item ou foto em que o usuário clicou. + Para ter atividades separadas para itens de texto e para fotos, use os dois atributos no mesmo arquivo. +

+

Interação com o serviço de redes sociais

+

+ Os usuários não precisam sair do aplicativo de contatos do dispositivo para convidar um contato para + seu site de contatos. Em vez disso, o aplicativo de contatos do dispositivo pode enviar uma intenção de convidar + o contato para uma das atividades. Para configurar isso: +

+
    +
  1. + Crie um arquivo chamado contacts.xml no diretório res/xml/ + do projeto. Se você já tiver esse arquivo, pule esta etapa. +
  2. +
  3. + Nesse arquivo, adicione o elemento +<ContactsAccountType xmlns:android="http://schemas.android.com/apk/res/android">. + Se esse elemento já existir, pule esta etapa. +
  4. +
  5. + Adicione os seguintes atributos: +
      +
    • inviteContactActivity="activityclass"
    • +
    • + inviteContactActionLabel="@string/invite_action_label" +
    • +
    + O valor activityclass é o nome de classe totalmente qualificado + da atividade que deve receber a intenção. O valor invite_action_label + é uma string de texto exibida no menu Adicionar conexão + no aplicativo de contatos do dispositivo. +
  6. +
+

+ Observação: ContactsSource é um nome de tag reprovado para + ContactsAccountType. +

+

Referência do contacts.xml

+

+ O arquivo contacts.xml contém elementos XML que controlam a interação + do adaptador de sincronização e o aplicativo com o aplicativo de contatos e o Provedor de Contatos. Esses + elementos são descritos nas seções a seguir. +

+

Elemento <ContactsAccountType>

+

+ O elemento <ContactsAccountType> controla a interação + do aplicativo com o aplicativo de contatos. Ele tem a seguinte sintaxe: +

+
+<ContactsAccountType
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        inviteContactActivity="activity_name"
+        inviteContactActionLabel="invite_command_text"
+        viewContactNotifyService="view_notify_service"
+        viewGroupActivity="group_view_activity"
+        viewGroupActionLabel="group_action_text"
+        viewStreamItemActivity="viewstream_activity_name"
+        viewStreamItemPhotoActivity="viewphotostream_activity_name">
+
+

+ contido em: +

+

+ res/xml/contacts.xml +

+

+ pode conter: +

+

+ <ContactsDataKind> +

+

+ Descrição: +

+

+ Declara componentes e etiquetas da IU do Android que permitem aos usuários convidar um dos seus contatos a + uma rede social, notificar usuários quando um dos seus fluxos de redes sociais é atualizado + etc. +

+

+ Observe que o prefixo android: do atributo não é necessário para os atributos + de <ContactsAccountType>. +

+

+ Atributos: +

+
+
{@code inviteContactActivity}
+
+ O nome de classe totalmente qualificado da atividade no aplicativo que você deseja + ativar quando o usuário seleciona Adicionar conexão no aplicativo + de contatos do dispositivo. +
+
{@code inviteContactActionLabel}
+
+ Uma string de texto exibida para a atividade especificada + em {@code inviteContactActivity}, no menu Adicionar conexão. + Por exemplo: você pode usar a string "Siga-me na rede". Você pode usar um identificador + do recurso da string nessa etiqueta. +
+
{@code viewContactNotifyService}
+
+ O nome de classe totalmente qualificado de um serviço no aplicativo que deve receber + notificações de quando o usuário exibe um contato. Essa notificação é enviada pelo aplicativo + de contatos do dispositivo, isso permite que o aplicativo adie operações de dados intensivos + até que elas sejam necessárias. Por exemplo: o aplicativo pode responder a essa notificação + lendo e exibindo a foto de alta resolução do contato e os itens de fluxo social + mais recentes. Esse recurso é descrito com mais detalhes na seção + Interações de fluxos sociais. É possível ver + um exemplo de serviço de notificação no arquivo NotifierService.java + no aplicativo de exemplo + SampleSyncAdapter. +
+
{@code viewGroupActivity}
+
+ O nome de classe totalmente qualificado de uma atividade no aplicativo que pode exibir + informações de grupo. Quando o usuário clica na etiqueta do grupo no aplicativo + de contatos do dispositivo, a IU dessa atividade é exibida. +
+
{@code viewGroupActionLabel}
+
+ A etiqueta que o aplicativo de contatos exibe para um controle de IU que permite + que o usuário veja grupos no seu aplicativo. +

+ Por exemplo: se você instalar o aplicativo do Google+ no dispositivo e sincronizá-lo + com o aplicativo de contatos, verá os círculos do Google+ listados como grupos + na aba Grupos do aplicativo de contatos. Ao clicar + em um círculo do Google+, você verá pessoas nesse círculo listadas como um "grupo". Na parte superior + da tela, você verá um ícone do Google+; ao clicar nele, o controle muda para + o aplicativo do Google+. O aplicativo de contatos faz isso com + {@code viewGroupActivity} usando o ícone como o valor + de {@code viewGroupActionLabel}. +

+

+ Um identificador de recurso de string é permitido para esse atributo. +

+
+
{@code viewStreamItemActivity}
+
+ O nome de classe totalmente qualificado de uma atividade no aplicativo que + o aplicativo de contatos do dispositivo executa quando o usuário clica em um item de fluxo de um contato bruto. +
+
{@code viewStreamItemPhotoActivity}
+
+ O nome de classe totalmente qualificado de uma atividade no aplicativo que + o aplicativo de contatos do dispositivo executa quando o usuário clica em uma foto no item de fluxo + de um contato bruto. +
+
+

Elemento <ContactsDataKind>

+

+ O elemento <ContactsDataKind> controla a tela das linhas de dados + personalizados do aplicativo na IU do aplicativo de contatos. Ele tem a seguinte sintaxe: +

+
+<ContactsDataKind
+        android:mimeType="MIMEtype"
+        android:icon="icon_resources"
+        android:summaryColumn="column_name"
+        android:detailColumn="column_name">
+
+

+ contido em: +

+<ContactsAccountType> +

+ Descrição: +

+

+ Use esse elemento para que o aplicativo de contatos exiba o conteúdo de uma linha de dados personalizados como + parte dos detalhes de um contato bruto. Cada elemento filho <ContactsDataKind> + de <ContactsAccountType> representa um tipo de linha de dados personalizados que o adaptador + de sincronização adiciona à tabela {@link android.provider.ContactsContract.Data}. Adicione + um elemento <ContactsDataKind> para cada tipo MIME personalizado usado. Não será necessário + adicionar o elemento se você tiver uma linha de dados personalizados para a qual não deseja exibir dados. +

+

+ Atributos: +

+
+
{@code android:mimeType}
+
+ O tipo MIME personalizado definido para um dos tipos de linha de dados personalizados + na tabela {@link android.provider.ContactsContract.Data}. Por exemplo: o valor + vnd.android.cursor.item/vnd.example.locationstatus poderia ser um tipo MIME + personalizado para uma linha de dados que registra a última localização conhecida do contato. +
+
{@code android:icon}
+
+ + Recurso desenhável do Android + que o aplicativo de contatos exibe próximo aos dados. Use isso para indicar + ao usuário que os dados são advindos do seu serviço. +
+
{@code android:summaryColumn}
+
+ O nome de coluna do primeiro de dois valores recuperados da linha de dados. + O valor é exibido como a primeira linha da entrada para essa linha de dados. A primeira linha + destina-se ao uso como um resumo dos dados, mas isso é opcional. Veja também + android:detailColumn. +
+
{@code android:detailColumn}
+
+ O nome de coluna do segundo de dois valores recuperados da linha de dados. O valor é + exibido como a segunda linha da entrada dessa linha de dados. Veja também + {@code android:summaryColumn}. +
+
+

Recursos adicionais do Provedor de Contatos

+

+ Além dos principais recursos descritos nas seções anteriores, o Provedor de Contatos oferece + estes recursos úteis para trabalhar com dados de contatos: +

+
    +
  • Grupos de contatos
  • +
  • Recursos de foto
  • +
+

Grupos de contatos

+

+ O Provedor de Contatos pode, opcionalmente, etiquetar grupos de contatos relacionados com + dados de grupo. Se o servidor associado a uma conta de usuário + deseja manter grupos, o adaptador de sincronização para o tipo da conta deve transferir + dados de grupo entre o Provedor de Contatos e o servidor. Quando o usuário adiciona um novo contato + ao servidor e, em seguida, coloca este contato em um novo grupo, o adaptador de sincronização deve adicionar o novo grupo + na tabela {@link android.provider.ContactsContract.Groups}. O grupo ou grupos a que um contato + bruto pertence são armazenados na tabela {@link android.provider.ContactsContract.Data}, usando + o tipo MIME {@link android.provider.ContactsContract.CommonDataKinds.GroupMembership}. +

+

+ Se estiver projetando um adaptador de sincronização que adicionará dados de contato bruto ao Provedor de Contatos + a partir de servidores e não estiver usando grupos, será necessário fazer com que + o provedor torne os dados visíveis. No código que é executado quando um usuário adiciona uma conta + ao dispositivo, atualize a linha {@link android.provider.ContactsContract.Settings} + que o Provedor de Contatos adicionou para a conta. Nessa linha, defina o valor da + coluna {@link android.provider.ContactsContract.SettingsColumns#UNGROUPED_VISIBLE + Settings.UNGROUPED_VISIBLE} como 1. Ao fazê-lo, o Provedor de Contatos sempre + deixará os dados de contatos visíveis, mesmo sem usar grupos. +

+

Fotos de contatos

+

+ A tabela {@link android.provider.ContactsContract.Data} armazena fotos como linhas com tipo MIME + {@link android.provider.ContactsContract.CommonDataKinds.Photo#CONTENT_ITEM_TYPE + Photo.CONTENT_ITEM_TYPE}. A coluna + {@link android.provider.ContactsContract.RawContactsColumns#CONTACT_ID} da linha é vinculada + à coluna {@code android.provider.BaseColumns#_ID} do contato bruto à qual pertence. + A classe {@link android.provider.ContactsContract.Contacts.Photo} define uma subtabela de + {@link android.provider.ContactsContract.Contacts} contendo informações de foto + de uma foto principal do contato, que é a foto principal do contato bruto principal do contato. Da mesma forma, + a classe {@link android.provider.ContactsContract.RawContacts.DisplayPhoto} define uma subtabela + de {@link android.provider.ContactsContract.RawContacts} contendo informações de foto + de uma foto principal do contato bruto. +

+

+ A documentação de referência de {@link android.provider.ContactsContract.Contacts.Photo} e + {@link android.provider.ContactsContract.RawContacts.DisplayPhoto} contém exemplos + de recuperação de informações de foto. Não há classe conveniente para recuperar a miniatura + principal de um contato bruto, mas é possível enviar uma consulta + à tabela {@link android.provider.ContactsContract.Data}, selecionando no {@code android.provider.BaseColumns#_ID} + do contato bruto, o + {@link android.provider.ContactsContract.CommonDataKinds.Photo#CONTENT_ITEM_TYPE + Photo.CONTENT_ITEM_TYPE} e a coluna + {@link android.provider.ContactsContract.Data#IS_PRIMARY} para encontrar a linha da foto principal do contato bruto. +

+

+ Dados de fluxos sociais de uma pessoa também podem conter fotos. Elas são armazenadas + na tabela {@code android.provider.ContactsContract.StreamItemPhotos}, que é descrita com mais + detalhes na seção Fotos de fluxos sociais. +

diff --git a/docs/html-intl/intl/pt-br/guide/topics/providers/content-provider-basics.jd b/docs/html-intl/intl/pt-br/guide/topics/providers/content-provider-basics.jd new file mode 100644 index 0000000000000000000000000000000000000000..5005f9208bfe7026bac63524ecbfd9dd6dd2d2f4 --- /dev/null +++ b/docs/html-intl/intl/pt-br/guide/topics/providers/content-provider-basics.jd @@ -0,0 +1,1196 @@ +page.title=Preceitos do provedor de conteúdo +@jd:body +
+
+ +

Neste documento

+
    +
  1. + Visão geral +
      +
    1. + Acesso a um provedor +
    2. +
    3. + URIs de conteúdo +
    4. +
    +
  2. +
  3. + Recuperação de dados do provedor +
      +
    1. + Solicitação de permissão de acesso para leitura +
    2. +
    3. + Construção da consulta +
    4. +
    5. + Exibição dos resultados da consulta +
    6. +
    7. + Obtenção de dados de resultados da consulta +
    8. +
    +
  4. +
  5. + Permissões do provedor de conteúdo +
  6. +
  7. + Inserção, atualização e exclusão de dados +
      +
    1. + Inserção de dados +
    2. +
    3. + Atualização de dados +
    4. +
    5. + Exclusão de dados +
    6. +
    +
  8. +
  9. + Tipos de dados do provedor +
  10. +
  11. + Formas alternativas de acesso ao provedor +
      +
    1. + Acesso em lote +
    2. +
    3. + Acesso a dados via intenções +
    4. +
    +
  12. +
  13. + Classes de contrato +
  14. +
  15. + Referência de tipo MIME +
  16. +
+ + +

Classes principais

+
    +
  1. + {@link android.content.ContentProvider} +
  2. +
  3. + {@link android.content.ContentResolver} +
  4. +
  5. + {@link android.database.Cursor} +
  6. +
  7. + {@link android.net.Uri} +
  8. +
+ + +

Exemplos relacionados

+
    +
  1. + + Cursor (Pessoas) +
  2. +
  3. + + Cursor (Telefones) +
  4. +
+ + +

Veja também

+
    +
  1. + + Criação de um Provedor de conteúdo +
  2. +
  3. + + Provedor de agenda +
  4. +
+
+
+ + +

+ O provedor de conteúdo gerencia o acesso a um repositório central de dados. Um provedor + é parte de um aplicativo do Android, que, em geral, fornece a própria IU para trabalhar com + os dados. Contudo, provedores de conteúdo destinam-se principalmente ao uso por outros + aplicativos, que acessam o provedor usando um objeto cliente do provedor. Juntos, provedores + e clientes do provedor oferecem interface padronizada e consistente para dados que também lidam com + comunicação em processos internos e garantem acesso a dados. +

+

+ Esse tópico descreve os conceitos básicos do seguinte: +

+
    +
  • Como os provedores de conteúdo funcionam.
  • +
  • A API usada para recuperar dados de um provedor de conteúdo.
  • +
  • A API usada para inserir, atualizar ou excluir dados em um provedor de conteúdo.
  • +
  • Outros recursos de API que facilitam o trabalho com provedores.
  • +
+ + +

Visão geral

+

+ O provedor de conteúdo apresenta dados a aplicativos externos na forma de uma ou mais tabelas + similares às tabelas encontradas em um banco de dados relacional. Uma linha representa uma instância de algum tipo + de dados que o provedor coleta e cada coluna na linha representa uma parte individual de + dados coletados por uma instância. +

+

+ Por exemplo: um dos provedores embutidos na plataforma do Android é o dicionário do usuário, que + armazena as grafias de palavras incomuns que o usuário deseja manter. A tabela 1 ilustra + como podem ser os dados nesta tabela do provedor: +

+

+ Tabela 1: Tabela de dicionário do usuário de exemplo. +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
palavraid do aplicativofrequêncialocalidade_ID
reduçãodomapausuário1100en_US1
pré-compiladorusuário14200fr_FR2
appletusuário2225fr_CA3
constusuário1255pt_BR4
intusuário5100en_UK5
+

+ Na tabela 1, cada linha representa uma instância de uma palavra que pode não ser + encontrada em um dicionário comum. Cada coluna representa alguns dados dessa palavra, como + a localidade em que foi encontrada pela primeira vez. Os cabeçalhos da coluna são nomes de coluna armazenados + no provedor. Para consultar a localidade de uma linha, consulte a sua coluna locale. Para + esse provedor, a coluna _ID serve como uma coluna de "chave principal" que + o provedor mantém automaticamente. +

+

+ Observação: os provedores não precisam ter uma chave principal e não precisam + usar _ID como o nome de coluna de uma chave principal se uma for apresentada. Contudo, + se você deseja agrupar dados de um provedor em um {@link android.widget.ListView}, um dos + nomes de coluna deve ser _ID. Esse requisito é explicado com mais detalhes + na seção Exibição dos resultados da consulta. +

+

Acesso a um provedor

+

+ Os aplicativos acessam dados a partir de um provedor de conteúdo + com um objeto cliente {@link android.content.ContentResolver}. Esse objeto tem métodos que chamam + métodos de nome idêntico no objeto do provedor, uma instância de uma das subclasses + concretas de {@link android.content.ContentProvider}. + Os métodos {@link android.content.ContentResolver} fornecem as funções básicas + do "CRUD" (criar, recuperar, atualizar e excluir) de armazenamento persistente. +

+

+ O objeto {@link android.content.ContentResolver} no processo do aplicativo + cliente e o objeto {@link android.content.ContentProvider} no aplicativo que possui + o provedor lidam automaticamente com a comunicação de processos internos. + {@link android.content.ContentProvider} também age como uma camada de abstração entre + ser repositório de dados e a aparência externa de dados na forma de tabelas. +

+

+ Observação: para acessar um provedor, o aplicativo normalmente precisa solicitar permissões + específicas no arquivo de manifesto. Isso é descrito com mais detalhes na seção + Permissões do provedor de conteúdo. +

+

+ Por exemplo: para obter uma lista das palavras e respectivas localidades do Provedor de dicionário do usuário, + chama-se {@link android.content.ContentResolver#query ContentResolver.query()}. + O método {@link android.content.ContentResolver#query query()} chama + o método {@link android.content.ContentProvider#query ContentProvider.query()} definido pelo + Provedor de dicionário do usuário. As linhas de código a seguir exibem + uma chamada {@link android.content.ContentResolver#query ContentResolver.query()}: +

+

+// Queries the user dictionary and returns results
+mCursor = getContentResolver().query(
+    UserDictionary.Words.CONTENT_URI,   // The content URI of the words table
+    mProjection,                        // The columns to return for each row
+    mSelectionClause                    // Selection criteria
+    mSelectionArgs,                     // Selection criteria
+    mSortOrder);                        // The sort order for the returned rows
+
+

+ A tabela 2 mostra como os argumentos para + {@link android.content.ContentResolver#query + query(Uri,projection,selection,selectionArgs,sortOrder)} correspondem a uma declaração SQL SELECT: +

+

+ Tabela 2: Query() comparada à consulta SQL. +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Argumento query()Palavra-chave/parâmetro de SELEÇÃOObservações
UriFROM table_nameUri mapeia para a tabela no provedor chamado table_name.
projectioncol,col,col,... + projection é uma matriz de colunas que devem ser incluídas para cada linha + recuperada. +
selectionWHERE col = valueselection especifica o critério para a seleção de linhas.
selectionArgs + (Não exatamente equivalente. Argumentos de seleção substituem marcadores de posição ? + na cláusula de seleção.) +
sortOrderORDER BY col,col,... + sortOrder especifica a ordem em que as linhas aparecem + no {@link android.database.Cursor} retornado. +
+

URIs de conteúdo

+

+ URI de conteúdo é uma URI que identifica dados em um provedor. URIs de conteúdo + contêm o nome simbólico de todo o provedor (sua autoridade) + e um nome que aponta para uma tabela (um caminho). Ao chamar + um método cliente para acessar uma tabela em um provedor, a URI de conteúdo da tabela é + um dos argumentos. +

+

+ Nas linhas de código anteriores, a constante + {@link android.provider.UserDictionary.Words#CONTENT_URI} contém a URI de conteúdo + da tabela de "palavras" do dicionário do usuário. O objeto {@link android.content.ContentResolver} + analisa a autoridade da URI e usa-na para "determinar" o provedor + comparando a autoridade a uma tabela de provedores conhecidos do sistema. + O {@link android.content.ContentResolver} pode, então, enviar os argumentos da consulta ao provedor + correto. +

+

+ O {@link android.content.ContentProvider} usa o caminho que é parte da URI de conteúdo para escolher + a tabela para acessar. Os provedores normalmente têm um caminho para cada tabela exposta. +

+

+ Nas linhas de código anteriores, a URI completa da tabela de "palavras" é: +

+
+content://user_dictionary/words
+
+

+ onde a string user_dictionary é a autoridade do provedor e + a string words é o caminho da tabela. A string + content:// (o esquema) está sempre presente + e identifica isso como uma URI de conteúdo. +

+

+ Muitos provedores permitem acesso a uma única linha em uma tabela, por meio da anexação do valor de um ID + no fim da URI. Por exemplo, para recuperar uma linha em que _ID seja + 4 do dicionário do usuário, é possível usar essa URI de conteúdo: +

+
+Uri singleUri = ContentUris.withAppendedId(UserDictionary.Words.CONTENT_URI,4);
+
+

+ Normalmente usam-se valores de ID ao recuperar um conjunto de linhas e, em seguida, é necessário atualizar ou excluir + uma delas. +

+

+ Observação: as classes {@link android.net.Uri} e {@link android.net.Uri.Builder} + contêm métodos convenientes para a construção de objetos de URI bem formados a partir de strings. + As {@link android.content.ContentUris} contêm métodos conveniente para anexar valores de ID + a uma URI. O fragmento anterior usa {@link android.content.ContentUris#withAppendedId + withAppendedId()} para anexar um ID à URI de conteúdo UserDictionary. +

+ + + +

Recuperação de dados pelo Provedor

+

+ Esta seção descreve como recuperar dados de um provedor usando o Provedor de dicionário do usuário + como um exemplo. +

+

+ Por uma questão de clareza, os fragmentos de código nesta seção chamam + {@link android.content.ContentResolver#query ContentResolver.query()} no "encadeamento da IU". + No código atual, contudo, deve-se realizar consultas assincronamente em um encadeamento separado. Um modo de fazê-lo + é usar a classe {@link android.content.CursorLoader}, descrita + com mais detalhes no guia + Carregadores. Além disso, as linhas de código são somente fragmentos — não mostram um aplicativo + completo. +

+

+ Para recuperar dados de um provedor, siga essas etapas básicas: +

+
    +
  1. + Solicite a permissão de acesso para leitura para um provedor. +
  2. +
  3. + Defina o código que envia uma consulta ao provedor. +
  4. +
+

Solicitação de permissão de acesso para leitura

+

+ Para recuperar dados de um provedor, o aplicativo precisa de "permissão de acesso a leitura" + para o provedor. Não é possível solicitar essa permissão em tempo de execução. Em vez disso, deve-se especificar + que precisa dessa permissão no manifesto com o elemento +<uses-permission> + e o nome da permissão exata definida + pelo provedor. Ao especificar esse elemento no manifesto, você está efetivamente "solicitando" essa + permissão para o aplicativo. Quando usuários instalam seu aplicativo, eles concedem essa solicitação + implicitamente. +

+

+ Para encontrar o nome exato da permissão de acesso para leitura do provedor que está usando, bem + como os nomes de outras permissões de acesso usadas pelo provedor, consulte a documentação + do provedor. +

+

+ O papel das permissões no acesso a provedores é descrito com mais detalhes na seção + Permissões do provedor de conteúdo. +

+

+ O Provedor de Dicionário do Usuário define a permissão + android.permission.READ_USER_DICTIONARY no arquivo de manifesto, portanto, se um + aplicativo quiser ler pelo provedor, deve solicitar essa permissão. +

+ +

Construção da consulta

+

+ A próxima etapa na recuperação de dados de um provedor é construir uma consulta. Este primeiro fragmento + define algumas variáveis para acessar o Provedor de Dicionário do Usuário: +

+
+
+// A "projection" defines the columns that will be returned for each row
+String[] mProjection =
+{
+    UserDictionary.Words._ID,    // Contract class constant for the _ID column name
+    UserDictionary.Words.WORD,   // Contract class constant for the word column name
+    UserDictionary.Words.LOCALE  // Contract class constant for the locale column name
+};
+
+// Defines a string to contain the selection clause
+String mSelectionClause = null;
+
+// Initializes an array to contain selection arguments
+String[] mSelectionArgs = {""};
+
+
+

+ O próximo fragmento mostra como usar + {@link android.content.ContentResolver#query ContentResolver.query()} usando o Provedor de + Dicionário do Usuário como exemplo. Uma consulta cliente do provedor é similar a uma consulta SQL e contém um + conjunto de colunas para retornar, um conjunto de critérios de seleção e uma classificação ordenada. +

+

+ O conjunto de colunas que a consulta deve retornar é chamado de projeção + (a variável mProjection). +

+

+ A expressão que especifica as linhas a recuperar é dividida em uma cláusula de seleção + e em argumentos de seleção. A cláusula de seleção é uma combinação de expressões lógicas e booleanas + e nomes e valores de colunas (a variável mSelectionClause). Ao especificar + o parâmetro ? substituível em vez de um valor, o método da consulta recupera o valor + da matriz de argumentos de seleção (a variável mSelectionArgs). +

+

+ No próximo fragmento, se o usuário não inserir nenhuma palavra, a cláusula de seleção será definida como + null e a consulta retornará todas as palavras no provedor. Se o usuário inserir + uma palavra, a cláusula de seleção será definida como UserDictionary.Words.WORD + " = ?" + e o primeiro elemento da matriz de argumentos de seleção é definida como a palavra que o usuário inserir. +

+
+/*
+ * This defines a one-element String array to contain the selection argument.
+ */
+String[] mSelectionArgs = {""};
+
+// Gets a word from the UI
+mSearchString = mSearchWord.getText().toString();
+
+// Remember to insert code here to check for invalid or malicious input.
+
+// If the word is the empty string, gets everything
+if (TextUtils.isEmpty(mSearchString)) {
+    // Setting the selection clause to null will return all words
+    mSelectionClause = null;
+    mSelectionArgs[0] = "";
+
+} else {
+    // Constructs a selection clause that matches the word that the user entered.
+    mSelectionClause = UserDictionary.Words.WORD + " = ?";
+
+    // Moves the user's input string to the selection arguments.
+    mSelectionArgs[0] = mSearchString;
+
+}
+
+// Does a query against the table and returns a Cursor object
+mCursor = getContentResolver().query(
+    UserDictionary.Words.CONTENT_URI,  // The content URI of the words table
+    mProjection,                       // The columns to return for each row
+    mSelectionClause                   // Either null, or the word the user entered
+    mSelectionArgs,                    // Either empty, or the string the user entered
+    mSortOrder);                       // The sort order for the returned rows
+
+// Some providers return null if an error occurs, others throw an exception
+if (null == mCursor) {
+    /*
+     * Insert code here to handle the error. Be sure not to use the cursor! You may want to
+     * call android.util.Log.e() to log this error.
+     *
+     */
+// If the Cursor is empty, the provider found no matches
+} else if (mCursor.getCount() < 1) {
+
+    /*
+     * Insert code here to notify the user that the search was unsuccessful. This isn't necessarily
+     * an error. You may want to offer the user the option to insert a new row, or re-type the
+     * search term.
+     */
+
+} else {
+    // Insert code here to do something with the results
+
+}
+
+

+ Essa consulta é análoga à declaração SQL: +

+
+SELECT _ID, word, locale FROM words WHERE word = <userinput> ORDER BY word ASC;
+
+

+ Nesta declaração SQL, os nomes de coluna reais são usados no lugar de constantes de classes de contrato. +

+

Proteção contra inserções mal-intencionadas

+

+ Se os dados gerenciados pelo provedor de conteúdo estiverem em um banco de dados SQL, inclusive dados não confiáveis + externos nas declarações SQL brutas, existe a possibilidade de haver uma injeção de SQL. +

+

+ Considere esta cláusula de seleção: +

+
+// Constructs a selection clause by concatenating the user's input to the column name
+String mSelectionClause =  "var = " + mUserInput;
+
+

+ Se você fizer isso, estará permitindo que o usuário concatene SQL mal-intencionados na sua declaração SQL. + Por exemplo: o usuário poderia inserir "nada; REMOVER TABELA *;" para mUserInput, o que + resultaria na cláusula de seleção var = nothing; DROP TABLE *;. Como + a cláusula de seleção é tratada como uma declaração SQL, isso pode fazer com que o provedor apague todas + as tabelas no banco de dados SQLite em questão (a menos que o provedor esteja configurado para capturar + tentativas de injeção de SQL). +

+

+ Para evitar este problema, use uma cláusula de seleção que use ? como um parâmetro + substituível e uma matriz de argumentos de seleção separada. Ao fazer isso, a inserção de dados do usuário + limita-se diretamente à consulta em vez de ser interpretada como parte de uma declaração SQL. + Pelo fato de não ser tratada como SQL, a inserção de dados do usuário não injeta SQL mal-intencionados. Em vez de usar + a concatenação para incluir a inserção de dados do usuário, use esta cláusula de seleção: +

+
+// Constructs a selection clause with a replaceable parameter
+String mSelectionClause =  "var = ?";
+
+

+ Configure a matriz de argumentos de seleção desta maneira: +

+
+// Defines an array to contain the selection arguments
+String[] selectionArgs = {""};
+
+

+ Insira um valor na matriz de argumentos de seleção desta maneira: +

+
+// Sets the selection argument to the user's input
+selectionArgs[0] = mUserInput;
+
+

+ Usar uma cláusula de seleção que use ? como um parâmetro substituível e uma matriz + de argumentos de seleção é o melhor modo de especificar uma seleção, mesmo que o provedor se baseie + base em um banco de dados SQL. +

+ +

Exibição dos resultados da consulta

+

+ O método cliente {@link android.content.ContentResolver#query ContentResolver.query()} sempre + retorna um {@link android.database.Cursor} contendo as colunas especificadas pela projeção + da consulta para as linhas que atendem aos critérios de seleção da consulta. + Um objeto {@link android.database.Cursor} fornece acesso para leitura aleatório para as linhas e colunas que + contém. Usando métodos {@link android.database.Cursor}, é possível repetir as linhas + nos resultados, determinar o tipo dos dados de cada coluna, extrair os dados de uma coluna e examinar + outras propriedades dos resultados. Algumas implementações do {@link android.database.Cursor} atualizam o objeto + automaticamente quando os dados do provedor mudam ou acionam métodos em um objeto observador + quando o {@link android.database.Cursor} muda, ou ambos. +

+

+ Observação: os provedores podem restringir acesso a colunas com base na natureza + do objeto que realiza a consulta. Por exemplo: o Provedor de Contatos restringe o acesso de algumas colunas + a adaptadores de sincronização, por isso ela não os retornará a uma atividade ou serviço. +

+

+ Se nenhuma linha atender aos critérios de seleção, o provedor + retorna um objeto {@link android.database.Cursor} para o qual + {@link android.database.Cursor#getCount Cursor.getCount()} é 0 (um cursor vazio). +

+

+ Se ocorrer um erro interno, os resultados da consulta dependem do provedor determinado. Ele pode + escolher retornar null ou pode gerar uma {@link java.lang.Exception}. +

+

+ Já que {@link android.database.Cursor} é uma "lista" de linhas, um bom modo de exibir + o conteúdo de um {@link android.database.Cursor} é vinculá-lo a uma {@link android.widget.ListView} + por meio de um {@link android.widget.SimpleCursorAdapter}. +

+

+ O fragmento a seguir continua o código do fragmento anterior. Ele cria + um objeto {@link android.widget.SimpleCursorAdapter} contendo o {@link android.database.Cursor} + recuperado pela consulta e configura esse objeto para ser o adaptador de uma + {@link android.widget.ListView}: +

+
+// Defines a list of columns to retrieve from the Cursor and load into an output row
+String[] mWordListColumns =
+{
+    UserDictionary.Words.WORD,   // Contract class constant containing the word column name
+    UserDictionary.Words.LOCALE  // Contract class constant containing the locale column name
+};
+
+// Defines a list of View IDs that will receive the Cursor columns for each row
+int[] mWordListItems = { R.id.dictWord, R.id.locale};
+
+// Creates a new SimpleCursorAdapter
+mCursorAdapter = new SimpleCursorAdapter(
+    getApplicationContext(),               // The application's Context object
+    R.layout.wordlistrow,                  // A layout in XML for one row in the ListView
+    mCursor,                               // The result from the query
+    mWordListColumns,                      // A string array of column names in the cursor
+    mWordListItems,                        // An integer array of view IDs in the row layout
+    0);                                    // Flags (usually none are needed)
+
+// Sets the adapter for the ListView
+mWordList.setAdapter(mCursorAdapter);
+
+

+ Observação: para retornar uma {@link android.widget.ListView} com um + {@link android.database.Cursor}, o cursor deve conter uma coluna chamada _ID. + Por isso, a consulta exibida anteriormente recupera a coluna _ID da + tabelas de "palavras", mesmo que a {@link android.widget.ListView} não a exiba. + Essa restrição também explica por que a maioria dos provedores tem uma coluna _ID para cada + tabela. +

+ + +

Obtenção de dados de resultados da consulta

+

+ Em vez de simplesmente exibir resultados da consulta, é possível usá-los para outras tarefas. Por + exemplo: é possível recuperar grafias de um dicionário do usuário e, em seguida, procurá-los + em outros provedores. Para isso, repetem-se as linhas no {@link android.database.Cursor}: +

+
+
+// Determine the column index of the column named "word"
+int index = mCursor.getColumnIndex(UserDictionary.Words.WORD);
+
+/*
+ * Only executes if the cursor is valid. The User Dictionary Provider returns null if
+ * an internal error occurs. Other providers may throw an Exception instead of returning null.
+ */
+
+if (mCursor != null) {
+    /*
+     * Moves to the next row in the cursor. Before the first movement in the cursor, the
+     * "row pointer" is -1, and if you try to retrieve data at that position you will get an
+     * exception.
+     */
+    while (mCursor.moveToNext()) {
+
+        // Gets the value from the column.
+        newWord = mCursor.getString(index);
+
+        // Insert code here to process the retrieved word.
+
+        ...
+
+        // end of while loop
+    }
+} else {
+
+    // Insert code here to report an error if the cursor is null or the provider threw an exception.
+}
+
+

+ As implementações {@link android.database.Cursor} contêm diversos métodos "get" (obter) + para recuperar diferentes tipos de dados do objeto. Por exemplo, o fragmento anterior + usa {@link android.database.Cursor#getString getString()}. Elas também têm + um método {@link android.database.Cursor#getType getType()} que retorna um valor indicando + o tipo dos dados da coluna. +

+ + + +

Permissões do provedor de conteúdo

+

+ Um aplicativo do provedor pode especificar permissões que outros aplicativos devem ter para + acessar os dados do provedor. Essas permissões garantem que o usuário saiba quais dados + um aplicativo tentará acessar. Com base nos requisitos do provedor, outros aplicativos + solicitam as permissões de que precisam para acessar o provedor. Usuários finais veem as permissões + solicitadas quando instalam o aplicativo. +

+

+ Se um aplicativo do provedor não especificar nenhuma permissão, outros aplicativos não terão + acesso aos dados do provedor. No entanto, os componentes no aplicativo do provedor sempre têm + acesso total para leitura e gravação, independentemente das permissões especificadas. +

+

+ Como observado anteriormente, o Provedor de Dicionário do Usuário requer + a permissão android.permission.READ_USER_DICTIONARY para recuperar dados dele. + O provedor tem a permissão android.permission.WRITE_USER_DICTIONARY + separadamente para inserção, atualização ou exclusão de dados. +

+

+ Para obter as permissões necessárias para acessar um provedor, um aplicativo as solicita +como um elemento <uses-permission> + no arquivo de manifesto. Quando o Android Package Manager (gerente de pacotes do Android) instala o aplicativo, o usuário + precisa aprovar todas as permissões que o aplicativo solicita. Se o usuário aprovar todas elas, + o gerente de pacotes continua a instalação; se o usuário não as aprovar, o gerente de pacotes + aborta a instalação. +

+

+ O elemento +<uses-permission> a seguir + solicita acesso para leitura ao Provedor de Dicionário do Usuário: +

+
+    <uses-permission android:name="android.permission.READ_USER_DICTIONARY">
+
+

+ O impacto das permissões no acesso ao provedor é explicado com mais detalhes + no guia Permissões e segurança. +

+ + + +

Inserção, atualização e exclusão de dados

+

+ Do mesmo modo que se recupera dados de um provedor, também usa-se a interação entre + um cliente do provedor e o {@link android.content.ContentProvider} do provedor para modificar dados. + Chama-se um método de {@link android.content.ContentResolver} com argumentos que são passados para + o método correspondente de {@link android.content.ContentProvider}. O provedor e o cliente + do provedor tratam automaticamente da segurança e da comunicação de processos internos. +

+

Inserção de dados

+

+ Para inserir dados em um provedor, chame + o método +{@link android.content.ContentResolver#insert ContentResolver.insert()}. Esse método insere uma nova linha no provedor e retorna uma URI de conteúdo dessa linha. + Este fragmento mostra como inserir uma nova palavra no Provedor de Dicionário do Usuário: +

+
+// Defines a new Uri object that receives the result of the insertion
+Uri mNewUri;
+
+...
+
+// Defines an object to contain the new values to insert
+ContentValues mNewValues = new ContentValues();
+
+/*
+ * Sets the values of each column and inserts the word. The arguments to the "put"
+ * method are "column name" and "value"
+ */
+mNewValues.put(UserDictionary.Words.APP_ID, "example.user");
+mNewValues.put(UserDictionary.Words.LOCALE, "en_US");
+mNewValues.put(UserDictionary.Words.WORD, "insert");
+mNewValues.put(UserDictionary.Words.FREQUENCY, "100");
+
+mNewUri = getContentResolver().insert(
+    UserDictionary.Word.CONTENT_URI,   // the user dictionary content URI
+    mNewValues                          // the values to insert
+);
+
+

+ Os dados da nova linha vão para um objeto {@link android.content.ContentValues} único, que + tem forma semelhante a um cursor de uma linha. As colunas nesse objeto não precisam ter + o mesmo tipo de dados e, se você não quiser especificar um valor, pode definir uma coluna + como null usando {@link android.content.ContentValues#putNull ContentValues.putNull()}. +

+

+ O fragmento não adiciona a coluna _ID porque essa coluna é mantida + automaticamente. O provedor atribui um valor exclusivo de _ID para cada linha + adicionada. Os provedores normalmente usam esse valor como a chave principal da tabela. +

+

+ A URI de conteúdo retornada em newUri identifica a linha recentemente adicionada com + o seguinte formato: +

+
+content://user_dictionary/words/<id_value>
+
+

+ O <id_value> é o conteúdo de _ID da nova linha. + A maioria dos provedores pode detectar essa forma de URI de conteúdo automaticamente e, em seguida, realizar a operação + solicitada naquela linha. +

+

+ Para obter o valor de _ID do {@link android.net.Uri} retornado, chame + {@link android.content.ContentUris#parseId ContentUris.parseId()}. +

+

Atualização de dados

+

+ Para atualizar uma linha, use um objeto {@link android.content.ContentValues} com os valores + e os critérios de seleção atualizados, como se faz com uma inserção e em uma consulta, respectivamente. + O método cliente usado é + {@link android.content.ContentResolver#update ContentResolver.update()}. Só é necessário adicionar + valores ao objeto {@link android.content.ContentValues} das colunas que forem atualizadas. Se você + deseja apagar o conteúdo de uma coluna, defina o valor como null. +

+

+ O fragmento a seguir altera todas as linhas cuja localidade tem o idioma "en" para + terem uma localidade de null. O valor de retorno é o número de linhas que foram atualizadas: +

+
+// Defines an object to contain the updated values
+ContentValues mUpdateValues = new ContentValues();
+
+// Defines selection criteria for the rows you want to update
+String mSelectionClause = UserDictionary.Words.LOCALE +  "LIKE ?";
+String[] mSelectionArgs = {"en_%"};
+
+// Defines a variable to contain the number of updated rows
+int mRowsUpdated = 0;
+
+...
+
+/*
+ * Sets the updated value and updates the selected words.
+ */
+mUpdateValues.putNull(UserDictionary.Words.LOCALE);
+
+mRowsUpdated = getContentResolver().update(
+    UserDictionary.Words.CONTENT_URI,   // the user dictionary content URI
+    mUpdateValues                       // the columns to update
+    mSelectionClause                    // the column to select on
+    mSelectionArgs                      // the value to compare to
+);
+
+

+ Você também deve filtrar a inserção de dados do usuário ao chamar + {@link android.content.ContentResolver#update ContentResolver.update()}. Para saber mais sobre + isso, leia a seção Proteção contra inserções mal-intencionadas. +

+

Exclusão de dados

+

+ Excluir linhas é semelhante a recuperar dados de linhas: especificam-se critérios de seleção para as linhas + que se deseja excluir e o método cliente retorna o número de linhas excluídas. + O fragmento a seguir exclui linhas cujo appid (id do aplicativo) corresponda a "usuário". O método retorna + o número de linhas excluídas. +

+
+
+// Defines selection criteria for the rows you want to delete
+String mSelectionClause = UserDictionary.Words.APP_ID + " LIKE ?";
+String[] mSelectionArgs = {"user"};
+
+// Defines a variable to contain the number of rows deleted
+int mRowsDeleted = 0;
+
+...
+
+// Deletes the words that match the selection criteria
+mRowsDeleted = getContentResolver().delete(
+    UserDictionary.Words.CONTENT_URI,   // the user dictionary content URI
+    mSelectionClause                    // the column to select on
+    mSelectionArgs                      // the value to compare to
+);
+
+

+ Também se deve filtrar a inserção de dados do usuário ao chamar + {@link android.content.ContentResolver#delete ContentResolver.delete()}. Para saber mais sobre + isso, leia a seção Proteção contra inserções mal-intencionadas. +

+ +

Tipos de dados do provedor

+

+ Provedores de conteúdo podem oferecer muitos tipos de dados diferentes. O Provedor de Dicionário do Usuário oferece somente + texto, mas provedores também podem oferecer os seguintes formatos: +

+
    +
  • + número inteiro +
  • +
  • + número inteiro longo (longo) +
  • +
  • + ponto flutuante +
  • +
  • + ponto flutuante longo (duplo) +
  • +
+

+ Outros tipos de dados que provedores usam com frequência são objetos binários largos (BLOB) implementados como uma + matriz de byte de 64 kB. É possível ver os tipos de dados disponíveis consultando + os métodos "get" da classe {@link android.database.Cursor}. +

+

+ O tipo dos dados para cada coluna em um provedor normalmente é listado na documentação do provedor. + Os tipos de dados do Provedor de Dicionário do Usuário são listados na documentação de referência + para sua classe de contrato {@link android.provider.UserDictionary.Words} (classes de contrato são + descritas na seção Classes de contrato). + Também é possível determinar o tipo dos dados chamando {@link android.database.Cursor#getType + Cursor.getType()}. +

+

+ Os provedores também mantêm informações do tipo MIME de dados para cada URI de conteúdo que definem. É possível + usar as informações do tipo MIME para descobrir se o aplicativo pode tratar de dados que + o provedor fornece ou para escolher um tipo de tratamento com base no tipo MIME. Normalmente é necessário ter + o tipo MIME ao trabalhar com um provedor que contenha estruturas + complexas de dados ou arquivos. Por exemplo: a tabela {@link android.provider.ContactsContract.Data} + no Provedor de contatos usa tipos MIME para etiquetar o tipo dos dados de contato armazenados em cada + linha. Para obter o tipo MIME correspondente a uma URI de conteúdo, chame + {@link android.content.ContentResolver#getType ContentResolver.getType()}. +

+

+ A seção Referência de tipo MIME descreve + a sintaxe de tipos MIME padrão e personalizados. +

+ + + +

Formas alternativas de acessar o provedor

+

+ Três formas alternativas de acesso ao provedor são importantes no desenvolvimento do aplicativo: +

+
    +
  • + Acesso em lote: É possível criar um lote de chamadas de acesso com métodos na + classe {@link android.content.ContentProviderOperation} e, em seguida, aplicá-los com + {@link android.content.ContentResolver#applyBatch ContentResolver.applyBatch()}. +
  • +
  • + Consultas assíncronas: Devem-se realizar consultas em um encadeamento separado. Um modo de fazer isso é + usar um objeto {@link android.content.CursorLoader}. Os exemplos + no guia Carregadores demonstram + como fazer isso. +
  • +
  • + Acesso a dados via intenções Embora não seja possível enviar uma intenção + diretamente a um provedor, é possível enviar uma intenção ao aplicativo do provedor, que + normalmente é o recomendável para modificar os dados do provedor. +
  • +
+

+ O acesso em lote e a modificação via intenções são descritos nas seções a seguir. +

+

Acesso em lote

+

+ O acesso em lote a um provedor é útil para inserir uma grande quantidade de linhas ou para inserir + linhas em diversas tabelas na mesma chamada de método ou, em geral, para realizar um conjunto de + operações dentre limites de processo como uma transação (uma operação atômica). +

+

+ Para acessar um provedor em "modo de lote", + cria-se uma matriz de objetos {@link android.content.ContentProviderOperation} e, em seguida, + envia-os a um provedor de conteúdo com + {@link android.content.ContentResolver#applyBatch ContentResolver.applyBatch()}. Confere-se a + autoridade do provedor de conteúdo para esse método em vez de para uma URI de conteúdo específica. + Isso permite que cada objeto {@link android.content.ContentProviderOperation} na matriz trabalhe + com uma tabela diferente. Chamar {@link android.content.ContentResolver#applyBatch + ContentResolver.applyBatch()} retorna uma matriz de resultados. +

+

+ A descrição da classe de contrato {@link android.provider.ContactsContract.RawContacts} + contém um fragmento de código que demonstra a inserção em lote. +O aplicativo de exemplo do Gerente de contato + contém um exemplo de acesso em lote em seu + arquivo de origem ContactAdder.java. +

+ +

Acesso a dados via intenções

+

+ Intenções podem fornecer acesso indireto a um provedor de conteúdo. Basta permitir que o usuário acesse + dados em um provedor mesmo se o aplicativo não tiver permissões de acesso, tanto + retornando uma intenção de resultado de um aplicativo que tem permissões quanto ativando + um aplicativo que tem permissões e permitindo que o usuário trabalhe nele. +

+

Obtenção de acesso com permissões temporárias

+

+ É possível acessar dados em um provedor de conteúdo, mesmo sem s permissões + de acesso adequadas. Basta enviar uma intenção a um aplicativo que tenha as permissões e + receber de volta uma intenção de resultado contendo permissões da "URI". + Essas são permissões para uma URI de conteúdo específica que duram enquanto durar a atividade + que as recebeu. O aplicativo que tem permissões permanentes concedem permissões + temporárias ativando um sinalizador na intenção de resultado: +

+
    +
  • + Permissão de leitura: + {@link android.content.Intent#FLAG_GRANT_READ_URI_PERMISSION} +
  • +
  • + Permissão de gravação: + {@link android.content.Intent#FLAG_GRANT_WRITE_URI_PERMISSION} +
  • +
+

+ Observação: esses sinalizadores não fornecem acesso geral de leitura ou gravação ao provedor + cuja autoridade esteja contida na URI de conteúdo. O acesso destina-se somente à URI. +

+

+ Um provedor define permissões de URI para URIs de conteúdo no manifesto usando o atributo +android:grantUriPermission + do elemento +<provider>, + bem como o elemento filho +<grant-uri-permission> + do elemento +<provider> +. O mecanismo das permissões de URI é explicado com mais detalhes + no guia Permissões e segurança, + na seção "Permissões da URI". +

+

+ Por exemplo: é possível recuperar dados de um contato no Provedor de Contatos, mesmo sem + a permissão {@link android.Manifest.permission#READ_CONTACTS}. Você pode desejar fazer + isso em um aplicativo que envie e-mails de parabenização a um contato no aniversário dele. Em vez de + solicitar {@link android.Manifest.permission#READ_CONTACTS}, que fornece acesso a todos + os contatos do usuário e suas informações, é preferível deixar que o usuário controle quais + contatos serão usados pelo seu aplicativo. Para isso, use o processo a seguir: +

+
    +
  1. + O aplicativo envia uma intenção contendo a ação + {@link android.content.Intent#ACTION_PICK} e o tipo MIME + {@link android.provider.ContactsContract.RawContacts#CONTENT_ITEM_TYPE} dos "contatos" usando + o método {@link android.app.Activity#startActivityForResult + startActivityForResult()}. +
  2. +
  3. + Como essa intenção corresponde ao filtro de intenções da + atividade de "seleção" do aplicativo Pessoas, a atividade ficará em primeiro plano. +
  4. +
  5. + Na atividade de seleção, o usuário seleciona + um contato para atualizar. Quando isso acontece, a atividade de seleção chama + {@link android.app.Activity#setResult setResult(resultcode, intent)} + para configurar uma intenção para retornar ao aplicativo. A intenção contém a URI de conteúdo + do contato que o usuário selecionou e os sinalizadores "extras" + {@link android.content.Intent#FLAG_GRANT_READ_URI_PERMISSION}. Esses sinalizadores concedem + permissão de URI para que o aplicativo leia dados do contato apontados pela + URI de conteúdo. A atividade de seleção, em seguida, chama {@link android.app.Activity#finish()} para + retornar o controle para o aplicativo. +
  6. +
  7. + A atividade volta ao primeiro plano e o sistema chama o + método {@link android.app.Activity#onActivityResult onActivityResult()} + da sua atividade. Esse método recebe a intenção de resultado criada pela atividade de seleção + no aplicativo Pessoas. +
  8. +
  9. + Com a URI de conteúdo da intenção de resultado, é possível ler os dados do contato + a partir do Provedor de Contatos, mesmo que não tenha solicitado permissão permanente de acesso a leitura + para o provedor no manifesto. Pode-se, então, pode obter as informações de data de nascimento do contato + ou seu endereço de e-mail e, assim, enviar um e-mail de parabenização. +
  10. +
+

Uso de outro aplicativo

+

+ Um modo simples que permite ao usuário modificar dados para os quais você não tem permissões de acesso é + ativar um aplicativo que tenha permissões e deixar o usuário fazer o resto. +

+

+ Por exemplo: o aplicativo Agenda aceita uma + intenção {@link android.content.Intent#ACTION_INSERT} que permite a ativação da + IU de inserção do aplicativo. Você pode passar dados "extras" nessa intenção que o aplicativo + usa para "pré-preencher" a IU. como os eventos recorrentes têm sintaxe complexa, o modo + recomendado de inserir eventos no Provedor de Agenda é ativar o aplicativo Agenda com um + {@link android.content.Intent#ACTION_INSERT} e, em seguida, deixar o usuário inserir o evento. +

+ +

Classes de contrato

+

+ As classes de contrato definem constantes que ajudam os aplicativos a trabalhar com URIs de conteúdo, nomes + de colunas, ações de intenções e outros recursos de um provedor de conteúdo. Elas não estão + automaticamente inclusas em um provedor; o desenvolvedor do provedor deve defini-las e + disponibilizá-las a outros desenvolvedores. Muitos dos provedores incluídos na plataforma + do Android têm classes de contrato correspondentes no pacote {@link android.provider}. +

+

+ Por exemplo: o Provedor de Dicionário do Usuário tem uma classe de contrato + {@link android.provider.UserDictionary} que contém constantes de URI de conteúdo e de nome de coluna. + A URI de conteúdo da tabela de "palavras" é definida na constante + {@link android.provider.UserDictionary.Words#CONTENT_URI UserDictionary.Words.CONTENT_URI}. + A classe {@link android.provider.UserDictionary.Words} também contém constantes de nome de coluna + que são usadas nos fragmentos de exemplo neste guia. Por exemplo, uma projeção de consulta pode ser + definida como: +

+
+String[] mProjection =
+{
+    UserDictionary.Words._ID,
+    UserDictionary.Words.WORD,
+    UserDictionary.Words.LOCALE
+};
+
+

+ Outra classe de contrato é {@link android.provider.ContactsContract} para o Provedor de Contatos. + A documentação de referência desta classe contém fragmentos de código de exemplo. Uma das suas + subclasses, {@link android.provider.ContactsContract.Intents.Insert}, é uma classe + de contrato que contém constantes para intenções e dados da intenção. +

+ + + +

Referência do tipo MIME

+

+ Provedores de conteúdo podem retornar tipos MIME de mídia, strings de tipos MIME personalizados ou ambos. +

+

+ Tipos MIME têm o formato +

+
+type/subtype
+
+

+ Por exemplo: o tipo MIME text/html conhecido tem o tipo text + e o subtipo html. Se o provedor retornar esse tipo para uma URI, + uma consulta usando essa URI retornará um texto que contém tags HTML. +

+

+ Strings de tipo MIME personalizado, também chamadas de tipos MIME "específicos do fornecedor" (vendor-specific), têm mais + valores de tipo e subtipo complexos. O valor de tipo é sempre +

+
+vnd.android.cursor.dir
+
+

+ para diversas linhas ou +

+
+vnd.android.cursor.item
+
+

+ para uma única linha. +

+

+ O subtipo é específico do provedor. Os provedores embutidos do Android normalmente têm um subtipo + simples. Por exemplo, quando o aplicativo de contatos cria uma linha para um número de telefone, + ele configura o seguinte tipo MIME na linha: +

+
+vnd.android.cursor.item/phone_v2
+
+

+ Observe que o valor do subtipo é simplesmente phone_v2. +

+

+ Outros desenvolvedores de provedor podem criar os próprios padrões de subtipos com base + na autoridade do provedor e nos nomes da tabela. Por exemplo: considere um provedor que contenha horários de trens. + A autoridade do provedor é com.example.trains e ele contém as tabelas + Linha1, Linha2 e Linha3. Em resposta à URI de conteúdo +

+

+

+content://com.example.trains/Line1
+
+

+ para a tabela Linha1, o provedor retorna o tipo MIME +

+
+vnd.android.cursor.dir/vnd.example.line1
+
+

+ Em resposta à URI de conteúdo +

+
+content://com.example.trains/Line2/5
+
+

+ da linha 5 na tabela Linha2, o provedor retorna o tipo MIME +

+
+vnd.android.cursor.item/vnd.example.line2
+
+

+ A maioria dos provedores define constantes de classe de contrato para os tipos MIME que usam. + A classe de contrato {@link android.provider.ContactsContract.RawContacts} do Provedor de Contatos, + por exemplo, define a constante + {@link android.provider.ContactsContract.RawContacts#CONTENT_ITEM_TYPE} para o tipo MIME + de uma linha exclusiva do contato bruto. +

+

+ URIs de conteúdo de linhas exclusivas são descritas na seção + URIs de conteúdo. +

diff --git a/docs/html-intl/intl/pt-br/guide/topics/providers/content-provider-creating.jd b/docs/html-intl/intl/pt-br/guide/topics/providers/content-provider-creating.jd new file mode 100644 index 0000000000000000000000000000000000000000..11ad4c3b1cca845e6d513919fd3a41039eafa615 --- /dev/null +++ b/docs/html-intl/intl/pt-br/guide/topics/providers/content-provider-creating.jd @@ -0,0 +1,1214 @@ +page.title=Criação de um Provedor de Conteúdo +@jd:body + + + +

+ O provedor de conteúdo gerencia o acesso a um repositório central de dados. Implementa-se + um provedor como uma ou mais classes em um aplicativo do Android, em conjunto com elementos + no arquivo de manifesto. Uma das classes implementa uma subclasse + {@link android.content.ContentProvider}, que é a interface entre o provedor + e outros aplicativos. Embora provedores de conteúdo se destinem a disponibilizar dados a outros + aplicativos, naturalmente é possível ter atividades no aplicativo que permitam ao usuário + consultar e modificar os dados gerenciados pelo provedor. +

+

+ O restante deste tópico é uma lista básica de etapas para a criação de um provedor de conteúdo e uma lista + de APIs para usar. +

+ + + +

Antes de começar a criar

+

+ Antes de começar a criar um provedor, faça o seguinte: +

+
    +
  1. + Avalie se você precisa de um provedor de conteúdo. É necessário criar um provedor + de conteúdo para fornecer um ou mais dos recursos a seguir: +
      +
    • Oferecer dados ou arquivos complexos a outros aplicativos.
    • +
    • Permitir que usuários copiem dados complexos do seu aplicativo para outros aplicativos.
    • +
    • Fornecer sugestões de pesquisa personalizada usando a estrutura de pesquisa.
    • +
    +

    + Não é necessário um provedor para usar um banco de dados SQLite se o uso for inteiramente dentro + do próprio aplicativo. +

    +
  2. +
  3. + Se você ainda não tiver feito isso, leia o tópico + + Preceitos do provedor de conteúdo para saber mais sobre provedores. +
  4. +
+

+ A seguir, siga estas etapas para criar seu provedor: +

+
    +
  1. + Projete o armazenamento bruto dos dados. Os provedores de conteúdo oferecem dados de duas maneiras: +
    +
    + Dados de arquivo +
    +
    + Dados que normalmente transitam em arquivos, como + fotos, áudio ou vídeos. Armazene os arquivos no espaço privado + do seu aplicativo. Em resposta a uma solicitação de um arquivo por outro aplicativo, + o provedor pode oferecer um identificador para o arquivo. +
    +
    + Dados "estruturados" +
    +
    + Dados que normalmente transitam em um banco de dados, uma matriz ou estrutura similar. + Armazene os dados de forma compatível com tabelas de linhas e colunas. Cada linha + representa uma entidade, como uma pessoa ou um item em um inventário. Cada coluna representa + alguns dados da entidade, como o nome da pessoa ou o preço do item. Um modo comum de + armazenar esse tipo de dados é em um banco de dados SQLite, mas é possível usar qualquer tipo + de armazenamento persistente. Para saber mais sobre os tipos de armazenamento disponíveis + no sistema Android, consulte a seção +Projeto de armazenamento de dados. +
    +
    +
  2. +
  3. + Defina uma implementação sólida da classe {@link android.content.ContentProvider} + e seus métodos obrigatórios. Essa classe é a interface entre seus dados e o restante + do sistema Android. Para obter mais informações sobre essa classe, consulte a seção + Implementação da classe ContentProvider. +
  4. +
  5. + Defina a string de autoridade do provedor, suas URIs de conteúdo e seus nomes de coluna. Se você deseja que + o aplicativo do provedor trate de intenções, defina também ações de intenção, dados extras + e sinalizadores. Também defina as permissões necessárias para aplicativos que queiram + acessar seus dados. Deve-se considerar definir todos esses valores como constantes + em uma classe de contrato separada; posteriormente, será possível expor essa classe a outros desenvolvedores. Para + obter mais informações sobre URIs de conteúdo, consulte + a seção Projeto de URIs de conteúdo. + Para obter mais informações sobre intenções, consulte + a seção Intenções e acesso a dados. +
  6. +
  7. + Adicione outras partes opcionais, como dados de exemplo ou uma implementação + de {@link android.content.AbstractThreadedSyncAdapter} que possa sincronizar dados entre + o provedor e os dados com base em nuvem. +
  8. +
+ + + +

Projeto de armazenamento de dados

+

+ Os provedores de conteúdo são a interface para dados salvos em um formato estruturado. Antes de criar + a interface, é preciso decidir como armazenar os dados. É possível armazená-los da forma que + quiser e, em seguida, projetar a interface para ler e gravar os dados conforme o necessário. +

+

+ Essas são algumas das tecnologias de armazenamento de dados disponíveis no Android: +

+
    +
  • + O sistema Android contém uma API de banco de dados SQLite que os provedores do Android usam + para armazenar dados orientados a tabela. + A classe {@link android.database.sqlite.SQLiteOpenHelper} ajuda a criar bancos de dados + e a classe {@link android.database.sqlite.SQLiteDatabase} é a classe de base para acessar + banco de dados. +

    + Lembre-se de que não é necessário usar nenhum banco de dados para implementar o repositório. Um provedor + aparece externamente como um conjunto de tabelas, semelhante a um banco de dados relacional, mas isso não + é um requisito para a implementação interna do provedor. +

    +
  • +
  • + Para armazenar dados de arquivos, o Android tem diversas APIs orientadas a arquivo. + Para saber mais sobre armazenamento de arquivos, leia o tópico + Armazenamento de dados. Se você estiver + projetando um provedor que oferece dados relacionados a mídia como música ou vídeos, é possível + ter um provedor que combine dados de tabela e de arquivos. +
  • +
  • + Para trabalhar com dados com base em rede, use classes em {@link java.net} + e em {@link android.net}. É possível, também, sincronizar dados com base em rede com uma armazenagem + local de dados, como um banco de dados e, em seguida, fornecer os dados na forma de tabelas ou arquivos. + O exemplo de aplicativo + Exemplo de adaptador de sincronização demonstra o tipo de sincronização. +
  • +
+

+ Considerações para projetar dados +

+

+ A seguir há algumas dicas para projetar a estrutura de dados do seu provedor: +

+
    +
  • + Os dados de tabela sempre devem ter uma coluna de "chave principal" que o provedor mantém + como um valor numérico exclusivo para cada linha. É possível usar esse valor para vincular a linha a linhas + relacionadas em outras tabelas (usando-o como uma "chave externa"). Embora seja possível usar qualquer nome + para essa coluna, é melhor usar {@link android.provider.BaseColumns#_ID BaseColumns._ID} + porque a vinculação dos resultados de uma consulta de provedor com uma + {@link android.widget.ListView} requer que uma das colunas tenha o nome + _ID. +
  • +
  • + Se você deseja fornecer imagens bitmap ou outras partes muito grandes de dados orientados a arquivo, armazene + os dados em um arquivo e, em seguida, forneça-o indiretamente em vez de armazená-lo diretamente em uma + tabela. Ao fazer isso, será necessário informar aos usuários do provedor que eles precisam usar + um método de arquivo {@link android.content.ContentResolver} para acessar os dados. +
  • +
  • + Use objeto grande binário (BLOB) como tipo de dados para armazenar dados que variam em tamanho ou que tenham + estrutura variável. Por exemplo: é possível usar uma coluna de BLOB para armazenar + um buffer de protocolo ou + uma estrutura JSON. +

    + Pode-se usar um BLOB para implementar uma tabela independente de esquema. Nesse + tipo de tabela, define-se uma coluna de chave principal, uma coluna de tipo MIME e uma + ou mais colunas genéricas como BLOB. O significado dos dados nas colunas BLOB é indicado + pelo valor na coluna de tipo MIME. Isso permite o armazenamento de diferentes tipos de linha + na mesma tabela. A tabela de "dados" {@link android.provider.ContactsContract.Data} + do Provedor de contatos é um exemplo de uma tabela + independente de esquema. +

    +
  • +
+ +

Projeto de URIs de conteúdo

+

+ URI de conteúdo é uma URI que identifica dados em um provedor. URIs de conteúdo + contêm o nome simbólico de todo o provedor (sua autoridade) e um + nome que aponta para uma tabela ou arquivo (um caminho). Parte do ID opcional aponta para + uma linha individual em uma tabela. Cada método de acesso aos dados + {@link android.content.ContentProvider} de tem uma URI de conteúdo como um argumento. Isso permite + determinar a tabela, a linha ou o arquivo a acessar. +

+

+ Os conceitos básicos de URIs de conteúdo são escritos no tópico + + Preceitos do provedor de conteúdo. +

+

Projeto de uma autoridade

+

+ Os provedores normalmente têm uma única autoridade, que serve como seu nome interno do Android. Para + evitar conflitos com outros provedores, deve-se usar a propriedade de domínio da internet (ao contrário) + como a base da autoridade do provedor. Em virtude de esta recomendação ser verdadeira para nomes + de pacote do Android, é possível definir a autoridade do provedor como uma extensão do nome + do pacote que contém o provedor. Por exemplo: se o nome do pacote do Android for + com.example.<appname>, deve-se atribuir ao provedor + a autoridade com.example.<appname>.provider. +

+

Projeto de uma estrutura de caminho

+

+ Desenvolvedores normalmente criam URIs de conteúdo a partir da autoridade anexando caminhos que apontam para + tabelas individuais. Por exemplo: se você tiver duas tabelas (tabela1 e + tabela2), combine a autoridade do exemplo anterior para produzir + as URIs de conteúdo + com.example.<appname>.provider/table1 e + com.example.<appname>.provider/table2. Caminhos não + se limitam a um único segmento e não precisa ter uma tabela para cada nível do caminho. +

+

Identificação de IDs de URIs de conteúdo

+

+ Por convenção, provedores fornecem acesso a uma linha exclusiva em uma tabela aceitando uma URI de conteúdo + com um valor de ID para a linha no fim da URI. Também por convenção, provedores combinam + o valor do ID com a coluna _ID da tabela e realizam o acesso solicitado + na linha correspondente. +

+

+ Essa convenção facilita a padronização comum do projeto para aplicativos que acessam um provedor. O aplicativo + realiza uma consulta no provedor e exibe o {@link android.database.Cursor} + resultante em uma {@link android.widget.ListView} usando um {@link android.widget.CursorAdapter}. + A definição de {@link android.widget.CursorAdapter} requer que uma das colunas + no {@link android.database.Cursor} seja _ID +

+

+ Em seguida, o usuário seleciona uma das linhas exibidas na IU para visualizar ou modificar + os dados. O aplicativo tem a linha correspondente do {@link android.database.Cursor} apoiando + a {@link android.widget.ListView}, obtém o valor _ID para essa linha, anexa-o + à URI de conteúdo e envia a solicitação de acesso ao provedor. O provedor, então, pode realizar + a consulta ou modificação na exata linha que o usuário selecionou. +

+

Padrões de URI de conteúdo

+

+ Para ajudar a escolher que ação tomar para uma URI de conteúdo recebida, a API do provedor contém + a classe de conveniência {@link android.content.UriMatcher}, que mapeia "padrões" de URI de conteúdo + como valores de número inteiro. É possível usar os valores de número inteiro em uma declaração switch que + escolha a ação desejada para a URI de conteúdo ou URIs que atendam ao padrão determinado. +

+

+ Um padrão de URI de conteúdo corresponde a URIs de conteúdo que usam caracteres especiais: +

+
    +
  • + *: Corresponde a uma string de qualquer caractere válido de qualquer comprimento. +
  • +
  • + #: Corresponde a uma string de caracteres numéricos de qualquer comprimento. +
  • +
+

+ Como um exemplo de projeto e codificação da identificação de URIs de conteúdo, considere um provedor + com a autoridade com.example.app.provider que reconheça as URIs de conteúdo + que apontam para tabelas: +

+
    +
  • + content://com.example.app.provider/table1: Uma tabela chamada table1. +
  • +
  • + content://com.example.app.provider/table2/dataset1: Uma tabela chamada + dataset1. +
  • +
  • + content://com.example.app.provider/table2/dataset2: Uma tabela chamada + dataset1. +
  • +
  • + content://com.example.app.provider/table3: Uma tabela chamada table3. +
  • +
+

+ O provedor também reconhece essas URIs de conteúdo se elas tiverem um ID de linha anexado, como + content://com.example.app.provider/table3/1 para a linha identificada por + 1 em table3. +

+

+ Os seguintes padrões de URI de conteúdo seriam possíveis: +

+
+
+ content://com.example.app.provider/* +
+
+ Corresponde a qualquer URI de conteúdo no provedor. +
+
+ content://com.example.app.provider/table2/*: +
+
+ Corresponde a uma URI de conteúdo das tabelas dataset1 + e dataset2, mas não a URIs de conteúdo de table1 + nem table3. +
+
+ content://com.example.app.provider/table3/#: Corresponde a uma URI de conteúdo + para linhas exclusivas em table3, como + content://com.example.app.provider/table3/6 da linha identificada por + 6. +
+
+

+ O fragmento de código a seguir mostra como funcionam os métodos em {@link android.content.UriMatcher}. + Esse código identifica URIs para toda uma tabela, diferentemente das URIs + para uma linha exclusiva, usando o padrão de URI de conteúdo + content://<authority>/<path> para tabelas e + content://<authority>/<path>/<id> para linhas exclusivas. +

+

+ O método {@link android.content.UriMatcher#addURI(String, String, int) addURI()} mapeia + uma autoridade e um caminho como um valor de número inteiro. O método {@link android.content.UriMatcher#match(Uri) + match()} retorna o valor de número inteiro para a URI. Uma declaração switch + escolhe entre consultar a tabela inteira e consultar um único registro: +

+
+public class ExampleProvider extends ContentProvider {
+...
+    // Creates a UriMatcher object.
+    private static final UriMatcher sUriMatcher;
+...
+    /*
+     * The calls to addURI() go here, for all of the content URI patterns that the provider
+     * should recognize. 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
+     */
+    sUriMatcher.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.
+     */
+    sUriMatcher.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 (sUriMatcher.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 is not recognized, you should do some error handling here.
+        }
+        // call the code to actually do the query
+    }
+
+

+ Outra classe, {@link android.content.ContentUris}, fornece métodos convenientes para trabalhar + com a parte id das URIs de conteúdo. As classes {@link android.net.Uri} + e {@link android.net.Uri.Builder} contêm métodos convenientes para analisar objetos + {@link android.net.Uri} existentes e criar novos. +

+ + +

Implementação da classe ContentProvider

+

+ A instância {@link android.content.ContentProvider} gerencia o acesso + a um conjunto de dados estruturado lidando com solicitações de outros aplicativos. Todas as formas + de acesso ocasionalmente chamam {@link android.content.ContentResolver} que, em seguida, chama um método + concreto de {@link android.content.ContentProvider} para obter acesso. +

+

Métodos obrigatórios

+

+ A classe abstrata {@link android.content.ContentProvider} define seis métodos abstratos + que devem ser implementados como parte das subclasses concretas. Todos esses métodos, exceto + {@link android.content.ContentProvider#onCreate() onCreate()}, são chamados por um aplicativo cliente + que está tentando acessar seu provedor de conteúdo: +

+
+
+ {@link android.content.ContentProvider#query(Uri, String[], String, String[], String) + query()} +
+
+ Recupere dados do provedor. Use os argumentos para selecionar a tabela + para consultar as linhas e colunas a retornar e a ordem de classificação do resultado. + Retorne os dados como um objeto {@link android.database.Cursor}. +
+
+ {@link android.content.ContentProvider#insert(Uri, ContentValues) insert()} +
+
+ Insira uma nova linha no provedor. Use os argumentos para selecionar + a tabela de destino e obter os valores de coluna a usar. Retorne uma URI de conteúdo para + a linha recentemente inserida. +
+
+ {@link android.content.ContentProvider#update(Uri, ContentValues, String, String[]) + update()} +
+
+ Atualize as linhas existentes no provedor. Use os argumentos para selecionar a tabela e linhas + para atualizar e obter os valores de coluna atualizados. Retorne o número de linhas atualizadas. +
+
+ {@link android.content.ContentProvider#delete(Uri, String, String[]) delete()} +
+
+ Exclua linhas do provedor. Use os argumentos para selecionar a tabela e as linhas + a excluir. Retorne o número de linhas excluídas. +
+
+ {@link android.content.ContentProvider#getType(Uri) getType()} +
+
+ Retorne o tipo MIME correspondente a uma URI de conteúdo. Esse método é descrito com mais + detalhes na seção Implementação de tipos MIME do Provedor de Conteúdo. +
+
+ {@link android.content.ContentProvider#onCreate() onCreate()} +
+
+ Inicializar o provedor. O sistema Android chama esse método imediatamente + após criar o provedor. Observe que o provedor não é criado até que + um objeto {@link android.content.ContentResolver} tente acessá-lo. +
+
+

+ Observe que esses métodos têm a mesma assinatura dos métodos + {@link android.content.ContentResolver} de mesmo nome. +

+

+ A implementação desses métodos deve levar em conta o seguinte: +

+
    +
  • + Todos esses métodos, exceto {@link android.content.ContentProvider#onCreate() onCreate()}, + podem ser chamados por diversos encadeamentos simultaneamente, então devem ter encadeamento seguro. Para saber + mais sobre encadeamentos múltiplos, consulte o tópico + + Processos e encadeamentos. +
  • +
  • + Evite realizar longas operações em {@link android.content.ContentProvider#onCreate() + onCreate()}. Adie tarefas de inicialização até que sejam realmente necessárias. + A seção Implementação do método onCreate() + aborda o assunto mais detalhadamente. +
  • +
  • + Embora seja preciso implementar esses métodos, o código não precisa fazer nada além + de retornar o tipo de dados esperado. Por exemplo: você pode querer evitar que outros aplicativos + insiram dados em algumas tabelas. Para tanto, pode-se ignorar a chamada de + {@link android.content.ContentProvider#insert(Uri, ContentValues) insert()} e retornar + 0. +
  • +
+

Implementação do método query()

+

+ + O método {@link android.content.ContentProvider#query(Uri, String[], String, String[], String) + ContentProvider.query()} precisa retornar um objeto {@link android.database.Cursor} ou, se + falhar, gerar uma {@link java.lang.Exception}. Se você estiver usando um banco de dados SQLite + como armazenamento de dados, pode simplesmente retornar o {@link android.database.Cursor} retornado + por um dos métodos query() da classe {@link android.database.sqlite.SQLiteDatabase}. + Se a consulta não corresponder a nenhuma linha, deve-se retornar uma instância {@link android.database.Cursor} +cujo método {@link android.database.Cursor#getCount()} retorne 0. + Deve-se retornar null somente se ocorrer um erro interno durante o processo de consulta. +

+

+ Se você não estiver usando um banco de dados SQLite como modo de armazenamento de dados, use uma das subclasses concretas + de {@link android.database.Cursor}. Por exemplo, a classe {@link android.database.MatrixCursor} + implementa um cursor em que cada linha é uma matriz de {@link java.lang.Object}. Com essa classe, + use {@link android.database.MatrixCursor#addRow(Object[]) addRow()} para adicionar uma nova linha. +

+

+ Lembre-se de que o sistema Android deve ser capaz de se comunicar com {@link java.lang.Exception} + entre limites de processo. O Android pode fazer isso para as exceções a seguir, que podem ser úteis + para tratar de erros de consulta: +

+
    +
  • + {@link java.lang.IllegalArgumentException} (é possível escolher lançar isso se o provedor + receber uma URI de conteúdo inválida) +
  • +
  • + {@link java.lang.NullPointerException} +
  • +
+

Implementação do método insert()

+

+ O método {@link android.content.ContentProvider#insert(Uri, ContentValues) insert()} adiciona + uma nova linha à tabela apropriada usando os valores no argumento + {@link android.content.ContentValues}. Se um nome de coluna não estiver no argumento {@link android.content.ContentValues}, + talvez seja necessário fornecer um valor padrão para ele tanto no código do provedor quanto no esquema + do banco de dados. +

+

+ Esse método deve retornar a URI de conteúdo da nova linha. Para construir isso, anexe o valor _ID + da nova linha (ou outra chave principal) à URI de conteúdo da tabela usando + {@link android.content.ContentUris#withAppendedId(Uri, long) withAppendedId()}. +

+

Implementação do método delete()

+

+ O método {@link android.content.ContentProvider#delete(Uri, String, String[]) delete()} + não precisa excluir linhas fisicamente do armazenamento de dados. Se você estiver usando um adaptador de sincronização + com o provedor, considere marcar uma linha excluída + com um sinalizador "excluir" em vez de removê-la totalmente. O adaptador de sincronização pode + verificar se há linhas excluídas e removê-las do servidor antes de excluí-las do provedor. +

+

Implementação do método update()

+

+ O método {@link android.content.ContentProvider#update(Uri, ContentValues, String, String[]) + update()} usa o mesmo argumento {@link android.content.ContentValues} usado por + {@link android.content.ContentProvider#insert(Uri, ContentValues) insert()}, e os + mesmos argumentos selection e selectionArgs usados por + {@link android.content.ContentProvider#delete(Uri, String, String[]) delete()} e + {@link android.content.ContentProvider#query(Uri, String[], String, String[], String) + ContentProvider.query()}. Isso deve permitir a reutilização do código entre esses métodos. +

+

Implementação do método onCreate()

+

+ O sistema Android chama {@link android.content.ContentProvider#onCreate() + onCreate()} quando inicia o provedor. Nesse método, devem-se realizar apenas tarefas de inicialização + de execução rápida e adiar a criação do banco de dados e o carregamento dos dados até que o provedor + receba efetivamente uma solicitação de acesso aos dados. Se houver tarefas longas + em {@link android.content.ContentProvider#onCreate() onCreate()}, a inicialização do provedor + ficará lenta. Consequentemente, isso diminuirá a rapidez da resposta do provedor + a outros aplicativos. +

+

+ Por exemplo: se você estiver usando um banco de dados SQLite, é possível criar + um novo objeto {@link android.database.sqlite.SQLiteOpenHelper} + em {@link android.content.ContentProvider#onCreate() ContentProvider.onCreate()} + e, em seguida, criar as tabelas SQL na primeira vez em que o banco de dados for aberto. Para facilitar isso, + a primeira vez que chamar {@link android.database.sqlite.SQLiteOpenHelper#getWritableDatabase + getWritableDatabase()}, ele automaticamente chamará + o método {@link android.database.sqlite.SQLiteOpenHelper#onCreate(SQLiteDatabase) + SQLiteOpenHelper.onCreate()} +

+

+ Os dois fragmentos a seguir demonstram a interação + entre {@link android.content.ContentProvider#onCreate() ContentProvider.onCreate()} + e {@link android.database.sqlite.SQLiteOpenHelper#onCreate(SQLiteDatabase) + SQLiteOpenHelper.onCreate()}. O primeiro fragmento é a implementação de + {@link android.content.ContentProvider#onCreate() ContentProvider.onCreate()}. +

+
+public class ExampleProvider extends ContentProvider
+
+    /*
+     * Defines a handle to the database helper object. The MainDatabaseHelper class is defined
+     * in a following snippet.
+     */
+    private MainDatabaseHelper mOpenHelper;
+
+    // Defines the database name
+    private static final String DBNAME = "mydb";
+
+    // Holds the database object
+    private SQLiteDatabase db;
+
+    public boolean onCreate() {
+
+        /*
+         * Creates a new helper object. This method always returns quickly.
+         * Notice that the database itself isn't created or opened
+         * until SQLiteOpenHelper.getWritableDatabase is called
+         */
+        mOpenHelper = new MainDatabaseHelper(
+            getContext(),        // the application context
+            DBNAME,              // the name of the database)
+            null,                // uses the default SQLite cursor
+            1                    // the version number
+        );
+
+        return true;
+    }
+
+    ...
+
+    // Implements the provider's insert method
+    public Cursor insert(Uri uri, ContentValues values) {
+        // Insert code here to determine which table to open, handle error-checking, and so forth
+
+        ...
+
+        /*
+         * Gets a writeable database. This will trigger its creation if it doesn't already exist.
+         *
+         */
+        db = mOpenHelper.getWritableDatabase();
+    }
+}
+
+

+ O próximo fragmento é a implementação de + {@link android.database.sqlite.SQLiteOpenHelper#onCreate(SQLiteDatabase) + SQLiteOpenHelper.onCreate()}, inclusive uma classe auxiliar: +

+
+...
+// A string that defines the SQL statement for creating a table
+private static final String SQL_CREATE_MAIN = "CREATE TABLE " +
+    "main " +                       // Table's name
+    "(" +                           // The columns in the table
+    " _ID INTEGER PRIMARY KEY, " +
+    " WORD TEXT"
+    " FREQUENCY INTEGER " +
+    " LOCALE TEXT )";
+...
+/**
+ * Helper class that actually creates and manages the provider's underlying data repository.
+ */
+protected static final class MainDatabaseHelper extends SQLiteOpenHelper {
+
+    /*
+     * Instantiates an open helper for the provider's SQLite data repository
+     * Do not do database creation and upgrade here.
+     */
+    MainDatabaseHelper(Context context) {
+        super(context, DBNAME, null, 1);
+    }
+
+    /*
+     * Creates the data repository. This is called when the provider attempts to open the
+     * repository and SQLite reports that it doesn't exist.
+     */
+    public void onCreate(SQLiteDatabase db) {
+
+        // Creates the main table
+        db.execSQL(SQL_CREATE_MAIN);
+    }
+}
+
+ + + +

Implementação de tipos MIME de ContentProvider

+

+ A classe {@link android.content.ContentProvider} tem dois métodos para retornar tipos MIME: +

+
+
+ {@link android.content.ContentProvider#getType(Uri) getType()} +
+
+ Um dos métodos obrigatórios que devem ser implementados em qualquer provedor. +
+
+ {@link android.content.ContentProvider#getStreamTypes(Uri, String) getStreamTypes()} +
+
+ Um método que deve ser implementado se o provedor fornecer arquivos. +
+
+

Tipos MIME para tabelas

+

+ O método {@link android.content.ContentProvider#getType(Uri) getType()} retorna + uma {@link java.lang.String} em formato MIME que descreve o tipo de dados retornado pelo argumento + da URI de conteúdo. O argumento {@link android.net.Uri} pode ser um padrão em vez de uma URI específica; + nesse caso, deve-se retornar o tipo de dados associado a URIs de conteúdo que correspondam + ao padrão. +

+

+ Para tipos de dados comuns como texto, HTML ou JPEG, + {@link android.content.ContentProvider#getType(Uri) getType()} deve retornar o tipo MIME + padrão daqueles dados. Há uma lista completa desse tipos de padrão + no site de + Tipos de mídia MIME IANA. +

+

+ Para obter URIs de conteúdo que apontam para uma linha ou linhas de dados de tabela, + {@link android.content.ContentProvider#getType(Uri) getType()} deve retornar + um tipo MIME no formato MIME específico do fornecedor do Android: +

+
    +
  • + Parte do tipo: vnd +
  • +
  • + Parte do subtipo: +
      +
    • + Se um padrão de URI se destinar a uma única linha: android.cursor.item/ +
    • +
    • + Se um padrão de URI se destinar a mais de uma linha: android.cursor.dir/ +
    • +
    +
  • +
  • + Parte específica do provedor: vnd.<name>.<type> +

    + Fornecem-se <name> e o <type>. + O valor <name> deve ser globalmente exclusivo + e o <type> deve ser exclusivo para o padrão de URI + correspondente. Uma boa escolha para <name> é o nome da empresa + ou alguma parte do nome do pacote do Android do seu aplicativo. Uma boa escolha para + <type> é uma string que identifique a tabela associada + à URI. +

    + +
  • +
+

+ Por exemplo: se a autoridade de um provedor + for com.example.app.provider e ele expuser uma tabela chamada + table1, o tipo MIME de diversas linhas em table1 será: +

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

+ Para uma única linha de table1, o tipo MIME será: +

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

Tipos MIME para arquivos

+

+ Se o provedor oferecer arquivos, implemente + {@link android.content.ContentProvider#getStreamTypes(Uri, String) getStreamTypes()}. + O método retorna uma matriz {@link java.lang.String} de tipos MIME para os arquivos que o provedor + pode retornar de uma dada URI de conteúdo. É preciso filtrar os tipos MIME oferecidos pelo argumento do filtro + de tipo MIME para retornar somente os tipos MIME que o cliente quer tratar. +

+

+ Por exemplo: considere um provedor que ofereça imagens de foto como arquivos em formatos .jpg, + .gif e .png. + Se um aplicativo chama {@link android.content.ContentResolver#getStreamTypes(Uri, String) + ContentResolver.getStreamTypes()} com a string de filtro image/* (algo que + seja uma "imagem"), + o método {@link android.content.ContentProvider#getStreamTypes(Uri, String) + ContentProvider.getStreamTypes()} deve retornar a matriz: +

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

+ Se o aplicativo estiver interessado apenas em arquivos .jpg, ele pode chamar + {@link android.content.ContentResolver#getStreamTypes(Uri, String) + ContentResolver.getStreamTypes()} com a string de filtro *\/jpeg + e {@link android.content.ContentProvider#getStreamTypes(Uri, String) + ContentProvider.getStreamTypes()} deve retornar: +

+{"image/jpeg"}
+
+

+ Se o provedor não oferecer nenhum tipo MIME solicitado na string de filtro, + {@link android.content.ContentProvider#getStreamTypes(Uri, String) getStreamTypes()} + deve retornar null. +

+ + + +

Implementação de uma classe de contrato

+

+ Uma classe de contrato é uma classe public final que contém definições de constantes para + os nomes de coluna das URIs, dos tipos MIME e de outros metadados que pertencem ao provedor. A classe + estabelece um contrato entre o provedor e outros aplicativos, garantindo que o provedor + possa ser corretamente acessado mesmo se houver mudanças nos valores atuais de URIs, nomes de coluna + etc. +

+

+ As classes de contrato também ajudam os desenvolvedores porque normalmente suas constantes têm nomes mnemônicos e, + por isso, os desenvolvedores têm menos chance de usar valores incorretos para nomes de coluna ou URIs. Já que é + uma classe, ela pode conter documentação Javadoc. Ambientes de desenvolvimento integrados, como + o Eclipse, podem preencher automaticamente os nomes de constantes da classe de contrato e exibir Javadoc para + as constantes. +

+

+ Os desenvolvedores não podem acessar o arquivo de classe da classe de contrato do aplicativo, mas podem + compilá-lo estaticamente no aplicativo a partir de um arquivo .jar que você fornece. +

+

+ A classe {@link android.provider.ContactsContract} e classes aninhadas são exemplos + de classes de contrato. +

+

Implementação de permissões do Provedor de Conteúdo

+

+ Permissões e acesso, para todos os aspectos do sistema Android, são descritos detalhadamente no + tópico Permissões e segurança. + O tópico Armazenamento de dados também + descreve segurança e permissões em vigor para diversos tipos de armazenamento. + Em resumo, os pontos importantes são: +

+
    +
  • + Por padrão, arquivos de dados armazenados no armazenamento interno do dispositivo são privados em relação + ao aplicativo e ao provedor. +
  • +
  • + Os bancos de dados {@link android.database.sqlite.SQLiteDatabase} criados são privados em relação + ao aplicativo e ao provedor. +
  • +
  • + Por padrão, arquivos de dados salvos em armazenamentos externos são públicos + e legíveis para todos. Não é possível usar um provedor de conteúdo para restringir o acesso a arquivos + em um armazenamento externo porque outros aplicativos podem usar chamadas de outra API para lê-los e gravar neles. +
  • +
  • + O método que chama a abertura ou criação de arquivos ou bancos de dados SQLite no armazenamento + interno do seu dispositivo podem fornecer acesso de leitura e gravação a todos os outros aplicativos. Se você + usar um arquivo ou banco de dados interno como o repositório do provedor e conceder-lhe + acesso "legível para todos" ou "gravável por todos", as permissões definidas para o provedor + no manifesto não protegerão os dados. O acesso padrão de arquivos e bancos de dados + no armazenamento interno é "privado" e, para o repositório do provedor, não é recomendável alterar isso. +
  • +
+

+ Se você deseja usar permissões do provedor de conteúdo para controlar o acesso aos dados, deve + armazená-los em arquivos internos, bancos de dados SQLite ou em "nuvem" (por exemplo, + em um servidor remoto) e mantê-los privados em relação ao aplicativo. +

+

Implementação de permissões

+

+ Todos os aplicativos podem ler ou gravar no provedor, mesmo que os dados em questão + sejam privados porque, por padrão, o provedor não tem permissões definidas. Para mudar isso, + defina permissões do provedor no arquivo de manifesto por meio de atributos ou elementos + filho do elemento + <provider>. É possível definir permissões que se apliquem a todo o provedor, + a determinadas tabelas, a registros específicos ou a todos os três. +

+

+ As permissões são definidas para o provedor com um ou mais + elementos + <permission> no arquivo de manifesto. Para tornar + a permissão exclusiva para o provedor, use escopo de estilo Java para + o atributo + android:name. Por exemplo: nomeie a permissão de leitura + com.example.app.provider.permission.READ_PROVIDER. + +

+

+ A lista a seguir descreve o escopo de permissões do provedor, começando + com as permissões que se aplicam a todo o provedor e seguindo para as mais específicas. + Permissões mais específicas têm precedência sobre as de escopo maior: +

+
+
+ Única permissão de leitura e gravação no nível do provedor +
+
+ Uma permissão que controla os acessos de leitura e gravação a todo o provedor, especificada + com o atributo + android:permission + do elemento + <provider>. +
+
+ Permissões de leitura e gravação separadas no nível do provedor +
+
+ Uma permissão de leitura e uma permissão de gravação para todo o provedor. São especificadas + com os atributos + android:readPermission + e + android:writePermission + do elemento + <provider>. Elas têm precedência sobre a permissão exigida + por + android:permission. +
+
+ Permissão no nível do caminho +
+
+ Permissão de leitura, gravação ou leitura/gravação para uma URI de conteúdo no provedor. Especifica-se + cada URI que se deseja controlar + com um elemento filho + <path-permission> + do elemento + <provider>. Para cada URI de conteúdo, pode-se especificar + uma permissão de leitura/gravação, uma permissão de leitura, uma permissão de gravação ou as três. As permissões + de leitura e gravação têm precedência sobre a permissão de leitura/gravação. Além disso, + permissões no nível do caminho têm precedência sobre permissões no nível do provedor. +
+
+ Permissão temporária +
+
+ Nível de permissão que concede acesso temporário a um aplicativo mesmo que ele + não tenha as permissões normalmente exigidas. O recurso de acesso + temporário reduz o número de permissões que um aplicativo deve solicitar + no manifesto. Ao ativar permissões temporárias, os únicos aplicativos que precisam de + permissões "permanentes" para seu provedor são os que têm acesso contínuo + a todos os dados. +

+ Considere as permissões necessárias para implementar um provedor e um aplicativo de e-mail + ao permitir um aplicativo visualizador de imagens externas para exibir anexos de fotos + do provedor. Para conceder o acesso necessário ao visualizador de imagens sem as permissões exigidas, + configure permissões temporárias das URIs de conteúdo de fotos. Projete o aplicativo de e-mail + para que, quando o usuário quiser exibir uma foto, o aplicativo envie uma intenção + com a URI de conteúdo da foto e sinalizadores de permissão para o visualizador de imagens. O visualizador de imagens poderá, + então, consultar o provedor de e-mail para recuperar a foto mesmo que ele + não tenha a permissão normal de leitura para o provedor. +

+

+ Para ativar permissões temporárias, defina + o atributo + android:grantUriPermissions + do elemento + <provider> ou adicione um ou mais + elementos filhos + <grant-uri-permission> + ao elemento + <provider>. Se forem usadas permissões temporárias, será necessário chamar + {@link android.content.Context#revokeUriPermission(Uri, int) + Context.revokeUriPermission()} sempre que remover suporte a URI de conteúdo do + provedor, e a URI de conteúdo será associada a uma permissão temporária. +

+

+ O valor do atributo determina o nível de acessibilidade do provedor. + Se o atributo estiver definido como true, o sistema concederá permissão + temporária a todo o provedor, sobrepondo todas as outras permissões exigidas + pelas permissões no nível do provedor ou no nível do caminho. +

+

+ Se esse sinalizador estiver definido como false, será necessário adicionar + elementos filhos + <grant-uri-permission> + ao elemento + <provider>. Cada elemento filho especifica a URI ou URIs + de conteúdo para as quais o acesso temporário é concedido. +

+

+ Para delegar o acesso temporário a um aplicativo, a intenção deve conter + os sinalizadores {@link android.content.Intent#FLAG_GRANT_READ_URI_PERMISSION}, + {@link android.content.Intent#FLAG_GRANT_WRITE_URI_PERMISSION} ou ambos. Eles + são definidos com o método {@link android.content.Intent#setFlags(int) setFlags()}. +

+

+ Se o atributo + android:grantUriPermissions não estiver presente, presume-se que ele seja + false. +

+
+
+ + + + +

O elemento <provider>

+

+ Como os componentes {@link android.app.Activity} e {@link android.app.Service}, + a subclasse de {@link android.content.ContentProvider} + deve ser definida no arquivo de manifesto do aplicativo + pelo elemento + <provider>. O sistema Android obtém as seguintes informações + do elemento: +

+
+ Autoridade + ({@code + android:authorities}) +
+
+ Nomes simbólicos que identificam todo o provedor dentro do sistema. Esse + atributo é descrito com mais detalhes na seção + Projeto de URIs de conteúdo. +
+
+ Nome da classe do provedor + ( +android:name + ) +
+
+ A classe que implementa {@link android.content.ContentProvider}. Esta classe é + abordada com mais detalhes na seção + Implementação da classe ContentProvider. +
+
+ Permissões +
+
+ Atributos que especificam as permissões que outros aplicativos precisam ter para acessar + os dados do provedor: + +

+ As permissões e os atributos correspondentes são abordados com mais + detalhes na seção + Implementação de permissões do Provedor de conteúdo. +

+
+
+ Atributos de início e controle +
+
+ Esses atributos determinam como e quando o sistema Android inicia o provedor, + as características do processo do provedor e outras configurações de tempo de execução: +
    +
  • + + android:enabled: sinalizador que permite ao sistema iniciar o provedor. +
  • +
  • + + android:exported: sinalizador que permite a outros aplicativos usarem esse provedor. +
  • +
  • + + android:initOrder: a ordem em que esse provedor deve ser iniciado, + relativa a outros provedores no mesmo processo. +
  • +
  • + + android:multiProcess: sinalizador que permite ao sistema iniciar o provedor + no mesmo processo que o cliente chama. +
  • +
  • + + android:process: o nome do processo em que o provedor deve + ser executado. +
  • +
  • + + android:syncable: sinalizador que indica que os dados do provedor devem ser + sincronizados com os dados em um servidor. +
  • +
+

+ os atributos estão totalmente documentados no tópico do guia de desenvolvimento + do elemento + + <provider>. +

+
+
+ Atributos informacionais +
+
+ Um ícone e um rótulo opcionais para o provedor: +
    +
  • + + android:icon: um recurso desenhável contendo um ícone para o provedor. + O ícone aparece próximo ao rótulo do provedor na lista de aplicativos + em Configurações > Aplicativos > Todos. +
  • +
  • + + android:label: um rótulo informacional que descreve o provedor, + seus dados ou ambos. O rótulo aparece na lista de aplicativos + em Configurações > Aplicativos > Todos. +
  • +
+

+ Os atributos estão totalmente documentados no tópico do guia de desenvolvimento + do elemento + <provider>. +

+
+
+ + +

Intenções e acesso a dados

+

+ Os aplicativos podem acessar um provedor de conteúdo indiretamente com uma {@link android.content.Intent}. + O aplicativo não chama nenhum método de {@link android.content.ContentResolver} + nem de {@link android.content.ContentProvider}. Em vez disso, ele envia uma intenção que inicia uma atividade, + que, em geral, é parte do aplicativo do próprio provedor. A atividade de destino é responsável + pela recuperação e exibição dos dados na IU. Conforme a ação na intenção, + a atividade de destino também pode levar o usuário a realizar modificações nos dados do provedor. + Uma intenção também pode conter dados "extras" que a atividade de destino exibe + na IU; o usuário terá, então, a opção de alterar esses dados antes de usá-los para modificar + os dados no provedor. +

+

+ +

+

+ Pode ser necessário usar acesso de intenções para ajudar a garantir a integridade deles. O provedor pode depender + de ter dados inseridos, atualizados e excluídos de acordo com a lógica de negócio rigorosamente definida. Se + for o caso, a permissão para que outros aplicativos modifiquem os dados diretamente pode levar + à invalidação dos dados. Se você deseja que os desenvolvedores usem o acesso via intenções, certifique-se de documentá-lo minuciosamente. + Explique por que o acesso via intenções pela IU do aplicativo é melhor do que tentar + modificar os dados com códigos. +

+

+ O tratamento das intenções recebidas com a intenção de modificar os dados do provedor não é diferente + de tratar de outras intenções. Para saber mais sobre o uso de intenções, leia o tópico + Intenções e filtros de intenções. +

diff --git a/docs/html-intl/intl/pt-br/guide/topics/providers/content-providers.jd b/docs/html-intl/intl/pt-br/guide/topics/providers/content-providers.jd new file mode 100644 index 0000000000000000000000000000000000000000..c9574f6b90c6e7613e3eda060bcdec0b0e2275b6 --- /dev/null +++ b/docs/html-intl/intl/pt-br/guide/topics/providers/content-providers.jd @@ -0,0 +1,108 @@ +page.title=Provedores de conteúdo +@jd:body + +

+ Provedores de conteúdo gerenciam o acesso a um conjunto estruturado de dados. Eles encapsulam os +dados e fornecem mecanismos para definir a segurança dos dados. Provedores de conteúdo são a interface + padrão que conecta dados em um processo com código em execução em outro processo. +

+

+ Quando desejar acessar dados em um provedor de conteúdo, você usa o + objeto {@link android.content.ContentResolver} no + {@link android.content.Context} do aplicativo para se comunicar com o provedor como cliente. + O objeto {@link android.content.ContentResolver} se comunica com o objeto provedor, uma + instância de uma classe que implementa {@link android.content.ContentProvider}. O objeto + provedor recebe solicitações de dados de clientes, realiza a ação solicitada e + devolve os resultados. +

+

+ Não é preciso desenvolver o próprio provedor se você não pretende compartilhar seus dados com + outros aplicativos. No entanto, precisará do próprio provedor para fornecer sugestões de pesquisa + personalizada em seu aplicativo. Também precisará do próprio provedor se quiser copiar e colar + dados complexos ou arquivos de seu aplicativo em outros aplicativos. +

+

+ O Android propriamente dito inclui provedores de conteúdo que gerenciam dados como áudio, vídeo, imagens e + informações de contato pessoais. Alguns deles estão listados na documentação de + referência do + pacote android.provider + . Com algumas restrições, esses provedores podem ser acessados por qualquer aplicativo + Android. +

+ Os tópicos a seguir descrevem provedores de conteúdo em mais detalhes: +

+
+
+ + Preceitos do provedor de conteúdo +
+
+ Como acessar dados em um provedor de conteúdo quando os dados estão organizados em tabelas. +
+
+ +Criação de um Provedor de conteúdo +
+
+ Como criar o próprio provedor de conteúdo. +
+
+ +Provedor de agenda +
+
+ Como acessar o Provedor de agenda que é parte da plataforma Android. +
+
+ +Provedor de contatos +
+
+ Como acessar o Provedor de contatos que é parte da plataforma Android. +
+
diff --git a/docs/html-intl/intl/pt-br/guide/topics/providers/document-provider.jd b/docs/html-intl/intl/pt-br/guide/topics/providers/document-provider.jd new file mode 100644 index 0000000000000000000000000000000000000000..25aab7aa7a9d247261553094926e8639f25c51a6 --- /dev/null +++ b/docs/html-intl/intl/pt-br/guide/topics/providers/document-provider.jd @@ -0,0 +1,916 @@ +page.title=Estrutura de acesso ao armazenamento +@jd:body + + + +

O Android 4.4 (API de nível 19) introduz a Estrutura de Acesso ao Armazenamento (SAF). A SAF + simplifica para os usuários a busca e abertura de documentos, imagens e outros arquivos +dentre todos os provedores de armazenamento de documentos de preferência. A interface gráfica padrão é fácil de usar +e permite aos usuários buscar arquivos e acessar arquivos recentes de modo coerente em todos os aplicativos e provedores.

+ +

Serviços de armazenamento local ou em nuvem podem participar desse ecossistema por meio da implementação +de um {@link android.provider.DocumentsProvider} que encapsula os serviços. Aplicativos +clientes que precisam acessar documentos de um provedor podem integrar-se com a SAF com apenas algumas +linhas de código.

+ +

A SAF contém:

+ +
    +
  • Provedor de documentos — Provedor de conteúdo que oferece +um serviço de armazenamento (como o Google Drive) para exibir os arquivos que gerencia. O provedor de documentos +é implementado como uma subclasse da classe {@link android.provider.DocumentsProvider}. +O esquema do provedor de documento se baseia em uma hierarquia de arquivo tradicional, +embora o modo de armazenamento físico de dados do provedor de documentos seja definido pelo programador. +A plataforma do Android contém diversos provedores de documento embutidos, como +Downloads, Imagens e Vídeos.
  • + +
  • Aplicativo cliente — Aplicativo personalizado que chama a intenção +{@link android.content.Intent#ACTION_OPEN_DOCUMENT} +e/ou {@link android.content.Intent#ACTION_CREATE_DOCUMENT} e recebe +os arquivos retornados pelos provedores de documentos.
  • + +
  • Seletor — IU de sistema que permite aos usuários acessar documentos de todos +os provedores de documentos que satisfazem os critérios de busca do aplicativo cliente.
  • +
+ +

A seguir há alguns recursos oferecidos pela SAF:

+
    +
  • Permitir que usuários busquem conteúdo de todos os provedores de documentos, não somente de um único aplicativo.
  • +
  • Possibilitar ao aplicativo a obtenção de acesso persistente e de longo prazo +a documentos de propriedade de um provedor de documentos. Por meio deste acesso, os usuários podem adicionar, editar, + salvar e excluir arquivos no provedor.
  • +
  • É compatível com diversas contas de usuário e raízes transitórias como provedores +de armazenamento USB, que só aparecem se o dispositivo estiver plugado.
  • +
+ +

Visão geral

+ +

A SAF consiste em um provedor de conteúdo que é uma +subclasse da classe {@link android.provider.DocumentsProvider}. Dentro de um provedor de documentos, os dados +são estruturados como uma hierarquia de arquivo tradicional:

+

data model

+

Figura 1. Modelo de dados do provedor de documentos Uma Raiz aponta para um único Documento, +que então inicia o fan-out de toda a árvore.

+ +

Observe o seguinte:

+
    + +
  • Cada provedor de documentos relata uma ou mais +"raízes", que são pontos de partida na exploração de uma árvore de documentos. +Cada raiz tem um {@link android.provider.DocumentsContract.Root#COLUMN_ROOT_ID} exclusivo +e ele aponta para um documento (um diretório) +representando o conteúdo sob essa raiz. +As raízes têm um projeto dinâmico para oferecer compatibilidade a casos de uso como diversas contas, +dispositivos de armazenamento USB transitórios ou login/logout do usuário.
  • + +
  • Sob cada raiz há um documento único. Esse documento indica 1 a N documentos, +cada um deles, por sua vez, podem indicar 1 a N documentos.
  • + +
  • Cada back-end de armazenamento apresenta +arquivos e diretórios individuais referenciando-os com um +{@link android.provider.DocumentsContract.Document#COLUMN_DOCUMENT_ID} exclusivo. +IDs de documentos devem ser exclusivos e não podem mudar depois de emitidos, pois são usados para concessões persistentes +da URI em reinicializações do dispositivo.
  • + + +
  • Documentos podem ser um arquivo ou um diretório que pode ser aberto (com um tipo MIME específico) + contendo documentos adicionais (com +o tipo MIME{@link android.provider.DocumentsContract.Document#MIME_TYPE_DIR}).
  • + +
  • Cada documento tem diferentes recursos, como descrito por +{@link android.provider.DocumentsContract.Document#COLUMN_FLAGS COLUMN_FLAGS}. +Por exemplo,{@link android.provider.DocumentsContract.Document#FLAG_SUPPORTS_WRITE}, +{@link android.provider.DocumentsContract.Document#FLAG_SUPPORTS_DELETE} +e {@link android.provider.DocumentsContract.Document#FLAG_SUPPORTS_THUMBNAIL}. +O mesmo {@link android.provider.DocumentsContract.Document#COLUMN_DOCUMENT_ID} pode ser +incluído em diversos diretórios.
  • +
+ +

Controle de fluxo

+

Como indicado anteriormente, o modelo de dados do provedor de documentos se baseia +em uma hierarquia de arquivo tradicional. Contudo, é possível armazenar os dados fisicamente como quiser desde +que eles possam ser acessados pela API {@link android.provider.DocumentsProvider}. Por exemplo: seria +possível usar armazenamento em nuvem com base em tag para os dados.

+ +

A figura 2 ilustra um exemplo de um aplicativo de foto que usa a SAF +para acessar dados armazenados:

+

app

+ +

Figura 2. Fluxo da estrutura de acesso ao armazenamento

+ +

Observe o seguinte:

+
    + +
  • Na SAF, provedores e clientes não interagem +diretamente. O cliente solicita permissão para interagir +com arquivos (ou seja, para ler, editar, criar ou excluir arquivos).
  • + +
  • A interação começa quando um aplicativo (neste exemplo, um aplicativo de foto) dispara a intenção +{@link android.content.Intent#ACTION_OPEN_DOCUMENT} ou {@link android.content.Intent#ACTION_CREATE_DOCUMENT}. A intenção pode conter filtros +para refinar ainda mais os critérios — por exemplo: "quero todos os arquivos que podem ser abertos +e que tenham o tipo MIME de tal imagem".
  • + +
  • Ao disparar a intenção, o seletor do sistema contata cada provedor registrado +e exibe as raízes de conteúdo correspondentes ao usuário.
  • + +
  • O seletor fornece aos usuários uma interface padrão para acessar documentos, +embora os provedores de documentos subjacentes possam ser bem diferentes. Por exemplo: a figura 2 +exibe um provedor do Google Drive, um provedor USB e um provedor de nuvem.
  • +
+ +

A figura 3 exibe um seletor em que um usuário em busca de imagens selecionou +uma conta do Google Drive:

+ +

picker

+ +

Figura 3. Seletor

+ +

Quando o usuário seleciona o Google Drive, as imagens são exibidas como ilustrado +na figura 4. Desse momento em diante, o usuário poderá interagir com eles de todos os modos +compatíveis com o provedor e o aplicativo cliente. + +

picker

+ +

Figura 4. Imagens

+ +

Programação de um aplicativo cliente

+ +

No Android 4.3 e em versões anteriores, para o aplicativo recuperar um arquivo de outro +aplicativo, ele precisa chamar uma intenção como {@link android.content.Intent#ACTION_PICK} +ou {@link android.content.Intent#ACTION_GET_CONTENT}. Em seguida, o usuário deve selecionar +um único aplicativo do qual deseja retirar um arquivo e o aplicativo selecionado deve fornecer uma interface +do usuário para a busca e a seleção dos arquivos disponíveis.

+ +

No Android 4.4 e em versões posteriores, existe a opção adicional de usar +a intenção {@link android.content.Intent#ACTION_OPEN_DOCUMENT}, +que exibe uma IU do seletor controlada pelo sistema que permite ao usuário +buscar todos os arquivos que outros aplicativos disponibilizaram. Nessa IU exclusiva, +o usuário pode selecionar um arquivo de qualquer aplicativo compatível.

+ +

{@link android.content.Intent#ACTION_OPEN_DOCUMENT} +não substitui {@link android.content.Intent#ACTION_GET_CONTENT}. +O uso de cada um deles depende da necessidade do aplicativo:

+ +
    +
  • Use {@link android.content.Intent#ACTION_GET_CONTENT} se deseja que o aplicativo +simplesmente leia ou importe dados. Nessa abordagem, o aplicativo importa uma cópia dos dados, +assim como um arquivo de imagem.
  • + +
  • Use {@link android.content.Intent#ACTION_OPEN_DOCUMENT} se deseja que o aplicativo +tenha acesso persistente e de longo prazo a documentos de propriedade de um provedor +de documentos. Um exemplo seria um aplicativo de edição de fotos que permite aos usuários editar +imagens armazenadas em um provedor de documentos.
  • + +
+ + +

Esta seção descreve como programar aplicativos clientes com base nas intenções +{@link android.content.Intent#ACTION_OPEN_DOCUMENT} +e {@link android.content.Intent#ACTION_CREATE_DOCUMENT}.

+ + + + +

+O fragmento a seguir usa {@link android.content.Intent#ACTION_OPEN_DOCUMENT} +para buscar provedores de documentos +que contenham arquivos de imagem:

+ +
private static final int READ_REQUEST_CODE = 42;
+...
+/**
+ * Fires an intent to spin up the "file chooser" UI and select an image.
+ */
+public void performFileSearch() {
+
+    // ACTION_OPEN_DOCUMENT is the intent to choose a file via the system's file
+    // browser.
+    Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
+
+    // Filter to only show results that can be "opened", such as a
+    // file (as opposed to a list of contacts or timezones)
+    intent.addCategory(Intent.CATEGORY_OPENABLE);
+
+    // Filter to show only images, using the image MIME data type.
+    // If one wanted to search for ogg vorbis files, the type would be "audio/ogg".
+    // To search for all documents available via installed storage providers,
+    // it would be "*/*".
+    intent.setType("image/*");
+
+    startActivityForResult(intent, READ_REQUEST_CODE);
+}
+ +

Observe o seguinte:

+
    +
  • Quando o aplicativo dispara a intenção +{@link android.content.Intent#ACTION_OPEN_DOCUMENT}, ele aciona um seletor que exibe todos os provedores de documentos compatíveis.
  • + +
  • A adição da categoria {@link android.content.Intent#CATEGORY_OPENABLE} +à intenção filtra os resultados, que exibem somente documentos que podem ser abertos, como os arquivos de imagem.
  • + +
  • A declaração {@code intent.setType("image/*")} filtra ainda mais +para exibir somente documentos que têm o tipo de dados MIME da imagem.
  • +
+ +

Processamento de resultados

+ +

Quando o usuário seleciona um documento no seletor, +{@link android.app.Activity#onActivityResult onActivityResult()} é chamado. +A URI direcionada ao documento selecionado está presente no parâmetro +{@code resultData}. Extraia a URI usando {@link android.content.Intent#getData getData()}. +Depois, será possível usá-la para recuperar o documento que o usuário deseja. Por +exemplo:

+ +
@Override
+public void onActivityResult(int requestCode, int resultCode,
+        Intent resultData) {
+
+    // The ACTION_OPEN_DOCUMENT intent was sent with the request code
+    // READ_REQUEST_CODE. If the request code seen here doesn't match, it's the
+    // response to some other intent, and the code below shouldn't run at all.
+
+    if (requestCode == READ_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
+        // The document selected by the user won't be returned in the intent.
+        // Instead, a URI to that document will be contained in the return intent
+        // provided to this method as a parameter.
+        // Pull that URI using resultData.getData().
+        Uri uri = null;
+        if (resultData != null) {
+            uri = resultData.getData();
+            Log.i(TAG, "Uri: " + uri.toString());
+            showImage(uri);
+        }
+    }
+}
+
+ +

Examinação de metadados de documentos

+ +

Quando tiver a URI de um documento, você terá acesso aos seus metadados. Este +fragmento apanha os metadados de um documento especificado pela URI e registra-os:

+ +
public void dumpImageMetaData(Uri uri) {
+
+    // The query, since it only applies to a single document, will only return
+    // one row. There's no need to filter, sort, or select fields, since we want
+    // all fields for one document.
+    Cursor cursor = getActivity().getContentResolver()
+            .query(uri, null, null, null, null, null);
+
+    try {
+    // moveToFirst() returns false if the cursor has 0 rows.  Very handy for
+    // "if there's anything to look at, look at it" conditionals.
+        if (cursor != null && cursor.moveToFirst()) {
+
+            // Note it's called "Display Name".  This is
+            // provider-specific, and might not necessarily be the file name.
+            String displayName = cursor.getString(
+                    cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME));
+            Log.i(TAG, "Display Name: " + displayName);
+
+            int sizeIndex = cursor.getColumnIndex(OpenableColumns.SIZE);
+            // If the size is unknown, the value stored is null.  But since an
+            // int can't be null in Java, the behavior is implementation-specific,
+            // which is just a fancy term for "unpredictable".  So as
+            // a rule, check if it's null before assigning to an int.  This will
+            // happen often:  The storage API allows for remote files, whose
+            // size might not be locally known.
+            String size = null;
+            if (!cursor.isNull(sizeIndex)) {
+                // Technically the column stores an int, but cursor.getString()
+                // will do the conversion automatically.
+                size = cursor.getString(sizeIndex);
+            } else {
+                size = "Unknown";
+            }
+            Log.i(TAG, "Size: " + size);
+        }
+    } finally {
+        cursor.close();
+    }
+}
+
+ +

Abertura de um documento

+ +

Assim que tiver a URI de um documento, você poderá abri-lo ou fazer o que +quiser com ele.

+ +

Bitmap

+ +

A seguir há um exemplo de como abrir um {@link android.graphics.Bitmap}:

+ +
private Bitmap getBitmapFromUri(Uri uri) throws IOException {
+    ParcelFileDescriptor parcelFileDescriptor =
+            getContentResolver().openFileDescriptor(uri, "r");
+    FileDescriptor fileDescriptor = parcelFileDescriptor.getFileDescriptor();
+    Bitmap image = BitmapFactory.decodeFileDescriptor(fileDescriptor);
+    parcelFileDescriptor.close();
+    return image;
+}
+
+ +

Observe que não se deve realizar essa operação no encadeamento da IU. Faça isso +em segundo plano usando {@link android.os.AsyncTask}. Assim que abrir o bitmap, você +poderá exibi-lo em uma {@link android.widget.ImageView}. +

+ +

Obter uma InputStream

+ +

Abaixo há um exemplo de como obter uma {@link java.io.InputStream} dessa URI. +Neste fragmento, as linhas do arquivo estão sendo lidas em uma string:

+ +
private String readTextFromUri(Uri uri) throws IOException {
+    InputStream inputStream = getContentResolver().openInputStream(uri);
+    BufferedReader reader = new BufferedReader(new InputStreamReader(
+            inputStream));
+    StringBuilder stringBuilder = new StringBuilder();
+    String line;
+    while ((line = reader.readLine()) != null) {
+        stringBuilder.append(line);
+    }
+    fileInputStream.close();
+    parcelFileDescriptor.close();
+    return stringBuilder.toString();
+}
+
+ +

Criação de um novo documento

+ +

O aplicativo pode criar um novo documento em um provedor de documentos usando +a intenção +{@link android.content.Intent#ACTION_CREATE_DOCUMENT}. Para criar um arquivo, deve-se fornecer um tipo MIME e um nome para o arquivo à intenção +e ativá-la com um código de solicitação exclusivo. O resto você não precisa fazer:

+ + +
+// Here are some examples of how you might call this method.
+// The first parameter is the MIME type, and the second parameter is the name
+// of the file you are creating:
+//
+// createFile("text/plain", "foobar.txt");
+// createFile("image/png", "mypicture.png");
+
+// Unique request code.
+private static final int WRITE_REQUEST_CODE = 43;
+...
+private void createFile(String mimeType, String fileName) {
+    Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
+
+    // Filter to only show results that can be "opened", such as
+    // a file (as opposed to a list of contacts or timezones).
+    intent.addCategory(Intent.CATEGORY_OPENABLE);
+
+    // Create a file with the requested MIME type.
+    intent.setType(mimeType);
+    intent.putExtra(Intent.EXTRA_TITLE, fileName);
+    startActivityForResult(intent, WRITE_REQUEST_CODE);
+}
+
+ +

Ao criar um novo documento, é possível obter sua URI +em {@link android.app.Activity#onActivityResult onActivityResult()} para que +seja possível continuar a gravar nele.

+ +

Exclusão de um documento

+ +

Se você tem uma URI de um documento +e os {@link android.provider.DocumentsContract.Document#COLUMN_FLAGS Document.COLUMN_FLAGS} do documento + contêm +{@link android.provider.DocumentsContract.Document#FLAG_SUPPORTS_DELETE SUPPORTS_DELETE}, +você pode excluir o documento. Por exemplo:

+ +
+DocumentsContract.deleteDocument(getContentResolver(), uri);
+
+ +

Edição de um documento

+ +

Você pode usar a SAF para editar o documento de texto no local em que está armazenado. +Esse fragmento dispara +a intenção {@link android.content.Intent#ACTION_OPEN_DOCUMENT} e usa +a categoria {@link android.content.Intent#CATEGORY_OPENABLE} para exibir somente +documentos que possam ser abertos. Ela filtra ainda mais para exibir somente arquivos de texto:

+ +
+private static final int EDIT_REQUEST_CODE = 44;
+/**
+ * Open a file for writing and append some text to it.
+ */
+ private void editDocument() {
+    // ACTION_OPEN_DOCUMENT is the intent to choose a file via the system's
+    // file browser.
+    Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
+
+    // Filter to only show results that can be "opened", such as a
+    // file (as opposed to a list of contacts or timezones).
+    intent.addCategory(Intent.CATEGORY_OPENABLE);
+
+    // Filter to show only text files.
+    intent.setType("text/plain");
+
+    startActivityForResult(intent, EDIT_REQUEST_CODE);
+}
+
+ +

Em seguida, de {@link android.app.Activity#onActivityResult onActivityResult()} +(consulte Processar resultados), você pode chamar o código para realizar a edição. +O fragmento a seguir obtém um {@link java.io.FileOutputStream} +do {@link android.content.ContentResolver}. Por padrão, ele usa o modo de "gravação". +Recomenda-se solicitar a menor quantidade de acesso necessária, portanto não solicite +acesso de leitura e programação se só for necessária a programação:

+ +
private void alterDocument(Uri uri) {
+    try {
+        ParcelFileDescriptor pfd = getActivity().getContentResolver().
+                openFileDescriptor(uri, "w");
+        FileOutputStream fileOutputStream =
+                new FileOutputStream(pfd.getFileDescriptor());
+        fileOutputStream.write(("Overwritten by MyCloud at " +
+                System.currentTimeMillis() + "\n").getBytes());
+        // Let the document provider know you're done by closing the stream.
+        fileOutputStream.close();
+        pfd.close();
+    } catch (FileNotFoundException e) {
+        e.printStackTrace();
+    } catch (IOException e) {
+        e.printStackTrace();
+    }
+}
+ +

Manutenção de permissões

+ +

Quando o aplicativo abre um arquivo para leitura ou programação, o sistema lhe fornece +uma concessão da permissão da URI para tal arquivo. Ela vigora até a reinicialização do dispositivo do usuário. +Contudo, suponhamos que seu aplicativo seja de edição de imagens e você queira que os usuários +acessem as últimas 5 imagens que editaram diretamente do aplicativo. Se o dispositivo do usuário +reiniciou, você teria que enviar o usuário de volta ao seletor do sistema para encontrar +os arquivos, o que, obviamente, não é o ideal.

+ +

Para evitar que isso aconteça, você pode manter as permissões que o sistema +forneceu ao aplicativo. Efetivamente, o aplicativo "toma" a concessão de permissão da URI persistente +que o sistema está oferecendo. Isso concede ao usuário um acesso contínuo aos arquivos +por meio do aplicativo mesmo se o dispositivo for reiniciado:

+ + +
final int takeFlags = intent.getFlags()
+            & (Intent.FLAG_GRANT_READ_URI_PERMISSION
+            | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+// Check for the freshest data.
+getContentResolver().takePersistableUriPermission(uri, takeFlags);
+ +

Há uma etapa final. Você pode ter salvo as URIs +mais recentes acessadas pelo seu aplicativo, mas elas não serão mais válidas — outro aplicativo +pode ter excluído ou modificado um documento. Portanto, deve-se sempre chamar +{@code getContentResolver().takePersistableUriPermission()} para verificar +se há dados mais recentes.

+ +

Criação de um provedor de documentos personalizado

+ +

+Se você está desenvolvimento um aplicativo que fornece serviços de armazenamento para arquivos (como +um serviço de armazenamento em nuvem), é possível disponibilizar os arquivos +pela SAF criando um provedor de documentos personalizado. Esta seção mostra +como fazê-lo.

+ + +

Manifesto

+ +

Para implementar um provedor de documentos personalizado, adicione ao manifesto +do aplicativo:

+
    + +
  • Um alvo de API de nível 19 ou posterior.
  • + +
  • Um elemento <provider> que declare o provedor de armazenamento +personalizado.
  • + +
  • O nome do provedor, que é o nome da classe, inclusive o nome do pacote. +Por exemplo: com.example.android.storageprovider.MyCloudProvider.
  • + +
  • O nome da autoridade, que é o nome do pacote (neste exemplo, +com.example.android.storageprovider) e o tipo de provedor de conteúdo +(documents). Por exemplo: {@code com.example.android.storageprovider.documents}.
  • + +
  • O atributo android:exported definido como "true". +É necessário exportar o provedor para que outros aplicativos possam vê-lo.
  • + +
  • O atributo android:grantUriPermissions definido como +"true". Essa configuração permite ao sistema conceder acesso ao conteúdo +no provedor a outros aplicativos. Para ver como manter uma concessão +para determinado documento, consulte Manutenção de permissões.
  • + +
  • A permissão {@code MANAGE_DOCUMENTS}. Por padrão, um provedor está disponível +para todos. A adição dessa permissão restringirá o provedor em relação ao sistema. +Essa restrição é importante em termos de segurança.
  • + +
  • O atributo {@code android:enabled} definido como um valor booleano determinado em um arquivo +de recursos. Esse atributo visa desativar o provedor em dispositivos que executam o Android 4.3 ou versões anteriores. +Por exemplo: {@code android:enabled="@bool/atLeastKitKat"}. Além +disso, para incluir esse atributo no manifesto, deve-se fazer o seguinte: +
      +
    • No arquivo de recursos {@code bool.xml} em {@code res/values/}, adicione +esta linha:
      <bool name="atLeastKitKat">false</bool>
    • + +
    • No arquivo de recursos {@code bool.xml} em {@code res/values-v19/}, adicione +esta linha:
      <bool name="atLeastKitKat">true</bool>
    • +
  • + +
  • Um filtro de intenções que contenha +a ação {@code android.content.action.DOCUMENTS_PROVIDER} para que o provedor +apareça no seletor quando o sistema procurar provedores.
  • + +
+

Abaixo há alguns excertos de amostra de um manifesto que contém um provedor:

+ +
<manifest... >
+    ...
+    <uses-sdk
+        android:minSdkVersion="19"
+        android:targetSdkVersion="19" />
+        ....
+        <provider
+            android:name="com.example.android.storageprovider.MyCloudProvider"
+            android:authorities="com.example.android.storageprovider.documents"
+            android:grantUriPermissions="true"
+            android:exported="true"
+            android:permission="android.permission.MANAGE_DOCUMENTS"
+            android:enabled="@bool/atLeastKitKat">
+            <intent-filter>
+                <action android:name="android.content.action.DOCUMENTS_PROVIDER" />
+            </intent-filter>
+        </provider>
+    </application>
+
+</manifest>
+ +

Compatibilidade com dispositivos que executam Android 4.3 ou anterior

+ +

+A intenção {@link android.content.Intent#ACTION_OPEN_DOCUMENT} está disponível somente +em dispositivos que executam o Android 4.4 ou posteriores. +Se você deseja que o aplicativo seja compatível com {@link android.content.Intent#ACTION_GET_CONTENT} +para adaptar-se a dispositivos que executam o Android 4.3 ou versões anteriores, é necessário +desativar o filtro de intenção {@link android.content.Intent#ACTION_GET_CONTENT} +no manifesto para dispositivos que executam Android 4.4 ou versões posteriores. +Um provedor de documentos e {@link android.content.Intent#ACTION_GET_CONTENT} devem ser avaliados +de forma mutuamente exclusiva. Se houver compatibilidade com ambos simultaneamente, o aplicativo +aparecerá duas vezes na IU do seletor do sistema, oferecendo dois meios de acesso +diferentes aos dados armazenados. Isso pode confundir os usuários.

+ +

A seguir apresenta-se a forma recomendada de desativar +o filtro de intenções {@link android.content.Intent#ACTION_GET_CONTENT} para dispositivos +que executam o Android 4.4 ou versões posteriores:

+ +
    +
  1. No arquivo de recursos {@code bool.xml} em {@code res/values/}, adicione +esta linha:
    <bool name="atMostJellyBeanMR2">true</bool>
  2. + +
  3. No arquivo de recursos {@code bool.xml} em {@code res/values-v19/}, adicione +esta linha:
    <bool name="atMostJellyBeanMR2">false</bool>
  4. + +
  5. Adicione +um alias +de atividade para desativar o filtro de intenções +{@link android.content.Intent#ACTION_GET_CONTENT} para versões 4.4 (API de nível 19) e posteriores. Por exemplo: + +
    +<!-- This activity alias is added so that GET_CONTENT intent-filter
    +     can be disabled for builds on API level 19 and higher. -->
    +<activity-alias android:name="com.android.example.app.MyPicker"
    +        android:targetActivity="com.android.example.app.MyActivity"
    +        ...
    +        android:enabled="@bool/atMostJellyBeanMR2">
    +    <intent-filter>
    +        <action android:name="android.intent.action.GET_CONTENT" />
    +        <category android:name="android.intent.category.OPENABLE" />
    +        <category android:name="android.intent.category.DEFAULT" />
    +        <data android:mimeType="image/*" />
    +        <data android:mimeType="video/*" />
    +    </intent-filter>
    +</activity-alias>
    +
    +
  6. +
+

Contratos

+ +

Normalmente, ao criar um provedor de conteúdo personalizado, uma das tarefas +é implementar classes de contrato, como descrito +no guia dos desenvolvedores de +Provedores de conteúdo. Classe de contrato é uma classe {@code public final} +que contém definições constantes das URIs, nomes de coluna, tipos MIME +e outros metadados que pertencem ao provedor. A SAF +fornece essas classes de contrato, portanto não é necessário +programá-las:

+ +
    +
  • {@link android.provider.DocumentsContract.Document}
  • +
  • {@link android.provider.DocumentsContract.Root}
  • +
+ +

Por exemplo, eis as colunas que podem retornar em um cursor +ao consultar documentos ou a raiz do provedor de documentos:

+ +
private static final String[] DEFAULT_ROOT_PROJECTION =
+        new String[]{Root.COLUMN_ROOT_ID, Root.COLUMN_MIME_TYPES,
+        Root.COLUMN_FLAGS, Root.COLUMN_ICON, Root.COLUMN_TITLE,
+        Root.COLUMN_SUMMARY, Root.COLUMN_DOCUMENT_ID,
+        Root.COLUMN_AVAILABLE_BYTES,};
+private static final String[] DEFAULT_DOCUMENT_PROJECTION = new
+        String[]{Document.COLUMN_DOCUMENT_ID, Document.COLUMN_MIME_TYPE,
+        Document.COLUMN_DISPLAY_NAME, Document.COLUMN_LAST_MODIFIED,
+        Document.COLUMN_FLAGS, Document.COLUMN_SIZE,};
+
+ +

Subclasse DocumentsProvider

+ +

A próxima etapa na criação de um provedor de documentos personalizado é atribuir uma subclasse à +classe {@link android.provider.DocumentsProvider} abstrata. No mínimo, é necessário +implementar os seguintes métodos:

+ +
    +
  • {@link android.provider.DocumentsProvider#queryRoots queryRoots()}
  • + +
  • {@link android.provider.DocumentsProvider#queryChildDocuments queryChildDocuments()}
  • + +
  • {@link android.provider.DocumentsProvider#queryDocument queryDocument()}
  • + +
  • {@link android.provider.DocumentsProvider#openDocument openDocument()}
  • +
+ +

Esses são os únicos métodos que obrigatoriamente devem ser implementados, mas há +muitos outros que podem ser usados. Consulte {@link android.provider.DocumentsProvider} +para ver mais detalhes.

+ +

Implementação de queryRoots

+ +

A implementação de {@link android.provider.DocumentsProvider#queryRoots +queryRoots()} deve retornar um {@link android.database.Cursor} que aponta para todos +os diretórios raiz dos provedores de documentos, usando colunas definidas +em {@link android.provider.DocumentsContract.Root}.

+ +

No fragmento a seguir, o parâmetro {@code projection} representa +os campos específicos que o autor da chamada quer receber de volta. O fragmento cria um novo cursor +e adiciona-lhe uma linha — uma raiz, um diretório de nível superior, como +Downloads ou Imagens. A maioria dos provedores tem somente uma raiz. É possível ter mais de uma, +por exemplo, no caso de diversas contas de usuário. Nesse caso, adicione somente +uma segunda linha ao cursor.

+ +
+@Override
+public Cursor queryRoots(String[] projection) throws FileNotFoundException {
+
+    // Create a cursor with either the requested fields, or the default
+    // projection if "projection" is null.
+    final MatrixCursor result =
+            new MatrixCursor(resolveRootProjection(projection));
+
+    // If user is not logged in, return an empty root cursor.  This removes our
+    // provider from the list entirely.
+    if (!isUserLoggedIn()) {
+        return result;
+    }
+
+    // It's possible to have multiple roots (e.g. for multiple accounts in the
+    // same app) -- just add multiple cursor rows.
+    // Construct one row for a root called "MyCloud".
+    final MatrixCursor.RowBuilder row = result.newRow();
+    row.add(Root.COLUMN_ROOT_ID, ROOT);
+    row.add(Root.COLUMN_SUMMARY, getContext().getString(R.string.root_summary));
+
+    // FLAG_SUPPORTS_CREATE means at least one directory under the root supports
+    // creating documents. FLAG_SUPPORTS_RECENTS means your application's most
+    // recently used documents will show up in the "Recents" category.
+    // FLAG_SUPPORTS_SEARCH allows users to search all documents the application
+    // shares.
+    row.add(Root.COLUMN_FLAGS, Root.FLAG_SUPPORTS_CREATE |
+            Root.FLAG_SUPPORTS_RECENTS |
+            Root.FLAG_SUPPORTS_SEARCH);
+
+    // COLUMN_TITLE is the root title (e.g. Gallery, Drive).
+    row.add(Root.COLUMN_TITLE, getContext().getString(R.string.title));
+
+    // This document id cannot change once it's shared.
+    row.add(Root.COLUMN_DOCUMENT_ID, getDocIdForFile(mBaseDir));
+
+    // The child MIME types are used to filter the roots and only present to the
+    //  user roots that contain the desired type somewhere in their file hierarchy.
+    row.add(Root.COLUMN_MIME_TYPES, getChildMimeTypes(mBaseDir));
+    row.add(Root.COLUMN_AVAILABLE_BYTES, mBaseDir.getFreeSpace());
+    row.add(Root.COLUMN_ICON, R.drawable.ic_launcher);
+
+    return result;
+}
+ +

Implementação de queryChildDocuments

+ +

A implementação +de {@link android.provider.DocumentsProvider#queryChildDocuments queryChildDocuments()} +deve retornar um {@link android.database.Cursor} que aponta para todos os arquivos +no diretório especificado com colunas definidas em +{@link android.provider.DocumentsContract.Document}.

+ +

Esse método é chamado quando uma raiz do aplicativo é escolhida na IU do seletor. +Ele coleta os documentos filhos de um diretório na raiz. Ele pode ser chamado em qualquer nível +na hierarquia de arquivos, não somente na raiz. Esse fragmento +cria um novo cursor com as colunas solicitadas e, em seguida, adiciona informações ao cursor +sobre cada filho imediato no diretório pai. +O filho pode ser uma imagem, outro diretório — qualquer arquivo:

+ +
@Override
+public Cursor queryChildDocuments(String parentDocumentId, String[] projection,
+                              String sortOrder) throws FileNotFoundException {
+
+    final MatrixCursor result = new
+            MatrixCursor(resolveDocumentProjection(projection));
+    final File parent = getFileForDocId(parentDocumentId);
+    for (File file : parent.listFiles()) {
+        // Adds the file's display name, MIME type, size, and so on.
+        includeFile(result, null, file);
+    }
+    return result;
+}
+
+ +

Implementação de queryDocument

+ +

A implementação de +{@link android.provider.DocumentsProvider#queryDocument queryDocument()} +deve retornar um {@link android.database.Cursor} que aponta para o arquivo especificado +com colunas definidas em {@link android.provider.DocumentsContract.Document}. +

+ +

O método {@link android.provider.DocumentsProvider#queryDocument queryDocument()} +retorna as mesmas informações passadas em +{@link android.provider.DocumentsProvider#queryChildDocuments queryChildDocuments()}, +mas para um arquivo específico.

+ + +
@Override
+public Cursor queryDocument(String documentId, String[] projection) throws
+        FileNotFoundException {
+
+    // Create a cursor with the requested projection, or the default projection.
+    final MatrixCursor result = new
+            MatrixCursor(resolveDocumentProjection(projection));
+    includeFile(result, documentId, null);
+    return result;
+}
+
+ +

Implementação de openDocument

+ +

Deve-se implementar {@link android.provider.DocumentsProvider#openDocument +openDocument()} para retornar um {@link android.os.ParcelFileDescriptor} que represente +o arquivo especificado. Outros aplicativos podem usar o {@link android.os.ParcelFileDescriptor} retornado +para transmitir dados. O sistema chama esse método quando o usuário seleciona um arquivo +e o aplicativo cliente solicita acesso a ele chamando +{@link android.content.ContentResolver#openFileDescriptor openFileDescriptor()}. +Por exemplo:

+ +
@Override
+public ParcelFileDescriptor openDocument(final String documentId,
+                                         final String mode,
+                                         CancellationSignal signal) throws
+        FileNotFoundException {
+    Log.v(TAG, "openDocument, mode: " + mode);
+    // It's OK to do network operations in this method to download the document,
+    // as long as you periodically check the CancellationSignal. If you have an
+    // extremely large file to transfer from the network, a better solution may
+    // be pipes or sockets (see ParcelFileDescriptor for helper methods).
+
+    final File file = getFileForDocId(documentId);
+
+    final boolean isWrite = (mode.indexOf('w') != -1);
+    if(isWrite) {
+        // Attach a close listener if the document is opened in write mode.
+        try {
+            Handler handler = new Handler(getContext().getMainLooper());
+            return ParcelFileDescriptor.open(file, accessMode, handler,
+                        new ParcelFileDescriptor.OnCloseListener() {
+                @Override
+                public void onClose(IOException e) {
+
+                    // Update the file with the cloud server. The client is done
+                    // writing.
+                    Log.i(TAG, "A file with id " +
+                    documentId + " has been closed!
+                    Time to " +
+                    "update the server.");
+                }
+
+            });
+        } catch (IOException e) {
+            throw new FileNotFoundException("Failed to open document with id "
+            + documentId + " and mode " + mode);
+        }
+    } else {
+        return ParcelFileDescriptor.open(file, accessMode);
+    }
+}
+
+ +

Segurança

+ +

Suponha que o provedor de documentos seja um serviço de armazenamento em nuvem protegido por senha +e que você queira certificar-se de que os usuários estejam conectados antes de iniciar o compartilhamento dos arquivos. +O que o aplicativo deve fazer se o usuário não estiver conectado? A solução é retornar +zero raiz na implementação de {@link android.provider.DocumentsProvider#queryRoots +queryRoots()}, ou seja, um cursor de raiz vazio:

+ +
+public Cursor queryRoots(String[] projection) throws FileNotFoundException {
+...
+    // If user is not logged in, return an empty root cursor.  This removes our
+    // provider from the list entirely.
+    if (!isUserLoggedIn()) {
+        return result;
+}
+
+ +

A outra etapa é chamar {@code getContentResolver().notifyChange()}. +Lembra-se do {@link android.provider.DocumentsContract}? Estamos usando-o para criar +esta URI. O fragmento a seguir pede ao sistema que consulte as raízes +do provedor de documentos sempre que o status de login do usuário mudar. Se o usuário não estiver +conectado, uma chamada de {@link android.provider.DocumentsProvider#queryRoots queryRoots()} retornará um +cursor vazio, como exibido anteriormente. Isso garante que os documentos do provedor estejam +disponíveis somente se o usuário tiver acesso ao provedor.

+ +
private void onLoginButtonClick() {
+    loginOrLogout();
+    getContentResolver().notifyChange(DocumentsContract
+            .buildRootsUri(AUTHORITY), null);
+}
+
\ No newline at end of file diff --git a/docs/html-intl/intl/pt-br/guide/topics/resources/accessing-resources.jd b/docs/html-intl/intl/pt-br/guide/topics/resources/accessing-resources.jd new file mode 100644 index 0000000000000000000000000000000000000000..f196dfeae779f9f81baac742054fbd065a6ffad5 --- /dev/null +++ b/docs/html-intl/intl/pt-br/guide/topics/resources/accessing-resources.jd @@ -0,0 +1,337 @@ +page.title=Acesso aos recursos +parent.title=Recursos de aplicativos +parent.link=index.html +@jd:body + +
+
+

Visualização rápida

+
    +
  • Recursos podem ser referenciados a partir do código usando números inteiros de {@code R.java}, como +{@code R.drawable.myimage}
  • +
  • Recursos podem ser referenciados de recursos usando uma sintaxe XML especial, como {@code +@drawable/myimage}
  • +
  • Também é possível acessar os recursos do aplicativo com métodos em +{@link android.content.res.Resources}
  • +
+ +

Classes principais

+
    +
  1. {@link android.content.res.Resources}
  2. +
+ +

Neste documento

+
    +
  1. Acesso aos recursos do código
  2. +
  3. Acesso aos recursos do XML +
      +
    1. Referência a atributos de estilo
    2. +
    +
  4. +
  5. Acesso aos recursos da plataforma
  6. +
+ +

Veja também

+
    +
  1. Fornecimento de recursos
  2. +
  3. Tipos de recursos
  4. +
+
+
+ + + + +

Depois de fornecer um recurso no aplicativo (discutido em Fornecimento de recursos), é possível aplicá-lo +referenciando seu ID de recurso. Todos os IDs de recursos são definidos na classe {@code R} do projeto, que +a ferramenta {@code aapt} gera automaticamente.

+ +

Quando o aplicativo é compilado, {@code aapt} gera a classe {@code R}, que contém +IDs de recursos para todos os recursos no diretório {@code +res/}. Para cada tipo de recurso, há uma subclasse {@code R} (por exemplo, +{@code R.drawable} para todos os recursos desenháveis) e, para cada recurso daquele tipo, há um número inteiro +estático (por exemplo, {@code R.drawable.icon}). Esse número inteiro é o ID do recurso que pode ser usado para +recuperá-lo.

+ +

Apesar de a classe {@code R} ser o local onde os IDs de recursos são especificados, não deve nunca ser necessário +verificá-la para descobrir um ID de recurso. Ele é sempre composto de:

+
    +
  • O tipo de recurso: cada recurso é agrupado em um "tipo", como {@code +string}, {@code drawable} e {@code layout}. Para saber mais sobre os diferentes tipos, consulte Tipos de recursos. +
  • +
  • O nome do recurso, que é: o nome do arquivo, +excluindo a extensão; ou o valor no atributo {@code android:name} do XML, se o +recurso for um valor simples (como uma string).
  • +
+ +

Há duas formas de acessar um recurso:

+
    +
  • No código: Usando um número inteiro estático de uma subclasse de sua classe {@code R}, +como: +
    R.string.hello
    +

    {@code string} é o tipo de recurso e {@code hello} é o nome do recurso. Há muitas +APIs do Android que podem acessar os seus recursos quando você fornece um ID de recurso nesse formato. Consulte +Acesso aos recursos no código.

    +
  • +
  • No XML: usando uma sintaxe XML especial que também corresponde ao +ID de recurso definido em sua classe {@code R}, como: +
    @string/hello
    +

    {@code string} é o tipo de recurso e {@code hello} é o nome do recurso. Você pode usar essa +sintaxe em um recurso XML em qualquer lugar em que um valor é esperado e que seja fornecido em um recurso. Consulte Acesso aos recursos do XML.

    +
  • +
+ + + +

Acesso aos recursos no código

+ +

Você pode usar um recurso no código passando o ID do recurso como um parâmetro do método. Por +exemplo, é possível definir uma {@link android.widget.ImageView} para usar o recurso {@code res/drawable/myimage.png} +usando {@link android.widget.ImageView#setImageResource(int) setImageResource()}:

+
+ImageView imageView = (ImageView) findViewById(R.id.myimageview);
+imageView.setImageResource(R.drawable.myimage);
+
+ +

Também é possível recuperar recursos individuais usando métodos em {@link +android.content.res.Resources}, dos quais é possível obter uma instância +com {@link android.content.Context#getResources()}.

+ + + + +

Sintaxe

+ +

Esta é a sintaxe para referenciar um recurso no código:

+ +
+[<package_name>.]R.<resource_type>.<resource_name>
+
+ +
    +
  • {@code <package_name>} é o nome do pacote no qual o recurso está localizado (não +é obrigatório ao referenciar recursos de seu próprio pacote).
  • +
  • {@code <resource_type>} é a subclasse {@code R} do tipo de recurso.
  • +
  • {@code <resource_name>} é o nome do arquivo do recurso +sem a extensão ou o valor do atributo {@code android:name} no elemento XML (para valores +simples).
  • +
+

Consulte Tipos de recursos para +obter mais informações sobre cada tipo de recurso e como referenciá-los.

+ + +

Casos de uso

+ +

Há muitos métodos que aceitam um parâmetro de ID de recurso e você pode recuperar recursos usando +métodos em {@link android.content.res.Resources}. É possível obter uma instância de {@link +android.content.res.Resources} com {@link android.content.Context#getResources +Context.getResources()}.

+ + +

Abaixo há alguns exemplos do acesso aos recursos no código:

+ +
+// Load a background for the current screen from a drawable resource
+{@link android.app.Activity#getWindow()}.{@link
+android.view.Window#setBackgroundDrawableResource(int)
+setBackgroundDrawableResource}(R.drawable.my_background_image) ;
+
+// Set the Activity title by getting a string from the Resources object, because
+//  this method requires a CharSequence rather than a resource ID
+{@link android.app.Activity#getWindow()}.{@link android.view.Window#setTitle(CharSequence)
+setTitle}(getResources().{@link android.content.res.Resources#getText(int)
+getText}(R.string.main_title));
+
+// Load a custom layout for the current screen
+{@link android.app.Activity#setContentView(int)
+setContentView}(R.layout.main_screen);
+
+// Set a slide in animation by getting an Animation from the Resources object
+mFlipper.{@link android.widget.ViewAnimator#setInAnimation(Animation)
+setInAnimation}(AnimationUtils.loadAnimation(this,
+        R.anim.hyperspace_in));
+
+// Set the text on a TextView object using a resource ID
+TextView msgTextView = (TextView) findViewById(R.id.msg);
+msgTextView.{@link android.widget.TextView#setText(int)
+setText}(R.string.hello_message);
+
+ + +

Atenção: nunca modifique o arquivo {@code +R.java} manualmente — ele é gerado pela ferramenta {@code aapt} quando o projeto é +compilado. As alterações serão sobrepostas na próxima compilação.

+ + + +

Acesso aos recursos do XML

+ +

Você pode definir valores para alguns atributos e elementos XML usando uma +referência a um recurso existente. Isso será feito com frequência ao criar arquivos de layout +para fornecer strings e imagens para os widgets.

+ +

Por enviar mensagem de texto, se você adicionar um {@link android.widget.Button} ao layout, deverá usar +um recurso de string para o texto do botão:

+ +
+<Button
+    android:layout_width="fill_parent"
+    android:layout_height="wrap_content"
+    android:text="@string/submit" />
+
+ + +

Sintaxe

+ +

Esta é a sintaxe para referenciar um recurso em um recurso XML:

+ +
+@[<package_name>:]<resource_type>/<resource_name>
+
+ +
    +
  • {@code <package_name>} é o nome do pacote no qual o recurso está localizado (não +é obrigatório ao referenciar recursos do mesmo pacote).
  • +
  • {@code <resource_type>} é a subclasse +{@code R} do tipo de recurso.
  • +
  • {@code <resource_name>} é o nome do arquivo do recurso +sem a extensão ou o valor do atributo {@code android:name} no elemento XML (para valores +simples).
  • +
+ +

Consulte Tipos de recursos para +obter mais informações sobre cada tipo de recurso e como referenciá-los.

+ + +

Casos de uso

+ +

Em alguns casos, é preciso usar um recurso para um valor em XML (por exemplo, para aplicar uma imagem desenhável +a um widget), mas você também pode usar um recurso em XML em qualquer lugar que aceite um valor simples. Por +exemplo, se você tiver o seguinte arquivo de recursos que inclui um recurso de cor e um recurso de string:

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+   <color name="opaque_red">#f00</color>
+   <string name="hello">Hello!</string>
+</resources>
+
+ +

É possível usar esses recursos no arquivo de layout a seguir para definir a cor to texto e a +string do texto:

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<EditText xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    android:textColor="@color/opaque_red"
+    android:text="@string/hello" />
+
+ +

Nesse caso, não é preciso especificar o nome do pacote na referência do recurso porque os +recursos são de seu próprio pacote. Para +referenciar um recurso do sistema, é preciso incluir o nome do pacote. Por exemplo:

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<EditText xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    android:textColor="@android:color/secondary_text_dark"
+    android:text="@string/hello" />
+
+ +

Observação: você deve usar recursos de string +o tempo inteiro para que o seu aplicativo possa ser localizado para outros idiomas. +Para obter informações sobre a criação de recursos +alternativos (como strings localizadas), consulte Fornecimento de recursos +alternativos. Para obter um guia completo para localizar o aplicativo para outros idiomas, +consulte Localização.

+ +

Você pode até mesmo usar recursos em XML para criar alias. Por exemplo, é possível criar um recurso +desenhável que seja um alias para outro recurso desenhável:

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
+    android:src="@drawable/other_drawable" />
+
+ +

Isso parece redundante, mas pode ser muito útil ao usar um recurso alternativo. Leia mais sobre +Criação de recursos de alias.

+ + + +

Referência a atributos de estilo

+ +

Um recurso de atributo de estilo permite referenciar o valor +de um atributo no tema atualmente aplicado. Referenciar um atributo de estilo permite +personalizar a aparência de elementos da IU deixando-os com estilo que corresponda a variações padrão fornecidas pelo +tema atual, em vez de fornecer um valor codificado. Referenciar um atributo de estilo +essencialmente significa "usar o estilo que é definido por esse atributo no tema atual".

+ +

Para referenciar um atributo de estilo, a sintaxe do nome é quase idêntica ao formato normal de recurso, +mas, em vez de o símbolo arroba ({@code @}), use um ponto de interrogação ({@code ?}). Além disso, +a parte do tipo de recurso é opcional. Por exemplo:

+ +
+?[<package_name>:][<resource_type>/]<resource_name>
+
+ +

Por exemplo, abaixo apresenta-se como você pode referenciar um atributo para definir a cor do texto para que corresponda à +cor "principal" do texto do tema do sistema:

+ +
+<EditText id="text"
+    android:layout_width="fill_parent"
+    android:layout_height="wrap_content"
+    android:textColor="?android:textColorSecondary"
+    android:text="@string/hello_world" />
+
+ +

Aqui, o atributo {@code android:textColor} especifica o nome de um atributo de estilo +no tema atual. O Android agora usa o valor aplicado ao atributo de estilo {@code android:textColorSecondary} +como o valor para {@code android:textColor} nesse widget. Como a ferramenta de recursos +do sistema sabe que um recurso de atributo é esperado nesse contexto, +não é preciso declarar explicitamente o tipo (que seria +?android:attr/textColorSecondary) — você pode excluir o tipo de {@code attr}.

+ + + + +

Acesso aos recursos da plataforma

+ +

O Android contém uma série de recursos padrão, como estilos, temas e layouts. Para +acessá-los, qualifique a referência de recurso com o +nome do pacote android. Por exemplo, o Android fornece um recurso de layout que pode ser usado para +listar itens em um {@link android.widget.ListAdapter}:

+ +
+{@link android.app.ListActivity#setListAdapter(ListAdapter)
+setListAdapter}(new {@link
+android.widget.ArrayAdapter}<String>(this, android.R.layout.simple_list_item_1, myarray));
+
+ +

Nesse exemplo, {@link android.R.layout#simple_list_item_1} é um recurso de layout definido pela +plataforma para itens em uma {@link android.widget.ListView}. Você pode usá-lo em vez de criar o +próprio layout para itens de lista. Para obter mais informações, consulte o +guia do desenvolvedor de Vista de lista.

+ diff --git a/docs/html-intl/intl/pt-br/guide/topics/resources/overview.jd b/docs/html-intl/intl/pt-br/guide/topics/resources/overview.jd new file mode 100644 index 0000000000000000000000000000000000000000..5bf37e691376c2ca7ace746aa463be9a7ed4dad0 --- /dev/null +++ b/docs/html-intl/intl/pt-br/guide/topics/resources/overview.jd @@ -0,0 +1,103 @@ +page.title=Visão geral dos recursos +@jd:body + + + + +

Deve-se sempre exteriorizar os recursos do aplicativo, como imagens e strings do código do aplicativo, +para que você possa mantê-los independentemente. Exteriorizar os recursos +também permite fornecer recursos alternativos que sejam compatíveis com configurações +de dispositivos específicos, como idiomas ou tamanhos de tela diferentes, que se tornam cada vez +mais importantes à medida que mais dispositivos com Android são disponibilizados com configurações diferentes. Para fornecer +compatibilidade com diferentes configurações, é preciso organizar recursos no +diretório {@code res/} de seu projeto usando vários subdiretórios que agrupem recursos por tipo e +configuração.

+ +
+ +

+Figura 1. Dois dispositivos diferentes, cada um usando o layout padrão +(o aplicativo não fornece layouts alternativos).

+
+ +
+ +

+Figura 2. Dois dispositivos diferentes, cada um usando um layout diferente fornecido +para diferentes tamanhos de tela.

+
+ +

Para qualquer tipo de recurso, é possível especificar recursos padrão e vários recursos +alternativos para o aplicativo:

+
    +
  • Recursos padrão são aqueles que devem ser usados independentemente +da configuração do dispositivo ou quando não há recursos alternativos que correspondam à +configuração atual.
  • +
  • Recursos alternativos são aqueles projetados para uso com uma configuração +específica. Para definir que um grupo de recursos é para uma configuração específica, +anexe um qualificador de configuração apropriado ao nome do diretório.
  • +
+ +

Por exemplo, enquanto o layout da IU padrão +é salvo no diretório {@code res/layout/}, é possível especificar um layout diferente +a ser usado quando a tela está na orientação de paisagem salvando-o no diretório {@code res/layout-land/} +. O Android automaticamente aplica os recursos adequados correspondendo +a configuração atual do dispositivo com os nomes de diretórios de recursos.

+ +

A figura 1 ilustra como o sistema aplica o mesmo layout para dois +dispositivos diferentes quando não há recursos alternativos disponíveis. A figura 2 mostra +o mesmo aplicativo quando é adicionado um recurso de layout alternativo para telas maiores.

+ +

Os documentos a seguir fornecem um guia completo sobre como organizar os recursos do aplicativo, +especificar recursos alternativos, acessá-los no aplicativo e muito mais:

+ +
+
Como fornecer recursos
+
Os tipos de recursos que você pode fornecer no aplicativo, onde salvá-los e como criar +recursos alternativos para configurações específicas de dispositivos.
+
Acesso aos recursos
+
Como usar os recursos que você forneceu referenciando-os no código do aplicativo +ou de outros recursos XML.
+
Tratar alterações no tempo de execução
+
Como gerenciar alterações de configuração que ocorrem enquanto a Atividade está em execução.
+
Localização
+
Um guia ascendente para localizar o aplicativo usando recursos alternativos. Apesar de esse ser +apenas um uso específico de recursos alternativos, ele é muito importante para atingir mais +usuários.
+
Tipos de recursos
+
Uma referência a vários tipos de recursos que você pode fornecer, descrevendo os elementos XML, +os atributos e a sintaxe. Por exemplo, esta referência mostra como criar um recurso +para menus do aplicativo, desenháveis, animações e mais.
+
+ + diff --git a/docs/html-intl/intl/pt-br/guide/topics/resources/providing-resources.jd b/docs/html-intl/intl/pt-br/guide/topics/resources/providing-resources.jd new file mode 100644 index 0000000000000000000000000000000000000000..1118fd536b32e90658cd5550636b3ba783e8e69a --- /dev/null +++ b/docs/html-intl/intl/pt-br/guide/topics/resources/providing-resources.jd @@ -0,0 +1,1094 @@ +page.title=Fornecimento de recursos +parent.title=Recursos de aplicativo +parent.link=index.html +@jd:body + +
+
+

Visualização rápida

+
    +
  • Tipos diferentes de recursos pertencem a subdiretórios diferentes de {@code res/}
  • +
  • Recursos alternativos fornecem arquivos de recurso específicos de configuração
  • +
  • Sempre inclua recursos padrão para que o aplicativo não dependa de configurações +específicas do dispositivo
  • +
+

Neste documento

+
    +
  1. Agrupamento de tipos de recursos
  2. +
  3. Fornecimento de recursos alternativos +
      +
    1. Regras de nome do qualificador
    2. +
    3. Criação de recursos de alias
    4. +
    +
  4. +
  5. Fornecimento da melhor compatibilidade de dispositivo com recursos
  6. +
  7. Como o Android encontra o melhor recurso compatível
  8. +
+ +

Veja também

+
    +
  1. Acesso aos recursos
  2. +
  3. Tipos de recursos
  4. +
  5. Compatibilidade com +várias telas
  6. +
+
+
+ +

Deve-se sempre exteriorizar os recursos do aplicativo, como imagens e strings do código, +para que você possa mantê-los independentemente. Deve-se também fornecer recursos alternativos para +configurações específicas do dispositivo, agrupando-os em diretórios de recursos especialmente nomeados. Em +tempo de execução, o Android usa o recurso adequado com base na configuração atual. Por +exemplo, você pode querer fornecer um layout de IU diferente dependendo do tamanho da tela ou +strings diferentes dependendo da configuração de idioma.

+ +

Ao exteriorizar os recursos do aplicativo, é possível acessá-los +usando IDs de recurso que são gerados na classe {@code R} do projeto. O procedimento para usar +recursos no aplicativo é discutido em Acesso aos +recursos. Este documento mostra como agrupar os recursos no projeto do Android +e fornecer recursos alternativos para configurações específicas do dispositivo.

+ + +

Agrupamento de tipos de recursos

+ +

Você deve posicionar cada tipo de recurso em um subdiretório específico do diretório +{@code res/} do projeto. Por exemplo, abaixo está a hierarquia de arquivos para um projeto simples:

+ +
+MyProject/
+    src/  
+        MyActivity.java  
+    res/
+        drawable/  
+            graphic.png  
+        layout/  
+            main.xml
+            info.xml
+        mipmap/  
+            icon.png 
+        values/  
+            strings.xml  
+
+ +

Como pode ver neste exemplo, o diretório {@code res/} contém todos os recursos (em subdiretórios): +um recurso de imagem, dois recursos de layout, diretórios {@code mipmap/} para ícones de +inicialização e um arquivo de recurso de string. Os nomes dos diretórios +de recursos são importantes e são descritos na tabela 1.

+ +

Observação: para obter mais informações sobre o uso de pastas de mipmap, consulte +Visão geral do gerenciamento de projetos.

+ +

Tabela 1. Os diretórios de recursos +compatíveis dentro do diretório {@code res/} do projeto.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
DiretórioTipo de recurso
animator/Arquivos XML que definem as animações +da propriedade.
anim/Arquivos XML que definem as animações +intermediárias. (As animações de propriedade também podem ser salvas neste diretório, +mas o diretório {@code animator/} é o preferencial para animações de propriedade para distinguir os dois +tipos.)
color/Arquivos XML que definem uma lista de estado de cores. Consulte Recurso de +lista de estado de cores
drawable/

Os arquivos Bitmap ({@code .png}, {@code .9.png}, {@code .jpg}, {@code .gif}) ou arquivos XML +são compilados nos seguintes subtipos de recurso desenhável:

+
    +
  • Arquivos Bitmap
  • +
  • Nine-Patch (bitmaps redimensionáveis)
  • +
  • Listas de estado
  • +
  • Formatos
  • +
  • Desenháveis de animação
  • +
  • Outros desenháveis
  • +
+

Veja Recursos desenháveis.

+
mipmap/Arquivos desenháveis para diferentes densidades do ícone do inicializador. Para obter mais informações sobre o gerenciamento de + ícones do inicializador com pastas {@code mipmap/}, consulte + Visão geral do gerenciamento de projetos.
layout/Arquivos XML que definem um layout de interface do usuário. + Consulte Recurso de layout.
menu/Arquivos XML que definem os menus do aplicativo, como Menu de opções, Menu de contexto +ou Submenu. Consulte Recurso de menu.
raw/

Arquivos arbitrários para salvar na forma bruta. Para abrir esses recursos +com {@link java.io.InputStream} bruto, chame {@link android.content.res.Resources#openRawResource(int) +Resources.openRawResource()} com o ID do recurso, que é {@code R.raw.filename}.

+

No entanto, caso precise de acesso aos nomes e à hierarquia dos arquivos originais, considere salvar +alguns recursos no diretório {@code +assets/} (em vez de {@code res/raw/}). Os arquivos em {@code assets/} não recebem um ID de recurso, +então é possível lê-los usando apenas o {@link android.content.res.AssetManager}.

values/

Arquivos XML que contêm valores simples, como strings, números inteiros e cores.

+

Enquanto os arquivos de recurso XML estiverem em outros subdiretórios {@code res/}, defina um único recurso +com base no nome do arquivo XML, os arquivos no diretório {@code values/} descrevem vários recursos. +Para cada arquivo neste diretório, cada filho do elemento {@code <resources>} define um único +recurso. Por exemplo, um elemento {@code <string>} cria +um recurso {@code R.string} e um elemento {@code <color>} cria um recurso +{@code R.color}.

+

Como cada recurso é definido com seu próprio elemento XML, é possível nomear o arquivo +da forma que quiser e colocar tipos de recurso variados em um arquivo. No entanto, para esclarecer, você pode +querer colocar tipos de recursos únicos em arquivos diferentes. Por exemplo, a seguir há algumas convenções +de nome de arquivo para recursos que podem ser criados neste diretório:

+ +

Consulte Recursos de string, + Recurso de estilo + e Mais tipos de recursos.

+
xml/Arquivos arbitrários XML que podem ser lidos em tempo de execução chamando {@link +android.content.res.Resources#getXml(int) Resources.getXML()}. Vários arquivos de configuração XML +devem ser salvos aqui, como uma configuração buscável. +
+ +

Atenção: nunca salve arquivos de recurso diretamente no diretório +{@code res/} — isto provocará um erro ao compilador.

+ +

Para obter mais informações sobre determinados tipos de recursos, consulte a documentação Tipos de recursos.

+ +

Os recursos salvos nos subdiretórios definidos na tabela 1 são os recursos +"padrão". Ou seja, esses recursos definem projeto e conteúdo padrão para o aplicativo. +No entanto, tipos diferentes de dispositivos com Android podem chamar diferentes tipos de recursos. +Por exemplo: se um dispositivo tiver uma tela maior do que o normal, deve-se fornecer +recursos de layout diferentes que aproveitem o espaço extra na tela. Ou, se um dispositivo tiver +uma configuração de idioma diferente, deve-se fornecer recursos de string diferentes que traduzam +o texto na interface do usuário. Para fornecer esses diferentes recursos para configurações de dispositivo +diferentes, você precisa fornecer recursos alternativos, além dos recursos +padrão.

+ + +

Fornecimento de recursos alternativos

+ + +
+ +

+Figura 1. Dois dispositivos diferentes, cada um usando recursos diferentes de layout.

+
+ +

Quase todos os aplicativos devem fornecer recursos alternativos para suportar +configurações específicas do dispositivo. Por exemplo: deve-se incluir recursos desenháveis alternativos para densidades +de tela diferentes e recursos alternativos de string para idiomas diferentes. Em tempo de execução, +o Android detecta a configuração atual do dispositivo e carrega os recursos +adequados para o aplicativo.

+ +

Para especificar as alternativas de configuração específica para um conjunto de recursos:

+
    +
  1. Crie um novo diretório no {@code res/} nomeado na forma de {@code +<resources_name>-<config_qualifier>}. +
      +
    • {@code <resources_name>} é o nome do diretório dos recursos padrão correspondentes +(definido na tabela 1).
    • +
    • {@code <qualifier>} é um nome que especifica uma configuração individual +para a qual esses recursos destinam-se (definido na tabela 2).
    • +
    +

    É possível anexar mais de um {@code <qualifier>}. Separe cada +um com um travessão.

    +

    Atenção: ao anexar vários qualificadores, deve-se +colocá-los na mesma ordem em que foram listados na tabela 2. Se os qualificadores forem ordenados +de forma incorreta, os recursos serão ignorados.

    +
  2. +
  3. Salve os respectivos recursos alternativos neste novo diretório. Os arquivos de recurso devem ser +nomeados exatamente da mesma forma que os arquivos de recurso padrão.
  4. +
+ +

Por exemplo, a seguir há alguns recursos alternativos e outros padrão:

+ +
+res/
+    drawable/   
+        icon.png
+        background.png    
+    drawable-hdpi/  
+        icon.png
+        background.png  
+
+ +

O qualificador {@code hdpi} indica que os recursos neste diretório são para os dispositivos +com tela de alta densidade. As imagens em cada um desses diretórios desenháveis são dimensionadas para uma densidade de tela +específica, mas os nomes dos arquivos +são exatamente os mesmos. Desta maneira, o ID de recurso usado para referenciar {@code icon.png} ou a imagem {@code +background.png} é sempre o mesmo, mas o Android seleciona +a versão de cada arquivo que melhor compatibiliza-se com o dispositivo atual, comparando as informações de configuração +com os qualificadores no nome do diretório do recurso.

+ +

O Android é compatível com vários qualificadores de configuração e é possível +adicionar vários qualificadores a um nome de diretório separando cada qualificador com um travessão. A tabela 2 +lista os qualificadores de configuração válidos, em ordem de precedência — caso use vários +qualificadores para um diretório de recursos, você deve adicioná-los ao nome do diretório na ordem que foram listados +na tabela.

+ + +

Tabela 2. Nomes de qualificadores +de configuração.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ConfiguraçãoValores do qualificadorDescrição
MCC e MNCExemplos:
+ mcc310
+ mcc310-mnc004
+ mcc208-mnc00
+ etc. +
+

O código de dispositivos móveis do país (MCC), opcionalmente seguido do código de rede móvel (MNC) + do cartão SIM no dispositivo. Por exemplo, mcc310 é dos E.U.A. em qualquer operadora, + mcc310-mnc004 é dos E.U.A., em Verizon, e mcc208-mnc00 é da França, + em Orange.

+

Se o dispositivo usar uma conexão de rádio (telefone GSM), os valores MCC e MNC serão + os do cartão SIM.

+

Você também pode usar somente o MCC (por exemplo, para incluir recursos legais específicos do país +no aplicativo). Caso precise especificar com base somente no idioma, use o qualificador +de idioma e região (discutido a seguir). Caso decida usar o qualificador +de MCC e MNC, tome cuidado e teste o seu funcionamento.

+

Veja também os campos de configuração {@link +android.content.res.Configuration#mcc} e {@link +android.content.res.Configuration#mnc}, que indicam o código de dispositivos móveis do país atual +e o código de rede móvel, respectivamente.

+
Idioma e regiãoExemplos:
+ en
+ fr
+ en-rUS
+ fr-rFR
+ fr-rCA
+ etc. +

O idioma é definido por um código de idiomaISO + 639-1 de duas letras, opcionalmente seguido por um código da região + ISO + 3166-1-alpha-2 (precedido de "{@code r}" em minúsculo). +

+ Os códigos não diferenciam maiúsculas e minúsculas; o prefixo {@code r} é usado + para distinguir a parte da região. + Não é possível especificar uma região só.

+

Isto pode mudar durante a vida útil +do aplicativo se o usuário mudar o idioma nas configurações do sistema. Consulte Tratamento de alterações em tempo de execução para obter informações +sobre como isto pode afetar o aplicativo em tempo de execução.

+

Consulte Localização para obter um guia completo para localizar +os aplicativos para outros idiomas.

+

Veja também o campo de configuração {@link android.content.res.Configuration#locale}, +que indica a localidade atual.

+
Direção do layoutldrtl
+ ldltr
+

A direção do layout do aplicativo. {@code ldrtl} significa "layout-direction-right-to-left" (direção do layout da direita para a esquerda). + {@code ldltr} significa "layout-direction-left-to-right" (direção do layout da esquerda para a direita) e é o valor implícito padrão. +

+

Isto pode aplicar-se a qualquer recurso, como layouts, desenháveis ou valores. +

+

Por exemplo, caso queira fornecer um layout específico para o idioma arábico e + layouts genéricos para outros idiomas que seguem a leitura da direita para a esquerda, como os idiomas hebreu e persa, então você teria: +

+
+res/
+    layout/   
+        main.xml  (Default layout)
+    layout-ar/  
+        main.xml  (Specific layout for Arabic)
+    layout-ldrtl/  
+        main.xml  (Any "right-to-left" language, except
+                  for Arabic, because the "ar" language qualifier
+                  has a higher precedence.)
+
+

Observação: para ativar recursos de layout de leitura da direita para a esquerda + para o aplicativo, você deve definir {@code + supportsRtl} como {@code "true"} e {@code targetSdkVersion} como 17 ou maior.

+

Adicionado à API de nível 17.

+
smallestWidthsw<N>dp

+ Exemplos:
+ sw320dp
+ sw600dp
+ sw720dp
+ etc. +
+

O tamanho fundamental de uma tela, como indicado pela menor dimensão da área da tela +disponível. Especificamente, o valor smallestWidth do dispositivo é o menor da altura e largura +da tela (pode-se também interpretar isso como a "menor largura possível" da tela). É possível +usar este qualificador para garantir que, independentemente da orientação atual da tela, +o aplicativo tenha pelo menos {@code <N>} dps de largura disponível para a IU.

+

Por exemplo, se o seu layout exigir que a menor dimensão da área da tela seja de pelo menos +600 dp, é possível usar o seguinte qualificador para criar os recursos do layout: {@code +res/layout-sw600dp/}. O sistema usará esses recursos somente quando a menor dimensão +da tela disponível for de pelo menos 600 dp, independentemente se o lado de 600 dp é a altura ou a largura +percebida pelo usuário. O valor smallestWidth é uma característica fixa do tamanho da tela do dispositivo;o valor smallestWidth +do dispositivo não altera quando a orientação da tela muda.

+

O smallestWidth de um dispositivo considera a IU do sistema e as decorações da tela. Por exemplo, +se o dispositivo tiver alguns elementos de IU persistentes na tela que considera o espaço ao longo do eixo +de smallestWidth, o sistema declara que smallestWidth é menor do que o tamanho +atual da tela, pois são pixels de tela não disponíveis para a IU. Portanto, o valor usado +deve ser a dimensão real menor necessária para o layout (geralmente, este valor +é a "menor largura" compatível com o layout, independente da orientação atual da tela).

+

Alguns dos valores que você pode usar para tamanhos de tela comuns:

+
    +
  • 320, para dispositivos com configurações de tela como: +
      +
    • 240 x 320 ldpi (celular QVGA)
    • +
    • 320 x 480 mdpi (celular)
    • +
    • 480 x 800 hdpi (celular de alta densidade)
    • +
    +
  • +
  • 480, para telas como 480 x 800 mdpi (tablet/celular).
  • +
  • 600, para telas como 600 x 1024 mdpi (tablet de 7 polegadas).
  • +
  • 720, para telas como 720 x 1280 mdpi (tablet de 10 polegadas).
  • +
+

Quando o aplicativo fornece vários diretórios de recursos com valores diferentes + para o qualificador smallestWidth, o sistema usa o mais próximo (sem exceder) ao +de smallestWidth do dispositivo.

+

Adicionado à API de nível 13.

+

Veja também o atributo {@code +android:requiresSmallestWidthDp}, que declara a smallestWidth mínima compatível +com o aplicativo e o campo de configuração {@link +android.content.res.Configuration#smallestScreenWidthDp}, que retém +o valor de smallestWidth do dispositivo.

+

Para obter mais informações sobre como projetar para telas diferentes e usar +este qualificador, consulte o guia do desenvolvedor Compatibilidade com +várias telas.

+
Largura disponívelw<N>dp

+ Exemplos:
+ w720dp
+ w1024dp
+ etc. +
+

Especifica uma largura mínima disponível da tela, em unidades {@code dp} em que o recurso + deve ser usado — definido pelo valor <N>. Este valor + de configuração mudará quando a orientação + alternar entre paisagem e retrato para corresponder à largura atual.

+

Quando o aplicativo fornece vários diretórios de recurso com valores diferentes + para esta configuração, o sistema usa o mais próximo (sem exceder) + da largura atual da tela do dispositivo. O + valor aqui considera as decorações da tela. Portanto, se o dispositivo tiver alguns + elementos de IU persistentes na borda esquerda ou direita da tela, ele usa + um valor para a largura menor do que o tamanho atual da tela, considerando + esses elementos de IU e reduzindo o espaço disponível do aplicativo.

+

Adicionado à API de nível 13.

+

Veja também o campo de configuração {@link android.content.res.Configuration#screenWidthDp}, + que possui a largura atual da tela.

+

Para obter mais informações sobre como projetar para telas diferentes e usar +este qualificador, consulte o guia do desenvolvedor Compatibilidade com +várias telas.

+
Altura disponívelh<N>dp

+ Exemplos:
+ h720dp
+ h1024dp
+ etc. +
+

Especifica uma altura mínima disponível da tela, em unidades "dp" em que o recurso + deve ser usado — definido pelo valor <N>. Este valor + de configuração mudará quando a orientação + alternar entre paisagem e retrato para corresponder à altura atual.

+

Quando o aplicativo fornece vários diretórios de recursos com valores diferentes + para esta configuração, o sistema usa o mais próximo (sem exceder) + da altura atual da tela do dispositivo. O + valor aqui considera as decorações da tela. Portanto, se o dispositivo tiver alguns + elementos de IU persistentes na borda superior ou inferior da tela, ele usa + um valor para a altura menor do que o tamanho atual da tela, considerando + esses elementos da IU e reduzindo o espaço disponível do aplicativo. As decorações da tela + que não forem fixas (como uma barra de status do telefone que pode ser + ocultada com tela cheia) não são consideradas aqui, assim como + as decorações da janela, como a barra de título ou a barra de ação. Portanto, os aplicativos devem ser preparados para + lidar com o espaço um pouco menor do que especificam. +

Adicionado à API de nível 13.

+

Veja também o campo de configuração {@link android.content.res.Configuration#screenHeightDp}, + que possui a largura atual da tela.

+

Para obter mais informações sobre como projetar para telas diferentes e usar +este qualificador, consulte o guia do desenvolvedor Compatibilidade com +várias telas.

+
Tamanho da tela + small
+ normal
+ large
+ xlarge +
+
    +
  • {@code small}: Telas de tamanho semelhante + à tela de pouca densidade QVGA. O tamanho mínimo do layout para uma tela pequena + é de aproximadamente 320 x 426 unidades dp. Exemplos são QVGA de pouca densidade e VGA de alta + densidade.
  • +
  • {@code normal}: Telas de tamanho semelhante + à tela de média densidade HVGA. O tamanho mínimo do layout para uma tela normal + é de aproximadamente 320 x 470 unidades dp. Exemplos + de tais delas são as WQVGA de pouca densidade, HVGA de média densidade e WVGA + de alta densidade.
  • +
  • {@code large}: Telas de tamanho semelhante + à tela de média densidade VGA. + O tamanho mínimo do layout para uma tela grande é de aproximadamente 480 x 640 unidades dp. + Exemplos são as telas de densidade média VGA e WVGA.
  • +
  • {@code xlarge}: Telas que são consideravelmente maiores do que a + tela tradicional de média densidade HVGA. O tamanho mínimo do layout para uma tela muito grande + é de aproximadamente 720x960 unidades dp. Na maioria dos casos, dispositivos com telas + muito grandes seriam grandes demais para serem carregados em bolsos e, provavelmente, + seriam dispositivos no estilo tablet. Adicionado à API de nível 9.
  • +
+

Observação: usar um qualificador de tamanho não significa +que os recursos sejam apenas para telas deste tamanho. Caso não forneça recursos +alternativos com qualificadores que melhor correspondem à configuração atual do dispositivo, o sistema poderá usar +quaisquer recursos que representarem a melhor correspondência.

+

Atenção: se todos os recursos usarem um qualificador de tamanho +maior do que a tela atual, o sistema não os usará +e o aplicativo apresentará um erro em tempo de execução (por exemplo, se todos os recursos de layout receberem tag com o qualificador {@code +xlarge}, mas o dispositivo tiver uma tela de tamanho normal).

+

Adicionado à API de nível 4.

+ +

Consulte Compatibilidade com +várias telas para obter mais informações.

+

Consulte também o campo de configuração {@link android.content.res.Configuration#screenLayout}, +que indica se a tela é pequena, normal +ou grande.

+
Aspecto da tela + long
+ notlong +
+
    +
  • {@code long}: Telas grandes, como WQVGA, WVGA, FWVGA
  • +
  • {@code notlong}: Telas que não são grandes, como QVGA, HVGA e VGA
  • +
+

Adicionado à API de nível 4.

+

Isto baseia-se puramente na relação de aspecto da tela (uma tela "grande" é mais larga). Isto +não está relacionado à orientação da tela.

+

Consulte também o campo de configuração {@link android.content.res.Configuration#screenLayout}, +que indica se a tela é grande.

+
Orientação da tela + port
+ land +
+
    +
  • {@code port}: O dispositivo está na orientação de retrato (vertical)
  • +
  • {@code land}: O dispositivo está na orientação de paisagem (horizontal)
  • + +
+

Isto pode mudar durante a vida útil do aplicativo se o usuário girar +a tela. Consulte Tratamento de alterações em tempo de execução para obter informações +sobre como isto pode afetar o aplicativo em tempo de execução.

+

Veja também o campo de configuração {@link android.content.res.Configuration#orientation}, +que indica a orientação atual do dispositivo.

+
Modo de IU + car
+ desk
+ television
+ appliance + watch +
+
    +
  • {@code car}: O dispositivo está exibindo em uma estação de acoplamento de carro
  • +
  • {@code desk}: O dispositivo está exibindo em uma estação de acoplamento de mesa
  • +
  • {@code television}: O dispositivo está exibindo em uma televisão, fornecendo + uma experiência à distância, onde a IU é em tela grande, + o usuário está longe, orientado principalmente por um controle direcional ou por outro tipo de + interação sem indicador
  • +
  • {@code appliance}: O dispositivo está servindo como uma aplicação, + sem tela
  • +
  • {@code watch}: O dispositivo tem uma tela que é usada no braço
  • +
+

Adicionado à API de nível 8, televisão adicionada à API de nível 13 e relógio adicionado à API de nível 20.

+

Para obter informações sobre como o aplicativo pode responder quando o dispositivo é inserido + ou removido de um dock, consulte Determinação +e monitoramento do tipo e do estado do dock.

+

Isto pode mudar durante a vida útil do aplicativo se o usuário colocar o dispositivo +em um dock. É possível ativar ou desativar alguns desses modos usando {@link +android.app.UiModeManager}. Consulte Tratamento de alterações em tempo de execução para obter informações +sobre como isto pode afetar o aplicativo em tempo de execução.

+
Modo noturno + night
+ notnight +
+
    +
  • {@code night}: Noite
  • +
  • {@code notnight}: Dia
  • +
+

Adicionado à API de nível 8.

+

Isto pode mudar durante a vida útil do aplicativo se o modo noturno for deixado +no modo automático (padrão), em que o modo altera-se com base no horário. É possível ativar +ou desativar este modo usando {@link android.app.UiModeManager}. Consulte Tratamento de alterações em tempo de execução para obter informações +sobre como isto pode afetar o aplicativo em tempo de execução.

+
Densidade de pixel da tela (dpi) + ldpi
+ mdpi
+ hdpi
+ xhdpi
+ xxhdpi
+ xxxhdpi
+ nodpi
+ tvdpi +
+
    +
  • {@code ldpi}: Telas de pouca densidade, aproximadamente 120 dpi.
  • +
  • {@code mdpi}: Telas de média densidade (em HVGA tradicional); aproximadamente +160 dpi.
  • +
  • {@code hdpi}: Telas de alta densidade, aproximadamente 240 dpi.
  • +
  • {@code xhdpi}: Telas de densidade extra-alta, aproximadamente 320 dpi. Adicionado à API de +nível 8
  • +
  • {@code xxhdpi}: Telas de densidade extra-extra-alta, aproximadamente 480 dpi. Adicionado à API de +nível 16
  • +
  • {@code xxxhdpi}: Usos de densidade extra-extra-extra-alta (somente ícone do inicializador, consulte a + observação + em Compatibilidade com várias telas), aproximadamente 640 dpi. Adicionado à API de +nível 18
  • +
  • {@code nodpi}: Isto pode ser usado para recursos de bitmap que você não deseja dimensionar +para corresponder à densidade do dispositivo.
  • +
  • {@code tvdpi}: Telas entre mdpi e hdpi, aproximadamente 213 dpi. Não é considerado +um grupo de densidade "principal". Geralmente usado para televisões +e a maioria dos aplicativos não precisam — fornecer recursos mdpi e hdpi é o suficiente para a maioria dos aplicativos +e o sistema dimensionará de forma adequada. Este qualificador foi introduzido com a API de nível 13.
  • +
+

Há uma razão de dimensionamento de 3:4:6:8:12:16 entre as seis densidades principais (ignorando +a densidade tvdpi). Então, um bitmap de 9 x 9 em ldpi é 12 x 12 em mdpi, 18 x 18 em hdpi, 24 x 24 em xhdpi e por aí em diante. +

+

Caso decida que os recursos de imagem não parecem suficientemente bons para uma televisão +ou outros dispositivos e queira testar recursos tvdpi, o fator de dimensionamento é 1,33*mdpi. Por exemplo: +uma imagem de 100 px x 100 px para telas mdpi deve ser de 133 px x 133 px para tvdpi.

+

Observação: usar um qualificador de densidade não significa +que os recursos sejam apenas para telas desta densidade. Caso não forneça recursos +alternativos com qualificadores que melhor correspondem à configuração atual do dispositivo, o sistema poderá usar +quaisquer recursos que representarem a melhor correspondência.

+

Consulte Compatibilidade com +várias telas para obter mais informações sobre como lidar com as diferentes densidades de tela e como o Android +pode dimensionar os bitmaps para encaixá-los na densidade atual.

+
Tipo de tela sensível ao toque + notouch
+ finger +
+
    +
  • {@code notouch}: Os dispositivos não têm uma tela sensível ao toque.
  • +
  • {@code finger}: O dispositivo tem uma tela sensível ao toque que destina-se + à interação direcional do dedo do usuário.
  • +
+

Veja também o campo de configuração {@link android.content.res.Configuration#touchscreen}, +que indica o tipo de tela sensível ao toque no dispositivo.

+
Disponibilidade de teclado + keysexposed
+ keyshidden
+ keyssoft +
+
    +
  • {@code keysexposed}: O dispositivo tem um teclado disponível. Se o dispositivo tiver um teclado de software +ativo (o que é provável), ele deve ser usado mesmo quando o teclado de hardware +não estiver exposto ao usuário, mesmo se o dispositivo não tiver teclado de hardware. Caso nenhum teclado de software +seja fornecido ou esteja desativado, então isto será usado apenas quando um teclado de hardware +for exposto.
  • +
  • {@code keyshidden}: O dispositivo tem um teclado de hardware disponível, +mas está oculto e o dispositivo não tem um teclado de software ativo.
  • +
  • {@code keyssoft}: O dispositivo tem um teclado de software ativo, +visível ou não.
  • +
+

Se você fornecer os recursos keysexposed, mas não os recursos keyssoft, + o sistema usará os recursos keysexposed independente da visibilidade +do teclado, contanto que o sistema tenha um teclado de software ativo.

+

Isto pode mudar durante a vida útil do aplicativo se o usuário abrir um teclado +de hardware. Consulte Tratamento de alterações em tempo de execução para obter informações +sobre como isto pode afetar o aplicativo em tempo de execução.

+

Veja também os campos de configuração {@link +android.content.res.Configuration#hardKeyboardHidden} e {@link +android.content.res.Configuration#keyboardHidden}, que indicam a visibilidade de um teclado de hardware +e a visibilidade de qualquer tipo de teclado (incluindo software), respectivamente.

+
Método principal de entrada de texto + nokeys
+ qwerty
+ 12key +
+
    +
  • {@code nokeys}: O dispositivo não tem teclas de hardware para entradas de texto.
  • +
  • {@code qwerty}: O dispositivo tem um teclado QWERTY de hardware, esteja ele visível ao +usuário +ou não.
  • +
  • {@code 12key}: O dispositivo tem um teclado de hardware de 12 teclas, esteja ele visível ao usuário +ou não.
  • +
+

Veja também o campo de configuração {@link android.content.res.Configuration#keyboard}, +que indica o método de entrada de texto principal disponível.

+
Versão da plataforma (nível de API)Exemplos:
+ v3
+ v4
+ v7
+ etc.
+

O nível de API suportado pelo dispositivo. Por exemplo, v1 para a API de nível 1 +(dispositivos com Android 1.0 ou mais recente) e v4 para API de nível 4 (dispositivos com Android +1.6 ou mais recente). Veja o documento Níveis de API do Android para obter mais informações +sobre esses valores.

+
+ + +

Observação: alguns qualificadores de configuração foram adicionados desde o Android 1.0, + então nem todas as versões do Android suportam todos eles. Usar um novo qualificador +adiciona implicitamente um qualificador da versão de plataforma, então dispositivos mais antigos com certeza o ignorarão. Por exemplo, usar +um qualificador w600dp incluirá automaticamente o qualificador v13, +pois o qualificador de largura disponível era novo na API de nível 13. Para evitar quaisquer problemas, sempre inclua um conjunto +de recursos padrão (um conjunto de recursos sem qualificadores). Para obter mais informações, consulte +a seção Fornecimento da melhor compatibilidade de dispositivo com +recursos.

+ + + +

Regras de nome do qualificador

+ +

A seguir há algumas regras sobre como usar nomes de qualificador de configuração:

+ +
    +
  • É possível especificar vários qualificadores para um único conjunto de recursos, separados por travessões. Por exemplo, +drawable-en-rUS-land aplica-se aos dispositivos em inglês dos E.U.A. +na orientação de paisagem.
  • +
  • Os qualificador devem estar na ordem listada na tabela 2. Por +exemplo: +
      +
    • Incorreto: drawable-hdpi-port/
    • +
    • Correto: drawable-port-hdpi/
    • +
    +
  • +
  • Os diretórios de recursos alternativos não podem ser aninhados. Por exemplo, não é possível ter +res/drawable/drawable-en/.
  • +
  • Os valores não diferenciam letras maiúsculas e minúsculas. O compilador de recursos converte nomes de diretório + para letras minúsculas antes de processar para evitar problemas nos sistemas de arquivo + que não diferenciam maiúsculas e minúsculas. Qualquer letra maiúscula nos nomes é apenas para o benefício da leitura.
  • +
  • Somente um valor para cada tipo de qualificador é suportado. Por exemplo, se quiser usar +os mesmos arquivos desenháveis para Espanha e França, não é possível ter um diretório chamado +drawable-rES-rFR/. Em vez disso, você precisa de dois diretórios de recursos, como +drawable-rES/ e drawable-rFR/, que contenham arquivos adequados. +No entanto, não é necessário duplicar os mesmos arquivos em ambos os locais. Em vez disso, +é possível criar um alias para um recurso. Consulte Criação de recursos +de alias abaixo.
  • +
+ +

Após salvar os recursos alternativos nos diretórios nomeados +com esses qualificadores, o Android aplicará automaticamente os recursos no aplicativo com base +na configuração atual do dispositivo. Sempre que um recurso for solicitado, o Android verificará diretórios de recursos alternativos +que contenham o arquivo de recurso solicitado e, em seguida,encontrará o melhor +recurso correspondente (discutido abaixo). Se não houver recursos alternativos que correspondam +a uma configuração de dispositivo específica, o Android usará os recursos padrão correspondentes +(o conjunto de recursos para um tipo de recurso específico que não inclua um qualificador +de configuração).

+ + + +

Criação de recursos de alias

+ +

Quando estiver com um recurso que gostaria de usar para mais +de uma configuração de dispositivo, mas não quer fornecê-lo como um recurso padrão), não será necessário usar o mesmo +recurso em mais de um diretório de recursos alternativos. Em vez disso, é possível (em alguns casos) criar um +recurso +alternativo que age como um alias para um recurso salvo no diretório de recurso padrão.

+ +

Observação: nem todos os recursos oferecem um mecanismo que possibilita +criar um alias para outro recurso. Em particular, recursos de animação, de menu, brutos +e de outros tipos no diretório {@code xml/} não oferecem esta função.

+ +

Por exemplo, imagine que você possui um ícone do aplicativo, {@code icon.png}, e precisa da versão exclusiva +para diferentes localidades. No entanto, duas localidades, inglês canadense e francês canadense, +precisam usar a mesma versão. Você pode presumir que precisa copiar a mesma imagem +para o diretório do recurso do inglês canadense e do francês canadense, +mas não é verdade. Em vez disso, é possível salvar a imagem que é usada para ambos como {@code icon_ca.png} (qualquer +nome que não seja {@code icon.png}) e colocá-la +no diretório {@code res/drawable/} padrão. Em seguida, crie um arquivo {@code icon.xml} em {@code +res/drawable-en-rCA/} e em {@code res/drawable-fr-rCA/} que mencione o recurso {@code icon_ca.png} +usando o elemento {@code <bitmap>}. Isto permite que você armazene apenas uma versão do arquivo +PNG e dois arquivos XML pequenos que apontam para ele. (Um exemplo de arquivo XML é exibido abaixo)

+ + +

Desenhável

+ +

Para criar um alias para um desenhável existente, use o elemento {@code <bitmap>}. +Por exemplo:

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
+    android:src="@drawable/icon_ca" />
+
+ +

Se salvar esse arquivo como {@code icon.xml} (em um diretório de recursos alternativos, +como {@code res/drawable-en-rCA/}), ele será compilado em um recurso +que pode ser mencionado como {@code R.drawable.icon}, mas é um alias para o recurso {@code +R.drawable.icon_ca}, que é salvo em{@code res/drawable/}.

+ + +

Layout

+ +

Para criar um alias para um layout existente, use o elemento {@code <include>} +, agrupado em um {@code <merge>}. Por exemplo:

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<merge>
+    <include layout="@layout/main_ltr"/>
+</merge>
+
+ +

Se salvar esse arquivo como {@code main.xml}, ele será compilado em um recurso +que pode ser mencionado como {@code R.layout.main}, mas é um alias para o recurso {@code R.layout.main_ltr} +.

+ + +

Strings e outros valores simples

+ +

Para criar um alias para uma string existente, basta usar o ID de recurso da string +desejado como o valor para a nova string. Por exemplo:

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <string name="hello">Hello</string>
+    <string name="hi">@string/hello</string>
+</resources>
+
+ +

O recurso {@code R.string.hi} é agora um alias para {@code R.string.hello}.

+ +

Outros valores simples funcionam +da mesma forma. Por exemplo, uma cor:

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <color name="yellow">#f00</color>
+    <color name="highlight">@color/red</color>
+</resources>
+
+ + + + +

Fornecimento da melhor compatibilidade de dispositivo com recursos

+ +

Para o aplicativo suportar várias configurações de dispositivo, é muito importante +que você sempre forneça recursos padrão para cada tipo de recurso que o aplicativo usar.

+ +

Por exemplo, se o aplicativo suportar vários idiomas, sempre inclua um diretório {@code +values/} (em que as strings sejam salvas) sem um qualificador de região e idioma. Se colocar todos os arquivos de string +em diretórios que têm qualificadores de região e idioma, o aplicativo apresentará erros ao entrar em execução +em dispositivo configurado para um idioma que as strings não suportem. Mas, contanto que você forneça recursos +{@code values/} padrão, o aplicativo será executado sem problemas (mesmo que o usuário +não entenda o idioma — é melhor do que apresentar erros).

+ +

Do mesmo modo, se você fornecer recursos de layout diferentes com base na orientação da tela, deve +escolher uma orientação como a padrão. Por exemplo, em vez de fornecer recursos de layout em {@code +layout-land/} para paisagem e {@code layout-port/} para retrato, deixe uma como padrão, como +{@code layout/} para paisagem e {@code layout-port/} para retrato.

+ +

Fornecer recursos padrão é importante não só porque o aplicativo pode ser executado +em uma configuração que você não tenha antecipado, mas também as novas versões do Android, às vezes, adicionam +qualificadores de configuração que as versões mais antigas não suportam. Se usar um novo qualificador de recurso, +mas mantiver a compatibilidade do código com versões mais antigas do Android, quando uma versão mais antiga +do Android executar seu aplicativo, ocorrerá um erro caso você não forneça os recursos padrão, pois ele +não poderá usar os recursos nomeados com o novo qualificador. Por exemplo, se {@code +minSdkVersion} estiver definido como 4 e você qualificar todos os recursos desenháveis usando o modo noturno ({@code night} ou {@code notnight}, que foram adicionados à API de nível 8), +então o dispositivo com API de nível 4 não poderá acessar os recursos desenháveis e apresentará erro. Neste caso, +você provavelmente quererá que {@code notnight} seja o recurso padrão, então deverá excluir esse qualificador +para que os recursos desenháveis fiquem em {@code drawable/} ou {@code drawable-night/}.

+ +

Então, para fornecer a melhor compatibilidade de dispositivo, sempre forneça os recursos +padrão para os recursos imprescindíveis para o aplicativo para obter o desempenho adequado. Em seguida, +crie recursos para configurações específicas de dispositivo usando os qualificadores de configuração.

+ +

Há uma exceção a esta regra: Se a {@code minSdkVersion} do aplicativo for 4 +ou maior, você não precisará de recursos desenháveis padrão ao fornecer recursos desenháveis alternativos +com o qualificador de densidade da tela. Mesmo sem os recursos desenháveis +padrão, o Android poderá encontrar a melhor correspondência dentre as densidades de tela alternativas e dimensionar +os bitmaps conforme necessário. No entanto, para obter a melhor experiência em todos os tipos de dispositivo, +você deve fornecer desenháveis alternativos para todos os três tipos de densidade.

+ + + +

Como o Android encontra o melhor recurso correspondente

+ +

Ao solicitar um recurso para o qual você fornece alternativas, o Android seleciona +quais recursos alternativos usar em tempo de execução, dependendo da configuração do dispositivo atual. Para demonstrar +como o Android seleciona um recurso alternativo, presuma que os seguintes diretórios desenháveis +contenham versões diferentes das mesmas imagens:

+ +
+drawable/
+drawable-en/
+drawable-fr-rCA/
+drawable-en-port/
+drawable-en-notouch-12key/
+drawable-port-ldpi/
+drawable-port-notouch-12key/
+
+ +

E presuma que a configuração do dispositivo é a seguinte:

+ +

+Localidade = en-GB
+Orientação da tela = port
+Densidade de pixel da tela = hdpi
+Tipo de tela sensível ao toque = notouch
+Método principal de entrada de texto = 12key +

+ +

Ao comparar a configuração do dispositivo com os recursos alternativos disponíveis, o Android seleciona +desenháveis de {@code drawable-en-port}.

+ +

O sistema chega à conclusão de quais recursos deve usar +com a seguinte lógica:

+ + +
+ +

Figura 2. Fluxograma de como o Android +encontra o melhor recurso correspondente.

+
+ + +
    +
  1. Elimine os arquivos de recurso que contradizem a configuração do dispositivo. +

    O diretório drawable-fr-rCA/ é eliminado, +pois contradiz a localidade en-GB.

    +
    +drawable/
    +drawable-en/
    +drawable-fr-rCA/
    +drawable-en-port/
    +drawable-en-notouch-12key/
    +drawable-port-ldpi/
    +drawable-port-notouch-12key/
    +
    +

    Exceção: a densidade de pixel da tela é a que o qualificador +não eliminou devido a uma contradição. Apesar de a densidade da tela do dispositivo ser hdpi, +drawable-port-ldpi/ não é eliminado, pois todas as densidades de telas +são consideradas uma correspondência neste ponto. Obtenha mais informações no documento Compatibilidade com +várias telas.

  2. + +
  3. Escolha o (próximo) qualificador de maior precedência na lista (tabela 2). +(Comece com MCC e, em seguida, siga para baixo.)
  4. +
  5. Algum dos diretórios de recurso incluem este qualificador?
  6. +
      +
    • Se não, volte à etapa 2 e veja o próximo qualificador. (Neste exemplo, + a resposta é "não" até que o qualificador de idioma seja alcançado.)
    • +
    • Se sim, prossiga para a etapa 4.
    • +
    + + +
  7. Elimine os diretórios de recurso que não incluem este qualificador. No exemplo, o sistema +elimina todos os diretórios que não incluem um qualificador de idioma:
  8. +
    +drawable/
    +drawable-en/
    +drawable-en-port/
    +drawable-en-notouch-12key/
    +drawable-port-ldpi/
    +drawable-port-notouch-12key/
    +
    +

    Exceção: se o qualificador em questão for a densidade de pixel da tensidade da tela +do dispositivo de forma mais aproximada. +Geralmente, o Android prefere dimensionar uma imagem original maior +em vez de uma maior. Consulte Compatibilidade com +várias telas.

    + + +
  9. Volte e repita as etapas 2, 3 e 4 até que reste apenas um diretório. No exemplo, a orientação da tela +é o próximo qualificador, onde há várias correspondências. +Portanto, os recursos que não especificarem uma orientação de tela serão eliminados: +
    +drawable-en/
    +drawable-en-port/
    +drawable-en-notouch-12key/
    +
    +

    O diretório restante é {@code drawable-en-port}.

    +
  10. +
+ +

Apesar de este processo ser executado para cada recurso solicitado, o sistema posteriormente aprimora +alguns aspectos. Tal otimização, quando a configuração do dispositivo é conhecida, +pode eliminar os recursos alternativos que nunca correspondem. Por exemplo, se o idioma +da configuração for inglês ("en"), então qualquer diretório de recurso que tiver um qualificador de idioma definido para +outro idioma que não seja inglês nunca será incluído no conjunto de recursos verificados (apesar de um +diretório de recursos sem o qualificador de idioma ainda ser incluído).

+ +

Ao selecionar os recursos com base nos qualificadores de tamanho da tela, o sistema usará os recursos +projetados para uma tela menor do que a tela atual, caso não tenha recursos que correspondam de forma mais eficaz +(por exemplo: uma tela de tamanho grande usará os recursos de tela de tamanho normal se necessário). No entanto, +se os únicos recursos disponíveis forem maiores do que a tela atual, o sistema +não os usará e o aplicativo apresentará erros se nenhum outro recurso corresponder à configuração +do dispositivo (por exemplo, se todos os recursos de layout estiverem com a tag do qualificador {@code xlarge}, +mas o dispositivo tiver uma tela de tamanho normal).

+ +

Observação: a precedência do qualificador (na tabela 2) é mais importante +do que o número de qualificadores que correspondem exatamente ao dispositivo. Por exemplo, na etapa 4 acima, a última +escolha na lista inclui três qualificadores que correspondem exatamente ao dispositivo (orientação, tipo de +tela sensível ao toque e método de entrada), enquanto que drawable-en possui apenas um parâmetro que corresponde +(idioma). No entanto, o idioma tem uma precedência maior que esses outros qualificadores, então +drawable-port-notouch-12key está fora.

+ +

Para obter mais informações sobre como usar os recursos no aplicativo, acesse Acesso aos recursos.

diff --git a/docs/html-intl/intl/pt-br/guide/topics/resources/runtime-changes.jd b/docs/html-intl/intl/pt-br/guide/topics/resources/runtime-changes.jd new file mode 100644 index 0000000000000000000000000000000000000000..366ce0d2c0728e8a8a94f8b75b904132988ac758 --- /dev/null +++ b/docs/html-intl/intl/pt-br/guide/topics/resources/runtime-changes.jd @@ -0,0 +1,281 @@ +page.title=Tratar alterações no tempo de execução +page.tags=atividade,ciclo de vida +@jd:body + + + +

Algumas configurações do dispositivo podem mudar durante o tempo de execução +(como orientação de tela, disponibilidade do teclado e idioma). Quando ocorre uma alteração, +o Android precisa reiniciar a execução +de {@link android.app.Activity} ({@link android.app.Activity#onDestroy()} é chamado, seguido de {@link +android.app.Activity#onCreate(Bundle) onCreate()}). O comportamento de reinício foi projetado para ajudar +o aplicativo a se adaptar a novas configurações recarregando automaticamente o aplicativo +com recursos alternativos que correspondam com a configuração do dispositivo.

+ +

Para tratar adequadamente um reinício, é importante que a atividade se restaure +ao estado anterior por meio do ciclo de vida +da atividade, no qual o Android chama +{@link android.app.Activity#onSaveInstanceState(Bundle) onSaveInstanceState()} antes de destruir +a atividade para que seja possível salvar os dados acerca do estado do aplicativo. Em seguida, é possível restaurar o estado +durante {@link android.app.Activity#onCreate(Bundle) onCreate()} ou {@link +android.app.Activity#onRestoreInstanceState(Bundle) onRestoreInstanceState()}.

+ +

Para testar se o aplicativo se reinicia com o estado de aplicativo intacto, deve-se +invocar as alterações de configuração (como alterações na orientação da tela) enquanto executa diversas +tarefas no aplicativo. O aplicativo deve ser capaz de reiniciar a qualquer momento sem perda +de dados do usuário ou estado para tratar eventos como alterações de configuração ou quando o usuário recebe +uma chamada telefônica e, em seguida, retorna ao aplicativo bem depois +de destruído o processo do aplicativo. Para ver como restaurar o estado da atividade, leia sobre o Ciclo de vida da atividade.

+ +

No entanto, pode-se encontrar uma situação em que o reinício do aplicativo +e a restauração representem quantidades significativas de dados que podem ser custosos e prejudicar a experiência do usuário. Nessa situação, +temos duas opções:

+ +
    +
  1. Reter um objeto durante uma alteração de configuração +

    Permita que a atividade reinicie quando uma configuração muda, mas transporte um objeto +de estado para a nova instância da atividade.

    + +
  2. +
  3. Tratar você mesmo da alteração de configuração +

    Evite que o sistema reinicie a atividade durante certas alterações +de configuração, mas receba um retorno de chamada quando as configurações se alteram, para que você atualize manualmente +a atividade conforme necessário.

    +
  4. +
+ + +

Retenção de um objeto durante uma alteração de configuração

+ +

Se a retenção da atividade exigir a recuperação de grandes conjuntos de dados, restabelecer uma conexão +de rede ou executar outras operações intensivas, um reinício completo devido a uma alteração +de configuração pode prejudicar a experiência do usuário. Além disso, pode não ser possível restaurar completamente +o estado da atividade com o {@link android.os.Bundle} que o sistema salva com o retorno de chamada {@link +android.app.Activity#onSaveInstanceState(Bundle) onSaveInstanceState()} — ele +não foi projetado para transportar objetos grandes (como bitmaps) e os dados contidos devem ser serializados +e, em seguida, desserializados, o que pode consumir muita memória e retardar a alteração de configuração. Nesse caso, +para aliviar o peso de reinicializar a atividade, pode-se reter um {@link +android.app.Fragment} quando a atividade for reiniciada devido a uma alteração de configuração. Esse fragmento +pode conter referências a objetos com estado que seja preciso reter.

+ +

Quando o sistema Android encerra a atividade devido a uma alteração de configuração, os fragmentos +da atividade marcados para serem retidos não são destruídos. É possível adicionar esses fragmentos +à atividade para preservar objetos de estado.

+ +

Para reter objetos de estado em um fragmento durante uma alteração de configuração em tempo de execução:

+ +
    +
  1. Estenda a classe {@link android.app.Fragment} e declare referências aos objetos +de estado.
  2. +
  3. Chame {@link android.app.Fragment#setRetainInstance(boolean)} quando o fragmento for criado. +
  4. +
  5. Acrescente o fragmento à atividade.
  6. +
  7. Use {@link android.app.FragmentManager} para recuperar o fragmento quando a atividade for +reiniciada.
  8. +
+ +

Por exemplo: defina o fragmento da seguinte forma:

+ +
+public class RetainedFragment extends Fragment {
+
+    // data object we want to retain
+    private MyDataObject data;
+
+    // this method is only called once for this fragment
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        // retain this fragment
+        setRetainInstance(true);
+    }
+
+    public void setData(MyDataObject data) {
+        this.data = data;
+    }
+
+    public MyDataObject getData() {
+        return data;
+    }
+}
+
+ +

Atenção: ao restaurar qualquer objeto, +não se deve nunca passar um objeto vinculado a {@link android.app.Activity}, como um {@link +android.graphics.drawable.Drawable}, um {@link android.widget.Adapter}, um {@link android.view.View} +ou qualquer outro objeto associado a um {@link android.content.Context}. Se o fizer, +ele vazará todas as vistas e recursos da instância da atividade original (vazar recursos +significa que o aplicativo mantém a retenção deles, que não podem ser recolhidos, o que +causa perda de memória).

+ +

Em seguida, use {@link android.app.FragmentManager} para adicionar o fragmento à atividade. +É possível obter o objeto de dados do fragmento quando a atividade reiniciar durante as alterações +de configuração em tempo de execução. Por exemplo: defina a atividade da seguinte forma:

+ +
+public class MyActivity extends Activity {
+
+    private RetainedFragment dataFragment;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.main);
+
+        // find the retained fragment on activity restarts
+        FragmentManager fm = getFragmentManager();
+        dataFragment = (DataFragment) fm.findFragmentByTag(“data”);
+
+        // create the fragment and data the first time
+        if (dataFragment == null) {
+            // add the fragment
+            dataFragment = new DataFragment();
+            fm.beginTransaction().add(dataFragment, “data”).commit();
+            // load the data from the web
+            dataFragment.setData(loadMyData());
+        }
+
+        // the data is available in dataFragment.getData()
+        ...
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+        // store the data in the fragment
+        dataFragment.setData(collectMyLoadedData());
+    }
+}
+
+ +

Nesse exemplo, {@link android.app.Activity#onCreate(Bundle) onCreate()} adiciona um fragmento +ou restaura uma referência a ele. {@link android.app.Activity#onCreate(Bundle) onCreate()} também +armazena o objeto de estado dentro da instância de fragmento. +{@link android.app.Activity#onDestroy() onDestroy()} atualiza o objeto de estado dentro +da instância de fragmento retida.

+ + + + + +

Tratar você mesmo da alteração de configuração

+ +

Se o aplicativo não tiver que atualizar recursos durante uma alteração de configuração específica +e se houver alguma limitação de desempenho que +impeça a atividade de reiniciar, pode-se declarar que a atividade trata ela mesma da alteração de configuração, +o que evita que o sistema reinicie a atividade.

+ +

Observação: Tratar você mesmo da alteração de configuração +pode dificultar muito o uso de recursos alternativos, pois o sistema não os aplicará +automaticamente. Esta técnica deve ser considerada um último recurso, quando é preciso evitar reinícios +devido a uma alteração de configuração e não é recomendada para a maioria dos aplicativos.

+ +

Para declarar que a atividade manipula uma alteração de configuração, edite o elemento {@code <activity>} +apropriado no arquivo de manifesto para que inclua o atributo {@code +android:configChanges} com um valor que represente a configuração +a tratar. Os valores possíveis estão listados na documentação do atributo {@code +android:configChanges} (os valores mais comumente usados são {@code "orientation"}, para +impedir reinícios durante alterações na orientação da tela, e {@code "keyboardHidden"} para impedir +reinícios quando a disponibilidade do teclado muda). Para declarar vários valores de configuração +no atributo, usa-se um separador na forma de caractere barra reta {@code |}.

+ +

Por exemplo: o código de manifesto a seguir declara uma atividade que trata tanto +da alteração de orientação da tela quanto da disponibilidade do teclado:

+ +
+<activity android:name=".MyActivity"
+          android:configChanges="orientation|keyboardHidden"
+          android:label="@string/app_name">
+
+ +

Agora, quando uma dessas configurações mudar, {@code MyActivity} não reiniciará. +Em vez disso, a {@code MyActivity} recebe uma chamada para {@link +android.app.Activity#onConfigurationChanged(Configuration) onConfigurationChanged()}. Um objeto +{@link android.content.res.Configuration} é passado a esse método e especifica +a nova configuração do dispositivo. Ao ler os campos em {@link android.content.res.Configuration}, +pode-se determinar a nova configuração e atualizar os recursos na interface para fazer +as alterações adequadas. No momento +em que o método é chamado, o objeto {@link android.content.res.Resources} da atividade é atualizado +para retornar recursos com base na nova configuração, o que facilita +a redefinição de elementos da IU sem que o sistema reinicie a atividade.

+ +

Atenção: a partir do Android 3.2 (nível da API 13), o "tamanho +da tela" também muda quando o dispositivo alterna entre as orientações retrato +e paisagem. Assim, se você deseja evitar que o tempo de execução reinicie devido a uma mudança da orientação +ao desenvolver uma API nível 13 ou posterior (conforme declarado pelos atributos {@code minSdkVersion} e {@code targetSdkVersion}), +é preciso incluir o valor {@code "screenSize"} além do valor {@code +"orientation"}. Ou seja, é preciso declarar {@code +android:configChanges="orientation|screenSize"}. No entanto, se o aplicativo tem como alvo uma API nível +12 ou inferior, a atividade sempre trata ela mesma a alteração de configuração (essa mudança +de configuração não reinicia a atividade, mesmo em execução em Android 3.2 ou dispositivo posterior).

+ +

Por exemplo: a implementação a seguir {@link +android.app.Activity#onConfigurationChanged(Configuration) onConfigurationChanged()} verifica +a orientação de dispositivo atual:

+ +
+@Override
+public void onConfigurationChanged(Configuration newConfig) {
+    super.onConfigurationChanged(newConfig);
+
+    // Checks the orientation of the screen
+    if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
+        Toast.makeText(this, "landscape", Toast.LENGTH_SHORT).show();
+    } else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT){
+        Toast.makeText(this, "portrait", Toast.LENGTH_SHORT).show();
+    }
+}
+
+ +

O objeto {@link android.content.res.Configuration} representa todas as configurações +atuais, não somente as que foram alteradas. Na maior parte do tempo, não importa como +a configuração foi alterada; basta reatribuir todos os recursos que apresentam alternativas +à configuração que estão sendo tratadas. Por exemplo: como o objeto {@link +android.content.res.Resources} está atualizado, pode-se redefinir +qualquer {@link android.widget.ImageView} com {@link android.widget.ImageView#setImageResource(int) +setImageResource()} +e será usado o recurso adequado à nova configuração (conforme descrito em Como fornecer recursos).

+ +

Observe que os valores dos campos de {@link +android.content.res.Configuration} são inteiros que correspondem a constantes específicas +da classe {@link android.content.res.Configuration}. Para ver a documentação sobre as constantes +a usar em cada campo, consulte o campo em questão na referência sobre {@link +android.content.res.Configuration}.

+ +

Lembre-se: ao declarar a atividade para tratar uma alteração +de configuração, você é responsável por redefinir todos os elementos que fornecem alternativas. Se você +declarar a atividade para tratar a alteração de orientação e tiver imagens que alterariam +entre paisagem e retrato, é preciso reatribuir cada recurso a cada elemento durante {@link +android.app.Activity#onConfigurationChanged(Configuration) onConfigurationChanged()}.

+ +

Se não for necessário atualizar o aplicativo com base nessas alterações +de configuração, pode-se não implementar {@link +android.app.Activity#onConfigurationChanged(Configuration) onConfigurationChanged()}. Nesse +caso, todos os recursos usados antes da alteração de configuração ainda são usados +e somente o reinício da atividade é evitado. No entanto, o aplicativo deve sempre ser capaz +de se encerrar e reiniciar com seu estado anterior intacto, portanto essa técnica não deve +ser considerada uma fuga da retenção do estado durante o ciclo de vida normal da atividade, Não somente porque +há outras alterações de configuração impossíveis de evitar que reiniciem o aplicativo, +mas também porque devem-se tratar eventos como o do usuário que sai do aplicativo e ele é destruído +antes de o usuário voltar a ele.

+ +

Para obter mais informações sobre as alterações de configuração que devem ser tratadas na atividade, consulte a documentação sobre {@code +android:configChanges} e a classe +{@link android.content.res.Configuration}.

diff --git a/docs/html-intl/intl/pt-br/guide/topics/ui/controls.jd b/docs/html-intl/intl/pt-br/guide/topics/ui/controls.jd new file mode 100644 index 0000000000000000000000000000000000000000..58a4fcdbe5a06d2cc6e1305a1d1bde7117282a4f --- /dev/null +++ b/docs/html-intl/intl/pt-br/guide/topics/ui/controls.jd @@ -0,0 +1,90 @@ +page.title=Controles de entrada +parent.title=Interface do usuário +parent.link=index.html +@jd:body + +
+ +
+ +

Controles de entrada são os componentes interativos na interface do usuário de seu aplicativo. O Android oferece +uma ampla variedade de controles que podem ser usados na IU, como botões, campos de texto, barras de busca, +caixas de seleção, botões de zoom, botões de alternância e muito mais.

+ +

Adicionar um controle de entrada à IU é tão simples quanto adicionar um elemento XML ao layout XML. Por exemplo, a seguir apresenta-se +um layout com um campo de texto e um botão:

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    android:orientation="horizontal">
+    <EditText android:id="@+id/edit_message"
+        android:layout_weight="1"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:hint="@string/edit_message" />
+    <Button android:id="@+id/button_send"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/button_send"
+        android:onClick="sendMessage" />
+</LinearLayout>
+
+ +

Cada controle de entrada tem suporte para um conjunto específico de eventos de entrada, portanto, você pode tratar eventos como quando +o usuário digita texto ou toca em um botão.

+ + +

Controles comuns

+

A seguir há uma lista de alguns controles comuns que você pode usar no aplicativo. Siga os links para saber +mais sobre o uso de cada um.

+ +

Observação: o Android fornece muitos outros controles além dos listados +aqui. Navegue no pacote {@link android.widget} para descobrir mais. Se o aplicativo precisa +de um tipo específico de controle de entrada, você pode criar os próprios componentes personalizados.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tipo de controleDescriçãoClasses relacionadas
BotãoUm botão de pressão que pode ser pressionado ou clicado pelo usuário para realizar uma ação.{@link android.widget.Button Button}
Campo de textoUm campo de texto editável. É possível usar o widget AutoCompleteTextView para criar um widget de entrada de texto que forneça sugestões para preenchimento automático{@link android.widget.EditText EditText}, {@link android.widget.AutoCompleteTextView}
Caixa de seleçãoUma chave liga/desliga que pode ser alternada pelo usuário. Use caixas de seleção ao apresentar aos usuários um grupo de opções selecionáveis que não sejam mutualmente exclusivas.{@link android.widget.CheckBox CheckBox}
Botão de opçãoSimilar às caixas de seleção, exceto que somente uma opção pode ser selecionada no grupo.{@link android.widget.RadioGroup RadioGroup} +
{@link android.widget.RadioButton RadioButton}
Botão de alternânciaUm botão liga/desliga com um indicador de luz.{@link android.widget.ToggleButton ToggleButton}
Controle giratórioUma lista suspensa que permite que os usuários selecionem um valor de um conjunto.{@link android.widget.Spinner Spinner}
SeletoresUma caixa de diálogo para que os usuários selecionem um valor para um conjunto usando botões para cima/para baixo ou via gesto de deslizar. Use um widget DatePickercode> para inserir os valores para a data (mês, dia, ano) ou um widget TimePicker para inserir valores para um horário (hora, minuto, AM/PM), que será formatado automaticamente para a localidade do usuário.{@link android.widget.DatePicker}, {@link android.widget.TimePicker}
diff --git a/docs/html-intl/intl/pt-br/guide/topics/ui/declaring-layout.jd b/docs/html-intl/intl/pt-br/guide/topics/ui/declaring-layout.jd new file mode 100644 index 0000000000000000000000000000000000000000..09dbd2cee275a66d220497cd0456d23a37b20ede --- /dev/null +++ b/docs/html-intl/intl/pt-br/guide/topics/ui/declaring-layout.jd @@ -0,0 +1,492 @@ +page.title=Layouts +page.tags=vista,grupodevistas +@jd:body + +
+
+

Neste documento

+
    +
  1. Programação do XML
  2. +
  3. Carregamento do recurso XML
  4. +
  5. Atributos +
      +
    1. ID
    2. +
    3. Parâmetros do layout
    4. +
    +
  6. +
  7. Posição do layout
  8. +
  9. Tamanho, preenchimento e margens
  10. +
  11. Layouts comuns
  12. +
  13. Criação de layouts com um adaptador +
      +
    1. Preenchimento da vista de um adaptador com dados
    2. +
    3. Tratamento de eventos de clique
    4. +
    +
  14. +
+ +

Classes principais

+
    +
  1. {@link android.view.View}
  2. +
  3. {@link android.view.ViewGroup}
  4. +
  5. {@link android.view.ViewGroup.LayoutParams}
  6. +
+ +

Veja também

+
    +
  1. Construção de uma interface do usuário +simples
+
+ +

O layout define a estrutura visual para uma interface do usuário, como a IU de uma atividade ou de um widget de aplicativo. +É possível declarar um layout de dois modos:

+
    +
  • Declarar elementos da IU em XML. O Android fornece um vocabulário XML +direto que corresponde às classes e subclasses de View, como as de widgets e layouts.
  • +
  • Instanciar elementos do layout em tempo de execução. +O aplicativo pode criar objetos de View e ViewGroup (e manipular suas propriedades) programaticamente.
  • +
+ +

A estrutura do Android é flexível para usar um destes métodos ou ambos para declarar e gerenciar a IU do seu aplicativo. Por exemplo: você pode declarar um layout padrão do aplicativo em XML, e incluir os elementos da tela que aparecerão neles e suas propriedades. Em seguida, você poderia adicionar códigos ao aplicativo que modificariam o estado dos objetos da tela, inclusive os declarados em XML, em tempo de execução.

+ + + +

A vantagem de declarar a IU em XML é separar melhor a apresentação do aplicativo a partir do código que controla seu comportamento. As descrições da IU são externas ao código do aplicativo, ou seja, é possível modificá-la ou adaptá-la sem modificar o código-fonte e recompilar. Por exemplo: é possível criar layouts XML para diferentes orientações de tela, diferentes tamanhos de tela de dispositivos e diferentes idiomas. Além disso, a declaração de layout em XML facilita a exibição da estrutura da sua IU, o que facilita a depuração de problemas. Assim sendo, este documento se concentrará em ensinar a declarar o layout em XML. Se você +estiver interessado em instanciar objetos View em tempo de execução, consulte as referências das classes +{@link android.view.ViewGroup} e {@link android.view.View}.

+ +

Em geral, o vocabulário XML para declarar elementos da IU segue rigorosamente a estrutura e a atribuição de nome às classes e aos métodos, em que os nomes de elementos correspondem a nomes de classe e nomes de atributos correspondem a métodos. Na verdade, a correspondência normalmente é tão direta que é possível supor qual atributo XML corresponde a determinado método de classe ou supor qual classe corresponde a certo elemento XML. Contudo, observe que nem todo vocabulário é idêntico. Em alguns casos, há pequenas diferenças de nome. Por exemplo: +o elemento EditText tem um atributo text que corresponde +a EditText.setText().

+ +

Dica: Veja os diferentes tipos de layout em Objetos +de layout comuns. Também há uma coleção de tutoriais sobre a criação de diversos layouts +no guia de tutorial Vistas de boas-vindas.

+ +

Programação do XML

+ +

Usando o vocabulário XML do Android, é possível projetar rapidamente layouts de IU e os elementos de tela intrínsecos do mesmo modo que se cria páginas web em HTML — com uma série de elementos aninhados.

+ +

Cada arquivo de layout deve conter exatamente um elemento raiz, que deve ser um objeto View ou ViewGroup. Com o elemento raiz definido, é possível adicionar objetos ou widgets de layout extras como elementos filho para construir gradualmente uma hierarquia de View que define o layout. Por exemplo: eis um layout XML que usa um {@link android.widget.LinearLayout} vertical +para conter uma {@link android.widget.TextView} e um {@link android.widget.Button}:

+
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:layout_width="match_parent"
+              android:layout_height="match_parent"
+              android:orientation="vertical" >
+    <TextView android:id="@+id/text"
+              android:layout_width="wrap_content"
+              android:layout_height="wrap_content"
+              android:text="Hello, I am a TextView" />
+    <Button android:id="@+id/button"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Hello, I am a Button" />
+</LinearLayout>
+
+ +

Após declarar o layout no XML, salve o arquivo com uma extensão .xml +no diretório res/layout/ do projeto do Android para compilá-lo adequadamente.

+ +

Veja mais informações sobre a sintaxe de um arquivo XML de layout no documento Recursos de layout.

+ +

Carregamento do recurso XML

+ +

Ao compilar o aplicativo, cada arquivo de layout XML é compilado +em um recurso {@link android.view.View}. Deve-se carregar o recurso de layout do código do aplicativo +na implementação de retorno de chamada {@link android.app.Activity#onCreate(android.os.Bundle) Activity.onCreate()}. +Para isso, chame {@link android.app.Activity#setContentView(int) setContentView()}, +passando a referência para o recurso de layout na forma: +R.layout.layout_file_name. +Por exemplo: se o layout XML for salvo como main_layout.xml, será necessário carregá-lo +para a Atividade desta forma:

+
+public void onCreate(Bundle savedInstanceState) {
+    super.onCreate(savedInstanceState);
+    setContentView(R.layout.main_layout);
+}
+
+ +

O método de retorno de chamada onCreate() na Atividade é chamado pela estrutura do Android quando +ela é inicializada (veja a discussão sobre ciclos de vida no documento +Atividades +).

+ + +

Atributos

+ +

Cada objeto View e ViewGroup aceita uma variedade própria de atributos XML. +Alguns atributos são específicos de um objeto View (por exemplo, TextView aceita o atributo +textSize), mas esses atributos também são herdados de objetos View que possam estender essa classe. +Alguns são comuns a todos os objetos View porque são herdados da classe View raiz (como +o atributo id). Além disso, outros atributos são considerados "parâmetros do layout", que são +os que descrevem determinadas orientações de layout do objeto View conforme definido pelo objeto +ViewGroup pai daquele objeto.

+ +

ID

+ +

Qualquer objeto View pode ter um ID de número inteiro associado para identificar exclusivamente o View dentro da árvore. +Ao compilar o aplicativo, esse ID é referenciado como um número inteiro, mas o ID normalmente +é atribuído no arquivo XML do layout como uma string, no atributo id. +É um atributo XML comum a todos os objetos View +(definido pela classe {@link android.view.View}) e você o usará com frequência. +A sintaxe de um ID, dentro de uma tag XML, é:

+
android:id="@+id/my_button"
+ +

Um símbolo de arroba (@) no início da string indica que o analisador XML deve analisar e expandir o restante +da string de ID e identificá-la como um recurso de ID. O símbolo de mais (+) significa que é um novo nome de recurso que precisa +ser criado e adicionado aos recursos (no arquivo R.java). Há diversos outros recursos de ID +oferecidos pela estrutura do Android. Ao referenciar um ID de recurso do Android, não é necessário ter o símbolo de mais, +mas deve-se adicionar o conjunto de nomes (namespace) do pacote android da seguinte maneira:

+
android:id="@android:id/empty"
+

Com o conjunto de nomes (namespace) do pacote android em vigor, podemos referenciar um ID da classe +de recursos android.R em vez de um da classe de recursos locais.

+ +

Para criar vistas e referenciá-las a partir do aplicativo, um modo padrão comum é:

+
    +
  1. Definir uma visualizaçãovistawidget no arquivo de layout e atribuir um ID exclusivo a ele/ela: +
    +<Button android:id="@+id/my_button"
    +        android:layout_width="wrap_content"
    +        android:layout_height="wrap_content"
    +        android:text="@string/my_button_text"/>
    +
    +
  2. +
  3. Em seguida, crie uma instância do objeto de vista e capture-a do layout +(normalmente no método {@link android.app.Activity#onCreate(Bundle) onCreate()}): +
    +Button myButton = (Button) findViewById(R.id.my_button);
    +
    +
  4. +
+

Definir IDs para objetos de vista é importante ao criar um {@link android.widget.RelativeLayout}. +Em um layout relativo, vistas irmãs podem definir o layout relativo para outra vista irmã +referenciada pelo ID exclusivo.

+

Os Ids não precisam ser exclusivo por toda a árvore, mas devem ser +exclusivos dentro da parte da árvore em que se está procurando (que pode, com frequência, ser toda a árvore, portanto é preferível +ser totalmente exclusivo sempre que possível).

+ + +

Parâmetros do layout

+ +

Os atributos do layout XML chamados layout_something definem +parâmetros para a View que for apropriado para a ViewGroup em que reside.

+ +

Cada classe ViewGroup implementa uma classe aninhada que estende {@link +android.view.ViewGroup.LayoutParams}. Essa subclasse +contém tipos de propriedade que definem o tamanho e a posição de cada vista filha, conforme +o necessário para o grupo de vistas. Como se pode ver na figura 1, um grupo de vistas +pais define parâmetros de layout para cada vista filha (incluindo o grupo de vistas filhas).

+ + +

Figura 1. Vista de uma hierarquia de vistas com parâmetros +de layout associados a cada uma delas.

+ +

Observe que cada subclasse LayoutParams tem a própria sintaxe para definir +valores. Cada elemento filho deve definir LayoutParams apropriados para seu pai, +embora possa também definir diferentes LayoutParams para os próprios filhos.

+ +

Todos os grupos de vistas contêm largura e altura (layout_width e +layout_height) e cada vista é obrigatória para defini-las. Muitos +LayoutParams também contêm margens e bordas opcionais.

+ +

É possível especificar largura e altura com medidas exatas, embora não seja +recomendável na maioria dos casos. Em geral, usa-se uma destas constantes para +definir a largura e a altura:

+ +
    +
  • wrap_content instrui a vista a dimensionar-se de acordo com +as medidas exigidas pelo conteúdo.
  • +
  • match_parent (de nome fill_parent antes da API de nível 8) +instrui a vista a tornar-se tão grande quanto o grupo de vistas pais permitir.
  • +
+ +

Em geral, a especificação de largura e altura de um layout com unidades absolutas, como +pixels, não é recomendada. Em vez disso, usam-se medidas relativas como +unidades de pixel independentes de densidade (dp), wrap_contentou +match_parent, é uma abordagem melhor porque ajuda a garantir +que o aplicativo exiba o conteúdo adequadamente dentre diversos tamanhos de tela de dispositivos. +Os tipos de medidas aceitos são definidos no documento + +Recursos disponíveis.

+ + +

Posição do layout

+

+ A geometria de uma vista de um retângulo. As vistas têm uma localização, + expressa como um par de coordenadas esquerda e topo + e duas dimensões, expressas como largura e altura. A unidade de localização + e de dimensões é o pixel. +

+ +

+ É possível recuperar a localização de uma vista chamando os métodos + {@link android.view.View#getLeft()} e {@link android.view.View#getTop()}. O primeiro retorna a coordenada + esquerda, ou X, do retângulo que representa a vista. O último retorna a coordenada + topo, ou Y, do retângulo que representa a vista. Esses métodos + retornam a localização da vista em relação ao pai correspondente. Por exemplo: + quando getLeft() retorna 20, significa que a vista se localiza 20 pixels à + direita da borda esquerda do seu pai direto. +

+ +

+ Adicionalmente, diversos métodos de conveniência são oferecidos para evitar cálculos +desnecessárias, chamados {@link android.view.View#getRight()} e {@link android.view.View#getBottom()}. + Esses métodos retornam as coordenadas das bordas direita e de baixo + do retângulo que representa a vista. Por exemplo: chamar {@link android.view.View#getRight()} + é semelhante ao seguinte cálculo: getLeft() + getWidth(). +

+ + +

Tamanho, preenchimento e margens

+

+ O tamanho de uma vista é expresso por largura e altura. As vistas, na verdade, + têm dois pares de valores de largura e altura. +

+ +

+ O primeiro par é conhecido como largura medida + e altura medida. Essas dimensões definem o tamanho que a vista terá + dentro da vista pai. + Para obter as dimensões medidas, chamam-se {@link android.view.View#getMeasuredWidth()} + e {@link android.view.View#getMeasuredHeight()}. +

+ +

+ O segundo par é simplesmente conhecido como largura e altura ou, + às vezes, largura do desenho e altura do desenho. Essas + dimensões definem o tamanho real da vista na tela, em tempo de desenho e + após o layout. Esses valores podem diferir da largura + e da altura medidas. Para obter os valores de largura e altura, chamam-se + {@link android.view.View#getWidth()} e {@link android.view.View#getHeight()}. +

+ +

+ Para medir as dimensões, a vista leva em conta o preenchimento. O preenchimento + é expresso em pixels para a esquerda, a direita e as partes de cima e de baixo da vista. + O preenchimento pode ser usado para compensar o conteúdo da vista por um número específico + de pixels. Por exemplo: um preenchimento à esquerda de 2 empurrará o conteúdo da vista + em 2 pixels para a direita da borda esquerda. Para definir o preenchimento, usa-se + o método {@link android.view.View#setPadding(int, int, int, int)} e consulta-se com as chamadas + {@link android.view.View#getPaddingLeft()}, {@link android.view.View#getPaddingTop()}, + {@link android.view.View#getPaddingRight()} e {@link android.view.View#getPaddingBottom()}. +

+ +

+ Mesmo que cada vista possa definir um preenchimento, ela não fornece nenhuma compatibilidade + com margens. No entanto, os grupos de vistas oferecem essa compatibilidade. Consulte + {@link android.view.ViewGroup} + e {@link android.view.ViewGroup.MarginLayoutParams} para ver mais informações. +

+ +

Para obter mais informações sobre dimensões, consulte + Valores de dimensões. +

+ + + + + + + + + + + +

Layouts comuns

+ +

Cada subclasse da classe {@link android.view.ViewGroup} fornece um modo exclusivo de exibir +as vistas aninhadas dentro dela. Eis alguns dos tipos de layout mais comuns criados +na plataforma Android.

+ +

Observação: Embora seja possível aninhar um ou mais layouts em outro +layout para obter o projeto de IU, deve-se procurar manter a hierarquia do layout a menos profunda +possível. O layout carrega mais rápido se tiver menos layouts aninhados (uma hierarquia de vistas grande é +melhor do que uma hierarquia de vistas profunda).

+ + + + +
+

Layout linear

+ +

Layout que organiza os filhos em uma única linha horizontal ou vertical. Ele + cria uma barra de rolagem se o comprimento da janela exceder o comprimento da tela.

+
+ +
+

Layout relativo

+ +

Permite especificar a localização de objetos filhos relativos entre si (filho A +à esquerda do filho B) ou relativos aos pais (alinhados no topo do pai).

+
+ +
+

Vista web

+ +

Exibe páginas da web.

+
+ + + + +

Criação de layouts com um adaptador

+ +

Quando o conteúdo do layout é dinâmico ou não predeterminado, é possível usar um layout que +torne {@link android.widget.AdapterView} uma subclasse para preencher o layout com vistas em tempo de execução. +Uma subclasse da classe {@link android.widget.AdapterView} usa um {@link android.widget.Adapter} +para agrupar dados ao seu layout. O {@link android.widget.Adapter} se comporta como um intermediário entre a fonte +dos dados e o layout do {@link android.widget.AdapterView} — o {@link android.widget.Adapter} +recupera os dados (de uma fonte como uma matriz ou uma consulta de banco de dados) e converte cada entrada +em uma vista que pode ser adicionada ao layout do {@link android.widget.AdapterView}.

+ +

Alguns layouts comuns retornados por um adaptador:

+ +
+

Vista em lista

+ +

Exibe uma lista de rolagem de coluna única.

+
+ +
+

Vista em grade

+ +

Exibe uma grade de rolagem de colunas e linhas.

+
+ + + +

Preenchimento da vista de adaptador com dados

+ +

É possível preencher um {@link android.widget.AdapterView} como {@link android.widget.ListView} +ou {@link android.widget.GridView} agrupando-se a instância do {@link android.widget.AdapterView} +a um {@link android.widget.Adapter}, o que recupera dados de uma fonte externa e cria uma {@link +android.view.View} que representa cada entrada de dados.

+ +

O Android oferece diversas subclasses de {@link android.widget.Adapter} que são úteis para +recuperar diferentes tipos de dados e criar vistas de um {@link android.widget.AdapterView}. +Os dois adaptadores mais comuns são:

+ +
+
{@link android.widget.ArrayAdapter}
+
Use esse adaptador quando a fonte de dados for uma matriz. Por padrão, {@link +android.widget.ArrayAdapter} cria uma vista para cada item de matriz chamando {@link +java.lang.Object#toString()} em cada item e posicionando o conteúdo em uma {@link +android.widget.TextView}. +

Por exemplo: se você tiver uma matriz de strings que deseja exibir em uma {@link +android.widget.ListView}, inicialize um novo {@link android.widget.ArrayAdapter} com +um construtor para especificar o layout de cada string e a matriz de strings:

+
+ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
+        android.R.layout.simple_list_item_1, myStringArray);
+
+

Os argumentos desse construtor são:

+
    +
  • O {@link android.content.Context} do aplicativo
  • +
  • O layout que contém uma {@link android.widget.TextView} para cada string na matriz
  • +
  • A matriz de strings
  • +
+

Em seguida, simplesmente chame +{@link android.widget.ListView#setAdapter setAdapter()} na {@link android.widget.ListView}:

+
+ListView listView = (ListView) findViewById(R.id.listview);
+listView.setAdapter(adapter);
+
+ +

Para personalizar a aparência de cada item, é possível suspender o método {@link +java.lang.Object#toString()} para os objetos na matriz. Ou, para criar uma vista para cada +item diferente de uma {@link android.widget.TextView} (por exemplo, se você quiser +uma {@link android.widget.ImageView} para cada item da matriz), estenda a classe {@link +android.widget.ArrayAdapter} e suspenda {@link android.widget.ArrayAdapter#getView +getView()} para retornar o tipo de vista que deseja para cada item.

+ +
+ +
{@link android.widget.SimpleCursorAdapter}
+
Use este adaptador quando os dados vierem de um {@link android.database.Cursor}. +Ao usar {@link android.widget.SimpleCursorAdapter}, é necessário especificar um layout a usar para cada +linha no {@link android.database.Cursor} e que colunas no {@link android.database.Cursor} +devem ser inseridas em determinadas vistas do layout. Por exemplo: se você deseja criar uma lista +de nome e número de telefone de pessoas, pode-se realizar uma consulta que retorna um {@link +android.database.Cursor} que contém uma linha para cada pessoa e colunas para os nomes +e números. Assim, cria-se uma matriz de strings especificando quais colunas do {@link +android.database.Cursor} estarão no layout para cada resultado e uma matriz de números inteiros especificando +as vistas correspondentes em que cada coluna deve ser colocada:

+
+String[] fromColumns = {ContactsContract.Data.DISPLAY_NAME,
+                        ContactsContract.CommonDataKinds.Phone.NUMBER};
+int[] toViews = {R.id.display_name, R.id.phone_number};
+
+

Ao instanciar o {@link android.widget.SimpleCursorAdapter}, passe o layout a usar +para cada resultado, o {@link android.database.Cursor} contendo os resultados e estas duas matrizes:

+
+SimpleCursorAdapter adapter = new SimpleCursorAdapter(this,
+        R.layout.person_name_and_number, cursor, fromColumns, toViews, 0);
+ListView listView = getListView();
+listView.setAdapter(adapter);
+
+

Em seguida, o {@link android.widget.SimpleCursorAdapter} cria uma vista de cada linha +no{@link android.database.Cursor} usando o layout fornecido por meio da inserção de cada item de {@code +fromColumns} na vista {@code toViews} correspondente.

.
+
+ + +

Se durante o curso de vida do aplicativo, você mudar os dados subjacentes lidos +pelo adaptador, chame {@link android.widget.ArrayAdapter#notifyDataSetChanged()}. Isso +notificará à vista anexada que os dados foram alterados e que ela deve se atualizar.

+ + + +

Tratamento de eventos de clique

+ +

Para responder a eventos de clique em cada item em um {@link android.widget.AdapterView}, +implementa-se a interface {@link android.widget.AdapterView.OnItemClickListener}. Por exemplo:

+ +
+// Create a message handling object as an anonymous class.
+private OnItemClickListener mMessageClickedHandler = new OnItemClickListener() {
+    public void onItemClick(AdapterView parent, View v, int position, long id) {
+        // Do something in response to the click
+    }
+};
+
+listView.setOnItemClickListener(mMessageClickedHandler);
+
+ + + diff --git a/docs/html-intl/intl/pt-br/guide/topics/ui/dialogs.jd b/docs/html-intl/intl/pt-br/guide/topics/ui/dialogs.jd new file mode 100644 index 0000000000000000000000000000000000000000..2cbedbebdebf3c37e1f5c61d801c3e4b6c89d5b5 --- /dev/null +++ b/docs/html-intl/intl/pt-br/guide/topics/ui/dialogs.jd @@ -0,0 +1,798 @@ +page.title=Caixas de diálogo +page.tags=diálogodealerta,fragmentodediálogo + +@jd:body + + + + + +

As caixas de diálogo são pequenas janelas que levam o usuário +a tomar uma decisão ou inserir informações adicionais. Elas não ocupam toda a tela e são +normalmente susada para eventos modais que exijam que usuários realizem uma ação antes de continuar.

+ +
+

Projeto de caixas de diálogo

+

Para obter mais informações sobre como projetar caixas de diálogo, inclusive sobre recomendações + de idioma, leia o guia de projeto Caixas de diálogo.

+
+ + + +

A classe {@link android.app.Dialog} é a classe de base para caixas de diálogo, mas +deve-se evitar instanciar {@link android.app.Dialog} diretamente. +Em vez disso, use uma das subclasses a seguir:

+
+
{@link android.app.AlertDialog}
+
Caixa de diálogo que pode exibir um título, até três botões, uma lista + de itens selecionáveis ou um layout personalizado.
+
{@link android.app.DatePickerDialog} ou {@link android.app.TimePickerDialog}
+
Caixa de diálogo com uma IU predefinida que permite ao usuário selecionar uma data ou hora.
+
+ + + +

Essas classes definem o estilo e a estrutura da caixa de diálogo, mas deve-se +usar um {@link android.support.v4.app.DialogFragment} como um contêiner para a caixa de diálogo. +A classe {@link android.support.v4.app.DialogFragment} fornece todos os controles +necessários para criar uma caixa de diálogo e gerenciar sua aparência em vez de chamar métodos +no objeto {@link android.app.Dialog}.

+ +

O uso de {@link android.support.v4.app.DialogFragment} para gerenciar a caixa de diálogo +garante que ela trate corretamente os eventos de ciclos de vida, +como quando o usuário pressiona o botão Voltar ou gira a tela. A classe {@link +android.support.v4.app.DialogFragment} também permite a reutilização da IU da caixa de diálogo como +um componente incorporável em uma IU maior, assim como um {@link +android.support.v4.app.Fragment} tradicional (como nas vezes em que se deseja que a IU da caixa de diálogo apareça de modos diferentes +em telas grandes e pequenas).

+ +

As seções a seguir neste guia descrevem como usar um {@link +android.support.v4.app.DialogFragment} combinado com um objeto +{@link android.app.AlertDialog}. Se você quiser criar um seletor de data ou hora, leia o guia +Seletores.

+ +

Observação: +como a classe {@link android.app.DialogFragment} foi adicionada originalmente com +o Android 3.0 (API de nível 11), este documento descreve como usar a classe {@link +android.support.v4.app.DialogFragment} fornecida com a Biblioteca de Suporte. Ao adicionar essa biblioteca +ao aplicativo, pode-se usar {@link android.support.v4.app.DialogFragment} e uma variedade de outras +APIs em dispositivos que executam o Android 1.6 ou versão posterior. Se a versão mais antiga com a qual seu aplicativo é compatível +for a API de nível 11 ou posterior, é possível usar a versão de estrutura de {@link +android.app.DialogFragment}, mas esteja ciente de que os links neste documento são para APIs +de biblioteca de suporte. Ao usar a biblioteca de suporte, +certifique-se de importar a classe android.support.v4.app.DialogFragment +e não a android.app.DialogFragment.

+ + +

Criação de um fragmento de caixa de diálogo

+ +

É possível realizar uma grande variedade de projetos de caixas de diálogo — inclusive +layouts personalizados e outros descritos no guia de projeto Caixas de diálogo + —, estendendo +{@link android.support.v4.app.DialogFragment} e criando uma{@link android.app.AlertDialog} +no método de retorno de chamada {@link android.support.v4.app.DialogFragment#onCreateDialog +onCreateDialog()}.

+ +

Por exemplo, a seguir há um {@link android.app.AlertDialog} básico gerenciado dentro +de um {@link android.support.v4.app.DialogFragment}:

+ +
+public class FireMissilesDialogFragment extends DialogFragment {
+    @Override
+    public Dialog onCreateDialog(Bundle savedInstanceState) {
+        // Use the Builder class for convenient dialog construction
+        AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+        builder.setMessage(R.string.dialog_fire_missiles)
+               .setPositiveButton(R.string.fire, new DialogInterface.OnClickListener() {
+                   public void onClick(DialogInterface dialog, int id) {
+                       // FIRE ZE MISSILES!
+                   }
+               })
+               .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
+                   public void onClick(DialogInterface dialog, int id) {
+                       // User cancelled the dialog
+                   }
+               });
+        // Create the AlertDialog object and return it
+        return builder.create();
+    }
+}
+
+ +
+ +

Figura 1. +Caixa de diálogo com uma mensagem e dois botões de ação.

+
+ +

Ao criar uma instância dessa classe e chamar {@link +android.support.v4.app.DialogFragment#show show()} nesse objeto, a caixa de diálogo será exibida como +ilustrado na figura 1.

+ +

A próxima seção descreve em mais detalhes o uso das APIs{@link android.app.AlertDialog.Builder} +para a criação de uma caixa de diálogo.

+ +

Conforme o nível de complexidade da caixa de diálogo, é possível implementar diversos outros métodos +de retorno de chamada no {@link android.support.v4.app.DialogFragment}, inclusive todos os +métodos de ciclo de vida de fragmentos básicos. + + + + + +

Construção de uma caixa de diálogo de alerta

+ + +

A classe {@link android.app.AlertDialog} permite a criação de diversos projetos de caixa de diálogo +e normalmente é a única classe de caixa de diálogo necessária. +Como ilustrado na figura 2, há três regiões de uma caixa de diálogo de alerta:

+ +
+ +

Figura 2. Layout de uma caixa de diálogo.

+
+ +
    +
  1. Título +

    É opcional e deve ser usado somente quando a área do conteúdo + estiver ocupada por uma mensagem detalhada, uma lista ou layout personalizado. Se for necessário declarar + uma mensagem ou pergunta simples (como a caixa de diálogo na figura 1), o título não é necessário.

  2. +
  3. Área do conteúdo +

    Pode exibir uma mensagem, uma lista ou outro layout personalizado.

  4. +
  5. Botões de ação +

    Não deve haver mais de três botões em uma caixa de diálogo.

  6. +
+ +

A classe {@link android.app.AlertDialog.Builder} + fornece APIs que permitem a criação de uma {@link android.app.AlertDialog} + com esses tipos de conteúdo, inclusive um layout personalizado.

+ +

Para criar uma {@link android.app.AlertDialog}:

+ +
+// 1. Instantiate an {@link android.app.AlertDialog.Builder} with its constructor
+AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+
+// 2. Chain together various setter methods to set the dialog characteristics
+builder.setMessage(R.string.dialog_message)
+       .setTitle(R.string.dialog_title);
+
+// 3. Get the {@link android.app.AlertDialog} from {@link android.app.AlertDialog.Builder#create()}
+AlertDialog dialog = builder.create();
+
+ +

Os tópicos a seguir mostram como definir diversos atributos de caixas de diálogo +com a classe {@link android.app.AlertDialog.Builder}.

+ + + + +

Adição de botões

+ +

Para adicionar botões de ação como os da figura 2, +chame os métodos {@link android.app.AlertDialog.Builder#setPositiveButton setPositiveButton()} e +{@link android.app.AlertDialog.Builder#setNegativeButton setNegativeButton()}:

+ +
+AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+// Add the buttons
+builder.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
+           public void onClick(DialogInterface dialog, int id) {
+               // User clicked OK button
+           }
+       });
+builder.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
+           public void onClick(DialogInterface dialog, int id) {
+               // User cancelled the dialog
+           }
+       });
+// Set other dialog properties
+...
+
+// Create the AlertDialog
+AlertDialog dialog = builder.create();
+
+ +

Os métodos set...Button() exigem um título para o botão (fornecido +por um recurso de string) e um +{@link android.content.DialogInterface.OnClickListener} que defina a ação a realizar +quando o usuário pressionar o botão.

+ +

Há três botões de ação diferente que podem ser adicionados:

+
+
Positivo
+
É o que se deve usar para aceitar e continuar a ação (a ação "OK").
+
Negativo
+
É o que se deve usar para cancelar a ação.
+
Neutro
+
É o que se deve usar quando houver a opção de o usuário não querer continuar a ação, + mas não necessariamente cancelá-la. Ele aparece entre os botões positivo + e negativo. Por exemplo: a ação pode ser "Notifique-me mais tarde".
+
+ +

É possível adicionar somente um de cada tipo de botão a uma {@link +android.app.AlertDialog}, ou seja, não é possível ter mais de um botão "positivo".

+ + + +
+ +

Figura 3. +Caixa de diálogo com um título e uma lista.

+
+ +

Adição de listas

+ +

Há três tipos de listas disponíveis nas APIs {@link android.app.AlertDialog}:

+
    +
  • Lista de escolha única tradicional
  • +
  • Lista de escolha única persistente (botões de opção)
  • +
  • Lista de escolhas múltiplas persistentes (caixas de seleção)
  • +
+ +

Para criar uma lista de escolha única como a da figura 3, +use o método {@link android.app.AlertDialog.Builder#setItems setItems()}:

+ +
+@Override
+public Dialog onCreateDialog(Bundle savedInstanceState) {
+    AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+    builder.setTitle(R.string.pick_color)
+           .setItems(R.array.colors_array, new DialogInterface.OnClickListener() {
+               public void onClick(DialogInterface dialog, int which) {
+               // The 'which' argument contains the index position
+               // of the selected item
+           }
+    });
+    return builder.create();
+}
+
+ +

Como a lista aparece na área do conteúdo da caixa de diálogo, +a caixa não pode exibir uma mensagem e uma lista, e será preciso definir um título +para ela com {@link android.app.AlertDialog.Builder#setTitle setTitle()}. +Para especificar os itens da lista, chame {@link +android.app.AlertDialog.Builder#setItems setItems()} passando uma matriz. +Alternativamente, é possível especificar uma lista com {@link +android.app.AlertDialog.Builder#setAdapter setAdapter()}. Isso permite retroceder a lista +com dados dinâmicos (como os de um banco de dados) com um {@link android.widget.ListAdapter}.

+ +

Se você optar por retroceder a lista com um {@link android.widget.ListAdapter}, +sempre use um {@link android.support.v4.content.Loader} para que o conteúdo carregue +assincronamente. Veja mais detalhes sobre isso nos guias +Criação de layouts +com um adaptador e +Carregadores.

+ +

Observação: por padrão, o toque em um item de lista dispensa a caixa de diálogo +a menos que você esteja usando uma das listas de escolha persistentes a seguir.

+ +
+ +

Figura 4. +Lista de itens de múltipla escolha.

+
+ + +

Adição de uma lista de escolha única ou de múltipla escolha persistente

+ +

Para adicionar uma lista de itens de múltipla escolha (caixas de seleção) +ou itens de escolha única (botões de rádio), use os métodos +{@link android.app.AlertDialog.Builder#setMultiChoiceItems(Cursor,String,String, +DialogInterface.OnMultiChoiceClickListener) setMultiChoiceItems()} ou +{@link android.app.AlertDialog.Builder#setSingleChoiceItems(int,int,DialogInterface.OnClickListener) +setSingleChoiceItems()} respectivamente.

+ +

Por exemplo, a seguir apresenta-se como criar uma lista de múltipla escolha como +a ilustrada na figura 4, que salva os itens +selecionados em uma {@link java.util.ArrayList}:

+ +
+@Override
+public Dialog onCreateDialog(Bundle savedInstanceState) {
+    mSelectedItems = new ArrayList();  // Where we track the selected items
+    AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+    // Set the dialog title
+    builder.setTitle(R.string.pick_toppings)
+    // Specify the list array, the items to be selected by default (null for none),
+    // and the listener through which to receive callbacks when items are selected
+           .setMultiChoiceItems(R.array.toppings, null,
+                      new DialogInterface.OnMultiChoiceClickListener() {
+               @Override
+               public void onClick(DialogInterface dialog, int which,
+                       boolean isChecked) {
+                   if (isChecked) {
+                       // If the user checked the item, add it to the selected items
+                       mSelectedItems.add(which);
+                   } else if (mSelectedItems.contains(which)) {
+                       // Else, if the item is already in the array, remove it 
+                       mSelectedItems.remove(Integer.valueOf(which));
+                   }
+               }
+           })
+    // Set the action buttons
+           .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
+               @Override
+               public void onClick(DialogInterface dialog, int id) {
+                   // User clicked OK, so save the mSelectedItems results somewhere
+                   // or return them to the component that opened the dialog
+                   ...
+               }
+           })
+           .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
+               @Override
+               public void onClick(DialogInterface dialog, int id) {
+                   ...
+               }
+           });
+
+    return builder.create();
+}
+
+ +

Embora a lista tradicional e uma lista com botões de opção +forneçam uma ação de "escolha única", deve-se usar {@link +android.app.AlertDialog.Builder#setSingleChoiceItems(int,int,DialogInterface.OnClickListener) +setSingleChoiceItems()} se você desejar manter a escolha do usuário. +Ou seja, se a caixa de diálogo abrir novamente mais tarde, deve indicar qual é a escolha atual do usuário, +portanto deve-se criar uma lista com botões de opção.

+ + + + + +

Criação de layout personalizado

+ +
+ +

Figura 5. Layout personalizado da caixa de diálogo.

+
+ +

Se você deseja um layout personalizado em uma caixa de diálogo, crie um layout e adicione-o a uma +{@link android.app.AlertDialog} chamando {@link +android.app.AlertDialog.Builder#setView setView()} no objeto {@link +android.app.AlertDialog.Builder}.

+ +

Por padrão, o layout personalizado preenche a janela da caixa de diálogo, mas ainda é possível +usar métodos {@link android.app.AlertDialog.Builder} para adicionar botões e um título.

+ +

Por exemplo, a seguir há o arquivo de layout para a caixa de diálogo na figura 5:

+ +

res/layout/dialog_signin.xml

+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content">
+    <ImageView
+        android:src="@drawable/header_logo"
+        android:layout_width="match_parent"
+        android:layout_height="64dp"
+        android:scaleType="center"
+        android:background="#FFFFBB33"
+        android:contentDescription="@string/app_name" />
+    <EditText
+        android:id="@+id/username"
+        android:inputType="textEmailAddress"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="16dp"
+        android:layout_marginLeft="4dp"
+        android:layout_marginRight="4dp"
+        android:layout_marginBottom="4dp"
+        android:hint="@string/username" />
+    <EditText
+        android:id="@+id/password"
+        android:inputType="textPassword"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="4dp"
+        android:layout_marginLeft="4dp"
+        android:layout_marginRight="4dp"
+        android:layout_marginBottom="16dp"
+        android:fontFamily="sans-serif"
+        android:hint="@string/password"/>
+</LinearLayout>
+
+ +

Dica: por padrão, ao definir um elemento +{@link android.widget.EditText} para usar o tipo de entrada {@code "textPassword"}, a família da fonte é definida como de espaço único, portanto +deve-se alterar a família da fonte para {@code "sans-serif"} para que os campos de texto usem +um estilo de fonte compatível.

+ +

Para inflar o layout no {@link android.support.v4.app.DialogFragment}, +obtenha um {@link android.view.LayoutInflater} com +{@link android.app.Activity#getLayoutInflater()} e chame +{@link android.view.LayoutInflater#inflate inflate()}, em que o primeiro parâmetro +é o ID de recurso do layout e o segundo é uma vista pai do layout. +Em seguida, pode-se chamar {@link android.app.AlertDialog#setView setView()} +para posicionar o layout na caixa de diálogo.

+ +
+@Override
+public Dialog onCreateDialog(Bundle savedInstanceState) {
+    AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+    // Get the layout inflater
+    LayoutInflater inflater = getActivity().getLayoutInflater();
+
+    // Inflate and set the layout for the dialog
+    // Pass null as the parent view because its going in the dialog layout
+    builder.setView(inflater.inflate(R.layout.dialog_signin, null))
+    // Add action buttons
+           .setPositiveButton(R.string.signin, new DialogInterface.OnClickListener() {
+               @Override
+               public void onClick(DialogInterface dialog, int id) {
+                   // sign in the user ...
+               }
+           })
+           .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
+               public void onClick(DialogInterface dialog, int id) {
+                   LoginDialogFragment.this.getDialog().cancel();
+               }
+           });      
+    return builder.create();
+}
+
+ +
+

Dica: se você deseja uma caixa de diálogo personalizada, +pode exibir uma {@link android.app.Activity} como uma caixa de diálogo +em vez de usar as APIs {@link android.app.Dialog}. Basta criar uma atividade e definir seu tema como +{@link android.R.style#Theme_Holo_Dialog Theme.Holo.Dialog} +no elemento {@code +<activity>} do manifesto:

+ +
+<activity android:theme="@android:style/Theme.Holo.Dialog" >
+
+

Pronto. Agora a atividade é exibida em uma janela da caixa de diálogo em vez de tela cheia.

+
+ + + +

Direcionamento de eventos de volta ao host da caixa de diálogo

+ +

Quando o usuário toca em um dos botões de ação da caixa de diálogo ou seleciona um item de sua lista, +o {@link android.support.v4.app.DialogFragment} pode realizar a ação +necessária sozinho, mas normalmente será necessário fornecer o evento à atividade ou ao fragmento +que abriu a caixa de diálogo. Para isso, defina uma interface com um método para cada tipo de evento de clique. +Em seguida, implemente essa interface no componente do host +que receberá os eventos de ação da caixa de diálogo.

+ +

Por exemplo, a seguir há um {@link android.support.v4.app.DialogFragment} que define +uma interface por meio da qual entrega os eventos de volta à atividade do host:

+ +
+public class NoticeDialogFragment extends DialogFragment {
+    
+    /* The activity that creates an instance of this dialog fragment must
+     * implement this interface in order to receive event callbacks.
+     * Each method passes the DialogFragment in case the host needs to query it. */
+    public interface NoticeDialogListener {
+        public void onDialogPositiveClick(DialogFragment dialog);
+        public void onDialogNegativeClick(DialogFragment dialog);
+    }
+    
+    // Use this instance of the interface to deliver action events
+    NoticeDialogListener mListener;
+    
+    // Override the Fragment.onAttach() method to instantiate the NoticeDialogListener
+    @Override
+    public void onAttach(Activity activity) {
+        super.onAttach(activity);
+        // Verify that the host activity implements the callback interface
+        try {
+            // Instantiate the NoticeDialogListener so we can send events to the host
+            mListener = (NoticeDialogListener) activity;
+        } catch (ClassCastException e) {
+            // The activity doesn't implement the interface, throw exception
+            throw new ClassCastException(activity.toString()
+                    + " must implement NoticeDialogListener");
+        }
+    }
+    ...
+}
+
+ +

A atividade que hospeda a caixa de diálogo cria uma instância da caixa +com o construtor do fragmento da caixa de diálogo e recebe os eventos +dela por meio de uma implementação da interface {@code NoticeDialogListener}:

+ +
+public class MainActivity extends FragmentActivity
+                          implements NoticeDialogFragment.NoticeDialogListener{
+    ...
+    
+    public void showNoticeDialog() {
+        // Create an instance of the dialog fragment and show it
+        DialogFragment dialog = new NoticeDialogFragment();
+        dialog.show(getSupportFragmentManager(), "NoticeDialogFragment");
+    }
+
+    // The dialog fragment receives a reference to this Activity through the
+    // Fragment.onAttach() callback, which it uses to call the following methods
+    // defined by the NoticeDialogFragment.NoticeDialogListener interface
+    @Override
+    public void onDialogPositiveClick(DialogFragment dialog) {
+        // User touched the dialog's positive button
+        ...
+    }
+
+    @Override
+    public void onDialogNegativeClick(DialogFragment dialog) {
+        // User touched the dialog's negative button
+        ...
+    }
+}
+
+ +

Como a atividade do host implementa o {@code NoticeDialogListener} — que é +forçado pelo método de retorno de chamada {@link android.support.v4.app.Fragment#onAttach onAttach()} +exibido acima —, o fragmento da caixa de diálogo pode usar +os métodos de retorno de chamada da interface para entregar eventos de clique à atividade:

+ +
+public class NoticeDialogFragment extends DialogFragment {
+    ...
+
+    @Override
+    public Dialog onCreateDialog(Bundle savedInstanceState) {
+        // Build the dialog and set up the button click handlers
+        AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+        builder.setMessage(R.string.dialog_fire_missiles)
+               .setPositiveButton(R.string.fire, new DialogInterface.OnClickListener() {
+                   public void onClick(DialogInterface dialog, int id) {
+                       // Send the positive button event back to the host activity
+                       mListener.onDialogPositiveClick(NoticeDialogFragment.this);
+                   }
+               })
+               .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
+                   public void onClick(DialogInterface dialog, int id) {
+                       // Send the negative button event back to the host activity
+                       mListener.onDialogNegativeClick(NoticeDialogFragment.this);
+                   }
+               });
+        return builder.create();
+    }
+}
+
+ + + +

Exibição de uma caixa de diálogo

+ +

Para exibir a caixa de diálogo, crie uma instância do {@link +android.support.v4.app.DialogFragment} e chame {@link android.support.v4.app.DialogFragment#show +show()} passando o {@link android.support.v4.app.FragmentManager} e um nome de tag +para o fragmento da caixa de diálogo.

+ +

Para obter o {@link android.support.v4.app.FragmentManager}, chama-se +{@link android.support.v4.app.FragmentActivity#getSupportFragmentManager()} +da {@link android.support.v4.app.FragmentActivity} ou {@link +android.support.v4.app.Fragment#getFragmentManager()} de um {@link +android.support.v4.app.Fragment}. Por exemplo:

+ +
+public void confirmFireMissiles() {
+    DialogFragment newFragment = new FireMissilesDialogFragment();
+    newFragment.show(getSupportFragmentManager(), "missiles");
+}
+
+ +

O segundo argumento, {@code "missiles"}, é um nome de tag exclusivo que o sistema usa para salvar +e restaurar o estado do fragmento quando necessário. Para que a tag obtenha um identificador +do fragmento, chama-se {@link android.support.v4.app.FragmentManager#findFragmentByTag +findFragmentByTag()}.

+ + + + +

Exibição de uma caixa de diálogo em tela cheia ou como um fragmento incorporado

+ +

Pode-se ter um projeto de IU em que uma parte da IU apareça como uma caixa de diálogo em determinadas +situações, mas como tela cheia ou fragmento incorporado em outras (dependendo, talvez, +do tamanho da tela do dispositivo). A classe {@link android.support.v4.app.DialogFragment} +oferece esta flexibilidade porque ainda pode comportar-se como um {@link +android.support.v4.app.Fragment} incorporável.

+ +

Contudo, não é possível usar {@link android.app.AlertDialog.Builder AlertDialog.Builder} +nem outros objetos {@link android.app.Dialog} para criar a caixa de diálogo nesse caso. Se +você deseja que {@link android.support.v4.app.DialogFragment} seja +incorporável, defina a IU da caixa de diálogo em um layout e carregue ou layout no retorno de chamada +{@link android.support.v4.app.DialogFragment#onCreateView +onCreateView()}.

+ +

A seguir há um exemplo de {@link android.support.v4.app.DialogFragment} que pode aparecer tanto como +caixa de diálogo quanto como fragmento incorporável (usando um layout chamado purchase_items.xml):

+ +
+public class CustomDialogFragment extends DialogFragment {
+    /** The system calls this to get the DialogFragment's layout, regardless
+        of whether it's being displayed as a dialog or an embedded fragment. */
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+            Bundle savedInstanceState) {
+        // Inflate the layout to use as dialog or embedded fragment
+        return inflater.inflate(R.layout.purchase_items, container, false);
+    }
+  
+    /** The system calls this only when creating the layout in a dialog. */
+    @Override
+    public Dialog onCreateDialog(Bundle savedInstanceState) {
+        // The only reason you might override this method when using onCreateView() is
+        // to modify any dialog characteristics. For example, the dialog includes a
+        // title by default, but your custom layout might not need it. So here you can
+        // remove the dialog title, but you must call the superclass to get the Dialog.
+        Dialog dialog = super.onCreateDialog(savedInstanceState);
+        dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
+        return dialog;
+    }
+}
+
+ +

A seguir, veja alguns códigos que decidem por exibir o fragmento como uma caixa de diálogo +ou como uma IU de tela cheia com base no tamanho da tela:

+ +
+public void showDialog() {
+    FragmentManager fragmentManager = getSupportFragmentManager();
+    CustomDialogFragment newFragment = new CustomDialogFragment();
+    
+    if (mIsLargeLayout) {
+        // The device is using a large layout, so show the fragment as a dialog
+        newFragment.show(fragmentManager, "dialog");
+    } else {
+        // The device is smaller, so show the fragment fullscreen
+        FragmentTransaction transaction = fragmentManager.beginTransaction();
+        // For a little polish, specify a transition animation
+        transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
+        // To make it fullscreen, use the 'content' root view as the container
+        // for the fragment, which is always the root view for the activity
+        transaction.add(android.R.id.content, newFragment)
+                   .addToBackStack(null).commit();
+    }
+}
+
+ +

Para obter mais informações sobre a realização de operações de fragmentos, consulte o guia +Fragmentos.

+ +

Nesse exemplo, o booleano mIsLargeLayout especifica se o dispositivo atual +deve usar o projeto de layout grande do aplicativo (e, portanto, exibir esse fragmento como uma caixa de diálogo em +vez de tela cheia). O melhor modo de definir esse tipo de booleano é declarar +um valor de recurso bool +com um valor de recurso alternativo para diferentes tamanhos de tela. Por exemplo, a seguir há duas +versões de recurso bool para diferentes tamanhos de tela:

+ +

res/values/bools.xml

+
+<!-- Default boolean values -->
+<resources>
+    <bool name="large_layout">false</bool>
+</resources>
+
+ +

res/values-large/bools.xml

+
+<!-- Large screen boolean values -->
+<resources>
+    <bool name="large_layout">true</bool>
+</resources>
+
+ +

Assim, é possível inicializar o valor {@code mIsLargeLayout} durante o método +{@link android.app.Activity#onCreate onCreate()} da atividade:

+ +
+boolean mIsLargeLayout;
+
+@Override
+public void onCreate(Bundle savedInstanceState) {
+    super.onCreate(savedInstanceState);
+    setContentView(R.layout.activity_main);
+
+    mIsLargeLayout = getResources().getBoolean(R.bool.large_layout);
+}
+
+ + + +

Exibição de uma atividade como uma caixa de diálogo em telas grandes

+ +

Em vez de exibir uma caixa de diálogo como uma IU de tela cheia em telas pequenas, é possível obter +o mesmo resultado exibindo uma {@link android.app.Activity} como uma caixa de diálogo +em telas grandes. A abordagem escolhida depende do projeto do aplicativo, +mas a exibição de uma atividade como caixa de diálogo normalmente é útil quando o aplicativo já está projetado +para telas pequenas e é preciso melhorar a experiência em tablets exibindo uma atividade de vida curta +como uma caixa de diálogo.

+ +

Para exibir uma atividade como uma caixa de diálogo somente em telas grandes, +aplique o tema {@link android.R.style#Theme_Holo_DialogWhenLarge Theme.Holo.DialogWhenLarge} +no elemento {@code +<activity>} do manifesto:

+ +
+<activity android:theme="@android:style/Theme.Holo.DialogWhenLarge" >
+
+ +

Para obter mais informações sobre estilizar as atividades com temas, consulte o guia Estilos e temas.

+ + + +

Dispensa de uma caixa de diálogo

+ +

Quando o usuário toca em qualquer botão de ação criado +com um {@link android.app.AlertDialog.Builder}, o sistema dispensa a caixa de diálogo.

+ +

O sistema também dispensa a caixa de diálogo quando o usuário toca em um item em uma lista da caixa de diálogo, exceto +quanto a lista usa botões de opção ou caixas de seleção. Caso contrário, é possível dispensá-la manualmente +chamando {@link android.support.v4.app.DialogFragment#dismiss()} no {@link +android.support.v4.app.DialogFragment}.

+ +

Se for necessário realizar determinadas +ações quando a caixa de diálogo é dispensada, é possível implementar o método {@link +android.support.v4.app.DialogFragment#onDismiss onDismiss()} no {@link +android.support.v4.app.DialogFragment}.

+ +

Também é possível cancelar uma caixa de diálogo. Trata-se de um evento especial que indica que o usuário +se retirou explicitamente da caixa de diálogo sem concluir a tarefa. Isso ocorre se o usuário pressionar o botão +Voltar, tocar na tela fora da área da caixa de diálogo +ou se você chamar {@link android.app.Dialog#cancel()} explicitamente no {@link +android.app.Dialog} (como em resposta a um botão "Cancelar" na caixa de diálogo).

+ +

Como mostrado no exemplo acima, é possível responder ao evento de cancelamento implementando +{@link android.support.v4.app.DialogFragment#onCancel onCancel()} na classe {@link +android.support.v4.app.DialogFragment}.

+ +

Observação: o sistema chama +{@link android.support.v4.app.DialogFragment#onDismiss onDismiss()} para cada evento que +chama o retorno de chamada {@link android.support.v4.app.DialogFragment#onCancel onCancel()}. Entretanto, +se você chamar {@link android.app.Dialog#dismiss Dialog.dismiss()} ou {@link +android.support.v4.app.DialogFragment#dismiss DialogFragment.dismiss()}, + o sistema chamará {@link android.support.v4.app.DialogFragment#onDismiss onDismiss()}, mas +não {@link android.support.v4.app.DialogFragment#onCancel onCancel()}. Portanto, geralmente, deve-se +chamar {@link android.support.v4.app.DialogFragment#dismiss dismiss()} quando o usuário pressiona +o botão positivo na caixa de diálogo para removê-la da vista.

+ + diff --git a/docs/html-intl/intl/pt-br/guide/topics/ui/menus.jd b/docs/html-intl/intl/pt-br/guide/topics/ui/menus.jd new file mode 100644 index 0000000000000000000000000000000000000000..833f8966b4391bfe7a3105d0e2acdc80dd1593e5 --- /dev/null +++ b/docs/html-intl/intl/pt-br/guide/topics/ui/menus.jd @@ -0,0 +1,1031 @@ +page.title=Menus +parent.title=Interface do usuário +parent.link=index.html +@jd:body + + + +

Menus são componentes comuns da interface do usuário em diversos tipos de aplicativos. Para fornecer uma experiência +familiar e consistente ao usuário, você deve usar APIs de {@link android.view.Menu} para apresentar +ações de usuário e outras opções em suas atividades.

+ +

Começando com Android 3.0 (API de nível 11), dispositivos Android não são mais necessários +para fornecer um botão de Menu dedicado. Com esta alteração, os aplicativos do Android devem migrar de uma dependência +no painel de menu 6 itens tradicional para fornecer uma barra de ação para apresentar as ações comuns +de usuário.

+ +

Apesar de o design e a experiência do usuário para alguns dos itens do menu terem passado por mudanças, a semântica para definir +um conjunto de ações e opções ainda baseia-se em APIs de {@link android.view.Menu} . Este guia +mostra como criar os três tipos fundamentais de menus ou apresentações de ação +em todas as versões do Android:

+ +
+
Menu de opções e barra de ação
+
O menu de opções é a coleção principal de itens de menu +para uma atividade. É onde deve-se colocar as ações que têm impacto global no aplicativo, +como "Buscar", "Escrever e-mail" e "Configurações". +

Se estiver desenvolvendo para Android 2.3 ou anterior, os usuários +podem revelar o painel do menu de opções pressionando o botão Menu.

+

No Android 3.0 ou em posteriores, os itens do menu de opções são apresentados pela barra de ação como uma combinação de itens +de ação na tela e opções de estouro. A partir do Android 3.0, o botão Menu é censurado (alguns +dispositivos +não têm), então você deve migrar usando a barra de ação para fornecer acesso a ações +e outras opções.

+

Consulte a seção Criação de um menu de opções.

+
+ +
Modo de ação contextual e menu de contexto
+ +
Um menu de contexto é um menu flutuante que aparece quando +o usuário realiza um clique longo em um elemento. Ele fornece ações que afetam o conteúdo selecionado +ou a estrutura do contexto. +

Ao desenvolver para Android 3.0 ou posterior, você deve usar o modo de ação contextual para ativar as ações no conteúdo selecionado. Este modo exibe os itens de ação +que afetam o conteúdo selecionado em uma barra no topo da tela e permite que o usuário +selecione vários itens.

+

Consulte a seção Criação de menus contextuais.

+
+ +
Menu pop-up
+
Um menu pop-up exibe itens em uma lista vertical ancorada à vista +que apresentou o menu. É bom para fornecer um estouro de ações relacionado a conteúdo específico +ou opções de fornecimento de uma segunda parte de um comando. As ações em um menu pop-up +não devem afetar diretamente o conteúdo correspondente — é para isso que servem +as ações contextuais. Preferivelmente, o menu pop-up serve para ações estendidas que relacionam as regiões de conteúdo +na atividade. +

Consulte a seção criar um menu pop-up.

+
+
+ + + +

Definição de um menu em XML

+ +

Para todos os tipos de menu, o Android fornece um formato XML padrão para definir os itens de menu. +Em vez de criar um menu no código da atividade, você deve definir um menu e todos os seus itens +em um recurso de menu XML. É possível, assim, +inflar o recurso do menu (carregá-lo como um objeto {@link android.view.Menu}) na atividade, ou +no fragmento.

+ +

Usar um recurso de menu é uma boa prática por alguns motivos:

+
    +
  • É mais fácil para visualizar a estrutura do menu em XML.
  • +
  • Ele separa o conteúdo do menu do código comportamental do aplicativo.
  • +
  • Ele permite criar configurações alternativas de menu para versões diferentes de plataforma, +de tamanhos de tela e de outras configurações aproveitando a estrutura dos recursos do aplicativo.
  • +
+ +

Para definir o menu, crie um arquivo XML dentro do diretório res/menu/ +do projeto e crie o menu com os seguintes elementos:

+
+
<menu>
+
Define um {@link android.view.Menu}, que é um recipiente para os itens de menu. Um elemento +<menu> deve ser o nódulo raiz para o arquivo e pode reter um ou mais elementos +<item> e <group>.
+ +
<item>
+
Cria um {@link android.view.MenuItem}, que representa um único item em um menu. Este +elemento pode conter um elemento <menu> aninhado para criar um submenu.
+ +
<group>
+
Um recipiente invisível e opcional para os elementos {@code <item>}. Ele permite que você categorize +itens de menu para que eles compartilhem propriedades como estado ativo e visibilidade. Para obter mais informações, +consulte a seção Criação de grupos de menu.
+
+ + +

A seguir há um exemplo de menu chamado game_menu.xml:

+
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:id="@+id/new_game"
+          android:icon="@drawable/ic_new_game"
+          android:title="@string/new_game"
+          android:showAsAction="ifRoom"/>
+    <item android:id="@+id/help"
+          android:icon="@drawable/ic_help"
+          android:title="@string/help" />
+</menu>
+
+ +

O elemento <item> é compatível com vários atributos que você pode usar para definir a aparência ou o comportamento +de um item. Os itens no menu acima incluem os seguintes atributos:

+ +
+
{@code android:id}
+
Um ID de recurso que é único para o item. Ele permite que o aplicativo reconheça o item +quando o usuário o seleciona.
+
{@code android:icon}
+
Uma referência a um desenhável para usar como o ícone do item.
+
{@code android:title}
+
Uma referência a uma string para usar como o título do item.
+
{@code android:showAsAction}
+
Especifica quando e como este item deve aparecer como um item de ação na barra de ação.
+
+ +

Esses são os atributos mais importantes que devem ser usados, mas há vários outros disponíveis. +Para obter informações sobre todos os atributos compatíveis, consulte o documento Recurso de menu.

+ +

É possível adicionar um submenu a um item em qualquer menu (exceto a um submenu) adicionando um elemento {@code <menu>} +como filho de um {@code <item>}. Os submenus são úteis quando o aplicativo +tem várias funções que podem ser organizadas em tópicos, como itens em uma barra de menu do aplicativo do PC (arquivo, +editar, visualizar etc.). Por exemplo:

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:id="@+id/file"
+          android:title="@string/file" >
+        <!-- "file" submenu -->
+        <menu>
+            <item android:id="@+id/create_new"
+                  android:title="@string/create_new" />
+            <item android:id="@+id/open"
+                  android:title="@string/open" />
+        </menu>
+    </item>
+</menu>
+
+ +

Para usar o menu em sua atividade, você precisa inflar o recurso do menu (converter o recurso +XML em um objeto programável) usando {@link android.view.MenuInflater#inflate(int,Menu) +MenuInflater.inflate()}. Nas seções a seguir, você verá como inflar um menu para cada +tipo de menu.

+ + + +

Criação de um menu de opções

+ +
+ +

Figura 1. Menu de opções +no navegador, no Android 2.3.

+
+ +

O menu de opções é onde você deve incluir ações e outras opções que são relevantes +para o contexto de atividade atual, como "Buscar", "Escrever e-mail" e "Configurações".

+ +

O local onde os itens no menu de opções aparecem na tela depende da versão em que o aplicativo +foi desenvolvido:

+ +
    +
  • Caso tenha desenvolvido o aplicativo para Android 2.3.x (API de nível 10) ou +inferior, os conteúdos do menu de opções aparecerão na parte inferior da tela, quando o usuário +pressionar o botão Menu, como exibido na figura 1. Quando aberto, a primeira parte visível é +o menu +de ícones, que possui até seis itens de menu. Se o menu incluir mais de seis itens, o Android +colocará o sexto item e o resto em um menu flutuante, que o usuário poderá abrir selecionando +Mais.
  • + +
  • Se você desenvolveu o aplicativo para Android 3.0 (API de nível 11) ou +superior, os itens do menu de opções estão disponíveis na barra de ação. Por padrão, o sistema +posiciona todos os itens na ação de estouro, que o usuário pode revelar com o ícone de estouro de ação +no lado direito da barra de ação (ou pressionando o botão Menu, se disponível). Para +ativar +o acesso rápido a ações importantes, é possível promover alguns itens para aparecerem na barra de ação adicionando +{@code android:showAsAction="ifRoom"} aos elementos {@code <item>} correspondentes (veja a figura +2).

    Para obter mais informações sobre os itens de ação e outros comportamentos da barra de ação, consulte o guia Barra de ação.

    +

    Observação: Mesmo se não estiver desenvolvendo para Android 3.0 ou +posteriores, é possível compilar seu próprio layout de barra de ação para obter um efeito semelhante. Para obter um exemplo de como é possível +suportar versões mais antigas do Android com uma barra de ação, consulte o exemplo Compatibilidade da barra de ação +.

    +
  • +
+ + +

Figura 2. Barra de ação do aplicativo Honeycomb Gallery, exibindo +guias de navegação e um item de ação de câmera (além do botão de estouro de ação).

+ +

É possível declarar itens para o menu de opções da subclasse {@link android.app.Activity} +ou de uma subclasse {@link android.app.Fragment}. Se a atividade e os fragmentos +declararem itens para o menu de opções, eles estarão combinados na IU. O item da atividade aparece primeiro, +seguido de cada um desses fragmentos na ordem em que são adicionados +à atividade. Se necessário, é possível reorganizar os itens do menu com o atributo {@code android:orderInCategory} +em cada {@code <item>} que precisar mover.

+ +

Para especificar o menu de opções para uma atividade, substitua {@link +android.app.Activity#onCreateOptionsMenu(Menu) onCreateOptionsMenu()} (os fragmentos fornecem +o próprio retorno de chamada de {@link android.app.Fragment#onCreateOptionsMenu onCreateOptionsMenu()}). Neste método +, é possível inflar o recurso de menu (definido no XML) em um {@link +android.view.Menu} fornecido no retorno de chamada. Por exemplo:

+ +
+@Override
+public boolean onCreateOptionsMenu(Menu menu) {
+    MenuInflater inflater = {@link android.app.Activity#getMenuInflater()};
+    inflater.inflate(R.menu.game_menu, menu);
+    return true;
+}
+
+ +

Também é possível adicionar itens de menu usando {@link android.view.Menu#add(int,int,int,int) +add()} e recuperar os itens com {@link android.view.Menu#findItem findItem()} para revisar +as propriedades com APIs de {@link android.view.MenuItem}.

+ +

Caso tenha desenvolvido o aplicativo para Android 2.3.x e anteriores, o sistema chamará {@link +android.app.Activity#onCreateOptionsMenu(Menu) onCreateOptionsMenu()} para criar o menu de opções +quando o usuário abrir o menu pela primeira vez. Caso tenha desenvolvido para Android 3.0 e posteriores, +o sistema chama {@link android.app.Activity#onCreateOptionsMenu(Menu) onCreateOptionsMenu()} +ao iniciar a atividade para mostrar os itens para a barra de ação.

+ + + +

Tratamento de eventos de clique

+ +

Quando o usuário seleciona um item para o menu de opções (incluindo os itens de ação na barra de ação), +o sistema chama o método {@link android.app.Activity#onOptionsItemSelected(MenuItem) +onOptionsItemSelected()} da atividade. Este método passa o {@link android.view.MenuItem} selecionado. É possível +identificar o item chamando {@link android.view.MenuItem#getItemId()}, que retorna o ID único +para o item de menu (definido pelo atributo {@code android:id} no recurso de menu ou em um número inteiro +dado ao método {@link android.view.Menu#add(int,int,int,int) add()}). É possível combinar este ID +com itens de menu conhecidos para realizar a ação adequada. Por exemplo:

+ +
+@Override
+public boolean onOptionsItemSelected(MenuItem item) {
+    // Handle item selection
+    switch (item.getItemId()) {
+        case R.id.new_game:
+            newGame();
+            return true;
+        case R.id.help:
+            showHelp();
+            return true;
+        default:
+            return super.onOptionsItemSelected(item);
+    }
+}
+
+ +

Ao lidar com um item de menu, retorne {@code true}. Se não lidar com o item de menu, + você deverá chamar a implementação de superclasse de {@link +android.app.Activity#onOptionsItemSelected(MenuItem) onOptionsItemSelected()} (a implementação +padrão retornará como falsa).

+ +

Se a atividade incluir fragmentos, o sistema chamará primeiro {@link +android.app.Activity#onOptionsItemSelected(MenuItem) onOptionsItemSelected()} para a atividade e, em seguida, +para cada fragmento (na ordem em que cada fragmento foi adicionado) até um retornar como +{@code true} ou até todos os fragmentos serem chamados.

+ +

Dica: o Android 3.0 adiciona a possibilidade de definir o comportamento do clique +para um item de menu em XML, usando o atributo {@code android:onClick}. O valor do atributo +deve ser o nome de um método definido pela atividade usando o menu. O método +deve ser público e aceitar um único parâmetro {@link android.view.MenuItem} — quando o sistema chamar este método, + ele passará o item de menu selecionado. Para obter mais informações e um exemplo, consulte o documento Recurso de menu.

+ +

Dica: se o aplicativo contiver várias atividades +e algumas delas fornecerem o mesmo menu de opções, +considere criar uma atividade que não implemente nada exceto os métodos {@link android.app.Activity#onCreateOptionsMenu(Menu) +onCreateOptionsMenu()} e {@link android.app.Activity#onOptionsItemSelected(MenuItem) +onOptionsItemSelected()}. Em seguida, estenda esta classe para cada atividade que deve compartilhar +o mesmo menu de opções. Desta maneira, é possível gerenciar um conjunto de códigos para lidar com ações de menu +e cada classe descendente herda os comportamentos do menu. +Se quiser adicionar itens de menu a uma das atividades descendentes, +substitua {@link android.app.Activity#onCreateOptionsMenu(Menu) +onCreateOptionsMenu()} nesta atividade. Chame {@code super.onCreateOptionsMenu(menu)} para que os itens de menu originais +sejam criados e, em seguida, adicione os novos itens de menu com {@link +android.view.Menu#add(int,int,int,int) menu.add()}. Você também pode substituir o comportamento +da superclasse para itens de menu individuais.

+ + +

Alteração dos itens de menu em tempo de execução

+ +

Depois que o sistema chamar {@link android.app.Activity#onCreateOptionsMenu(Menu) +onCreateOptionsMenu()}, ele reterá uma instância do {@link android.view.Menu} que você populará +e não chamará {@link android.app.Activity#onCreateOptionsMenu(Menu) onCreateOptionsMenu()} +novamente, a não ser que o menu seja invalidado por algum motivo. No entanto, você deve usar {@link +android.app.Activity#onCreateOptionsMenu(Menu) onCreateOptionsMenu()} somente para criar o estado inicial do menu +e não para realizar alterações durante o ciclo de vida da atividade.

+ +

Caso queira modificar o menu de opções com base +em eventos que ocorrem durante o ciclo de vida da atividade, é possível fazê-lo +no método {@link android.app.Activity#onPrepareOptionsMenu(Menu) onPrepareOptionsMenu()}. Este método +passa a você o objeto {@link android.view.Menu}, já que ele existe para que seja possível modificá-lo, +como com adição, remoção ou desativação de itens. (Os fragmentos também fornecem um retorno de chamada {@link +android.app.Fragment#onPrepareOptionsMenu onPrepareOptionsMenu()}.)

+ +

No Android 2.3.x e em anteriores, o sistema chamará {@link +android.app.Activity#onPrepareOptionsMenu(Menu) +onPrepareOptionsMenu()} sempre que o usuário abrir o menu de opções (pressionar o botão Menu +).

+ +

No Android 3.0 e posteriores, avalia-se o menu de opções quanto a sempre estar aberto quando os itens de menu +são apresentados na barra de ação. Quando um evento ocorre e você quer realizar uma atualização de menu, +você deve chamar {@link android.app.Activity#invalidateOptionsMenu invalidateOptionsMenu()} para pedir +que o sistema chame {@link android.app.Activity#onPrepareOptionsMenu(Menu) onPrepareOptionsMenu()}.

+ +

Observação: +você nunca deve alterar os itens no menu de opções com base no {@link android.view.View} atualmente +em foco. Quando estiver no modo de toque (quando o usuário não está usando cursor de bola ou um teclado), as vistas +não podem ter foco, então você nunca deve usar o foco como base para modificar +os itens no menu de opções. Se quiser fornecer itens de menu que sejam sensíveis a contexto para um {@link +android.view.View}, use um menu de contexto.

+ + + + +

Criação de menus contextuais

+ +
+ +

Figura 3. Capturas de tela de um menu de contexto flutuante (esquerda) +e a barra de ação contextual (direita).

+
+ +

Um menu contextual oferece ações que afetam um item ou estrutura de contexto específica na IU. +É possível fornecer um menu de contexto para qualquer vista, mas ele é geralmente usado para itens em um {@link +android.widget.ListView}, {@link android.widget.GridView}, ou em outras coleções de vistas +em que o usuário pode realizar ações diretas em cada item.

+ +

Há duas formas de fornecer ações contextuais:

+
    +
  • Em um menu de contexto flutuante. Um menu aparece como uma lista flutuante +de itens de menu (semelhante a uma caixa de diálogo) quando o usuário realiza um clique longo (pressiona e segura) +em uma vista que declara suporte para um menu de contexto. Os usuários podem realizar uma ação contextual +em um item por vez.
  • + +
  • No modo de ação contextual. Este modo é uma implementação de sistema de +{@link android.view.ActionMode} que exibe uma barra de ação contextual no topo da tela +com itens de ação que afetam os itens selecionados. Quando este modo está ativo, +os usuários podem realizar uma ação em vários itens por vez (se o aplicativo permitir).
  • +
+ +

Observação: o modo de ação contextual está disponível no Android 3.0 (API +de nível 11) e em posteriores e é a técnica preferencial para exibir ações contextuais +quando disponível. Se o aplicativo for compatível com versões mais antigas que a 3.0, então você deve retornar a um menu +de contexto flutuante nestes dispositivos.

+ + +

Criação de um menu de contexto flutuante

+ +

Para fornecer um menu de contexto flutuante:

+
    +
  1. Registre o {@link android.view.View} ao qual o menu de contexto deve estar associado +chamando {@link android.app.Activity#registerForContextMenu(View) registerForContextMenu()} e passe-o +para {@link android.view.View}. +

    Se a atividade usar {@link android.widget.ListView} ou {@link android.widget.GridView} +e você quiser que cada item forneça o mesmo menu de contexto, registre todos os itens para um menu de contexto +passando {@link android.widget.ListView} ou {@link android.widget.GridView} para {@link +android.app.Activity#registerForContextMenu(View) registerForContextMenu()}.

    +
  2. + +
  3. Implemente o método {@link +android.view.View.OnCreateContextMenuListener#onCreateContextMenu onCreateContextMenu()} +em {@link android.app.Activity} ou {@link android.app.Fragment}. +

    Quando a vista registrada receber um evento de clique longo, o sistema chamará o método {@link +android.view.View.OnCreateContextMenuListener#onCreateContextMenu onCreateContextMenu()} +. É aqui que você define os itens de menu, geralmente inflando um recurso de menu. Por +exemplo:

    +
    +@Override
    +public void onCreateContextMenu(ContextMenu menu, View v,
    +                                ContextMenuInfo menuInfo) {
    +    super.onCreateContextMenu(menu, v, menuInfo);
    +    MenuInflater inflater = getMenuInflater();
    +    inflater.inflate(R.menu.context_menu, menu);
    +}
    +
    + +

    {@link android.view.MenuInflater} permite que você infle o menu de contexto de um recurso de menu. Os parâmetros do método +de retorno de chamada incluem o {@link android.view.View} +que o usuário selecionou e um objeto {@link android.view.ContextMenu.ContextMenuInfo} que fornece +informações adicionais sobre o item selecionado. Se sua atividade tiver várias vistas, em que cada uma forneça +um menu de contexto diferente, você deve usar esses parâmetros para determinar qual menu de contexto +deve ser inflado.

    +
  4. + +
  5. Implemente {@link android.app.Activity#onContextItemSelected(MenuItem) +onContextItemSelected()}. +

    Quando o usuário selecionar um item de menu, o sistema chamará este método para que você possa realizar +a ação adequada. Por exemplo:

    + +
    +@Override
    +public boolean onContextItemSelected(MenuItem item) {
    +    AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo();
    +    switch (item.getItemId()) {
    +        case R.id.edit:
    +            editNote(info.id);
    +            return true;
    +        case R.id.delete:
    +            deleteNote(info.id);
    +            return true;
    +        default:
    +            return super.onContextItemSelected(item);
    +    }
    +}
    +
    + +

    O método {@link android.view.MenuItem#getItemId()} consulta o ID +para o item de menu selecionado, o qual pode ser atribuído a cada item de menu no XML usando o atributo {@code +android:id}, como exibido na seção Definição de um menu em +XML.

    + +

    Ao lidar com um item de menu, retorne {@code true}. Se não lidar com o item de menu, +você deverá passar o item de menu para a implementação de superclasse. Se a atividade incluir fragmentos, +ela receberá este retorno de chamada primeiro. Ao chamar a superclasse ao não lidar, o sistema +passará o evento para o respectivo método de retorno de chamada em cada fragmento, um por vez (na ordem +em que cada fragmento foi adicionado) até que {@code true} ou {@code false} seja retornado. (A implementação +padrão para {@link android.app.Activity} e {@code android.app.Fragment} retorna {@code +false}, então você deve sempre chamar a superclasse ao não tratar de um item de menu.)

    +
  6. +
+ + +

Uso do modo de ação contextual

+ +

O modo de ação contextual é uma implementação de sistema de {@link android.view.ActionMode} +que direciona a interação do usuário a efetuar ações contextuais. Quando um usuário +ativa este modo selecionando um item, uma barra de ação contextual aparece na parte superior da tela +para apresentar as ações que o usuário pode realizar nos itens selecionados. Enquanto este modo estiver ativo, +o usuário pode selecionar vários itens (se você permitir), desmarcar itens e continuar +a navegar dentro da atividade (o tanto que você permitir). O modo de ação +é desativado e a barra de ação contextual desaparece quando o usuário desmarca todos os itens, pressiona o botão VOLTAR, +ou seleciona a ação Pronto na lateral esquerda da barra.

+ +

Observação: a barra de ação contextual não é necessariamente +associada à barra de ação. Elas operam de forma independente, +apesar de a barra de ação contextual ocupar visualmente a posição +da barra de ação.

+ +

Caso esteja desenvolvendo para Android 3.0 (API de nível 11) e posteriores, +você deve usar o modo de ação contextual para apresentar ações contextuais, em vez de usar o menu de contexto flutuante.

+ +

Para oferecer vistas que fornecem ações contextuais, você deve invocar o modo de ação contextual +sobre um dos eventos (ou ambos):

+
    +
  • O usuário realiza um clique longo na vista.
  • +
  • O usuário seleciona uma caixa de seleção ou um componente de IU semelhante dentro da vista.
  • +
+ +

A maneira do aplicativo de invocar o modo de ação contextual e definir o comportamento +para cada ação depende do seu projeto. Há basicamente dois projetos:

+
    +
  • Para ações contextuais em vistas arbitrárias individuais.
  • +
  • Para ações contextuais de agrupadas em itens em um {@link +android.widget.ListView} ou {@link android.widget.GridView} (permitindo que o usuário selecione vários itens +e realize uma ação em todos eles).
  • +
+ +

As seguintes seções descrevem a configuração necessária para cada cenário.

+ + +

Ativação do modo de ação contextual para vistas individuais

+ +

Caso queira invocar o modo de ação contextual somente quando o usuário selecionar +vistas específicas, você deve:

+
    +
  1. Implementar a interface {@link android.view.ActionMode.Callback}. Em seus métodos de retorno de chamada, +é possível especificar as ações da barra de ação contextual, responder aos eventos de clique em itens de ação, +e tratar de outros eventos de ciclo de vida do modo de ação.
  2. +
  3. Chame {@link android.app.Activity#startActionMode startActionMode()} quando quiser exibir +a barra (como quando o usuário realiza cliques longos na visualização).
  4. +
+ +

Por exemplo:

+ +
    +
  1. Implementar a interface {@link android.view.ActionMode.Callback ActionMode.Callback}: +
    +private ActionMode.Callback mActionModeCallback = new ActionMode.Callback() {
    +
    +    // Called when the action mode is created; startActionMode() was called
    +    @Override
    +    public boolean onCreateActionMode(ActionMode mode, Menu menu) {
    +        // Inflate a menu resource providing context menu items
    +        MenuInflater inflater = mode.getMenuInflater();
    +        inflater.inflate(R.menu.context_menu, menu);
    +        return true;
    +    }
    +
    +    // Called each time the action mode is shown. Always called after onCreateActionMode, but
    +    // may be called multiple times if the mode is invalidated.
    +    @Override
    +    public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
    +        return false; // Return false if nothing is done
    +    }
    +
    +    // Called when the user selects a contextual menu item
    +    @Override
    +    public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
    +        switch (item.getItemId()) {
    +            case R.id.menu_share:
    +                shareCurrentItem();
    +                mode.finish(); // Action picked, so close the CAB
    +                return true;
    +            default:
    +                return false;
    +        }
    +    }
    +
    +    // Called when the user exits the action mode
    +    @Override
    +    public void onDestroyActionMode(ActionMode mode) {
    +        mActionMode = null;
    +    }
    +};
    +
    + +

    Observe que esses retornos de chamada de eventos são quase exatamente iguais aos retornos de chamada do menu de opções, exceto que cada um deles também passa o objeto {@link +android.view.ActionMode} associado ao evento. É possível usar APIs de {@link +android.view.ActionMode} para realizar várias alterações ao CAB, como revisar um título e um subtítulo +com {@link android.view.ActionMode#setTitle setTitle()} e {@link +android.view.ActionMode#setSubtitle setSubtitle()} (útil para indicar quantos itens +são selecionados).

    + +

    Observe também que os exemplos acima definem a variável {@code mActionMode} como nula quando +o modo de ação é destruído. Na etapa a seguir, você verá como ela é inicializada +e quão útil salvar a variável do membro na atividade ou no fragmento pode ser.

    +
  2. + +
  3. Chame {@link android.app.Activity#startActionMode startActionMode()} para ativar o modo de ação contextual +quando apropriado, como em resposta a um clique longo em um {@link +android.view.View}:

    + +
    +someView.setOnLongClickListener(new View.OnLongClickListener() {
    +    // Called when the user long-clicks on someView
    +    public boolean onLongClick(View view) {
    +        if (mActionMode != null) {
    +            return false;
    +        }
    +
    +        // Start the CAB using the ActionMode.Callback defined above
    +        mActionMode = getActivity().startActionMode(mActionModeCallback);
    +        view.setSelected(true);
    +        return true;
    +    }
    +});
    +
    + +

    Ao chamar {@link android.app.Activity#startActionMode startActionMode()}, o sistema +retorna o {@link android.view.ActionMode} criado. Ao salvar isto em uma variável do membro, +é possível realizar alterações na barra de ação contextual em resposta a outros eventos. No exemplo acima, +{@link android.view.ActionMode} é usado para garantir que a instância {@link android.view.ActionMode} +não seja recriada se já estiver ativa, verificando se o membro é nulo antes de iniciar +o modo de ação.

    +
  4. +
+ + + +

Ativação de ações contextuais agrupadas em ListView ou GridView

+ +

Se tiver uma coleção de itens em um {@link android.widget.ListView} ou {@link +android.widget.GridView} (ou outra extensão de {@link android.widget.AbsListView}) e quiser +permitir que os usuários realizem ações de agrupamento, você deve:

+ +
    +
  • Implementar a interface {@link android.widget.AbsListView.MultiChoiceModeListener} e defini-la +para o grupo de visualização com {@link android.widget.AbsListView#setMultiChoiceModeListener +setMultiChoiceModeListener()}. Nos métodos de retorno de chamada da escuta, é possível especificar as ações +para a barra de ação contextual, responder a eventos de clique em itens de ação e lidar com outros retornos de chamada +herdados da interface {@link android.view.ActionMode.Callback}.
  • + +
  • Chame {@link android.widget.AbsListView#setChoiceMode setChoiceMode()} com o argumento {@link +android.widget.AbsListView#CHOICE_MODE_MULTIPLE_MODAL}.
  • +
+ +

Por exemplo:

+ +
+ListView listView = getListView();
+listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL);
+listView.setMultiChoiceModeListener(new MultiChoiceModeListener() {
+
+    @Override
+    public void onItemCheckedStateChanged(ActionMode mode, int position,
+                                          long id, boolean checked) {
+        // Here you can do something when items are selected/de-selected,
+        // such as update the title in the CAB
+    }
+
+    @Override
+    public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
+        // Respond to clicks on the actions in the CAB
+        switch (item.getItemId()) {
+            case R.id.menu_delete:
+                deleteSelectedItems();
+                mode.finish(); // Action picked, so close the CAB
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    @Override
+    public boolean onCreateActionMode(ActionMode mode, Menu menu) {
+        // Inflate the menu for the CAB
+        MenuInflater inflater = mode.getMenuInflater();
+        inflater.inflate(R.menu.context, menu);
+        return true;
+    }
+
+    @Override
+    public void onDestroyActionMode(ActionMode mode) {
+        // Here you can make any necessary updates to the activity when
+        // the CAB is removed. By default, selected items are deselected/unchecked.
+    }
+
+    @Override
+    public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
+        // Here you can perform updates to the CAB due to
+        // an {@link android.view.ActionMode#invalidate} request
+        return false;
+    }
+});
+
+ +

É isso. Agora, quando o usuário selecionar um item com um clique longo, o sistema chamará o método {@link +android.widget.AbsListView.MultiChoiceModeListener#onCreateActionMode onCreateActionMode()} +e exibirá a barra de ação contextual com as ações especificadas. Enquanto a barra de ação contextual +estiver visível, os usuários poderão selecionar itens adicionais.

+ +

Em alguns casos em que as ações contextuais fornecem itens de ação comuns, você pode +querer adicionar uma caixa de seleção ou um elemento de IU semelhante que permite que os usuários selecionem itens, +pois eles podem não descobrir o comportamento do clique longo. Quando um usuário seleciona a caixa de seleção, +é possível invocar o modo de ação contextual definindo o respectivo item de lista +para o estado marcado com {@link android.widget.AbsListView#setItemChecked setItemChecked()}.

+ + + + +

Criação de um menu pop-up

+ +
+ +

Figura 4. Um menu pop-up no aplicativo do Gmail, ancorado ao botão +de estouro no cando direito superior.

+
+ +

Um {@link android.widget.PopupMenu} é um menu modal ancorado a uma {@link android.view.View}. +Ele aparece sob a vista de âncora se tiver espaço, ou sobre a vista. Ele é útil para:

+
    +
  • Fornecer um menu de estilo de estouro para ações que se relacionam com o conteúdo específico (como +cabeçalhos de e-mail do Gmail, exibidos na figura 4). +

    Observação: Isto não é igual ao menu de contexto, +que geralmente é usado para ações que afetam o conteúdo selecionado. Para ações que afetam o conteúdo +selecionado, use o modo de ação contextual ou o menu de contexto flutuante.

  • +
  • Fornecer uma segunda parte de uma sentença de comando (como um botão marcado como "Adicionar" +que produz um menu pop-up com opções diferentes de "Adicionar").
  • +
  • Fornecer um menu suspenso semelhante a {@link android.widget.Spinner} que não retenha +uma seleção persistente.
  • +
+ + +

Observação: {@link android.widget.PopupMenu} está disponível com a API +de nível 11 ou posteriores.

+ +

Se definir o menu em XML, abaixo é exposto o modo de exibir o menu pop-up:

+
    +
  1. Represente um {@link android.widget.PopupMenu} com seu construtor, +que usa o aplicativo {@link android.content.Context} e {@link android.view.View} atual ao qual o menu +deve ser ancorado.
  2. +
  3. Use {@link android.view.MenuInflater} para inflar o recurso de menu no objeto {@link +android.view.Menu} retornado por {@link +android.widget.PopupMenu#getMenu() PopupMenu.getMenu()}. Em APIs de nível 14 ou posteriores, é possível usar +{@link android.widget.PopupMenu#inflate PopupMenu.inflate()}.
  4. +
  5. Chame {@link android.widget.PopupMenu#show() PopupMenu.show()}.
  6. +
+ +

Por exemplo, a seguir há um botão com o atributo {@link android.R.attr#onClick android:onClick} +que exibe um menu pop-up:

+ +
+<ImageButton
+    android:layout_width="wrap_content" 
+    android:layout_height="wrap_content" 
+    android:src="@drawable/ic_overflow_holo_dark"
+    android:contentDescription="@string/descr_overflow_button"
+    android:onClick="showPopup" />
+
+ +

A atividade pode então exibir o menu pop-up desta forma:

+ +
+public void showPopup(View v) {
+    PopupMenu popup = new PopupMenu(this, v);
+    MenuInflater inflater = popup.getMenuInflater();
+    inflater.inflate(R.menu.actions, popup.getMenu());
+    popup.show();
+}
+
+ +

Em APIs de nível 14 ou posteriores, é possível combinar as duas linhas que inflam o menu com {@link +android.widget.PopupMenu#inflate PopupMenu.inflate()}.

+ +

O menu é dispensado quando o usuário seleciona um item ou toca fora +da área do menu. É possível ouvir o evento de dispensa usando {@link +android.widget.PopupMenu.OnDismissListener}.

+ +

Tratamento de eventos de clique

+ +

Para realizar uma ação +quando o usuário seleciona um item de menu, você deve implementar a interface {@link +android.widget.PopupMenu.OnMenuItemClickListener} e registrá-la com {@link +android.widget.PopupMenu} chamando {@link android.widget.PopupMenu#setOnMenuItemClickListener +setOnMenuItemclickListener()}. Quando o usuário seleciona um item, o sistema chama o retorno de chamada {@link +android.widget.PopupMenu.OnMenuItemClickListener#onMenuItemClick onMenuItemClick()} +na interface.

+ +

Por exemplo:

+ +
+public void showMenu(View v) {
+    PopupMenu popup = new PopupMenu(this, v);
+
+    // This activity implements OnMenuItemClickListener
+    popup.setOnMenuItemClickListener(this);
+    popup.inflate(R.menu.actions);
+    popup.show();
+}
+
+@Override
+public boolean onMenuItemClick(MenuItem item) {
+    switch (item.getItemId()) {
+        case R.id.archive:
+            archive(item);
+            return true;
+        case R.id.delete:
+            delete(item);
+            return true;
+        default:
+            return false;
+    }
+}
+
+ + +

Criação de grupos de menu

+ +

Um grupo de menu é uma coleção de itens de menu que compartilham certas peculiaridades. Com um grupo, +é possível:

+
    +
  • Exibir ou ocultar todos os itens com {@link android.view.Menu#setGroupVisible(int,boolean) +setGroupVisible()}
  • +
  • Ativar ou desativar todos os itens com {@link android.view.Menu#setGroupEnabled(int,boolean) +setGroupEnabled()}
  • +
  • Especificar se todos os itens são marcáveis com {@link +android.view.Menu#setGroupCheckable(int,boolean,boolean) setGroupCheckable()}
  • +
+ +

É possível criar um grupo aninhando elementos {@code <item>} dentro de um elemento {@code <group>} +no recurso de menu ou especificando um ID de grupo com o método {@link +android.view.Menu#add(int,int,int,int) add()}.

+ +

Abaixo há um exemplo de recurso de menu que inclui um grupo:

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:id="@+id/menu_save"
+          android:icon="@drawable/menu_save"
+          android:title="@string/menu_save" />
+    <!-- menu group -->
+    <group android:id="@+id/group_delete">
+        <item android:id="@+id/menu_archive"
+              android:title="@string/menu_archive" />
+        <item android:id="@+id/menu_delete"
+              android:title="@string/menu_delete" />
+    </group>
+</menu>
+
+ +

Os itens que estão no grupo aparecem no mesmo nível que o primeiro item — todos os três itens +no menu são irmãos. No entanto, é possível modificar as peculiaridades dos dois itens +no grupo mencionando o ID do grupo e usando os métodos listados acima. O sistema +também nunca separará os itens agrupados. Por exemplo, se você declarar {@code +android:showAsAction="ifRoom"} para cada item, eles aparecerão na barra de ação +ou no estouro de ação.

+ + +

Uso de itens de menu marcáveis

+ +
+ +

Figura 5. Captura de tela de um submenu +com itens marcáveis.

+
+ +

Um menu como uma interface pode ser útil para ativar e desativar as opções, usar uma caixa de seleção +para opções independentes ou botões de rádio para grupos +de opções mutuamente exclusivas. A figura 5 mostra um submenu com itens marcáveis +com botões de rádio.

+ +

Observação: os itens de menu no menu de ícones (do menu de opções) +não podem exibir uma caixa de seleção ou um botão de rádio. Caso escolha tornar marcáveis os itens no menu de ícones, +você deverá indicar manualmente o estado marcado arrastando o ícone e/ou digitando +sempre que o estado for alterado.

+ +

É possível definir o comportamento marcável para itens individuais de menu usando o atributo {@code +android:checkable} no elemento {@code <item>}, ou para um grupo inteiro +com o atributo {@code android:checkableBehavior} no elemento {@code <group>}. Por exemplo, +todos os itens neste grupo de menu são marcáveis com um botão de rádio:

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+    <group android:checkableBehavior="single">
+        <item android:id="@+id/red"
+              android:title="@string/red" />
+        <item android:id="@+id/blue"
+              android:title="@string/blue" />
+    </group>
+</menu>
+
+ +

O atributo {@code android:checkableBehavior} aceita: +

+
{@code single}
+
Somente um item do grupo pode ser marcado (botões de rádio)
+
{@code all}
+
Todos os itens podem ser marcados (caixas de seleção)
+
{@code none}
+
Nenhum item é marcável
+
+ +

É possível aplicar um estado marcado padrão a um item usando o atributo {@code android:checked} +no elemento {@code <item>} e alterar o seu código com o método {@link +android.view.MenuItem#setChecked(boolean) setChecked()}.

+ +

Quando um item marcável é selecionado, o sistema chama o respectivo método retorno de chamada do item selecionado +(como {@link android.app.Activity#onOptionsItemSelected(MenuItem) onOptionsItemSelected()}). É aqui +que você deve definir o estado da caixa de seleção, pois a caixa de seleção ou o botão de rádio +não altera o seu estado automaticamente. É possível consultar o estado do item (como ele era antes +do usuário selecioná-lo) com {@link android.view.MenuItem#isChecked()} e, em seguida, definir o estado marcado com +{@link android.view.MenuItem#setChecked(boolean) setChecked()}. Por exemplo:

+ +
+@Override
+public boolean onOptionsItemSelected(MenuItem item) {
+    switch (item.getItemId()) {
+        case R.id.vibrate:
+        case R.id.dont_vibrate:
+            if (item.isChecked()) item.setChecked(false);
+            else item.setChecked(true);
+            return true;
+        default:
+            return super.onOptionsItemSelected(item);
+    }
+}
+
+ +

Caso você não defina o estado marcado desta maneira, o estado visível do item (a caixa de seleção +ou o botão de rádio) +não se alterará quando o usuário selecioná-lo. Quando o estado é definido, a atividade preserva o estado marcado +do item para que, quando o usuário abrir o menu posteriormente, o estado marcado +definido esteja visível.

+ +

Observação: +os itens de menu marcáveis servem para serem usados somente em uma base por sessão e não são salvos quando +o aplicativo é destruído. Caso tenha configurações de aplicativo que gostaria de salvar para o usuário, +você deve armazenar os dados usando as preferências compartilhadas.

+ + + +

Adição de itens de menu com base em uma intenção

+ +

Às vezes, você desejará que um item de menu inicie uma atividade usando uma {@link android.content.Intent} +(se é uma atividade no seu ou em outro aplicativo). Quando você sabe qual intenção +quer usar e tem um item de menu específico que deve iniciar a intenção, é possível executá-la +com {@link android.app.Activity#startActivity(Intent) startActivity()} durante +o método de retorno de chamada selecionado no item (como o retorno de chamada {@link +android.app.Activity#onOptionsItemSelected(MenuItem) onOptionsItemSelected()}).

+ +

No entanto, caso não tenha certeza de que o dispositivo +do usuário contém um aplicativo que lida com a intenção, adicionar um item que o invoca +resulta em um item de menu que não funciona, pois a intenção pode não se resolver +em uma atividade. Para resolver isto, o Android permite que você adicione itens de menu dinamicamente ao seu menu +quando encontra atividades no dispositivo que lidam com a intenção.

+ +

Para adicionar itens de menu com base nas atividades disponíveis que aceitam uma intenção:

+
    +
  1. Defina a intenção +com a categoria {@link android.content.Intent#CATEGORY_ALTERNATIVE} +e/ou {@link android.content.Intent#CATEGORY_SELECTED_ALTERNATIVE}, além de quaisquer outros requisitos.
  2. +
  3. Chame {@link +android.view.Menu#addIntentOptions(int,int,int,ComponentName,Intent[],Intent,int,MenuItem[]) +Menu.addIntentOptions()}. O Android procura um aplicativo que possa realizar a intenção +e adiciona-o ao seu menu.
  4. +
+ +

Se não houver nenhum aplicativo instalado +que satisfaça a intenção, nenhum item de menu será adicionado.

+ +

Observação: +{@link android.content.Intent#CATEGORY_SELECTED_ALTERNATIVE} é usado para lidar com o elemento atualmente selecionado +na tela. Portanto, ele deve ser usado apenas ao criar um menu em {@link +android.app.Activity#onCreateContextMenu(ContextMenu,View,ContextMenuInfo) +onCreateContextMenu()}.

+ +

Por exemplo:

+ +
+@Override
+public boolean onCreateOptionsMenu(Menu menu){
+    super.onCreateOptionsMenu(menu);
+
+    // Create an Intent that describes the requirements to fulfill, to be included
+    // in our menu. The offering app must include a category value of Intent.CATEGORY_ALTERNATIVE.
+    Intent intent = new Intent(null, dataUri);
+    intent.addCategory(Intent.CATEGORY_ALTERNATIVE);
+
+    // Search and populate the menu with acceptable offering applications.
+    menu.addIntentOptions(
+         R.id.intent_group,  // Menu group to which new items will be added
+         0,      // Unique item ID (none)
+         0,      // Order for the items (none)
+         this.getComponentName(),   // The current activity name
+         null,   // Specific items to place first (none)
+         intent, // Intent created above that describes our requirements
+         0,      // Additional flags to control items (none)
+         null);  // Array of MenuItems that correlate to specific items (none)
+
+    return true;
+}
+ +

Para cada atividade encontrada que fornece um filtro de intenção correspondente à intenção definida, +um item de menu é adicionado, usando o valor no android:label do filtro de intenção +como o título do item e o ícone do aplicativo como o ícone do item de menu. O método +{@link android.view.Menu#addIntentOptions(int,int,int,ComponentName,Intent[],Intent,int,MenuItem[]) +addIntentOptions()} retorna o número de itens de menu adicionados.

+ +

Observação: Ao chamar {@link +android.view.Menu#addIntentOptions(int,int,int,ComponentName,Intent[],Intent,int,MenuItem[]) +addIntentOptions()}, ele substitui todos os itens de menu no grupo do menu especificado +no primeiro argumento.

+ + +

Permissão para a atividade ser adicionada a outros menus

+ +

Você pode também oferecer os serviços da sua atividade para outros aplicativos, +para que o aplicativo possa ser incluído no menu de outros (revertendo as funções descritas acima).

+ +

Para ser incluído nos menus de outros aplicativos, você precisa definir +um filtro de intenção como normalmente faz, mas certificando-se de incluir os valores {@link android.content.Intent#CATEGORY_ALTERNATIVE} +e/ou {@link android.content.Intent#CATEGORY_SELECTED_ALTERNATIVE} para a categoria +do filtro de intenção. Por exemplo:

+
+<intent-filter label="@string/resize_image">
+    ...
+    <category android:name="android.intent.category.ALTERNATIVE" />
+    <category android:name="android.intent.category.SELECTED_ALTERNATIVE" />
+    ...
+</intent-filter>
+
+ +

Leia mais sobre a criação de filtros de intenção no documento +Intenções e filtros de intenções.

+ +

Para obter um exemplo de aplicativo que usa esta técnica, consulte o código de exemplo do +Bloco +de notas.

diff --git a/docs/html-intl/intl/pt-br/guide/topics/ui/notifiers/notifications.jd b/docs/html-intl/intl/pt-br/guide/topics/ui/notifiers/notifications.jd new file mode 100644 index 0000000000000000000000000000000000000000..42563ace224828b749aaf52f649ca7121708925a --- /dev/null +++ b/docs/html-intl/intl/pt-br/guide/topics/ui/notifiers/notifications.jd @@ -0,0 +1,979 @@ +page.title=Notificações +@jd:body + + +

+ Uma notificação é uma mensagem que pode ser exibida ao usuário fora da IU normal do aplicativo. +Quando você diz ao sistema para emitir uma notificação, ela primeiro aparece como um ícone +na área de notificação. Para ver os detalhes da notificação, o usuário abre +a gaveta de notificação. A área de notificação e a gaveta de notificação +são áreas controladas pelo sistema que o usuário pode visualizar a qualquer momento. +

+ +

+ Figura 1. Notificações na área de notificação. +

+ +

+ Figura 2. Notificações na gaveta de notificação. +

+ +

Observação: exceto quando notado, este guia menciona a classe +{@link android.support.v4.app.NotificationCompat.Builder NotificationCompat.Builder} +na versão 4 da Biblioteca de suporte. +A classe {@link android.app.Notification.Builder Notification.Builder} foi adicionada no Android +3.0 (API de nível 11).

+ +

Considerações de projeto

+ +

As notificações, como parte importante da interface do usuário do Android, possuem as próprias diretrizes de projeto. +As alterações do Material Design introduzidas no Android 5.0 (API de nível 21) são de importância +específica e, por isso, recomenda-se revisar o treinamento do Material Design + para obter mais informações. Para saber como projetar notificações e suas interações, leia o guia de projeto +Notificações.

+ +

Criação de uma notificação

+ +

Você especifica as ações e informações da IU para uma notificação +no objeto {@link android.support.v4.app.NotificationCompat.Builder NotificationCompat.Builder}. +Para criar a própria notificação, chama-se +{@link android.support.v4.app.NotificationCompat.Builder#build NotificationCompat.Builder.build()}, +que retorna um objeto {@link android.app.Notification} contendo suas especificações. Para emitir a notificação, +passa-se o objeto {@link android.app.Notification} ao sistema +chamando {@link android.app.NotificationManager#notify NotificationManager.notify()}.

+ +

Conteúdo necessário da notificação

+

+ Um objeto {@link android.app.Notification} deve conter o seguinte: +

+
    +
  • + Um ícone pequeno, definido por + {@link android.support.v4.app.NotificationCompat.Builder#setSmallIcon setSmallIcon()} +
  • +
  • + Um título, definido por + {@link android.support.v4.app.NotificationCompat.Builder#setContentTitle setContentTitle()} +
  • +
  • + Texto de detalhes, definido por + {@link android.support.v4.app.NotificationCompat.Builder#setContentText setContentText()} +
  • +
+

Configurações e conteúdo opcionais da notificação

+

+ Todas as outras configurações e o conteúdo da notificação são opcionais. Para saber mais sobre isso, + consulte as documentações de referência de {@link android.support.v4.app.NotificationCompat.Builder}. +

+ +

Ações da notificação

+

+ Apesar de serem opcionais, deve-se adicionar pelo menos uma ação à notificação. + Uma ação permite que os usuários direcionem-se diretamente da notificação + para uma {@link android.app.Activity} no aplicativo, onde podem visualizar um ou mais eventos + ou realizar outros trabalhos. +

+

+ Uma notificação pode fornecer várias ações. Deve-se sempre definir a ação que será ativada + quando o usuário clicar na notificação. Geralmente, esta ação abre uma + {@link android.app.Activity} no aplicativo. É possível também adicionar botões à notificação + que realizem ações adicionais, como ativar a soneca de um alarme imediatamente + para uma mensagem de texto. Este recurso está disponível a partir do Android 4.1. Se você usar botões de ação adicionais, + também deverá disponibilizar a funcionalidade em uma {@link android.app.Activity} no aplicativo; consulte + a seção Tratamento da compatibilidade para obter mais informações. +

+

+ Dentro de uma {@link android.app.Notification}, a própria ação é definida + por uma {@link android.app.PendingIntent} contendo uma + {@link android.content.Intent} que inicia + uma {@link android.app.Activity} no aplicativo. Para associar + a {@link android.app.PendingIntent} a um gesto, chame o método adequado + de {@link android.support.v4.app.NotificationCompat.Builder}. Por exemplo, se quiser iniciar + {@link android.app.Activity} quando o usuário clicar no texto da notificação + na gaveta de notificação, deve-se adicionar {@link android.app.PendingIntent} chamando + {@link android.support.v4.app.NotificationCompat.Builder#setContentIntent setContentIntent()}. +

+

+ Iniciar uma {@link android.app.Activity} quando o usuário clica na notificação + é o cenário de ação mais comum. É possível também iniciar uma {@link android.app.Activity} quando o usuário + dispensa uma notificação. A partir do Android 4.1, é possível iniciar + uma {@link android.app.Activity} a partir de um botão de ação. Para obter mais informações, leia o guia de referência + de {@link android.support.v4.app.NotificationCompat.Builder}. +

+ +

Prioridade da notificação

+

+ Se quiser, é possível definir a prioridade de uma notificação. A prioridade + age como uma sugestão à IU do dispositivo sobre como a notificação deve ser exibida. + Para definir a prioridade de uma notificação, chame {@link + android.support.v4.app.NotificationCompat.Builder#setPriority(int) + NotificationCompat.Builder.setPriority()} e passe em uma das constantes de prioridade {@link + android.support.v4.app.NotificationCompat}. Há cinco níveis de prioridade, + de {@link + android.support.v4.app.NotificationCompat#PRIORITY_MIN} (-2) a {@link + android.support.v4.app.NotificationCompat#PRIORITY_MAX} (2); se não for definida, a prioridade + segue o padrão de {@link + android.support.v4.app.NotificationCompat#PRIORITY_DEFAULT} (0). +

+

Para obter mais informações sobre como definir um nível de prioridade adequado, Consulte "Definição e gerenciamento corretos da prioridade das notificações" + no guia de projeto + Notificações. +

+ +

Criação de uma notificação simples

+

+ O seguinte fragmento ilustra uma notificação simples que especifica uma atividade para abrir quando +o usuário clica na notificação. Observe que o código cria um objeto + {@link android.support.v4.app.TaskStackBuilder} e usa-o para criar + a {@link android.app.PendingIntent} para a ação. Este padrão é explicado com mais detalhes + na seção + Preservação da navegação ao iniciar uma atividade: +

+
+NotificationCompat.Builder mBuilder =
+        new NotificationCompat.Builder(this)
+        .setSmallIcon(R.drawable.notification_icon)
+        .setContentTitle("My notification")
+        .setContentText("Hello World!");
+// Creates an explicit intent for an Activity in your app
+Intent resultIntent = new Intent(this, ResultActivity.class);
+
+// The stack builder object will contain an artificial back stack for the
+// started Activity.
+// This ensures that navigating backward from the Activity leads out of
+// your application to the Home screen.
+TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);
+// Adds the back stack for the Intent (but not the Intent itself)
+stackBuilder.addParentStack(ResultActivity.class);
+// Adds the Intent that starts the Activity to the top of the stack
+stackBuilder.addNextIntent(resultIntent);
+PendingIntent resultPendingIntent =
+        stackBuilder.getPendingIntent(
+            0,
+            PendingIntent.FLAG_UPDATE_CURRENT
+        );
+mBuilder.setContentIntent(resultPendingIntent);
+NotificationManager mNotificationManager =
+    (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+// mId allows you to update the notification later on.
+mNotificationManager.notify(mId, mBuilder.build());
+
+

É isso. O usuário foi notificado.

+ +

Aplicação de um layout expandido a uma notificação

+

+ Para que uma notificação apareça em uma vista expandida, cria-se primeiro + um objeto {@link android.support.v4.app.NotificationCompat.Builder} com as opções + de visualização normal desejadas. Em seguida, chama-se {@link android.support.v4.app.NotificationCompat.Builder#setStyle + Builder.setStyle()} com um objeto de layout expandido como o argumento. +

+

+ Lembre-se de que as notificações expandidas não estão disponíveis em plataformas anteriores ao Android 4.1. Para saber mais + sobre como tratar de notificações para Android 4.1 e plataformas anteriores, leia + a seção Tratamento da compatibilidade. +

+

+ Por exemplo, o seguinte fragmento de código demonstra como alterar a notificação criada + no fragmento anterior para usar o layout expandido: +

+
+NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(this)
+    .setSmallIcon(R.drawable.notification_icon)
+    .setContentTitle("Event tracker")
+    .setContentText("Events received")
+NotificationCompat.InboxStyle inboxStyle =
+        new NotificationCompat.InboxStyle();
+String[] events = new String[6];
+// Sets a title for the Inbox in expanded layout
+inboxStyle.setBigContentTitle("Event tracker details:");
+...
+// Moves events into the expanded layout
+for (int i=0; i < events.length; i++) {
+
+    inboxStyle.addLine(events[i]);
+}
+// Moves the expanded layout object into the notification object.
+mBuilder.setStyle(inBoxStyle);
+...
+// Issue the notification here.
+
+ +

Tratamento da compatibilidade

+ +

+ Nem todos os recursos de notificação estão disponíveis para uma versão específica, + mesmo se os métodos que os definem estiverem na classe + {@link android.support.v4.app.NotificationCompat.Builder NotificationCompat.Builder} da biblioteca de suporte. + Por exemplo, botões de ação, que dependem das notificações expandidas, aparecem somente a partir do Android + 4.1 ou de posteriores porque as próprias notificações expandidas estão disponíveis somente a partir + destas versões. +

+

+ Para garantir a melhor compatibilidade, crie notificações + com {@link android.support.v4.app.NotificationCompat NotificationCompat} e suas subclasses, + particularmente {@link android.support.v4.app.NotificationCompat.Builder + NotificationCompat.Builder}. Além disso, siga o processo a seguir ao implementar uma notificação: +

+
    +
  1. + Forneça toda a funcionalidade da notificação aos usuários, independentemente + da versão que estejam usando. Para fazer isto, verifique se toda a disponibilidade está disponível a partir de uma + {@link android.app.Activity} no aplicativo. Pode-se adicionar uma nova + {@link android.app.Activity} para fazer isto. +

    + Por exemplo, caso queira usar + {@link android.support.v4.app.NotificationCompat.Builder#addAction addAction()} + para fornecer um controle que interrompa e inicie a reprodução de mídia, primeiro implemente + este controle em uma {@link android.app.Activity} no aplicativo. +

    +
  2. +
  3. + Certifique-se de que todos os usuários possam acessar a funcionalidade na {@link android.app.Activity}, + iniciando-a quando o usuário clicar na notificação. Para fazer isto, + crie uma {@link android.app.PendingIntent} + para a{@link android.app.Activity}. Chame + {@link android.support.v4.app.NotificationCompat.Builder#setContentIntent + setContentIntent()} para adicionar a {@link android.app.PendingIntent} à notificação. +
  4. +
  5. + Agora, adicione os recursos de notificação expandidos que deseja usar à notificação. Lembre-se + de que qualquer funcionalidade adicionada também deve estar disponível na {@link android.app.Activity} + que é iniciada quando os usuários clicam na notificação. +
  6. +
+ + + + +

Gerenciamento de notificações

+

+ Quando for necessário emitir uma notificação várias vezes para o mesmo tipo de evento, + deve-se evitar criar uma notificação completamente nova. Em vez disso, deve-se considerar atualizar + uma notificação anterior, alterando e/ou adicionado alguns valores. +

+

+ Por exemplo, o Gmail notifica o usuário de que novos e-mails foram recebidos aumentando a contagem +de mensagens não lidas e adicionando um resumo de cada e-mail à notificação. Isto é chamado + de "acumular" a notificação. Isto é descrito com mais detalhes no guia de projeto + Notificações. +

+

+ Observação: este recurso do Gmail requer o layout expandido da "caixa de entrada", + que faz parte do recurso de notificação expandida disponível a partir do Android 4.1. +

+

+ A seguinte seção descreve como atualizar as notificações e como removê-las. +

+

Atualização de notificações

+

+ Para definir uma notificação para que possa ser atualizada, deve-se emiti-la com um ID de notificação + chamando {@link android.app.NotificationManager#notify(int, android.app.Notification) NotificationManager.notify()}. + Para atualizar esta notificação após emiti-la, + atualize ou crie um objeto {@link android.support.v4.app.NotificationCompat.Builder}, + compile um objeto {@link android.app.Notification} a partir dele e emita + a {@link android.app.Notification} com o mesmo ID usado anteriormente. Se a notificação anterior + ainda estiver visível, o sistema a atualizará com o conteúdo + do objeto {@link android.app.Notification}. Se a notificação anterior for dispensada, + uma nova notificação será criada. +

+

+ O seguinte fragmento demonstra uma notificação que é atualizada para refletir + o número de eventos que ocorreram. Ele acumula a notificação, exibindo um resumo: +

+
+mNotificationManager =
+        (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+// Sets an ID for the notification, so it can be updated
+int notifyID = 1;
+mNotifyBuilder = new NotificationCompat.Builder(this)
+    .setContentTitle("New Message")
+    .setContentText("You've received new messages.")
+    .setSmallIcon(R.drawable.ic_notify_status)
+numMessages = 0;
+// Start of a loop that processes data and then notifies the user
+...
+    mNotifyBuilder.setContentText(currentText)
+        .setNumber(++numMessages);
+    // Because the ID remains unchanged, the existing notification is
+    // updated.
+    mNotificationManager.notify(
+            notifyID,
+            mNotifyBuilder.build());
+...
+
+ + +

Remoção de notificações

+

+ As notificações permanecem visíveis até que um dos seguintes casos aconteça: +

+
    +
  • + O usuário dispense a notificação individualmente usando "Apagar tudo" + (se a notificação puder ser apagada). +
  • +
  • + O usuário clique na notificação + e chame-se {@link android.support.v4.app.NotificationCompat.Builder#setAutoCancel setAutoCancel()} + quando a notificação é criada. +
  • +
  • + Chame-se {@link android.app.NotificationManager#cancel(int) cancel()} + para um ID de notificação específico. Este método também exclui notificações contínuas. +
  • +
  • + Chame-se {@link android.app.NotificationManager#cancelAll() cancelAll()}, que remove +todas as notificações emitidas anteriormente. +
  • +
+ + +

Preservação da navegação ao iniciar uma atividade

+

+ Ao iniciar uma {@link android.app.Activity} a partir de uma notificação, deve-se preservar + a experiência de navegação esperada pelo usuário. Clicar em Voltar deve levar o usuário + de volta pelo fluxo de trabalho normal do aplicativo à tela Inicial, e clicar em Recentes deve exibir + a {@link android.app.Activity} como uma tarefa separada. Para preservar a experiência de navegação, + deve-se iniciar a {@link android.app.Activity} em uma tarefa nova. O modo de definição + da {@link android.app.PendingIntent} para fornecer uma tarefa nova depende da natureza + da {@link android.app.Activity} que está sendo iniciado. Há duas situações gerais: +

+
+
+ Atividade comum +
+
+ Inicia-se uma {@link android.app.Activity} que faz parte do fluxo de trabalho normal + do aplicativo. Nesta situação, defina {@link android.app.PendingIntent} + para iniciar uma tarefa nova e forneça a {@link android.app.PendingIntent} com uma pilha de retorno + que reproduza o comportamento Voltar normal do aplicativo. +

+ As notificações do aplicativo do Gmail demonstram isso. Ao clicar em uma notificação + para uma única mensagem de e-mail, a própria mensagem será exibida. Tocar em Voltar + faz com que o usuário volte ao Gmail até a tela inicial, como se ele tivesse acessado o Gmail + a partir da tela inicial em vez de a partir da notificação. +

+

+ Isto acontece independentemente do aplicativo que estava em primeiro plano + quando a notificação foi tocada. Por exemplo, se você estiver no Gmail escrevendo uma mensagem e clicar + em uma notificação de um e-mail, você acessará este e-mail imediatamente. Tocar em Voltar + leva você de volta à caixa de entrada e, em seguida, à tela inicial, em vez de levar + à mensagem que estava escrevendo. +

+
+
+ Atividade especial +
+
+ O usuário vê apenas esta {@link android.app.Activity} se for iniciada a partir de uma notificação. + De certo modo, a {@link android.app.Activity} estende a notificação fornecendo + informações que seriam difíceis de exibir na própria notificação. Para estas situações, + defina a {@link android.app.PendingIntent} para iniciar em uma tarefa nova. Não há necessidade + de criar uma pilha de retorno, pois a {@link android.app.Activity} iniciada + não faz parte do fluxo de atividades do aplicativo. Clicar em Voltar ainda levará o usuário + à tela inicial. +
+
+ +

Definição de uma atividade PendingIntent comum

+

+ Para definir uma {@link android.app.PendingIntent} que inicia uma {@link android.app.Activity} de entrada + direta, siga estas etapas: +

+
    +
  1. + Defina a hierarquia de {@link android.app.Activity} do aplicativo no manifesto. +
      +
    1. + Adicione compatibilidade com Android 4.0.3 e mais antigos. Para fazer isto, especifique o pai + da {@link android.app.Activity} que está iniciando adicionando um elemento +<meta-data> + como o filho de +<activity>. +

      + Para este elemento, defina +android:name="android.support.PARENT_ACTIVITY". + Defina +android:value="<parent_activity_name>", + onde <parent_activity_name> é o valor de +android:name + para o elemento +<activity> + pai. Veja o XML a seguir para ver um exemplo. +

      +
    2. +
    3. + Adicione também compatibilidade com Android 4.1 e mais recentes. Para fazer isto, adicione o atributo +android:parentActivityName + ao elemento +<activity> + da {@link android.app.Activity} que estiver iniciando. +
    4. +
    +

    + O XML final deve parecer-se com isto: +

    +
    +<activity
    +    android:name=".MainActivity"
    +    android:label="@string/app_name" >
    +    <intent-filter>
    +        <action android:name="android.intent.action.MAIN" />
    +        <category android:name="android.intent.category.LAUNCHER" />
    +    </intent-filter>
    +</activity>
    +<activity
    +    android:name=".ResultActivity"
    +    android:parentActivityName=".MainActivity">
    +    <meta-data
    +        android:name="android.support.PARENT_ACTIVITY"
    +        android:value=".MainActivity"/>
    +</activity>
    +
    +
  2. +
  3. + Crie uma pilha de retorno com base na {@link android.content.Intent} que inicia + a {@link android.app.Activity}: +
      +
    1. + Crie a {@link android.content.Intent} para iniciar a{@link android.app.Activity}. +
    2. +
    3. + Crie um compilador de pilhas chamando {@link android.app.TaskStackBuilder#create + TaskStackBuilder.create()}. +
    4. +
    5. + Adicione a pilha de retorno ao compilador de pilha chamando + {@link android.support.v4.app.TaskStackBuilder#addParentStack addParentStack()}. + Para cada {@link android.app.Activity} na hierarquia definida no manifesto, + a pilha de retorno conterá um objeto {@link android.content.Intent} + que inicia a {@link android.app.Activity}. Este método adiciona sinalizadores + que iniciam a pilha em uma tarefa nova. +

      + Observação: Apesar de o argumento + de {@link android.support.v4.app.TaskStackBuilder#addParentStack addParentStack()} + ser uma referência para a {@link android.app.Activity} iniciada, a chamada do método + não adiciona a {@link android.content.Intent} que inicia + a {@link android.app.Activity}. Em vez disso, lidamos com isto na próxima etapa. +

      +
    6. +
    7. + Adicione a {@link android.content.Intent} que inicia a {@link android.app.Activity} + a partir da notificação chamando + {@link android.support.v4.app.TaskStackBuilder#addNextIntent addNextIntent()}. + Passe a {@link android.content.Intent} criada na primeira etapa + como o argumento + para {@link android.support.v4.app.TaskStackBuilder#addNextIntent addNextIntent()}. +
    8. +
    9. + Se for necessário, adicione argumentos para os objetos {@link android.content.Intent} + na pilha chamando {@link android.support.v4.app.TaskStackBuilder#editIntentAt + TaskStackBuilder.editIntentAt()}. Às vezes, isto é necessário para garantir + que a {@link android.app.Activity} alvo exiba dados significantes quando o usuário + navegar a ela usando Voltar. +
    10. +
    11. + Adquira uma {@link android.app.PendingIntent} para esta pilha de retorno chamando + {@link android.support.v4.app.TaskStackBuilder#getPendingIntent getPendingIntent()}. + É possível usar esta {@link android.app.PendingIntent} como o argumento + para {@link android.support.v4.app.NotificationCompat.Builder#setContentIntent + setContentIntent()}. +
    12. +
    +
  4. +
+

+ O seguinte fragmento de código demonstra o processo: +

+
+...
+Intent resultIntent = new Intent(this, ResultActivity.class);
+TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);
+// Adds the back stack
+stackBuilder.addParentStack(ResultActivity.class);
+// Adds the Intent to the top of the stack
+stackBuilder.addNextIntent(resultIntent);
+// Gets a PendingIntent containing the entire back stack
+PendingIntent resultPendingIntent =
+        stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
+...
+NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
+builder.setContentIntent(resultPendingIntent);
+NotificationManager mNotificationManager =
+    (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+mNotificationManager.notify(id, builder.build());
+
+ +

Definição de uma atividade PendingIntent especial

+

+ A seção a seguir descreve como definir uma atividade + {@link android.app.PendingIntent} especial. +

+

+ Uma {@link android.app.Activity} especial não precisa de uma pilha de retorno, então não é necessário + definir sua hierarquia de {@link android.app.Activity} no manifesto + e chamar + {@link android.support.v4.app.TaskStackBuilder#addParentStack addParentStack()} para compilar + uma pilha de retorno. Em vez disso, use o manifesto para definir as opções de tarefa da {@link android.app.Activity} + e crie a {@link android.app.PendingIntent} + chamando {@link android.app.PendingIntent#getActivity getActivity()}: +

+
    +
  1. + No manifesto, adicione os seguintes atributos ao elemento +<activity> + para a {@link android.app.Activity} +
    +
    +android:name="activityclass" +
    +
    + O nome da classe completamente qualificado da atividade. +
    +
    +android:taskAffinity="" +
    +
    + Combinado com o sinalizador + {@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK FLAG_ACTIVITY_NEW_TASK} + que foi definido no código, isto garante que esta {@link android.app.Activity} + não acesse a tarefa padrão do aplicativo. Tarefas existentes + que tiverem a afinidade padrão do aplicativo não serão afetadas. +
    +
    +android:excludeFromRecents="true" +
    +
    + Exclui a nova tarefa de Recentespara que o usuário não + navegue acidentalmente de volta. +
    +
    +

    + Este fragmento mostra o elemento: +

    +
    +<activity
    +    android:name=".ResultActivity"
    +...
    +    android:launchMode="singleTask"
    +    android:taskAffinity=""
    +    android:excludeFromRecents="true">
    +</activity>
    +...
    +
    +
  2. +
  3. + Compilar e emitir a notificação: +
      +
    1. + Crie uma {@link android.content.Intent} que inicie + a {@link android.app.Activity}. +
    2. +
    3. + Defina a {@link android.app.Activity} para iniciar em uma tarefa nova e vazia + chamando {@link android.content.Intent#setFlags setFlags()} com os sinalizadores + {@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK FLAG_ACTIVITY_NEW_TASK} + e + {@link android.content.Intent#FLAG_ACTIVITY_CLEAR_TASK FLAG_ACTIVITY_CLEAR_TASK}. +
    4. +
    5. + Defina quaisquer outras opções necessárias para a {@link android.content.Intent}. +
    6. +
    7. + Crie uma {@link android.app.PendingIntent} a partir da {@link android.content.Intent} + chamando {@link android.app.PendingIntent#getActivity getActivity()}. + É possível usar esta {@link android.app.PendingIntent} como o argumento + para {@link android.support.v4.app.NotificationCompat.Builder#setContentIntent + setContentIntent()}. +
    8. +
    +

    + O seguinte fragmento de código demonstra o processo: +

    +
    +// Instantiate a Builder object.
    +NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
    +// Creates an Intent for the Activity
    +Intent notifyIntent =
    +        new Intent(this, ResultActivity.class);
    +// Sets the Activity to start in a new, empty task
    +notifyIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
    +                        | Intent.FLAG_ACTIVITY_CLEAR_TASK);
    +// Creates the PendingIntent
    +PendingIntent notifyPendingIntent =
    +        PendingIntent.getActivity(
    +        this,
    +        0,
    +        notifyIntent,
    +        PendingIntent.FLAG_UPDATE_CURRENT
    +);
    +
    +// Puts the PendingIntent into the notification builder
    +builder.setContentIntent(notifyPendingIntent);
    +// Notifications are issued by sending them to the
    +// NotificationManager system service.
    +NotificationManager mNotificationManager =
    +    (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
    +// Builds an anonymous Notification object from the builder, and
    +// passes it to the NotificationManager
    +mNotificationManager.notify(id, builder.build());
    +
    +
  4. +
+ + +

Exibição do progresso em uma notificação

+

+ As notificações podem incluir um indicador de progresso animado que exibe aos usuários + o status de uma operação em andamento. Se for possível estimar a duração da operação + e o quanto dela já foi concluído em um determinado momento, use a forma "determinada" do indicador + (uma barra de progresso). Se não for possível estimar a duração da operação, + use a forma "indeterminada" do indicador (um indicador de atividade). +

+

+ Os indicadores de progresso são exibidos com a implementação da plataforma + da classe {@link android.widget.ProgressBar}. +

+

+ Para usar o indicador de progresso em plataformas a partir do Android 4.0, + chame {@link android.support.v4.app.NotificationCompat.Builder#setProgress setProgress()}. Para versões mais antigas, + deve-se criar o próprio layout personalizado de notificação + que inclua uma vista de {@link android.widget.ProgressBar}. +

+

+ As seguintes seções descrevem como exibir o progresso em uma notificação + usando {@link android.support.v4.app.NotificationCompat.Builder#setProgress setProgress()}. +

+ +

Exibição de um indicador de progresso de duração fixa

+

+ Para exibir uma determinada barra de progresso, adicione a barra à notificação + chamando {@link android.support.v4.app.NotificationCompat.Builder#setProgress + setProgress(max, progress, false)} e, em seguida, emitindo a notificação. À medida que a operação prosseguir, + incremente progress e atualize a notificação. No término da operação, + progress deve ser igual a max. Uma maneira comum de chamar + {@link android.support.v4.app.NotificationCompat.Builder#setProgress setProgress()} + é definir max como 100 e, em seguida, incrementar progress + como um valor "percentual completo" para a operação. +

+

+ É possível deixar a barra de progresso em exibição ou removê-la quando a operação for concluída. Em ambos os casos, + lembre-se de atualizar o texto da notificação para exibir que a operação foi concluída. + Para remover a barra de progresso, + chame {@link android.support.v4.app.NotificationCompat.Builder#setProgress + setProgress(0, 0, false)}. Por exemplo: +

+
+...
+mNotifyManager =
+        (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+mBuilder = new NotificationCompat.Builder(this);
+mBuilder.setContentTitle("Picture Download")
+    .setContentText("Download in progress")
+    .setSmallIcon(R.drawable.ic_notification);
+// Start a lengthy operation in a background thread
+new Thread(
+    new Runnable() {
+        @Override
+        public void run() {
+            int incr;
+            // Do the "lengthy" operation 20 times
+            for (incr = 0; incr <= 100; incr+=5) {
+                    // Sets the progress indicator to a max value, the
+                    // current completion percentage, and "determinate"
+                    // state
+                    mBuilder.setProgress(100, incr, false);
+                    // Displays the progress bar for the first time.
+                    mNotifyManager.notify(0, mBuilder.build());
+                        // Sleeps the thread, simulating an operation
+                        // that takes time
+                        try {
+                            // Sleep for 5 seconds
+                            Thread.sleep(5*1000);
+                        } catch (InterruptedException e) {
+                            Log.d(TAG, "sleep failure");
+                        }
+            }
+            // When the loop is finished, updates the notification
+            mBuilder.setContentText("Download complete")
+            // Removes the progress bar
+                    .setProgress(0,0,false);
+            mNotifyManager.notify(ID, mBuilder.build());
+        }
+    }
+// Starts the thread by calling the run() method in its Runnable
+).start();
+
+ + +

Exibição de um indicador de atividade contínua

+

+ Para exibir um indicador de atividade indeterminado, adicione-o à notificação + com {@link android.support.v4.app.NotificationCompat.Builder#setProgress setProgress(0, 0, true)} + (os dois primeiros argumentos são ignorados) e emita a notificação. O resultado é um indicador + que tem o mesmo estilo que uma barra de progresso, exceto que sua animação é contínua. +

+

+ Emita a notificação no início da operação. A animação será executada + até que a notificação seja modificada. Quando a operação for concluída, + chame {@link android.support.v4.app.NotificationCompat.Builder#setProgress setProgress(0, 0, false)} + e, em seguida, atualize a notificação para remover o indicador de atividade. + Sempre faça isso. Caso contrário, a animação será executada mesmo quando a operação for concluída. Lembre-se também de alterar + o texto da notificação para indicar que a operação foi concluída. +

+

+ Para ver como os indicadores de atividade funcionam, consulte o fragmento anterior. Localize as seguintes linhas: +

+
+// Sets the progress indicator to a max value, the current completion
+// percentage, and "determinate" state
+mBuilder.setProgress(100, incr, false);
+// Issues the notification
+mNotifyManager.notify(0, mBuilder.build());
+
+

+ Substitua as linhas encontradas pelas seguintes linhas: +

+
+ // Sets an activity indicator for an operation of indeterminate length
+mBuilder.setProgress(0, 0, true);
+// Issues the notification
+mNotifyManager.notify(0, mBuilder.build());
+
+ +

Metadados de notificação

+ +

As notificações podem ser classificadas de acordo com os metadados atribuídos +com os seguintes métodos {@link android.support.v4.app.NotificationCompat.Builder}:

+ +
    +
  • {@link android.support.v4.app.NotificationCompat.Builder#setCategory(java.lang.String) setCategory()} + diz ao sistema como lidar com as notificações do aplicativo quando o dispositivo estiver no modo de Prioridade + (por exemplo: se a notificação representar uma chamada recebida, mensagem instantânea ou alarme).
  • +
  • {@link android.support.v4.app.NotificationCompat.Builder#setPriority(int) setPriority()} faz com que as notificações + com o campo de prioridade definido para {@code PRIORITY_MAX} ou {@code PRIORITY_HIGH} + apareçam em uma pequena janela flutuante se a notificação também tiver som ou vibração.
  • +
  • {@link android.support.v4.app.NotificationCompat.Builder#addPerson(java.lang.String) addPerson()} + permite a adição de uma lista de pessoas a uma notificação. O aplicativo pode usar isto para sinalizar + ao sistema que ele deve agrupar as notificações de pessoas específicas, ou classificar notificações + destas pessoas como sendo mais importantes.
  • +
+ +
+ +

+ Figura 3. Atividade de tela cheia exibindo uma notificação de informações prévias +

+
+ +

Notificação de informações prévias

+ +

Com o Android 5.0 (API de nível 21), as notificações podem aparecer em uma pequena janela flutuante +(também chamada de notificação de informações prévias) quando o dispositivo estiver ativo +(ou seja, com o dispositivo desbloqueado e a tela ativada). Essas notificações +aparecem de maneira semelhante à forma compacta da notificação, exceto que a notificação de informações prévias +também exibe botões de ação. Os usuários podem agir +ou dispensar a notificação de informações prévias sem deixar o aplicativo atual.

+ +

Exemplos de condições que podem ativar uma notificação de informações prévias incluem:

+ +
    +
  • A atividade do usuário estar no modo de tela cheia (o aplicativo usar +{@link android.app.Notification#fullScreenIntent}) ou
  • +
  • A notificação tem alta prioridade e usa toques +ou vibrações
  • +
+ +

Notificações da tela de bloqueio

+ +

Com o lançamento do Android 5.0 (API de nível 21), as notificações podem aparecer +na tela de bloqueio. O aplicativo pode usar esta funcionalidade para fornecer controles de reprodução de mídia e outras +ações comuns. Os usuários podem escolher, acessando Configurações, se querem exibir notificações na tela de bloqueio +e é possível designar se uma notificação do aplicativo será visível na tela de bloqueio.

+ +

Configuração de visibilidade

+ +

O aplicativo pode controlar o nível de detalhe visível nas notificações exibidas +em uma tela de bloqueio segura. Chama-se {@link android.support.v4.app.NotificationCompat.Builder#setVisibility(int) setVisibility()} +e especifica-se um dos seguintes valores:

+ +
    +
  • {@link android.support.v4.app.NotificationCompat#VISIBILITY_PUBLIC} exibe o conteúdo completo +da notificação.
  • +
  • {@link android.support.v4.app.NotificationCompat#VISIBILITY_SECRET} não exibe parte alguma +desta notificação na tela de bloqueio.
  • +
  • {@link android.support.v4.app.NotificationCompat#VISIBILITY_PRIVATE} exibe informações básicas, +como o ícone e o título do conteúdo da notificação, mas oculta o conteúdo completo.
  • +
+ +

Quando {@link android.support.v4.app.NotificationCompat#VISIBILITY_PRIVATE} é definido, é possível +fornecer também uma versão alternativa do conteúdo da notificação que oculta determinados detalhes. Por exemplo, +um aplicativo de SMS pode exibir uma notificação que exiba Você tem 3 novas mensagens de texto, mas oculte +o conteúdo e o remetente das mensagens. Para fornecer esta notificação alternativa, cria-se primeiro +uma notificação de substituição usando {@link android.support.v4.app.NotificationCompat.Builder}. Ao criar +o objeto de notificação privada, anexe a notificação de substituição a ele +usando o método {@link android.support.v4.app.NotificationCompat.Builder#setPublicVersion(android.app.Notification) setPublicVersion()} +.

+ +

Controle de reprodução de mídia na tela de bloqueio

+ +

No Android 5.0 (API de nível 21), a tela de bloqueio deixa de exibir controles de mídia +com base em {@link android.media.RemoteControlClient}, que foi reprovado. Em vez disso, usa-se o modelo +{@link android.app.Notification.MediaStyle} com o método +{@link android.app.Notification.Builder#addAction(android.app.Notification.Action) addAction()}, +que converte as ações em ícones clicáveis.

+ +

Observação: o modelo e o método {@link android.app.Notification.Builder#addAction(android.app.Notification.Action) addAction()} +não estão incluídos na biblioteca de suporte. Portanto, esses recursos funcionam somente no Android 5.0 +e em versões mais recentes.

+ +

Para exibir os controles de reprodução de mídia na tela de bloqueio no Android 5.0, defina a visibilidade +como {@link android.support.v4.app.NotificationCompat#VISIBILITY_PUBLIC}, como descrito acima. Em seguida, +adicione as ações e defina o modelo {@link android.app.Notification.MediaStyle}, como descrito +no seguinte exemplo de código:

+ +
+Notification notification = new Notification.Builder(context)
+    // Show controls on lock screen even when user hides sensitive content.
+    .setVisibility(Notification.VISIBILITY_PUBLIC)
+    .setSmallIcon(R.drawable.ic_stat_player)
+    // Add media control buttons that invoke intents in your media service
+    .addAction(R.drawable.ic_prev, "Previous", prevPendingIntent) // #0
+    .addAction(R.drawable.ic_pause, "Pause", pausePendingIntent)  // #1
+    .addAction(R.drawable.ic_next, "Next", nextPendingIntent)     // #2
+    // Apply the media style template
+    .setStyle(new Notification.MediaStyle()
+    .setShowActionsInCompactView(1 /* #1: pause button */)
+    .setMediaSession(mMediaSession.getSessionToken())
+    .setContentTitle("Wonderful music")
+    .setContentText("My Awesome Band")
+    .setLargeIcon(albumArtBitmap)
+    .build();
+
+ +

Observação: a reprovação de {@link android.media.RemoteControlClient} +tem mais implicações para o controle de mídia. Consulte +Controle de reprodução de mídia +para obter mais informações sobre as novas APIs para gerenciar a sessão de mídia e o controle de reprodução.

+ + + +

Layouts de notificação personalizados

+

+ A estrutura das notificações permite que um layout de notificação personalizado seja definido, + o que define a aparência da notificação em um objeto {@link android.widget.RemoteViews}. + As notificações de layout personalizado são parecidas com notificações normais, mas baseiam-se + em um {@link android.widget.RemoteViews} definido em um arquivo de layout XML. +

+

+ A altura disponível para um layout de notificação personalizado depende da vista da notificação. Layouts de vista normal + são limitados a 64 dp, e layouts de vista expandida são limitados a 256 dp. +

+

+ Para definir um layout de notificação personalizada, comece instanciando + um objeto {@link android.widget.RemoteViews} que infle um arquivo de layout XML. Em seguida, + em vez de chamar métodos como + {@link android.support.v4.app.NotificationCompat.Builder#setContentTitle setContentTitle()}, + chame {@link android.support.v4.app.NotificationCompat.Builder#setContent setContent()}. Para definir + os detalhes do conteúdo na notificação personalizada, use os métodos em + {@link android.widget.RemoteViews} para definir os valores dos filhos da vista: +

+
    +
  1. + Crie um layout XML para a notificação em um arquivo separado. É possível usar o nome de arquivo que desejar, + mas deve-se usar a extensão .xml +
  2. +
  3. + No aplicativo, use métodos {@link android.widget.RemoteViews} para definir os ícones + e o texto da notificação. Coloque este objeto {@link android.widget.RemoteViews} + em {@link android.support.v4.app.NotificationCompat.Builder} + chamando {@link android.support.v4.app.NotificationCompat.Builder#setContent setContent()}. Evite definir + um {@link android.graphics.drawable.Drawable} de segundo plano + no objeto {@link android.widget.RemoteViews}, pois a cor do texto pode torná-lo ilegível. +
  4. +
+

+ A classe {@link android.widget.RemoteViews} também inclui métodos que podem ser usados + para adicionar facilmente um {@link android.widget.Chronometer} ou uma {@link android.widget.ProgressBar} + ao layout da notificação. Para obter mais informações sobre como criar layouts personalizados + para a notificação, consulte a documentação de referência de {@link android.widget.RemoteViews}. +

+

+ Atenção: ao usar um layout personalizado de notificação, certifique-se + de garantir que ele funcione com diferentes orientações e resoluções do dispositivo. Enquanto este aviso + aplica-se a todos os layouts de vistas, é muito importante para as notificações, + pois o espaço na gaveta da notificação é muito restrito. Não torne o layout personalizado muito complexo + e certifique-se de testá-lo em várias configurações. +

+ +

Uso de recursos de estilo para texto de notificação personalizada

+

+ Sempre use recursos de estilo para o texto de uma notificação personalizada A cor de fundo + da notificação pode variar dentre vários dispositivos e versões e o uso de recursos de estilo + ajuda a lidar com isto. A partir do Android 2.3, o sistema definiu um estilo + para o texto do layout de notificação padrão. Se usar o mesmo estilo nos aplicativos que usam Android + 2.3 ou mais recentes, você garantirá que o texto esteja visível em relação ao fundo da exibição. +

diff --git a/docs/html-intl/intl/pt-br/guide/topics/ui/overview.jd b/docs/html-intl/intl/pt-br/guide/topics/ui/overview.jd new file mode 100644 index 0000000000000000000000000000000000000000..d12bfe5fac8b593e37f764a5cf9cca45457a8a3f --- /dev/null +++ b/docs/html-intl/intl/pt-br/guide/topics/ui/overview.jd @@ -0,0 +1,71 @@ +page.title=Visão geral da IU +@jd:body + + +

Todos os elementos da interface do usuário em um aplicativo para Android são criados usando objetos {@link android.view.View} e +{@link android.view.ViewGroup}. Uma {@link android.view.View} é um objeto que desenha +algo na tela com o qual o usuário pode interagir. Um {@link android.view.ViewGroup} é um +objeto que contém outros objetos {@link android.view.View} (e {@link android.view.ViewGroup}) para +definir o layout da interface.

+ +

O Android fornece uma coleção de subclasses {@link android.view.View} e {@link +android.view.ViewGroup} que oferecem controles de entrada comuns (como botões e campos de +texto) e vários modelos de layout (como um layout linear ou relativo).

+ + +

Layout da interface do usuário

+ +

A interface do usuário de cada componente do aplicativo é definida usando uma hierarquia de objetos {@link +android.view.View} e {@link android.view.ViewGroup}, como mostra a figura 1. Cada grupo de vistas +é um recipiente invisível que organiza vistas de nível inferior, enquanto que as vistas de nível inferior podem ser +controles de entrada ou outros widgets +que desenham alguma parte da IU. Essa árvore hierárquica pode ser simples ou complexa, conforme necessário +(mas a simplicidade é melhor para desempenho).

+ + +

Figura 1. Ilustração de uma hierarquia de vistas, que define o layout +de uma IU.

+ +

Para declarar o layout, é possível instanciar objetos {@link android.view.View} no código e começar a +criar uma árvore. Mas a forma mais fácil e efetiva de definir o layout é com um arquivo XML. +O XML oferece uma estrutura legível por humanos para o layout, similar a HTML.

+ +

O nome de um elemento XML para uma vista é respectivo à classe do Android que ele representa. Portanto, um elemento +<TextView> cria um widget {@link android.widget.TextView} na IU +e um elemento <LinearLayout> cria um grupo de vistas de {@link android.widget.LinearLayout} +.

+ +

Por exemplo, um layout vertical simples com uma vista de texto e um botão se parece com:

+
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:layout_width="fill_parent" 
+              android:layout_height="fill_parent"
+              android:orientation="vertical" >
+    <TextView android:id="@+id/text"
+              android:layout_width="wrap_content"
+              android:layout_height="wrap_content"
+              android:text="I am a TextView" />
+    <Button android:id="@+id/button"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="I am a Button" />
+</LinearLayout>
+
+ +

Ao carregar um recurso de layout no aplicativo, o Android inicializa cada nó do layout em um +objeto em tempo de execução que pode ser usado para definir comportamentos adicionais, consultar o estado do objeto ou modificar o +layout.

+ +

Para obter um guia completo para criar um layout de IU, consulte Layouts +XML. + + +

Componentes da interface do usuário

+ +

Você não precisa criar toda a IU usando objetos {@link android.view.View} e {@link +android.view.ViewGroup}. O Android fornece vários componentes de aplicativo que oferecem +um layout de IU padrão para os quais basta definir o conteúdo. Esses componentes de IU +têm um conjunto único de APIs que são descritas nos respectivos documentos, como Barra de ação, Caixas de diálogo e Notificações de status.

+ + diff --git a/docs/html-intl/intl/pt-br/guide/topics/ui/settings.jd b/docs/html-intl/intl/pt-br/guide/topics/ui/settings.jd new file mode 100644 index 0000000000000000000000000000000000000000..f95966c37578da9aa6583b460b86c937494a6a0c --- /dev/null +++ b/docs/html-intl/intl/pt-br/guide/topics/ui/settings.jd @@ -0,0 +1,1202 @@ +page.title=Configurações +page.tags=preferência,preferenceactivity,preferencefragment + +@jd:body + + + + + + + +

Geralmente os aplicativos contêm configurações que permitem aos usuários modificar características e comportamentos do aplicativo. Por +exemplo: alguns aplicativos permitem aos usuários especificar se as notificações estão ativadas ou especificar a frequência +com que o aplicativo sincroniza dados com a nuvem.

+ +

Para fornecer configurações ao aplicativo, é preciso usar +as APIs {@link android.preference.Preference} do Android para programar uma interface coerente +com a experiência do usuário em outros aplicativos Android (inclusive as configurações do sistema). Este documento descreve +como programar as configurações do aplicativo por meio de APIs {@link android.preference.Preference}.

+ +
+

Projeto de configurações

+

Para obter mais informações sobre o projeto de configurações, leia o guia de projeto Configurações.

+
+ + + +

Figura 1. Capturas de tela das configurações do aplicativo Mensagens +do Android. A seleção de um item definido por uma {@link android.preference.Preference} +abre uma interface para alterar a configuração.

+ + + + +

Visão geral

+ +

Em vez de usar objetos {@link android.view.View} para criar a interface do usuário, as configurações +são criadas por meio de várias subclasses da classe {@link android.preference.Preference} +declaradas em um arquivo XML.

+ +

Os objetos {@link android.preference.Preference} são as peças fundamentais de uma única +configuração. Cada {@link android.preference.Preference} aparece como um item em uma lista e oferece a IU +adequada para que os usuários modifiquem a configuração. Por exemplo: uma {@link +android.preference.CheckBoxPreference} cria um item de lista que exibe uma caixa de seleção e uma {@link +android.preference.ListPreference} cria um item que abre uma caixa de diálogo com uma lista de opções.

+ +

Cada {@link android.preference.Preference} adicionada tem um par de valor-chave correspondente +que o sistema usa para salvar a configuração em um arquivo +{@link android.content.SharedPreferences} padrão para as configurações do aplicativo. Quando o usuário altera uma configuração, o sistema atualiza o valor +correspondente no arquivo {@link android.content.SharedPreferences}. O único momento em que +se deve interagir diretamente com o arquivo {@link android.content.SharedPreferences} associado +é no momento de ler o valor para determinar o comportamento do aplicativo com base na configuração do usuário.

+ +

O valor salvo em {@link android.content.SharedPreferences} para cada configuração pode ser +um dos seguintes tipos de dados:

+ +
    +
  • Boolean
  • +
  • Float
  • +
  • Int
  • +
  • Long
  • +
  • String
  • +
  • String {@link java.util.Set}
  • +
+ +

Como a IU de configurações do aplicativo é criada com objetos {@link android.preference.Preference} + em vez de objetos +{@link android.view.View}, é preciso usar uma subclasse {@link android.app.Activity} ou +{@link android.app.Fragment} especializada para exibir as configurações de lista:

+ +
    +
  • Se o aplicativo for compatível com versões do Android anteriores à 3.0 (nível da API 10 ou anterior), será +necessário criar a atividade como uma extensão da classe {@link android.preference.PreferenceActivity}.
  • +
  • No Android 3.0 ou versões posteriores, deve-se usar um {@link android.app.Activity} tradicional +que hospeda um {@link android.preference.PreferenceFragment} que exige as configurações do aplicativo. +No entanto, pode-se também usar {@link android.preference.PreferenceActivity} para criar um layout de dois painéis +para telas maiores quando há vários grupos de configurações.
  • +
+ +

Veja como configurar a {@link android.preference.PreferenceActivity} e instâncias de {@link +android.preference.PreferenceFragment} nas seções sobre a Criação de uma atividade de preferência e Uso +de fragmentos de preferência.

+ + +

Preferências

+ +

Toda configuração do aplicativo é representada por uma subclasse específica da classe {@link +android.preference.Preference}. Cada subclasse contém um conjunto de propriedades essenciais que permitem +especificar itens como o título da configuração e o valor padrão. Cada subclasse também oferece +suas propriedades e interface do usuário especializadas. Por exemplo: a figura 1 ilustra uma captura de tela +das configurações do aplicativo Mensagens. Cada item de lista na tela de configurações tem, como fundo, um objeto {@link +android.preference.Preference} diferente.

+ +

A seguir há algumas das preferências mais comuns:

+ +
+
{@link android.preference.CheckBoxPreference}
+
Exibe um item com uma caixa de seleção para uma configuração que esteja ativada ou desativada. O valor +salvo é um booleano (true se estiver selecionada).
+ +
{@link android.preference.ListPreference}
+
Abre uma caixa de diálogo com uma lista de botões de opção. O valor salvo +pode ser qualquer um dos tipos de valor compatíveis (listados acima).
+ +
{@link android.preference.EditTextPreference}
+
Abre uma caixa de diálogo com um widget {@link android.widget.EditText}. O valor salvo é um {@link +java.lang.String}.
+
+ +

Consulte a classe {@link android.preference.Preference} para ver uma lista de todas as outras subclasses e +as propriedades correspondentes.

+ +

É claro que as classes embutidas não acomodam todas as necessidades e o aplicativo pode exigir +algo mais especializado. Por exemplo: a plataforma atualmente não fornece nenhuma classe {@link +android.preference.Preference} para selecionar um número ou data. Portanto, pode ser necessário definir +a própria subclasse {@link android.preference.Preference}. Veja mais informações na seção sobre Composição de uma preferência personalizada.

+ + + +

Definição de preferências em XML

+ +

Embora se possa instanciar novos objetos {@link android.preference.Preference} em tempo de execução, +deve-se definir uma lista de configurações no XML com uma hierarquia +de objetos {@link android.preference.Preference}. Recomenda-se o uso de um arquivo XML para definir a coleção de configurações porque o arquivo +oferece uma estrutura fácil de ler e simples de atualizar. Além disso, as configurações do aplicativo +geralmente são predeterminadas, embora ainda seja possível modificar a coleção em tempo de execução.

+ +

Cada subclasse {@link android.preference.Preference} pode ser declarada com um elemento XML +correspondente ao nome da classe, como {@code <CheckBoxPreference>}.

+ +

É preciso salvar o arquivo XML no diretório {@code res/xml/}. Embora seja possível nomear livremente +o arquivo, é mais frequente vê-lo com o nome {@code preferences.xml}. Geralmente só é necessário um arquivo +porque as ramificações na hierarquia (que abrem sua própria lista de configurações) são declaradas +por meio de instâncias aninhadas de {@link android.preference.PreferenceScreen}.

+ +

Observação: se você deseja criar um layout de vários painéis +para as configurações, serão necessários arquivos XML separados para cada fragmento.

+ +

O nó raiz do arquivo XML deve ser um elemento {@link android.preference.PreferenceScreen +<PreferenceScreen>}. É dentro desse elemento que se adiciona cada {@link +android.preference.Preference}. Cada filho adicionado dentro do elemento +{@link android.preference.PreferenceScreen <PreferenceScreen>} é exibido com um item +único na lista de configurações.

+ +

Por exemplo:

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <CheckBoxPreference
+        android:key="pref_sync"
+        android:title="@string/pref_sync"
+        android:summary="@string/pref_sync_summ"
+        android:defaultValue="true" />
+    <ListPreference
+        android:dependency="pref_sync"
+        android:key="pref_syncConnectionType"
+        android:title="@string/pref_syncConnectionType"
+        android:dialogTitle="@string/pref_syncConnectionType"
+        android:entries="@array/pref_syncConnectionTypes_entries"
+        android:entryValues="@array/pref_syncConnectionTypes_values"
+        android:defaultValue="@string/pref_syncConnectionTypes_default" />
+</PreferenceScreen>
+
+ +

Nesse exemplo, existe um {@link android.preference.CheckBoxPreference} e um {@link +android.preference.ListPreference}. Os dois itens contêm estes três atributos:

+ +
+
{@code android:key}
+
Esse atributo é necessário para preferências que persistem a um valor de dados. Ele especifica a chave +exclusiva (uma string) que o sistema usa ao salvar o valor dessa configuração em {@link +android.content.SharedPreferences}. +

As únicas instâncias em que esse atributo é dispensável ocorrem quando a preferência é um +{@link android.preference.PreferenceCategory} ou {@link android.preference.PreferenceScreen}, +ou quando a preferência especifica um {@link android.content.Intent} para invocar (com um elemento {@code <intent>}) ou um {@link android.app.Fragment} para exibir (com um atributo {@code +android:fragment}).

+
+
{@code android:title}
+
Fornece à configuração um nome visível ao usuário.
+
{@code android:defaultValue}
+
Especifica o valor inicial que o sistema deve definir no arquivo {@link +android.content.SharedPreferences}. Deve-se fornecer um valor padrão para +todas as configurações.
+
+ +

Para mais informações sobre todos os outros atributos compatíveis, consulte a documentação {@link +android.preference.Preference} (e subclasse respectiva).

+ + +
+ +

Figura 2. Definição de categorias +com títulos.
1. A categoria é especificada pelo elemento {@link +android.preference.PreferenceCategory <PreferenceCategory>}.
2. O título +é especificado com o atributo {@code android:title}.

+
+ + +

Quando a lista de configurações excede cerca de 10 itens, pode ser necessário adicionar títulos +para definir grupos de configurações ou exibir esses grupos +em uma tela separada. Essas opções são descritas nas seções a seguir.

+ + +

Criação de grupos de configuração

+ +

Se você apresentar uma lista de 10 ou mais configurações, +os usuários podem ter dificuldade de percorrê-las, compreendê-las e processá-las. Para solucionar isso, +pode-se dividir algumas ou todas as configurações em grupos, transformando uma longa lista +em várias listas mais curtas. Um grupo de configurações relacionadas pode ser apresentado de uma das seguintes formas:

+ + + +

Pode-se usar uma ou ambas as técnicas de agrupamento para organizar as configurações do aplicativo. Ao decidir +qual delas usar e como dividir as configurações, deve-se seguir as diretrizes do guia +Configurações de Projeto do Android.

+ + +

Uso de títulos

+ +

Para usar divisores com cabeçalhos entre grupos de configurações (como ilustrado na figura 2), +coloca-se cada grupo de objetos {@link android.preference.Preference} dentro de {@link +android.preference.PreferenceCategory}.

+ +

Por exemplo:

+ +
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <PreferenceCategory 
+        android:title="@string/pref_sms_storage_title"
+        android:key="pref_key_storage_settings">
+        <CheckBoxPreference
+            android:key="pref_key_auto_delete"
+            android:summary="@string/pref_summary_auto_delete"
+            android:title="@string/pref_title_auto_delete"
+            android:defaultValue="false"... />
+        <Preference 
+            android:key="pref_key_sms_delete_limit"
+            android:dependency="pref_key_auto_delete"
+            android:summary="@string/pref_summary_delete_limit"
+            android:title="@string/pref_title_sms_delete"... />
+        <Preference 
+            android:key="pref_key_mms_delete_limit"
+            android:dependency="pref_key_auto_delete"
+            android:summary="@string/pref_summary_delete_limit"
+            android:title="@string/pref_title_mms_delete" ... />
+    </PreferenceCategory>
+    ...
+</PreferenceScreen>
+
+ + +

Uso de subtelas

+ +

Para usar grupos de configurações em uma subtela (como ilustrado na figura 3), coloque o grupo +de objetos {@link android.preference.Preference} dentro de {@link +android.preference.PreferenceScreen}.

+ + +

Figura 3. Subtelas de configuração. O elemento {@code +<PreferenceScreen>} cria +um item que, quando selecionado, abre uma lista separada para exibir as configurações aninhadas.

+ +

Por exemplo:

+ +
+<PreferenceScreen  xmlns:android="http://schemas.android.com/apk/res/android">
+    <!-- opens a subscreen of settings -->
+    <PreferenceScreen
+        android:key="button_voicemail_category_key"
+        android:title="@string/voicemail"
+        android:persistent="false">
+        <ListPreference
+            android:key="button_voicemail_provider_key"
+            android:title="@string/voicemail_provider" ... />
+        <!-- opens another nested subscreen -->
+        <PreferenceScreen
+            android:key="button_voicemail_setting_key"
+            android:title="@string/voicemail_settings"
+            android:persistent="false">
+            ...
+        </PreferenceScreen>
+        <RingtonePreference
+            android:key="button_voicemail_ringtone_key"
+            android:title="@string/voicemail_ringtone_title"
+            android:ringtoneType="notification" ... />
+        ...
+    </PreferenceScreen>
+    ...
+</PreferenceScreen>
+
+ + +

Uso de intenções

+ +

Em alguns casos, pode ser necessário que um item de preferência abra em um atividade diferente +e não na tela de configuração, como um navegador da web para exibir uma página da web. Para invocar um {@link +android.content.Intent} quando o usuário seleciona um item de preferência, adicione um elemento {@code <intent>} +como filho do elemento {@code <Preference>} correspondente.

+ +

Por exemplo, a seguir apresenta-se como usar um item de preferência para abrir uma página da web:

+ +
+<Preference android:title="@string/prefs_web_page" >
+    <intent android:action="android.intent.action.VIEW"
+            android:data="http://www.example.com" />
+</Preference>
+
+ +

É possível criar intenções implícitas e explícitas usando os seguintes atributos:

+ +
+
{@code android:action}
+
A ação a atribuir, conforme o método {@link android.content.Intent#setAction setAction()}. +
+
{@code android:data}
+
Os dados a atribuir, conforme o método {@link android.content.Intent#setData setData()}.
+
{@code android:mimeType}
+
O tipo MIME atribuir, conforme o método {@link android.content.Intent#setType setType()}. +
+
{@code android:targetClass}
+
A parte de classe do nome do componente, conforme o método {@link android.content.Intent#setComponent +setComponent()}.
+
{@code android:targetPackage}
+
A parte de pacote do nome do componente, conforme o método {@link +android.content.Intent#setComponent setComponent()}.
+
+ + + +

Criação de uma atividade de preferência

+ +

Para exibir as configurações em uma atividade, estenda a classe {@link +android.preference.PreferenceActivity}. É uma extensão da classe tradicional {@link +android.app.Activity} que exibe uma lista de configurações com base em uma hierarquia de objetos {@link +android.preference.Preference}. A {@link android.preference.PreferenceActivity} +automaticamente persiste às configurações associadas a cada {@link +android.preference.Preference} quando o usuário faz uma alteração.

+ +

Observação: ao desenvolver um aplicativo para Android 3.0 +ou superior, deve-se usar o {@link android.preference.PreferenceFragment}. Consulte a próxima +seção sobre o Uso de fragmentos de preferência.

+ +

O mais importante é não carregar um layout de vistas durante o retorno de chamada {@link +android.preference.PreferenceActivity#onCreate onCreate()}. Em vez disso, chama-se {@link +android.preference.PreferenceActivity#addPreferencesFromResource addPreferencesFromResource()} +para adicionar à atividade as preferências declaradas em um arquivo XML. Por exemplo: abaixo há o código mínimo +necessário para um {@link android.preference.PreferenceActivity} funcional:

+ +
+public class SettingsActivity extends PreferenceActivity {
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        addPreferencesFromResource(R.xml.preferences);
+    }
+}
+
+ +

Na verdade, esse código é suficiente para alguns aplicativos porque, assim que o usuário modifica uma preferência, +o sistema salva as alterações em um arquivo padrão {@link android.content.SharedPreferences} +que os componentes do outro aplicativo poderá ler quando for necessário verificar as configurações do usuário. No entanto, +muitos aplicativos exigem um pouco mais de código para escutar as alterações que ocorrem nas preferências. +Para informações sobre a escuda de alterações no arquivo {@link android.content.SharedPreferences}, +consulte a seção sobre Leitura de preferências.

+ + + + +

Uso de fragmentos de preferência

+ +

Ao desenvolver para Android 3.0 (nível da API 11) ou versões posteriores, deve-se usar um {@link +android.preference.PreferenceFragment} para exibir a lista de objetos {@link android.preference.Preference}. + Pode-se adicionar um {@link android.preference.PreferenceFragment} a qualquer atividade — não é +necessário usar {@link android.preference.PreferenceActivity}.

+ +

Os fragmentos permitem uma arquitetura +mais flexível para o aplicativo em comparação com o uso de apenas atividades para qualquer +tipo de atividade criada. Assim, sugerimos usar {@link +android.preference.PreferenceFragment} para controlar a exibição das configurações em vez de {@link +android.preference.PreferenceActivity} sempre que possível.

+ +

A implementação de {@link android.preference.PreferenceFragment} pode ser tão simples +quanto definir o método {@link android.preference.PreferenceFragment#onCreate onCreate()} para carregar +um arquivo de preferências com {@link android.preference.PreferenceFragment#addPreferencesFromResource +addPreferencesFromResource()}. Por exemplo:

+ +
+public static class SettingsFragment extends PreferenceFragment {
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        // Load the preferences from an XML resource
+        addPreferencesFromResource(R.xml.preferences);
+    }
+    ...
+}
+
+ +

É possível adicionar esse fragmento a um {@link android.app.Activity} como se faria com qualquer outro +{@link android.app.Fragment}. Por exemplo:

+ +
+public class SettingsActivity extends Activity {
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        // Display the fragment as the main content.
+        getFragmentManager().beginTransaction()
+                .replace(android.R.id.content, new SettingsFragment())
+                .commit();
+    }
+}
+
+ +

Observação: um {@link android.preference.PreferenceFragment} não tem +seu próprio objeto {@link android.content.Context}. Se for necessário um objeto {@link android.content.Context} +, é possível chamar {@link android.app.Fragment#getActivity()}. No entanto, tome cuidado para chamar +{@link android.app.Fragment#getActivity()} somente quando o fragmento estiver anexado a uma atividade. Quando +o fragmento ainda não estiver anexado, ou tiver sido separado durante o fim do seu ciclo de vida, {@link +android.app.Fragment#getActivity()} retornará como nulo.

+ + +

Configuração de valores padrão

+ +

As preferências criadas provavelmente definem alguns comportamentos importantes do aplicativo, portanto +é necessário inicializar o arquivo {@link android.content.SharedPreferences} associado +com os valores padrão de cada {@link android.preference.Preference} quando o usuário abre o aplicativo +pela primeira vez.

+ +

A primeira coisa a fazer é especificar o valor padrão de cada objeto {@link +android.preference.Preference} +no arquivo XML com o atributo {@code android:defaultValue}. O valor pode ser qualquer tipo de dados +apropriado para o objeto {@link android.preference.Preference} correspondente. Por +exemplo:

+ +
+<!-- default value is a boolean -->
+<CheckBoxPreference
+    android:defaultValue="true"
+    ... />
+
+<!-- default value is a string -->
+<ListPreference
+    android:defaultValue="@string/pref_syncConnectionTypes_default"
+    ... />
+
+ +

Em seguida, a partir do método {@link android.app.Activity#onCreate onCreate()} na atividade principal +do aplicativo — e em qualquer outra atividade pela qual o usuário possa entrar no aplicativo pela +primeira vez —, chame {@link android.preference.PreferenceManager#setDefaultValues +setDefaultValues()}:

+ +
+PreferenceManager.setDefaultValues(this, R.xml.advanced_preferences, false);
+
+ +

Essa chamada durante {@link android.app.Activity#onCreate onCreate()} garante que o aplicativo +seja adequadamente inicializado com as configurações padrão, que o aplicativo pode precisar ler +para determinar alguns comportamentos (se, por exemplo, baixará dados enquanto estiver +em uma rede de celular).

+ +

Esse método usa três argumentos:

+
    +
  • O {@link android.content.Context} do aplicativo.
  • +
  • O ID de recurso do arquivo XML de preferências para o qual você deseja definir os valores padrão.
  • +
  • Booleano que indica se os valores padrão devem ser definidos mais de uma vez. +

    Quando false, o sistema define os valores parão somente se esse método nunca tiver +sido chamado (ou se {@link android.preference.PreferenceManager#KEY_HAS_SET_DEFAULT_VALUES} +no arquivo de preferências compartilhadas do valor padrão for falso).

  • +
+ +

Enquanto o terceiro argumento estiver definido como false, pode-se chamar esse método com segurança +toda vez que a atividade iniciar sem substituir as preferências salvas do usuário redefinindo-as +para os padrões. No entanto, se ele for definido como true, todos os valores anteriores +serão substituídos pelos padrões.

+ + + +

Uso de cabeçalhos de preferência

+ +

Em casos raros, pode ser necessário projetar as configurações de forma que a primeira tela +exiba somente uma lista de subtelas (como as do aplicativo Configurações +conforme ilustrado nas figuras 4 e 5). Ao desenvolver um projeto desse tipo para Android 3.0 ou versão posterior, +deve-se usar um novo recurso "cabeçalhos" no Android 3.0 em vez de criar subtelas com elementos +{@link android.preference.PreferenceScreen} aninhados.

+ +

Para criar as configurações com cabeçalhos, é preciso:

+
    +
  1. Separar cada grupo de configurações em instâncias separadas de {@link +android.preference.PreferenceFragment}. Ou seja, cada grupo de configurações precisa de um arquivo +XML separado.
  2. +
  3. Criar um arquivo XML de cabeçalhos que lista cada grupo de configurações e declara que fragmento +contém a lista correspondente de configurações.
  4. +
  5. Estender a classe {@link android.preference.PreferenceActivity} para hospedar as configurações.
  6. +
  7. Implementar o retorno de chamada {@link +android.preference.PreferenceActivity#onBuildHeaders onBuildHeaders()} para especificar +o arquivo de cabeçalhos.
  8. +
+ +

Um grande benefício de usar esse modelo é que {@link android.preference.PreferenceActivity} +automaticamente apresenta o layout de dois painéis ilustrado na figura 4 ao executar em telas grandes.

+ +

Mesmo se o aplicativo for compatível com versões de Android anteriores à 3.0, é possível programar +o aplicativo para usar {@link android.preference.PreferenceFragment} para uma apresentação em dois painéis +em dispositivos mais novos e ser compatível com a hierarquia tradicional multitelas +em dispositivos mais antigos (veja a seção sobre Compatibilidade de versões +mais antigas com cabeçalhos de preferência).

+ + +

Figura 4. Layout de dois painéis com cabeçalhos.
1. Os cabeçalhos +são definidos com um arquivo XML de cabeçalhos.
2. Cada grupo de configurações é definido por um +{@link android.preference.PreferenceFragment} especificado por um elemento {@code <header>} +no arquivo de cabeçalhos.

+ + +

Figura 5. Um dispositivo celular com cabeçalhos de configuração. Quando +um item é selecionado o {@link android.preference.PreferenceFragment} associado substitui +os cabeçalhos.

+ + +

Criação do arquivo de cabeçalhos

+ +

Cada grupo de configurações na lista de cabeçalhos é especificado por um único elemento {@code <header>} +dentro de um elemento raiz {@code <preference-headers>}. Por exemplo:

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<preference-headers xmlns:android="http://schemas.android.com/apk/res/android">
+    <header 
+        android:fragment="com.example.prefs.SettingsActivity$SettingsFragmentOne"
+        android:title="@string/prefs_category_one"
+        android:summary="@string/prefs_summ_category_one" />
+    <header 
+        android:fragment="com.example.prefs.SettingsActivity$SettingsFragmentTwo"
+        android:title="@string/prefs_category_two"
+        android:summary="@string/prefs_summ_category_two" >
+        <!-- key/value pairs can be included as arguments for the fragment. -->
+        <extra android:name="someKey" android:value="someHeaderValue" />
+    </header>
+</preference-headers>
+
+ +

Com o atributo {@code android:fragment}, cada cabeçalho declara uma instância de {@link +android.preference.PreferenceFragment} que deve abrir quando o usuário selecionar o cabeçalho.

+ +

O elemento {@code <extras>} permite passar os pares de valores-chave para o fragmento em um {@link +android.os.Bundle}. O fragmento pode recuperar os argumentos chamando {@link +android.app.Fragment#getArguments()}. Há vários motivos para passar argumentos ao fragmento, +mas um bom motivo é reutilizar a mesma subclasse de {@link +android.preference.PreferenceFragment} para cada grupo e usar o argumento para especificar +o arquivo XML de preferências que o fragmento deve carregar.

+ +

Por exemplo, a seguir há um fragmento que pode ser reutilizado em vários grupos de configurações, quando +cada cabeçalho define um argumento {@code <extra>} com a chave {@code "settings"}:

+ +
+public static class SettingsFragment extends PreferenceFragment {
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        String settings = getArguments().getString("settings");
+        if ("notifications".equals(settings)) {
+            addPreferencesFromResource(R.xml.settings_wifi);
+        } else if ("sync".equals(settings)) {
+            addPreferencesFromResource(R.xml.settings_sync);
+        }
+    }
+}
+
+ + + +

Exibição de cabeçalhos

+ +

Para exibir os cabeçalhos de preferência, é preciso implementar o método de retorno de chamada {@link +android.preference.PreferenceActivity#onBuildHeaders onBuildHeaders()} e chamar +{@link android.preference.PreferenceActivity#loadHeadersFromResource +loadHeadersFromResource()}. Por exemplo:

+ +
+public class SettingsActivity extends PreferenceActivity {
+    @Override
+    public void onBuildHeaders(List<Header> target) {
+        loadHeadersFromResource(R.xml.preference_headers, target);
+    }
+}
+
+ +

Quando o usuário seleciona um item de uma lista de cabeçalhos, o sistema abre o {@link +android.preference.PreferenceFragment} associado.

+ +

Observação: ao usar cabeçalhos de preferência, a subclasse de {@link +android.preference.PreferenceActivity} não precisa implementar o método {@link +android.preference.PreferenceActivity#onCreate onCreate()} porque a única tarefa +necessária para a atividade é carregar os cabeçalhos.

+ + +

Compatibilidade de versões mais antigas com cabeçalhos de preferência

+ +

Se o aplicativo for compatível com versões de Android anteriores à 3.0, ainda será possível usar cabeçalhos +para fornecer um layout em dois painéis ao executar no Android 3.0 e versões posteriores. Basta criar um arquivo XML de preferências +adicional que usa elementos básicos {@link android.preference.Preference +<Preference>} que se comportam como os itens de cabeçalho (para uso das versões mais antigas +do Android).

+ +

No entanto, em vez de abrir um novo {@link android.preference.PreferenceScreen}, cada elemento {@link +android.preference.Preference <Preference>} envia um {@link android.content.Intent} +ao {@link android.preference.PreferenceActivity} que especifica que arquivo XML de preferência +carregar.

+ +

Por exemplo, abaixo há um arquivo XML de cabeçalhos de preferência usado no Android 3.0 +e posterior ({@code res/xml/preference_headers.xml}):

+ +
+<preference-headers xmlns:android="http://schemas.android.com/apk/res/android">
+    <header 
+        android:fragment="com.example.prefs.SettingsFragmentOne"
+        android:title="@string/prefs_category_one"
+        android:summary="@string/prefs_summ_category_one" />
+    <header 
+        android:fragment="com.example.prefs.SettingsFragmentTwo"
+        android:title="@string/prefs_category_two"
+        android:summary="@string/prefs_summ_category_two" />
+</preference-headers>
+
+ +

E apresenta-se também um arquivo de preferências que fornece os mesmos cabeçalhos de versões de Android +mais antigas que a 3.0 ({@code res/xml/preference_headers_legacy.xml}):

+ +
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <Preference 
+        android:title="@string/prefs_category_one"
+        android:summary="@string/prefs_summ_category_one"  >
+        <intent 
+            android:targetPackage="com.example.prefs"
+            android:targetClass="com.example.prefs.SettingsActivity"
+            android:action="com.example.prefs.PREFS_ONE" />
+    </Preference>
+    <Preference 
+        android:title="@string/prefs_category_two"
+        android:summary="@string/prefs_summ_category_two" >
+        <intent 
+            android:targetPackage="com.example.prefs"
+            android:targetClass="com.example.prefs.SettingsActivity"
+            android:action="com.example.prefs.PREFS_TWO" />
+    </Preference>
+</PreferenceScreen>
+
+ +

Como a compatibilidade com {@code <preference-headers>} foi adicionada no Android 3.0, o sistema chama +{@link android.preference.PreferenceActivity#onBuildHeaders onBuildHeaders()} em seu {@link +android.preference.PreferenceActivity} somente ao executar em Androd 3.0 ou posterior. Para carregar +o arquivo de cabeçalhos de “legado" ({@code preference_headers_legacy.xml}), é preciso verificar a versãodo Android +e, se a versão for mais antiga que o Android 3.0 ({@link +android.os.Build.VERSION_CODES#HONEYCOMB}), chama {@link +android.preference.PreferenceActivity#addPreferencesFromResource addPreferencesFromResource()} +para carregar o arquivo de cabeçalho legado. Por exemplo:

+ +
+@Override
+public void onCreate(Bundle savedInstanceState) {
+    super.onCreate(savedInstanceState);
+    ...
+
+    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
+        // Load the legacy preferences headers
+        addPreferencesFromResource(R.xml.preference_headers_legacy);
+    }
+}
+
+// Called only on Honeycomb and later
+@Override
+public void onBuildHeaders(List<Header> target) {
+   loadHeadersFromResource(R.xml.preference_headers, target);
+}
+
+ +

Depois só resta tratar o {@link android.content.Intent} passado para a atividade +para identificar que arquivo de preferências carregar. Portanto, para recuperar a ação da intenção e compará-la +com strings de ações conhecidas usadas nas tags de {@code <intent>} do XML de preferências:

+ +
+final static String ACTION_PREFS_ONE = "com.example.prefs.PREFS_ONE";
+...
+
+@Override
+public void onCreate(Bundle savedInstanceState) {
+    super.onCreate(savedInstanceState);
+
+    String action = getIntent().getAction();
+    if (action != null && action.equals(ACTION_PREFS_ONE)) {
+        addPreferencesFromResource(R.xml.preferences);
+    }
+    ...
+
+    else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
+        // Load the legacy preferences headers
+        addPreferencesFromResource(R.xml.preference_headers_legacy);
+    }
+}
+
+ +

Observe que chamadas consecutivas a {@link +android.preference.PreferenceActivity#addPreferencesFromResource addPreferencesFromResource()} +empilharão todas as preferências em uma única lista, portanto certifique-se de que seja chamado somente uma vez, encadeando +as condições com declarações else-if.

+ + + + + +

Leitura de preferências

+ +

Por padrão, todas as preferências do aplicativo são salvas em um arquivo acessível de qualquer lugar +dentro do aplicativo chamando o método estático {@link +android.preference.PreferenceManager#getDefaultSharedPreferences +PreferenceManager.getDefaultSharedPreferences()}. Isso retorna o objeto {@link +android.content.SharedPreferences} que contém todos os pares de valores-chave associados +aos objetos {@link android.preference.Preference} usados em {@link +android.preference.PreferenceActivity}.

+ +

Por exemplo, abaixo apresenta-se como ler um dos valores de preferência de outra atividade +no aplicativo:

+ +
+SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this);
+String syncConnPref = sharedPref.getString(SettingsActivity.KEY_PREF_SYNC_CONN, "");
+
+ + + +

Escuta de alterações de preferência

+ +

Há alguns motivos pelos quais pode ser necessário ser notificado assim que o usuário altera +uma das preferências. Para receber um retorno de chamada quando acontece uma alteração em alguma das preferências, +implemente a interface {@link android.content.SharedPreferences.OnSharedPreferenceChangeListener +SharedPreference.OnSharedPreferenceChangeListener} e registre a escuta +para o objeto {@link android.content.SharedPreferences} chamando {@link +android.content.SharedPreferences#registerOnSharedPreferenceChangeListener +registerOnSharedPreferenceChangeListener()}.

+ +

A interface tem somente um método de retorno de chamada, {@link +android.content.SharedPreferences.OnSharedPreferenceChangeListener#onSharedPreferenceChanged +onSharedPreferenceChanged()}, e pode ser mais fácil implementar a interface como parte +da atividade. Por exemplo:

+ +
+public class SettingsActivity extends PreferenceActivity
+                              implements OnSharedPreferenceChangeListener {
+    public static final String KEY_PREF_SYNC_CONN = "pref_syncConnectionType";
+    ...
+
+    public void onSharedPreferenceChanged(SharedPreferences sharedPreferences,
+        String key) {
+        if (key.equals(KEY_PREF_SYNC_CONN)) {
+            Preference connectionPref = findPreference(key);
+            // Set summary to be the user-description for the selected value
+            connectionPref.setSummary(sharedPreferences.getString(key, ""));
+        }
+    }
+}
+
+ +

Nesse exemplo, o método verifica se a configuração alterada se destina a uma chave de preferência conhecida. Ele +chama {@link android.preference.PreferenceActivity#findPreference findPreference()} para obter +o objeto {@link android.preference.Preference} alterado para que possa modificar o sumário +do item como uma descrição da seleção do usuário. Ou seja, quando a configuração for uma {@link +android.preference.ListPreference} ou outra configuração de múltipla escolha, deve-se chamar {@link +android.preference.Preference#setSummary setSummary()} quando a configuração for alterada para exibir +o status atual (como a configuração Suspensão mostrada na figura 5).

+ +

Observação: conforme descrito no documento do Projeto para Android sobre Configurações, recomendamos atualizar +o sumário de {@link android.preference.ListPreference} a cada vez que o usuário alterar a preferência +para descrever a configuração atual.

+ +

Para um gerenciamento adequado do ciclo de vida na atividade, recomendamos registrar e remover o registro +de {@link android.content.SharedPreferences.OnSharedPreferenceChangeListener} durante os retornos de chamada de {@link +android.app.Activity#onResume} e {@link android.app.Activity#onPause} respectivamente:

+ +
+@Override
+protected void onResume() {
+    super.onResume();
+    getPreferenceScreen().getSharedPreferences()
+            .registerOnSharedPreferenceChangeListener(this);
+}
+
+@Override
+protected void onPause() {
+    super.onPause();
+    getPreferenceScreen().getSharedPreferences()
+            .unregisterOnSharedPreferenceChangeListener(this);
+}
+
+ +

Atenção: ao chamar {@link +android.content.SharedPreferences#registerOnSharedPreferenceChangeListener +registerOnSharedPreferenceChangeListener()}, o gerenciador de preferências +não armazena atualmente uma referência à escuta. É preciso armazenar uma referência +forte à escuta, senão ela será suscetível à coleta de lixo. Recomendamos +manter uma referência à escuta nos dados de instância de um objeto +que existirá enquanto a escuta for necessária.

+ +

Por exemplo: no código a seguir, o autor da chamada não mantém nenhuma +referência à escuta. Como resultado, a escuta estará sujeita à coleta de lixo +e falhará futuramente em algum momento indeterminado:

+ +
+prefs.registerOnSharedPreferenceChangeListener(
+  // Bad! The listener is subject to garbage collection!
+  new SharedPreferences.OnSharedPreferenceChangeListener() {
+  public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
+    // listener implementation
+  }
+});
+
+ +

Em vez disso, armazene uma referência à escuta nos dados de instância de um objeto +que existirá enquanto a escuta for necessária:

+ +
+SharedPreferences.OnSharedPreferenceChangeListener listener =
+    new SharedPreferences.OnSharedPreferenceChangeListener() {
+  public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
+    // listener implementation
+  }
+};
+prefs.registerOnSharedPreferenceChangeListener(listener);
+
+ +

Gerenciamento de uso de rede

+ + +

A partir do Android 4.0, o aplicativo Configurações do sistema permite aos usuários ver o quanto +de dados de rede que os aplicativos usam em primeiro e segundo plano. Portanto, os usuários +podem desativar os dados em segundo plano de aplicativos individuais. Para evitar que os usuários desativem +o acesso do aplicativo a dados em segundo plano, deve-se usar a conexão de dados de forma eficiente +e permitir aos usuários refinar o uso de dados do aplicativo por meio das configurações do aplicativo.

+ +

Por exemplo: deve-se permitir ao usuário controlar a frequência de sincronização dos dados do aplicativo para +uploads/downloads somente quando estiver em Wi-Fi, o aplicativo usar dados em deslocamento etc. Com esses +controles disponíveis para eles, é bem menos provável que os usuários desativem o acesso do aplicativo a dados +quando eles se aproximam dos limites que definem nas Configurações do sistema porque, em vez disso, podem controlar +precisamente a quantidade de dados que o aplicativo usa.

+ +

Depois de adicionadas as preferências necessárias em {@link android.preference.PreferenceActivity} +para controlar os hábitos de dados do aplicativo, deve-se adicionar um filtro de intenções para {@link +android.content.Intent#ACTION_MANAGE_NETWORK_USAGE} no arquivo de manifesto. Por exemplo:

+ +
+<activity android:name="SettingsActivity" ... >
+    <intent-filter>
+       <action android:name="android.intent.action.MANAGE_NETWORK_USAGE" />
+       <category android:name="android.intent.category.DEFAULT" />
+    </intent-filter>
+</activity>
+
+ +

Esse filtro de intenções indica ao sistema que se trata da atividade que controla +o uso de dados do aplicativo. Assim, quando o usuário inspeciona a quantidade de dados que o aplicativo está usando +no aplicativo Configurações do sistema, um botão Exibir configurações de aplicativo fica disponível e inicia +{@link android.preference.PreferenceActivity} para que o usuário refine a quantidade de dados que +o aplicativo usa.

+ + + + + + + +

Composição de uma preferência personalizada

+ +

A estrutura do Android contém uma variedade de subclasses {@link android.preference.Preference} que permitem +criar uma IU com diferentes tipos de configurações. +No entanto, pode ser necessário descobrir uma configuração pra a qual não há nenhuma solução embutida, +como um seletor de números ou seletor de datas. Nesse caso, será preciso criar uma preferência personalizada, estendendo +a classe {@link android.preference.Preference} ou uma das outras subclasses.

+ +

Ao estender a classe {@link android.preference.Preference}, há algumas coisas importantes +a fazer:

+ +
    +
  • Especificar a interface do usuário exibida quando o usuário seleciona as configurações.
  • +
  • Salvar os valores da configuração conforme apropriado.
  • +
  • Inicializar {@link android.preference.Preference} com o valor atual (ou padrão) +quando ela é exibida.
  • +
  • Fornecer o valor padrão quando solicitado pelo sistema.
  • +
  • Se {@link android.preference.Preference} fornece sua própria IU (como uma caixa de diálogo, por exemplo), salve +e restaure o estado para tratar de alterações de ciclo de vida (como quando o usuário gira a tela).
  • +
+ +

As seções a seguir descrevem como executar cada uma dessas tarefas.

+ + + +

Especificação da interface do usuário

+ +

Se a classe {@link android.preference.Preference} for estendida, será preciso implementar +{@link android.preference.Preference#onClick()} para definir a ação que ocorre quando +o usuário a seleciona. No entanto, a maioria das configurações personalizadas estendem {@link android.preference.DialogPreference} +para exibir uma caixa de diálogo, o que simplifica o procedimento. Quando se estende {@link +android.preference.DialogPreference}, é preciso chamar {@link +android.preference.DialogPreference#setDialogLayoutResource setDialogLayoutResourcs()} na classe +do construtor para especificar o layout da caixa de diálogo.

+ +

Por exemplo, eis o construtor de um {@link +android.preference.DialogPreference} personalizado que declara o layout e especifica o texto dos botões padrão +da caixa de diálogo positiva e negativa:

+ +
+public class NumberPickerPreference extends DialogPreference {
+    public NumberPickerPreference(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        
+        setDialogLayoutResource(R.layout.numberpicker_dialog);
+        setPositiveButtonText(android.R.string.ok);
+        setNegativeButtonText(android.R.string.cancel);
+        
+        setDialogIcon(null);
+    }
+    ...
+}
+
+ + + +

Salvamento do valor da configuração

+ +

Para salvar um valor da configuração a qualquer momento, chame um dos métodos {@code persist*()} da classe {@link +android.preference.Preference}, como {@link +android.preference.Preference#persistInt persistInt()} se o valor da configuração for um inteiro +ou {@link android.preference.Preference#persistBoolean persistBoolean()} para salvar um booleano.

+ +

Observação: cada {@link android.preference.Preference} pode salvar somente +um tipo de dados, portanto é preciso usar o método {@code persist*()} adequado para o tipo de dados usado pela +{@link android.preference.Preference} personalizada.

+ +

Quando se opta por persistir, a configuração pode depender da classe {@link +android.preference.Preference} estendida. Se {@link +android.preference.DialogPreference} for estendida, deve-se persistir o valor somente quando a caixa de diálogo +fecha devido a um resultado positivo (o usuário seleciona o botão "OK").

+ +

Quando uma {@link android.preference.DialogPreference} fecha, o sistema chama o método {@link +android.preference.DialogPreference#onDialogClosed onDialogClosed()}. O método contém um argumento +booleano que especifica se o resultado do usuário é "positivo" — se o valor é +true e, em seguida, o usuário selecionou o botão positivo e você deve salvar o novo valor. Por +exemplo:

+ +
+@Override
+protected void onDialogClosed(boolean positiveResult) {
+    // When the user selects "OK", persist the new value
+    if (positiveResult) {
+        persistInt(mNewValue);
+    }
+}
+
+ +

Nesse exemplo, mNewValue é um membro da classe que retém o valor atual +da configuração. A chamada de {@link android.preference.Preference#persistInt persistInt()} salva o valor +no arquivo {@link android.content.SharedPreferences} (usando automaticamente a chave +especificada no arquivo XML dessa {@link android.preference.Preference}).

+ + +

Inicialização do valor atual

+ +

Quando o sistema adiciona o {@link android.preference.Preference} à tela, ele chama +{@link android.preference.Preference#onSetInitialValue onSetInitialValue()} para notificar +se a configuração tem um valor persistido. Se não houver valor persistido, essa chamada fornece +o valor padrão.

+ +

O método {@link android.preference.Preference#onSetInitialValue onSetInitialValue()} passa +um booleano, restorePersistedValue, para indicar se um valor já foi persistido +para a configuração. Se for true, deve-se recuperar o valor persistindo chamando-se + um dos métodos {@code getPersisted*()} da classe {@link +android.preference.Preference}, como {@link +android.preference.Preference#getPersistedInt getPersistedInt()} para um valor inteiro. Geralmente +se recupera o valor persistido para atualizar adequadamente a IU de forma a refletir +o valor salvo anteriormente.

+ +

Se restorePersistedValue for false, deve-se +usar o valor padrão passado no segundo argumento.

+ +
+@Override
+protected void onSetInitialValue(boolean restorePersistedValue, Object defaultValue) {
+    if (restorePersistedValue) {
+        // Restore existing state
+        mCurrentValue = this.getPersistedInt(DEFAULT_VALUE);
+    } else {
+        // Set default state from the XML attribute
+        mCurrentValue = (Integer) defaultValue;
+        persistInt(mCurrentValue);
+    }
+}
+
+ +

Cada método {@code getPersisted*()} pega um argumento que especifica o valor +padrão a usar caso não haja nenhum valor persistido ou se a chave não existir. No +exemplo acima, uma constante local é usada para especificar o valor padrão se {@link +android.preference.Preference#getPersistedInt getPersistedInt()} não puder retornar um valor persistido.

+ +

Atenção: não é possível usar +defaultValue como valor padrão no método {@code getPersisted*()} porque +seu valor é sempre nulo quando restorePersistedValue é true.

+ + +

Fornecimento de um valor padrão

+ +

Se a instância da classe {@link android.preference.Preference} especificar um valor padrão +(com o atributo {@code android:defaultValue}), +o sistema chama {@link android.preference.Preference#onGetDefaultValue +onGetDefaultValue()} quando instancia o objeto para recuperar o valor. É preciso +implementar esse método para que o sistema salve o valor padrão em {@link +android.content.SharedPreferences}. Por exemplo:

+ +
+@Override
+protected Object onGetDefaultValue(TypedArray a, int index) {
+    return a.getInteger(index, DEFAULT_VALUE);
+}
+
+ +

Os argumentos do método oferecem todo o necessário: a matriz de atributos e a posição +do índice do {@code android:defaultValue}, que é preciso recuperar. É preciso implementar esse método +para extrair o valor padrão do atributo porque deve-se especificar um valor padrão +local para o atributo caso o valor seja indefinido.

+ + + +

Salvamento e restauração do estado da preferência

+ +

Como um {@link android.view.View} em um layout, a subclasse {@link android.preference.Preference} +é responsável por salvar e restaurar seu estado caso a atividade ou fragmento seja +reiniciado (como ocorre quando o usuário gira a tela). Para salvar e restaurar +adequadamente o estado da classe {@link android.preference.Preference}, é preciso implementar +os métodos de retorno de chamada do ciclo de vida {@link android.preference.Preference#onSaveInstanceState +onSaveInstanceState()} e {@link +android.preference.Preference#onRestoreInstanceState onRestoreInstanceState()}.

+ +

O estado de {@link android.preference.Preference} é definido por um objeto que implementa +a interface {@link android.os.Parcelable}. A estrutura do Android fornece esse objeto +como um ponto inicial para definir o objeto de estado: a classe {@link +android.preference.Preference.BaseSavedState}.

+ +

Para definir como a classe {@link android.preference.Preference} salva seu estado, deve-se estender +a classe {@link android.preference.Preference.BaseSavedState}. É preciso substituir +alguns métodos e definir o objeto {@link android.preference.Preference.BaseSavedState#CREATOR}. +

+ +

Na maioria dos aplicativos, é possível copiar a implementação a seguir e simplesmente alterar as linhas +que tratam o {@code value} se a subclasse {@link android.preference.Preference} salvar um tipo +de dados que não seja um inteiro.

+ +
+private static class SavedState extends BaseSavedState {
+    // Member that holds the setting's value
+    // Change this data type to match the type saved by your Preference
+    int value;
+
+    public SavedState(Parcelable superState) {
+        super(superState);
+    }
+
+    public SavedState(Parcel source) {
+        super(source);
+        // Get the current preference's value
+        value = source.readInt();  // Change this to read the appropriate data type
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        super.writeToParcel(dest, flags);
+        // Write the preference's value
+        dest.writeInt(value);  // Change this to write the appropriate data type
+    }
+
+    // Standard creator object using an instance of this class
+    public static final Parcelable.Creator<SavedState> CREATOR =
+            new Parcelable.Creator<SavedState>() {
+
+        public SavedState createFromParcel(Parcel in) {
+            return new SavedState(in);
+        }
+
+        public SavedState[] newArray(int size) {
+            return new SavedState[size];
+        }
+    };
+}
+
+ +

Com a implementação acima de {@link android.preference.Preference.BaseSavedState} adicionada +ao aplicativo (geralmente como uma subclasse da subclasse {@link android.preference.Preference}), +é preciso implementar os métodos {@link android.preference.Preference#onSaveInstanceState +onSaveInstanceState()} e {@link +android.preference.Preference#onRestoreInstanceState onRestoreInstanceState()} +da subclasse {@link android.preference.Preference}.

+ +

Por exemplo:

+ +
+@Override
+protected Parcelable onSaveInstanceState() {
+    final Parcelable superState = super.onSaveInstanceState();
+    // Check whether this Preference is persistent (continually saved)
+    if (isPersistent()) {
+        // No need to save instance state since it's persistent,
+        // use superclass state
+        return superState;
+    }
+
+    // Create instance of custom BaseSavedState
+    final SavedState myState = new SavedState(superState);
+    // Set the state's value with the class member that holds current
+    // setting value
+    myState.value = mNewValue;
+    return myState;
+}
+
+@Override
+protected void onRestoreInstanceState(Parcelable state) {
+    // Check whether we saved the state in onSaveInstanceState
+    if (state == null || !state.getClass().equals(SavedState.class)) {
+        // Didn't save the state, so call superclass
+        super.onRestoreInstanceState(state);
+        return;
+    }
+
+    // Cast state to custom BaseSavedState and pass to superclass
+    SavedState myState = (SavedState) state;
+    super.onRestoreInstanceState(myState.getSuperState());
+    
+    // Set this Preference's widget to reflect the restored state
+    mNumberPicker.setValue(myState.value);
+}
+
+ diff --git a/docs/html-intl/intl/pt-br/guide/topics/ui/ui-events.jd b/docs/html-intl/intl/pt-br/guide/topics/ui/ui-events.jd new file mode 100644 index 0000000000000000000000000000000000000000..e0ace1d4b3cac5a90467e8b3d2c9a936291c4431 --- /dev/null +++ b/docs/html-intl/intl/pt-br/guide/topics/ui/ui-events.jd @@ -0,0 +1,291 @@ +page.title=Eventos de entrada +parent.title=Interface do usuário +parent.link=index.html +@jd:body + + + +

No Android, há mais de uma maneira de interceptar os eventos da interação de um usuário com o aplicativo. +Ao considerar os eventos dentro da interface do usuário, a abordagem é capturar os eventos +de um objeto de View específico com o qual o usuário interage. A classe View fornece os meios para fazer isto.

+ +

Dentro das várias classes View que você usará para compor o layout, é possível notar vários métodos +públicos de retorno de chamada que parecem úteis para eventos de IU. Esses métodos são chamados pela estrutura do Android quando +a ação respectiva ocorre neste objeto. Por exemplo, quando uma View (como um botão) é tocada, +o método onTouchEvent() é chamado neste objeto. No entanto, para interceptar isto, você deve estender +a classe e substituir o método. No entanto, estender todos os objetos de View +para lidar com tal evento não seria algo prático. É por isso que a classe View também contém +uma coleção de interfaces aninhadas com retornos de chamada que podem ser definidas com muito mais facilidade. Essas interfaces, +chamadas de escutas de evento, são a sua passagem para capturar a interação do usuário com a IU.

+ +

Geralmente, as escutas de evento são usadas para escutar a interação do usuário. +No entanto, há casos em que você pode querer estender uma classe View para criar um componente personalizado. +Talvez você queira estender a classe {@link android.widget.Button} +para deixar algo mais extravagante. Neste caso, você poderá definir os comportamentos de evento padrão +para a classe usando manipuladores de evento.

+ + +

Escutas de evento

+ +

Uma escuta de evento é uma interface na classe {@link android.view.View} que contém +um único método de retorno de chamada. Esses métodos serão chamados pela estrutura do Android, quando a View para a qual a escuta +estiver registrada for ativada pela interação do usuário com o item na IU.

+ +

Inclusos nas interfaces da escuta de evento estão os seguintes métodos de retorno de chamada:

+ +
+
onClick()
+
De {@link android.view.View.OnClickListener}. + Isto é chamado quando o usuário toca no item + (no modo de toque), ou atribui foco ao item com as teclas de navegação ou cursor de bola + e pressiona a tecla "enter" adequada ou pressiona o cursor de bola.
+
onLongClick()
+
De {@link android.view.View.OnLongClickListener}. + Isto é chamado quando o usuário toca e mantém o item pressionado (no modo de toque), + ou atribui foco ao item com as teclas de navegação ou cursor de bola + e mantém pressionada a tecla "enter" adequada ou o cursor de bola (por um segundo).
+
onFocusChange()
+
De {@link android.view.View.OnFocusChangeListener}. + Isto é chamado quando o usuário navega no ou do item, usando as teclas de navegação ou cursor de bola.
+
onKey()
+
De {@link android.view.View.OnKeyListener}. + Isto é chamado quando o usuário está com foco no item ou solta uma tecla de hardware no dispositivo.
+
onTouch()
+
De {@link android.view.View.OnTouchListener}. + Isto é chamado quando o usuário realiza uma ação qualificada como um toque de evento, incluindo o pressionamento, a liberação, + ou qualquer outro gesto de movimento na tela (dentro dos limites do item).
+
onCreateContextMenu()
+
De {@link android.view.View.OnCreateContextMenuListener}. + Isto é chamado quando um menu de contexto está sendo construído (como resultado de um "clique longo"). Consulte a discussão + sobre menus de contexto no guia do desenvolvedor Menus +.
+
+ +

Esses métodos são os únicos habitantes de suas respectivas interfaces. Para definir um desses métodos +e lidar com seus eventos, implemente a interface aninhada na atividade ou defina-a como uma classe anônima. +Em seguida, passe uma instância da implementação +para o respectivo método View.set...Listener(). (Ex.:, chame +{@link android.view.View#setOnClickListener(View.OnClickListener) setOnClickListener()} +e passe-o à implementação de {@link android.view.View.OnClickListener OnClickListener}.)

+ +

O exemplo abaixo mostra como registrar uma escuta de clique para um botão.

+ +
+// Create an anonymous implementation of OnClickListener
+private OnClickListener mCorkyListener = new OnClickListener() {
+    public void onClick(View v) {
+      // do something when the button is clicked
+    }
+};
+
+protected void onCreate(Bundle savedValues) {
+    ...
+    // Capture our button from layout
+    Button button = (Button)findViewById(R.id.corky);
+    // Register the onClick listener with the implementation above
+    button.setOnClickListener(mCorkyListener);
+    ...
+}
+
+ +

Você também pode achar mais conveniente implementar OnClickListener como parte da atividade. +Isto evitará carga adicional na classe e a alocação do objeto. Por exemplo:

+
+public class ExampleActivity extends Activity implements OnClickListener {
+    protected void onCreate(Bundle savedValues) {
+        ...
+        Button button = (Button)findViewById(R.id.corky);
+        button.setOnClickListener(this);
+    }
+
+    // Implement the OnClickListener callback
+    public void onClick(View v) {
+      // do something when the button is clicked
+    }
+    ...
+}
+
+ +

Observe que o retorno de chamada onClick() no exemplo acima +não tem valor de retorno, mas outros métodos de escuta de evento podem retornar um booleano. O motivo +depende do evento. Para os poucos que retornam, apresenta-se a razão:

+
    +
  • {@link android.view.View.OnLongClickListener#onLongClick(View) onLongClick()} - + Isto retorna um booleano para indicar se você consumiu o evento e se ele deve ser levado adiante. + Ou seja, ele retorna verdadeiro para indicar que você lidou com o evento e não deve seguir adiante; + ou retorna falso caso você não tenha lidado com ele e/ou o evento deva continuar para qualquer + outra escuta de clique.
  • +
  • {@link android.view.View.OnKeyListener#onKey(View,int,KeyEvent) onKey()} - + Isto retorna um booleano para indicar se você consumiu o evento e se ele deve ser levado adiante. + Ou seja, ele retorna verdadeiro para indicar que você lidou com o evento e não deve seguir adiante; + ou retorna falso caso você não tenha lidado com ele e/ou o evento deva continuar para qualquer + outra escuta de tecla.
  • +
  • {@link android.view.View.OnTouchListener#onTouch(View,MotionEvent) onTouch()} - + Isto retorna um booleano para indicar se a escuta consome este evento. O importante é que este evento + pode possuir várias ações que se seguem mutuamente. Portanto, se retornar falso quando + o evento de ação inferior for recebido, você indicará que não consumiu o evento e que não está + interessado em ações subsequentes deste evento. Logo, você não será chamado para outras ações + dentro do evento, como um gesto de dedo ou um evento de ação para cima eventual.
  • +
+ +

Lembre-se de que os eventos de tecla de hardware sempre são entregues à vista atualmente em foco. Eles são enviados a partir da parte superior +da hierarquia de vistas e segue à parte inferior até atingir o destino adequado. Se a vista (ou um filho da vista) +estiver em foco, é possível ver o percurso do evento pelo método {@link android.view.View#dispatchKeyEvent(KeyEvent) +dispatchKeyEvent()}. Como uma alternativa para capturar eventos de tecla por meio da vista, também é possível +receber todos os eventos dentro da Atividade com {@link android.app.Activity#onKeyDown(int,KeyEvent) onKeyDown()} +e {@link android.app.Activity#onKeyUp(int,KeyEvent) onKeyUp()}.

+ +

Além disso, ao pensar sobre a entrada de texto para o aplicativo, lembre-se de que vários dispositivos possuem apenas +métodos de entrada de software. Tais métodos não precisam ser baseados em teclas; alguns podem usar entrada de texto por voz, por escrita e outros. Mesmo se um método de entrada +apresentar uma interface parecida com teclado, geralmente ele não ativa +a família de eventos {@link android.app.Activity#onKeyDown(int,KeyEvent) onKeyDown()}. Nunca deve-se compilar +uma IU que exija pressionamentos de teclas específicas para ser controlada, a não ser que você queira limitar o aplicativo a dispositivos +com um teclado físico. Em particular, não confie nestes métodos para validar a entrada quando o usuário pressiona a tecla +de retorno; em vez disso, use ações como {@link android.view.inputmethod.EditorInfo#IME_ACTION_DONE} para sinalizar +ao método de entrada como o aplicativo espera reagir para que ele possa alterar a IU de forma significativa. Evite suposições +sobre como um método de entrada de software deve funcionar e confie apenas no fornecimento do texto já formatado para o aplicativo.

+ +

Observação: o Android chamará manipuladores de evento e, em seguida, manipuladores adequados padrão +a partir da segunda definição de classe. Logo, retornar verdadeiro destas escutas de evento +interromperá a propagação do evento para outras escutas de evento e também bloqueará o retorno de chamada +para o manipulador de evento padrão na vista. Portanto, certifique-se de que quer encerrar o evento ao retornar verdadeiro.

+ + +

Manipuladores de evento

+ +

Se estiver compilando um componente personalizado a partir de View, então você poderá definir vários métodos de retorno de chamada +usados como manipuladores de evento padrão. +No documento sobre Componentes +personalizados, você aprenderá a ver alguns dos retornos de chamada usados para lidar com eventos, +incluindo:

+
    +
  • {@link android.view.View#onKeyDown} - Chamado quando um novo evento de tecla ocorre.
  • +
  • {@link android.view.View#onKeyUp} - Chamado quando um evento de tecla para cima ocorre.
  • +
  • {@link android.view.View#onTrackballEvent} - Chamado quando um evento de movimento do cursor de bola ocorre.
  • +
  • {@link android.view.View#onTouchEvent} - Chamado quando um evento de movimento de toque ocorre.
  • +
  • {@link android.view.View#onFocusChanged} - Chamado quando a vista ganha ou perde foco.
  • +
+

Há alguns outros métodos que você deve ter ciência que não fazem parte da classe View, +mas podem ter impacto direto na maneira de lidar com os eventos. Portanto, ao gerenciar eventos mais complexos +dentro de um layout, considere esses outros métodos:

+
    +
  • {@link android.app.Activity#dispatchTouchEvent(MotionEvent) + Activity.dispatchTouchEvent(MotionEvent)} - Isto permite que {@link + android.app.Activity} intercepte todos os evento de toque antes de serem enviados à janela.
  • +
  • {@link android.view.ViewGroup#onInterceptTouchEvent(MotionEvent) + ViewGroup.onInterceptTouchEvent(MotionEvent)} - Isto permite que {@link + android.view.ViewGroup} assista aos eventos à medida que são enviados para as vistas filho.
  • +
  • {@link android.view.ViewParent#requestDisallowInterceptTouchEvent(boolean) + ViewParent.requestDisallowInterceptTouchEvent(boolean)} - Chame isto + sobre uma Vista pai para indicar que ela não deve interceptar eventos de toque com {@link + android.view.ViewGroup#onInterceptTouchEvent(MotionEvent)}.
  • +
+ +

Modo de toque

+

+Quando um usuário está navegando em uma interface do usuário com teclas direcionais ou cursor de bola, +é necessário fornecer foco para itens de ação (como botões) para que o usuário possa +ver o que aceitará entrada. Se o dispositivo tiver capacidades de toque, no entanto, e o usuário +começar a interagir com a interface por meio de toque, então não é mais necessário +destacar itens ou fornecer foco para uma vista específica. Contudo, há um modo +de interação chamado "modo de toque". +

+

+Para dispositivos com capacidades de toque, quando o usuário toca na tela, o dispositivo +entra no modo de toque. A partir deste ponto, somente vistas que tiverem +{@link android.view.View#isFocusableInTouchMode} como verdadeiro poderão ter foco, como widgets de edição de texto. +Outras vistas tocáveis, como botões, não receberão foco ao serem tocadas. Em vez disso, +elas simplesmente dispararão escutas de clique quando forem pressionadas. +

+

+Sempre que um usuário pressionar teclas direcionais ou rolar com o cursor de bola, o dispositivo +sairá do modo de toque e encontrará uma vista para atribuir foco. Agora, o usuário pode retomar a interação +com a interface do usuário sem tocar na tela. +

+

+O estado de modo de toque é mantido em todo o sistema (todas as janelas e atividades). +Para consultar o estado atual, é possível chamar +{@link android.view.View#isInTouchMode} para ver se o dispositivo está no modo de toque no momento. +

+ + +

Tratamento de foco

+ +

A estrutura lidará com a rotina de movimento de foco em resposta à entrada do usuário. +Isto inclui a mudança de foco à medida que as vistas são removidas ou ocultadas, ou à medida que novas +vistas se tornem disponíveis. As vistas indicam a prontidão para receber foco +por meio do método {@link android.view.View#isFocusable()}. Para determinar se uma vista pode receber +foco, chame {@link android.view.View#setFocusable(boolean) setFocusable()}. Quando no modo de toque, +é possível consultar se uma vista permite foco com {@link android.view.View#isFocusableInTouchMode()}. +É possível alterar isto com {@link android.view.View#setFocusableInTouchMode(boolean) setFocusableInTouchMode()}. +

+ +

O movimento de foco é baseado em um algoritmo que encontra um semelhante mais próximo +em uma dada direção. Em casos raros, o algoritmo padrão pode não corresponder +ao comportamento pretendido do desenvolvedor. Nessas situações, é possível +fornecer substituições explícitas com os seguintes atributos XML no arquivo do layout: +nextFocusDown, nextFocusLeft, nextFocusRighte +nextFocusUp. Adicione um desses atributos à vista a partir +do foco que ela está abandonando. Defina o valor do atributo para ser o ID da vista +para o foco que deve ser fornecido. Por exemplo:

+
+<LinearLayout
+    android:orientation="vertical"
+    ... >
+  <Button android:id="@+id/top"
+          android:nextFocusUp="@+id/bottom"
+          ... />
+  <Button android:id="@+id/bottom"
+          android:nextFocusDown="@+id/top"
+          ... />
+</LinearLayout>
+
+ +

Geralmente, neste layout vertical, navegar para cima a partir do primeiro botão +não resultaria em nada, nem navegar para baixo a partir do segundo botão. Agora que o botão superior +definiu o botão do fundo como nextFocusUp (e vice-versa), o foco da navegação +alternará de "cima para baixo" e "baixo para cima".

+ +

Caso queira declarar uma vista como alvo de foco na IU (quando tradicionalmente não é), +adicione o atributo XML android:focusable à vista, na declaração do layout. +Defina o valor como verdadeiro. Também é possível declarar uma vista +como alvo de foco no Modo de Toque com android:focusableInTouchMode.

+

Para solicitar foco a uma determinada Vista, chame {@link android.view.View#requestFocus()}.

+

Para ouvir eventos de foco (receber notificações quando uma vista receber ou perder foco), use +{@link android.view.View.OnFocusChangeListener#onFocusChange(View,boolean) onFocusChange()}, +como discutido na seção Escutas de evento acima.

+ + + + diff --git a/docs/html-intl/intl/pt-br/sdk/index.jd b/docs/html-intl/intl/pt-br/sdk/index.jd new file mode 100644 index 0000000000000000000000000000000000000000..7570f4c0fa1b6ee2b0a7bbf80e0fc47a57a4d188 --- /dev/null +++ b/docs/html-intl/intl/pt-br/sdk/index.jd @@ -0,0 +1,487 @@ +page.title=Como baixar o Android Studio e o SDK Tools +page.tags=sdk, android studio +page.template=sdk +header.hide=1 +page.metaDescription=Baixar o Android IDE e ferramentas do desenvolvedor para compilar aplicativos para celulares, tablets, dispositivos de uso junto ao corpo, TVs e muito mais do Android. + +studio.version=1.1.0 + +studio.linux_bundle_download=android-studio-ide-135.1740770-linux.zip +studio.linux_bundle_bytes=259336386 +studio.linux_bundle_checksum=e8d166559c50a484f83ebfec6731cc0e3f259208 + +studio.mac_bundle_download=android-studio-ide-135.1740770-mac.dmg +studio.mac_bundle_bytes=261303345 +studio.mac_bundle_checksum=f9745d0fec1eefd498f6160a2d6a1b5247d4cda3 + +studio.win_bundle_exe_download=android-studio-bundle-135.1740770-windows.exe +studio.win_bundle_exe_bytes=856233768 +studio.win_bundle_exe_checksum=7484b9989d2914e1de30995fbaa97a271a514b3f + +studio.win_notools_exe_download=android-studio-ide-135.1740770-windows.exe +studio.win_notools_exe_bytes=242135128 +studio.win_notools_exe_checksum=5ea77661cd2300cea09e8e34f4a2219a0813911f + +studio.win_bundle_download=android-studio-ide-135.1740770-windows.zip +studio.win_bundle_bytes=261667054 +studio.win_bundle_checksum=e903f17cc6a57c7e3d460c4555386e3e65c6b4eb + + + +sdk.linux_download=android-sdk_r24.1.2-linux.tgz +sdk.linux_bytes=168121693 +sdk.linux_checksum=68980e4a26cca0182abb1032abffbb72a1240c51 + +sdk.mac_download=android-sdk_r24.1.2-macosx.zip +sdk.mac_bytes=89151287 +sdk.mac_checksum=00e43ff1557e8cba7da53e4f64f3a34498048256 + +sdk.win_download=android-sdk_r24.1.2-windows.zip +sdk.win_bytes=159778618 +sdk.win_checksum=704f6c874373b98e061fe2e7eb34f9fcb907a341 + +sdk.win_installer=installer_r24.1.2-windows.exe +sdk.win_installer_bytes=111364285 +sdk.win_installer_checksum=e0ec864efa0e7449db2d7ed069c03b1f4d36f0cd + + + + + + +@jd:body + + + + + + + +
+ + + + + + + + + +
+ +
 
+ + + +
+ +

Android Studio

+ +

O ambiente de desenvolvimento integrado (IDE) oficial do Android

+ +
    +
  • IDE do Android Studio
  • +
  • Ferramentas do Android SDK
  • +
  • Plataforma do Android 5.0 (Lollipop)
  • +
  • Imagem do sistema do emulador do Android 5.0 com APIs da Google
  • +
+ + +Baixar o Android Studio + + +

+Para obter o Android Studio ou a versão independente das ferramentas SDK, acesse developer.android.com/sdk/ +

+ + + +
+ + + + +

Editor de código inteligente

+ +
+ +
+ +
+

No núcleo do Android Studio, há um editor de código inteligente capaz de realizar, +refatorar e analisar códigos avançados.

+

O poderoso editor de código auxilia na maior produtividade do desenvolvedor de aplicativos Android.

+
+ + + + + +

Modelos de códigos e integração GitHub

+ +
+ +
+ +
+

Os novos assistentes de projeto facilitam iniciar um projeto como nunca antes.

+ +

Inicie projetos usando códigos de modelo para padrões como menus de navegação e ViewPagers, +e até importe exemplos de código da Google a partir do GitHub.

+
+ + + + +

Desenvolvimento de aplicativos multitelas

+ +
+ +
+ +
+

Programe aplicativos para celulares Android, tablets, Android Wear, +Android TV, Android Auto e Google Glass.

+

Com a nova visualização de projeto Android e a compatibilidade com módulos no Android Studio, +é mais fácil gerenciar projetos e recursos de aplicativos. +

+ + + + +

Dispositivos virtuais para todas as formas e tamanhos

+ +
+ +
+ +
+

O Android Studio já é pré-configurado com uma imagem do emulador otimizada.

+

O Gerenciador de dispositivos virtual atualizado e simplificado fornece +perfis de dispositivos pré-definidos para dispositivos Android comuns.

+
+ + + + +

+As versões do Android evoluíram com o Gradle

+ +
+ +
+ +
+

Crie diversos APKs para seu aplicativo Android com diferentes recursos usando o mesmo projeto.

+

Gerencie dependências de aplicativo com o Maven.

+

Crie APKs com o Android Studio ou com a linha de comando.

+
+ + + + +

Saiba mais sobre o Android Studio

+
+ +Baixe o Android Studio + +
    +
  • Programado no IntelliJ IDEA Edição de Comunidade, o popular IDE da Java da JetBrains.
  • +
  • Sistema flexível de programação baseado em Gradle.
  • +
  • Crie variantes e várias gerações de APK.
  • +
  • Compatibilidade com modelos do Google Services ampliada e com diversos tipos de dispositivos.
  • +
  • Editor de layout completo compatível com edição de tema.
  • +
  • Ferramentas de identificação de construção suspeita para identificar problemas de desempenho, usabilidade, compatibilidade de versão e outros problemas.
  • +
  • ProGuard e recursos de assinatura de aplicativo.
  • +
  • Compatibilidade embutida na Google Cloud Platform, facilitando a integração com o Google Cloud +Messasing e com o App Engine.
  • +
+ +

+Para obter mais detalhes sobre recursos disponíveis no Android Studio, +leia o guia Conceitos básicos do Android Studio.

+
+ + +

Se você usou o Eclipse com ADT, esteja ciente de que o Android Studio é agora o IDE oficial +para Android, portanto, é preciso migrar para o Android Studio para receber todas +as últimas atualizações do IDE. Para obter ajuda para mover projetos, +consulte Como migrar para o Android +Studio.

+ + + + + + + +

Requisitos do sistema

+ +

Windows

+ +
    +
  • Microsoft® Windows® 8/7/Vista/2003 (32 ou 64 bits)
  • +
  • Mínimo de 2 GB de RAM, 4 GB de RAM recomendado
  • +
  • Espaço de 400 MB no disco rígido
  • +
  • Pelo menos 1 GB para o Android SDK, imagens do sistema de emulador e caches
  • +
  • Resolução de tela de 1.280 x 800 no mínimo
  • +
  • Kit de desenvolvimento Java (JDK) 7
  • +
  • Opcional para emulador acelerado: Processador Intel® compatível com Intel® VT-x, Intel® EM64T +(Intel® 64) e Desativador de bit executável (XD)
  • +
+ + +

Mac OS X

+ +
    +
  • Mac® OS X® 10.8.5 ou posterior, até o 10.9 (Mavericks)
  • +
  • Mínimo de 2 GB de RAM, 4 GB de RAM recomendado
  • +
  • Espaço de 400 MB no disco rígido
  • +
  • Pelo menos 1 GB para o Android SDK, imagens do sistema de emulador e caches
  • +
  • Resolução de tela de 1.280 x 800 no mínimo
  • +
  • Ambiente de tempo de execução Java (JRE) 6
  • +
  • Kit de desenvolvimento Java (JDK) 7
  • +
  • Opcional para emulador acelerado: Processador Intel® compatível com Intel® VT-x, Intel® EM64T +(Intel® 64) e Desativador de bit executável (XD)
  • +
+ +

No Mac OS, execute o Android Studio com o Ambiente de tempo de execução Java (JRE) 6 para otimizar +a renderização de fontes. Você pode, então, configurar o projeto para usar o Kit de desenvolvimento Java (JDK) 6 ou o JDK 7.

+ + + +

Linux

+ +
    +
  • Área de trabalho GNOME ou KDE
  • +
  • Biblioteca GNU C (glibc) 2.15 ou posterior
  • +
  • Mínimo de 2 GB de RAM, 4 GB de RAM recomendado
  • +
  • Espaço de 400 MB no disco rígido
  • +
  • Pelo menos 1 GB para o Android SDK, imagens do sistema de emulador e caches
  • +
  • Resolução de tela de 1.280 x 800 no mínimo
  • +
  • Kit de desenvolvimento Oracle® Java (JDK) 7
  • +
+

Testado no Trusty Tahr do Ubuntu® 14.04 (distribuição de 64 bits capaz de executar +aplicativos de 32 bits).

+ + + + +

Outras opções de download

+ + diff --git a/docs/html-intl/intl/pt-br/sdk/installing/adding-packages.jd b/docs/html-intl/intl/pt-br/sdk/installing/adding-packages.jd new file mode 100644 index 0000000000000000000000000000000000000000..f7022a4eea513616324bcfd35aa75e13b0dd3a10 --- /dev/null +++ b/docs/html-intl/intl/pt-br/sdk/installing/adding-packages.jd @@ -0,0 +1,227 @@ +page.title=Como adicionar pacotes SDK + +page.tags=sdk manager +helpoutsWidget=true + +@jd:body + + + + +

+Por padrão, o Android SDK não inclui tudo que é necessário para começar a desenvolver. +O SDK separa ferramentas, plataformas e outros componentes em pacotes que podem ser +baixados conforme necessário usando o +Android SDK Manager. +Portanto, antes de iniciar, há alguns pacotes que você deve adicionar ao Android SDK.

+ +

Para começar a adicionar pacotes, execute o Android SDK Manager de uma das formas a seguir:

+
    +
  • No Android Studio, clique em SDK Manager + na barra de ferramentas.
  • +
  • Se não estiver usando o Android Studio: +
      +
    • Windows: Clique duas vezes no arquivo SDK Manager.exe na raiz do diretório do Android + SDK.
    • +
    • Mac/Linux: Abra um terminal e navegue para o diretório tools/ no + local em que o Android SDK foi instalado. Em seguida, execute android sdk.
    • +
    +
  • +
+ +

Ao abrir o SDK Manager pela primeira vez, vários pacotes são selecionados +por padrão. Deixe-os selecionados, mas certifique-se de ter tudo o que é necessário +para começar seguindo os seguintes passos:

+ + +
    +
  1. +

    Obtenha as ferramentas mais recentes do SDK

    + + + +

    No mínimo, ao configurar o Android SDK, + é preciso baixar as ferramentas mais recentes e a plataforma Android:

    +
      +
    1. Abra o diretório Tools e selecione: +
        +
      • Ferramentas do Android SDK
      • +
      • Ferramentas-plataforma Android SDK
      • +
      • Ferramentas-Android SDK Build (versão mais recente)
      • +
      +
    2. +
    3. Abra a pasta do primeiro Android X.X (a versão mais recente) e selecione: +
        +
      • Plataforma SDK
      • +
      • Uma imagem do sistema para o emulador, como
        + Imagem do sistema ARM EABI v7a
      • +
      +
    4. +
    +
  2. + +
  3. +

    Obtenha a biblioteca de suporte para APIs adicionais

    + + + +

    A Biblioteca de Suporte do Android + fornece um conjunto estendido de APIs compatíveis com a maioria das versões do Android.

    + +

    Abra o diretório Extras e selecione:

    +
      +
    • Repositório de Suporte do Android
    • +
    • Biblioteca de Suporte do Android
    • +
    + +

     

    +

     

    + +
  4. + + +
  5. +

    Obtenha os serviços do Google Play para obter ainda mais APIs

    + + + +

    Para desenvolver com as APIs do Google, você precisa do pacote de serviços do Google Play:

    +

    Abra o diretório Extras e selecione:

    +
      +
    • Repositório do Google
    • +
    • Serviços Google Play
    • +
    + +

    Observação: as APIs dos serviços do Google Play não estão disponíveis em todos os dispositivos + com Android, mas estão disponíveis em todos os dispositivos com Google Play Store. Para usar essas + APIs no emulador do Android, também é preciso instalar a imagem do sistema das APIs do Google + do diretório Android X.X mais recente no SDK Manager.

    +
  6. + + +
  7. +

    Instale os pacotes

    +

    Depois de selecionar todos os pacotes desejados, prossiga para a instalação:

    +
      +
    1. Clique em Install X packages (Instalar X pacotes).
    2. +
    3. Na janela seguinte, clique duas vezes no nome de cada pacote à esquerda + para aceitar o contrato de licença de cada um.
    4. +
    5. Clique em Install (Instalar).
    6. +
    +

    O andamento do download é exibido na parte inferior da tela do SDK Manager. + Não saia do SDK Manager, pois isso cancelará o download.

    +
  8. + +
  9. +

    Crie alguma coisa!

    + +

    Com os pacotes acima agora no seu Android SDK, você está pronto para criar aplicativos +para o Android. À medida que novas ferramentas e outras APIs forem disponibilizadas, basta executar o SDK Manager + para baixar os novos pacotes para o SDK.

    + +

    A seguir há algumas opções sobre como prosseguir:

    + +
    +
    +

    Introdução

    +

    Se você é novo no desenvolvimento para Android, aprenda os fundamentos de aplicativos para Android seguindo +o guia para Criação do primeiro aplicativo.

    + +
    +
    +

    Desenvolva para vestíveis

    +

    Se você está pronto para começar a desenvolver aplicativos para vestíveis Android, consulte o guia para +Criação de aplicativos para Android Wear.

    + +
    +
    +

    Use as APIs do Google

    +

    Para começar a usar as APIs do Google, como os serviços Maps ou +Play Game, consulte o guia para +Configuração dos serviços do +Google Play.

    + +
    +
    + + +
  10. + +
+ + diff --git a/docs/html-intl/intl/ru/guide/components/activities.jd b/docs/html-intl/intl/ru/guide/components/activities.jd new file mode 100644 index 0000000000000000000000000000000000000000..5f55a35213503fd533294699a9f6f92e5f080e2a --- /dev/null +++ b/docs/html-intl/intl/ru/guide/components/activities.jd @@ -0,0 +1,756 @@ +page.title=Операции +page.tags=операция,намерение +@jd:body + + + + + +

{@link android.app.Activity} — это компонент приложения, который выдает экран, + и с которым пользователи могут взаимодействовать для выполнения каких-либо действий, например набрать номер телефона, сделать фото, отправить письмо или +просмотреть карту. Каждой операции присваивается окно для прорисовки соответствующего пользовательского интерфейса. Обычно окно +отображается во весь экран, однако его размер может быть меньше, и оно может размещаться поверх других +окон.

+ +

Как правило, приложение состоит из нескольких операций, которые слабо +связаны друг с другом. Обычно одна из операций в приложении обозначается как «основная», +предлагаемая пользователю при первом запуске приложения. В свою очередь, каждая +операция может запустить другую операцию для выполнения различных действий. Каждый раз, когда +запускается новая операция, предыдущая операция останавливается, однако система сохраняет ее +в стеке («стек переходов назад»). При запуске новой операции она помещается в стек переходов назад и +отображается для пользователя. Стек переходов назад работает по принципу «последним вошёл — первым вышел», +поэтому после того как пользователь завершил текущую операцию и нажал кнопку Назад, текущая операция +удаляется из стека (и уничтожается), и возобновляется предыдущая операция. (Подробные сведения о стеке +переходов назад представлены в статье Задачи и стек +переходов назад.)

+ +

Когда операция останавливается по причине запуска новой операции, для уведомления об изменении ее состояния +используются методы обратного вызова жизненного цикла операции. +Существует несколько таких методов, которые может принимать операция вследствие изменения своего состояния +— создание операции, ее остановка, возобновление или уничтожение системой; также +каждый обратный вызов представляет возможность выполнить определенное действие, +подходящее для соответствующего изменения состояния. Например, в случае остановки операция должна освободить любые +крупные объекты, например, подключение к сети или базе данных. При возобновлении операции вы можете +повторно получить необходимые ресурсы и возобновить выполнение прерванных действий. Такие изменения состояния +являются частью жизненного цикла операции.

+ +

Далее в этом документе рассматриваются основы создания и использования операций, +включая полное описание жизненного цикла операции, чтобы вы могли лучше понять, как следует управлять +переходами между различными состояниями операции.

+ + + +

Создание операции

+ +

Чтобы создать операцию, сначала необходимо создать подкласс класса {@link android.app.Activity} (или +воспользоваться существующим его подклассом). В таком подклассе необходимо реализовать методы обратного вызова, которые +вызывает система при переходе операции из одного состояния своего жизненного цикла в другое, например при +создании, остановке, возобновлении или уничтожении операции. Вот два наиболее важных метода +обратного вызова:

+ +
+
{@link android.app.Activity#onCreate onCreate()}
+
Этот метод необходимо обязательно реализовать, поскольку система вызывает его при создании вашей +операции. В своей реализации вам необходимо инициализировать ключевые компоненты +операции. + Наиболее важно именно здесь вызвать {@link android.app.Activity#setContentView +setContentView()} для определения макета пользовательского интерфейса операции.
+
{@link android.app.Activity#onPause onPause()}
+
Система вызывает этот метод в качестве первого признака выхода пользователя из +операции (однако это не всегда означает, что операция будет уничтожена). Обычно именно здесь +необходимо применять любые изменения, которые должны быть сохранены помимо текущего сеанса работы пользователя (поскольку +пользователь может не вернуться назад).
+
+ +

Существуют также и некоторые другие методы обратного вызова жизненного цикла, которые необходимо использовать для того, чтобы обеспечить +плавный переход между операциями, а также для обработки непредвиденных нарушений в работе операции, в результате которых она может быть +остановлена или даже уничтожена. Более подробное описание всех методов обратного вызова жизненного цикла представлены в разделе, +посвященном управлению жизненным циклом операций.

+ + + +

Реализация пользовательского интерфейса

+ +

Для реализации пользовательского интерфейса операции используется иерархия представлений —объектов, полученных из +класса {@link android.view.View}. Каждое представление отвечает за определенную прямоугольную область +окна операции и может реагировать на действия пользователей. Например, представлением может быть +кнопка, нажатие на которую приводит к выполнению определенного действия.

+ +

В Android предусмотрен набор уже готовых представлений, которые можно использовать для создания дизайна макета и его +организации. Виджеты — это представления с визуальными (и интерактивными) элементами, например, +кнопками, текстовыми полями, чекбоксами или просто изображениями. Макеты — это представления, полученные из класса {@link +android.view.ViewGroup}, обеспечивающие уникальную модель компоновки для своих дочерних представлений, таких как линейный +макет, сетка или относительный макет. Также можно создать подкласс для классов {@link android.view.View} и +{@link android.view.ViewGroup} (или воспользоваться существующими подклассами), чтобы создать собственные виджеты и +макеты, и затем применить их к макету своей операции.

+ +

Чаще всего для задания макета с помощью представлений используется XML-файл макета, сохраненный в +ресурсах приложения. Таким образом вы можете хранить дизайн пользовательского интерфейса отдельно от +исходного кода, который служит для задания поведения операции. Чтобы задать макет в качестве пользовательского интерфейса +операции, можно использовать метод {@link android.app.Activity#setContentView(int) setContentView()}, передав в него +идентификатор ресурса для макета. Однако вы также можете создать новые {@link android.view.View} в коде вашей +операции и создать иерархию представлений. Для этого вставьте {@link +android.view.View} в {@link android.view.ViewGroup}, а затем используйте этот макет, передав корневой объект +{@link android.view.ViewGroup} в метод {@link android.app.Activity#setContentView(View) +setContentView()}.

+ +

Подробные сведения о создании пользовательского интерфейса см. в статье Пользовательский интерфейс.

+ + + +

Объявление операции в манифесте

+ +

Чтобы операция стала доступна системе, ее необходимо объявить в файле +манифеста. Для этого откройте файл манифеста и добавьте элемент {@code <activity>} в +качестве дочернего для элемента +{@code <application>}. Например:

+ +
+<manifest ... >
+  <application ... >
+      <activity android:name=".ExampleActivity" />
+      ...
+  </application ... >
+  ...
+</manifest >
+
+ +

Существует несколько других атрибутов, которые можно включить в этот элемент, чтобы определить такие +свойства, как метка операции, значок операции или тема оформления для пользовательского интерфейса операции. +Единственным обязательным атрибутом является {@code android:name} +— он определяет имя класса операции. После +публикации вашего приложения вам не следует переименовывать его, поскольку это может нарушить некоторые +функциональные возможности приложения, например, ярлыки приложения (ознакомьтесь с публикацией Вещи +, которые нельзя менять в блоге разработчиков).

+ +

Дополнительные сведения об объявлении операции +в манифесте см. в справке по элементу {@code <activity>}.

+ + +

Использование фильтров намерений

+ +

Элемент {@code +<activity>} также может задавать различные фильтры намерений — с помощью элемента {@code +<intent-filter>} — для объявления того, как другие компоненты приложения +могут активировать операцию.

+ +

При создании нового приложения с помощью инструментов Android SDK в заготовке операции, +создаваемой автоматически, имеется фильтр намерений, который объявляет операцию. +Эта операция реагирует на выполнение «основного» действия, и ее следует поместить в категорию переходсредства запуска. Фильтр намерений +выглядит следующим образом.

+ +
+<activity android:name=".ExampleActivity" android:icon="@drawable/app_icon">
+    <intent-filter>
+        <action android:name="android.intent.action.MAIN" />
+        <category android:name="android.intent.category.LAUNCHER" />
+    </intent-filter>
+</activity>
+
+ +

Элемент {@code +<action>} указывает, что это «основная» точка входа в приложение. Элемент {@code +<category>}указывает, что эту операцию следует указать в +средстве запуска приложений системы (чтобы пользователи могли запускать эту операцию).

+ +

Если приложение планируется создать самодостаточным и запретить другим приложениям активировать +его операции, то других фильтров намерений создавать не нужно. В этом случае только в одной операции +должно иметься «основное» действие, и ее следует поместить в категорию средства запуска, как в примере выше. В операциях, +которые не должны быть доступны для других приложений, не следует включать фильтры намерений. +Вы можете самостоятельно запустить такие операции с помощью явных намерений (как описывается в следующем разделе).

+ +

Однако, если вам необходимо, чтобы операция реагировала на неявные намерения, +получаемые от других приложений (а также из вашего приложения), для операции необходимо определить дополнительные фильтры +намерений. Для каждого типа намерения, на который необходимо реагировать, необходимо указать объект {@code +<intent-filter>}, включающий элемент +{@code +<action>} и необязательный элемент {@code +<category>} или {@code +<data>} (или оба этих элемента). Эти элементы определяют тип намерения, на который может реагировать +ваша операция.

+ +

Дополнительные сведения о том, как операции могут реагировать на намерения, приведены в статье Намерения и фильтры намерений. +

+ + + +

Запуск операции

+ +

Для запуска другой операции достаточно вызвать метод {@link android.app.Activity#startActivity +startActivity()}, передав в него объект {@link android.content.Intent}, который описывает запускаемую +операцию. В намерении указывается либо точная операция для запуска, либо описывается +тип операции, которую вы хотите выполнить (после чего система выбирает для вас подходящую +операцию, которая +может даже находиться в другом приложении). Намерение также может содержать небольшой объем данных, +которые будут использоваться запущенной операцией.

+ +

При работе с собственным приложением зачастую требуется лишь запустить нужную операцию. + Для этого необходимо создать намерение, которое явно определяет требуемую операцию +с помощью имени класса. Ниже представлен пример запуска одной операцией другой операции с именем {@code +SignInActivity}.

+ +
+Intent intent = new Intent(this, SignInActivity.class);
+startActivity(intent);
+
+ +

Однако в вашем приложении также может потребоваться выполнить некоторое действие, например, отправить письмо, текстовое +сообщение или обновить статус, используя данные из вашей операции. В этом случае в вашем приложении могут +отсутствовать такие действия, поэтому вы можете воспользоваться операциями +из других приложений, имеющихся на устройстве, которые могут выполнять требуемые действия. Как раз в этом случае +намерения особенно полезны — можно создать намерение, которое описывает необходимое действие, +после чего система +запускает его из другого приложения. При наличии +нескольких операций, которые могут обработать намерение, пользователь может выбирать, какое из них следует использовать. Например, +если пользователю требуется предоставить возможность отправить электронное письмо, можно создать +следующее намерение:

+ +
+Intent intent = new Intent(Intent.ACTION_SEND);
+intent.putExtra(Intent.EXTRA_EMAIL, recipientArray);
+startActivity(intent);
+
+ +

Дополнительный компонент {@link android.content.Intent#EXTRA_EMAIL}, добавленный в намерение, представляет собой строковый массив +адресов электронной почты для отправки письма. Когда почтовая программа реагирует на это +намерение, она считывает дополнительно добавленный строковый массив и помещает имеющиеся в нем адреса в поле получателя +в окне создания письма. При этом запускается операция почтовой программы, а после того, как +пользователь завершит требуемые действия, возобновляется ваша операция.

+ + + + +

Запуск операции для получения результата

+ +

В некоторых случаях после запуска операции может потребоваться получить результат. Для этого +вызовите метод {@link android.app.Activity#startActivityForResult +startActivityForResult()} (вместо {@link android.app.Activity#startActivity +startActivity()}). Чтобы получить результат после выполнения последующей +операции, реализуйте метод обратного +вызова {@link android.app.Activity#onActivityResult onActivityResult()}. По завершении последующей операции она возвращает результат в объекте {@link +android.content.Intent} +в вызванный метод {@link android.app.Activity#onActivityResult onActivityResult()}.

+ +

К примеру, пользователю потребуется выбрать один из контактов, чтобы ваша операция могла +выполнить некоторые действия с информацией об этом контакте. Ниже представлен пример создания такого намерения +и обработки результата.

+ +
+private void pickContact() {
+    // Create an intent to "pick" a contact, as defined by the content provider URI
+    Intent intent = new Intent(Intent.ACTION_PICK, Contacts.CONTENT_URI);
+    startActivityForResult(intent, PICK_CONTACT_REQUEST);
+}
+
+@Override
+protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+    // If the request went well (OK) and the request was PICK_CONTACT_REQUEST
+    if (resultCode == Activity.RESULT_OK && requestCode == PICK_CONTACT_REQUEST) {
+        // Perform a query to the contact's content provider for the contact's name
+        Cursor cursor = getContentResolver().query(data.getData(),
+        new String[] {Contacts.DISPLAY_NAME}, null, null, null);
+        if (cursor.moveToFirst()) { // True if the cursor is not empty
+            int columnIndex = cursor.getColumnIndex(Contacts.DISPLAY_NAME);
+            String name = cursor.getString(columnIndex);
+            // Do something with the selected contact's name...
+        }
+    }
+}
+
+ +

В этом примере демонстрируется базовая логика, которой следует руководствоваться при использовании метода {@link +android.app.Activity#onActivityResult onActivityResult()} для обработки результата +выполнения операции. Первое условие проверяет, успешен ли запрос, и если он успешен, то результат для +{@code resultCode} будет {@link android.app.Activity#RESULT_OK}; также проверяется, известен ли запрос, +для которого получен этот результат, и в этом случае {@code requestCode} соответствует +второму параметру, отправленному в метод {@link android.app.Activity#startActivityForResult +startActivityForResult()}. Здесь код обрабатывает результат выполнения операции путем запроса данных, +возвращенных в {@link android.content.Intent} (параметр {@code data}).

+ +

При этом {@link +android.content.ContentResolver} выполняет запрос к поставщику контента, который возвращает объект +{@link android.database.Cursor}, обеспечивающий считывание запрошенных данных. Дополнительные сведения представлены в статье +Поставщики контента.

+ +

Дополнительные сведения об использовании намерений см. в статье Намерения и фильтры +намерений.

+ + +

Завершение операции

+ +

Для завершения операции достаточно вызвать ее метод {@link android.app.Activity#finish +finish()}. Также для завершения отдельной операции, запущенной ранее, можно вызвать метод +{@link android.app.Activity#finishActivity finishActivity()}.

+ +

Примечание. В большинстве случаев вам не следует явно завершать операцию +с помощью этих методов. Как указано в следующем разделе, посвященном управлению жизненным циклом операции, система +Android выполняет такое управление за вас, поэтому вам не нужно завершать ваши +собственные операции. Вызов этих методов может отрицательно сказаться на ожидаемом +поведении приложения. Их следует использовать исключительно тогда, когда вы абсолютно не хотите, чтобы пользователь возвращался к этому +экземпляру операции.

+ + +

Управление жизненным циклом операций

+ +

Управление жизненным циклом операций путем реализации методов обратного вызова +имеет важное значение при разработке надежных +и гибких приложений. На жизненный цикл операции напрямую влияют его связи +с другими операциями, его задачами и стеком переходов назад.

+ +

Существует всего три состояния операции:

+ +
+
Возобновлена
+
Операция выполняется на переднем плане экрана и отображается для пользователя. (Это состояние +также иногда называется «Выполняется».)
+ +
Приостановлена
+
На переднем фоне выполняется другая операция, которая отображается для пользователя, однако эта операция по-прежнему не скрыта. То есть +поверх текущей операции отображается другая операция, частично прозрачная или не занимающая +полностью весь экран. Приостановленная операция полностью активна (объект {@link android.app.Activity} +по-прежнему находится в памяти, в нем сохраняются все сведения о состоянии и информация об элементах, и он также остается связанным с +диспетчером окон), однако в случае острой нехватки памяти система может завершить ее.
+ +
Остановлена
+
Операция полностью перекрывается другой операцией (теперь она выполняется в +фоновом режиме). Остановленная операция по-прежнему активна (объект {@link android.app.Activity} +по-прежнему находится в памяти, в нем сохраняются все сведения о состоянии и информация об элементах, но объект больше не +связан с диспетчером окон). Однако операция больше не видна пользователю, и в случае нехватки памяти система +может завершить ее.
+
+ +

Если операция приостановлена или полностью остановлена, система может очистить ее из памяти путем +завершения самой операции (с помощью метода {@link android.app.Activity#finish finish()}) или просто завершить ее +процесс. В случае повторного открытия операции (после ее завершения) ее потребуется создать +полностью.

+ + + +

Реализация обратных вызовов жизненного цикла

+ +

При переходе операции из одного вышеописанного состояния в другое, уведомления об этом +реализуются через различные методы обратного вызова. Все методы обратного вызова представляют собой привязки, которые +можно переопределить для выполнения подходящего действия при изменении состояния операции. Указанная ниже базовая +операция включает каждый из основных методов жизненного цикла.

+ + +
+public class ExampleActivity extends Activity {
+    @Override
+    public void {@link android.app.Activity#onCreate onCreate}(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        // The activity is being created.
+    }
+    @Override
+    protected void {@link android.app.Activity#onStart onStart()} {
+        super.onStart();
+        // The activity is about to become visible.
+    }
+    @Override
+    protected void {@link android.app.Activity#onResume onResume()} {
+        super.onResume();
+        // The activity has become visible (it is now "resumed").
+    }
+    @Override
+    protected void {@link android.app.Activity#onPause onPause()} {
+        super.onPause();
+        // Another activity is taking focus (this activity is about to be "paused").
+    }
+    @Override
+    protected void {@link android.app.Activity#onStop onStop()} {
+        super.onStop();
+        // The activity is no longer visible (it is now "stopped")
+    }
+    @Override
+    protected void {@link android.app.Activity#onDestroy onDestroy()} {
+        super.onDestroy();
+        // The activity is about to be destroyed.
+    }
+}
+
+ +

Примечание. При реализации этих методов жизненного цикла +всегда вызывайте реализацию суперкласса, прежде чем выполнять какие-либо действия, как показано в примерах выше.

+ +

Вместе все эти методы определяют весь жизненный цикл операции. С помощью реализации этих +методов можно отслеживать три вложенных цикла в жизненном цикле операции:

+ +
    +
  • Весь жизненный цикл операции происходит между вызовом метода {@link +android.app.Activity#onCreate onCreate()} и вызовом метода {@link +android.app.Activity#onDestroy}. Ваша операция должна выполнить настройку +«глобального» состояния (например, определение макета) в методе {@link android.app.Activity#onCreate onCreate()}, а затем +освободить все оставшиеся в {@link android.app.Activity#onDestroy} ресурсы. Например, если в вашей операции +имеется поток, выполняющийся в фоновом режиме, для загрузки данных по сети, операция может создать +такой поток в методе {@link android.app.Activity#onCreate onCreate()}, а затем остановить его в методе {@link +android.app.Activity#onDestroy}.
  • + +
  • Видимый жизненный цикл операции происходит между вызовами методов {@link +android.app.Activity#onStart onStart()} и {@link +android.app.Activity#onStop onStop()}. В течение этого времени операция +отображается на экране, где пользователь может взаимодействовать с ней. Например, метод {@link android.app.Activity#onStop onStop()} вызывается в +случае, когда запускается новая операция, а текущая больше не отображается. В промежутке между вызовами этих двух методов можно +сохранить ресурсы, необходимые для отображения операции для пользователя. Например, можно зарегистрировать объект +{@link android.content.BroadcastReceiver} в методе {@link +android.app.Activity#onStart onStart()} для отслеживания изменений, влияющих на пользовательский интерфейс, а затем отменить его регистрацию +в методе {@link android.app.Activity#onStop onStop()}, когда пользователь больше не видит +отображаемого. В течение всего жизненного цикла операции система может несколько раз вызывать методы {@link android.app.Activity#onStart onStart()} и {@link +android.app.Activity#onStop onStop()}, поскольку +операция то отображается для пользователя, то скрывается от него.

  • + +
  • Жизненный цикл операции, выполняемый на переднем плане, происходит между вызовами методов {@link +android.app.Activity#onResume onResume()} и {@link android.app.Activity#onPause +onPause()}. В течение этого времени операция выполняется на фоне всех прочих операций и +отображается для пользователя. Операция может часто уходить в фоновый режим и выходить из него — например, +метод {@link android.app.Activity#onPause onPause()} вызывается при переходе устройства в спящий режим или +при появлении диалогового окна. Поскольку переход в это состояние может выполняться довольно часто, код в этих двух методах +должен быть легким, чтобы не допустить медленных переходов и не заставлять пользователя ждать.

  • +
+ +

На рисунке 1 иллюстрируются проходы и пути, которые операция может пройти между состояниями. +Прямоугольниками обозначены методы обратного вызова, которые можно реализовать для выполнения действий между переходами операции из одного +состояния в другое.

+ + +

Рисунок 1. Жизненный цикл операции.

+ +

Эти же методы жизненного цикла перечислены в таблице 1, в которой подробно описан каждый метод +обратного вызова и указано его место в +жизненном цикле операции в целом, включая сведения о том, может ли система завершить операцию по завершении +метода обратного вызова.

+ +

Таблица 1. Сводные сведения о методах обратного вызова +жизненного цикла операции.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Метод Описание Завершаемый? Следующий
{@link android.app.Activity#onCreate onCreate()}Вызывается при первом создании операции. + Здесь необходимо настроить все обычные статические элементы — +создать представления, привязать данные и т. д. Этот метод передает +объект Bundle, содержащий предыдущее состояние операции (если +такое состояние было зафиксировано ранее; см. раздел Сохранение состояния +операции). +

За ним всегда следует метод {@code onStart()}.

Нет{@code onStart()}
    {@link android.app.Activity#onRestart +onRestart()}Вызывается после остановки операции непосредственно перед ее +повторным запуском. +

За ним всегда следует метод {@code onStart()}.

Нет{@code onStart()}
{@link android.app.Activity#onStart onStart()}Вызывается непосредственно перед тем, как операция становится видимой для пользователя. +

За ним следует метод {@code onResume()}, если операция +переходит на передний план, или метод {@code onStop()}, если она становится скрытой.

Нет{@code onResume()}
или
{@code onStop()}
    {@link android.app.Activity#onResume onResume()}Вызывается непосредственно перед тем, как операция +начинает взаимодействие с пользователем. На этом этапе операция находится в самом +верху стека операций, и в нее поступают данные, вводимые пользователем. +

За ним всегда следует метод {@code onPause()}.

Нет{@code onPause()}
{@link android.app.Activity#onPause onPause()}Вызывается, когда система собирается возобновить +другую операцию. Этот метод обычно используется для записи несохраненных изменений +в постоянное место хранения данных, остановки анимаций и других элементов, которые могут +использовать ресурсы ЦП и т. д. Здесь крайне важна оперативность, поскольку +следующая операция не будет возобновлена до тех пор, пока она не будет возвращена на передний план. +

За ним следует либо метод {@code onResume()}, если операция +возвращается на передний план, либо метод {@code onStop()}, если операция +становится скрытой для пользователя.

Да{@code onResume()}
или
{@code onStop()}
{@link android.app.Activity#onStop onStop()}Вызывается в случае, когда операция больше не отображается для пользователя. Это может +произойти по причине того, что операция уничтожена, или ввиду возобновления поверх нее другой операции +(существующей или новой). +

За ним следует либо метод {@code onRestart()}, если +операция возобновляет взаимодействие с пользователем, либо метод +{@code onDestroy()}, если операция переходит в фоновый режим.

Да{@code onRestart()}
или
{@code onDestroy()}
{@link android.app.Activity#onDestroy +onDestroy()}Вызывается перед тем, как операция будет уничтожена. Это финальный вызов, +который получает операция. Его можно вызвать либо по причине +завершения операции (вызов метода {@link android.app.Activity#finish + finish()}), либо ввиду временного уничтожения системой этого +экземпляра операции с целью освободить место. Чтобы различить эти два +сценария, используется метод {@link + android.app.Activity#isFinishing isFinishing()}.ДаНичего
+ +

В столбце «Завершаемый?» указывается, может ли система +в любое время завершить процесс, содержащий операцию, после возвращения метода без +выполнения другой строки кода операции. Для трех методов в этом столбце указано «Да»: ({@link +android.app.Activity#onPause +onPause()}, {@link android.app.Activity#onStop onStop()} и {@link android.app.Activity#onDestroy +onDestroy()}). Поскольку метод {@link android.app.Activity#onPause onPause()} является первым из +этих трех после создания операции, метод {@link android.app.Activity#onPause onPause()} является +последним, который гарантированно будет вызван перед тем, как процесс можно будет завершить; если +системе потребуется срочно восстановить память в случае аварийной ситуации, то методы {@link +android.app.Activity#onStop onStop()} и {@link android.app.Activity#onDestroy onDestroy()} вызвать +не удастся. Поэтому следует воспользоваться {@link android.app.Activity#onPause onPause()}, чтобы записать +критически важные данные (такие как правки пользователя) в хранилище постоянных данных. Однако следует внимательно подходить к выбору информации, +которую необходимо сохранить во время выполнения метода {@link android.app.Activity#onPause onPause()}, поскольку любая блокировка +процедур в этом методе может вызвать блокирование перехода к следующей операции и тормозить работу +пользователя.

+ +

Методы, для которых в столбце Завершаемый? указано «Нет», защищают процесс, содержащий операцию +, от завершения сразу с момента их вызова. Поэтому завершить операцию +можно в период между возвратом {@link android.app.Activity#onPause onPause()} и вызовом +{@link android.app.Activity#onResume onResume()}. Его снова не удастся завершить, пока снова не будет вызван и возвращен +{@link android.app.Activity#onPause onPause()}.

+ +

Примечание. Операцию, которую технически невозможно завершить в соответствии с определением +в таблице 1, по-прежнему может завершить система, однако это может произойти только в +чрезвычайных ситуациях, когда нет другой возможности. Случаи, когда возможно завершение операции, +более подробно рассматриваются в статьеПроцессы и +потоки.

+ + +

Сохранение состояния операции

+ +

В обзорных сведениях об управлении жизненным циклом операции кратко упоминается, +что +в случае приостановки или полной остановки операции ее состояние сохраняется. Это действительно так, поскольку +объект {@link android.app.Activity} при этом по-прежнему находится в памяти +, и вся информация о ее элементах и текущем состоянии по-прежнему активна. Поэтому любые +вносимые пользователем в операции изменения сохраняются, и когда операция возвращается на передний план +(когда она «возобновляется»), эти изменения остаются в этом объекте.

+ +

Однако когда система уничтожает операцию в целях восстановления памяти, объект {@link +android.app.Activity} уничтожается, в результате чего системе не удается просто восстановить состояние операции для взаимодействия +с ней. Вместо этого системе необходимо повторно создать объект {@link android.app.Activity}, если пользователь +возвращается к нему. Но пользователю неизвестно, +что система уже уничтожила операцию и создала ее повторно, поэтому, возможно, +он ожидает, что операция осталась прежней. В этой ситуации можно обеспечить +сохранение важной информации о состоянии операции путем реализации дополнительного +метода обратного вызова, который позволяет сохранить информацию о вашей операции: {@link +android.app.Activity#onSaveInstanceState onSaveInstanceState()}.

+ +

Прежде чем сделать операцию доступной для уничтожения, система вызывает метод +{@link android.app.Activity#onSaveInstanceState onSaveInstanceState()}. Система передает в этот метод объект +{@link android.os.Bundle}, в котором можно сохранить +информацию о состоянии операции в виде пар «имя-значение», используя для этого такие методы, как {@link +android.os.Bundle#putString putString()} и {@link +android.os.Bundle#putInt putInt()}. Затем, если система завершает процесс +вашего приложения и пользователь возвращается к вашей операции, система повторно создает операцию и передает объект +{@link android.os.Bundle} в оба метода: {@link android.app.Activity#onCreate onCreate()} и {@link +android.app.Activity#onRestoreInstanceState onRestoreInstanceState()}. С помощью любого из этих +методов можно извлечь из объекта {@link android.os.Bundle} сохраненную информацию о состоянии операции и восстановить +ее. Если такая информация отсутствует, то объект {@link +android.os.Bundle} передается с нулевым значением (это происходит в случае, когда операция +создается в первый раз).

+ + +

Рисунок 2. Два способа возврата операции к отображению для пользователя +в неизмененном состоянии: уничтожение операции с последующим ее повторным созданием, когда операция должна восстановить свое +ранее сохраненное состояние, или остановка операции и ее последующее восстановление в неизмененном +состоянии.

+ +

Примечание. Нет никаких гарантий, что метод {@link +android.app.Activity#onSaveInstanceState onSaveInstanceState()} будет вызван до того, как ваша +операция будет уничтожена, поскольку существуют случаи, когда нет необходимости сохранять состояние +(например, когда пользователь покидает вашу операцию нажатием кнопки Назад, +явным образом +закрывая ее). Если система вызывает метод {@link android.app.Activity#onSaveInstanceState +onSaveInstanceState()}, она делает это до вызова метода {@link +android.app.Activity#onStop onStop()} и, возможно, перед вызовом метода {@link android.app.Activity#onPause +onPause()}.

+ +

Однако, даже если вы ничего не предпринимаете и не реализуете метод {@link +android.app.Activity#onSaveInstanceState onSaveInstanceState()}, часть состояния операции восстанавливается +реализацией по умолчанию метода {@link +android.app.Activity#onSaveInstanceState onSaveInstanceState()} класса {@link android.app.Activity}. В частности, реализация +по умолчанию вызывает соответствующий метод {@link +android.view.View#onSaveInstanceState onSaveInstanceState()} для каждого объекта {@link +android.view.View} в макете, благодаря чему каждое представление может предоставлять ту информацию о себе, +которую следует сохранить. Почти каждый виджет в платформе Android реализует этот метод необходимым +для себя способом так, что любые видимые изменения в пользовательском интерфейсе автоматически сохраняются и восстанавливаются при повторном +создании операции. Например, виджет {@link android.widget.EditText} сохраняет любой текст, +введенный пользователем, а виджет {@link android.widget.CheckBox} сохраняет информацию о том, был +ли установлен флажок. От вас требуется лишь указать уникальный идентификатор (с атрибутом {@code android:id}) +для каждого виджета, состояние которого необходимо сохранить. Если виджету не присвоен идентификатор, то системе +не удастся сохранить его состояние.

+ + + +

Несмотря на то что реализация метода {@link +android.app.Activity#onSaveInstanceState onSaveInstanceState()} по умолчанию позволяет сохранить полезную информацию о +пользовательском интерфейсе вашей операции, вам по-прежнему может потребоваться переопределить ее для сохранения дополнительной информации. +Например, может потребоваться сохранить значения элементов, которые изменялись в течение жизненного цикла операции (которые могут +коррелировать со значениями, восстановленными в пользовательском интерфейсе, однако элементы, содержащие эти значения пользовательского интерфейса, по умолчанию не +были восстановлены).

+ +

Поскольку реализация метода {@link +android.app.Activity#onSaveInstanceState onSaveInstanceState()} по умолчанию позволяет сохранить состояние пользовательского интерфейса, в случае +, если вы переопределите метод с целью сохранить дополнительную информацию о состоянии, перед выполнением каких-либо действий вы всегда можете вызвать реализацию +суперкласса для метода +{@link android.app.Activity#onSaveInstanceState onSaveInstanceState()}. Точно так же реализацию суперкласса {@link +android.app.Activity#onRestoreInstanceState onRestoreInstanceState()} следует вызывать в случае ее переопределения, чтобы реализация по умолчанию могла сохранить +состояния представлений.

+ +

Примечание. Поскольку вызов метода {@link android.app.Activity#onSaveInstanceState +onSaveInstanceState()} не гарантируется, +вам следует использовать его только для записи переходного состояния операции (состояние +пользовательского интерфейса) — никогда не используйте его для хранения постоянных данных. Вместо этого используйте метод {@link +android.app.Activity#onPause onPause()} для сохранения постоянных данных (например, тех, которые следует сохранить в +базу данных), когда пользователь покидает операцию.

+ +

Отличный способ проверить возможность вашего приложения восстанавливать свое состояние — это просто повернуть +устройство для изменения ориентации экрана. При изменении ориентации экрана система +уничтожает и повторно создает операцию, чтобы применить альтернативные ресурсы, которые могут быть +доступны для новой конфигурации экрана. Только по одной этой причине крайне важно, чтобы ваша операция +могла полностью восстанавливать свое состояние при ее повторном создании, поскольку пользователи постоянно работают с +приложениями в разных ориентациях экрана.

+ + +

Обработка изменений в конфигурации

+ +

Некоторые конфигурации устройств могут изменяться в режиме выполнения +(например, ориентация экрана, доступность клавиатуры и язык). В таких случаях Android повторно создает выполняющуюся операцию +(система сначала вызывает метод {@link android.app.Activity#onDestroy}, а затем сразу же вызывает метод {@link +android.app.Activity#onCreate onCreate()}). Такое поведение позволяет +приложению учитывать новые конфигурации путем автоматической перезагрузки в приложение +альтернативных ресурсов, которые вы предоставили (например, различные макеты для +разных ориентаций и экранов разных размеров).

+ +

Если операция разработана должным образом и должным образом поддерживает перезапуск после изменения ориентации экрана и +восстановление своего состояния, как описано выше, ваше приложение можно считать более устойчивым к другим +непредвиденным событиям в жизненном цикле операции.

+ +

Лучший способ обработки такого перезапуска +— сохранить и восстановить состояние операции с помощью методов {@link +android.app.Activity#onSaveInstanceState onSaveInstanceState()} и {@link +android.app.Activity#onRestoreInstanceState onRestoreInstanceState()} (или {@link +android.app.Activity#onCreate onCreate()}), как описано в предыдущем разделе.

+ +

Дополнительные сведения об изменениях конфигурации, происходящих в режиме выполнения, и о способах их обработки +представлены в руководствеОбработка изменений в режиме +выполнения.

+ + + +

Согласование операций

+ +

Когда одна операция запускает другую, в жизненных циклах обеих из них происходит переход из одного состояния в другое. Первая операция +приостанавливается и заврешается (однако она не будет остановлена, если она по-прежнему видима на фоне), а вторая +операция создается. В случае, если эти операции обмениваются данным, сохраненными на диске или в другом месте, важно понимать, +что первая операция не останавливается полностью до тех пор, пока не будет создана вторая операция. +Наоборот, процесс запуска второй операции накладывается на процесс остановки первой +операции.

+ +

Порядок обратных вызовов жизненного цикла четко определен, в частности, когда в одном и том же процессе находятся две операции +, и одна из них запускает другую. Ниже представлен порядок выполнения действий в случае, когда операция +А запускает операцию Б.

+ +
    +
  1. Выполняется метод {@link android.app.Activity#onPause onPause()} операции А.
  2. + +
  3. Последовательно выполняются методы {@link android.app.Activity#onCreate onCreate()}, {@link +android.app.Activity#onStart onStart()} и {@link android.app.Activity#onResume onResume()} +операции Б. (Теперь для пользователя отображается операция Б.)
  4. + +
  5. Затем, если операция A больше не отображается на экране, выполняется ее метод {@link +android.app.Activity#onStop onStop()}.
  6. +
+ +

Такая предсказуемая последовательность выполнения обратных вызовов жизненного цикла позволяет управлять переходом +информации из одной операции в другую. Например, если после остановки первой операции требуется выполнить запись в базу данных, +чтобы следующая операция могла считать их, то запись в базу данных следует выполнить +во время выполнения метода {@link android.app.Activity#onPause onPause()}, а не во время выполнения метода {@link +android.app.Activity#onStop onStop()}.

+ + diff --git a/docs/html-intl/intl/ru/guide/components/bound-services.jd b/docs/html-intl/intl/ru/guide/components/bound-services.jd new file mode 100644 index 0000000000000000000000000000000000000000..ad690b776ef07c419618ba636e1072421f882831 --- /dev/null +++ b/docs/html-intl/intl/ru/guide/components/bound-services.jd @@ -0,0 +1,658 @@ +page.title=Привязанные службы +parent.title=Службы +parent.link=services.html +@jd:body + + +
+
    +

    Содержание документа

    +
      +
    1. Основы
    2. +
    3. Создание привязанной службы +
        +
      1. Расширение класса Binder
      2. +
      3. Использование объекта Messenger
      4. +
      +
    4. +
    5. Привязка к службе
    6. +
    7. Управление жизненным циклом привязанной службы
    8. +
    + +

    Ключевые классы

    +
      +
    1. {@link android.app.Service}
    2. +
    3. {@link android.content.ServiceConnection}
    4. +
    5. {@link android.os.IBinder}
    6. +
    + +

    Примеры

    +
      +
    1. {@code + RemoteService}
    2. +
    3. {@code + LocalService}
    4. +
    + +

    См. также:

    +
      +
    1. Службы
    2. +
    +
+ + +

Привязанная служба предоставляет интерфейс типа клиент-сервер. Привязанная служба позволяет компонентам приложения +(например, операциям) взаимодействовать со службой, отправлять запросы, получать результаты и даже делать то же самое с другими процессами через +IPC. Привязанная служба обычно работает, пока другой компонент приложения +привязан к ней. Она не работает постоянно в фоновом режиме.

+ +

В этом документе рассказывается, как создать привязанную службу, включая привязку +службы к другим компонентам приложения. Также рекомендуем обратиться к статье Службы, чтобы узнать подробнее +о службах, например, об организации отправки уведомлений от службы, настройке службы +на работу на переднем плане и т. д.

+ + +

Основы

+ +

Привязанная служба представляет собой реализацию класса {@link android.app.Service}, которая позволяет +другим приложениям привязываться к нему и взаимодействовать с ним. Чтобы обеспечить привязку службы +, сначала необходимо реализовать метод обратного вызова {@link android.app.Service#onBind onBind()}. Этот +метод возвращает объект {@link android.os.IBinder}. Он определяет программный интерфейс, +с помощью которого клиенты могут взаимодействовать со службой.

+ + + +

Для привязки к службе клиент может вызвать метод {@link android.content.Context#bindService +bindService()}. После привязки он должен предоставить реализацию метода {@link +android.content.ServiceConnection}, который служит для отслеживания подключения к службе. Метод {@link +android.content.Context#bindService bindService()} возвращается незамедлительно без значения, однако +, когда система Android устанавливает подключение +клиент-служба, она вызывает метод {@link +android.content.ServiceConnection#onServiceConnected onServiceConnected()} для {@link +android.content.ServiceConnection}, чтобы выдать объект {@link android.os.IBinder}, который +клиент может использовать для взаимодействия со службой.

+ +

Одновременно к службе могут подключиться сразу несколько клиентов. Однако система вызывает метод +{@link android.app.Service#onBind onBind()} вашей службы для получения объекта {@link android.os.IBinder} +только при первой привязке клиента. После чего система выдает такой же объект {@link android.os.IBinder} для любых +дополнительных клиентов, которые выполняют привязку, без повторного вызова метода {@link android.app.Service#onBind onBind()}.

+ +

Когда отменяется привязка последнего клиента от службы, система уничтожает службу (если только +служба не была так же запущена методом {@link android.content.Context#startService startService()}).

+ +

Самую важную роль в реализации привязанной службы играет определение интерфейса, +который возвращает ваш метод обратного вызова {@link android.app.Service#onBind onBind()}. Существует несколько +различных способов определения интерфейса {@link android.os.IBinder} службы. Каждый из них рассматривается в следующем +разделе.

+ + + +

Создание привязанной службы

+ +

При создании службы, обеспечивающей привязку, требуется объект {@link android.os.IBinder}, +который обеспечивает программный интерфейс, с помощью которого клиенты могут взаимодействовать со службой. Существует +три способа определения такого интерфейса:

+ +
+
Расширение класса Binder
+
Если служба является частной и предоставляется в рамках вашего собственного приложения, а также выполняется в том же процессе, что и клиент +(общий процесс), создавать интерфейс следует путем расширения класса {@link android.os.Binder} +и возврата его экземпляра из метода +{@link android.app.Service#onBind onBind()}. Клиент получает объект {@link android.os.Binder}, +после чего он может использовать его для получения прямого доступа к общедоступным методам, имеющимся либо в реализации {@link android.os.Binder}, +либо даже в {@link android.app.Service}. +

Этот способ является предпочтительным, когда служба просто выполняется в фоновом режиме для +вашего приложения. Этот способ не подходит для создания интерфейса только тогда, +когда ваша служба используется другими приложениями или в отдельных процессах.

+ +
Использование объекта Messenger
+
Если необходимо, чтобы интерфейс службы был доступен для разных процессов, его можно создать +с помощью объекта {@link android.os.Messenger}. Таким образом, служба +определяет объект {@link android.os.Handler}, соответствующий различным типам объектов {@link +android.os.Message}. Этот объект {@link android.os.Handler} +является основой для объекта {@link android.os.Messenger}, который, в свою очередь, предоставляет клиенту объект {@link android.os.IBinder}, +благодаря чему последний может отправлять команды в службу с помощью объектов {@link +android.os.Message}. Кроме того, клиент может определить объект {@link android.os.Messenger} для самого +себя, что позволяет службе возвращать сообщения клиенту. +

Это самый простой способ организовать взаимодействие процессов, поскольку {@link +android.os.Messenger} организует очередь всех запросов в рамках одного потока, поэтому вам не нужно делать +свою службу потокобезопасной.

+
+ +
Использование языка AIDL
+
AIDL (Android Interface Definition Language) выполняет всю работу по разделению объектов на +примитивы, которые операционная система может распознать и распределить по процессам для организации +взаимодействия между ними (IPC). Предыдущий способ с использованием объекта {@link android.os.Messenger} фактически основан на AIDL, поскольку это его +базовая структура. Как уже упоминалось выше, объект {@link android.os.Messenger} создает очередь из всех +запросов клиентов в рамках одного потока, поэтому служба одновременно получает только один запрос. Однако, +если необходимо, чтобы служба обрабатывала одновременно сразу несколько запросов, можно использовать AIDL +напрямую. В таком случае ваша служба должна поддерживать многопоточность и должна быть потокобезопасной. +

Чтобы использовать AIDL напрямую, необходимо +создать файл {@code .aidl}, который определяет программный интерфейс. Этот файл используется инструментами SDK Android для +создания абстрактного класса, который реализует интерфейс и обеспечивает взаимодействие процессов, и который +в дальнейшем можно расширить в службе.

+
+
+ +

Примечание. В большинстве приложений не следует использовать AIDL для +создания и привязки службы, поскольку для этого может потребоваться поддержка многопоточности, что, в свою очередь, может привести +к более сложной реализации. Поэтому AIDL не подходит для большинства приложений, +и в этой статье мы не будем рассматривать использование этого способа для вашей службы. Если же вы точно уверены, что +вам необходимо использовать AIDL напрямую, обратитесь к статье +AIDL.

+ + + + +

Расширение класса Binder

+ +

Если ваша служба используется только локальным приложением и не взаимодействует с разными процессами, +можно реализовать собственный класс {@link android.os.Binder}, с помощью которого клиент получает прямой +доступ к общедоступным методам в службе.

+ +

Примечание. Этот вариант подходит только в том случае, если клиент и служба выполняются внутри +одного приложения и процесса, что является наиболее распространенной ситуацией. Например, расширение класса отлично подойдет для +музыкального приложения, в котором необходимо привязать операцию к собственной службе приложения, которая +воспроизводит музыку в фоновом режиме.

+ +

Вот как это сделать:

+
    +
  1. Создайте в вашей службе экземпляр класса {@link android.os.Binder} со следующими характеристиками: +
      +
    • экземпляр содержит общедоступные методы, которые может вызывать клиент; либо
    • +
    • экземпляр возвращает текущий экземпляр класса {@link android.app.Service}, содержащий +общедоступные методы, которые может вызывать клиент; или
    • +
    • экземпляр возвращает экземпляр другого класса, размещенного в службе, содержащий общедоступные методы, +которые может вызывать клиент.
    • +
    +
  2. Верните этот экземпляр класса {@link android.os.Binder} из метода обратного вызова {@link +android.app.Service#onBind onBind()}.
  3. +
  4. В клиенте получите класс {@link android.os.Binder} от метода обратного вызова {@link +android.content.ServiceConnection#onServiceConnected onServiceConnected()} и +выполните вызовы к привязанной службе с помощью предоставленных методов.
  5. +
+ +

Примечание. Служба и клиент должны выполняться в одном и том же приложении +, поскольку в этом случае клиент может транслировать возвращенный объект и надлежащим образом вызывать его API-интерфейсы. Кроме того, служба +и клиент должны выполняться в рамках одного и того же процесса, поскольку этот способ не подразумевает какого-либо +распределения по процессам.

+ +

Ниже представлен пример службы, которая предоставляет клиентам доступ к методам посредством реализации класса +{@link android.os.Binder}:

+ +
+public class LocalService extends Service {
+    // Binder given to clients
+    private final IBinder mBinder = new LocalBinder();
+    // Random number generator
+    private final Random mGenerator = new Random();
+
+    /**
+     * Class used for the client Binder.  Because we know this service always
+     * runs in the same process as its clients, we don't need to deal with IPC.
+     */
+    public class LocalBinder extends Binder {
+        LocalService getService() {
+            // Return this instance of LocalService so clients can call public methods
+            return LocalService.this;
+        }
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return mBinder;
+    }
+
+    /** method for clients */
+    public int getRandomNumber() {
+      return mGenerator.nextInt(100);
+    }
+}
+
+ +

Объект {@code LocalBinder} предоставляет для клиентов метод {@code getService()}, чтобы они могли получить +текущий экземпляр класса {@code LocalService}. Благодаря этому клиенты могут вызывать общедоступные методы в +службе. Например, клиенты могут вызвать метод {@code getRandomNumber()} из службы.

+ +

Ниже представлен пример операции, которая выполняет привязку к классу {@code LocalService} и вызывает метод {@code getRandomNumber()} +при нажатии кнопки:

+ +
+public class BindingActivity extends Activity {
+    LocalService mService;
+    boolean mBound = false;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.main);
+    }
+
+    @Override
+    protected void onStart() {
+        super.onStart();
+        // Bind to LocalService
+        Intent intent = new Intent(this, LocalService.class);
+        bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
+    }
+
+    @Override
+    protected void onStop() {
+        super.onStop();
+        // Unbind from the service
+        if (mBound) {
+            unbindService(mConnection);
+            mBound = false;
+        }
+    }
+
+    /** Called when a button is clicked (the button in the layout file attaches to
+      * this method with the android:onClick attribute) */
+    public void onButtonClick(View v) {
+        if (mBound) {
+            // Call a method from the LocalService.
+            // However, if this call were something that might hang, then this request should
+            // occur in a separate thread to avoid slowing down the activity performance.
+            int num = mService.getRandomNumber();
+            Toast.makeText(this, "number: " + num, Toast.LENGTH_SHORT).show();
+        }
+    }
+
+    /** Defines callbacks for service binding, passed to bindService() */
+    private ServiceConnection mConnection = new ServiceConnection() {
+
+        @Override
+        public void onServiceConnected(ComponentName className,
+                IBinder service) {
+            // We've bound to LocalService, cast the IBinder and get LocalService instance
+            LocalBinder binder = (LocalBinder) service;
+            mService = binder.getService();
+            mBound = true;
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName arg0) {
+            mBound = false;
+        }
+    };
+}
+
+ +

В примере выше показано, как клиент привязывается к службе с помощью реализации +{@link android.content.ServiceConnection} и обратного вызова {@link +android.content.ServiceConnection#onServiceConnected onServiceConnected()}. В +следующем разделе представлена более подробная информация об этом процессе привязки к службе.

+ +

Примечание. В примере выше не выполняется явная отмена привязки к службе, +однако всем клиентам следует отменять привязку в соответствующие сроки (например, когда операция приостанавливается).

+ +

Примеры кода представлены в статьях, посвященных классам {@code +LocalService.java} и {@code +LocalServiceActivities.java}, в ApiDemos.

+ + + + + +

Использование объекта Messenger

+ + + +

Если необходимо, чтобы служба взаимодействовала с удаленными процессами, для предоставления интерфейса службы можно воспользоваться объектом +{@link android.os.Messenger}. Такой подход +позволяет организовать взаимодействие между процессами (IPC) без необходимости использовать AIDL.

+ +

Вот краткий обзор того, как следует использовать объект {@link android.os.Messenger}:

+ +
    +
  • Служба реализует объект {@link android.os.Handler}, который получает обратный вызов для каждого +вызова от клиента.
  • +
  • Объект {@link android.os.Handler} используется для создания объекта {@link android.os.Messenger} +(который является ссылкой на объект {@link android.os.Handler}).
  • +
  • Объект {@link android.os.Messenger} создает объект {@link android.os.IBinder}, который служба +возвращает клиентам из метода {@link android.app.Service#onBind onBind()}.
  • +
  • Клиенты используют полученный объект {@link android.os.IBinder} для создания экземпляра объекта {@link android.os.Messenger} +(который ссылается на объект {@link android.os.Handler} службы), используемого клиентом для отправки объектов +{@link android.os.Message} в службу.
  • +
  • Служба получает каждый объект {@link android.os.Message} в своем объекте {@link +android.os.Handler} — в частности, в методе {@link android.os.Handler#handleMessage +handleMessage()}.
  • +
+ + +

Таким образом, для клиента отсутствуют «методы» для отправки вызова службе. Вместо этого +клиент отправляет «сообщения» (объекты {@link android.os.Message}), которые служба получает в +своем объекте {@link android.os.Handler}.

+ +

Ниже представлен пример службы, которая использует интерфейс {@link android.os.Messenger}:

+ +
+public class MessengerService extends Service {
+    /** Command to the service to display a message */
+    static final int MSG_SAY_HELLO = 1;
+
+    /**
+     * Handler of incoming messages from clients.
+     */
+    class IncomingHandler extends Handler {
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_SAY_HELLO:
+                    Toast.makeText(getApplicationContext(), "hello!", Toast.LENGTH_SHORT).show();
+                    break;
+                default:
+                    super.handleMessage(msg);
+            }
+        }
+    }
+
+    /**
+     * Target we publish for clients to send messages to IncomingHandler.
+     */
+    final Messenger mMessenger = new Messenger(new IncomingHandler());
+
+    /**
+     * When binding to the service, we return an interface to our messenger
+     * for sending messages to the service.
+     */
+    @Override
+    public IBinder onBind(Intent intent) {
+        Toast.makeText(getApplicationContext(), "binding", Toast.LENGTH_SHORT).show();
+        return mMessenger.getBinder();
+    }
+}
+
+ +

Обратите внимание, что метод {@link android.os.Handler#handleMessage handleMessage()} в объекте +{@link android.os.Handler} — это место, где служба получает входящие объекты {@link android.os.Message} +и решает, что делать дальше, руководствуясь элементом {@link android.os.Message#what}.

+ +

Клиенту требуется лишь создать объект {@link android.os.Messenger} на основе объекта {@link +android.os.IBinder}, возвращенного службой, и отправить сообщение с помощью метода {@link +android.os.Messenger#send send()}. Ниже представлен пример того, как простая операция выполняет привязку к +службе и отправляет ей сообщение {@code MSG_SAY_HELLO}:

+ +
+public class ActivityMessenger extends Activity {
+    /** Messenger for communicating with the service. */
+    Messenger mService = null;
+
+    /** Flag indicating whether we have called bind on the service. */
+    boolean mBound;
+
+    /**
+     * Class for interacting with the main interface of the service.
+     */
+    private ServiceConnection mConnection = new ServiceConnection() {
+        public void onServiceConnected(ComponentName className, IBinder service) {
+            // This is called when the connection with the service has been
+            // established, giving us the object we can use to
+            // interact with the service.  We are communicating with the
+            // service using a Messenger, so here we get a client-side
+            // representation of that from the raw IBinder object.
+            mService = new Messenger(service);
+            mBound = true;
+        }
+
+        public void onServiceDisconnected(ComponentName className) {
+            // This is called when the connection with the service has been
+            // unexpectedly disconnected -- that is, its process crashed.
+            mService = null;
+            mBound = false;
+        }
+    };
+
+    public void sayHello(View v) {
+        if (!mBound) return;
+        // Create and send a message to the service, using a supported 'what' value
+        Message msg = Message.obtain(null, MessengerService.MSG_SAY_HELLO, 0, 0);
+        try {
+            mService.send(msg);
+        } catch (RemoteException e) {
+            e.printStackTrace();
+        }
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.main);
+    }
+
+    @Override
+    protected void onStart() {
+        super.onStart();
+        // Bind to the service
+        bindService(new Intent(this, MessengerService.class), mConnection,
+            Context.BIND_AUTO_CREATE);
+    }
+
+    @Override
+    protected void onStop() {
+        super.onStop();
+        // Unbind from the service
+        if (mBound) {
+            unbindService(mConnection);
+            mBound = false;
+        }
+    }
+}
+
+ +

Обратите внимание, что в этом примере не показано, как служба отвечает клиенту. Если требуется, +чтобы служба реагировала, необходимо создать объект {@link android.os.Messenger} и в клиенте. Затем, +когда клиент получает обратный вызов {@link android.content.ServiceConnection#onServiceConnected +onServiceConnected()}, она отправляет в службу объект {@link android.os.Message}, который включает объект +{@link android.os.Messenger} клиента в качестве значения параметра {@link android.os.Message#replyTo} +метода {@link android.os.Messenger#send send()}.

+ +

Пример организации двустороннего обмена сообщениями приведен в примерах кода {@code +MessengerService.java} (служба) и {@code +MessengerServiceActivities.java} (клиент).

+ + + + + +

Привязка к службе

+ +

Для привязки к службе компоненты приложения (клиенты) могут использовать метод +{@link android.content.Context#bindService bindService()}. После этого система Android +вызывает метод {@link android.app.Service#onBind +onBind()} службы, который возвращает объект {@link android.os.IBinder} для взаимодействия со службой.

+ +

Привязка выполняется асинхронно. {@link android.content.Context#bindService +bindService()} возвращается сразу же и не возвращает клиенту объект +{@link android.os.IBinder}. Для получения объекта {@link android.os.IBinder} клиенту необходимо создать экземпляр {@link +android.content.ServiceConnection} и передать его в метод {@link android.content.Context#bindService +bindService()}. Интерфейс {@link android.content.ServiceConnection} включает метод обратного вызова, +который система использует для того, чтобы выдать объект {@link android.os.IBinder}.

+ +

Примечание. Выполнить привязку к службе могут только операции, другие службы и поставщики контента +— вы не можете самостоятельно выполнить привязку к службе из ресивера.

+ +

Поэтому для привязки к службе из клиента необходимо выполнить указанные ниже действия.

+
    +
  1. Реализуйте интерфейс {@link android.content.ServiceConnection}. +

    Ваша реализация должна переопределять два метода обратного вызова:

    +
    +
    {@link android.content.ServiceConnection#onServiceConnected onServiceConnected()}
    +
    Система вызывает этот метод, чтобы выдать объект {@link android.os.IBinder}, возвращенный методом +{@link android.app.Service#onBind onBind()}службы.
    +
    {@link android.content.ServiceConnection#onServiceDisconnected +onServiceDisconnected()}
    +
    Система Android вызывает этот метод в случае непредвиденной потери +подключения к службе, например при сбое в работе службы или в случае ее завершения. Этот метод не вызывается, когда клиент +отменяет привязку.
    +
    +
  2. +
  3. Вызовите метод {@link +android.content.Context#bindService bindService()}, передав в него реализацию интерфейса {@link +android.content.ServiceConnection}.
  4. +
  5. Когда система вызывает ваш метод обратного вызова {@link android.content.ServiceConnection#onServiceConnected +onServiceConnected()}, вы можете приступить к выполнению вызовов к службе с помощью +методов, определенных интерфейсом.
  6. +
  7. Чтобы отключиться от службы, вызовите метод {@link +android.content.Context#unbindService unbindService()}. +

    В случае уничтожения клиента выполняется отмена его привязки к службе, однако вам всегда следует отменять +привязку по завершении взаимодействия со службой или в случае приостановки операции, чтобы служба +могла завершить свою работу, когда она не используется. (Более подробно подходящее время для привязки и ее отмены +рассматриваются далее в этом документе).

    +
  8. +
+ +

Ниже представлен пример фрагмента кода для подключения клиента к созданной выше службе путем +расширения класса Binder — клиенту нужно лишь передать возвращенный объект +{@link android.os.IBinder} в класс {@code LocalService} и запросить экземпляр {@code +LocalService}:

+ +
+LocalService mService;
+private ServiceConnection mConnection = new ServiceConnection() {
+    // Called when the connection with the service is established
+    public void onServiceConnected(ComponentName className, IBinder service) {
+        // Because we have bound to an explicit
+        // service that is running in our own process, we can
+        // cast its IBinder to a concrete class and directly access it.
+        LocalBinder binder = (LocalBinder) service;
+        mService = binder.getService();
+        mBound = true;
+    }
+
+    // Called when the connection with the service disconnects unexpectedly
+    public void onServiceDisconnected(ComponentName className) {
+        Log.e(TAG, "onServiceDisconnected");
+        mBound = false;
+    }
+};
+
+ +

С помощью этого интерфейса {@link android.content.ServiceConnection} клиент может выполнить привязку к службе, передав ее в +метод {@link android.content.Context#bindService bindService()}. Например:

+ +
+Intent intent = new Intent(this, LocalService.class);
+bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
+
+ +
    +
  • Первый параметр в методе {@link android.content.Context#bindService bindService()} представляет собой объект +{@link android.content.Intent}, который явным образом именует службу для привязки (хотя переход может быть +и неявным).
  • +
  • Второй параметр — это объект {@link android.content.ServiceConnection}.
  • +
  • Третий параметр представляет собой флаг, указывающий параметры привязки. Обычно им является {@link +android.content.Context#BIND_AUTO_CREATE}, создающий службу, если она уже не выполняется. +Другие возможные значения: {@link android.content.Context#BIND_DEBUG_UNBIND} +и {@link android.content.Context#BIND_NOT_FOREGROUND} или {@code 0}, если значение отсутствует.
  • +
+ + +

Дополнительные примечания

+ +

Ниже представлены некоторые важные замечания о привязке к службе.

+
    +
  • Всегда отлавливайте исключения {@link android.os.DeadObjectException}, которые выдаются +при возникновении сбоя подключения. Это единственное исключение, которое выдается удаленными методами.
  • +
  • Для объектов всегда учитываются ссылки на них в процессах.
  • +
  • Обычно необходимо связать привязку и ее отмену во время +сопоставления моментов подключения и отключения в жизненном цикле клиента. Например: +
      +
    • Если взаимодействие со службой требуется лишь в то время, когда операция отображается, привязку +необходимо выполнить во время метода {@link android.app.Activity#onStart onStart()}, а отмену привязки — во время выполнения метода {@link +android.app.Activity#onStop onStop()}.
    • +
    • Если необходимо, чтобы операция получала ответы даже в случае ее остановки во время работы в фоновом режиме, +то привязку можно выполнить во время {@link android.app.Activity#onCreate onCreate()}, а отмену привязки + — во время выполнения {@link android.app.Activity#onDestroy onDestroy()}. Однако следует помнить, что такой способ подразумевает, +что вашей операции необходимо использовать службу все время, пока она выполняется (даже если она выполняется в фоновом режиме). Поэтому, +если служба находится в другом процессе, вы тем самым утяжеляете процесс, а это +повышает вероятность того, что система завершит его.
    • +
    +

    Примечание. Обычно не следует выполнять привязку или отменять ее +во время выполнения методов {@link android.app.Activity#onResume onResume()} и {@link +android.app.Activity#onPause onPause()} вашей операции, поскольку такие обратные вызовы происходят при каждом переходе из одного состояния в другое, + а обработка данных, выполняемая при таких переходах, должна быть минимальной. Кроме того, если к одной и той же +службе привязано несколько операций в вашем приложении, и имеется переход между +двумя этими операциями, служба может быть уничтожена и создана повторно, поскольку текущая операция выполняет отмену привязки +(во время приостановки) до того, как следующая служба выполнит привязку (во время возобновления). (Подробные сведения о согласовании жизненных циклов операций при таких переходах +представлены в статье +Операции.)

    +
+ +

Пример кода, в котором показан порядок привязки к службе, см. в статье, посвященной классу {@code +RemoteService.java}, в ApiDemos.

+ + + + + +

Управление жизненным циклом привязанной службы

+ +

Когда выполняется отмена привязки службы ко всем клиентам, система Android уничтожает такую службу (если она не была запущена +вместе с {@link android.app.Service#onStartCommand onStartCommand()}). В таком случае вам не нужно +управлять жизненным циклом своей службы, если она исключительно привязанная служба +— система Android управляет ей за вас на основании привязки службы к любым другим клиентам.

+ +

Однако, если вы решите реализовать метод обратного вызова {@link android.app.Service#onStartCommand +onStartCommand()}, вам необходимо явным образом остановить службу, поскольку в +этом случае она считается запущенной. В таком случае служба выполняется до тех пор, пока +сама не остановит свою работу с помощью метода {@link android.app.Service#stopSelf()} или до тех пор, пока другой компонент не вызовет метод {@link +android.content.Context#stopService stopService()}, независимо от привязки службы к каким-либо +клиентам.

+ +

Кроме того, если ваша служба запущена и принимает привязку, то при вызове системой +вашего метода {@link android.app.Service#onUnbind onUnbind()} вы также можете вернуть +{@code true}, если желаете получить вызов к {@link android.app.Service#onRebind +onRebind()} при следующей привязке к службе (вместо получения вызова к методу {@link +android.app.Service#onBind onBind()}). Метод {@link android.app.Service#onRebind +onRebind()} возвращает значение void, однако клиент по-прежнему получает объект {@link android.os.IBinder} в своем методе обратного вызова +{@link android.content.ServiceConnection#onServiceConnected onServiceConnected()}. +На рисунке 1 ниже иллюстрируется логика жизненного цикла такого рода.

+ + + +

Рисунок 1. Жизненный цикл запущенной службы, +для которой выполняется привязка.

+ + +

Дополнительные сведения о жизненном цикле уже запущенной службы представлены в статье Службы.

+ + + + diff --git a/docs/html-intl/intl/ru/guide/components/fragments.jd b/docs/html-intl/intl/ru/guide/components/fragments.jd new file mode 100644 index 0000000000000000000000000000000000000000..b13fcc3da7ef5871c1b636f819d49588f955b8d0 --- /dev/null +++ b/docs/html-intl/intl/ru/guide/components/fragments.jd @@ -0,0 +1,812 @@ +page.title=Фрагменты +parent.title=Операции +parent.link=activities.html +@jd:body + + + +

Фрагмент (класс {@link android.app.Fragment}) представляет поведение или часть пользовательского интерфейса в операции +(класс {@link android.app.Activity}). Разработчик может объединить несколько фрагментов в одну операцию для построения +многопанельного пользовательского интерфейса и повторного использования фрагмента в нескольких операциях. Фрагмент можно рассматривать как + модульную часть операции. Такая часть имеет свой жизненный цикл и самостоятельно обрабатывает события ввода. Кроме того, +ее можно добавить или удалить непосредственно во время выполнения операции. Это нечто вроде вложенной операции, которую +можно многократно использовать в различных операциях.

+ +

Фрагмент всегда должен быть встроен в операцию, и на его жизненный цикл напрямую +влияет жизненный цикл операции. Например, когда операция приостановлена, в том же состоянии находятся и все +фрагменты внутри нее, а когда операция уничтожается, уничтожаются и все фрагменты. Однако пока +операция выполняется (это соответствует состоянию возобновлена жизненного цикла), можно +манипулировать каждым фрагментом независимо, например добавлять или удалять их. Когда разработчик выполняет такие +транзакции с фрагментами, он может также добавить их в стек переходов назад, которым управляет +операция. Каждый элемент стека переходов назад в операции является записью + выполненной транзакции с фрагментом. Стек переходов назад позволяет пользователю обратить транзакцию с фрагментом (выполнить навигацию в обратном направлении), +нажимая кнопку Назад.

+ +

Когда фрагмент добавлен как часть макета операции, он находится в объекте {@link +android.view.ViewGroup} внутри иерархии представлений операции и определяет собственный +макет представлений. +Разработчик может вставить фрагмент в макет операции двумя способами. Для этого следует объявить фрагмент в +файле макета операции как элемент {@code <fragment>} или добавить его в +существующий объект{@link android.view.ViewGroup} в коде приложения. Впрочем, фрагмент не обязан быть частью +макета операции. Можно использовать фрагмент без интерфейса в качестве невидимого рабочего потока +операции.

+ +

В этом документе показано, как построить приложение, использующее фрагменты. В частности, обсуждается, +как фрагменты могут поддерживать свое состояние, когда они добавляются в стек переходов назад операции, использовать +события совместно с операцией и другими фрагментами внутри нее, выводить данные в +строку действий операции и т. д.

+ + +

Философия проектирования

+ +

Фрагменты впервые появились в Android версии 3.0 (API уровня 11), главным образом, для обеспечения +большей динамичности и гибкости пользовательских интерфейсов на больших экранах, например, у планшетов. Поскольку экраны +планшетов гораздо больше, чем у смартфонов, они предоставляют больше возможностей для объединения и +перестановки компонентов пользовательского интерфейса. Фрагменты позволяют делать это, избавляя разработчика от необходимости управлять +сложными изменениями в иерархии представлений. Разбивая макет операции на фрагменты, разработчик получает возможность +модифицировать внешний вид операции в ходе выполнения и сохранять эти изменения в стеке переходов назад, +которым управляет операция.

+ +

Например, новостное приложение может использовать один фрагмент для показа списка статей слева, +а другой—для отображения статьи справа. Оба фрагмента отображаются за +одну операцию рядом друг с другом, и каждый имеет собственный набор методов обратного вызова жизненного цикла и управляет +собственными событиями пользовательского ввода. Таким образом, вместо применения одной операции для выбора статьи, а другой + — для чтения статей, пользователь может выбрать статью и читать ее в рамках одной +операции, как на планшете, изображенном на рисунке 1.

+ +

Следует разрабатывать каждый фрагмент как модульный и повторно используемый компонент операции. Поскольку + каждый фрагмент определяет собственный макет и собственное поведение со своими обратными вызовами жизненного цикла, разработчик может +включить один фрагмент в несколько операций. Поэтому он должен предусмотреть повторное использование фрагмента и не допускать, +чтобы один фрагмент непосредственно манипулировал другим. Это особенно важно, потому что модульность фрагментов +позволяет изменять их сочетания в соответствии с различными размерами экранов. Если +приложение должно работать и на планшетах, и на смартфонах, можно повторно использовать фрагменты в различных +конфигурациях макета, чтобы оптимизировать взаимодействие с пользователем в зависимости от доступного размера экрана. Например, +на смартфоне может возникнуть необходимость в разделении фрагментов для предоставления однопанельного пользовательского интерфейса, если + разработчику не удается поместить более одного фрагмента в одну операцию.

+ + +

Рисунок 1. Пример того, как два модуля пользовательского интерфейса, определенные +фрагментами, могут быть объединены внутри одной операции для работы на планшетах, но разделены на +смартфонах.

+ +

Вернемся к примеру с новостным приложением. Оно может иметь +два фрагмента, встроенных в Операцию А, когда выполняется на устройстве планшетного формата. В то же время на +экране смартфона недостаточно места для обоих фрагментов, и поэтому Операция А включает в себя +только фрагмент со списком статей. Когда пользователь выбирает статью, запускается +Операция В, содержащая второй фрагмент для чтения статьи. Таким образом, приложение +поддерживает как планшеты, так и смартфоны благодаря повторному использованию фрагментов в различных сочетаниях, как показано на +рисунке 1.

+ +

Подробные сведения относительно разработки приложения с различными сочетаниями фрагментов для +различных конфигураций экрана приводятся в руководстве Поддержка планшетов и смартфонов

+ + + +

Создание фрагмента

+ +
+ +

Рисунок 2. Жизненный цикл фрагмента (во время +выполнения операции)

+
+ +

Для создания фрагмента необходимо создать подкласс класса {@link android.app.Fragment} (или его существующего +подкласса). Класс {@link android.app.Fragment} имеет код, во многом схожий с +кодом {@link android.app.Activity}. Он содержит методы обратного вызова, аналогичные методам операции, такие +как {@link android.app.Fragment#onCreate onCreate()}, {@link android.app.Fragment#onStart onStart()}, +{@link android.app.Fragment#onPause onPause()} и {@link android.app.Fragment#onStop onStop()}. На практике, +если требуется преобразовать существующее приложение Android так, чтобы в нем использовались фрагменты, достаточно просто переместить +код из методов обратного вызова операции в соответствующие методы обратного вызова +фрагмента.

+ +

Как правило, необходимо реализовать следующие методы жизненного цикла:

+ +
+
{@link android.app.Fragment#onCreate onCreate()}
+
Система вызывает этот метод, когда создает фрагмент. В своей реализации разработчик должен +инициализировать ключевые компоненты фрагмента, которые требуется сохранить, когда фрагмент находится в состоянии +паузы или возобновлен после остановки.
+
{@link android.app.Fragment#onCreateView onCreateView()}
+
Система вызывает этот метод при первом отображении пользовательского интерфейса фрагмента +на дисплее. Для прорисовки пользовательского интерфейса фрагмента следует возвратить из этого метода объект {@link android.view.View}, +который является корневым в макете фрагмента. Если фрагмент не имеет пользовательского интерфейса, можно +возвратить null.
+
{@link android.app.Activity#onPause onPause()}
+
Система вызывает этот метод как первое указание того, что пользователь покидает +фрагмент (это не всегда означает уничтожение фрагмента). Обычно именно в этот момент +необходимо фиксировать все изменения, которые должны быть сохранены за рамками текущего сеанса работы пользователя (поскольку +пользователь может не вернуться назад).
+
+ +

В большинстве приложений для каждого фрагмента должны быть реализованы, как минимум, эти три метода. Однако существуют и +другие методы обратного вызова, которые следует использовать для управления различными этапами жизненного цикла +фрагмента. Все методы обратного вызова жизненного цикла подробно обсуждаются в разделе + Управление жизненным циклом фрагмента.

+ + +

Существует также ряд подклассов, которые, возможно, потребуется расширить вместо использования базового класса {@link +android.app.Fragment}:

+ +
+
{@link android.app.DialogFragment}
+
Отображение перемещаемого диалогового окна. Использование этого класса для создания диалогового окна является хорошей альтернативой + вспомогательным методам диалогового окна в классе{@link android.app.Activity}. Дело в том, что он дает возможность +вставить диалоговое окно фрагмента в управляемый операцией стек переходов назад для фрагментов, +что позволяет пользователю вернуться к закрытому фрагменту.
+ +
{@link android.app.ListFragment}
+
Отображение списка элементов, управляемых адаптером (например, {@link +android.widget.SimpleCursorAdapter}), аналогично классу {@link android.app.ListActivity}. Этот класс предоставляет +несколько методов для управления списком представлений, например, метод обратного вызова {@link +android.app.ListFragment#onListItemClick(ListView,View,int,long) onListItemClick()} для +обработки нажатий.
+ +
{@link android.preference.PreferenceFragment}
+
Отображение иерархии объектов {@link android.preference.Preference} в виде списка, аналогично классу +{@link android.preference.PreferenceActivity}. Этот класс полезен, когда в приложении создается +операция «Настройки».
+
+ + +

Добавление пользовательского интерфейса

+ +

Фрагмент обычно используется как часть пользовательского интерфейса операции, при этом он добавляет в операцию +свой макет.

+ +

Чтобы создать макет для фрагмента, разработчик должен реализовать метод обратного вызова {@link +android.app.Fragment#onCreateView onCreateView()}, который система Android вызывает, +когда для фрагмента наступает время отобразить свой макет. Реализация этого метода должна возвращать объект +{@link android.view.View}, который является корневым в макете фрагмента.

+ +

Примечание. Если фрагмент является подклассом класса {@link +android.app.ListFragment}, реализация по умолчанию возвращает класс {@link android.widget.ListView} из метода + {@link android.app.Fragment#onCreateView onCreateView()}, так что реализовывать его нет необходиомости.

+ +

Чтобы возвратить макет из метода {@link +android.app.Fragment#onCreateView onCreateView()}, можно выполнить его раздувание из ресурса макета, определенного в XML-файле. Для +этой цели метод {@link android.app.Fragment#onCreateView onCreateView()} предоставляет объект +{@link android.view.LayoutInflater}.

+ +

Например, код подкласса класса {@link android.app.Fragment}, загружающий макет из файла +{@code example_fragment.xml}, может выглядеть так:

+ +
+public static class ExampleFragment extends Fragment {
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+                             Bundle savedInstanceState) {
+        // Inflate the layout for this fragment
+        return inflater.inflate(R.layout.example_fragment, container, false);
+    }
+}
+
+ + + +

Параметр {@code container}, передаваемый методу {@link android.app.Fragment#onCreateView +onCreateView()}, является родительским классом {@link android.view.ViewGroup} (из макета операции), в который +будет вставлен макет +фрагмента. Параметр {@code savedInstanceState} является классом {@link android.os.Bundle}, который +предоставляет данные о предыдущем экземпляре фрагмента во время возобновления фрагмента +(восстановление состояния подробно обсуждается в разделе Управление +жизненным циклом фрагмента).

+ +

Метод {@link android.view.LayoutInflater#inflate(int,ViewGroup,boolean) inflate()} принимает +три аргумента:

+
    +
  • Идентификатор ресурса макета, раздувание которого следует выполнить.
  • +
  • Объект класса {@link android.view.ViewGroup}, который должен стать родительским для макета после раздувания. Передача параметра {@code +container} необходима для того, чтобы система смогла применить параметры макета к корневому представлению +раздутого макета, определяемому родительским представлением, в которое направляется макет.
  • +
  • Логическое значение, показывающее, следует ли прикрепить макет к объекту {@link +android.view.ViewGroup} (второй параметр) во время раздувания. (В данном случае это +false, потому что система уже вставляет раздутый макет в объект {@code +container}, ипередача значения true создала бы лишнюю группу представления в окончательном макете).
  • +
+ +

Мы увидели, как создавать фрагмент, предоставляющий макет. Теперь необходимо добавить +фрагмент в операцию.

+ + + +

Добавление фрагмента в операцию

+ +

Как правило, фрагмент добавляет часть пользовательского интерфейса в операцию, и этот интерфейс встраивается +в общую иерархию представлений операции. Разработчик может добавить фрагмент в макет операции двумя +способами:

+ +
    +
  • объявив фрагмент в файле макета операции. +

    В этом случае можно +указать свойства макета для фрагмента, как будто он является представлением. Например, файл макета операции +с двумя фрагментами может выглядеть следующим образом:

    +
    +<?xml version="1.0" encoding="utf-8"?>
    +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    +    android:orientation="horizontal"
    +    android:layout_width="match_parent"
    +    android:layout_height="match_parent">
    +    <fragment android:name="com.example.news.ArticleListFragment"
    +            android:id="@+id/list"
    +            android:layout_weight="1"
    +            android:layout_width="0dp"
    +            android:layout_height="match_parent" />
    +    <fragment android:name="com.example.news.ArticleReaderFragment"
    +            android:id="@+id/viewer"
    +            android:layout_weight="2"
    +            android:layout_width="0dp"
    +            android:layout_height="match_parent" />
    +</LinearLayout>
    +
    +

    Атрибут {@code android:name} в элементе {@code <fragment>} определяет класс {@link +android.app.Fragment}, экземпляр которого создается в макете.

    + +

    Когда система создает этот макет операции, она создает экземпляр каждого фрагмента, определенного в макете, +и для каждого вызывает метод {@link android.app.Fragment#onCreateView onCreateView()}, +чтобы получить макет каждого фрагмента. Система вставляет объект {@link android.view.View}, возвращенный + фрагментом, непосредственно вместо элемента {@code <fragment>}.

    + +
    +

    Примечание. Каждый фрагмент должен иметь уникальный идентификатор, который +система сможет использовать для восстановления фрагмента в случае перезапуска операции. (Что касается разработчика, он может использовать этот идентификатор для +захвата фрагмента с целью выполнения транзакций с ним, например, чтобы удалить его). Предоставить +идентификатор фрагменту можно тремя способами:

    +
      +
    • указать атрибут {@code android:id} с уникальным идентификатором;
    • +
    • указать атрибут {@code android:tag} с уникальной строкой;
    • +
    • ничего не предпринимать, чтобы система использовала идентификатор контейнерного +представления.
    • +
    +
    +
  • + +
  • или программным образом, добавив фрагмент в существующий объект {@link android.view.ViewGroup}. +

    В любой момент выполнения операции разработчик может добавить фрагменты в ее макет. Для +этого достаточно указать объект {@link +android.view.ViewGroup}, в котором следует разместить фрагмент.

    +

    Для выполнения транзакций с фрагментами внутри операции (таких как добавление, удаление или замена +фрагмента) необходимо использовать API-интерфейсы из {@link android.app.FragmentTransaction}. Экземпляр + класса {@link android.app.FragmentTransaction} можно получить от объекта {@link android.app.Activity} следующим образом:

    + +
    +FragmentManager fragmentManager = {@link android.app.Activity#getFragmentManager()}
    +FragmentTransaction fragmentTransaction = fragmentManager.{@link android.app.FragmentManager#beginTransaction()};
    +
    + +

    После этого можно добавить фрагмент методом {@link +android.app.FragmentTransaction#add(int,Fragment) add()}, указав добавляемый фрагмент и +представление, в которое он должен быть добавлен. Например:

    + +
    +ExampleFragment fragment = new ExampleFragment();
    +fragmentTransaction.add(R.id.fragment_container, fragment);
    +fragmentTransaction.commit();
    +
    + +

    Первый аргумент, передаваемый методу {@link android.app.FragmentTransaction#add(int,Fragment) add()}, +представляет собой контейнерный объект {@link android.view.ViewGroup} для фрагмента, указанный при помощи +идентификатора ресурса. Второй параметр — это фрагмент, который нужно добавить.

    +

    Выполнив изменения с помощью +{@link android.app.FragmentTransaction}, необходимо +вызвать метод {@link android.app.FragmentTransaction#commit}, чтобы они вступили в силу.

    +
  • +
+ + +

Добавление фрагмента, не имеющего пользовательского интерфейса

+ +

Пример, приведенный выше, демонстрирует, как добавлять в операцию фрагмент с предоставлением пользовательского интерфейса. Однако +можно использовать фрагмент и для реализации фонового поведения операции без какого-либо дополнительного +пользовательского интерфейса.

+ +

Чтобы добавить фрагмент без пользовательского интерфейса, добавьте фрагмент из операции, используя метод {@link +android.app.FragmentTransaction#add(Fragment,String)} (передав ему уникальный строковый «тег» для +фрагмента вместо идентификатора представления). Фрагмент будет добавлен, но, поскольку он не связан +с представлением в макете операции, он не будет принимать вызов метода {@link +android.app.Fragment#onCreateView onCreateView()}. Поэтому в реализации этого метода нет необходимости.

+ +

Передача строкового тега свойственна не только фрагментам без пользовательского интерфейса, поэтому можно +передавать строковые теги и фрагментам, имеющим пользовательский интерфейс. Однако, если у фрагмента нет + пользовательского интерфейса, то строковый тег является единственным способом его идентификации. Если впоследствии потребуется получить фрагмент от +операции, нужно будет вызвать метод {@link android.app.FragmentManager#findFragmentByTag +findFragmentByTag()}.

+ +

Пример операции, использующей фрагмент в качестве фонового потока, без пользовательского интерфейса, приведен в образце кода {@code +FragmentRetainInstance.java}, входящем в число образцов в SDK (и доступном при помощи +Android SDK Manager). Путь к нему в системе — +<sdk_root>/APIDemos/app/src/main/java/com/example/android/apis/app/FragmentRetainInstance.java.

+ + + +

Управление фрагментами

+ +

Для управления фрагментами в операции нужен класс {@link android.app.FragmentManager}. Чтобы получить его, + следует вызвать метод {@link android.app.Activity#getFragmentManager()} из кода операции.

+ +

Ниже указаны действия, которые позволяет выполнить{@link android.app.FragmentManager}:

+ +
    +
  • получать фрагменты, имеющиеся в операции, с помощью метода {@link +android.app.FragmentManager#findFragmentById findFragmentById()} (для фрагментов, предоставляющих пользовательский интерфейс в + макете операции) или {@link android.app.FragmentManager#findFragmentByTag +findFragmentByTag()} (как для фрагментов, имеющих пользовательский интерфейс, так и для фрагментов без него);
  • +
  • снимать фрагменты со стека переходов назад методом {@link +android.app.FragmentManager#popBackStack()} (имитируя нажатие кнопки Назад пользователем);
  • +
  • регистрировать процесс-слушатель изменений в стеке переходов назад при помощи метода {@link +android.app.FragmentManager#addOnBackStackChangedListener addOnBackStackChangedListener()}.
  • +
+ +

Дополнительные сведения об этих и других методах приводятся в документации по классу {@link +android.app.FragmentManager}.

+ +

Как было показано в предыдущем разделе, можно использовать класс {@link android.app.FragmentManager} +для открытия {@link android.app.FragmentTransaction}, что позволяет выполнять транзакции с фрагментами, например, +добавление и удаление.

+ + +

Выполнение транзакций с фрагментами

+ +

Большим достоинством использования фрагментов в операции является возможность добавлять, удалять, заменять их и +выполнять другие действия с ними в ответ на действия пользователя. Любой набор изменений, +вносимых в операцию, называется транзакцией. Ее можно выполнить при помощи API-интерфейсов в {@link +android.app.FragmentTransaction}. Каждую транзакцию можно сохранить в стеке переходов назад, +которым управляет операция. Это позволит пользователю перемещаться назад по изменениям во фрагментах (аналогично перемещению +назад по операциям).

+ +

Экземпляр класса {@link android.app.FragmentTransaction} можно получить от {@link +android.app.FragmentManager}, например, так:

+ +
+FragmentManager fragmentManager = {@link android.app.Activity#getFragmentManager()};
+FragmentTransaction fragmentTransaction = fragmentManager.{@link android.app.FragmentManager#beginTransaction()};
+
+ +

Каждая транзакция является набором изменений, выполняемых одновременно. Разработчик может указать +все изменения, которые ему нужно выполнить в данной транзакции, вызывая методы {@link +android.app.FragmentTransaction#add add()}, {@link android.app.FragmentTransaction#remove remove()} +и {@link android.app.FragmentTransaction#replace replace()}. Затем, чтобы применить транзакцию +к операции, следует вызвать метод {@link android.app.FragmentTransaction#commit()}.

+ + +

Впрочем, до вызова метода{@link +android.app.FragmentTransaction#commit()} у разработчика может возникнуть необходимость вызвать метод {@link +android.app.FragmentTransaction#addToBackStack addToBackStack()}, чтобы добавить транзакцию +в стек переходов назад по транзакциям фрагмента. Этим стеком переходов назад управляет операция, что позволяет +пользователю вернуться к предыдущему состоянию фрагмента, нажав кнопку Назад.

+ +

Например, следующий код демонстрирует, как можно заменить один фрагмент другим, сохранив при этом предыдущее +состояние в стеке переходов назад:

+ +
+// Create new fragment and transaction
+Fragment newFragment = new ExampleFragment();
+FragmentTransaction transaction = getFragmentManager().beginTransaction();
+
+// Replace whatever is in the fragment_container view with this fragment,
+// and add the transaction to the back stack
+transaction.replace(R.id.fragment_container, newFragment);
+transaction.addToBackStack(null);
+
+// Commit the transaction
+transaction.commit();
+
+ +

В этом коде объект {@code newFragment} замещает фрагмент (если таковой имеется), находящийся в +контейнере макета, на который указывает идентификатор {@code R.id.fragment_container}. В результате вызова метода {@link +android.app.FragmentTransaction#addToBackStack addToBackStack()} транзакция замены +сохраняется в стеке переходов назад, чтобы пользователь мог обратить транзакцию и вернуть +предыдущий фрагмент, нажав кнопку Назад.

+ +

Если в транзакцию добавить несколько изменений (например, еще раз вызвать {@link +android.app.FragmentTransaction#add add()} или {@link android.app.FragmentTransaction#remove +remove()}), а затем вызвать {@link +android.app.FragmentTransaction#addToBackStack addToBackStack()}, все изменения, примененные до вызова +метода {@link android.app.FragmentTransaction#commit commit()}, будут добавлены в стек + переходов назад как одна транзакция, и кнопкаНазад обратит их все вместе.

+ +

Порядок добавления изменений к объекту {@link android.app.FragmentTransaction} не играет роли + за следующими исключениями:

+
    +
  • метод {@link android.app.FragmentTransaction#commit()} должен быть вызван в последнюю очередь;
  • +
  • если в один контейнер добавляется несколько фрагментов, то порядок их +добавления определяет порядок, в котором они появляются в иерархии видов.
  • +
+ +

Если при выполнении транзакции, удаляющей фрагмент, не вызвать метод {@link android.app.FragmentTransaction#addToBackStack(String) +addToBackStack()}, при фиксации транзакции фрагмент +уничтожается, и пользователь теряет возможность вернуться к нему. В то же время, если +вызвать {@link android.app.FragmentTransaction#addToBackStack(String) addToBackStack()} при +удалении фрагмента, фрагмент перейдет в состояние остановки и будет возобновлен, если пользователь вернется +к нему.

+ +

Совет. К каждой транзакции с фрагментом можно применить анимацию +перехода, вызвав {@link android.app.FragmentTransaction#setTransition setTransition()} до +фиксации.

+ +

Вызов метода {@link android.app.FragmentTransaction#commit()} не приводит к немедленному выполнению + транзакции. Метод запланирует ее выполнение в потоке пользовательского интерфейса операции (в «главном» потоке), как только +у потока появится возможность для этого. Впрочем, при необходимости можно вызвать {@link +android.app.FragmentManager#executePendingTransactions()} из потока пользовательского интерфейса, чтобы +транзакции, запланированные методом {@link android.app.FragmentTransaction#commit()} были выполнены немедленно. Как правило, +в этом нет необходимости, за исключением случаев, когда транзакция является зависимостью для заданий в других потоках.

+ +

Внимание! Фиксировать транзакцию методом {@link +android.app.FragmentTransaction#commit commit()} можно только до того, как операциясохранит свое +состояние (после того, как пользователь покинет ее). Попытка зафиксировать транзакцию после этого момента + вызовет исключение. Дело в том, что состояние после фиксации может быть потеряно, если понадобится +восстановить операцию. В ситуациях, в которых потеря фиксации не критична, следует вызывать {@link +android.app.FragmentTransaction#commitAllowingStateLoss()}.

+ + + + +

Взаимодействие с операцией

+ +

Хотя {@link android.app.Fragment} реализован как объект, независимый от +класса {@link android.app.Activity}, и может быть использован внутри нескольких операций, конкретный экземпляр +фрагмента напрямую связан с содержащей его операцией.

+ +

В частности, фрагмент может обратиться к экземпляру {@link android.app.Activity} с помощью метода {@link +android.app.Fragment#getActivity()} и без труда выполнить такие задачи, как поиск представления в макете +операции:

+ +
+View listView = {@link android.app.Fragment#getActivity()}.{@link android.app.Activity#findViewById findViewById}(R.id.list);
+
+ +

Аналогичным образом операция может вызывать методы фрагмента, получив ссылку на объект +{@link android.app.Fragment} от {@link android.app.FragmentManager} с помощью метода {@link +android.app.FragmentManager#findFragmentById findFragmentById()} или {@link +android.app.FragmentManager#findFragmentByTag findFragmentByTag()}. Например:

+ +
+ExampleFragment fragment = (ExampleFragment) getFragmentManager().findFragmentById(R.id.example_fragment);
+
+ + +

Создание обратного вызова события для операции

+ +

В некоторых случаях необходимо, чтобы фрагмент использовал события совместно с операцией. Хороший способ реализации этого +состоит в том, чтобы определить интерфейс обратного вызова внутри фрагмента и потребовать от контейнерной операции +его реализации. Когда операция примет обратный вызов через этот интерфейс, она сможет обмениваться информацией с +другими фрагментами в макете по мере необходимости.

+ +

Пусть, например, у новостного приложения имеются два фрагмента в одной операции: один для отображения списка +статей (фрагмент A), а другой—для отображения статьи (фрагмент B). Тогда фрагмент A должен сообщать +операции о том, что выбран пункт списка, чтобы она могла сообщить фрагменту B о необходимости отобразить статью. В +этом случае интерфейс {@code OnArticleSelectedListener} объявляется во фрагменте A:

+ +
+public static class FragmentA extends ListFragment {
+    ...
+    // Container Activity must implement this interface
+    public interface OnArticleSelectedListener {
+        public void onArticleSelected(Uri articleUri);
+    }
+    ...
+}
+
+ +

Тогда операция, содержащая этот фрагмент, реализует интерфейс {@code OnArticleSelectedListener} +и переопределит +метод {@code onArticleSelected()}, чтобы извещать фрагмент B о событии, исходящем от фрагмента A. Чтобы +контейнерная операция наверняка реализовала этот интерфейс, метод обратного вызова {@link +android.app.Fragment#onAttach onAttach()} во фрагменте A (который система вызывает при добавлении +фрагмента в операцию) создает экземпляр класса {@code OnArticleSelectedListener}, выполнив +приведение типа объекта {@link android.app.Activity}, который передается методу {@link android.app.Fragment#onAttach +onAttach()}:

+ +
+public static class FragmentA extends ListFragment {
+    OnArticleSelectedListener mListener;
+    ...
+    @Override
+    public void onAttach(Activity activity) {
+        super.onAttach(activity);
+        try {
+            mListener = (OnArticleSelectedListener) activity;
+        } catch (ClassCastException e) {
+            throw new ClassCastException(activity.toString() + " must implement OnArticleSelectedListener");
+        }
+    }
+    ...
+}
+
+ +

Если операция не реализовала интерфейс, фрагмент генерирует исключение +{@link java.lang.ClassCastException}. +В случае успеха элемент {@code mListener} будет содержать ссылку на реализацию интерфейса +{@code OnArticleSelectedListener} в операции, чтобы фрагмент A мог использовать +события совместно с операцией, вызывая методы, определенные интерфейсом {@code OnArticleSelectedListener}. Например, если фрагмент A является расширением +класса {@link android.app.ListFragment}, то всякий раз, +когда пользователь нажимает элемент списка, система вызывает {@link android.app.ListFragment#onListItemClick +onListItemClick()} во фрагменте. Этот метод, в свою очередь, вызывает метод {@code onArticleSelected()}, чтобы использовать +событие совместно с операцией:

+ +
+public static class FragmentA extends ListFragment {
+    OnArticleSelectedListener mListener;
+    ...
+    @Override
+    public void onListItemClick(ListView l, View v, int position, long id) {
+        // Append the clicked item's row ID with the content provider Uri
+        Uri noteUri = ContentUris.{@link android.content.ContentUris#withAppendedId withAppendedId}(ArticleColumns.CONTENT_URI, id);
+        // Send the event and Uri to the host activity
+        mListener.onArticleSelected(noteUri);
+    }
+    ...
+}
+
+ +

Параметр {@code id}, передаваемый методу {@link +android.app.ListFragment#onListItemClick onListItemClick()}, — это идентификатор строки с выбранным элементом списка, +который операция (или другой фрагмент) использует для получения статьи от объекта {@link +android.content.ContentProvider} приложения.

+ +

Дополнительные сведения о +работе с поставщиком контента приводятся в документе Поставщики контента.

+ + + +

Добавление элементов в строку действий

+ +

Фрагменты могут добавлять пункты меню в Меню вариантов операции (и, следовательно, в Строку действий), реализовав +{@link android.app.Fragment#onCreateOptionsMenu(Menu,MenuInflater) onCreateOptionsMenu()}. Однако, чтобы этот метод мог +принимать вызовы, необходимо вызывать {@link +android.app.Fragment#setHasOptionsMenu(boolean) setHasOptionsMenu()} во время выполнения метода {@link +android.app.Fragment#onCreate(Bundle) onCreate()}, чтобы сообщить, что фрагмент +намеревается добавить пункты в Меню вариантов (в противном случае фрагмент не примет вызов метода +{@link android.app.Fragment#onCreateOptionsMenu onCreateOptionsMenu()}).

+ +

Любые пункты, добавляемые фрагментом в Меню вариантов, присоединяются к +уже существующим. Кроме того, фрагмент принимает обратные вызовы метода {@link +android.app.Fragment#onOptionsItemSelected(MenuItem) onOptionsItemSelected()}, когда пользователь выбирает пункт +меню.

+ +

Разработчик может также зарегистрировать представление в макете своего фрагмента, чтобы предоставить контекстное меню. Для этого следует вызвать метод {@link +android.app.Fragment#registerForContextMenu(View) registerForContextMenu()}. Когда пользователь открывает +контекстное меню, фрагмент принимает вызов метода {@link +android.app.Fragment#onCreateContextMenu(ContextMenu,View,ContextMenu.ContextMenuInfo) +onCreateContextMenu()}. Когда пользователь выбирает пункт меню, фрагмент принимает вызов метода {@link +android.app.Fragment#onContextItemSelected(MenuItem) onContextItemSelected()}.

+ +

Примечание. Хотя фрагмент принимает обратный вызов по событию «выбран пункт меню» +для каждого добавленного им пункта, операция первой принимает соответствующий обратный вызов, когда пользователь +выбирает пункт меню. Если имеющаяся в операции реализация обратного вызова по событию «выбран пункт меню» не +обрабатывает выбранный пункт, событие передается методу обратного вызова во фрагменте. Это справедливо для +Меню вариантов и контекстных меню.

+ +

Подробные сведения относительно меню см. в руководствах для разработчиков Меню и Строка действий

+ + + + +

Управление жизненным циклом фрагмента

+ +
+ +

Рисунок 3. Влияние жизненного цикла операции на жизненный цикл +фрагмента

+
+ +

Управление жизненным циклом фрагмента во многом аналогично управлению жизненным циклом операции. Как и +операция, фрагмент может существовать в одном из трех состояний:

+ +
+
Возобновлен
+
Фрагмент виден во время выполнения операции.
+ +
Приостановлен
+
На переднем плане выполняется и находится в фокусе другая операция, но операция, содержащая данный +фрагмент, по-прежнему видна (операция переднего плана частично прозрачна или не +занимает весь экран).
+ +
Остановлен
+
Фрагмент не виден. Либо контейнерная операция остановлена, либо +фрагмент удален из нее, но добавлен в стек переходов назад. Остановленный фрагмент +по-прежнему активен (вся информация о состоянии и элементах сохранена в системе). Однако он больше +не виден пользователю и будет уничтожен в случае уничтожения операции.
+
+ +

Здесь снова просматривается аналогия с операцией: разработчик может сохранить состояние фрагмента с помощью {@link +android.os.Bundle} на случай, если процесс операции будет уничтожен, а разработчику понадобится восстановить +состояние фрагмента при повторном создании операции. Состояние можно сохранить во время выполнения метода обратного вызова {@link +android.app.Fragment#onSaveInstanceState onSaveInstanceState()} во фрагменте и восстановить его во время выполнения +{@link android.app.Fragment#onCreate onCreate()}, {@link +android.app.Fragment#onCreateView onCreateView()} или {@link +android.app.Fragment#onActivityCreated onActivityCreated()}. Дополнительные сведения о сохранении +состояния приводятся в документе Операции. +

+ +

Самое значительное различие в ходе жизненного цикла между операцией и фрагментом состоит в принципах +их сохранения в соответствующих стеках переходов назад. По умолчанию операция помещается в управляемый системой стек переходов назад для операций, +когда она останавливается (чтобы пользователь мог вернуться +к ней с помощью кнопки Назад, как описано в статье Задачи и стек переходов назад). +В то же время, фрагмент помещается в стек переходов назад, управляемый операцией, только когда разработчик +явно запросит сохранение конкретного экземпляра, вызвав метод {@link +android.app.FragmentTransaction#addToBackStack(String) addToBackStack()} во время транзакции, +удаляющей фрагмент.

+ +

В остальном управление жизненным циклом фрагмента очень похоже на управление жизненным циклом +операции. Поэтому практические рекомендации по управлению жизненным циклом +операций применимы и к фрагментам. При этом разработчику необходимо понимать, как жизненный цикл +операции влияет на жизненный цикл фрагмента.

+ +

Внимание! Если возникнет необходимость в объекте {@link android.content.Context} +внутри объекта класса {@link android.app.Fragment}, можно вызвать метод{@link android.app.Fragment#getActivity()}. +Однако разработчик должен быть внимательным и вызывать метод {@link android.app.Fragment#getActivity()} только когда фрагмент +прикреплен к операции. Если фрагмент еще не прикреплен или был откреплен в конце +его жизненного цикла, метод {@link android.app.Fragment#getActivity()} возвратит null.

+ + +

Согласование с жизненным циклом операции

+ +

Жизненый цикл операции, содержащей фрагмент, непосредственным образом влияет на жизненый цикл +фрагмента, так что каждый обратный вызов жизненного цикла операции приводит к аналогичному обратного вызову для каждого +фрагмента. Например, когда операция принимает вызов {@link android.app.Activity#onPause}, каждый +ее фрагмент принимает {@link android.app.Fragment#onPause}.

+ +

Однако у фрагментов есть несколько дополнительных методов обратного вызова жизненого цикла, которые обеспечивают уникальное взаимодействие с операцией +для выполнения таких действий, как создание и уничтожение пользовательского интерфейса фрагмента. Вот эти +методы:

+ +
+
{@link android.app.Fragment#onAttach onAttach()}
+
Вызывается, когда фрагмент связывается с операцией (ему передается объект {@link +android.app.Activity}).
+
{@link android.app.Fragment#onCreateView onCreateView()}
+
Вызывается для создания иерархии представлений, связанной с фрагментом.
+
{@link android.app.Fragment#onActivityCreated onActivityCreated()}
+
Вызывается, когда метод {@link android.app.Activity#onCreate +onCreate()}, принадлежащий операции, возвращает управление.
+
{@link android.app.Fragment#onDestroyView onDestroyView()}
+
Вызывается при удалении иерархии представлений, связанной с фрагментом.
+
{@link android.app.Fragment#onDetach onDetach()}
+
Вызывается при разрыве связи фрагмента с операцией.
+
+ +

Зависимость жизненого цикла фрагмента от содержащей его операции иллюстрируется +рисунком 3. На этом рисунке можно видеть, что очередное состояние операции определяет, какие +методы обратного вызова может принимать фрагмент. Например, когда операция принимает свой метод обратного вызова {@link +android.app.Activity#onCreate onCreate()}, фрагмент внутри этой операции принимает всего лишь метод обратного вызова +{@link android.app.Fragment#onActivityCreated onActivityCreated()}.

+ +

Когда операция переходит в состояние «возобновлена», можно свободно добавлять в нее фрагменты и удалять +их. Таким образом, жизненный цикл фрагмента может быть независимо изменен, только пока операция остается +в состоянии «возобновлена».

+ +

Однако, когда операция выходит из этого состояния, продвижение фрагмента по его +жизненному циклу снова осуществляется операцией.

+ + + + +

Пример:

+ +

Чтобы суммировать все сказанное в этом документе, рассмотрим пример операции, +использующей два фрагмента для создания макета с двумя панелями. Операция, код которой приведен ниже, включает в себя один фрагмент для +отображения списка пьес Шекспира, а другой — для отображения краткого содержания пьесы, выбранной +из списка. В примере показано, как следует организовывать различные конфигурации фрагментов +в зависимости от конфигурации экрана.

+ +

Примечание. Полный исходный код этой операции находится в разделе +{@code +FragmentLayout.java}.

+ +

Главная операция применяет макет обычным способом, в методе {@link +android.app.Activity#onCreate onCreate()}:

+ +{@sample development/samples/ApiDemos/src/com/example/android/apis/app/FragmentLayout.java main} + +

Здесь применяется макет {@code fragment_layout.xml}:

+ +{@sample development/samples/ApiDemos/res/layout-land/fragment_layout.xml layout} + +

Пользуясь этим макетом, система создает экземпляр класса {@code TitlesFragment} (список +пьес), как только операция загрузит макет. При этом объект {@link android.widget.FrameLayout} +(в котором будет находиться фрагмент с кратким содержанием) занимает место в правой части +экрана, но поначалу остается пустым. Как будет показано ниже, фрагмент не помещается в {@link android.widget.FrameLayout}, пока пользователь не выберет элемент +в списке.

+ +

Однако не все экраны достаточно широки, чтобы отображать +краткое содержание рядом со списком пьес. Поэтому описанный выше макет используется только при альбомной ориентации +экрана и хранится в файле {@code res/layout-land/fragment_layout.xml}.

+ +

Когда же устройство находится в книжной ориентации, система применяет макет, приведенный ниже, который +хранится в файле{@code res/layout/fragment_layout.xml}:

+ +{@sample development/samples/ApiDemos/res/layout/fragment_layout.xml layout} + +

В этом макете присутствует только объект {@code TitlesFragment}. Это означает, что при +книжной ориентации устройства виден только список пьес. Когда пользователь нажимает на элемент +списка в этой конфигурации, приложение запускает новую операцию для отображения краткого содержания, +а не загружает второй фрагмент.

+ +

Далее можно видеть, как это реализовано в классах фрагмента. Вначале идет код класса {@code +TitlesFragment}, отображающий список пьес Шекспира. Этот фрагмент является расширением класса {@link +android.app.ListFragment} и использует его функции для выполнения основной работы со списком.

+ +

Изучая код, обратите внимание на то, что в качестве реакции на нажатие пользователем +элемента списка возможны две модели поведения. В зависимости от того, какой из двух макетов активен, либо в рамках одной операции создается и отображается новый фрагмент +с кратким содержанием (за счет добавления фрагмента в объект {@link +android.widget.FrameLayout}), либо запускается новая операция (отображающая фрагмент).

+ +{@sample development/samples/ApiDemos/src/com/example/android/apis/app/FragmentLayout.java titles} + +

Второй фрагмент, {@code DetailsFragment}, отображает краткое содержание пьесы, выбранной в +списке {@code TitlesFragment}:

+ +{@sample development/samples/ApiDemos/src/com/example/android/apis/app/FragmentLayout.java details} + +

Вспомним код класса {@code TitlesFragment}: если пользователь нажимает на пункт списка, а +текущий макет не включает в себя представление {@code R.id.details} (которому принадлежит фрагмент +{@code DetailsFragment}), то приложение запускает операцию {@code DetailsActivity} +для отображения содержимого элемента.

+ +

Далее идет код класса {@code DetailsActivity}, который всего лишь содержит объект {@code DetailsFragment} для отображения +краткого содержания выбранной пьесы на экране в книжной ориентации:

+ +{@sample development/samples/ApiDemos/src/com/example/android/apis/app/FragmentLayout.java +details_activity} + +

Обратите внимание, что в альбомной конфигурации эта операция самостоятельно завершается, чтобы главная +операция могла принять управление и отобразить фрагмент {@code DetailsFragment} рядом с фрагментом{@code TitlesFragment}. +Это может произойти, если пользователь запустит операцию {@code DetailsActivity} в книжной ориентации экрана, а +затем перевернет устройство в альбомную ориентацию (в результате чего текущая операция будет перезапущена).

+ + +

Дополнительные образцы кода, использующего фрагменты (и файлы с полным исходным кодом этого примера), +доступны в приложении-примере API Demos в разделе +ApiDemos (которое можно загрузить из компонента Samples SDK).

+ + diff --git a/docs/html-intl/intl/ru/guide/components/fundamentals.jd b/docs/html-intl/intl/ru/guide/components/fundamentals.jd new file mode 100644 index 0000000000000000000000000000000000000000..181cbbdfb10771335b329e1db2fcbc7825cacb22 --- /dev/null +++ b/docs/html-intl/intl/ru/guide/components/fundamentals.jd @@ -0,0 +1,480 @@ +page.title=Основы создания приложений +@jd:body + + + +

Приложения для Android пишутся на языке программирования Java. Инструменты Android SDK (Software Development Kit – комплект разработки программного обеспечения) компилируют +написанный вами код — и все требуемые файлы данных и ресурсов — в файл APK – программный пакет Android, +который представляет собой файл архива с расширением {@code .apk}. В файле APK находится все, что требуется для работы +Android-приложения, и он позволяет установить приложение на любом устройстве под управлением системы Android.

+ +

Каждое приложение Android, установленное на устройстве, работает в собственной "песочнице" (изолированной программной среде):

+ +
    +
  • операционная система Android представляет собой многопользовательскую систему Linux, в которой каждое приложение является +отдельным пользователем;
  • + +
  • по умолчанию система назначает каждому приложению уникальный идентификатор пользователя Linux (этот идентификатор используется только +системой и неизвестен приложению); система устанавливает полномочия для всех файлов + в приложении, с тем чтобы доступ к ним был разрешен только пользователю с идентификатором, назначенным этому приложению;
  • + +
  • у каждого процесса имеется собственная виртуальная машина (ВМ), так что код приложения выполняется изолированно от +других приложений;
  • + +
  • по умолчанию каждое приложение выполняется в собственном процессе Linux. Android запускает процесс, когда требуется +выполнить какой-либо компонент приложения, а затем завершает процесс, когда он больше не +нужен либо когда системе требуется освободить память для других приложений.
  • +
+ +

Таким образом система Android реализует принцип предоставления минимальных прав. То есть +каждое приложение по умолчанию имеет доступ только к тем компонентам, которые ему необходимы для работы, и +ни к каким другим. Благодаря этому формируется исключительно безопасная среда, в которой приложение не имеет доступа к недозволенным областям +системы.

+ +

Однако у приложения есть варианты предоставления своих данных другим приложениям и +доступа к системным службам:

+ +
    +
  • двум приложениям можно назначить один идентификатор пользователя Linux. В этом случае +каждый из них сможет обращаться к файлам другого приложения. Для экономии ресурсов системы также можно +сделать так, чтобы приложения с одинаковым идентификатором пользователя выполнялись в одном процессе Linux и использовали одну ВМ ( +приложения также должны быть подписаны одним сертификатом);
  • +
  • приложение может запросить разрешение на доступ к данным устройства, например к контактам +пользователя, SMS-сообщениям, подключаемой карте памяти (SD-карте), камере, Bluetooth и др. Все +разрешения должны предоставляться приложению при его установке.
  • +
+ +

Это основные сведения о том, каким образом приложение Android существует в системе. В остальной части +этого документа раскрываются следующие темы:

+
    +
  • базовые компоненты, которые определяют приложение;
  • +
  • файл манифеста, в котором объявляются компоненты и функции устройства, необходимые для +приложения;
  • +
  • ресурсы, которые существуют отдельно от кода приложения и позволяют приложению +адаптировать свою работу к устройствам с различными конфигурациями.
  • +
+ + + +

Компоненты приложения

+ +

Компоненты приложения являются кирпичиками, из которых состоит приложение для Android. Каждый +компонент представляет собой отдельную точку, через которую система может войти в приложение. Не все +компоненты являются точками входа для пользователя, а некоторые из них зависят друг от друга. При этом каждый компонент является +самостоятельной структурной единицей и играет определенную роль — каждый из них представляет собой уникальный элемент структуры, который +определяет работу приложения в целом.

+ +

Компоненты приложения можно отнести к одному из четырех типов. Компоненты каждого типа предназначены для определенной цели, +они имеют собственный жизненный цикл, который определяет способ создания и прекращения существования компонента.

+ +

Четыре типа компонентов:

+ +
+ +
Операции
+ +
Операция (Activity) представляет собой один экран с пользовательским интерфейсом. Например, +в приложении для работы с электронной почтой одна операция может служить для отображения списка новых +сообщений, другая – для составления сообщения и третья операция – для чтения сообщений. Несмотря на то что +операции совместно формируют связное взаимодействие пользователя с приложением по работе с электронной почтой, каждая из них +не зависит от других операций. Любые из этих операций могут быть запущены +другим приложением (если это позволяет приложение по работе с электронной почтой). Например, приложение для камеры может запустить +операцию в приложении по работе с электронной почтой, которая составляет новое сообщение, чтобы пользователь мог отослать фотографию. + +

Операция относится к подклассу класса {@link android.app.Activity}. Подробные сведения об этом можно +найти в руководстве для разработчиков в статье Операции +.

+
+ + +
Службы
+ +
Служба (Service) представляет собой компонент, который работает в фоновом режиме и выполняет длительные +операции, связанные с работой удаленных процессов. Служба +не имеет пользовательского интерфейса. Например, она может воспроизводить музыку в фоновом режиме, пока +пользователь работает в другом приложении, или же она может получать данные по сети, не +блокируя взаимодействие пользователя с операцией. Служба может быть запущена другим компонентом, который затем будут взаимодействовать с ней, – например +операцией. + +

Служба относится к подклассу класса {@link android.app.Service}. Подробные сведения об этом можно +найти в руководстве для разработчиков в статье Службы +.

+
+ + +
Поставщики контента
+ +
Поставщик контента (Content provider) управляет общим набором данных приложения. Данные можно хранить в +файловой системе, базе данных SQLite, в Интернете или любом другом постоянном месте хранения, к которому у вашего +приложения имеется доступ. Посредством поставщика контента другие приложения могут запрашивать или даже изменять +данные (если поставщик контента позволяет делать это). Например, в системе Android есть поставщик +контента, который управляет информацией контактов пользователя. Любое приложение, получившее соответствующие +разрешения, может запросить часть этого поставщика контента (например {@link +android.provider.ContactsContract.Data}), для чтения и записи сведений об определенном человеке. + +

Поставщики контента также используются для чтения и записи данных, доступ к которым внешним компонентам +приложение не предоставляет. Например, в образце приложения Note Pad с помощью +поставщика контента выполняется сохранение заметок.

+ +

Поставщик контента относится к подклассу класса {@link android.content.ContentProvider}. +Он должен реализовывать стандартный набор API-интерфейсов, с помощью которых другие приложения будут выполнять +транзакции. Подробные сведения можно найти в руководстве для разработчиков в статье Поставщики контента +.

+
+ + +
Приемники широковещательных сообщений
+ +
Приемник широковещательных сообщений (Broadcast receiver) представляет собой компонент, который реагирует на объявления +распространяемые по всей системе. Многие из этих объявлений рассылает система — например объявление о том, +что экран выключился, аккумулятор разряжен или был сделан фотоснимок. +Объявления также могут рассылаться приложениями, — например, чтобы сообщить другим приложениям о том, что +какие-то данные были загружены на устройство и теперь готовы для использования. Несмотря на то что приемники широковещательных сообщений +не имеют пользовательского интерфейса, они могутсоздавать уведомления в строке состояния, +чтобы предупредить пользователя о событии "рассылка объявления". Однако чаще всего они являются +просто "шлюзом" для других компонентов и предназначены для выполнения минимального объема работы. Например +, они могут инициировать выполнение службой определенных действий при возникновении события. + +

Приемник широковещательных сообщений относится к подклассу класса {@link android.content.BroadcastReceiver} +, а каждое такое сообщение предоставляется как объект {@link android.content.Intent}. Подробные сведения изложены +в руководстве, посвященном классу {@link android.content.BroadcastReceiver}.

+
+ +
+ + + +

Уникальной особенностью системы Android является то, что любое приложение может запустить компонент +другого приложения. Например, если вы хотите дать пользователю возможность фотографировать, используя +камеру устройства, то, поскольку наверняка имеется другое приложение, которое может выполнить это действие, вместо того чтобы разработать операцию фотографирования в своем приложении, вы можете вызвать +такое приложение. Вам не +нужно внедрять код из приложения для камеры или даже устанавливать на него ссылку. +Вместо этого вы можете просто запустить операцию фотографирования + из приложения для камеры. По завершении этой операции фотография будет возвращена в ваше приложение, и ее можно будет использовать. Для пользователя + это будет выглядеть как одно приложение.

+ +

Когда система запускает компонент, она запускает процесс для этого приложения (если +он еще не был запущен) и создает экземпляры классов, которые требуются этому компоненту. Например, если ваше приложение +запустит операцию фотографирования в приложении для камеры, эта операция +будет выполняться в процессе, который относится к этому стороннему приложению, а не в процессе вашего приложения. +Поэтому, в отличие от приложений для большинства других систем, в приложениях для Android отсутствует единая +точка входа (например, в них нет функции {@code main()}).

+ +

Поскольку система выполняет каждое приложение в отдельном процессе с такими правами доступа к файлам, которые +ограничивают доступ в другие приложения, ваше приложение не может напрямую вызвать компонент из +другого приложения. Это может сделать сама система Android. Поэтому, чтобы вызвать компонент в +другом приложении, необходимо сообщить системе о своем намерении (Intent) +запустить определенный компонент. После этого система активирует для вас этот компонент.

+ + +

Активация компонентов

+ +

Компоненты трех из четырех возможных типов — операции, службы и +приемники широковещательных сообщений — активируются асинхронным сообщением, которое называется Intent (намерение). +Объекты Intent связывают друг с другом отдельные компоненты во время выполнения, будь то это компоненты + вашего или стороннего приложения (эти объекты Intent можно представить себе +в виде мессенджеров, которые посылают другим компонентам запрос на выполнение действий).

+ +

Объект Intent создается с помощью объекта {@link android.content.Intent}, который описывает запрос на +активацию либо конкретного компонента, либо компонента конкретного типа — соответственно, намерение Intent +может быть явным или неявным.

+ +

Для операций и служб Объект Intent определяет действие, которое требуется выполнить (например, просмотреть (view) или +отправить (send) что-то), а также может указывать URI (Uniform Resource Identifier – унифицированный идентификатор ресурса) данных, с которыми это действие нужно выполнить (помимо прочих сведений, которые +нужно знать запускаемому компоненту). Например, объект Intent может передавать запрос +на выполнение операции "показать изображение" или "открыть веб-страницу". В некоторых ситуациях операцию можно +запустить, чтобы получить результат. В этом случае операция возвращает +результат также в виде объекта {@link android.content.Intent} (например, можно отправить сообщение Intent, чтобы дать +пользователю возможность выбрать контакт и вернуть его вам — в ответном сообщении Intent будет содержаться +URI, указывающий на выбранный контакт).

+ +

Для приемников широковещательных сообщений Intent просто определяет +передаваемое объявление (например, широковещательное сообщение о низком уровне заряда аккумулятора +содержит только строку "аккумулятор разряжен").

+ +

Компоненты четвертого типа – поставщики контента – сообщениями Intent не активируются. Они +активируются по запросу от {@link android.content.ContentResolver}. Процедура определения + контента (content resolver) обрабатывает все прямые транзакции с поставщиком контента, с тем чтобы этого не пришлось делать компоненту, который +выполняет транзакции с поставщиком. Вместо этого он вызывает методы для объекта {@link +android.content.ContentResolver}. Это формирует слой, абстрагирующий (в целях безопасности) поставщика +контента от компонента, запрашивающего информацию.

+ +

Для активации компонентов каждого типа имеются отдельные методы:

+
    +
  • Можно запустить операцию (или определить для нее какое-то новое действие), +передав объект {@link android.content.Intent} методу {@link android.content.Context#startActivity +startActivity()} или {@link android.app.Activity#startActivityForResult startActivityForResult()} +(если требуется, чтобы операция вернула результат).
  • +
  • Можно запустить службу (либо выдать работающей службе новые инструкции), +передав объект {@link android.content.Intent} методу {@link android.content.Context#startService +startService()}. Либо можно установить привязку к службе, передав объект{@link android.content.Intent} методу +{@link android.content.Context#bindService bindService()}.
  • +
  • Можно инициировать рассылку сообщений, передав объект {@link android.content.Intent} таким методам, как +{@link android.content.Context#sendBroadcast(Intent) sendBroadcast()}, {@link +android.content.Context#sendOrderedBroadcast(Intent, String) sendOrderedBroadcast()} и {@link +android.content.Context#sendStickyBroadcast sendStickyBroadcast()}.
  • +
  • Можно выполнить запрос к поставщику контента, вызвав метод {@link +android.content.ContentProvider#query query()} для объекта {@link android.content.ContentResolver}.
  • +
+ +

Подробные сведения об использовании объектов Intent приведены в документе Объекты Intent и +фильтры объектов Intent. Более подробная информация об активации определенных компонентов также приведена +в следующих документах: Операции, Службы, {@link +android.content.BroadcastReceiver} и Поставщики контента.

+ + +

Файл манифеста

+ +

Для запуска компонента приложения системе Android необходимо знать, что +компонент существует. Для этого она читает файл {@code AndroidManifest.xml} приложения (файл +манифеста). В этом файле, который должен находиться в корневой папке +приложения, должны быть объявлены все компоненты приложения.

+ +

Помимо объявления компонентов приложения, манифест служит и для других целей, +среди которых:

+
    +
  • указание всех полномочий пользователя, которые требуются приложению, например разрешения на доступ в Интернет или +на чтение контактов пользователя;
  • +
  • объявление минимальногоуровня API, +требуемого приложению, с учетом того, какие API-интерфейсы оно использует;
  • +
  • объявление аппаратных и программных функций, которые нужны приложению или используются им, например камеры, +службы Bluetooth или сенсорного экрана;
  • +
  • указание библиотек API, с которыми необходимо связать приложение (отличные от API-интерфейсов платформы +Android), например библиотеки Google Maps +;
  • +
  • и многое другое.
  • +
+ + +

Объявление компонентов

+ +

Основная задача манифеста – это информировать систему о компонентах приложения. Например, + файл манифеста может объявлять операцию следующим образом:

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<manifest ... >
+    <application android:icon="@drawable/app_icon.png" ... >
+        <activity android:name="com.example.project.ExampleActivity"
+                  android:label="@string/example_label" ... >
+        </activity>
+        ...
+    </application>
+</manifest>
+ +

Атрибут {@code android:icon} в элементе <application> +указывает на ресурсы для значка, который обозначает +приложение.

+ +

Атрибут {@code android:name} в элементе <activity> +указывает полное имя класса подкласса {@link +android.app.Activity}, а атрибут {@code android:label} указывает строку, +которую необходимо использовать в качестве метки операции, отображаемой для пользователя.

+ +

Все компоненты приложения необходимо объявлять следующим образом:

+
    +
  • элементы <activity> +для операций;
  • +
  • элементы <service> +для служб;
  • +
  • элементы <receiver> +для приемников широковещательных сообщений;
  • +
  • элементы <provider> +для поставщиков контента
  • +
+ +

Системе не видны операции, службы и поставщики контента, которые имеются в исходном коде, но не объявлены +в манифесте, поэтому они не могут быть запущены. А вот +приемники широковещательных сообщений +можно либо объявить в манифесте, либо создать динамически в коде (как объекты +{@link android.content.BroadcastReceiver}) и зарегистрировать в системе путем вызова +{@link android.content.Context#registerReceiver registerReceiver()}.

+ +

Подробные сведения о структуризации файла манифеста для приложения см. в документе Файл AndroidManifest.xml +.

+ + + +

Объявление возможностей компонентов

+ +

Как уже говорилось в разделе Активация компонентов, с помощью объекта +{@link android.content.Intent} можно запускать операции, службы и приемники широковещательных сообщений. Для этого в объекте Intent следует +явно указать имя целевого компонента (с помощью имени класса компонента). Однако +в полной мере возможности объектов Intent раскрываются при использовании концепции неявных Intent. В неявном сообщении Intent +просто описывается тип действия, которое требуется выполнить (а также, хотя это и не обязательно, дата, в которую вы бы хотели +выполнить это действие). Системе же предоставляется возможности найти на устройстве компонент, который может выполнить это +действие, и запустить его. При наличии нескольких компонентов, которые могут выполнить действие, описанное в сообщении +Intent, пользователь выбирает, какой из них будет использоваться.

+ +

Система определяет компоненты, которые могут ответить на сообщение Intent, путем сравнения +полученного сообщения Intent с фильтрами объектов Intent, указанными в файле манифеста других приложений, имеющихся + на устройстве.

+ +

При объявлении операции в манифесте своего приложения по желанию можно указать +фильтры объектов Intent, которые указывают возможности операции, с тем чтобы она могла реагировать на сообщения Intent +от других приложений. Чтобы объявить фильтр Intent для своего компонента, +необходимо добавить элемент {@code +<intent-filter>} в качестве дочернего для элемента объявления компонента.

+ +

Например, если вы создали приложение для работы с электронной почтой с операцией составления нового сообщения, вы можете +объявить фильтр для ответа на сообщения Intent типа "send" (для отправки нового сообщения электронной почты) следующим образом:

+
+<manifest ... >
+    ...
+    <application ... >
+        <activity android:name="com.example.project.ComposeEmailActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.SEND" />
+                <data android:type="*/*" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </activity>
+    </application>
+</manifest>
+
+ +

Затем, если другое приложение создаст объект Intent с действием {@link +android.content.Intent#ACTION_SEND} и передаст его в {@link android.app.Activity#startActivity +startActivity()}, система сможет запустить вашу операцию, дав пользователю возможность написать и отправить +сообщение электронной почты.

+ +

Подробные сведения о создании фильтров объектов Intent приведены в документе Объекты Intent и фильтры объектов Intent. +

+ + + +

Объявление требований приложения

+ +

Существует огромное количество устройств, работающих под управлением Android, и не все они имеют +одинаковые функциональные возможности. Чтобы ваше приложение не могло быть установлено на устройствах, +в которых отсутствуют функции, необходимые приложению, важно четко определить профиль для +типов устройств, поддерживаемых вашим приложением, указав требования к аппаратному и программному обеспечению в +файле манифеста. Эти объявления по большей части носят информационный характер, система их не +читает. Однако их читают внешние службы, например Google Play, с целью обеспечения +фильтрации для пользователей, которые ищут приложения для своих устройств.

+ +

Например, если вашему приложению требуется камера и оно использует API-интерфейсы из Android 2.1 (уровень API 7), +эти параметры следует объявить в файле манифеста в качестве требований следующим образом:

+ +
+<manifest ... >
+    <uses-feature android:name="android.hardware.camera.any"
+                  android:required="true" />
+    <uses-sdk android:minSdkVersion="7" android:targetSdkVersion="19" />
+    ...
+</manifest>
+
+ +

Теперь ваше приложение нельзя будет установить из Google Play на устройствах, в которых нет камеры, а также на устройствах, работающих под управлением +Android версии ниже 2.1.

+ +

Однако можно также объявить, что приложение использует камеру, но для его работы она не является +непременно необходимой. В этом случае в приложении атрибуту {@code required} +необходимо задать значение {@code "false"}, а во время работы оно должно проверять, имеется ли +на устройстве камера, и при необходимости отключать свои функции, которые используют камеру.

+ +

Более подробные сведения об управлении совместимостью своего приложения с различными устройствами +приведены в документе Совместимость устройств +.

+ + + +

Ресурсы приложения

+ +

Приложение Android состоит не только из кода — ему необходимы такие существующие отдельно от исходного кода +ресурсы, как изображения, аудиофайлы и все, что связано с визуальным +представлением приложения. Например, необходимо определять анимацию, меню, стили, цвета +и макет пользовательских интерфейсов операций в файлах XML. Используя ресурсы приложения, можно без труда +изменять его различные характеристики, не меняя код, а, кроме того, —путем предоставления +наборов альтернативных ресурсов — можно оптимизировать свое приложение для работы с различными +конфигурациями устройств (например, для различных языков или размеров экрана).

+ +

Для каждого ресурса, включаемого в проект Android, инструменты SDK задают уникальный +целочисленный идентификатор, который может использоваться, чтобы сослаться на ресурс из кода приложения или из +других ресурсов, определенных в XML. Например, если в вашем приложении имеется файл изображения с именем {@code +logo.png} (сохраненный в папке {@code res/drawable/}), инструменты SDK сформируют идентификатор ресурса +под именем {@code R.drawable.logo}, с помощью которого на изображение можно будет ссылаться и вставлять его в +пользовательский интерфейс.

+ +

Один из наиболее важных аспектов предоставления ресурсов отдельно от исходного кода +заключается в возможности использовать альтернативные ресурсы для различных конфигураций +устройств. Например, определив строки пользовательского интерфейса в XML, вы сможете перевести их на другие +языки и сохранить эти переводы в отдельных файлах. Затем по квалификатору языка +, добавленному к имени каталога ресурса (скажем {@code res/values-fr/} для строк на французском +языке), и выбранному пользователем языку система Android применит к вашему пользовательскому интерфейсу строки на +соответствующем языке.

+ +

Android поддерживает разные квалификаторы для соответствующих ресурсов. Квалификатор + представляет собой короткую строку, которая включается в имена каталогов ресурсов с целью +определения конфигурации устройства, для которой эти ресурсы следует использовать. В качестве другого +примера можно сказать, что для своих операций следует создавать разные макеты, которые будут соответствовать +размеру и ориентации экрана устройства. Например, когда экран устройства имеет книжную +ориентацию (расположен вертикально), кнопки в макете можно также размещатьь по вертикали, а когда экран +развернут горизонтально (альбомная ориентация), кнопки следует размещать по горизонтали. Чтобы при изменении ориентации экрана изменялся макет, +можно определить два разных макета и применить соответствующий +квалификатор к имени каталога каждого макета. После этого система будет автоматически применять соответствующий +макет в зависимости от ориентации устройства.

+ +

Подробные сведения о различных видах ресурсов, которые можно включить в приложение, а также о том, как +создавать альтернативные ресурсы для разных конфигурацией устройств, см. в разделе Предоставление ресурсов.

+ + + +
+
+

Также читайте:

+
+
Объекты Intent и фильтры объектов Intent +
+
Сведения об использовании API-интерфейсов {@link android.content.Intent} для + активации таких компонентов приложений, как операции и службы, а также о предоставлении возможности другим приложениям + использовать компоненты своего приложения.
+
Операции
+
Сведения о создании экземпляра класса {@link android.app.Activity}, + который выдает определенный экран в вашем приложении с пользовательским интерфейсом.
+
Предоставление ресурсов
+
Описание структуры приложений Android, в которой ресурсы приложения существуют отдельно от + его кода, а также сведения о том, как предоставлять альтернативные ресурсы для определенных конфигураций + устройств. +
+
+
+
+

Возможно, вас также заинтересует:

+
+
Совместимость устройств
+
Сведения о том, каким образом система Android работает на устройствах разных типов, и общие сведения о том, + как оптимизировать свое приложение для каждого устройства или ограничить круг устройств, на которых может быть установлено + приложение.
+
Системные разрешения
+
Сведения о том, как система Android ограничивает доступ приложений к определенным API-интерфейсам с помощью системы + разрешений, которая требует согласия пользователя на использование этих API-интерфейсов вашим приложением.
+
+
+
+ diff --git a/docs/html-intl/intl/ru/guide/components/index.jd b/docs/html-intl/intl/ru/guide/components/index.jd new file mode 100644 index 0000000000000000000000000000000000000000..41d5a3491bf2ecda71feb25dc8717cb9e94f75e7 --- /dev/null +++ b/docs/html-intl/intl/ru/guide/components/index.jd @@ -0,0 +1,57 @@ +page.title=Компоненты приложения +page.landing=true +page.landing.intro=Платформа приложений системы Android позволяет создавать функциональные и инновационные приложения с помощью набора компонентов, которые можно использовать многократно. В этом разделе рассказывается о том, как создавать компоненты, определяющие элементы структуры вашего приложения, и как связывать их воедино с помощью объектов Intent. +page.metaDescription=Платформа приложений системы Android позволяет создавать функциональные и инновационные приложения с помощью набора компонентов, которые можно использовать многократно. В этом разделе рассказывается о том, как создавать компоненты, определяющие элементы структуры вашего приложения, и как связывать их воедино с помощью объектов Intent. +page.landing.image=images/develop/app_components.png +page.image=images/develop/app_components.png + +@jd:body + +
+ +
+

Статьи блога

+ + +

Использование класса DialogFragment

+

В этой статье я расскажу, как с помощью DialogFragment с использованием вспомогательной библиотеки v4 (в целях обеспечения совместимости с устройствами, работающими под управлением системы с версией ниже, чем Honeycomb) можно отобразить простое диалоговое окно редактирования и вернуть результат в вызывающую операцию с помощью интерфейса.

+
+ + +

Фрагменты для всех

+

Сегодня мы выпустили библиотеку статических элементов, которая предоставляет доступ к тому же Fragments API (а также новому классу LoaderManager и нескольким другим классам), с тем чтобы приложения, совместимые с Android 1.6 и более поздними версиями, могли использовать фрагменты для создания пользовательских интерфейсов для планшетов.

+
+ + +

Многопоточность для повышения производительности

+

Для создания быстро реагирующих приложений рекомендуется, чтобы в основном потоке пользовательского интерфейса +выполнялся минимальный объем работы. Любая задача, которая в принципе может выполнять долго и привести к зависанию приложения, должна +обрабатываться в другом потоке.

+
+
+ +
+

Обучение

+ + +

Управление жизненным циклом операций

+

В этом учебном курсе разъясняются важные методы обратного вызова жизненного цикла, которые получает каждый экземпляр + операции, и описывается, как их использовать, чтобы операция выполнялась так, как этого ожидает пользователь, и не потребляла системные + ресурсы, когда они ей не нужны.

+
+ + +

Создание динамического интерфейса пользователя с использованием фрагментов

+

Данный курс обучения посвящен созданию динамического интерфейса пользователя с использованием +фрагментов и его оптимизации для устройств с экранами разных размеров, включая поддержку +устройств с версией Android 1.6.

+
+ + +

Общий доступ к контенту

+

В этом курсе обучения рассказывается о некоторых стандартных способах отправки и получения контента + приложениями с помощью API-интерфейсов Intent и объекта ActionProvider.

+
+
+ +
diff --git a/docs/html-intl/intl/ru/guide/components/intents-filters.jd b/docs/html-intl/intl/ru/guide/components/intents-filters.jd new file mode 100644 index 0000000000000000000000000000000000000000..d710081e4181141a9bfbcca0dadccd0ae26dfefd --- /dev/null +++ b/docs/html-intl/intl/ru/guide/components/intents-filters.jd @@ -0,0 +1,899 @@ +page.title=Объекты Intent и фильтры объектов Intent +page.tags="IntentFilter" +@jd:body + + + + + + +

{@link android.content.Intent} представляет собой объект обмена сообщениями, с помощью которого можно запросить выполнение действия +у компонента другого приложения. +Несмотря на то, что объекты Intent упрощают обмен данными между компонентами по нескольким аспектам, в основном они используются +в трех ситуациях:

+ +
    +
  • Для запуска операции: +

    Компонент {@link android.app.Activity} представляет собой один экран в приложении. Для запуска нового +экземпляра компонента {@link android.app.Activity} необходимо передать объект {@link android.content.Intent} +методу{@link android.content.Context#startActivity startActivity()}. Объект {@link android.content.Intent} +описывает операцию, которую требуется запустить, а также содержит все остальные необходимые данные.

    + +

    Если после завершения операции от нее требуется получить результат, +вызовите метод {@link android.app.Activity#startActivityForResult +startActivityForResult()}. Ваша операция получит результат +в виде отдельного объекта {@link android.content.Intent} в обратном вызове метода {@link +android.app.Activity#onActivityResult onActivityResult()} операции. +Подробные сведения см. в руководстве Операции.

  • + +
  • Для запуска службы: +

    {@link android.app.Service} является компонентом, который выполняет действия в фоновом режиме +без пользовательского интерфейса. Службу можно запустить для выполнения однократного действия +(например, чтобы загрузить файл), передав объект{@link android.content.Intent} +методу {@link android.content.Context#startService startService()}. Объект {@link android.content.Intent} +описывает службу, которую требуется запустить, а также содержит все остальные необходимые данные.

    + +

    Если служба сконструирована с интерфейсом клиент-сервер, к ней +можно установить привязку из другого компонента, передав объект{@link android.content.Intent} методу {@link +android.content.Context#bindService bindService()}. Подробные сведения см. в руководстве Службы.

  • + +
  • Для рассылки широковещательных сообщений: +

    Широковещательное сообщение ― это сообщение, которое может принять любое приложение. Система выдает различные +широковещательные сообщения о системных событиях, например, когда система загружается или устройство начинает заряжаться. +Для выдачи широковещательных сообщений другим приложениям необходимо передать объект {@link android.content.Intent} +методу {@link android.content.Context#sendBroadcast(Intent) sendBroadcast()}, +{@link android.content.Context#sendOrderedBroadcast(Intent, String) +sendOrderedBroadcast()} или {@link +android.content.Context#sendStickyBroadcast sendStickyBroadcast()}.

    +
  • +
+ + + + +

Типы объектов Intent

+ +

Есть два типа объектов Intent:

+ +
    +
  • Явные объекты Intent указывают компонент, который требуется запустить, по имени ( +полное имя класса). Явные объекты Intent обычно используются для запуска компонента из +вашего собственного приложения, поскольку вам известно имя класса операции или службы, которую необходимо запустить. Например +, можно запустить новую операцию в ответ на действие пользователя или запустить службу, чтобы загрузить +файл в фоновом режиме.
  • + +
  • Неявные объекты Intent не содержат имени конкретного компонента. Вместо этого они в целом объявляют действие, +которое требуется выполнить, что дает возможность компоненту из другого приложения обработать этот запрос. Например, если требуется +показать пользователю место на карте, то с помощью неявного объекта Intent можно запросить, чтобы это сделало другое приложение, в котором +такая возможность предусмотрена.
  • +
+ +

Когда создан явный объект Intent для запуска операции или службы, система немедленно +запускает компонент приложения, указанный в объекте {@link android.content.Intent}.

+ +
+ +

Рисунок 1. Схематическое изображение процесса передачи неявного объекта Intent +по системе для запуска другой операции: [1] Операция А создает объект +{@link android.content.Intent} с описанием действия и передает его методу {@link +android.content.Context#startActivity startActivity()}. [2] Система Android ищет во всех +приложениях фильтры Intent, которые соответствуют данному объекту Intent. Когда приложение с подходящим фильтром найдено, [3] система +запускает соответствующую операцию (Операция B), вызвав ее метод {@link +android.app.Activity#onCreate onCreate()} и передав ему объект {@link android.content.Intent}. +

+
+ +

Когда создан неявный объект Intent, система Android находит подходящий компонент путем +сравнения содержимого объекта Intent с фильтрами Intent, объявленными в файлах манифеста других приложений, имеющихся на +устройстве. Если объект Intent совпадает с фильтром Intent, система запускает этот компонент и передает ему +объект {@link android.content.Intent}. Если подходящими оказываются несколько фильтров Intent, система +выводит диалоговое окно, где пользователь может выбрать приложение для выполнения данного действия.

+ +

Фильтр Intent представляет собой выражение в файле манифеста приложения, +указывающее типы объектов Intent, которые мог бы +принимать компонент. Например, объявив фильтр Intent для операции, +вы даете другим приложениям возможность напрямую запускать вашу операцию с помощью некоторого объекта Intent. +Точно так же, если вы не объявите какие-либо фильтры Intent для операции, то ее можно будет запустить +только с помощью явного объекта Intent.

+ +

Внимание! В целях обеспечения безопасности приложения всегда используйте явный объект +Intent при запуске {@link android.app.Service} и не +объявляйте фильтры Intent для своих служб. Запуск служб с помощью неявных объектов Intent является +рискованным с точки зрения безопасности, поскольку нельзя быть на абсолютно уверенным, какая служба отреагирует на такой объект Intent, +а пользователь не может видеть, какая служба запускается. Начиная с Android 5.0 (уровень API 21) система +вызывает исключение при вызове метода {@link android.content.Context#bindService bindService()} +с помощью неявного объекта Intent.

+ + + + + +

Создание объекта Intent

+ +

Объект {@link android.content.Intent} содержит информацию, на основании которой система Android +определяет, какой компонент требуется запустить (например, точное имя компонента или категорию +компонентов, которые должны получить этот объект Intent), а также сведения, которые необходимы компоненту-получателю, +чтобы надлежащим образом выполнить действие (а именно — выполняемое действие и данные, с которыми его требуется выполнить).

+ + +

Основные сведения, содержащиеся в объекте {@link android.content.Intent}:

+ +
+ +
Имя компонента
+
Имя компонента, который требуется запустить. + +

Эта информация является необязательной, но именно она и делает объект Intent +явным. Ее наличие означает, что объект Intent следует доставить только компоненту приложения, +определенному по имени. При отсутствии имени компонента объект Intent является неявным, а +система определяет, какой компонент получит этот объект Intent по другим сведениям, которые в нем содержатся +(например, по действию, данным и категории — см. описание далее). Поэтому, если вам требуется запустить определенный +компонент из своего приложения, следует указать его имя.

+ +

Примечание. При запуске {@link android.app.Service} следует +всегда указывать имя компонента. В противном случае вы не сможете быть на абсолютно уверенным в том, какая служба +отреагирует на объект Intent, а пользователь не может видеть, какая служба запускается.

+ +

Это поле объекта {@link android.content.Intent} представляет собой объект +{@link android.content.ComponentName}, который можно указать с помощью полного +имени класса целевого компонента, включая имя пакета приложения. Например, +{@code com.example.ExampleActivity}. Задать имя компонента можно с помощью метода {@link +android.content.Intent#setComponent setComponent()}, {@link android.content.Intent#setClass +setClass()}, {@link android.content.Intent#setClassName(String, String) setClassName()} или конструктора +{@link android.content.Intent}.

+ +
+ +

Действие
+
Строка, определяющая стандартное действие, которое требуется выполнить (например, view (просмотр) или pick (выбор)). + +

При выдаче объектов Intent с широковещательными сообщениями это действие, которое произошло и о котором сообщается. +Действие в значительной степени определяет, каким образом структурирована остальная часть объекта Intent,—в частности, +что именно содержится в разделе данных и дополнительных данных. + +

Для использования объектами Intent в пределах своего приложения (либо для использования другими +приложениями, чтобы вызывать компоненты из вашего приложения) можно указать собственные действия. Обычно же следует использовать константы действий, +определенные классом {@link android.content.Intent} или другими классами платформы. Вот несколько +стандартных действий для запуска операции:

+ +
+
{@link android.content.Intent#ACTION_VIEW}
+
Используйте это действие в объекте Intent с методом {@link + android.content.Context#startActivity startActivity()}, когда имеется определенная информация, которую + операция может показать пользователю, например, фотография в приложении галереи или адрес для + просмотра в картографическом приложении.
+ +
{@link android.content.Intent#ACTION_SEND}
+
Его еще называют объектом Intent "share" (намерение предоставить общий доступ). Это действие следует использовать в объекте Intent с методом {@link + android.content.Context#startActivity startActivity()}, при наличии определенных данных, доступ к которым пользователь может + предоставить через другое приложение, например приложение для работы с электронной почтой или социальными сетями.
+
+ +

Другие константы, определяющие стандартные действия, см. в справочнике по классу {@link android.content.Intent} +. Другие действия определяются +в других частях платформы Android. Например, в {@link android.provider.Settings} определяются действия, +открывающие ряд экранов приложения настройки системы.

+ +

Действие можно указать для объекта Intent с методом {@link android.content.Intent#setAction +setAction()} или конструктором {@link android.content.Intent}.

+ +

Если вы определяете собственные действия, обязательно используйте в качестве их префикса имя пакета +вашего приложения. Например:

+
static final String ACTION_TIMETRAVEL = "com.example.action.TIMETRAVEL";
+
+ +
Данные
+
URI (объект {@link android.net.Uri}), ссылающийся на данные, с которыми будет выполняться действие и/или +тип MIME этих данных. Тип передаваемых данных обычно определяется действием объекта Intent. Например, +если действием является {@link android.content.Intent#ACTION_EDIT}, в данных должен содержаться +URI документа, который требуется отредактировать. + +

При создании объекта Intent, +помимо URI, зачастую бывает важно указать тип данных (их тип MIME). +Например, операция, которая может выводить на экран изображения, скорее всего, не сможет +воспроизвести аудиофайл, даже если и у тех, и у других данных будут одинаковые форматы URI. +Поэтому указание типа MIME данных помогает системе Android +найти наиболее подходящий компонент для получения вашего объекта Intent. +Однако тип MIME иногда можно унаследовать от URI — в частности, когда данные представляют собой +{@code content:} URI, который указывает, что данные находятся на устройстве и ими управляет +{@link android.content.ContentProvider}, а это дает возможность системе видеть тип MIME данных.

+ +

Чтобы задать только URI данных, вызовите {@link android.content.Intent#setData setData()}. +Чтобы задать только тип MIME, вызовите {@link android.content.Intent#setType setType()}. При необходимости +оба этих параметра можно в явном виде задать с помощью {@link +android.content.Intent#setDataAndType setDataAndType()}.

+ +

Внимание! Если требуется задать и URI, и тип MIME, +не вызывайте {@link android.content.Intent#setData setData()} и +{@link android.content.Intent#setType setType()}, поскольку каждый из этих методов аннулирует результат выполнения другого. +Чтобы задать URI и тип MIME всегда используйте + метод {@link android.content.Intent#setDataAndType setDataAndType()}.

+
+ +

Категория
+
Строка, содержащая прочие сведения о том, каким компонентом +должна выполняться обработка этого объекта Intent. В объект Intent можно поместить любое количество +описаний категорий, однако большинству объектов Intent категория не требуется. +Вот некоторые стандартные категории: + +
+
{@link android.content.Intent#CATEGORY_BROWSABLE}
+
Целевая операция позволяет запускать себя веб-браузером для отображения данных, + указанных по ссылке — например, изображения или сообщения электронной почты. +
+
{@link android.content.Intent#CATEGORY_LAUNCHER}
+
Эта операция является начальной операцией задачи, она указана в + средстве запуска приложений системы. +
+
+ +

Полный список категорий см. в описании класса {@link android.content.Intent} +.

+ +

Указать категорию можно с помощью{@link android.content.Intent#addCategory addCategory()}.

+
+
+ + +

Приведенные выше свойства (имя компонента, действие, данные и категория) представляют собой +характеристики, определяющие объект Intent. На основании этих свойств система Android +может решить, какой компонент следует запустить.

+ +

Однако в объекте Intent может быть приведена и другая информация, которая не влияет на то, +каким образом определяется требуемый компонент приложения. Объект Intent также может содержать:

+ +
+
Дополнительные данные
+
Пары "ключ-значение", содержащие прочую информацию, которая необходима для выполнения запрошенного действия. +Точно так же, как некоторые действия используют определенные виды URI данных, некоторые действия используют определенные дополнительные данные. + +

Добавлять дополнительные данные можно с помощью различных методов {@link android.content.Intent#putExtra putExtra()}, +каждый из которых принимает два параметра: имя и значение ключа. +Также можно создать объект {@link android.os.Bundle} со всеми дополнительными данными, затем вставить +объект {@link android.os.Bundle} в объект {@link android.content.Intent} с помощью метода {@link +android.content.Intent#putExtras putExtras()}.

+ +

Например, при создании объекта Intent для отправки сообщения электронной почты с методом +{@link android.content.Intent#ACTION_SEND} можно указать получателя с помощью ключа +{@link android.content.Intent#EXTRA_EMAIL}, а тему сообщения ― с помощью ключа +{@link android.content.Intent#EXTRA_SUBJECT}.

+ +

Класс {@link android.content.Intent} указывает много констант {@code EXTRA_*} +для стандартных типов данных. Если вам требуется объявить собственные дополнительные ключи (для объектов Intent, которые +принимает ваше приложение), обязательно указывайте в качестве префикса +имя пакета своего приложения. Например:

+
static final String EXTRA_GIGAWATTS = "com.example.EXTRA_GIGAWATTS";
+
+ +
Флаги
+
Флаги, определенные в классе {@link android.content.Intent}, которые действуют как метаданные для объекта +Intent. Флаги должны указывать системе Android, каким образом следует запускать операцию (например, к какой +задаче должна принадлежать операция +) и как с ней обращаться после запуска (например, будет ли она указана в списке последних +операций). + +

Подробные сведения см. в документе, посвященном методу {@link android.content.Intent#setFlags setFlags()}.

+
+ +
+ + + + +

Пример явного объекта Intent

+ +

Явные объекты Intent используются для запуска конкретных компонентов приложения, например +определенной операции или службы. Чтобы создать явный объект Intent, задайте +имя компонента для объекта {@link android.content.Intent} — все +остальные свойства объекта Intent можно не задавать.

+ +

Например, если в своем приложении вы создали службу с именем {@code DownloadService}, +предназначенную для загрузки файлов из Интернета, то для ее запуска можно использовать следующий код:

+ +
+// Executed in an Activity, so 'this' is the {@link android.content.Context}
+// The fileUrl is a string URL, such as "http://www.example.com/image.png"
+Intent downloadIntent = new Intent(this, DownloadService.class);
+downloadIntent.setData({@link android.net.Uri#parse Uri.parse}(fileUrl));
+startService(downloadIntent);
+
+ +

Конструктор {@link android.content.Intent#Intent(Context,Class)} +предоставляет {@link android.content.Context} приложению, а +компоненту ― объект {@link java.lang.Class}. Фактически +этот объект Intent явно запускает класс{@code DownloadService} в приложении.

+ +

Подробные сведения о создании и запуске службы см. в руководстве +Службы.

+ + + + +

Пример неявного объекта Intent

+ +

Неявный объект Intent указывает действие, которым может быть вызвано любое имеющееся на устройстве приложение, способное +выполнить это действие. Неявные объекты Intent используются, когда ваше приложение не может выполнить +то или иное действие, а другие приложения, скорее всего, могут и вы хотите, чтобы пользователь имел возможность выбрать, какое приложение для этого использовать.

+ +

Например, если у вас есть контент и вы хотите, чтобы пользователь поделился им с другими людьми, создайте объект Intent +с действием {@link android.content.Intent#ACTION_SEND} +и добавьте дополнительные данные, указывающие на контент, общий доступ к которому следует предоставить. Когда с помощью этого объекта Intent вы вызываете +{@link android.content.Context#startActivity startActivity()}, пользователь сможет +выбрать приложение, посредством которого к контенту будет предоставлен общий доступ.

+ +

Внимание! Возможна ситуация, когда на устройстве пользователя не будетникакого +приложения, которое может откликнуться на неявный объект Intent, отправленный вами методу {@link android.content.Context#startActivity +startActivity()}. В этом случае вызов закончится неудачей, а работа приложения аварийно завершится. Чтобы проверить, +будет получен ли операцией объект Intent, вызовите метод {@link android.content.Intent#resolveActivity +resolveActivity()} для своего объекта {@link android.content.Intent}. Если результатом будет значение, отличное от null, +значит, имеется хотя бы одно приложение, которое способно откликнуться на объект Intent и можно вызывать +{@link android.content.Context#startActivity startActivity()}. Если же результатом будет значение null, +объект Intent не следует использовать и по возможности следует отключить функцию, которая выдает +этот объект Intent.

+ + +
+// Create the text message with a string
+Intent sendIntent = new Intent();
+sendIntent.setAction(Intent.ACTION_SEND);
+sendIntent.putExtra(Intent.EXTRA_TEXT, textMessage);
+sendIntent.setType("text/plain");
+
+// Verify that the intent will resolve to an activity
+if (sendIntent.resolveActivity(getPackageManager()) != null) {
+    startActivity(sendIntent);
+}
+
+ +

Примечание. В этом случае URI не используется, а вместо этого следует объявить +тип данных объекта Intent, чтобы указать контент, содержащийся в дополнительных данных.

+ + +

При вызове метода {@link android.content.Context#startActivity startActivity()} система +анализирует все установленные приложения, чтобы определить, какие из них могут откликнуться на объект Intent этого вида (объект +Intent с действием {@link android.content.Intent#ACTION_SEND} и данными "text/plain" +). Если имеется только одно подходящее приложение, оно будет сразу же открыто и получит данный объект +Intent. Если объект Intent принимают несколько операций, система +отображает диалоговое окно, в котором пользователь может выбрать приложение для выполнения данного действия.

+ + +
+ +

Рисунок 2. Диалоговое окно выбора.

+
+ +

Принудительное использование блока выбора приложения

+ +

При наличии нескольких приложений, откликающихся на ваш неявный объект Intent, +пользователь может выбрать требуемое приложение и указать, что оно будет по умолчанию выполнять это +действие. Это удобно в случае действия, для выполнения которого +пользователь обычно хочет всегда использовать одно и то же приложение, например, при открытии веб-страницы (пользователи +обычно используют один и тот же браузер).

+ +

Однако если на объект Intent могут откликнуться несколько приложений, возможно, пользователь предпочтет каждый раз использовать другое +приложение, поэтому следует явно выводить диалоговое окно выбора. В диалоговом окне +выбора приложения пользователю предлагается при каждом запуске выбирать, какое приложение использовать для действия (пользователь не может выбрать приложение, +используемое по умолчанию). Например, когда ваше приложение выполняет операцию "share" (поделиться) с помощью действия {@link +android.content.Intent#ACTION_SEND}, пользователи могут, в зависимости от ситуации, предпочесть каждый раз делать это с помощью разных приложений +, поэтому следует всегда использовать диалоговое окно выбора, как показано на рисунке 2.

+ + + + +

Чтобы вывести на экран блок выбора приложения, создайте {@link android.content.Intent} с помощью {@link +android.content.Intent#createChooser createChooser()} и передайте его {@link +android.app.Activity#startActivity startActivity()}. Например:

+ +
+Intent sendIntent = new Intent(Intent.ACTION_SEND);
+...
+
+// Always use string resources for UI text.
+// This says something like "Share this photo with"
+String title = getResources().getString(R.string.chooser_title);
+// Create intent to show the chooser dialog
+Intent chooser = Intent.createChooser(sendIntent, title);
+
+// Verify the original intent will resolve to at least one activity
+if (sendIntent.resolveActivity(getPackageManager()) != null) {
+    startActivity(chooser);
+}
+
+ +

В результате на экран будет выведено диалоговое окно со списком приложений, которые могут отреагировать на объект Intent, переданный методу {@link +android.content.Intent#createChooser createChooser()} и используют указанный текст в качестве +заголовка диалога.

+ + + + + + + + + +

Получение неявного объекта Intent

+ +

Чтобы указать, какие неявные объекты Intent может принимать ваше приложение, объявите один или несколько фильтров Intent для +каждого компонента приложения с помощью элемента {@code <intent-filter>}файле манифеста. +Каждый фильтр Intent указывает тип объектов Intent, которые принимает компонент на основании действия, +данных и категории, заданных в объекте Intent. Система передаст неявный объект Intent вашему приложению, только если +он может пройти через один из ваших фильтров Intent.

+ +

Примечание. Явный объект Intent всегда доставляется его целевому компоненту, +без учета любых фильтров Intent, объявленных компонентом.

+ +

Компонент приложения должен объявлять отдельные фильтры для каждой уникальной работы, которую он может выполнить. +Например, у операции из приложения для работы с галереей изображений может быть два фильтра: один фильтр +для просмотра изображения, и второй для его редактирования. Когда операция запускается, +она анализирует объект {@link android.content.Intent} и выбирает режим своей работы на основании информации, +приведенной в {@link android.content.Intent} (например, показывать элементы управления редактора или нет).

+ +

Каждый фильтр Intent определяется элементом {@code <intent-filter>} +в файле манифеста приложения, указанном в объявлении соответствующего компонента приложения (например, +в элементе {@code <activity>}). + Внутри элемента {@code <intent-filter>}, +можно указать тип объектов Intent, которые будут приниматься, с помощью одного или нескольких +из следующих трех элементов:

+ +
+
{@code <action>}
+
Объявляет принимаемое действие, заданное в объекте Intent, в атрибуте {@code name}. Значение + должно быть текстовой строкой действия, а не константой класса.
+
{@code <data>}
+
Объявляет тип принимаемых данных, для чего используется один или несколько атрибутов, указывающих различные + составные части URI данных (scheme, host, port, + path и т. д.) и тип MIME.
+
{@code <category>}
+
Объявляет принимаемую категорию, заданную в объекте Intent, в атрибуте {@code name}. Значение + должно быть текстовой строкой действия, а не константой класса. + +

Примечание. Для получения неявных объектов Intent + необходимо включить категорию +{@link android.content.Intent#CATEGORY_DEFAULT} в фильтр Intent. Методы + {@link android.app.Activity#startActivity startActivity()} и + {@link android.app.Activity#startActivityForResult startActivityForResult()} обрабатывают все объекты Intent, как если бы они объявляли + категорию{@link android.content.Intent#CATEGORY_DEFAULT}. + Если вы не объявляете эту категорию в своем фильтре Intent, никакие неявные объекты Intent не будут переданы в +вашу операцию.

+
+
+ +

В следующем примере объявлена операция с фильтром Intent, определяющим получение объекта Intent +{@link android.content.Intent#ACTION_SEND}, когда данные относятся к типу text:

+ +
+<activity android:name="ShareActivity">
+    <intent-filter>
+        <action android:name="android.intent.action.SEND"/>
+        <category android:name="android.intent.category.DEFAULT"/>
+        <data android:mimeType="text/plain"/>
+    </intent-filter>
+</activity>
+
+ +

Можно создавать фильтры, в которых будет несколько экземпляров +{@code <action>}, +{@code <data>} или +{@code <category>}. +В этом случае просто нужно убедиться в том, что компонент может справиться с любыми сочетаниями +этих элементов фильтра.

+ +

Когда требуется обрабатывать объекты Intent нескольких видов, но только в определенных сочетаниях +действия, типа данных и категории, необходимо создать несколько фильтров Intent.

+ + + + +

Неявный объект Intent проверяется фильтром путем сравнения объекта Intent с каждым из +этих трех элементов. Чтобы объект Intent был доставлен компоненту, он должен пройти все три теста. +Если он не будет соответствовать хотя бы одному из них, система Android не доставит этот объект Intent +компоненту. Однако, поскольку у компонента может быть несколько фильтров Intent, объект Intent, который +не проходит через один из фильтров компонента, может пройти через другой фильтр. +Подробные сведения о том, каким образом система принимает решения по поводу объектов Intent, см. в приведенном далее разделе + Разрешение объектов Intent.

+ +

Внимание! Чтобы случайно не запустить +{@link android.app.Service} другого приложения, всегда используйте явные объекты Intent для запуска собственных служб и не +объявляйте для них фильтры Intent.

+ +

Примечание. +Фильтры Intent необходимо объявлять в файле манифеста для всех операций. +Фильтры для приемников широковещательных сообщений можно регистрировать динамически путем вызова +{@link android.content.Context#registerReceiver(BroadcastReceiver, IntentFilter, String, +Handler) registerReceiver()}. После этого регистрацию приемника широковещательных сообщений можно отменить с помощью {@link +android.content.Context#unregisterReceiver unregisterReceiver()}. В результате ваше приложение +сможет воспринимать определенные объявления только в течение указанного периода времени в процессе работы +приложения.

+ + + + + + + +

Примеры фильтров

+ +

Чтобы лучше понять различные режимы работы фильтров Intent, рассмотрите следующий фрагмент +из файла манифеста приложения для работы с социальными сетями.

+ +
+<activity android:name="MainActivity">
+    <!-- This activity is the main entry, should appear in app launcher -->
+    <intent-filter>
+        <action android:name="android.intent.action.MAIN" />
+        <category android:name="android.intent.category.LAUNCHER" />
+    </intent-filter>
+</activity>
+
+<activity android:name="ShareActivity">
+    <!-- This activity handles "SEND" actions with text data -->
+    <intent-filter>
+        <action android:name="android.intent.action.SEND"/>
+        <category android:name="android.intent.category.DEFAULT"/>
+        <data android:mimeType="text/plain"/>
+    </intent-filter>
+    <!-- This activity also handles "SEND" and "SEND_MULTIPLE" with media data -->
+    <intent-filter>
+        <action android:name="android.intent.action.SEND"/>
+        <action android:name="android.intent.action.SEND_MULTIPLE"/>
+        <category android:name="android.intent.category.DEFAULT"/>
+        <data android:mimeType="application/vnd.google.panorama360+jpg"/>
+        <data android:mimeType="image/*"/>
+        <data android:mimeType="video/*"/>
+    </intent-filter>
+</activity>
+
+ +

Первая операция ({@code MainActivity}) является основной точкой входа приложения — это операция, которая +открывается, когда пользователь запускает приложение нажатием на его значок:

+
    +
  • Действие {@link android.content.Intent#ACTION_MAIN} + указывает на то, что это основная точка входа, и не ожидает никаких данных объекта Intent.
  • +
  • Категория {@link android.content.Intent#CATEGORY_LAUNCHER} указывает, что значок этой операции + следует поместить в средство запуска приложений системы. Если элемент {@code <activity>} + не содержит указаний на конкретный значок с помощью {@code icon}, то система воспользуется значком из элемента {@code <application>} +.
  • +
+

Чтобы операция отображалась в средстве запуска приложений системы, два этих элемента необходимо связать вместе.

+ +

Вторая операция ({@code ShareActivity}) предназначена для упрощения обмена текстовым и мультимедийным +контентом. Несмотря на то, что пользователи могут входить в эту операцию, выбрав ее из {@code MainActivity}, +они также могут входить в {@code ShareActivity} напрямую из другого приложения, которое выдает неявный объект +Intent, соответствующий одному из двух фильтров Intent.

+ +

Примечание. Тип MIME +({@code +application/vnd.google.panorama360+jpg}) является особым типом данных, указывающим на +панорамные фотографии, с которыми можно работать с помощью API-интерфейсов Google +panorama.

+ + + + + + + + + + + + + +

Использование ожидающего объекта Intent

+ +

Объект {@link android.app.PendingIntent} является оболочкой, в которую заключается объект {@link +android.content.Intent}. Объект {@link android.app.PendingIntent} +в основном предназначен для того, чтобы предоставлять разрешение внешним приложениям +на использование содержащегося в нем объекта {@link android.content.Intent}, как если бы он исполнялся из +процесса вашего собственного приложения.

+ +

Основные варианты использования ожидающего объекта Intent:

+
    +
  • Объявление объекта Intent, который должен будет исполняться, когда пользователь выполняет действие с вашим уведомлением + ({@link android.app.NotificationManager} системы Android + исполняет {@link android.content.Intent}). +
  • Объявление объекта Intent, который должен будет исполняться, когда пользователь выполняет действие с вашим +виджетом приложения + (приложение главного экрана исполняет {@link android.content.Intent}). +
  • Объявление объекта Intent, который должен будет исполняться в указанное время в будущем ({@link android.app.AlarmManager} системы +Android исполняет {@link android.content.Intent}). +
+ +

Поскольку каждый объект {@link android.content.Intent} предназначен для обработки компонентом приложения, +который относится к определенному типу ({@link android.app.Activity}, {@link android.app.Service} или + {@link android.content.BroadcastReceiver}), объект {@link android.app.PendingIntent} также следует создавать +с учетом этого обстоятельства. При использовании ожидающего объекта Intent ваше приложение не будет +исполнять объект Intent вызовом, например, {@link android.content.Context#startActivity +startActivity()}. Вместо этого вам необходимо будет объявить требуемый тип компонента при создании +{@link android.app.PendingIntent} путем вызова соответствующего метода-создателя:

+ +
    +
  • Метод {@link android.app.PendingIntent#getActivity PendingIntent.getActivity()} для + {@link android.content.Intent}, который запускает {@link android.app.Activity}.
  • +
  • Метод {@link android.app.PendingIntent#getService PendingIntent.getService()} для + {@link android.content.Intent}, который запускает {@link android.app.Service}.
  • +
  • Метод {@link android.app.PendingIntent#getBroadcast PendingIntent.getBroadcast()} для + {@link android.content.Intent}, который запускает {@link android.content.BroadcastReceiver}.
  • +
+ +

Если только ваше приложение не принимает ожидающие объекты Intent от других приложений, +указанные выше методы создания {@link android.app.PendingIntent} являются единственными методами +{@link android.app.PendingIntent}, которые вам когда-либо понадобятся.

+ +

Каждый метод принимает текущий {@link android.content.Context} приложения, объект +{@link android.content.Intent}, который требуется поместить в оболочку, и один или несколько флагов, указывающих, +каким образом следует использовать объект Intent (например, можно ли использовать объект Intent неоднократно).

+ +

Подробные сведения об использовании ожидающих объектов Intent приведены в документации по каждому +из соответствующих вариантов использования, например, в руководствах, посвященным API-интерфейсам: УведомленияВиджеты приложений.

+ + + + + + + +

Разрешение объектов Intent

+ + +

Когда система получает неявный объект Intent для запуска операции, она выполняет поиск +наиболее подходящей операции путем сравнения объекта Intent с фильтрами Intent по трем критериям:

+ +
    +
  • действие объекта Intent; +
  • данные объекта Intent (тип URI и данных); +
  • категория объекта Intent. +
+ +

В следующих разделах описывается, каким образом объекты Intent сопоставляются с соответствующими компонентами, +а именно, как должен быть фильтр Intent объявлен в файле манифеста приложения.

+ + +

Тестирование действия

+ +

Для указания принимаемых действий объекта Intent фильтр Intent может объявлять любое (в том числе нулевое) число элементов +{@code +action>}. Например:

+ +
+<intent-filter>
+    <action android:name="android.intent.action.EDIT" />
+    <action android:name="android.intent.action.VIEW" />
+    ...
+</intent-filter>
+
+ +

Чтобы пройти через этот фильтр, действие, указанное в объекте {@link android.content.Intent}, + должно соответствовать одному или нескольким действиям, перечисленным в фильтре.

+ +

Если в фильтре не перечислены какие-либо действия, объекту +Intent будет нечему соответствовать, поэтому все объекты Intent не пройдут этот тест. Однако, если в объекте {@link android.content.Intent} +не указано действие, он пройдет тест (если в фильтре +содержится хотя бы одно действие).

+ + + +

Тестирование категории

+ +

Для указания принимаемых категорий объекта Intent фильтр Intent может объявлять любое (в том числе нулевое) число элементов +{@code +<category>}. Например:

+ +
+<intent-filter>
+    <category android:name="android.intent.category.DEFAULT" />
+    <category android:name="android.intent.category.BROWSABLE" />
+    ...
+</intent-filter>
+
+ +

Чтобы объект Intent прошел тестирование категории, все категории, приведенные в объекте {@link android.content.Intent}, +должны соответствовать категории из фильтра. Обратное не требуется — фильтр Intent может +объявлять и другие категории, которых нет в объекте {@link android.content.Intent}, объект +{@link android.content.Intent} при этом все равно пройдет тест. Поэтому объект Intent без категорий +всегда пройдет этот тест, независимо от того, какие категории объявлены в фильтре.

+ +

Примечание. +Система Android автоматически применяет категорию {@link android.content.Intent#CATEGORY_DEFAULT} +ко всем неявным объектам Intent, которые передаются в {@link +android.content.Context#startActivity startActivity()} и {@link +android.app.Activity#startActivityForResult startActivityForResult()}. +Поэтому, если вы хотите, чтобы ваша операция принимала неявные объекты Intent, в ее фильтрах Intent +должна быть указана категория для {@code "android.intent.category.DEFAULT"} (как +показано в предыдущем примере {@code <intent-filter>}).

+ + + +

Тестирование данных

+ +

Для указания принимаемых данных объекта Intent фильтр Intent может объявлять любое (в том числе нулевое) число элементов +{@code +<data>}. Например:

+ +
+<intent-filter>
+    <data android:mimeType="video/mpeg" android:scheme="http" ... />
+    <data android:mimeType="audio/mpeg" android:scheme="http" ... />
+    ...
+</intent-filter>
+
+ +

Каждый элемент <data> +может конкретизировать структуру URI и тип данных (тип мультимедиа MIME). Имеются отдельные +атрибуты — {@code scheme}, {@code host}, {@code port} +и {@code path} — для каждой составной части URI: +

+ +

{@code <scheme>://<host>:<port>/<path>}

+ +

+Например: +

+ +

{@code content://com.example.project:200/folder/subfolder/etc}

+ +

В этом URI схема имеет вид {@code content}, узел ― {@code com.example.project}, +порт ― {@code 200}, а путь ― {@code folder/subfolder/etc}. +

+ +

Каждый из этих атрибутов является необязательным в элементе {@code <data>}, +однако имеются линейные зависимости:

+
    +
  • Если схема не указана, узел игнорируется.
  • +
  • Если узел не указан, порт игнорируется.
  • +
  • Если не указана ни схема, ни узел, путь игнорируется.
  • +
+ +

Когда URI, указанный в объекте Intent, сравнивается с URI из фильтра, +сравнение выполняется только с теми составными частями URI, которые приведены в фильтре. Например:

+
    +
  • Если в фильтре указана только схема, то все URI с этой схемой будет соответствовать +фильтру.
  • +
  • Если в фильтре указаны схема и полномочия, но отсутствует путь, все URI +с такими же схемой и полномочиями пройдут фильтр, а их пути учитываться не будут.
  • +
  • Если в фильтре указаны схема, полномочия и путь, то только URI с такими же схемой, +полномочиями и путем пройдут фильтр.
  • +
+ +

Примечание. Путь может быть +указан с подстановочным символом (*), чтобы потребовалось только частичное соответствие имени пути.

+ +

При выполнении тестирования данных сравнивается и URI, и тип MIME, указанные в объекте Intent, с URI +и типом MIME из фильтра. Действуют следующие правила: +

+ +
    +
  1. Объект Intent, который не содержит ни URI, ни тип MIME, пройдет этот +тест, только если в фильтре не указано никаких URI или типов MIME.
  2. + +
  3. Объект Intent, в котором имеется URI, но отсутствует тип MIME (ни явный, ни тот, который можно вывести из +URI), пройдет этот тест, только если URI соответствует формату URI из фильтра +, а в фильтре также не указан тип MIME.
  4. + +
  5. Объект Intent, в котором имеется тип MIME, но отсутствует URI, пройдет этот тест, +только если в фильтре указан тот же тип MIME и не указан формат URI.
  6. + +
  7. Объект Intent, в котором имеется и URI, и тип MIME (явный или тот, который можно вывести из +URI), пройдет только часть этого теста, проверяющую тип MIME, +в том случае, если этот тип совпадает с типом, приведенным в фильтре. Он пройдет часть этого теста, которая проверяет URI, +либо если его URI совпадает с URI из фильтра, либо если этот объект содержит URI {@code content:} +или {@code file:}, а в фильтре URI не указан. Другими словами, +предполагается, что компонент поддерживает данные {@code content:} и {@code file:}, если в его +фильтре указан только тип MIME.

  8. +
+ +

+Это последнее правило (правило (d)) отражает ожидание того, +что компоненты будут в состоянии получать локальные данные из файла или от поставщика контента. +Поэтому их фильтры могут содержать только тип данных, а явно +указывать схемы {@code content:} и {@code file:} не требуется. +Это типичный случай. Например, такой элемент {@code <data>}, как +приведенный далее, сообщает системе Android, что компонент может получать данные изображений от поставщика +контента и выводить их на экран: +

+ +
+<intent-filter>
+    <data android:mimeType="image/*" />
+    ...
+</intent-filter>
+ +

+Поскольку имеющиеся данные преимущественно распространяются поставщиками контента, фильтры, в которых +указан тип данных и нет URI, вероятно, являются самыми распространенными. +

+ +

+Другой стандартной конфигурацией являются фильтры со схемой и типом данных. Например, +такой элемент {@code <data>}, +как приведенный далее, сообщает системе Android, что +компонент может получать видеоданные из сети для выполнения действия: +

+ +
+<intent-filter>
+    <data android:scheme="http" android:type="video/*" />
+    ...
+</intent-filter>
+ + + +

Сопоставление объектов Intent

+ +

Объекты Intent сопоставляются с фильтрами Intent не только для определения целевого +компонента, который требуется активировать, но также для выявления определенных сведений о наборе +компонентов, имеющихся на устройстве. Например, приложение главного экрана заполняет средство запуска приложений +путем поиска всех операций с фильтрами Intent, в которых указано действие +{@link android.content.Intent#ACTION_MAIN} и категория +{@link android.content.Intent#CATEGORY_LAUNCHER}.

+ +

Ваше приложение может использовать сопоставление объектов Intent таким же образом. +В {@link android.content.pm.PackageManager} имеется набор методов {@code query...()}, +которые возвращают все компоненты, способные принять определенный объект, а также +схожий набор методов {@code resolve...()}, которые определяют наиболее подходящий +компонент, способный реагировать на объект Intent. Например, метод +{@link android.content.pm.PackageManager#queryIntentActivities +queryIntentActivities()} возвращает передаваемый как аргумент список всех операций, которые могут выполнить +объект Intent, а метод {@link +android.content.pm.PackageManager#queryIntentServices +queryIntentServices()} возвращает такой же список служб. +Ни тот, ни другой метод не активирует компоненты; они просто перечисляют те из них, которые +могут откликнуться. Имеется схожий метод для приемников широковещательных сообщений ( +{@link android.content.pm.PackageManager#queryBroadcastReceivers +). +

+ + + + diff --git a/docs/html-intl/intl/ru/guide/components/loaders.jd b/docs/html-intl/intl/ru/guide/components/loaders.jd new file mode 100644 index 0000000000000000000000000000000000000000..eea72a2296ee510769d348791950b4116ca57aa9 --- /dev/null +++ b/docs/html-intl/intl/ru/guide/components/loaders.jd @@ -0,0 +1,494 @@ +page.title=Загрузчики +parent.title=Операции +parent.link=activities.html +@jd:body + + +

Загрузчики, которые появились в Android 3.0, упрощают асинхронную загрузку данных +в операцию или фрагмент. Загрузчики имеют следующие свойства:

+
    +
  • Они имеются для любых операций {@link android.app.Activity} и фрагментов {@link +android.app.Fragment}.
  • +
  • Они обеспечивают асинхронную загрузку данных.
  • +
  • Они отслеживают источник своих данных и выдают новые результаты при +изменении контента.
  • +
  • Они автоматически переподключаются к последнему курсору загрузчика при +воссоздании после изменения конфигурации. Таким образом, им не требуется повторно запрашивать свои +данные.
  • +
+ +

Сводная информация об API-интерфейсе загрузчика

+ +

Имеется несколько классов и интерфейсов, которые могут использовать +загрузчики в приложении. Они приведены в этой таблице:

+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Класс/интерфейсОписание
{@link android.app.LoaderManager}Абстрактный класс, связываемый с {@link android.app.Activity} или +{@link android.app.Fragment} для управления одним или несколькими интерфейсами {@link +android.content.Loader}. Это позволяет приложению управлять +длительно выполняющимися операциями вместе с жизненным циклом {@link android.app.Activity} +или {@link android.app.Fragment}; чаще всего этот класс используется с +{@link android.content.CursorLoader}, однако приложения могут писать +свои собственные загрузчики для работы с другими типами данных. +
+
+ Имеется только один класс {@link android.app.LoaderManager} на операцию или фрагмент. Однако у класса {@link android.app.LoaderManager} может быть +несколько загрузчиков.
{@link android.app.LoaderManager.LoaderCallbacks}Интерфейс обратного вызова, обеспечивающий взаимодействие клиента с {@link +android.app.LoaderManager}. Например, с помощью метода обратного вызова {@link +android.app.LoaderManager.LoaderCallbacks#onCreateLoader onCreateLoader()} +создается новый загрузчик.
{@link android.content.Loader}Абстрактный класс, который выполняет асинхронную загрузку данных. Это +базовый класс для загрузчика. Обычно используется {@link +android.content.CursorLoader}, но можно реализовать и собственный подкласс. Когда +загрузчики активны, они должны отслеживать источник своих данных и выдавать новые +результаты при изменении контента.
{@link android.content.AsyncTaskLoader}Абстрактный загрузчик, который предоставляет {@link android.os.AsyncTask} для выполнения работы.
{@link android.content.CursorLoader}Подкласс класса {@link android.content.AsyncTaskLoader}, который запрашивает +{@link android.content.ContentResolver} и возвращает {@link +android.database.Cursor}. Этот класс реализует протокол {@link +android.content.Loader} стандартным способом для выполнения запросов к курсорам. +Он строится на {@link android.content.AsyncTaskLoader} для выполнения запроса к курсору +в фоновом потоке, чтобы не блокировать пользовательский интерфейс приложения. Использование +этого загрузчика — это лучший способ асинхронной загрузки данных из {@link +android.content.ContentProvider} вместо выполнения управляемого запроса через +платформу или API-интерфейсы операции.
+ +

Приведенные в этой таблице классы и интерфейсы являются наиболее важными компонентами, +с помощью которых в приложении реализуется загрузчик. При создании каждого загрузчика +не нужно использовать все эти компоненты, однако всегда следует указывать ссылку на {@link +android.app.LoaderManager} для инициализации загрузчика и использовать реализацию +класса {@link android.content.Loader}, например {@link +android.content.CursorLoader}. В следующих разделах рассказывается, как использовать эти +классы и интерфейсы в приложении.

+ +

Использование загрузчиков в приложении

+

В этом разделе описывается использование загрузчиков в приложении для Android. В +приложениях, использующих загрузчики, обычно имеются следующие элементы:

+
    +
  • {@link android.app.Activity} или {@link android.app.Fragment};
  • +
  • экземпляр {@link android.app.LoaderManager};
  • +
  • {@link android.content.CursorLoader} для загрузки данных, выдаваемых {@link +android.content.ContentProvider}. Вы также можете реализовать собственный подкласс +класса {@link android.content.Loader} или {@link android.content.AsyncTaskLoader} для +загрузки данных из другого источника;
  • +
  • реализация для {@link android.app.LoaderManager.LoaderCallbacks}. +Именно здесь создаются новые загрузчики и ведется управление ссылками на существующие +загрузчики;
  • +
  • способ отображения данных загрузчика, например {@link +android.widget.SimpleCursorAdapter};
  • +
  • источник данных, например {@link android.content.ContentProvider}, когда используется +{@link android.content.CursorLoader}.
  • +
+

Запуск загрузчика

+ +

{@link android.app.LoaderManager} управляет одним или несколькими экземплярами {@link +android.content.Loader} в {@link android.app.Activity} или +{@link android.app.Fragment}. Имеется только один {@link +android.app.LoaderManager} на каждую операцию или каждый фрагмент.

+ +

{@link android.content.Loader} обычно +инициализируется в методе {@link +android.app.Activity#onCreate onCreate()} операции или в методе фрагмента +{@link android.app.Fragment#onActivityCreated onActivityCreated()}. Делается +это следующим образом:

+ +
// Prepare the loader.  Either re-connect with an existing one,
+// or start a new one.
+getLoaderManager().initLoader(0, null, this);
+ +

Метод {@link android.app.LoaderManager#initLoader initLoader()} принимает +следующие параметры:

+
    +
  • уникальный идентификатор, обозначающий загрузчик. В данном примере идентификатором является 0;
  • +
  • необязательные аргументы, которые передаются загрузчику при +построении (в данном примере это null);
  • + +
  • реализация {@link android.app.LoaderManager.LoaderCallbacks}, которая +вызывает класс {@link android.app.LoaderManager} для выдачи событий загрузчика. В данном +примере локальный класс реализует интерфейс {@link +android.app.LoaderManager.LoaderCallbacks}, поэтому он передает ссылку +самому себе: {@code this}.
  • +
+

Вызов {@link android.app.LoaderManager#initLoader initLoader()} обеспечивает инициализацию +загрузчика. Возможен один из двух результатов:

+
    +
  • Если загрузчик, указанный с помощью идентификатора, уже существует, будет повторно использован загрузчик, созданный +последним.
  • +
  • Если загрузчик, указанный с помощью идентификатора, не существует, +{@link android.app.LoaderManager#initLoader initLoader()} вызывает метод +{@link android.app.LoaderManager.LoaderCallbacks} из {@link android.app.LoaderManager.LoaderCallbacks#onCreateLoader onCreateLoader()}. +Именно здесь реализуется код для создания экземпляра и возврата нового загрузчика. +Более подробные сведения см. в разделе onCreateLoader.
  • +
+

В любом случае данная реализация {@link android.app.LoaderManager.LoaderCallbacks} +связывается с загрузчиком и будет вызываться +при изменении состояния загрузчика. Если в момент этого вызова вызывающий компонент находится в +запущенном состоянии, это означает, что запрошенный загрузчик уже существует и сформировал свои +данные. В этом случае система сразу же вызовет {@link +android.app.LoaderManager.LoaderCallbacks#onLoadFinished onLoadFinished()} + (во время{@link android.app.LoaderManager#initLoader initLoader()}), +будьте готовы к этому. Более подробные сведения об этом обратном вызове см. в разделе +onLoadFinished.

+ +

Обратите внимание, что метод {@link android.app.LoaderManager#initLoader initLoader()} +возвращает создаваемый класс {@link android.content.Loader}, но записывать +ссылку на него не требуется. Класс {@link android.app.LoaderManager} управляет +жизненным циклом загрузчика автоматически. Класс {@link android.app.LoaderManager} +начинает загрузку и заканчивает ее при необходимости, а также поддерживает состояние загрузчика +и связанного с ним контента. А это подразумевает, что вы будете редко взаимодействовать с загрузчиками +напрямую (однако пример использования методов загрузчика для тонкой настройки его +поведения см. в образце кода LoaderThrottle). +Для вмешательства в процесс загрузки при возникновении определенных событий обычно используются методы {@link +android.app.LoaderManager.LoaderCallbacks} +. Более подробные сведения об этом см. в разделе Использование обратных вызовов LoaderManager.

+ +

Перезапуск загрузчика

+ +

При использовании метода {@link android.app.LoaderManager#initLoader initLoader()}, как +показано выше, он задействует существующий загрузчик с указанным идентификатором — в случае его наличия. +Если такого загрузчика нет, метод его создаст. Однако иногда старые данные нужно отбросить +и начать все заново.

+ +

Для удаления старых данных используется метод {@link +android.app.LoaderManager#restartLoader restartLoader()}. Например, эта +реализация метода{@link android.widget.SearchView.OnQueryTextListener} перезапускает +загрузчик, когда изменяется запрос пользователя. Загрузчик необходимо перезагрузить, + с тем чтобы он мог использовать измененный фильтр поиска для выполнения нового запроса:

+ +
+public boolean onQueryTextChanged(String newText) {
+    // Called when the action bar search text has changed.  Update
+    // the search filter, and restart the loader to do a new query
+    // with this filter.
+    mCurFilter = !TextUtils.isEmpty(newText) ? newText : null;
+    getLoaderManager().restartLoader(0, null, this);
+    return true;
+}
+ +

Использование обратных вызовов класса LoaderManager

+ +

{@link android.app.LoaderManager.LoaderCallbacks} представляет собой интерфейс обратного вызова, +который позволяет клиенту взаимодействовать с классом {@link android.app.LoaderManager}.

+

Ожидается, что загрузчики, в частности {@link android.content.CursorLoader}, будут сохранять +свои данные после их остановки. Это позволяет приложениям сохранять свои +данные в методах {@link android.app.Activity#onStop +onStop()} и {@link android.app.Activity#onStart onStart()} операции или фрагмента, с тем чтобы, +когда пользователь вернется в приложение, ему не пришлось ждать, пока данные +загрузятся заново. Методы {@link android.app.LoaderManager.LoaderCallbacks} используются, +чтобы узнать, когда требуется создать новый загрузчик, а также для того, чтобы указать приложению, когда +пришло время перестать использовать данные загрузчика.

+ +

Интерфейс {@link android.app.LoaderManager.LoaderCallbacks} использует следующие +методы:

+
    +
  • {@link android.app.LoaderManager.LoaderCallbacks#onCreateLoader onCreateLoader()} — +создает экземпляр и возвращает новый класс {@link android.content.Loader} для данного идентификатора. +
+
    +
  • {@link android.app.LoaderManager.LoaderCallbacks#onLoadFinished onLoadFinished()} +— вызывается, когда созданный ранее загрузчик завершил загрузку. +
+
    +
  • {@link android.app.LoaderManager.LoaderCallbacks#onLoaderReset onLoaderReset()} +— вызывается, когда состояние созданного ранее загрузчика сбрасывается, в результате чего его данные +теряются. +
  • +
+

Более подробно эти методы описаны в разделах ниже.

+ +

onCreateLoader

+ +

При попытке доступа к загрузчику (например, посредством метода {@link +android.app.LoaderManager#initLoader initLoader()}), он проверяет, существует ли +загрузчик, указанный с помощью идентификатора. Если он не существует, он вызывает метод {@link +android.app.LoaderManager.LoaderCallbacks} {@link +android.app.LoaderManager.LoaderCallbacks#onCreateLoader onCreateLoader()}. Именно +здесь и создается новый загрузчик. Обычно это будет класс {@link +android.content.CursorLoader}, однако можно реализовать и собственный подкласс класса {@link +android.content.Loader}.

+ +

В этом примере метод обратного вызова {@link +android.app.LoaderManager.LoaderCallbacks#onCreateLoader onCreateLoader()} +создает класс {@link android.content.CursorLoader}. Вы должны построить +класс {@link android.content.CursorLoader} с помощью его метода конструктора, для чего +требуется полный набор информации, которая нужна для выполнения запроса к {@link +android.content.ContentProvider}. В частности, требуется:

+
    +
  • uri — URI контента, который необходимо получить;
  • +
  • projection — список столбцов, которые будут возвращены. При передаче +null будут возвращены все столбцы, а это неэффективно;
  • +
  • selection — фильтр, объявляющий, какие строки возвращать, +отформатированный в виде предложения SQL WHERE (за исключением самого WHERE). При передаче +null будут возвращены все строки для данного URI;
  • +
  • selectionArgs — в выборку можно включить символы "?", которые будут +заменены значениями из selectionArgs в порядке следования в +выборке. Значения будут привязаны как строки;
  • +
  • sortOrder — порядок расположения строк, отформатированный в виде предложения SQL +ORDER BY (за исключением самого ORDER BY). При передаче null будет +использоваться стандартный порядок сортировки, поэтому, список, возможно, будет неотсортирован.
  • +
+

Например:

+
+ // If non-null, this is the current filter the user has provided.
+String mCurFilter;
+...
+public Loader<Cursor> onCreateLoader(int id, Bundle args) {
+    // This is called when a new Loader needs to be created.  This
+    // sample only has one Loader, so we don't care about the ID.
+    // First, pick the base URI to use depending on whether we are
+    // currently filtering.
+    Uri baseUri;
+    if (mCurFilter != null) {
+        baseUri = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI,
+                  Uri.encode(mCurFilter));
+    } else {
+        baseUri = Contacts.CONTENT_URI;
+    }
+
+    // Now create and return a CursorLoader that will take care of
+    // creating a Cursor for the data being displayed.
+    String select = "((" + Contacts.DISPLAY_NAME + " NOTNULL) AND ("
+            + Contacts.HAS_PHONE_NUMBER + "=1) AND ("
+            + Contacts.DISPLAY_NAME + " != '' ))";
+    return new CursorLoader(getActivity(), baseUri,
+            CONTACTS_SUMMARY_PROJECTION, select, null,
+            Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC");
+}
+

onLoadFinished

+ +

Этот метод вызывается, когда созданный ранее загрузчик завершил загрузку. +Этот метод гарантировано вызывается до высвобождения последних данных, +которые были предоставлены этому загрузчику. К этому моменту необходимо полностью перестать использовать +старые данные (поскольку они скоро будут заменены). Однако этого не следует делать +самостоятельно, поскольку данными владеет загрузчик и он позаботится об этом.

+ + +

Загрузчик высвободит данные, как только узнает, что приложение их больше не +использует. Например, если данными является курсор из {@link +android.content.CursorLoader}, не следует вызывать {@link +android.database.Cursor#close close()} самостоятельно. Если курсор +размещается в {@link android.widget.CursorAdapter}, следует использовать метод {@link +android.widget.SimpleCursorAdapter#swapCursor swapCursor()} с тем, чтобы +старый {@link android.database.Cursor} не закрылся. Например:

+ +
+// This is the Adapter being used to display the list's data.
SimpleCursorAdapter mAdapter; +... + +public void onLoadFinished(Loader<Cursor> loader, Cursor data) { + // Swap the new cursor in.  (The framework will take care of closing the + // old cursor once we return.) + mAdapter.swapCursor(data); +}
+ +

onLoaderReset

+ +

Этот метод вызывается, когда состояние созданного ранее загрузчика сбрасывается, в результате чего +его данные теряются. Этот обратный вызов позволяет узнать, когда данные +вот-вот будут высвобождены, с тем чтобы можно было удалить свою ссылку на них.  

+

Данная реализация вызывает +{@link android.widget.SimpleCursorAdapter#swapCursor swapCursor()} +со значением null:

+ +
+// This is the Adapter being used to display the list's data.
+SimpleCursorAdapter mAdapter;
+...
+
+public void onLoaderReset(Loader<Cursor> loader) {
+    // This is called when the last Cursor provided to onLoadFinished()
+    // above is about to be closed.  We need to make sure we are no
+    // longer using it.
+    mAdapter.swapCursor(null);
+}
+ + +

Пример

+ +

В качестве примера далее приведена полная реализация фрагмента {@link +android.app.Fragment}, который отображает {@link android.widget.ListView} с +результатами запроса к поставщику такого контента, как контакты. Для управления запросом к поставщику используется класс {@link +android.content.CursorLoader}.

+ +

Чтобы приложение могло обращаться к контактам пользователя, как показано в этом примере, в его +манифесте должно присутствовать разрешение +{@link android.Manifest.permission#READ_CONTACTS READ_CONTACTS}.

+ +
+public static class CursorLoaderListFragment extends ListFragment
+        implements OnQueryTextListener, LoaderManager.LoaderCallbacks<Cursor> {
+
+    // This is the Adapter being used to display the list's data.
+    SimpleCursorAdapter mAdapter;
+
+    // If non-null, this is the current filter the user has provided.
+    String mCurFilter;
+
+    @Override public void onActivityCreated(Bundle savedInstanceState) {
+        super.onActivityCreated(savedInstanceState);
+
+        // Give some text to display if there is no data.  In a real
+        // application this would come from a resource.
+        setEmptyText("No phone numbers");
+
+        // We have a menu item to show in action bar.
+        setHasOptionsMenu(true);
+
+        // Create an empty adapter we will use to display the loaded data.
+        mAdapter = new SimpleCursorAdapter(getActivity(),
+                android.R.layout.simple_list_item_2, null,
+                new String[] { Contacts.DISPLAY_NAME, Contacts.CONTACT_STATUS },
+                new int[] { android.R.id.text1, android.R.id.text2 }, 0);
+        setListAdapter(mAdapter);
+
+        // Prepare the loader.  Either re-connect with an existing one,
+        // or start a new one.
+        getLoaderManager().initLoader(0, null, this);
+    }
+
+    @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+        // Place an action bar item for searching.
+        MenuItem item = menu.add("Search");
+        item.setIcon(android.R.drawable.ic_menu_search);
+        item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
+        SearchView sv = new SearchView(getActivity());
+        sv.setOnQueryTextListener(this);
+        item.setActionView(sv);
+    }
+
+    public boolean onQueryTextChange(String newText) {
+        // Called when the action bar search text has changed.  Update
+        // the search filter, and restart the loader to do a new query
+        // with this filter.
+        mCurFilter = !TextUtils.isEmpty(newText) ? newText : null;
+        getLoaderManager().restartLoader(0, null, this);
+        return true;
+    }
+
+    @Override public boolean onQueryTextSubmit(String query) {
+        // Don't care about this.
+        return true;
+    }
+
+    @Override public void onListItemClick(ListView l, View v, int position, long id) {
+        // Insert desired behavior here.
+        Log.i("FragmentComplexList", "Item clicked: " + id);
+    }
+
+    // These are the Contacts rows that we will retrieve.
+    static final String[] CONTACTS_SUMMARY_PROJECTION = new String[] {
+        Contacts._ID,
+        Contacts.DISPLAY_NAME,
+        Contacts.CONTACT_STATUS,
+        Contacts.CONTACT_PRESENCE,
+        Contacts.PHOTO_ID,
+        Contacts.LOOKUP_KEY,
+    };
+    public Loader<Cursor> onCreateLoader(int id, Bundle args) {
+        // This is called when a new Loader needs to be created.  This
+        // sample only has one Loader, so we don't care about the ID.
+        // First, pick the base URI to use depending on whether we are
+        // currently filtering.
+        Uri baseUri;
+        if (mCurFilter != null) {
+            baseUri = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI,
+                    Uri.encode(mCurFilter));
+        } else {
+            baseUri = Contacts.CONTENT_URI;
+        }
+
+        // Now create and return a CursorLoader that will take care of
+        // creating a Cursor for the data being displayed.
+        String select = "((" + Contacts.DISPLAY_NAME + " NOTNULL) AND ("
+                + Contacts.HAS_PHONE_NUMBER + "=1) AND ("
+                + Contacts.DISPLAY_NAME + " != '' ))";
+        return new CursorLoader(getActivity(), baseUri,
+                CONTACTS_SUMMARY_PROJECTION, select, null,
+                Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC");
+    }
+
+    public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
+        // Swap the new cursor in.  (The framework will take care of closing the
+        // old cursor once we return.)
+        mAdapter.swapCursor(data);
+    }
+
+    public void onLoaderReset(Loader<Cursor> loader) {
+        // This is called when the last Cursor provided to onLoadFinished()
+        // above is about to be closed.  We need to make sure we are no
+        // longer using it.
+        mAdapter.swapCursor(null);
+    }
+}
+

Другие примеры

+ +

В ApiDemos есть несколько различных примеров, которые +иллюстрируют использование загрузчиков:

+
    +
  • +LoaderCursor — полная версия +показанного выше фрагмента.
  • +
  • LoaderThrottle — пример того, как использовать регулирование +для сокращения числа запросов, выполняемых поставщиком контента при изменении его данных.
  • +
+ +

Сведения о загрузке и установке образцов кода SDK см. в статье Получение +образцов кода.

+ diff --git a/docs/html-intl/intl/ru/guide/components/processes-and-threads.jd b/docs/html-intl/intl/ru/guide/components/processes-and-threads.jd new file mode 100644 index 0000000000000000000000000000000000000000..fd298e0d3c4936ac0c2f0f14f8407b3c2e4b51c1 --- /dev/null +++ b/docs/html-intl/intl/ru/guide/components/processes-and-threads.jd @@ -0,0 +1,411 @@ +page.title=Процессы и потоки +page.tags=жизненный цикл,фон + +@jd:body + + + +

Когда компонент приложения запускается при отсутствии других работающих компонентов +, система Android запускает новый процесс Linux для приложения с одним потоком +выполнения. По умолчанию все компоненты одного приложения работают в одном процессе и потоке +(называется «главным потоком»). Если компонент приложения запускается при наличии процесса +для этого приложения (так как существует другой компонент из приложения), тогда компонент +запускается в этом процессе и использует тот же поток выполнения. Однако можно организовать выполнение +других компонентов приложения в отдельных процессах и создавать дополнительный +поток для любого процесса.

+ +

В этом документе обсуждается работа процессов и потоков в приложении Android.

+ + +

Процессы

+ +

По умолчанию все компоненты одного приложения работают в одном процессе, и большинство приложений +не должно менять это поведение. Однако, если необходимо контролировать, к какому процессу принадлежат определенный +компонент, можно сделать это в файле манифеста.

+ +

Запись манифеста для каждого типа элементов компонента —{@code +<activity>}, {@code +<service>}, {@code +<receiver>} и {@code +<provider>} —поддерживает атрибут {@code android:process}, позволяющий задавать +процесс, в котором следует выполнять этот компонент. Можно установить этот атрибут так, чтобы каждый компонент выполнялся +в собственном процессе, или так, чтобы только некоторые компоненты совместно использовали один процесс. Можно также настроить процесс +{@code android:process} так, чтобы компоненты разных приложений выполнялись в одном +процессе, при условии что приложения совместно используют один идентификатор пользователя Linux и выполняют вход с +одним сертификатом.

+ +

Элемент {@code +<application>} также поддерживает атрибут {@code android:process}, позволяющий задать +значение по умолчанию, которое применяется ко всем компонентам.

+ +

Android может остановить процесс в некоторой точке, когда не хватает памяти и она необходима другим +процессам, которые обслуживают пользователя в данный момент. Работа компонентов +приложения, работающих в этом процессе, последовательно останавливается. Процесс для этих компонентов запускается +повторно, когда для них появляется работа.

+ +

Принимая решение о прерывании процессов, система Android взвешивает их относительную важность +для пользователя. Например, более вероятно выключение процессов, содержащих действия, которые +не отображаются на экране, по сравнению с процессом, содержащим видимые действия. Следовательно, решение +о прерывании процесса зависит от состояния компонентов, работающих в этом процессе. Ниже обсуждаются правила, +на основании которых принимается решение о выборе прерываемых процессов.

+ + +

Жизненный цикл процесса

+ +

Система Android пытается сохранять процесс приложения как можно дольше, но + в конечном счете вынуждена удалять старые процессы, чтобы восстановить память для новых или более важных процессов. Чтобы +определить, какие процессы сохранить, +а какие удалить, система помещает каждый процесс в «иерархию важности» на основе +компонентов, выполняющихся в процессе, и состояния этих компонентов. Процессы с самым низким уровнем +важности исключаются в первую очередь, затем исключаются процессы следующего уровня важности и т. д., насколько это необходимо +для восстановления ресурсов системы.

+ +

В иерархии важности предусмотрено пять уровней. В следующем списке представлены различные +типы процессов в порядке важности (первый процесс является наиболее важным и +удаляется в последнюю очередь):

+ +
    +
  1. Процесс переднего плана +

    Процесс, необходимый для текущей деятельности пользователя. Процесс + считается процессом переднего плана, если выполняется любое из следующих условий:

    + +
      +
    • Он содержит действие {@link android.app.Activity}, с которым взаимодействует пользователь (вызван метод {@link +android.app.Activity} +{@link android.app.Activity#onResume onResume()}).
    • + +
    • Он содержит службу {@link android.app.Service}, связанную с действием, с которым +взаимодействует пользователь.
    • + +
    • Он содержит службу {@link android.app.Service}, которая выполняется "на переднем плане", — службу, +которая называется {@link android.app.Service#startForeground startForeground()}. + +
    • Он содержит службу{@link android.app.Service}, которая выполняет один из обратных вызовов + жизненного цикла ({@link android.app.Service#onCreate onCreate()}, {@link android.app.Service#onStart +onStart()} или {@link android.app.Service#onDestroy onDestroy()}).
    • + +
    • Он содержит ресивер {@link android.content.BroadcastReceiver}, который выполняет метод {@link +android.content.BroadcastReceiver#onReceive onReceive()}.
    • +
    + +

    Обычно одновременно работает лишь несколько процессов переднего плана. Они уничтожаются только +в крайнем случае, если памяти остается так мало, что они не могут продолжать совместную работу. Обычно в этот момент +устройство достигло состояния разбиения памяти на страницы, поэтому для того, чтобы пользовательский интерфейс откликался на действия пользователя, необходимо +удаление некоторых процессов на переднем плане.

  2. + +
  3. Видимые процессы +

    Процессы, которые не содержат компонентов переднего плана, но могут +влиять на отображение на экране. Процесс считается видимым, +если выполняется любое из следующих условий:

    + +
      +
    • Он содержит действие {@link android.app.Activity}, которое не находится на переднем плане, но +видно пользователю (вызван метод {@link android.app.Activity#onPause onPause()}). +Например, это может происходить, если действие на переднем плане запустило диалоговое окно, которое позволяет видеть +предыдущее действие позади него.
    • + +
    • Он содержит службу {@link android.app.Service}, связанную с видимым +действием или действием переднего плана.
    • +
    + +

    Видимый процесс считается исключительно важным, его следует удалять +только в случае, если требуется сохранить работу всех процессов переднего плана.

    +
  4. + +
  5. Служебный процесс +

    Процесс, который выполняет службу, запущенную с помощью метода {@link +android.content.Context#startService startService()}, и не попадает ни в одну из двух +категорий более высокого уровня. Хотя служебные процессы не связаны непосредственно с тем, что видит пользователь, +они обычно выполняют важные для пользователя действия (например, воспроизводят музыку в фоновом режиме или +загружают данные в сеть), поэтому система сохраняет их выполнение, если памяти достаточно для +их работы наряду со всеми видимыми процессами и процессами переднего плана.

    +
  6. + +
  7. Фоновый процесс +

    Процесс, содержащий действия, которые не видны пользователю в настоящее время (вызван метод +{@link android.app.Activity#onStop onStop()} действия). Эти процессы не оказывают непосредственного +воздействия на работу пользователя, и система может удалить их в любой момент, чтобы освободить память для +процессов переднего плана, +видимых или служебных процессов. Обычно выполняется множество фоновых процессов, поэтому они хранятся в списке +LRU (недавно использованные), чтобы процессы, содержащие самые +недавние действия, которые видел пользователь, удалялись в последнюю очередь. Если для действия правильно реализованы методы жизненного цикла, +и действие сохраняет текущее состояние, удаление процесса этого действия не оказывает видимого воздействия на +работу пользователя, так как когда пользователь возвращается к этому действию, оно восстанавливает +все элементы своего видимого состояния. Информацию о сохранении и восстановлении состояния см. в документе Действия +.

    +
  8. + +
  9. Пустой процесс +

    Процесс, не содержащий никаких компонентов активного приложения. Единственная причина сохранять процесс +такого типа — это кэширование, которое улучшает время следующего запуска +компонента в этом процессе. Система часто удаляет эти процессы для равномерного распределения всех системных +ресурсов между кэшем процесса и кэшем базового ядра.

    +
  10. +
+ + +

Система Android относит процесс к максимально высокому уровню на основе важности +компонентов, активных в процессе в текущее время. Например, если процесс содержит служебное и видимое действие, +процесс считается видимым, а не служебным процессом.

+ +

Кроме того, уровень процесса может быть повышен, поскольку имеются другие процессы, зависимые от него. +Например, процесс, обслуживающий другой процесс, не может иметь уровень ниже уровня обслуживаемого +процесса. Например, если поставщик контента в процессе A обслуживает клиента в процессе B или +служебный процесс A связан с компонентом в процессе B, процесс A всегда считается не менее +важным, чем процесс B.

+ +

Так как процесс, выполняющий службу, оценивается выше процесса с фоновыми действиям, +действие, запускающее долговременную операцию, может запустить службу для этой операции, а не просто +создать рабочий поток, особенно в случае, если операция продлится дольше действия. +Например, действие, которое загружает изображение на веб-сайт, должно запустить службу для выполнения +загрузки, так что загрузка может продолжаться в фоновом режиме даже после выхода пользователя из действия. +Использование службы гарантирует, что операция будет иметь приоритет не ниже «служебного процесса», +независимо от того, что происходит с действием. По этой же причине ресиверы должны +использовать службы, а не просто ставить в поток операции, требующие много времени для выполнения.

+ + + + +

Потоки

+ +

При запуске приложения система создает поток выполнения для приложения, +который называется «главным». Этот поток очень важен, так как он отвечает за диспетчеризацию событий +на виджеты соответствующего интерфейса пользователя, включая события графического представления. Он также является потоком, в котором +приложение взаимодействует с компонентами из набора инструментов пользовательского интерфейса Android (компонентами из пакетов {@link +android.widget} и {@link android.view}). По существу, главный поток — это то, что иногда называют +потоком пользовательского интерфейса.

+ +

Система не создает отдельного потока для каждого экземпляра компонента. Все +компоненты, которые выполняются в одном процессе, создают экземпляры в потоке пользовательского интерфейса, и системные вызовы +каждого компонента отправляются из этого потока. Поэтому методы, которые отвечают на системные +обратные вызовы (такие как метод {@link android.view.View#onKeyDown onKeyDown()} для сообщения о действиях пользователя +или метод обратного вызова жизненного цикла), всегда выполняются в потоке пользовательского интерфейса процесса.

+ +

Например, когда пользователь нажимает кнопку на экране, поток пользовательского интерфейса вашего приложения отправляет +событие нажатия в виджет, который, в свою очередь, устанавливает кнопку в нажатое состояние и отправляет запрос на аннулирование +в очередь событий. Поток пользовательского интерфейса исключает запрос из очереди и уведомляет виджет, что он должен +отобразиться повторно.

+ +

Когда приложение выполняет интенсивную работу в ответ на действия пользователя, эта одиночная модель потока +может показывать плохую производительность, если приложение реализовано неправильно. То есть, если +все происходит в потоке пользовательского интерфейса, выполнение долговременных операций, таких как сетевой доступ или +запросы к базе данных, будет блокировать весь пользовательский интерфейс. Когда поток заблокирован, не могут обрабатываться никакие события, +включая события изменения отображения. С точки зрения пользователя +приложение выглядит зависшим. Хуже того, если поток пользовательского интерфейса заблокирован более нескольких секунд +(в настоящее время около 5 секунд), отображается печально известное диалоговое окно «приложение не +отвечает». После этого недовольный пользователь может выйти из вашего приложения +и удалить его.

+ +

Кроме того, набор инструментов пользовательского интерфейса Andoid не является потокобезопасным. Поэтому, вы не должны работать +с пользовательским интерфейсом из рабочего потока. Манипуляции с пользовательским интерфейсом необходимо выполнять из +потока пользовательского интерфейса. Таким образом, существует только два правила однопоточной модели Android:

+ +
    +
  1. Не блокируйте поток пользовательского интерфейса +
  2. Не обращайтесь к набору инструментов пользовательского интерфейса Android снаружи потока пользовательского интерфейса +
+ +

Рабочие потоки

+ +

Вследствие описанной выше однопоточной модели для динамичности пользовательского интерфейса ваших приложений +очень важно не блокировать поток пользовательского интерфейса. Если требуется выполнять операции, +занимающие некоторое время, обязательно выполняйте их в отдельных потоках (»фоновых» или +«рабочих» потоках).

+ +

Например, ниже приведен код контроля нажатий, который загружает изображение из отдельного +потока и отображает их в виджете {@link android.widget.ImageView}:

+ +
+public void onClick(View v) {
+    new Thread(new Runnable() {
+        public void run() {
+            Bitmap b = loadImageFromNetwork("http://example.com/image.png");
+            mImageView.setImageBitmap(b);
+        }
+    }).start();
+}
+
+ +

На первый взгляд, он должен работать хорошо, так как он создает новый поток для обработки сетевой +операции. Однако, он нарушает второе правило однопоточной модели: не обращайтесь к набору инструментов пользовательского интерфейса +Android снаружи потока пользовательского интерфейса — этот пример изменяет {@link +android.widget.ImageView} из рабочего потока, а не из потока пользовательского интерфейса. Это может привести +к неопределенному и непредвиденному поведению, отследить которое будет трудно.

+ +

Для устранения этой проблемы Android предлагает несколько путей доступа к потоку пользовательского интерфейса из других +потоков. Ниже приведен список полезных методов:

+ +
    +
  • {@link android.app.Activity#runOnUiThread(java.lang.Runnable) +Activity.runOnUiThread(Runnable)}
  • +
  • {@link android.view.View#post(java.lang.Runnable) View.post(Runnable)}
  • +
  • {@link android.view.View#postDelayed(java.lang.Runnable, long) View.postDelayed(Runnable, +long)}
  • +
+ +

Например, можно исправить приведенный выше код с помощью метода {@link +android.view.View#post(java.lang.Runnable) View.post(Runnable)}:

+ +
+public void onClick(View v) {
+    new Thread(new Runnable() {
+        public void run() {
+            final Bitmap bitmap = loadImageFromNetwork("http://example.com/image.png");
+            mImageView.post(new Runnable() {
+                public void run() {
+                    mImageView.setImageBitmap(bitmap);
+                }
+            });
+        }
+    }).start();
+}
+
+ +

Теперь реализация является потокобезопасной: сетевая операция выполняется из отдельного потока, +тогда как {@link android.widget.ImageView} работает из потока пользовательского интерфейса.

+ +

Однако по мере роста сложности, код такого типа может становиться запутанным и сложным +для поддержания. Чтобы обрабатывать более сложные взаимодействия с рабочим потоком, можно +использовать метод {@link android.os.Handler} в рабочем потоке для обработки сообщений, поступающих из потока +пользовательского интерфейса. Вероятно, самым лучшим решением является расширение класса {@link android.os.AsyncTask}, +которое упрощает выполнение заданий рабочего потока, которые должны взаимодействовать с пользовательским интерфейсом.

+ + +

Использование AsyncTask

+ +

Метод {@link android.os.AsyncTask} позволяет выполнять асинхронную работу в пользовательском +интерфейсе. Он выполняет операции блокирования в рабочем потоке и затем публикует результаты в потоке +пользовательского интерфейса без необходимости самостоятельно обрабатывать потоки и/или обработчики.

+ +

Для использования этого метода необходимо создать подкласс {@link android.os.AsyncTask} и реализовать метод обратного вызова {@link +android.os.AsyncTask#doInBackground doInBackground()}, который работает в пуле +фоновых потоков. Чтобы обновить пользовательский интерфейс, следует реализовать метод {@link +android.os.AsyncTask#onPostExecute onPostExecute()}, который доставляет результат из {@link +android.os.AsyncTask#doInBackground doInBackground()} и работает в потоке пользовательского интерфейса, так что вы можете безопасно +обновлять пользовательский интерфейс. Задача выполняется через вызов метода {@link android.os.AsyncTask#execute execute()} +из потока пользовательского интерфейса.

+ +

Например, можно реализовать предыдущий пример с помощью метода {@link android.os.AsyncTask} следующим +образом:

+ +
+public void onClick(View v) {
+    new DownloadImageTask().execute("http://example.com/image.png");
+}
+
+private class DownloadImageTask extends AsyncTask<String, Void, Bitmap> {
+    /** The system calls this to perform work in a worker thread and
+      * delivers it the parameters given to AsyncTask.execute() */
+    protected Bitmap doInBackground(String... urls) {
+        return loadImageFromNetwork(urls[0]);
+    }
+    
+    /** The system calls this to perform work in the UI thread and delivers
+      * the result from doInBackground() */
+    protected void onPostExecute(Bitmap result) {
+        mImageView.setImageBitmap(result);
+    }
+}
+
+ +

Теперь пользовательский интерфейс защищен и код стал проще, так как работа разделена на +часть, которая должна выполняться в рабочем потоке, и часть, которая должна выполняться в потоке пользовательского интерфейса.

+ +

Прочитайте статью {@link android.os.AsyncTask}, чтобы полностью понять +использование этого класса. Здесь приведен краткий обзор его работы:

+ +
    +
  • Можно указывать тип параметров, значения хода выполнения и конечное +значение задания с помощью универсальных компонентов
  • +
  • Метод {@link android.os.AsyncTask#doInBackground doInBackground()} выполняется автоматически +в рабочем потоке
  • +
  • Методы {@link android.os.AsyncTask#onPreExecute onPreExecute()}, {@link +android.os.AsyncTask#onPostExecute onPostExecute()} и {@link +android.os.AsyncTask#onProgressUpdate onProgressUpdate()} запускаются в потоке пользовательского интерфейса
  • +
  • Значение, возвращенное методом {@link android.os.AsyncTask#doInBackground doInBackground()}, отправляется в метод +{@link android.os.AsyncTask#onPostExecute onPostExecute()}
  • +
  • Можно вызвать {@link android.os.AsyncTask#publishProgress publishProgress()} в любой момент в {@link +android.os.AsyncTask#doInBackground doInBackground()} для выполнения {@link +android.os.AsyncTask#onProgressUpdate onProgressUpdate()} в потоке пользовательского интерфейса
  • +
  • Задание можно отменить в любой момент из любого потока
  • +
+ +

Предупреждение! Другая проблема, с которой вы можете столкнуться при использовании рабочего +потока, состоит в непредсказуемом перезапуске действия вследствие изменения конфигурации в режиме выполнения, +(например, когда пользователь изменяет ориентацию экрана), что может разрушить рабочий поток. Чтобы +увидеть, как можно сохранить задание во время одного из подобных перезапусков и как правильно отменить задание +при разрушении действия, изучите исходный код примера приложения Shelves.

+ + +

Потокобезопасные методы

+ +

В некоторых ситуациях реализованные методы могут вызываться из нескольких потоков и, следовательно, +должны быть написаны с сохранением потокобезопасности.

+ +

В первую очередь это относится к методам, которые можно вызывать удаленно, например, к методам в связанной службе. Когда вызов +метода реализуется в классе {@link android.os.IBinder}, происходящем из того же процесса, в котором выполняется +{@link android.os.IBinder IBinder}, метод выполняется в потоке вызывающего метода. +Однако, когда вызов происходит из другого процесса, метод выполняется в потоке, выбранном из пула +потоков, которые система поддерживает в том же процессе, что и {@link android.os.IBinder +IBinder} (он не выполняется в потоке пользовательского интерфейса процесса). Например, поскольку метод +{@link android.app.Service#onBind onBind()} службы будет вызываться из потока пользовательского интерфейса +процесса службы, методы, реализованные в объекте, который возвращает {@link android.app.Service#onBind +onBind()} (например, подкласс, который реализует методы RPC), будут вызываться из потоков +в пуле. Так как служба может иметь несколько клиентов, несколько потоков из пула могут одновременно использовать +один и тот же метод {@link android.os.IBinder IBinder}. Поэтому методы {@link android.os.IBinder +IBinder} должны быть реализованы с сохранением потокобезопасности.

+ +

Аналогичным образом поставщик контента может получать запросы данных, которые происходят из другого процесса. +Хотя классы {@link android.content.ContentResolver} и {@link android.content.ContentProvider} +скрывают подробности управления взаимодействием процессов, методы {@link +android.content.ContentProvider}, которые отвечают на эти запросы, —методы {@link +android.content.ContentProvider#query query()}, {@link android.content.ContentProvider#insert +insert()}, {@link android.content.ContentProvider#delete delete()}, {@link +android.content.ContentProvider#update update()} и {@link android.content.ContentProvider#getType +getType()} —вызываются из пула потоков в процессе поставщика контента, а не в процессе +потока пользовательского интерфейса. Поскольку эти методы могут вызываться из любого числа потоков одновременно, +они также должны быть реализованы с сохранением потокобезопасности.

+ + +

Взаимодействие процессов

+ +

Система Android предлагает механизм взаимодействия процессов (IPC) с помощью удаленного вызова процедуры +(RPC), при котором метод вызывается действием или другим компонентом приложения, но выполняется +удаленно (в другом процессе) с возвратом всех результатов +вызывающему компоненту. Это влечет разложение вызова метода и его данных до уровня, понятного +операционной системе, передачу его из локального процесса и адресного пространства удаленному процессу и +адресному пространству, а затем повторную сборку и восстановление вызова. После этого возвращенные значения +передаются в обратном направлении. Система Android содержит все коды для выполнения этих механизмов IPC, +так что вы можете сосредоточиться на определении и реализации программного интерфейса RPC.

+ +

Для выполнения IPC приложение должно быть привязано к службе с помощью метода {@link +android.content.Context#bindService bindService()}. Дополнительные сведения представлены в разделе Службы руководства для разработчиков.

+ + + diff --git a/docs/html-intl/intl/ru/guide/components/recents.jd b/docs/html-intl/intl/ru/guide/components/recents.jd new file mode 100644 index 0000000000000000000000000000000000000000..37206318e2f3ffcc82d56f1ccc03f1b4661d73d0 --- /dev/null +++ b/docs/html-intl/intl/ru/guide/components/recents.jd @@ -0,0 +1,256 @@ +page.title=Экран обзора +page.tags="recents","overview" + +@jd:body + + + +

Экран обзора (также используются названия: экран последних задач, список последних задач или последние приложения) +является элементом пользовательского интерфейса системного уровня, в котором содержится список последних +операций и задач. Пользователь +может перемещаться по списку и выбирать задачи для возобновления, или жестом удалять задачи +из списка. В версии Android 5.0 (уровень API 21) несколько экземпляров +одной операции, содержащие различные документы, могут отображаться в виде задач на экране обзора. Например, +Google Диск может иметь задачу для каждого из нескольких документов Google. На экране обзора каждый документ +отображается в виде задачи.

+ + +

Рисунок 1. Экран обзора, на котором показаны три документа Google Диск, + представленные в виде отдельных задач.

+ +

Обычно следует разрешить системе определить способ представления ваших задач и +операций на экране обзора. Вам не нужно менять это поведение. +Однако приложение может определять способ и время появления операции на экране обзора. С помощью класса +{@link android.app.ActivityManager.AppTask} можно управлять задачами, а с помощью флагов операции класса +{@link android.content.Intent} указывается, когда операция добавляется на экран обзора +или удаляется с него. Кроме того, атрибуты +<activity> позволяют устанавливать поведение в манифесте.

+ +

Добавление задач на экран обзора

+ +

Использование флагов класса {@link android.content.Intent} для добавления задачи обеспечивает лучшее управление +временем и способом открытия или повторного открытия документа на экране обзора. С помощью атрибутов +<activity> +можно выбрать открытие документа в новой задаче или повторное использование +существующей задачи для документа.

+ +

Использование флага Intent для добавления задачи

+ +

При создании нового документа для операции вы вызываете метод +{@link android.app.ActivityManager.AppTask#startActivity(android.content.Context, android.content.Intent, android.os.Bundle) startActivity()} +класса {@link android.app.ActivityManager.AppTask}, передавая ему intent, +который запускает операцию. Для вставки логического разрыва, чтобы система обрабатывала вашу операцию как новую +задачу на экране обзора, передайте флаг {@link android.content.Intent#FLAG_ACTIVITY_NEW_DOCUMENT} +в метод {@link android.content.Intent#addFlags(int) addFlags()} {@link android.content.Intent}, +который запускает операцию.

+ +

Примечание. Флаг {@link android.content.Intent#FLAG_ACTIVITY_NEW_DOCUMENT} +замещает флаг {@link android.content.Intent#FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET}, +который является устаревшим для систем Android 5.0 и выше (уровень API 21).

+ +

Если вы установили флаг {@link android.content.Intent#FLAG_ACTIVITY_MULTIPLE_TASK} при создании +нового документа, система всегда создает новую задачу с целевой операцией в качестве корня. +Этот параметр позволяет открывать один документ в нескольких задачах. Следующий код показывает, +как это делает основная операция:

+ +

+DocumentCentricActivity.java

+
+public void createNewDocument(View view) {
+      final Intent newDocumentIntent = newDocumentIntent();
+      if (useMultipleTasks) {
+          newDocumentIntent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
+      }
+      startActivity(newDocumentIntent);
+  }
+
+  private Intent newDocumentIntent() {
+      boolean useMultipleTasks = mCheckbox.isChecked();
+      final Intent newDocumentIntent = new Intent(this, NewDocumentActivity.class);
+      newDocumentIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
+      newDocumentIntent.putExtra(KEY_EXTRA_NEW_DOCUMENT_COUNTER, incrementAndGet());
+      return newDocumentIntent;
+  }
+
+  private static int incrementAndGet() {
+      Log.d(TAG, "incrementAndGet(): " + mDocumentCounter);
+      return mDocumentCounter++;
+  }
+}
+
+ +

Примечание. Операции, запущенные с флагом {@code FLAG_ACTIVITY_NEW_DOCUMENT}, +должны иметь значение атрибута {@code android:launchMode="standard"} (по умолчанию), установленное +в манифесте.

+ +

Когда основная операция запускает новую операцию, система ищет в существующих задачах одну, +значение intent которой соответствует имени компонента и данным Intent для операции. Если задача +не найдена или intent содержит флаг {@link android.content.Intent#FLAG_ACTIVITY_MULTIPLE_TASK}, +создается новая задача с операцией в качестве корня. Если задача найдена, система выводит + эту задачу на передний план и передает новое значение intent в {@link android.app.Activity#onNewIntent onNewIntent()}. +Новая операция получает intent и создает новый документ на экране обзора, как +в следующем примере:

+ +

+NewDocumentActivity.java

+
+@Override
+protected void onCreate(Bundle savedInstanceState) {
+    super.onCreate(savedInstanceState);
+    setContentView(R.layout.activity_new_document);
+    mDocumentCount = getIntent()
+            .getIntExtra(DocumentCentricActivity.KEY_EXTRA_NEW_DOCUMENT_COUNTER, 0);
+    mDocumentCounterTextView = (TextView) findViewById(
+            R.id.hello_new_document_text_view);
+    setDocumentCounterText(R.string.hello_new_document_counter);
+}
+
+@Override
+protected void onNewIntent(Intent intent) {
+    super.onNewIntent(intent);
+    /* If FLAG_ACTIVITY_MULTIPLE_TASK has not been used, this activity
+    is reused to create a new document.
+     */
+    setDocumentCounterText(R.string.reusing_document_counter);
+}
+
+ + +

Использование атрибута Операция для добавления задачи

+ +

В манифесте операции можно также указать, что операция всегда запускается в новой задаче. Для этого используется атрибут +<activity> +, +{@code android:documentLaunchMode}. Этот атрибут имеет четыре значения, которые работают следующим образом, +когда пользователь открывает документ в приложении:

+ +
+
"{@code intoExisting}"
+
Операция повторно использует существующую задачу для документа. Это равносильно установке +флага {@link android.content.Intent#FLAG_ACTIVITY_NEW_DOCUMENT} без установки +флага {@link android.content.Intent#FLAG_ACTIVITY_MULTIPLE_TASK}, как описано в разделе +Использование флага Intent для добавления задачи выше.
+ +
"{@code always}"
+
Операция создает новую задачу для документа, даже если документ уже открыт. Использование +этого значения равносильно установке обоих флагов {@link android.content.Intent#FLAG_ACTIVITY_NEW_DOCUMENT} +и {@link android.content.Intent#FLAG_ACTIVITY_MULTIPLE_TASK}.
+ +
"{@code none”}"
+
Операция не создает новой задачи для документа. Экран обзора обрабатывает +операцию как операцию по умолчанию: на экране обзора отображается одна задача для приложения, которая +возобновляется с любой последней операции, вызванной пользователем.
+ +
"{@code never}"
+
Операция не создает новой задачи для документа. Установка этого значения +переопределяет поведение флагов {@link android.content.Intent#FLAG_ACTIVITY_NEW_DOCUMENT} + и {@link android.content.Intent#FLAG_ACTIVITY_MULTIPLE_TASK}, если оба они установлены в +intent, и на экране обзора отображается одна задача для приложения, которая +возобновляется с любой последней операции, вызванной пользователем.
+
+ +

Примечание. Для значений кроме {@code none} и {@code never} +операция должна быть определена с атрибутом {@code launchMode="standard"}. Если этот атрибут не указан, +используется {@code documentLaunchMode="none"}.

+ +

Удаление задач

+ +

По умолчанию задача документа автоматически удаляется с экрана обзора после завершения +соответствующей операции. Можно переопределить это поведение с помощью класса {@link android.app.ActivityManager.AppTask}, +с флагом {@link android.content.Intent} или атрибутом +<activity>.

+ +

Можно в любой момент полностью убрать задачу с экрана обзора, установив для атрибута +<activity> + +{@code android:excludeFromRecents} значение {@code true}.

+ +

Можно установить максимальное число задач, которое ваше приложение может включить в экран обзора, установив для атрибута +<activity> + {@code android:maxRecents} + целое значение. Значение по умолчанию: 16. При достижении максимального количества задач самая +долго не используемая задача удаляется с экрана обзора. Максимальное значение {@code android:maxRecents} составляет +50 (25 для устройств с малым объемом памяти); Значения менее 1 не допускаются.

+ +

Использование класса AppTask для удаления задач

+ +

В операции, которая создает новую задачу на экране обзора, +можно указать время удаления задачи и завершения всех связанных с ней операций, вызвав +метод {@link android.app.ActivityManager.AppTask#finishAndRemoveTask() finishAndRemoveTask()}.

+ +

+NewDocumentActivity.java

+
+public void onRemoveFromRecents(View view) {
+    // The document is no longer needed; remove its task.
+    finishAndRemoveTask();
+}
+
+ +

Примечание. Использование +метода {@link android.app.ActivityManager.AppTask#finishAndRemoveTask() finishAndRemoveTask()}, +который переопределяет использование тега {@link android.content.Intent#FLAG_ACTIVITY_RETAIN_IN_RECENTS}, +рассмотрен ниже.

+ +

Сохранение завершенных задач

+ +

Чтобы сохранить задачу на экране обзора, даже если ее операция завершена, передайте +флаг {@link android.content.Intent#FLAG_ACTIVITY_RETAIN_IN_RECENTS} в метод +{@link android.content.Intent#addFlags(int) addFlags()} объекта Intent, который запускает операцию.

+ +

+DocumentCentricActivity.java

+
+private Intent newDocumentIntent() {
+    final Intent newDocumentIntent = new Intent(this, NewDocumentActivity.class);
+    newDocumentIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT |
+      android.content.Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS);
+    newDocumentIntent.putExtra(KEY_EXTRA_NEW_DOCUMENT_COUNTER, incrementAndGet());
+    return newDocumentIntent;
+}
+
+ +

Для достижения того же результата установите для атрибута +<activity> + +{@code android:autoRemoveFromRecents} значение {@code false}. Значение по умолчанию {@code true} +для операций документа и {@code false} для обычных операций. Использование этого атрибута переопределяет +флаг {@link android.content.Intent#FLAG_ACTIVITY_RETAIN_IN_RECENTS}, описанный выше.

+ + + + + + + diff --git a/docs/html-intl/intl/ru/guide/components/services.jd b/docs/html-intl/intl/ru/guide/components/services.jd new file mode 100644 index 0000000000000000000000000000000000000000..62a6a7ef9d15a8460cce5afbaf5fbb9b36f7a83c --- /dev/null +++ b/docs/html-intl/intl/ru/guide/components/services.jd @@ -0,0 +1,813 @@ +page.title=Службы +@jd:body + + + + +

{@link android.app.Service} является компонентом приложения, который может выполнять +длительные операции в фоновом режиме и не содержит пользовательского интерфейса. Другой компонент +приложения может запустить службу, которая продолжит работу в фоновом режиме даже в том случае, когда пользователь +перейдет в другое приложение. Кроме того, компонент может привязаться к службе для +взаимодействия с ней и даже выполнять межпроцессное взаимодействие (IPC). Например, служба может +обрабатывать сетевые транзакции, воспроизводить музыку, выполнять ввод-вывод файла или взаимодействовать с поставщиком контента, и все +это в фоновом режиме.

+ +

Фактически служба может принимать две формы:

+ +
+
Запущенная
+
Служба является «запущенной», когда компонент приложения (например, операция) запускает ее +вызовом {@link android.content.Context#startService startService()}. После запуска служба +может работать в фоновом режиме в течение неограниченного времени, даже если уничтожен компонент, который ее запустил. Обычно +запущенная служба выполняет одну операцию и не возвращает результатов вызывающему компоненту. +Например, она может загружать или выгружать файл по сети. Когда операция выполнена, +служба должна остановиться самостоятельно.
+
Привязанная
+
Служба является «привязанной», когда компонент приложения привязывается к ней вызовом {@link +android.content.Context#bindService bindService()}. Привязанная служба предлагает интерфейс клиент-сервер, +который позволяет компонентам взаимодействовать со службой, отправлять запросы, получать результаты и даже +делать это между разными процессами посредством межпроцессного взаимодействия (IPC). Привязанная служба работает только пока +к ней привязан другой компонент приложения. К службе могут быть привязаны несколько компонентов одновременно, +но когда все они отменяют привязку, служба уничтожается.
+
+ +

Хотя в этой документации эти два типа служб обсуждаются отдельно, служба может +работать обеими способами — она может быть запущенной (и работать в течение неограниченного времени) и допускать привязку. +Это зависит от реализации пары методов обратного вызова: {@link +android.app.Service#onStartCommand onStartCommand()} позволяет компонентам запускать службу, а {@link +android.app.Service#onBind onBind()} позволяет выполнять привязку.

+ +

Независимо от состояния приложения (запущенное, привязанное или и оба сразу) любой компонент приложения +может использовать службу (даже из отдельного приложения) подобно тому, как любой компонент может использовать +операцию — запустив ее с помощью {@link android.content.Intent}. Однако вы можете объявить закрытую +службу в файле манифеста и заблокировать доступ к ней из других приложений. Более подробно +это обсуждается в разделе Объявление службы +в манифесте.

+ +

Внимание! Служба работает +в основном потоке ведущего процесса — служба не создает своего потока +и не выполняется в отдельном процессе (если вы не указали иное). Это означает, +что если ваша служба собирается выполнять любую работу с высокой нагрузкой ЦП или блокирующие операции (например, воспроизведение MP3 +или сетевые операции), вы должны создать в службе новый поток для выполнения этой работы. Используя +отдельный поток, вы снижаете риск возникновения ошибок «Приложение не отвечает», и +основной поток приложения может отрабатывать взаимодействие пользователя с вашими операциями.

+ + +

Основы

+ + + +

Чтобы создать службу, необходимо создать подкласс класса {@link android.app.Service} (или одного +из существующих его подклассов). В вашей реализации необходимо переопределить некоторые методы обратного вызова, +которые обрабатывают ключевые моменты жизненного цикла службы и при необходимости +предоставляют механизм привязывания компонентов. Наиболее важные методы обратного вызова, которые необходимо переопределить:

+ +
+
{@link android.app.Service#onStartCommand onStartCommand()}
+
Система вызывает этот метод, когда другой компонент, например, операция, +запрашивает запуск этой службы, вызывая {@link android.content.Context#startService +startService()}. После выполнения этого метода служба запускается и может в течение неограниченного времени +работать в фоновом режиме. Если вы реализуете такой метод, вы обязаны остановить службу +посредством вызова {@link android.app.Service#stopSelf stopSelf()} или {@link +android.content.Context#stopService stopService()}. (Если требуется только обеспечить привязку, +реализовывать этот метод не обязательно).
+
{@link android.app.Service#onBind onBind()}
+
Система вызывает этот метод, когда другой компонент хочет выполнить привязку +к службе (например, для выполнения удаленного вызова процедуры) путем вызова {@link android.content.Context#bindService +bindService()}. В вашей реализации этого метода вы должны обеспечить интерфейс, который клиенты +используют для взаимодействия со службой, возвращая {@link android.os.IBinder}. Всегда необходимо реализовывать +этот метод, но если вы не хотите разрешать привязку, необходимо возвращать значение null.
+
{@link android.app.Service#onCreate()}
+
Система вызывает этот метод при первом создании службы для выполнения однократных +процедур настройки (перед вызовом {@link android.app.Service#onStartCommand onStartCommand()} или +{@link android.app.Service#onBind onBind()}). Если служба уже запущена, этот метод не +вызывается.
+
{@link android.app.Service#onDestroy()}
+
Система вызывает этот метод, когда служба более не используется и выполняется ее уничтожение. +Ваша служба должна реализовать это для очистки ресурсов, таких как потоки, зарегистрированные +приемники, ресиверы и т. д. Это последний вызов, который получает служба.
+
+ +

Если компонент запускает службу посредством вызова {@link +android.content.Context#startService startService()} (что приводит к вызову {@link +android.app.Service#onStartCommand onStartCommand()}), то служба +продолжает работу, пока она не остановится самостоятельно с помощью {@link android.app.Service#stopSelf()} или другой +компонент не остановит ее посредством вызова {@link android.content.Context#stopService stopService()}.

+ +

Если компонент вызывает +{@link android.content.Context#bindService bindService()} для создания службы (и {@link +android.app.Service#onStartCommand onStartCommand()} не вызывается), то служба работает, пока +к ней привязан компонент. Как только выполняется отмена привязки службы ко всем клиентам, +система уничтожает службу.

+ +

Система Android будет принудительно останавливать службу только в том случае, когда не хватает памяти, и необходимо восстановить системные +для операции, которая отображается на переднем плане. Если служба привязана к операции, которая отображается на переднем плане, +менее вероятно, что она будет уничтожена, и если служба объявлена для выполнения в фоновом режиме (как обсуждалось выше), она почти никогда не будет уничтожаться. +В противном случае, если служба была запущена и является длительной, система со временем будет опускать ее положение в списке +фоновых задач, и служба станет очень чувствительной к +уничтожению — если ваша служба запущена, вы должны предусмотреть изящную обработку ее перезапуска +системой. Если система уничтожает вашу службу, она перезапускает ее, как только снова появляется +доступ к ресурсам (хотя это также зависит от значения, возвращаемого методом {@link +android.app.Service#onStartCommand onStartCommand()}, как обсуждается ниже). Дополнительная информация +о ситуациях, в которых система может уничтожить службу приведена в документе Процессы и потоки +.

+ +

В следующих разделах описаны способы создания служб каждого типа и использования +их из других компонентов приложения.

+ + + +

Объявление службы в манифесте

+ +

Все службы, как и операции (и другие компоненты), должны быть объявлены в файле +манифеста вашего приложения.

+ +

Чтобы объявить службу, добавьте элемент {@code <service>} +, в качестве дочернегоэлемента {@code <application>} +. Например:

+ +
+<manifest ... >
+  ...
+  <application ... >
+      <service android:name=".ExampleService" />
+      ...
+  </application>
+</manifest>
+
+ +

Дополнительные сведения об объявлении службы +в манифесте см. в справке по элементу {@code <service>}.

+ +

Имеются другие атрибуты, которые можно включить в элемент {@code <service>} для +задания свойств, например, необходимых для запуска разрешений, и процесса, +в котором должна выполняться служба. Атрибут {@code android:name} +является единственным обязательным атрибутом — он указывает имя класса для службы. После +публикации вашего приложения вам не следует менять это имя, поскольку это может разрушить +код из-за зависимости от явных намерений, используемых, чтобы запустить или привязать службу (ознакомьтесь с публикацией Вещи, которые +нельзя менять в блоге разработчиков). + +

Для обеспечения безопасности приложения всегда используйте явное намерение при запуске +или привязке {@link android.app.Service} и не объявляйте фильтров намерений для службы. Если вам +важно допустить некоторую неопределенность в отношении того, какая служба запускается, вы можете +предоставить фильтры намерений для ваших служб и исключить имя компонента из {@link +android.content.Intent}, но затем вы должны установить пакет для намерения с помощью {@link +android.content.Intent#setPackage setPackage()}, который обеспечивает достаточное устранение неоднозначности +для целевой службы.

+ +

Дополнительно можно обеспечить доступность вашей службы только для вашего приложения, +включив атрибут {@code android:exported} +и установив для него значение {@code "false"}. Это не позволяет другим приложениям запускать +вашу службу даже при использовании явного намерения.

+ + + + +

Создание запущенной службы

+ +

Запущенная служба — это служба, которую запускает другой компонент вызовом {@link +android.content.Context#startService startService()}, что приводит к вызову +метода {@link android.app.Service#onStartCommand onStartCommand()} службы.

+ +

При запуске служба обладает сроком жизни, не зависящим от запустившего ее компонента, +и может работать в фоновом режиме в течение неограниченного времени, +даже если уничтожен компонент, который ее запустил. Поэтому после выполнения своей работы служба должна остановиться самостоятельно +посредством вызова метода {@link android.app.Service#stopSelf stopSelf()}, либо ее может остановить другой компонент +посредством вызова метода{@link android.content.Context#stopService stopService()}.

+ +

Компонент приложения, например, операция, может запустить службу, вызвав метод {@link +android.content.Context#startService startService()} и передав объект {@link android.content.Intent}, +который указывает службу и любые данные, которые служба должна использовать. Служба получает +этот объект {@link android.content.Intent} в методе {@link android.app.Service#onStartCommand +onStartCommand()}.

+ +

Предположим, что операции требуется сохранить некоторые данные в сетевой базе данных. Операция может +запустить службу и предоставить ей данные для сохранения, передав намерение в метод {@link +android.content.Context#startService startService()}. Служба получает намерение в методе {@link +android.app.Service#onStartCommand onStartCommand()}, подключается к Интернету и выполняет транзакцию +с базой данных. Когда транзакция выполнена, служба останавливается +самостоятельно и уничтожается.

+ +

Внимание! По умолчанию службы работают в том же процессе, что и приложение, +в котором они объявлены, а также в основном потоке этого приложения. Поэтому, если ваша служба +выполняет интенсивные или блокирующие операции, в то время как пользователь взаимодействует с операцией из того же +приложения, служба будет замедлять выполнение операции. Чтобы избежать негативного воздействия на скорость работы +приложения, вы должны запустить новый поток внутри службы.

+ +

Традиционно имеется два класса, которые вы можете наследовать для создания запущенной службы:

+
+
{@link android.app.Service}
+
Это базовый класс для всех служб. Когда вы наследуете этот класс, важно +создать новый поток, в котором будет выполняться вся работа службы, поскольку по умолчанию служба использует основной поток вашего +приложения, что может замедлить любую операцию, которую +выполняет ваше приложение.
+
{@link android.app.IntentService}
+
Это подкласс класса {@link android.app.Service}, который использует рабочий поток для обработки всех +запросов запуска поочередно. Это оптимальный вариант, если вам не требуется, чтобы ваша служба +обрабатывала несколько запросов одновременно. Достаточно реализовать метод {@link +android.app.IntentService#onHandleIntent onHandleIntent()}, который получает намерение для каждого +запроса запуска, позволяя выполнять фоновую работу.
+
+ +

В следующих разделах описано, как реализовать службу с помощью любого их этих +классов.

+ + +

Наследование класса IntentService

+ +

Так как большинству запущенных приложений не требуется обрабатывать несколько запросов одновременно, +(что может быть действительно опасным сценарием), вероятно будет лучше, если вы +реализуете свою службу с помощью класса {@link android.app.IntentService}.

+ +

Класс {@link android.app.IntentService} делает следующее:

+ +
    +
  • Создает рабочий поток по умолчанию, который выполняет все намерения, доставленные в метод {@link +android.app.Service#onStartCommand onStartCommand()}, отдельно от основного потока +вашего приложения.
  • +
  • Создает рабочую очередь, которая передает намерения по одному в вашу реализацию метода {@link +android.app.IntentService#onHandleIntent onHandleIntent()}, поэтому вы не должны беспокоиться +относительно многопоточности.
  • +
  • Останавливает службу после обработки всех запросов запуска, поэтому вам никогда не требуется вызывать +{@link android.app.Service#stopSelf}.
  • +
  • Предоставляет реализацию метода {@link android.app.IntentService#onBind onBind()} по умолчанию, которая +возвращает null.
  • +
  • Предоставляет реализацию метода {@link android.app.IntentService#onStartCommand +onStartCommand()} по умолчанию, которая отправляет намерение в рабочую очередь и затем в вашу реализацию {@link +android.app.IntentService#onHandleIntent onHandleIntent()}.
  • +
+ +

Все это означает, что вам достаточно реализовать метод {@link +android.app.IntentService#onHandleIntent onHandleIntent()} для выполнения работы, предоставленной +клиентом. (Хотя, кроме того, вы должны предоставить маленький конструктор для службы).

+ +

Здесь приведен пример реализации класса {@link android.app.IntentService}:

+ +
+public class HelloIntentService extends IntentService {
+
+  /**
+   * A constructor is required, and must call the super {@link android.app.IntentService#IntentService}
+   * constructor with a name for the worker thread.
+   */
+  public HelloIntentService() {
+      super("HelloIntentService");
+  }
+
+  /**
+   * The IntentService calls this method from the default worker thread with
+   * the intent that started the service. When this method returns, IntentService
+   * stops the service, as appropriate.
+   */
+  @Override
+  protected void onHandleIntent(Intent intent) {
+      // Normally we would do some work here, like download a file.
+      // For our sample, we just sleep for 5 seconds.
+      long endTime = System.currentTimeMillis() + 5*1000;
+      while (System.currentTimeMillis() < endTime) {
+          synchronized (this) {
+              try {
+                  wait(endTime - System.currentTimeMillis());
+              } catch (Exception e) {
+              }
+          }
+      }
+  }
+}
+
+ +

Это все, что нужно: конструктор и реализация класса {@link +android.app.IntentService#onHandleIntent onHandleIntent()}.

+ +

Если вы решили переопределить также и другие методы обратного вызова, такие как {@link +android.app.IntentService#onCreate onCreate()}, {@link +android.app.IntentService#onStartCommand onStartCommand()} или {@link +android.app.IntentService#onDestroy onDestroy()}, обязательно вызовите реализацию суперкласса, +чтобы класс {@link android.app.IntentService} мог правильно обрабатывать жизненный цикл рабочего потока.

+ +

Например, метод {@link android.app.IntentService#onStartCommand onStartCommand()} должен возвращать +реализацию по умолчанию (которая доставляет намерение в {@link +android.app.IntentService#onHandleIntent onHandleIntent()}):

+ +
+@Override
+public int onStartCommand(Intent intent, int flags, int startId) {
+    Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show();
+    return super.onStartCommand(intent,flags,startId);
+}
+
+ +

Помимо {@link android.app.IntentService#onHandleIntent onHandleIntent()}, единственный метод, +из которого вам не требуется вызывать суперкласс, это метод {@link android.app.IntentService#onBind +onBind()} (но его нужно реализовывать только в случае, если ваша служба допускает привязку).

+ +

В следующем разделе вы увидите, как реализовывается служба такого же типа при наследовании +базового класса {@link android.app.Service}, которая содержит намного больше кода, но которая может +подойти, если вам требуется обрабатывать одновременные запросы запуска.

+ + +

Наследование класса Service

+ +

Как вы видели в предыдущем разделе, использование класса {@link android.app.IntentService} значительно упрощает +реализацию запущенной службы. Однако, если необходимо, чтобы ваша служба +поддерживала многопоточность (вместо обработки запросов запуска через рабочую очередь), можно +наследовать класс {@link android.app.Service} для обработки каждого намерения.

+ +

В качестве примера приведена следующая реализация класса {@link +android.app.Service}, которая выполняет ту же работу, как и пример выше, использующий класс {@link +android.app.IntentService}. То есть для каждого запроса запуска он использует рабочий поток для выполнения +задания и обрабатывает запросы по одному.

+ +
+public class HelloService extends Service {
+  private Looper mServiceLooper;
+  private ServiceHandler mServiceHandler;
+
+  // Handler that receives messages from the thread
+  private final class ServiceHandler extends Handler {
+      public ServiceHandler(Looper looper) {
+          super(looper);
+      }
+      @Override
+      public void handleMessage(Message msg) {
+          // Normally we would do some work here, like download a file.
+          // For our sample, we just sleep for 5 seconds.
+          long endTime = System.currentTimeMillis() + 5*1000;
+          while (System.currentTimeMillis() < endTime) {
+              synchronized (this) {
+                  try {
+                      wait(endTime - System.currentTimeMillis());
+                  } catch (Exception e) {
+                  }
+              }
+          }
+          // Stop the service using the startId, so that we don't stop
+          // the service in the middle of handling another job
+          stopSelf(msg.arg1);
+      }
+  }
+
+  @Override
+  public void onCreate() {
+    // Start up the thread running the service.  Note that we create a
+    // separate thread because the service normally runs in the process's
+    // main thread, which we don't want to block.  We also make it
+    // background priority so CPU-intensive work will not disrupt our UI.
+    HandlerThread thread = new HandlerThread("ServiceStartArguments",
+            Process.THREAD_PRIORITY_BACKGROUND);
+    thread.start();
+
+    // Get the HandlerThread's Looper and use it for our Handler
+    mServiceLooper = thread.getLooper();
+    mServiceHandler = new ServiceHandler(mServiceLooper);
+  }
+
+  @Override
+  public int onStartCommand(Intent intent, int flags, int startId) {
+      Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show();
+
+      // For each start request, send a message to start a job and deliver the
+      // start ID so we know which request we're stopping when we finish the job
+      Message msg = mServiceHandler.obtainMessage();
+      msg.arg1 = startId;
+      mServiceHandler.sendMessage(msg);
+
+      // If we get killed, after returning from here, restart
+      return START_STICKY;
+  }
+
+  @Override
+  public IBinder onBind(Intent intent) {
+      // We don't provide binding, so return null
+      return null;
+  }
+
+  @Override
+  public void onDestroy() {
+    Toast.makeText(this, "service done", Toast.LENGTH_SHORT).show();
+  }
+}
+
+ +

Как можно видеть, этот код значительно длиннее, чем код с использованием класса {@link android.app.IntentService}.

+ +

Однако, так как вы обрабатываете каждый вызов {@link android.app.Service#onStartCommand +onStartCommand()} самостоятельно, вы можете выполнять несколько запросов одновременно. Данный код +выполняет не совсем эту работу, но при необходимости вы можете создавать новые потоки для каждого +запроса и сразу запускать их (а не ожидать завершения предыдущего запроса).

+ +

Обратите внимание, что метод {@link android.app.Service#onStartCommand onStartCommand()} должен +возвращать целое число. Это целое число описывает, как система должна продолжать выполнение службы в случае, +когда система уничтожила ее (как описано выше, реализация по умолчанию для класса {@link +android.app.IntentService} обрабатывает эту ситуацию, хотя вы изменить ход реализации). Значение, +возвращаемое методом {@link android.app.Service#onStartCommand onStartCommand()}, должно быть одной из следующих +констант:

+ +
+
{@link android.app.Service#START_NOT_STICKY}
+
Если система уничтожает службу после возвращения из {@link android.app.Service#onStartCommand +onStartCommand()}, не нужно повторно создавать службу, если нет ожидающих +доставки намерений. Это самый безопасный вариант, позволяющий избежать запуска вашей службы, когда это не нужно +и когда ваше приложение может просто перезапустить любые незавершенные задания.
+
{@link android.app.Service#START_STICKY}
+
Если система уничтожает службу после возвращения из {@link android.app.Service#onStartCommand +onStartCommand()}, повторно создайте службу и вызовите {@link +android.app.Service#onStartCommand onStartCommand()}, но не передавайте последнее намерение повторно. +Вместо этого система вызывает метод {@link android.app.Service#onStartCommand onStartCommand()} с намерением, +которое имеет значение null, если нет ожидающих намерений для запуска службы. Если ожидающие намерения есть, +они доставляются. Это подходит для мультимедийных проигрывателей (или подобных служб), которые не +выполняют команды, а работают независимо и ожидают задание.
+
{@link android.app.Service#START_REDELIVER_INTENT}
+
Если система уничтожает службу после возвращения из {@link android.app.Service#onStartCommand +onStartCommand()}, повторно создайте службу и вызовите {@link +android.app.Service#onStartCommand onStartCommand()} с последним намерением, которое было доставлено +в службу. Все ожидающие намерения доставляются по очереди. Это подходит для служб, +активно выполняющих задание, которое должно быть возобновлено немедленно, например, для загрузок файла.
+
+

Для получения дополнительных сведений об этих возвращаемых значениях см. справочную документацию по ссылке для каждой +константы.

+ + + +

Запуск службы

+ +

Можно запустить службу из операции или другого компонента приложения, передав объект +{@link android.content.Intent} (указывающий службу, которую требуется запустить) в {@link +android.content.Context#startService startService()}. Система Android вызывает метод {@link +android.app.Service#onStartCommand onStartCommand()} службы и передает ей {@link +android.content.Intent}. (Ни в коем случае не следует вызывать метод {@link android.app.Service#onStartCommand +onStartCommand()} напрямую).

+ +

Например, операция может запустить службу из примера в предыдущем разделе ({@code +HelloSevice}), используя явное намерение с помощью {@link android.content.Context#startService +startService()}:

+ +
+Intent intent = new Intent(this, HelloService.class);
+startService(intent);
+
+ +

Метод {@link android.content.Context#startService startService()} возвращается немедленно, и система +Android вызывает метод службы {@link android.app.Service#onStartCommand +onStartCommand()}. Если служба еще не выполняется, система сначала вызывает {@link +android.app.Service#onCreate onCreate()}, а затем {@link android.app.Service#onStartCommand +onStartCommand()}.

+ +

Если служба также не представляет привязку, намерение, доставляемое с помощью {@link +android.content.Context#startService startService()}, является единственным режимом связи между +компонентом приложения и службой. Однако, если вы хотите, чтобы служба оправляла результат обратно, +клиент, который запускает службу, может создать объект {@link android.app.PendingIntent} для сообщения +(с помощью {@link android.app.PendingIntent#getBroadcast getBroadcast()}) и доставить его в службу +в объекте {@link android.content.Intent}, который запускает службу. Затем служба может использовать +сообщение для доставки результата.

+ +

Несколько запросов запуска службы приводят к нескольким соответствующим вызовам метода +{@link android.app.Service#onStartCommand onStartCommand()} службы. Однако для ее остановки достаточно только одного запроса на остановку +службы (с помощью {@link android.app.Service#stopSelf stopSelf()} или {@link +android.content.Context#stopService stopService()}).

+ + +

Остановка службы

+ +

Запущенная служба должна управлять своим жизненным циклом. То есть, система не останавливает и не +уничтожает службу, если не требуется восстановить память системы, и служба +продолжает работу после возвращения из метода {@link android.app.Service#onStartCommand onStartCommand()}. Поэтому +служба должна останавливаться самостоятельно посредством вызова метода {@link android.app.Service#stopSelf stopSelf()}, либо другой +компонент может остановить ее посредством вызова метода {@link android.content.Context#stopService stopService()}.

+ +

Получив запрос на остановку посредством {@link android.app.Service#stopSelf stopSelf()} или {@link +android.content.Context#stopService stopService()}, система как можно скорее уничтожает службу +.

+ +

Однако, если служба обрабатывает несколько запросов {@link +android.app.Service#onStartCommand onStartCommand()} одновременно, вы не должны останавливать службу +после завершения обработки запроса запуска, поскольку вы, вероятно, уже получили новый +запрос запуска (остановка в конце первого запроса привела бы к прерыванию второго). Чтобы избежать +этой проблемы, вы можете использовать метод {@link android.app.Service#stopSelf(int)}, гарантирующий, что ваш запрос на +остановку службы всегда основан на самом последнем запросе запуска. То есть, когда вы вызываете {@link +android.app.Service#stopSelf(int)}, вы передаете идентификатор запроса запуска (идентификатор startId, +доставленный в {@link android.app.Service#onStartCommand onStartCommand()}), которому соответствует ваш +запрос остановки. Тогда, если служба получит новый запрос запуска до того, как вы сможете вызвать {@link +android.app.Service#stopSelf(int)}, идентификатор не будет совпадать и служба не будет остановлена.

+ +

Внимание! Ваше приложение обязательно должно останавливать свои службы по окончании работы, +чтобы избежать расходования ресурсов системы и потребления энергии аккумулятора. При необходимости +другие компоненты могут остановить службу посредством вызова метода {@link +android.content.Context#stopService stopService()}. Даже если вы можете выполнять привязку службы, +следует всегда останавливать службу самостоятельно, если она когда-либо получила вызов {@link +android.app.Service#onStartCommand onStartCommand()}.

+ +

Дополнительные сведения о жизненном цикле службы представлены в разделе Управление жизненным циклом службы ниже.

+ + + +

Создание привязанной службы

+ +

Привязанная служба — это служба, которая допускает привязку к ней компонентов приложения посредством вызова {@link +android.content.Context#bindService bindService()} для создания долговременного соединения +(и обычно не позволяет компонентам запускать ее посредством вызова {@link +android.content.Context#startService startService()}).

+ +

Вы должны создать привязанную службу, когда вы хотите взаимодействовать со службой из операций +и других компонентов вашего приложения или показывать некоторые функции вашего приложения +другим приложениям посредством межпроцессного взаимодействия (IPC).

+ +

Чтобы создать привязанную службу, необходимо реализовать метод обратного вызова {@link +android.app.Service#onBind onBind()} для возвращения объекта {@link android.os.IBinder}, +который определяет интерфейс взаимодействия со службой. После этого другие компоненты приложения могут вызвать +метод {@link android.content.Context#bindService bindService()} для извлечения интерфейса и +начать вызывать методы службы. Служба существует только для обслуживания привязанного к ней компонента приложения, +поэтому, когда нет компонентов, привязанных к службе, система уничтожает ее +(вам не требуется останавливать привязанную службу, как это требуется для службы, запущенной +посредством {@link android.app.Service#onStartCommand onStartCommand()}).

+ +

Чтобы создать привязанную службу, необходимо в первую очередь определить интерфейс, взаимодействия +клиента со службой. Этот интерфейс между службой +и клиентом должен быть реализацией объекта {@link android.os.IBinder}, которую ваша служба должна +возвращать из метода обратного вызова {@link android.app.Service#onBind +onBind()}. После того, как клиент получает объект {@link android.os.IBinder}, он может начать +взаимодействие со службой посредством этого интерфейса.

+ +

Одновременно к службе могут быть привязаны несколько клиентов. Когда клиент заканчивает взаимодействие +со службой, он вызывает {@link android.content.Context#unbindService unbindService()} для отмены привязки. Как только +не остается ни одного клиента, привязанного к службе, система уничтожает службу.

+ +

Существует несколько способов реализации привязанной службы, и эти реализации сложнее, +чем реализации запущенной службы, поэтому обсуждение привязанной службы приведено в отдельном +документе Привязанные службы.

+ + + +

Отправка уведомлений пользователю

+ +

После запуска служба может уведомлять пользователя о событиях, используя Всплывающие уведомления или Уведомления в строке состояния.

+ +

Всплывающее уведомление — это сообщение, кратковременно появляющееся на поверхности текущего окна, +тогда как уведомление в строке состояния — это значок в строке состояния с сообщением, +который пользователь может выбрать, чтобы выполнить действие (такое как запуск операции).

+ +

Обычно уведомление в строке состояния является самым удобным решением, когда завершается какая-то фоновая работа +(например, завершена +загрузка файла), и пользователь может действовать. Когда пользователь выбирает уведомление в +расширенном виде, уведомление может запустить операцию (например, для просмотра загруженного файла).

+ +

Дополнительную информацию см. в руководствах для разработчиков Всплывающие уведомления и +Уведомления в строке состояния.

+ + + +

Запуск службы на переднем плане

+ +

Служба переднего плана — это служба, о которой пользователь активно +осведомлен, и поэтому она не является кандидатом для удаления системой в случае нехватки памяти. Служба +переднего плана должна выводить уведомление в строку состояния, которая находится под заголовком +«Постоянные». Это означает, что уведомление не может быть удалено, пока служба +не будет остановлена или удалена с переднего плана.

+ +

Например, музыкальный проигрыватель, который воспроизводит музыку из службы, должен быть настроен на работу +на переднем плане, так как пользователь точно знает о +его работе. Уведомление в строке состояния может показывать текущее произведение и позволять пользователю +запускать операцию для взаимодействия с музыкальным проигрывателем.

+ +

Для запроса на выполнение вашей службы на переднем плане вызовите метод {@link +android.app.Service#startForeground startForeground()}. Этот метод имеет два параметра: целое число, +которое однозначно идентифицирует уведомление и объект {@link +android.app.Notification} для строки состояния. Например:

+ +
+Notification notification = new Notification(R.drawable.icon, getText(R.string.ticker_text),
+        System.currentTimeMillis());
+Intent notificationIntent = new Intent(this, ExampleActivity.class);
+PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);
+notification.setLatestEventInfo(this, getText(R.string.notification_title),
+        getText(R.string.notification_message), pendingIntent);
+startForeground(ONGOING_NOTIFICATION_ID, notification);
+
+ +

Внимание! Целочисленный идентификатор ID, который вы передаете в метод {@link +android.app.Service#startForeground startForeground()}, не должен быть равен 0.

+ + +

Чтобы удалить службу с переднего плана, вызовите {@link +android.app.Service#stopForeground stopForeground()}. Этот метод содержит логическое значение, указывающее, +следует ли также удалять уведомление в строке состояния. Этот метод не останавливает +службу. Однако, если вы останавливаете службу, работающую на переднем плане, +уведомление также удаляется.

+ +

Дополнительную информацию об уведомлениях см. в разделе Создание уведомлений +в строке состояния.

+ + + +

Управление жизненным циклом службы

+ +

Жизненный цикл службы намного проще, чем жизненный цикл операции. Однако, намного важнее +уделить пристальное внимание тому, как ваша служба создается и уничтожается, так как служба +может работать в фоновом режиме без ведома пользователя.

+ +

Жизненный цикл службы от создания до уничтожения может следовать двум +разным путям:

+ +
    +
  • Запущенная служба +

    Служба создается, когда другой компонент вызывает метод {@link +android.content.Context#startService startService()}. Затем служба работает в течение неограниченного времени и должна +остановиться самостоятельно посредством вызова метода {@link +android.app.Service#stopSelf() stopSelf()}. Другой компонент также может остановить службу +посредством вызова метода {@link android.content.Context#stopService +stopService()}. Когда служба останавливается, система уничтожает ее.

  • + +
  • Привязанная служба +

    Служба создается, когда другой компонент (клиент) вызывает метод {@link +android.content.Context#bindService bindService()}. Затем клиент взаимодействует со службой +через интерфейс {@link android.os.IBinder}. Клиент может закрыть соединение посредством вызова +метода {@link android.content.Context#unbindService unbindService()}. К одной службе могут быть привязано +несколько клиентов, и когда все они отменяют привязку, система уничтожает службу. (Служба +не должна останавливаться самостоятельно.)

  • +
+ +

Эти два способа необязательно работают независимо друг от друга. То есть вы можете привязать службу, которая уже была +запущена посредством метода {@link android.content.Context#startService startService()}. Например, фоновая +музыкальная служба может быть запущена посредством вызова метода {@link android.content.Context#startService +startService()} с объектом {@link android.content.Intent}, который идентифицирует музыку для воспроизведения. Позже, +например, когда пользователь хочет получить доступ к управлению проигрывателем или информацию о текущем произведении, +операция может установить привязку к службе посредством вызова метода {@link +android.content.Context#bindService bindService()}. В подобных случаях методы {@link +android.content.Context#stopService stopService()} и {@link android.app.Service#stopSelf +stopSelf()} фактически не останавливают службу, пока не будет отменена привязка всех клиентов.

+ + +

Реализация обратных вызовов жизненного цикла

+ +

Подобно операции, служба содержит методы обратного вызова жизненного цикла, которые можно реализовать для контроля +изменений состояния службы и выполнения работы в соответствующие моменты времени. Указанная ниже базовая +служба показывает каждый из методов жизненного цикла.

+ +
+public class ExampleService extends Service {
+    int mStartMode;       // indicates how to behave if the service is killed
+    IBinder mBinder;      // interface for clients that bind
+    boolean mAllowRebind; // indicates whether onRebind should be used
+
+    @Override
+    public void {@link android.app.Service#onCreate onCreate}() {
+        // The service is being created
+    }
+    @Override
+    public int {@link android.app.Service#onStartCommand onStartCommand}(Intent intent, int flags, int startId) {
+        // The service is starting, due to a call to {@link android.content.Context#startService startService()}
+        return mStartMode;
+    }
+    @Override
+    public IBinder {@link android.app.Service#onBind onBind}(Intent intent) {
+        // A client is binding to the service with {@link android.content.Context#bindService bindService()}
+        return mBinder;
+    }
+    @Override
+    public boolean {@link android.app.Service#onUnbind onUnbind}(Intent intent) {
+        // All clients have unbound with {@link android.content.Context#unbindService unbindService()}
+        return mAllowRebind;
+    }
+    @Override
+    public void {@link android.app.Service#onRebind onRebind}(Intent intent) {
+        // A client is binding to the service with {@link android.content.Context#bindService bindService()},
+        // after onUnbind() has already been called
+    }
+    @Override
+    public void {@link android.app.Service#onDestroy onDestroy}() {
+        // The service is no longer used and is being destroyed
+    }
+}
+
+ +

Примечание. В отличие от методов обратного вызова жизненного цикла операции, вам +не требуется вызывать реализацию суперкласса этих методов обратного вызова.

+ + +

Рисунок 2. Жизненный цикл службы. На схеме слева +показан жизненный цикл, когда служба создана посредством метода {@link android.content.Context#startService +startService()}, а на схеме справа показан жизненный цикл, когда служба создана +посредством метода {@link android.content.Context#bindService bindService()}.

+ +

С помощью реализации этих методов можно отслеживать два вложенных цикла в жизненном цикле службы:

+ +
    +
  • Весь жизненный цикл службы происходит между вызовом метода {@link +android.app.Service#onCreate onCreate()} и возвратом из метода {@link +android.app.Service#onDestroy}. Подобно операции, служба выполняет начальную настройку в методе +{@link android.app.Service#onCreate onCreate()} и освобождает все оставшиеся ресурсы в методе {@link +android.app.Service#onDestroy onDestroy()}. Например, +служба воспроизведения музыки может создать поток для воспроизведения музыки в методе {@link +android.app.Service#onCreate onCreate()}, затем остановить поток в методе {@link +android.app.Service#onDestroy onDestroy()}. + +

    Методы {@link android.app.Service#onCreate onCreate()} и {@link android.app.Service#onDestroy +onDestroy()} вызываются для всех служб, независимо от метода создания: +{@link android.content.Context#startService startService()} или {@link +android.content.Context#bindService bindService()}.

  • + +
  • Активный жизненный цикл службы начинается с вызова метода {@link +android.app.Service#onStartCommand onStartCommand()} или {@link android.app.Service#onBind onBind()}. +Каждый метод направляется намерением {@link +android.content.Intent}, которое было передано методу {@link android.content.Context#startService +startService()} или {@link android.content.Context#bindService bindService()}, соответственно. +

    Если служба запущена, активный жизненный цикл заканчивается одновременно с окончанием +всего жизненного цикла (служба активна даже после возврата из метода {@link android.app.Service#onStartCommand +onStartCommand()}). Если служба является привязанной, активный жизненный цикл заканчивается, когда возвращается метод {@link +android.app.Service#onUnbind onUnbind()}.

    +
  • +
+ +

Примечание. Хотя запущенная служба останавливается посредством вызова +метода {@link android.app.Service#stopSelf stopSelf()} или {@link +android.content.Context#stopService stopService()}, для службы не существует соответствующего обратного вызова +(нет обратного вызова {@code onStop()}). Поэтому, если служба не привязана к клиенту, +система уничтожает ее при остановке службы — метод {@link +android.app.Service#onDestroy onDestroy()} является единственным получаемым методом обратного вызова.

+ +

Рисунок 2 иллюстрирует типичные методы обратного вызова для службы. Хотя на рисунке отделены +службы, созданные посредством метода {@link android.content.Context#startService startService()}, от служб, +созданных посредством метода {@link android.content.Context#bindService bindService()}, помните, +что любая служба, независимо от способа запуска, позволяет клиентам выполнять привязку к ней. +Поэтому служба, изначально созданная посредством метода {@link android.app.Service#onStartCommand +onStartCommand()} (клиентом, который вызвал {@link android.content.Context#startService startService()}), +может получать вызов метода {@link android.app.Service#onBind onBind()} (когда клиент вызывает +метод {@link android.content.Context#bindService bindService()}).

+ +

Дополнительные сведения о создании службы, которая обеспечивает привязку, см. в документе Привязанные службы, +который содержит дополнительную информацию о методе обратного вызова {@link android.app.Service#onRebind onRebind()} +в разделе Управление жизненным циклом +привязанной службы.

+ + + diff --git a/docs/html-intl/intl/ru/guide/components/tasks-and-back-stack.jd b/docs/html-intl/intl/ru/guide/components/tasks-and-back-stack.jd new file mode 100644 index 0000000000000000000000000000000000000000..c9fdc0e069bb013f7aff7318a77198f23ac662a6 --- /dev/null +++ b/docs/html-intl/intl/ru/guide/components/tasks-and-back-stack.jd @@ -0,0 +1,578 @@ +page.title=Задачи и стек переходов назад +parent.title=Операции +parent.link=activities.html +@jd:body + + + + +

Обычно приложение содержит несколько операций. Каждая операция +должна разрабатываться в связи с действием определенного типа, которое пользователь может выполнять, и может запускать другие +операции. Например, приложение электронной почты может содержать одну операцию для отображения списка новых сообщений. +Когда пользователь выбирает сообщение, открывается новая операция для просмотра этого сообщения.

+ +

Операция может даже запускать операции, существующие в других приложениях на устройстве. Например, +если ваше приложение хочет отправить сообщение электронной почты, вы можете определить намерение для выполнения +действия «отправить» и включить в него некоторые данные, например, адрес электронной почты и текст сообщения. После этого открывается операция из другого +приложения, которая объявила, что она обрабатывает намерения такого типа. В этом случае намерение состоит в том, чтобы +отправить сообщение электронной почты, поэтому в приложении электронной почты запускается операция «составить сообщение» (если одно намерение +может обрабатываться несколькими операциями, система предлагает пользователю выбрать, какую из операций использовать). После отправки сообщения электронной почты +ваша операция возобновляет работу, и все выглядит так, будто операция отправки электронной почты является частью вашего приложения. Хотя +операции могут быть частями разных приложений, система Android поддерживает удобство работы +пользователя, сохраняя обе операции в одной задаче.

+ +

Задача — это коллекция операций, с которыми взаимодействует пользователь +при выполнении определенного задания. Операции упорядочены в виде стека (стека переходов назад), в том +порядке, в котором открывались операции.

+ + + +

Начальным местом для большинства задач является главный экран устройства. Когда пользователь касается значка в средстве +запуска +приложений (или ярлыка на главном экране), эта задача приложения переходит на передний план. Если для +приложения нет задач (приложение не использовалось в последнее время), тогда создается новая задача +и открывается «основная» операция для этого приложения в качестве корневой операции в стеке.

+ +

Когда текущая операция запускает другую, новая операция помещается в вершину стека +и получает фокус. Предыдущая операция остается в стеке, но ее выполнение останавливается. Когда операция останавливается +, система сохраняет текущее состояние ее пользовательского интерфейса. Когда пользователь нажимает кнопку +Назад, +текущая операция удаляется из вершины стека (операция уничтожается) и возобновляется +работа предыдущей операции (восстанавливается предыдущее состояние ее пользовательского интерфейса). Операции в стеке +никогда не переупорядочиваются, только добавляются в стек и удаляются из него — добавляются в стек при запуске текущей операцией +и удаляются, когда пользователь выходит из нее с помощью кнопки Назад. По существу, +стек +переходов назад работает по принципу «последним вошел — первым вышел». На рисунке 1 это поведение +показано на временной шкале: состояние операций и текущее состояние стека переходов назад + показано в каждый момент времени.

+ + +

Рисунок 1. Иллюстрация того, как каждая новая операция в задаче +добавляет элемент в стек переходов назад. Когда пользователь нажимает кнопкуНазад, текущая +операция +уничтожается, и возобновляется работа предыдущей операции.

+ + +

Если пользователь продолжает нажимать кнопку Назад, операции поочередно удаляются из стека, +открывая +предыдущую операцию, пока пользователь не вернется на главный экран (или в операцию, которая была запущена +в начале выполнения задачи). Когда все операции удалены из стека, задача прекращает существование.

+ +
+

Рисунок 2. Две задачи: Задача B взаимодействует с пользователем +на переднем плане, тогда как Задача A находится в фоновом режиме, ожидая возобновления.

+
+
+

Рисунок 3. Создается несколько экземпляров одной операции.

+
+ +

Задача — это связанный блок, который может переходить в фоновый режим, когда пользователи начинают новую задачу или переходят +на главный экран с помощью кнопки Домой. В фоновом режиме все операции +задачи +остановлены, но стек обратного вызова для задачи остается неизменным. Задача просто потеряла фокус во время +выполнения другой задачи, как показано на рисунке 2. Затем задача может вернуться на передний план, так что пользователи +могут продолжить ее с прерванного места. Предположим, например, что текущая задача (Задача A) содержит три операции +в своем стеке — две операции под текущей операцией. Пользователь нажимает кнопку Домой, + затем запускает +новое приложение из средства запуска приложений. Когда появляется главный экран, Задача A переходит +в фоновый режим. Когда запускается новое приложение, система запускает задачу для этого приложения +(Задачу B) со своим собственным стеком операций. После взаимодействия с этим +приложением пользователь снова возвращается на главный экран и выбирает изначально запущенную +Задачу A. Теперь Задача A переходит на передний +план — все три операции ее стека остались неизменными, и возобновляется операция, находящаяся на +вершине стека. В этот +момент пользователь может также переключиться обратно на Задачу B, перейдя на главный экран и выбрав значок приложения, +которое запустило эту задачу (или выбрав задачу приложения на +экране обзора). +Это пример многозадачности в системе Android.

+ +

Примечание. В фоновом режиме может находиться несколько задач одновременно. +Однако, если пользователь запускает много фоновых задач одновременно, система может начать +уничтожение фоновых операций для освобождения памяти, что приведет к потере состояния задач. +См. следующий раздел Состояние операции.

+ +

Поскольку операции в стеке никогда не переупорядочиваются, если ваше приложение позволяет +пользователям запускать определенную операцию из нескольких операций, новый экземпляр +такой операции создается и помещается в стек (вместо помещения любого из предыдущих экземпляров +операции на вершину стека). По существу, для одной операции вашего приложения может быть создано несколько +экземпляров (даже из разных задач), как показано на рисунке 3. Поэтому, если пользователь переходит назад с помощью +кнопки Назад, каждый экземпляр операции появляется в том порядке, в котором они +были открыты (каждый +со своим состоянием пользовательского интерфейса). Однако вы можете изменить это поведение, если вы не хотите, чтобы создавалось несколько +экземпляров операции. Это описано в разделе Управление задачами ниже.

+ + +

Подведем итоги поведения операций и задач:

+ +
    +
  • Когда Операция A запускает Операцию B, Операция A останавливается, но система сохраняет ее состояние +(например, положение прокрутки и текст, введенный в формы). +Если пользователь нажимает кнопку Назад в Операции B, Операция A возобновляет работу +из сохраненного состояния.
  • +
  • Когда пользователь выходит из задачи нажатием кнопки Домой, текущая операция +останавливается и +ее задача переводится в фоновый режим. Система сохраняет состояние каждой операции в задаче. Если +пользователь впоследствии возобновляет задачу, выбирая значок запуска задачи, она переводится +на передний план и возобновляет операцию на вершине стека.
  • +
  • Если пользователь нажимает кнопку Назад, текущая операция удаляется из стека +и +уничтожается. Возобновляется предыдущая операция в стеке. Когда операция уничтожается, система +не сохраняет состояние операции.
  • +
  • Можно создавать несколько экземпляров операции, даже из других задач.
  • +
+ + +
+

Дизайн навигации

+

Для получения дополнительной информации о работе навигации в приложении Android, прочитайте раздел Навигация руководства «Дизайн для Android».

+
+ + +

Сохранение состояния операции

+ +

Как говорилось выше, система по умолчанию сохраняет состояние операции, когда она +останавливается. Таким образом, когда пользователи возвращаются обратно в предыдущую операцию, восстанавливается ее пользовательский интерфейс +в момент остановки. Однако вы можете — и должны — с упреждением сохранять +состояние ваших операций посредством методов обратного вызова на случай уничтожения операции и необходимости ее +повторного создания.

+ +

Когда система останавливает одну из ваших операций (например, когда запускается новая операция или задача +перемещается в фоновый режим), система может полностью уничтожить эту операцию, если необходимо восстановить +память системы. Когда это происходит, информация о состоянии операции теряется. Если это происходит, +система +знает, что операция находится в стеке переходов назад, но когда операция переходит +на вершину стека, система должна создать ее повторно (а не возобновить ее). Чтобы избежать +потери работы пользователя, вы должны с упреждением сохранять ее путем реализации методов +обратного вызова {@link android.app.Activity#onSaveInstanceState onSaveInstanceState()} +в вашей операции.

+ +

Дополнительную информацию о сохранении состояния вашей операции см. в документе +Операции.

+ + + +

Управление задачами

+ +

Для большинства приложений способ, которым Android управляет задачами и стеком переходов назад, описанный выше, — помещение всех +операций последовательно в одну задачу в стек «последним вошёл — первым вышел», — +работает хорошо, и вы не должны беспокоиться о связи ваших операций с задачами +или об их существовании в стеке переходов назад. Однако вы можете решить, что вы хотите прервать +обычное поведение. Возможно, вы хотите, чтобы операция в вашем приложении начинала новую задачу +при запуске (вместо помещения в текущую задачу), или при запуске операции вы хотите +перенести на передний план ее существующий экземпляр (вместо создания нового +экземпляра на вершине стека переходов назад), или вы хотите чтобы при выходе пользователя из задачи из вашего стек переходов удалялись все +операции, кроме корневой операции.

+ +

Вы можете совершать эти и многие другие действия с помощью атрибутов в элементе манифеста +{@code <activity>} +и с помощью флагов в намерении, которое вы передаете в +{@link android.app.Activity#startActivity startActivity()}.

+ +

В этом смысле главными атрибутами +{@code <activity>}, которые вы можете использовать, являются следующие:

+ + + +

А главными флагами намерений, которые вы можете использовать, являются следующие:

+ +
    +
  • {@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK}
  • +
  • {@link android.content.Intent#FLAG_ACTIVITY_CLEAR_TOP}
  • +
  • {@link android.content.Intent#FLAG_ACTIVITY_SINGLE_TOP}
  • +
+ +

В следующих разделах показано, как можно использовать эти атрибуты манифеста и флаги +намерений для определения связи операций с задачами и их поведения в стеке переходов назад.

+ +

Кроме того, отдельно обсуждаются рекомендации о представлении задач и операций и управлении ими +на экране обзора. Дополнительную информацию см. в разделе +Экран обзора. Обычно следует разрешить системе определить способ представления вашей задачи и +операций на экране обзора. Вам не нужно менять это поведение.

+ +

Внимание! В большинстве приложений не следует прерывать поведение +операций и задач по умолчанию. Если вы обнаружили, что вашей операции необходимо изменить +поведение по умолчанию, будьте внимательны и протестируйте удобство работы с операцией во время +запуска и при обратной навигации к ней из других операций и задач с помощью кнопки Назад. +Обязательно протестируйте поведение навигации, которое может противоречить поведению, ожидаемому пользователем.

+ + +

Определение режимов запуска

+ +

Режимы запуска позволяют вам определять связь нового экземпляра операции +с текущей задачей. Вы можете задавать различные режимы запуска двумя способами:

+
    +
  • Использование файла манифеста +

    Когда вы объявляете операцию в вашем файле манифеста, вы можете указать, как операция +должна связываться с задачами при ее запуске.

  • +
  • Использование флагов намерений +

    Когда вы вызываете {@link android.app.Activity#startActivity startActivity()}, +вы можете включить флаг в {@link android.content.Intent}, который объявляет, как должна быть связана +новая операция с текущей задачей (и должна ли).

  • +
+ +

По существу, если Операция A запускает Операцию B, Операция B может определить в своем манифесте, как она +должна быть связана с текущей задачей (если вообще должна), а Операция A может также запросить, как Операция B +должна быть связана с текущей задачей. Если обе операции определяют, как Операция B +должна быть связана с задачей, тогда запрос Операции A (как определено в намерении) обрабатывается через +запрос Операции B (как определено в ее манифесте).

+ +

Примечание. Некоторые режимы запуска, доступные для файла манифеста, +недоступны в виде флагов для намерения и, аналогичным образом, некоторые режимы запуска, доступные в виде флагов +для намерения, не могут быть определены в манифесте.

+ + +

Использование файла манифеста

+ +

При объявлении операции в вашем файле манифеста вы можете указать, как операция должна +быть связана с задачей посредством атрибута {@code +launchMode} +элемента {@code <activity>}.

+ +

Атрибут {@code +launchMode} указывает инструкцию по запуску операции в +задаче. Существует четыре различных режима запуска, +которые вы можете назначить атрибуту +launchMode:

+ +
+
{@code "standard"} (режим по умолчанию)
+
Режим по умолчанию. Система создает новый экземпляр операции в задаче, из +которой она была запущена, и направляет ему намерение. Может быть создано несколько экземпляров операции, +каждый экземпляр может принадлежать различным задачам, и одна задача может содержать несколько экземпляров.
+
{@code "singleTop"}
+
Если экземпляр операции уже существует на вершине текущей задачи, система +направляет намерение в этот экземпляр путем вызова его метода {@link +android.app.Activity#onNewIntent onNewIntent()}, а не путем создания нового экземпляра +операции. Может быть создано несколько экземпляров операции, каждый экземпляр может +принадлежать различным задачам, и одна задача может содержать несколько экземпляров (но только если +операция на вершине стека переходов назад не является существующим экземпляром операции). +

Предположим, что стек переходов назад задачи состоит из корневой операции A с операциями B, C +и D на вершине (стек имеет вид A-B-C-D и D находится на вершине). Поступает намерение для операции типа D. +Если D имеет режим запуска {@code "standard"} по умолчанию, запускается новый экземпляр класса и +стек принимает вид A-B-C-D-D. Однако, если D имеет режим запуска {@code "singleTop"}, существующий экземпляр +D получает намерение через {@link +android.app.Activity#onNewIntent onNewIntent()}, так как этот экземпляр находится на вершине стека — +стек сохраняет вид A-B-C-D. Однако, если поступает намерение для операции типа B, тогда в стек +добавляется новый экземпляр B, даже если он имеет режим запуска {@code "singleTop"}.

+

Примечание. Когда создается новый экземпляр операции, +пользователь может нажать кнопку Назад для возврата к предыдущей операции. Но когда существующий +экземпляр +операции обрабатывает новое намерение, пользователь не может нажать кнопку Назад для возврата к +состоянию +операции до поступления нового намерения в {@link android.app.Activity#onNewIntent +onNewIntent()}.

+
+ +
{@code "singleTask"}
+
Система создает новую задачу и создает экземпляр операции в корне новой задачи. +Однако, если экземпляр операции уже существует в отдельной задаче, система направляет +намерение в существующий экземпляр путем вызова его метода {@link +android.app.Activity#onNewIntent onNewIntent()}, а не путем создания нового экземпляра. Одновременно +может существовать только один экземпляр операции. +

Примечание. Хотя операция запускает новую задачу, кнопка +Назад возвращает пользователя к предыдущей операции.

+
{@code "singleInstance"}.
+
То же, что и {@code "singleTask"}, но при этом система не запускает никаких других операций +в задаче, содержащей этот экземпляр. Операция всегда является единственным членом своей задачи; +любые операции, запущенные этой операцией, открываются в отдельной задаче.
+
+ + +

В качестве другого примера: приложение Android Browser объявляет, что операция веб-браузера должна +всегда открываться в своей собственной задаче — путем указания режима запуска {@code singleTask} в элементе {@code <activity>}. +Это означает, что если ваше приложение выдает +намерение открыть Android Browser, его операция не помещается в ту же +задачу, что и ваше приложение. Вместо этого, либо для браузера запускается новая задача, либо, если браузер уже +имеет задачу, работающую в фоновом режиме, эта задача переводится на передний план для обработки нового +намерения.

+ +

И при запуске операции в новой задаче, и при запуске операции в существующей задаче, + кнопка Назад всегда возвращает пользователя к предыдущей операции. Однако, если вы +запускаете операцию, которая указывает режим запуска {@code singleTask}, вся задача переводится на передний план, если экземпляр +этой операции существует в фоновой задаче. В этот момент +стек переходов назад помещает все операции из задачи, переведенной на передний план, на вершину +стека. Рисунок 4 иллюстрирует сценарий этого типа.

+ + +

Рисунок 4. Представление того, как операция с режимом +запуска singleTask добавляется в стек переходов назад. Если операция уже является частью +фоновой задачи со своим собственным стеком переходов назад, то весь стек переходов назад также переносится вверх, +на вершину текущей задачи.

+ +

Дополнительную информацию об использовании режимов запуска в файле манифеста см. в документации элемента +<activity>, + где более подробно обсуждаются атрибут {@code launchMode} +и принимаемые значения.

+ +

Примечание. Поведение, которое вы указываете для вашей операции с помощью атрибута {@code launchMode}, +может быть переопределено флагами, включенными в намерение, которое запускает вашу операцию, как описано +в следующем разделе.

+ + + +

Использование флагов намерений

+ +

При запуске операции вы можете изменить связывание операции с ее задачей по умолчанию +путем включения флагов в намерение, которое доставляется в {@link +android.app.Activity#startActivity startActivity()}. Для изменения поведения по умолчанию +вы можете использовать следующие флаги:

+ +

+

{@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK}
+
Запуск операции в новой задаче. Если задача уже работает для операции, которую вы запускаете +сейчас, эта задача переводится на передний план, ее последнее состояние восстанавливается, и операция получает +новое намерение в {@link android.app.Activity#onNewIntent onNewIntent()}. +

Это приводит к тому же поведению, что и значение {@code launchMode} в режиме {@code "singleTask"}, +как описано в предыдущем разделе.

+
{@link android.content.Intent#FLAG_ACTIVITY_SINGLE_TOP}
+
Если запускаемая операция является текущей операцией (находится на вершине стека переходов назад), тогда +вызов в {@link android.app.Activity#onNewIntent onNewIntent()} получает существующий экземпляр, + без создания нового экземпляра операции. +

Это приводит к тому же поведению, что и значение {@code launchMode} в режиме {@code "singleTop"}, +как описано в предыдущем разделе.

+
{@link android.content.Intent#FLAG_ACTIVITY_CLEAR_TOP}
+
Если запускаемая операция уже работает в текущей задаче, тогда вместо +запуска нового экземпляра этой операции уничтожаются все другие операции, расположенные в стеке выше нее +, и это намерение доставляется в возобновленный экземпляр этой операции (которая теперь находится на вершине стека) +посредством {@link android.app.Activity#onNewIntent onNewIntent()}). +

Для формирования такого поведения не существует значения для атрибута +{@code launchMode}.

+

Флаг {@code FLAG_ACTIVITY_CLEAR_TOP} чаще всего используется совместно с +флагом {@code FLAG_ACTIVITY_NEW_TASK}. +При использовании вместе эти флаги позволяют найти существующую операцию +в другой задаче и поместить ее в положение, где она сможет реагировать на намерение.

+

Примечание. Если для назначенной операции установлен режим запуска +{@code "standard"}, +она также удаляется из стека и на ее месте запускается новый экземпляр, чтобы обработать +входящее намерение. Именно поэтому в режиме запуска {@code "standard"} всегда создается новый +экземпляр для нового намерения.

+
+ + + + + + +

Обработка привязок

+ +

Привязка указывает предпочтительную принадлежность операции к задаче. По умолчанию все +операции из одного приложения имеют привязку друг к другу. Поэтому по умолчанию все +операции одного приложения предпочитают находиться в одной задаче. Однако вы можете изменить +привязку по умолчанию для операции. Операции, определенные +в разных приложениях, могут совместно использовать одну привязку; таким же образом операции, определенные в одном приложении, могут получить +привязки к разным задачам.

+ +

Вы можете изменить привязку любой данный операции с помощью +атрибута {@code taskAffinity} +элемента {@code <activity>}.

+ +

Атрибут {@code taskAffinity} +принимает строковое значение, которое должно отличаться от имени пакета по умолчанию, +объявленного в элементе +{@code <manifest>} +, поскольку система использует это имя для идентификации привязки задачи по умолчанию +для приложения.

+ +

Привязка вступает в игру в двух случаях:

+
    +
  • Когда намерение, запускающее +операцию, содержит флаг +{@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK}. + +

    Новая операция по умолчанию запускается в задаче той операции, +которая вызвала {@link android.app.Activity#startActivity startActivity()}. Она помещается в тот же +стек переходов назад, что и вызывающая операция. Однако, если намерение, переданное +в {@link android.app.Activity#startActivity startActivity()}, +содержит флаг {@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK}, + система ищет другую задачу для помещения новой операции. Часто это новая задача. +Но необязательно. Если уже существует задача с той же привязкой, что +и у новой операции, операция запускается в этой задаче. Если нет, операция начинает новую задачу.

    + +

    Если этот флаг приводит к тому, что операция начинает новую задачу, и пользователь нажимает кнопку Домой +для выхода из нее, +должен существовать способ, позволяющий пользователю вернуться к задаче. Некоторые объекты (такие как +диспетчер уведомлений) всегда запускают операции во внешней задаче, а не в составе собственной, поэтому +они всегда помещают флаг {@code FLAG_ACTIVITY_NEW_TASK} в намерения, которые они передают +в {@link android.app.Activity#startActivity startActivity()}. +Если у вас есть операция, которую можно вызвать +внешним объектом, использующим этот флаг, позаботьтесь о том, чтобы у пользователя был независимый способ +вернуться в запущенную задачу, например, с помощью значка запуска (корневая операция задачи +содержит фильтр намерений {@link android.content.Intent#CATEGORY_LAUNCHER}; см. раздел Запуск задачи ниже).

    +
  • + +
  • Если для атрибута +{@code allowTaskReparenting} операции установлено значение {@code "true"}. +

    В этом случае операция может переместиться из запустившей ее задачи в задачу, к которой у операции есть привязка, +когда эта задача переходит на передний план.

    +

    Предположим, что операция, которая сообщает о погодных условиях в выбранных городах, +определена в составе приложения для путешественников. Она имеет ту же привязку, что и другие операции в том же +приложении (привязка приложения по умолчанию), и допускает переподчинение с этим атрибутом. +Когда одна из ваших операций запускает операцию прогноза погоды, она изначально принадлежит той же +задаче, что и ваша операция. Однако, когда задача из приложения для путешественников переходит на передний план, +операция прогноза погоды переназначается этой задаче и отображается внутри нее.

    +
  • +
+ +

Совет. Если файл {@code .apk} содержит более одного «приложения» +с точки зрения пользователя, вы, вероятно, захотите использовать атрибут {@code taskAffinity} +для назначения разных привязок операциям, связанным с каждым «приложением».

+ + + +

Очистка стека переходов назад

+ +

Если пользователь выходит из задачи на длительное время, система удаляет из задачи все операции, кроме +корневой операции. Когда пользователь возвращается в задачу, восстанавливается только корневая операция. +Система ведет себя таким образом, так как после продолжительного времени пользователи обычно уже забросили то, +чем они занимались ранее, и возвращаются в задачу, чтобы начать что-то новое.

+ +

Для изменения такого поведения предусмотрено несколько атрибутов операции, которыми вы можете воспользоваться:

+ +
+
alwaysRetainTaskState +
+
Если для этого атрибута установлено значение {@code "true"} в корневой операции задачи, +описанное выше поведение по умолчанию не происходит. +Задача восстанавливает все операции в своем стеке даже по истечении длительного периода времени.
+ +
clearTaskOnLaunch
+
Если для этого атрибута установлено значение {@code "true"} в корневой операции задачи, +стек очищается до корневой операции каждый раз, когда пользователь выходит из задачи +и возвращается в нее. Другими словами, этот атрибут противоположен атрибуту + +{@code alwaysRetainTaskState}. Пользователь всегда возвращается в задачу в ее +исходном состоянии, даже после кратковременного выхода из нее.
+ +
finishOnTaskLaunch +
+
Этот атрибут похож на {@code clearTaskOnLaunch}, +но он действует на +одну операцию, а не на всю задачу. Он также может приводить к удалению любой операции, +включая корневую операцию. Когда для него установлено значение {@code "true"}, +операция остается частью задачи только для текущего сеанса. Если пользователь +выходит из задачи, а затем возвращается в нее, операция уже отсутствует.
+
+ + + + +

Запуск задачи

+ +

Вы можете сделать операцию точкой входа, назначая ей фильтр намерений со значением +{@code "android.intent.action.MAIN"} в качестве указанного действия и +{@code "android.intent.category.LAUNCHER"} +в качестве указанной категории. Например:

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

Фильтр намерений такого типа приводит к тому, что в средстве запуска приложения отображаются значок и метка для +операции, что позволяет пользователю запускать операцию +и возвращаться в создавшую ее задачу в любой момент после ее запуска. +

+ +

Эта вторая возможность очень важна: пользователи должны иметь возможность выходить из задачи и затем возвращаться в нее +с помощью этого средства запуска операции. Поэтому два режима +запуска, которые отмечают, что операции всегда инициируют задачу, {@code "singleTask"} и +{@code "singleInstance"}, должны использоваться только в тех случаях, когда операция содержит +{@link android.content.Intent#ACTION_MAIN} +и фильтр {@link android.content.Intent#CATEGORY_LAUNCHER}. Представьте, например, что может произойти, +если фильтр отсутствует: намерение запускает операцию {@code "singleTask"}, которая инициирует +новую задачу, и пользователь некоторое время работает в этой задаче. Затем пользователь нажимает кнопку +Домой. Задача переводится в фоновый режим и не отображается. Теперь у пользователя нет возможности вернуться +к задаче, так как она отсутствует в средстве запуска приложения.

+ +

Для таких случаев, когда вы не хотите, чтобы пользователь мог вернуться к операции, установите для атрибута +{@code finishOnTaskLaunch} +элемента +<activity> +значение {@code "true"} (см. раздел Очистка стека).

+ +

Дополнительную информацию о представлении задач и операций и управлении ими +на экране обзора см. в разделе +Экран обзора.

+ + diff --git a/docs/html-intl/intl/ru/guide/index.jd b/docs/html-intl/intl/ru/guide/index.jd new file mode 100644 index 0000000000000000000000000000000000000000..b0732722e1b853d4bba65e162f2ddbc659a3875c --- /dev/null +++ b/docs/html-intl/intl/ru/guide/index.jd @@ -0,0 +1,74 @@ +page.title=Общие сведения о платформе Android + +@jd:body + + + + +

Система Android предоставляет разностороннюю платформу приложений, на основе которой можно создавать инновационные приложения и игры +для мобильных устройств в среде языка Java. В документах, ссылки на которые приведены на панели навигации слева, +рассказывается о том, как создавать приложения с помощью различных API-интерфейсов Android.

+ +

Если создание программ для Android является для вас новым делом, вам важно усвоить +следующие основные концепции, касающиеся платформы приложений Android:

+ + +
+ +
+ +

Приложения имеют несколько точек входа

+ +

Приложения для Android строятся из отдельных компонентов, которые можно вызывать +независимо друг от друга. Например, отдельная операция предоставляет один +экран для пользовательского интерфейса, а служба независимо выполняет +работу в фоновом режиме.

+ +

С помощью объекта Intent из одного компонента можно запустить другой компонент. Можно даже запустить +компонент из другого приложения, скажем, операцию из картографического приложения, чтобы показать адрес. Эта модель +формирует несколько точек входа для одного приложения, и при этом пользователь может выбрать любое приложение для выполнения по умолчанию +того или иного действия, которое могут вызывать другие приложения.

+ + +

Подробнее:

+ + +
+ + +
+ +

Приложения адаптируются к различным устройствам

+ +

Android предоставляет адаптивную платформу приложений, которая позволяет обеспечивать уникальные ресурсы +для различных конфигураций устройств. Например, можно создать разные файлы XML +макета для экранов разных размеров, а система будет +определять, какой макет использовать, с учетом размера экрана данного устройства.

+ +

Если каким-либо функциям приложения требуется определенное оборудование, например камера, можно +запрашивать его наличие в устройстве во время выполнения. При необходимости также можно объявлять функции, которые требуются приложению, +с тем чтобы такие магазины приложений, как Google Play, не позволяли устанавливать приложения на устройствах, в которых +этой функции нет.

+ + +

Подробнее:

+ + +
+ +
+ + + diff --git a/docs/html-intl/intl/ru/guide/topics/manifest/manifest-intro.jd b/docs/html-intl/intl/ru/guide/topics/manifest/manifest-intro.jd new file mode 100644 index 0000000000000000000000000000000000000000..f2c5a9e0359096bfcc080c52840922ea5555f2e8 --- /dev/null +++ b/docs/html-intl/intl/ru/guide/topics/manifest/manifest-intro.jd @@ -0,0 +1,517 @@ +page.title=Манифест приложения +@jd:body + + + +

+ В корневой папке каждого приложения должен находиться файл AndroidManifest.xml (который именно так и называется +). Файл манифеста + содержит важную информацию о приложении, которая требуется системе Android. + Только получив эту информацию, система может выполнить какой-либо код + приложения. Среди прочего файл манифеста выполняет следующие действия: +

+ +
    +
  • Он задает имя пакета Java для приложения. +Это имя пакета служит уникальным идентификатором приложения.
  • + +
  • Он описывает компоненты приложения — операции, +службы, приемники широковещательных сообщений и поставщиков контента, из которых состоит +приложение. Он содержит имена классов, которые реализуют каждый компонент, и +публикует их возможности (указывает, например, какие сообщения {@link android.content.Intent +Intent} они могут принимать). На основании этих деклараций система Android +может определить, из каких компонентов состоит приложение и при каких условиях их можно запускать.
  • + +
  • Он определяет, в каких процессах будут размещаться компоненты приложения.
  • + +
  • Он объявляет, какие разрешения должны быть выданы приложению, чтобы оно могло получить +доступ к защищенным частям API-интерфейса и взаимодействовать с другими приложениями.
  • + +
  • Он также объявляет разрешения, требуемые для +взаимодействия с компонентами данного приложения.
  • + +
  • Он содержит список классов {@link android.app.Instrumentation}, которые при выполнении приложения предоставляют +сведения о профиле и прочую информацию. Эти объявления +присутствуют в файле манифеста только во время разработки и отладки +приложения и удаляются перед его публикацией.
  • + +
  • Он объявляет минимальный уровень API-интерфейса Android, который требуется +приложению.
  • + +
  • Он содержит список библиотек, с которыми должно быть связано приложение.
  • +
+ + +

Структура файла манифеста

+ +

+Приведенная далее схема позволяет ознакомиться с общей структурой файла манифеста и +всеми элементами, которые могут в нем содержаться. Каждый элемент вместе со всеми своими +атрибутами, полностью описывается в отдельном файле. Для просмотра подробных +сведений о любом элементе, щелкните имя элемента на схеме, +в алфавитном списке элементов, приведенном после схемы, или +в любом другом месте, где этот элемент упоминается. +

+ +
+<?xml version="1.0" encoding="utf-8"?>
+
+<manifest>
+
+    <uses-permission />
+    <permission />
+    <permission-tree />
+    <permission-group />
+    <instrumentation />
+    <uses-sdk />
+    <uses-configuration />  
+    <uses-feature />  
+    <supports-screens />  
+    <compatible-screens />  
+    <supports-gl-texture />  
+
+    <application>
+
+        <activity>
+            <intent-filter>
+                <action />
+                <category />
+                <data />
+            </intent-filter>
+            <meta-data />
+        </activity>
+
+        <activity-alias>
+            <intent-filter> . . . </intent-filter>
+            <meta-data />
+        </activity-alias>
+
+        <service>
+            <intent-filter> . . . </intent-filter>
+            <meta-data/>
+        </service>
+
+        <receiver>
+            <intent-filter> . . . </intent-filter>
+            <meta-data />
+        </receiver>
+
+        <provider>
+            <grant-uri-permission />
+            <meta-data />
+            <path-permission />
+        </provider>
+
+        <uses-library />
+
+    </application>
+
+</manifest>
+
+ +

+Далее приведен список всех элементов, расположенных в алфавитном порядке, которые могут +присутствовать в файле манифеста. Там могут находиться только эти элементы, а никакие другие +элементы или атрибуты добавлять нельзя. +

+ +

+<action> +
<activity> +
<activity-alias> +
<application> +
<category> +
<data> +
<grant-uri-permission> +
<instrumentation> +
<intent-filter> +
<manifest> +
<meta-data> +
<permission> +
<permission-group> +
<permission-tree> +
<provider> +
<receiver> +
<service> +
<supports-screens> +
<uses-configuration> +
<uses-feature> +
<uses-library> +
<uses-permission> +
<uses-sdk> +

+ + + + +

Соглашения о компонентах файла

+ +

+Ко всем элементам и атрибутам +из файла манифеста применяется рад соглашений и правил: +

+ +
+
Элементы
+
Обязательными +являются только элементы<manifest> и +<application> +. Оба они должны присутствовать в файле манифеста, при этом указать их можно только один раз. +Большинство других элементов можно указывать по нескольку раз или не указывать вовсе — хотя по +крайней мере некоторые из них нужны, чтобы файл манифеста был сколько-нибудь +информативным. + +

+Если в элементе и есть какое-то содержимое, то это другие элементы. +Все значения задаются с помощью атрибутов, а не как символьные данные в элементе. +

+ +

+Элементы, находящиеся на одном уровне, обычно не упорядочиваются. Например, + элементы <activity>, +<provider> и +<service> +можно указать в любой последовательности. (Элемент +<activity-alias> +является исключением из этого правила. Он должен следовать за элементом +<activity>, +псевдонимом которого он является.) +

+ +
Атрибуты
+
Формально все атрибуты являются необязательными. Однако некоторые их них +указывать необходимо, чтобы файл мог выполнять свое предназначение. В качестве руководства используйте эту +документацию. В отношении атрибутов, которые являются и вправду необязательными, в ней указывается значение, +используемое по умолчанию, или говорится, что произойдет, если такой атрибут не будет указан. + +

За исключением некоторых атрибутов корневого элемента +<manifest> +, имена всех атрибутов должны начинаться с префикса {@code android:} — +например, {@code android:alwaysRetainTaskState}. Поскольку этот префикс является +универсальным, в документации при указании атрибутов по имени +он обычно опускается.

+ +
Объявление имен классов
+
Многие элементы соответствуют объектам Java, в том числе элементы для самого +приложения (элемент +<application> +) и основных его компонентов — операций +(<activity>), +служб +(<service>), +приемников широковещательных сообщений +(<receiver>) +и поставщиков контента +(<provider>). + +

+Если вы определяете подкласс, а это практически всегда делается для классов компонентов +({@link android.app.Activity}, {@link android.app.Service}, +{@link android.content.BroadcastReceiver} и {@link android.content.ContentProvider}), +выполняется это с помощью атрибута {@code name}. В состав имени должно входить +полное обозначение пакета. +Например, подкласс {@link android.app.Service} можно объявить следующим образом: +

+ +
<manifest . . . >
+    <application . . . >
+        <service android:name="com.example.project.SecretService" . . . >
+            . . .
+        </service>
+        . . .
+    </application>
+</manifest>
+ +

+Однако его можно укоротить. Если первым символом в строке указать точку, эта +строка будет добавляться к имени пакета приложения (указанного атрибутом +package +элемента +package +). Следующее назначение является таким же, как приведенное выше: +

+ +
<manifest package="com.example.project" . . . >
+    <application . . . >
+        <service android:name=".SecretService" . . . >
+            . . .
+        </service>
+        . . .
+    </application>
+</manifest>
+ +

+При запуске компонента Android создает экземпляр подкласса, указанного по имени. +Если подкласс не указан, система создает экземпляр базового класса. +

+ +
Несколько значений
+
Если можно указать несколько значений, элемент почти всегда +приводится повторно. Делается это вместо перечисления нескольких значений в одном элементе. +Например, в фильтре Intent может быть перечислено несколько действий: + +
<intent-filter . . . >
+    <action android:name="android.intent.action.EDIT" />
+    <action android:name="android.intent.action.INSERT" />
+    <action android:name="android.intent.action.DELETE" />
+    . . .
+</intent-filter>
+ +
Значения ресурсов
+
Значения некоторых атрибутов могут отображаться на экране — например, +метка и значок операции. Значения этих атрибутов +следует локализовать, поэтому они должны задаваться в ресурсе или теме. Значения +ресурсов выражаются в следующем формате:

+ +

{@code @[пакет:]тип:имя}

+ +

+где имя пакета можно опустить, если ресурс находится в одном пакете +с приложением, тип — это тип ресурса, — например "string" или +"drawable", — а имя — это имя, определяющее ресурс. +Например: +

+ +
<activity android:icon="@drawable/smallPic" . . . >
+ +

+Значения из темы выражаются схожим образом, только в начале у них идет "{@code ?}", +а не "{@code @}": +

+ +

{@code ?[пакет:]тип:имя} +

+ +
Строковые значения
+
Когда значением атрибута является строка, следует использовать двойную обратную косую черту ("{@code \\}") +для выделения управляющей последовательности символов, — например "{@code \\n}" для +новой строки или "{@code \\uxxxx}" для символа Юникода.
+
+ + +

Отображение функций в файле

+ +

+В следующих разделах описано, как некоторые функции Android отображаются +в файле манифеста. +

+ + +

Фильтры объектов Intent

+ +

+Базовые компоненты приложения (его операции, службы и +приемники широковещательных сообщений) активируются объектами Intent. Intent — +это совокупность информации (объект {@link android.content.Intent}), описывающей +требуемое действие, — в том числе в ней указаны данные, с которыми следует выполнить это действие, категория +компонентов, которые должны выполнять это действие, и другие уместные инструкции. +Система Android находит компонент, который отреагирует на объект Intent, запускает +новый экземпляр компонента, если он требуется, и передает ему +объект Intent. +

+ +

+Компоненты объявляют свои возможности — виды объектов Intent, на которые они могут +реагировать, — с помощью фильтров Intent. Поскольку система Android +должна узнать, какие объекты Intent может обрабатывать тот или иной компонент, до того как она его запустит, +фильтры Intent указываются в файле манифеста как +элементы <intent-filter> +. Компонент может иметь любое количество фильтров, каждый из которых описывает +отдельную возможность компонента. +

+ +

+Объект Intent, в котором целевой компонент явно указан по имени, активирует этот компонент, +и фильтр при этом не учитывается. Но объект Intent, в котором имя целевого +компонента не указано, может активировать компонент, только если он может пройти через один из фильтров +компонента. +

+ +

+Сведения о том, каким образом объекты Intent проверяются по фильтрам Intent, +см. в отдельном документе +Объекты Intent +и фильтры объектов Intent. +

+ + +

Значки и метки

+ +

+У ряда элементов есть атрибуты {@code icon} и {@code label} для +небольшого значка и текстовой метки, которые могут отображаться на экране. У некоторых из них также есть атрибут +{@code description} для более длинного описательного текста, который также может +отображаться на экране. Например, элемент +<permission> +имеет все три таких атрибута, поэтому, когда пользователю задается вопрос, предоставить ли +разрешение запросившему его приложению, на экране может отображаться значок, +представляющий разрешение, имя разрешения и описание того, что оно +за собой влечет. +

+ +

+В любом случае значок и метка, заданные в элементе-контейнере, становятся параметрами +{@code icon} и {@code label}, используемыми по умолчанию для всех вложенных в этот контейнер дочерних элементов. +Так, значок и метка, заданные в элементе +<application>, +являются значком и меткой, используемыми по умолчанию для каждого компонента приложения. +Точно так же, значок и метка, заданные для компонента, — например элемента +<activity>, — +являются параметрами, используемыми по умолчанию для каждого элемента +<intent-filter> + компонента. Если в элементе +<application> +задана метка, а в операции и ее фильтре Intent — нет, +метка приложения будет считаться меткой и для операции, и для +фильтра Intent. +

+ +

+Значок и метка, заданные для фильтра Intent, используются для обозначения компонента, +когда он представляется пользователю, для указания функции, +которую анонсирует фильтр. Например, фильтр с параметрами +"{@code android.intent.action.MAIN}" и +"{@code android.intent.category.LAUNCHER}" сообщает, что эта операция +инициирует приложение, — то есть он обозначает ее как + операцию, которая должна быть отображена в средстве запуска приложений. Отсюда следует, что значок и метка, +заданные в фильтре, отображаются в средстве запуска. +

+ + +

Разрешения

+ +

+Разрешение представляет собой ограничение на доступ к части кода +или к данным, имеющимся на устройстве. Это ограничение накладывается для защиты важных +данных и кода, ненадлежащее использование которых может пагубно сказаться на работе приложения. +

+ +

+Каждое разрешение обозначается уникальной меткой. Зачастую метка обозначает +действие, выполнение которого ограничивается. Например, вот некоторые разрешения, определенные +системой Android: +

+ +

{@code android.permission.CALL_EMERGENCY_NUMBERS} +
{@code android.permission.READ_OWNER_DATA} +
{@code android.permission.SET_WALLPAPER} +
{@code android.permission.DEVICE_POWER}

+ +

+Функцию можно защитить не более чем одним разрешением. +

+ +

+Если приложению требуется доступ к функции, защищенной разрешением, +оно должно объявить, что ему необходимо это разрешение, с помощью элемента +<uses-permission> +в файле манифеста. Затем, когда приложение устанавливается на +устройство, установщик определяет, выдать ли запрошенное +разрешение, проверяя полномочия органов, подписавших сертификаты +приложения, а также, в некоторых случаях, спрашивая об этом пользователя. +Если разрешение предоставляется, приложение сможет использовать защищенные +функции. В противном случае его попытки доступа к этим функциям будут безуспешными, +причем пользователь не получит никакого уведомления об этом. +

+ +

+Приложение также может защищать с помощью разрешений собственные компоненты (операции, службы, +приемники широковещательных сообщений и поставщиков контента). Оно может использовать +любые разрешения, определенные системой Android (они приведены в объекте +{@link android.Manifest.permission android.Manifest.permission}) или объявленные +другими приложениями. Либо оно может определить разрешения самостоятельно. Новое разрешение объявляется +с помощью элемента +<permission> +. Например, операцию можно защитить следующим образом: +

+ +
+<manifest . . . >
+    <permission android:name="com.example.project.DEBIT_ACCT" . . . />
+    <uses-permission android:name="com.example.project.DEBIT_ACCT" />
+    . . .
+    <application . . .>
+        <activity android:name="com.example.project.FreneticActivity"
+                  android:permission="com.example.project.DEBIT_ACCT"
+                  . . . >
+            . . .
+        </activity>
+    </application>
+</manifest>
+
+ +

+Обратите внимание, что в этом примере разрешение {@code DEBIT_ACCT} не только +объявляется с помощью элемента +<permission> +, его использование также запрашивается с помощью элемента +<uses-permission> +. Чтобы другие компоненты приложения запускали защищенную +операцию, ее использование должно быть запрошено, даже несмотря на то, что защита +наложена самим приложением. +

+ +

+В этом же примере: если атрибут {@code permission} был бы задан как +разрешение, объявленное где-то еще +(например, {@code android.permission.CALL_EMERGENCY_NUMBERS}), его бы не +нужно было объявлять еще раз с помощью элемента +<permission> +. Однако все равно нужно было бы запрашивать его использование с помощью +<uses-permission>. +

+ +

+Элемент +<permission-tree> +объявляет пространство имен для группы разрешений, которые будут определены в +коде. А элемент +<permission-group> +определяет метку для набора разрешений (как для разрешений, объявленных в файле манифеста с помощью элементов +<permission> +, так и для объявленных где-то еще). Это влияет только на то, каким образом разрешения +группируются, когда отображаются пользователю. Элемент +<permission-group> +не указывает, какие разрешения относятся к группе. +Он просто дает группе имя. Чтобы включить разрешение в группу, +атрибуту +permissionGroup + его элемента +<permission> +необходимо присвоить имя группы. +

+ + +

Библиотеки

+ +

+Каждое приложение связывается с используемой по умолчанию библиотекой Android, в которой +имеются базовые пакеты для построения приложений (со стандартными классами, +например Activity, Service, Intent, View, Button, Application, ContentProvider +и так далее). +

+ +

+Однако некоторые пакеты находятся в собственных библиотеках. Если ваше приложение +использует код из одного из таких пакетов, оно должно в явном виде потребовать, чтобы его связали +с этим пакетом. Файл манифеста должен содержать отдельный элемент +<uses-library> +для указания имени каждой библиотеки. (Имя библиотеки можно найти в +документации по пакету.) +

diff --git a/docs/html-intl/intl/ru/guide/topics/providers/calendar-provider.jd b/docs/html-intl/intl/ru/guide/topics/providers/calendar-provider.jd new file mode 100644 index 0000000000000000000000000000000000000000..2d12e1261d9332adacd75ed6e1a343c7a56c490a --- /dev/null +++ b/docs/html-intl/intl/ru/guide/topics/providers/calendar-provider.jd @@ -0,0 +1,1184 @@ +page.title=Поставщик календаря +@jd:body + + + +

Поставщик календаря представляет собой репозиторий для событий календаря пользователя. API +поставщика календаря позволяет запрашивать, вставлять, обновлять и удалять календари, +события, участников, напоминания и т. д.

+ + +

API поставщика календаря может использоваться как приложениями, так и адаптерами синхронизации. Правила +зависят от типа программы, которая выполняет вызовы. В этой статье +главным образом рассматривается использование API поставщика календаря в качестве приложения. Сведения о различиях +между адаптерами синхронизации представлены в разделе +Адаптеры синхронизации.

+ + +

Обычно, чтобы считать или записать данные календаря, в манифесте приложения +должны быть включены надлежащие разрешения, которые описываются в разделе Разрешения +пользователей. Чтобы упростить выполнение часто используемых операций, в поставщике +календаря предусмотрен набор намерений, как описано в разделе Намерения +календаря. Эти намерения позволяют пользователям переходить в приложение календаря для вставки, просмотра +и редактирования событий. После взаимодействия пользователя с календарем он возвращается +в исходное приложение. Поэтому вашему приложению не нужно запрашивать разрешения, +а также предоставлять пользовательский интерфейс для просмотра или создания событий.

+ +

Основы

+ +

Поставщики контента хранят в себе данные и предоставляют к ним доступ +для приложений. Поставщики контента, предлагаемые платформой Android (включая поставщик календаря) обычно представляют данные в виде набора таблиц, в основе +которых лежит модель реляционной базы данных. Каждая строка в такой таблице представляет собой запись, а каждый столбец — данные +определенного типа и значения. Благодаря API поставщика календаря приложения +и адаптеры синхронизации получают доступ на чтение/запись к таблицам в базе данных, в которых +представлены данные календаря пользователя.

+ +

Каждый поставщик календаря предоставляет общедоступный URI (упакованный в объект +{@link android.net.Uri}), +который служит уникальным идентификатором своего набора данных. Поставщик контента, который управляет +несколькими наборами данных (несколькими таблицами), предоставляет отдельный URI для каждого набора. Все +URI поставщиков начинаются со строки content://. Она +определяет данные, которые находятся под управлением поставщика контента. Поставщик календаря +задает константы для URI каждого из своих классов (таблиц). Такие +URI имеют формат <class>.CONTENT_URI. Например, +{@link android.provider.CalendarContract.Events#CONTENT_URI Events.CONTENT_URI}.

+ +

На рисунке 1 изображено графическое представление модели данных поставщика календаря. На нем представлены +основные таблицы и поля, которые связывают их друг с другом.

+ +Calendar Provider Data Model +

Рисунок 1. Модель данных поставщика календаря.

+ +

У пользователя может быть несколько календарей, причем они могут быть связаны с аккаунтами разных типов (Google Календарь, Exchange и т. д.).

+ +

Класс {@link android.provider.CalendarContract} определяет модель данных календаря и информацию, относящуюся к событиям. Эти данные хранятся в различных таблицах, указанных ниже.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Таблица (класс)Описание

{@link android.provider.CalendarContract.Calendars}

В этой таблице находится +информация о календарях. В каждой строке этой таблицы представлены сведения +об отдельном календаре, например, его название, цвет, информация о синхронизации и т. д.
{@link android.provider.CalendarContract.Events}В этой таблице находится +информация о событиях. В каждой строке этой таблицы содержится информация об отдельном +событии —например, заголовок события, место проведения, время начала, время +завершения и т. д. Событие может быть однократным или повторяющимся. Сведения об участниках, +напоминаниях и расширенные свойства хранятся в отдельных таблицах. +В каждой из них имеется целочисленная переменная {@link android.provider.CalendarContract.AttendeesColumns#EVENT_ID}, +которая ссылается на объект {@link android.provider.BaseColumns#_ID} в таблице событий.
{@link android.provider.CalendarContract.Instances}В этой таблице содержатся данные о времени +начала и окончания каждого повторения события. В каждой строке этой таблицы +представлено одно повторение события. Однократные события сопоставляются с повторениями +один к одному. Для повторяющихся событий автоматически создаются несколько строк, +которые соответствуют нескольким повторениям события.
{@link android.provider.CalendarContract.Attendees}В этой таблице находится +информация об участниках (гостях). В каждой строке этой таблицы указан один +гость. В ней указываются тип гостя и информация о том, +посетит ли он событие.
{@link android.provider.CalendarContract.Reminders}В этой таблице находятся +данные уведомлений или оповещений. В каждой строке этой таблицы указано одно уведомление или оповещение. Для одного +события можно создать несколько напоминаний. Максимальное количество таких напоминаний для события +задается с помощью +целочисленной переменной {@link android.provider.CalendarContract.CalendarColumns#MAX_REMINDERS}, +значение которой задает адаптер синхронизации, владеющий +указанным календарем. Напоминания задаются в минутах до начала события и +имеют метод, который определяет порядок уведомления пользователя.
+ +

API поставщика календаря обеспечивает достаточную гибкость и эффективность. В то же время +важно предоставить интерфейс, который будет удобным для пользователя, +и обеспечить защиту целостности календаря и его данных. Поэтому существует +ряд моментов, которые следует учитывать при использовании этого API.

+ +
    + +
  • Вставка, обновление и просмотр событий календаря. Чтобы вставить, изменить и считать события напрямую из поставщика календаря, требуются соответствующие разрешения. Однако, если вы не планируете создавать полнофункциональное приложение календаря или адаптер синхронизации, запрашивать такие разрешения не обязательно. Вместо этого можно использовать намерения, поддерживаемые приложением «Календарь» Android, для обработки операций чтения и записи в этом приложении. При использовании намерений ваше приложение отправляет пользователям приложение «Календарь» для выполнения требуемой операции +в предварительно заполненной форме. По завершении они возвращаются в приложение. +Реализовав в вашем приложении возможность выполнения часто используемых операций через приложение «Календарь», +вы обеспечиваете для пользователей единообразный и функциональный пользовательский интерфейс. Мы рекомендуем использовать +именно такой подход. Дополнительные сведения представлены в разделе Намерения +календаря.

    + + +
  • Адаптеры синхронизации. Адаптер синхронизации синхронизирует данные календаря +на устройстве пользователя с данными на сервере или в другом источнике данных. В таблицах +{@link android.provider.CalendarContract.Calendars} и +{@link android.provider.CalendarContract.Events} имеются +столбцы, зарезервированные для адаптеров синхронизации. +Ни поставщик, ни приложения не должны изменять их. Фактически они скрыты +до тех пор, пока адаптер синхронизации не начнет использовать их. Дополнительные сведения об +адаптерах синхронизации представлены в разделе Адаптеры синхронизации.
  • + +
+ + +

Разрешения пользователей

+ +

Чтобы считать данные календаря, в файл манифеста приложения необходимо включить разрешение {@link +android.Manifest.permission#READ_CALENDAR}. Также в него +следует включить разрешение {@link android.Manifest.permission#WRITE_CALENDAR} +для удаления, вставки или обновления данных календаря:

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"...>
+    <uses-sdk android:minSdkVersion="14" />
+    <uses-permission android:name="android.permission.READ_CALENDAR" />
+    <uses-permission android:name="android.permission.WRITE_CALENDAR" />
+    ...
+</manifest>
+
+ + +

Таблица календарей

+ +

В таблице {@link android.provider.CalendarContract.Calendars} содержатся подробные сведения +о каждом отдельном календаре. Выполнять запись в указанные ниже столбцы +этой таблицы могут и приложение, и адаптер синхронизации. +Полный список поддерживаемых полей представлен в справке по классу +{@link android.provider.CalendarContract.Calendars}.

+ + + + + + + + + + + + + + + + + + + + + + + + + +
КонстантаОписание
{@link android.provider.CalendarContract.Calendars#NAME}Название календаря.
{@link android.provider.CalendarContract.Calendars#CALENDAR_DISPLAY_NAME}Название этого календаря, которое отображается для пользователя.
{@link android.provider.CalendarContract.Calendars#VISIBLE}Логическое значение, обозначающее, выбран ли календарь для отображения. Значение +«0» указывает на то, что события, связанные с +этим календарем, не отображаются. Значение «1» указывает на то, что события, связанные с +этим календарем, отображаются. Это значение влияет на создание строк в таблице {@link +android.provider.CalendarContract.Instances}.
{@link android.provider.CalendarContract.CalendarColumns#SYNC_EVENTS}Логическое значение, обозначающее, следует ли синхронизировать календарь и хранить имеющиеся в нем события +на устройстве. Значение «0» указывает, что не следует синхронизировать этот календарь или +хранить имеющиеся в нем события на устройстве. Значение «1» указывает, что этот календарь следует синхронизировать и +хранить имеющиеся в нем события на устройстве.
+ +

Запрос календаря

+ +

Ниже представлен пример того, как получить календари, которыми +владеет определенный пользователь. Для простоты демонстрации операция запроса в этом примере находится в +потоке пользовательского интерфейса («основной поток»). На практике это следует делать в асинхронном +потоке, а не в основном. Дополнительные сведения представлены в статье +Загрузчики. Если же вы не только +считываете данные, но и вносите в них изменения, обратитесь к справке по классу {@link android.content.AsyncQueryHandler}. +

+ + +
+// Projection array. Creating indices for this array instead of doing
+// dynamic lookups improves performance.
+public static final String[] EVENT_PROJECTION = new String[] {
+    Calendars._ID,                           // 0
+    Calendars.ACCOUNT_NAME,                  // 1
+    Calendars.CALENDAR_DISPLAY_NAME,         // 2
+    Calendars.OWNER_ACCOUNT                  // 3
+};
+  
+// The indices for the projection array above.
+private static final int PROJECTION_ID_INDEX = 0;
+private static final int PROJECTION_ACCOUNT_NAME_INDEX = 1;
+private static final int PROJECTION_DISPLAY_NAME_INDEX = 2;
+private static final int PROJECTION_OWNER_ACCOUNT_INDEX = 3;
+ + + + + +

В следующей части примера создается запрос. С помощью выбора определяются +критерии для запроса. В этом примере выполняется поиск +календарей со следующими значениями параметров: ACCOUNT_NAME +— sampleuser@google.com, ACCOUNT_TYPE +— com.google и OWNER_ACCOUNT + — sampleuser@google.com. Для просмотра всех просмотренных +пользователем календарей, а не только имеющихся у него, не указывайте параметр OWNER_ACCOUNT. +Запрос возвращает объект {@link android.database.Cursor}, +который можно использовать для перебора результатов, возвращенных запросом к базе +данных. Дополнительные сведения об использовании запросов в поставщиках контента +представлены в статье Поставщики контента.

+ + +
// Run query
+Cursor cur = null;
+ContentResolver cr = getContentResolver();
+Uri uri = Calendars.CONTENT_URI;   
+String selection = "((" + Calendars.ACCOUNT_NAME + " = ?) AND (" 
+                        + Calendars.ACCOUNT_TYPE + " = ?) AND ("
+                        + Calendars.OWNER_ACCOUNT + " = ?))";
+String[] selectionArgs = new String[] {"sampleuser@gmail.com", "com.google",
+        "sampleuser@gmail.com"}; 
+// Submit the query and get a Cursor object back. 
+cur = cr.query(uri, EVENT_PROJECTION, selection, selectionArgs, null);
+ +

В следующем разделе кода выполняется пошаговый обзор набора результатов с помощью курсора. В нем +используются константы, которые были заданы в начале примера, для получения значений +для каждого из полей.

+ +
// Use the cursor to step through the returned records
+while (cur.moveToNext()) {
+    long calID = 0;
+    String displayName = null;
+    String accountName = null;
+    String ownerName = null;
+      
+    // Get the field values
+    calID = cur.getLong(PROJECTION_ID_INDEX);
+    displayName = cur.getString(PROJECTION_DISPLAY_NAME_INDEX);
+    accountName = cur.getString(PROJECTION_ACCOUNT_NAME_INDEX);
+    ownerName = cur.getString(PROJECTION_OWNER_ACCOUNT_INDEX);
+              
+    // Do something with the values...
+
+   ...
+}
+
+ +

Изменение календаря

+ +

Чтобы обновить календарь, можно указать {@link +android.provider.BaseColumns#_ID} календаря: либо в виде идентификатора, +добавленного к URI + +({@link android.content.ContentUris#withAppendedId(android.net.Uri,long) withAppendedId()}), +либо в качестве первого элемента выделения. Выделение +должно начинаться с "_id=?", а первым аргументом +selectionArg должен быть {@link +android.provider.BaseColumns#_ID} календаря. +Также для выполнения обновлений можно закодировать идентификатор в URI. В этом примере для +изменения отображаемого имени календаря используется +подход +({@link android.content.ContentUris#withAppendedId(android.net.Uri,long) withAppendedId()}):

+ +
private static final String DEBUG_TAG = "MyActivity";
+...
+long calID = 2;
+ContentValues values = new ContentValues();
+// The new display name for the calendar
+values.put(Calendars.CALENDAR_DISPLAY_NAME, "Trevor's Calendar");
+Uri updateUri = ContentUris.withAppendedId(Calendars.CONTENT_URI, calID);
+int rows = getContentResolver().update(updateUri, values, null, null);
+Log.i(DEBUG_TAG, "Rows updated: " + rows);
+ +

Вставка календаря

+ +

Для управления календарями в основном используются адаптеры синхронизации, поэтому +новые календари следует вставлять исключительно как адаптер синхронизации. По большей части +приложения могут вносить в календари только поверхностные изменения, такие как изменение отображаемого имени. Если +приложению требуется создать локальный календарь, это можно сделать путем +вставки календаря в виде адаптера синхронизации с помощью параметра {@link +android.provider.CalendarContract.SyncColumns#ACCOUNT_TYPE} типа {@link +android.provider.CalendarContract#ACCOUNT_TYPE_LOCAL}. +{@link android.provider.CalendarContract#ACCOUNT_TYPE_LOCAL} +представляет собой особый тип аккаунтов для календарей, которые не связаны +с аккаунтом устройства. Календари этого типа не синхронизируются с сервером. Дополнительные сведения +об адаптерах синхронизации представлены в статье Адаптеры синхронизации.

+ +

Таблица событий

+ +

В таблице {@link android.provider.CalendarContract.Events} содержатся подробные сведения +о каждом отдельном событии. Чтобы получить возможность добавлять, обновлять или удалять события, +в файл манифеста +приложения необходимо включить разрешение {@link android.Manifest.permission#WRITE_CALENDAR}.

+ +

Выполнять запись в указанные ниже столбцы этой таблицы могут и приложение, и +адаптер синхронизации. Полный список поддерживаемых полей представлен в справке по классу {@link +android.provider.CalendarContract.Events}.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
КонстантаОписание
{@link android.provider.CalendarContract.EventsColumns#CALENDAR_ID}{@link android.provider.BaseColumns#_ID} календаря, к которому принадлежит событие.
{@link android.provider.CalendarContract.EventsColumns#ORGANIZER}Адрес эл. почты организатора (владельца) события.
{@link android.provider.CalendarContract.EventsColumns#TITLE}Название события.
{@link android.provider.CalendarContract.EventsColumns#EVENT_LOCATION}Место проведения.
{@link android.provider.CalendarContract.EventsColumns#DESCRIPTION}Описание события.
{@link android.provider.CalendarContract.EventsColumns#DTSTART}Время начала события по UTC (в миллисекундах) от точки отсчета.
{@link android.provider.CalendarContract.EventsColumns#DTEND}Время окончания события по UTC (в миллисекундах) от точки отсчета.
{@link android.provider.CalendarContract.EventsColumns#EVENT_TIMEZONE}Часовой пояс события.
{@link android.provider.CalendarContract.EventsColumns#EVENT_END_TIMEZONE}Часовой пояс для времени окончания события.
{@link android.provider.CalendarContract.EventsColumns#DURATION}Продолжительность события в формате RFC5545. +Например, значение "PT1H" обозначает, что событие +должно длиться один час, а значение "P2W" указывает на продолжительность +в 2 недели.
{@link android.provider.CalendarContract.EventsColumns#ALL_DAY}Значение «1» обозначает, что это событие занимает весь день по +местному часовому поясу. Значение «0» указывает на то, что это регулярное событие, которое может начаться +и завершиться в любое время в течение дня.
{@link android.provider.CalendarContract.EventsColumns#RRULE}Правило повторения для формата события. Например, +"FREQ=WEEKLY;COUNT=10;WKST=SU". С другими +примерами можно ознакомиться здесь.
{@link android.provider.CalendarContract.EventsColumns#RDATE}Даты повторения события. +Обычно {@link android.provider.CalendarContract.EventsColumns#RDATE} +используется вместе с {@link android.provider.CalendarContract.EventsColumns#RRULE} +для определения агрегированного набора +повторяющихся событий. Дополнительные сведения представлены в спецификации RFC5545.
{@link android.provider.CalendarContract.EventsColumns#AVAILABILITY}Если событие считается как занятое или как свободное время, + доступное для планирования.
{@link android.provider.CalendarContract.EventsColumns#GUESTS_CAN_MODIFY}Указывает, могут ли гости вносить изменения в событие.
{@link android.provider.CalendarContract.EventsColumns#GUESTS_CAN_INVITE_OTHERS}Указывает, могут ли гости приглашать других гостей.
{@link android.provider.CalendarContract.EventsColumns#GUESTS_CAN_SEE_GUESTS}Указывает, могут ли гости просматривать список участников.
+ +

Добавление событий

+ +

Когда ваше приложение вставляет новое событие, мы рекомендуем использовать намерение +{@link android.content.Intent#ACTION_INSERT INSERT}, как описано в разделе Использование намерения для вставки события. Однако при +необходимости вы можете вставлять события напрямую. В этом разделе как раз описывается то, как это +сделать.

+ + +

Ниже указаны правила, которыми следует руководствоваться для вставки нового события.

+
    + +
  • Необходимо указать {@link +android.provider.CalendarContract.EventsColumns#CALENDAR_ID} и {@link +android.provider.CalendarContract.EventsColumns#DTSTART}.
  • + +
  • Необходимо указать {@link +android.provider.CalendarContract.EventsColumns#EVENT_TIMEZONE}. Чтобы получить список +установленных в системе идентификаторов часовых поясов, воспользуйтесь методом {@link +java.util.TimeZone#getAvailableIDs()}. Обратите внимание, что это правило не применяется при +вставке события с помощью намерения {@link +android.content.Intent#ACTION_INSERT INSERT}, как описано в разделе Использование намерения для вставки события, — в этом +случае используется часовой пояс по умолчанию.
  • + +
  • Для однократных событий необходимо указать {@link +android.provider.CalendarContract.EventsColumns#DTEND}.
  • + + +
  • Для повторяющихся событий необходимо указать {@link +android.provider.CalendarContract.EventsColumns#DURATION} в дополнение к {@link +android.provider.CalendarContract.EventsColumns#RRULE} или {@link +android.provider.CalendarContract.EventsColumns#RDATE}. Обратите внимание, что это правило не применяется при +вставке события с помощью намерения {@link +android.content.Intent#ACTION_INSERT INSERT}, как описано в разделе Использование намерения для вставки события, — в этом +случае можно использовать {@link +android.provider.CalendarContract.EventsColumns#RRULE} в сочетании с {@link android.provider.CalendarContract.EventsColumns#DTSTART} и {@link android.provider.CalendarContract.EventsColumns#DTEND}; кроме того, приложение «Календарь» + автоматически преобразует указанный период в продолжительность.
  • + +
+ +

Ниже представлен пример вставки события. Для простоты все это выполняется в потоке +пользовательского интерфейса. На практике же все вставки и обновления следует выполнять в +асинхронном потоке, чтобы переместить операцию в фоновый поток. Дополнительные сведения представлены в справке по +{@link android.content.AsyncQueryHandler}.

+ + +
+long calID = 3;
+long startMillis = 0; 
+long endMillis = 0;     
+Calendar beginTime = Calendar.getInstance();
+beginTime.set(2012, 9, 14, 7, 30);
+startMillis = beginTime.getTimeInMillis();
+Calendar endTime = Calendar.getInstance();
+endTime.set(2012, 9, 14, 8, 45);
+endMillis = endTime.getTimeInMillis();
+...
+
+ContentResolver cr = getContentResolver();
+ContentValues values = new ContentValues();
+values.put(Events.DTSTART, startMillis);
+values.put(Events.DTEND, endMillis);
+values.put(Events.TITLE, "Jazzercise");
+values.put(Events.DESCRIPTION, "Group workout");
+values.put(Events.CALENDAR_ID, calID);
+values.put(Events.EVENT_TIMEZONE, "America/Los_Angeles");
+Uri uri = cr.insert(Events.CONTENT_URI, values);
+
+// get the event ID that is the last element in the Uri
+long eventID = Long.parseLong(uri.getLastPathSegment());
+// 
+// ... do something with event ID
+//
+//
+ +

Примечание. Ниже демонстрируется, как в примере кода выполняется захват +идентификатора события после создания этого события. Это самый простой способ получить идентификатор события. Зачастую +идентификатор события необходим для выполнения других действий с календарем — например, для добавления участников или +напоминаний о событии.

+ + +

Обновление событий

+ +

Когда ваше приложение хочет предоставить пользователю возможность изменить событие, мы рекомендуем использовать намерение +{@link android.content.Intent#ACTION_EDIT EDIT}, как описано в разделе +Использование намерения для вставки события. +Однако при необходимости вы можете редактировать события напрямую. Чтобы обновить +событие, можно указать +_ID события: либо в виде идентификатора, добавленного к URI({@link +android.content.ContentUris#withAppendedId(android.net.Uri,long) withAppendedId()}), +либо в качестве первого элемента выделения. Выделение +должно начинаться с "_id=?", а первым аргументом +selectionArg должен быть _ID события. Также можно обновлять +выделения без идентификаторов. Ниже представлен пример обновления +события. Это пример изменения названия события с помощью +метода +{@link android.content.ContentUris#withAppendedId(android.net.Uri,long) withAppendedId()}:

+ + +
private static final String DEBUG_TAG = "MyActivity";
+...
+long eventID = 188;
+...
+ContentResolver cr = getContentResolver();
+ContentValues values = new ContentValues();
+Uri updateUri = null;
+// The new title for the event
+values.put(Events.TITLE, "Kickboxing"); 
+updateUri = ContentUris.withAppendedId(Events.CONTENT_URI, eventID);
+int rows = getContentResolver().update(updateUri, values, null, null);
+Log.i(DEBUG_TAG, "Rows updated: " + rows);  
+ +

Удаление событий

+ +

Удалить событие можно по его {@link +android.provider.BaseColumns#_ID}, который добавлен в качестве идентификатора к URI, или с помощью +стандартного выделения. В случае использования добавленного идентификатора невозможно также выполнить и выделение. +Существует две версии операции удаления: для приложения и для адаптера синхронизации. При удалении +для приложения в столбце deleted устанавливается значение «1». Этот флаг +сообщает адаптеру синхронизации о том, что строка была удалена и информацию об удалении следует +передать серверу. При удалении для адаптера синхронизации событие удаляется из +базы данных вместе со всеми связанными с ним данными. Ниже представлен пример удаления +события для приложения по его {@link android.provider.BaseColumns#_ID}.

+ + +
private static final String DEBUG_TAG = "MyActivity";
+...
+long eventID = 201;
+...
+ContentResolver cr = getContentResolver();
+ContentValues values = new ContentValues();
+Uri deleteUri = null;
+deleteUri = ContentUris.withAppendedId(Events.CONTENT_URI, eventID);
+int rows = getContentResolver().delete(deleteUri, null, null);
+Log.i(DEBUG_TAG, "Rows deleted: " + rows);  
+
+ +

Таблица участников

+ +

В каждой строке таблицы {@link android.provider.CalendarContract.Attendees} +указан один участник или гость события. При вызове метода +{@link android.provider.CalendarContract.Reminders#query(android.content.ContentResolver, long, java.lang.String[]) query()} +возвращается список участников для события +с заданным {@link android.provider.CalendarContract.AttendeesColumns#EVENT_ID}. +Этот {@link android.provider.CalendarContract.AttendeesColumns#EVENT_ID} +должен соответствовать {@link +android.provider.BaseColumns#_ID} определенного события.

+ +

В таблице ниже указаны +поля, доступные для записи. При вставке нового участника необходимо указать все эти поля, кроме +ATTENDEE_NAME. +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
КонстантаОписание
{@link android.provider.CalendarContract.AttendeesColumns#EVENT_ID}Идентификатор события.
{@link android.provider.CalendarContract.AttendeesColumns#ATTENDEE_NAME}Имя участника.
{@link android.provider.CalendarContract.AttendeesColumns#ATTENDEE_EMAIL}Адрес эл. почты участника.
{@link android.provider.CalendarContract.AttendeesColumns#ATTENDEE_RELATIONSHIP}

Связь участника с событием. Одно из следующего:

+
    +
  • {@link android.provider.CalendarContract.AttendeesColumns#RELATIONSHIP_ATTENDEE}
  • +
  • {@link android.provider.CalendarContract.AttendeesColumns#RELATIONSHIP_NONE}
  • +
  • {@link android.provider.CalendarContract.AttendeesColumns#RELATIONSHIP_ORGANIZER}
  • +
  • {@link android.provider.CalendarContract.AttendeesColumns#RELATIONSHIP_PERFORMER}
  • +
  • {@link android.provider.CalendarContract.AttendeesColumns#RELATIONSHIP_SPEAKER}
  • +
+
{@link android.provider.CalendarContract.AttendeesColumns#ATTENDEE_TYPE}

Тип участника. Одно из следующего:

+
    +
  • {@link android.provider.CalendarContract.AttendeesColumns#TYPE_REQUIRED}
  • +
  • {@link android.provider.CalendarContract.AttendeesColumns#TYPE_OPTIONAL}
  • +
{@link android.provider.CalendarContract.AttendeesColumns#ATTENDEE_STATUS}

Статус посещения события участником. Одно из следующего:

+
    +
  • {@link android.provider.CalendarContract.AttendeesColumns#ATTENDEE_STATUS_ACCEPTED}
  • +
  • {@link android.provider.CalendarContract.AttendeesColumns#ATTENDEE_STATUS_DECLINED}
  • +
  • {@link android.provider.CalendarContract.AttendeesColumns#ATTENDEE_STATUS_INVITED}
  • +
  • {@link android.provider.CalendarContract.AttendeesColumns#ATTENDEE_STATUS_NONE}
  • +
  • {@link android.provider.CalendarContract.AttendeesColumns#ATTENDEE_STATUS_TENTATIVE}
  • +
+ +

Добавление участников

+ +

Ниже представлен пример добавления одного участника события. Обратите внимание, что нужно в обязательном порядке +указать +{@link android.provider.CalendarContract.AttendeesColumns#EVENT_ID}.

+ +
+long eventID = 202;
+...
+ContentResolver cr = getContentResolver();
+ContentValues values = new ContentValues();
+values.put(Attendees.ATTENDEE_NAME, "Trevor");
+values.put(Attendees.ATTENDEE_EMAIL, "trevor@example.com");
+values.put(Attendees.ATTENDEE_RELATIONSHIP, Attendees.RELATIONSHIP_ATTENDEE);
+values.put(Attendees.ATTENDEE_TYPE, Attendees.TYPE_OPTIONAL);
+values.put(Attendees.ATTENDEE_STATUS, Attendees.ATTENDEE_STATUS_INVITED);
+values.put(Attendees.EVENT_ID, eventID);
+Uri uri = cr.insert(Attendees.CONTENT_URI, values);
+
+ +

Таблица напоминаний

+ +

В каждой строке таблицы {@link android.provider.CalendarContract.Reminders} +указано одно напоминание о событии. При вызове метода +{@link android.provider.CalendarContract.Reminders#query(android.content.ContentResolver, long, java.lang.String[]) query()}возвращается список напоминаний для события +с заданным +{@link android.provider.CalendarContract.AttendeesColumns#EVENT_ID}.

+ + +

В таблице ниже указаны поля, доступные для записи. При вставке нового +напоминания необходимо указать все эти поля. Обратите внимание, что адаптеры синхронизации задают +типы напоминаний, которые они поддерживают, в таблице {@link +android.provider.CalendarContract.Calendars}. Подробные сведения см. +в справке по +{@link android.provider.CalendarContract.CalendarColumns#ALLOWED_REMINDERS}.

+ + + + + + + + + + + + + + + + + + + +
КонстантаОписание
{@link android.provider.CalendarContract.RemindersColumns#EVENT_ID}Идентификатор события.
{@link android.provider.CalendarContract.RemindersColumns#MINUTES}Время срабатывания уведомления (в минутах) до начала события.
{@link android.provider.CalendarContract.RemindersColumns#METHOD}

Метод уведомления, заданный на сервере. Одно из следующего:

+
    +
  • {@link android.provider.CalendarContract.RemindersColumns#METHOD_ALERT}
  • +
  • {@link android.provider.CalendarContract.RemindersColumns#METHOD_DEFAULT}
  • +
  • {@link android.provider.CalendarContract.RemindersColumns#METHOD_EMAIL}
  • +
  • {@link android.provider.CalendarContract.RemindersColumns#METHOD_SMS}
  • +
+ +

Добавление напоминаний

+ +

Ниже представлен пример добавления напоминания в событие. Напоминание срабатывает за 15 +минут до начала события.

+
+long eventID = 221;
+...
+ContentResolver cr = getContentResolver();
+ContentValues values = new ContentValues();
+values.put(Reminders.MINUTES, 15);
+values.put(Reminders.EVENT_ID, eventID);
+values.put(Reminders.METHOD, Reminders.METHOD_ALERT);
+Uri uri = cr.insert(Reminders.CONTENT_URI, values);
+ +

Таблица экземпляров

+ +

В таблице +{@link android.provider.CalendarContract.Instances} содержатся данные о времени +начала и окончания повторений события. В каждой строке этой таблицы +представлено одно повторение события. Таблица экземпляров недоступна для записи; она предоставляет только +возможность запрашивать повторения событий.

+ +

В таблице ниже перечислены некоторые из полей, которые можно запросить для экземпляра. Обратите внимание, +что часовой пояс задается параметрами +{@link android.provider.CalendarContract.CalendarCache#KEY_TIMEZONE_TYPE} +и +{@link android.provider.CalendarContract.CalendarCache#KEY_TIMEZONE_INSTANCES}.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
КонстантаОписание
{@link android.provider.CalendarContract.Instances#BEGIN}Время начала экземпляра в формате UTC (в миллисекундах).
{@link android.provider.CalendarContract.Instances#END}Время окончания экземпляра в формате UTC (в миллисекундах).
{@link android.provider.CalendarContract.Instances#END_DAY}День окончания экземпляра по юлианскому календарю относительно часового пояса +приложения «Календарь». + +
{@link android.provider.CalendarContract.Instances#END_MINUTE}Минута окончания экземпляра, вычисленная от полуночи по часовому поясу +приложения «Календарь».
{@link android.provider.CalendarContract.Instances#EVENT_ID}_ID события для этого экземпляра.
{@link android.provider.CalendarContract.Instances#START_DAY}День начала экземпляра по юлианскому календарю относительно часового пояса приложения «Календарь». +
{@link android.provider.CalendarContract.Instances#START_MINUTE}Минута начала экземпляра, вычисленная от полуночи по часовому поясу +приложения «Календарь». +
+ +

Запрос таблицы экземпляров

+ +

Чтобы запросить таблицу экземпляров, необходимо указать промежуток времени для запроса в +URI. В этом примере {@link android.provider.CalendarContract.Instances} +получает доступ к полю {@link +android.provider.CalendarContract.EventsColumns#TITLE} посредством своей реализации +интерфейса {@link android.provider.CalendarContract.EventsColumns}. +Другими словами, {@link +android.provider.CalendarContract.EventsColumns#TITLE} +возвращается посредством обращения к базе данных, а не путем запроса таблицы {@link +android.provider.CalendarContract.Instances} с необработанными данными.

+ +
+private static final String DEBUG_TAG = "MyActivity";
+public static final String[] INSTANCE_PROJECTION = new String[] {
+    Instances.EVENT_ID,      // 0
+    Instances.BEGIN,         // 1
+    Instances.TITLE          // 2
+  };
+  
+// The indices for the projection array above.
+private static final int PROJECTION_ID_INDEX = 0;
+private static final int PROJECTION_BEGIN_INDEX = 1;
+private static final int PROJECTION_TITLE_INDEX = 2;
+...
+
+// Specify the date range you want to search for recurring
+// event instances
+Calendar beginTime = Calendar.getInstance();
+beginTime.set(2011, 9, 23, 8, 0);
+long startMillis = beginTime.getTimeInMillis();
+Calendar endTime = Calendar.getInstance();
+endTime.set(2011, 10, 24, 8, 0);
+long endMillis = endTime.getTimeInMillis();
+  
+Cursor cur = null;
+ContentResolver cr = getContentResolver();
+
+// The ID of the recurring event whose instances you are searching
+// for in the Instances table
+String selection = Instances.EVENT_ID + " = ?";
+String[] selectionArgs = new String[] {"207"};
+
+// Construct the query with the desired date range.
+Uri.Builder builder = Instances.CONTENT_URI.buildUpon();
+ContentUris.appendId(builder, startMillis);
+ContentUris.appendId(builder, endMillis);
+
+// Submit the query
+cur =  cr.query(builder.build(), 
+    INSTANCE_PROJECTION, 
+    selection, 
+    selectionArgs, 
+    null);
+   
+while (cur.moveToNext()) {
+    String title = null;
+    long eventID = 0;
+    long beginVal = 0;    
+    
+    // Get the field values
+    eventID = cur.getLong(PROJECTION_ID_INDEX);
+    beginVal = cur.getLong(PROJECTION_BEGIN_INDEX);
+    title = cur.getString(PROJECTION_TITLE_INDEX);
+              
+    // Do something with the values. 
+    Log.i(DEBUG_TAG, "Event:  " + title); 
+    Calendar calendar = Calendar.getInstance();
+    calendar.setTimeInMillis(beginVal);  
+    DateFormat formatter = new SimpleDateFormat("MM/dd/yyyy");
+    Log.i(DEBUG_TAG, "Date: " + formatter.format(calendar.getTime()));    
+    }
+ }
+ +

Намерения календаря

+

Вашему приложению не нужно запрашивать разрешения на чтение и запись данных календаря. Вместо этого можно использовать намерения, поддерживаемые приложением «Календарь» Android, для обработки операций чтения и записи в этом приложении. В таблице ниже указаны намерения, поддерживаемые поставщиком календаря.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ДействиеURIОписаниеДополнительные данные

+ {@link android.content.Intent#ACTION_VIEW VIEW}

content://com.android.calendar/time/<ms_since_epoch>

+ Сослаться на URI также можно с помощью +{@link android.provider.CalendarContract#CONTENT_URI CalendarContract.CONTENT_URI}. +Пример использования этого намерения представлен в разделе Использование намерений для просмотра данных календаря. + +
Открытие календаря во время, заданное параметром <ms_since_epoch>.Отсутствуют.

{@link android.content.Intent#ACTION_VIEW VIEW}

+ +

content://com.android.calendar/events/<event_id>

+ + Сослаться на URI также можно с помощью +{@link android.provider.CalendarContract.Events#CONTENT_URI Events.CONTENT_URI}. +Пример использования этого намерения представлен в разделе Использование намерений для просмотра данных календаря. + +
Просмотр события, указанного с помощью <event_id>.{@link android.provider.CalendarContract#EXTRA_EVENT_BEGIN_TIME CalendarContract.EXTRA_EVENT_BEGIN_TIME}
+
+
+ {@link android.provider.CalendarContract#EXTRA_EVENT_END_TIME CalendarContract.EXTRA_EVENT_END_TIME}
{@link android.content.Intent#ACTION_EDIT EDIT}

content://com.android.calendar/events/<event_id>

+ + Сослаться на URI также можно с помощью +{@link android.provider.CalendarContract.Events#CONTENT_URI Events.CONTENT_URI}. +Пример использования этого намерения представлен в разделе Использование намерения для редактирования события. + + +
Редактирование события, указанного с помощью <event_id>.{@link android.provider.CalendarContract#EXTRA_EVENT_BEGIN_TIME CalendarContract.EXTRA_EVENT_BEGIN_TIME}
+
+
+ {@link android.provider.CalendarContract#EXTRA_EVENT_END_TIME CalendarContract.EXTRA_EVENT_END_TIME}
{@link android.content.Intent#ACTION_EDIT EDIT}
+
+ {@link android.content.Intent#ACTION_INSERT INSERT}

content://com.android.calendar/events

+ + Сослаться на URI также можно с помощью +{@link android.provider.CalendarContract.Events#CONTENT_URI Events.CONTENT_URI}. +Пример использования этого намерения представлен в разделе Использование намерения для редактирования события. + +
Создание события.Любые из дополнительных данных, указанных в таблице ниже.
+ +

В таблице ниже указаны дополнительные данные намерения, которые поддерживаются поставщиком календаря. +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Дополнительные данные намеренияОписание
{@link android.provider.CalendarContract.EventsColumns#TITLE Events.TITLE}Название события.
{@link android.provider.CalendarContract#EXTRA_EVENT_BEGIN_TIME +CalendarContract.EXTRA_EVENT_BEGIN_TIME}Время начала события (в миллисекундах) от эпохи.
{@link android.provider.CalendarContract#EXTRA_EVENT_END_TIME +CalendarContract.EXTRA_EVENT_END_TIME}Время окончания события (в миллисекундах) от эпохи.
{@link android.provider.CalendarContract#EXTRA_EVENT_ALL_DAY +CalendarContract.EXTRA_EVENT_ALL_DAY}Логическое значение, обозначающее, что это событие на весь день. Значение может быть +true или false.
{@link android.provider.CalendarContract.EventsColumns#EVENT_LOCATION +Events.EVENT_LOCATION}Место проведения события.
{@link android.provider.CalendarContract.EventsColumns#DESCRIPTION +Events.DESCRIPTION}Описание события.
+ {@link android.content.Intent#EXTRA_EMAIL Intent.EXTRA_EMAIL}Адреса эл. почты приглашенных (через запятую).
+ {@link android.provider.CalendarContract.EventsColumns#RRULE Events.RRULE}Правило повторения для события.
+ {@link android.provider.CalendarContract.EventsColumns#ACCESS_LEVEL +Events.ACCESS_LEVEL}Указывает на то, является ли событие общедоступным или закрытым.
{@link android.provider.CalendarContract.EventsColumns#AVAILABILITY +Events.AVAILABILITY}Если событие считается как занятое или как свободное время, доступное для планирования.
+

В разделах ниже указан порядок использования этих намерений.

+ + +

Использование намерения для вставки события

+ +

С помощью намерения {@link android.content.Intent#ACTION_INSERT INSERT} +ваше приложение может отправлять задачи вставки события прямо в приложение «Календарь». +Благодаря этому в файл манифеста вашего приложения не нужно включать разрешение {@link +android.Manifest.permission#WRITE_CALENDAR}.

+ + +

Когда пользователи работают с приложением, в котором используется такой подход, приложение отправляет +их в «Календарь» для завершения добавления события. Намерение {@link +android.content.Intent#ACTION_INSERT INSERT} использует дополнительные поля +для предварительного указания в форме сведений о событии в приложении «Календарь». После этого пользователи +могут отменить событие, отредактировать форму или сохранить событие в своем +календаре.

+ + + +

Ниже представлен фрагмент кода, в котором на 19 января 2012 г. планируется событие, которое будет проходить с +7:30 до 8:30. Однако следует учесть некоторые моменты, касающиеся этого примера кода.

+ +
    +
  • В качестве URI в нем задается +{@link android.provider.CalendarContract.Events#CONTENT_URI Events.CONTENT_URI}.
  • + +
  • В нем используются дополнительные поля {@link +android.provider.CalendarContract#EXTRA_EVENT_BEGIN_TIME +CalendarContract.EXTRA_EVENT_BEGIN_TIME} и {@link +android.provider.CalendarContract#EXTRA_EVENT_END_TIME +CalendarContract.EXTRA_EVENT_END_TIME} для предварительного указания в форме +сведений о времени события. Значения времени должны быть указаны в формате UTC и в миллисекундах от +эпохи.
  • + +
  • В нем используется дополнительное поле {@link android.content.Intent#EXTRA_EMAIL Intent.EXTRA_EMAIL} +для предоставления списка участников, разделенных запятыми (их адреса эл. почты).
  • + +
+
+Calendar beginTime = Calendar.getInstance();
+beginTime.set(2012, 0, 19, 7, 30);
+Calendar endTime = Calendar.getInstance();
+endTime.set(2012, 0, 19, 8, 30);
+Intent intent = new Intent(Intent.ACTION_INSERT)
+        .setData(Events.CONTENT_URI)
+        .putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, beginTime.getTimeInMillis())
+        .putExtra(CalendarContract.EXTRA_EVENT_END_TIME, endTime.getTimeInMillis())
+        .putExtra(Events.TITLE, "Yoga")
+        .putExtra(Events.DESCRIPTION, "Group class")
+        .putExtra(Events.EVENT_LOCATION, "The gym")
+        .putExtra(Events.AVAILABILITY, Events.AVAILABILITY_BUSY)
+        .putExtra(Intent.EXTRA_EMAIL, "rowan@example.com,trevor@example.com");
+startActivity(intent);
+
+ +

Использование намерения для редактирования события

+ +

Событие можно отредактировать напрямую, как описано в разделе Обновление событий. Благодаря намерению {@link +android.content.Intent#ACTION_EDIT EDIT} приложение, +у которого нет разрешения, может делегировать редактирование события приложению «Календарь». +По завершении редактирования события в приложении «Календарь» пользователи возвращаются +в исходное приложение.

Ниже представлен пример намерения, который задает +новый заголовок для указанного события и позволяет пользователям редактировать событие в приложении «Календарь».

+ + +
long eventID = 208;
+Uri uri = ContentUris.withAppendedId(Events.CONTENT_URI, eventID);
+Intent intent = new Intent(Intent.ACTION_EDIT)
+    .setData(uri)
+    .putExtra(Events.TITLE, "My New Title");
+startActivity(intent);
+ +

Использование намерений для просмотра данных календаря

+

Поставщик календаря позволяет использовать намерение {@link android.content.Intent#ACTION_VIEW VIEW} двумя различными способами:

+
    +
  • Открытие приложения «Календарь» на определенной дате.
  • +
  • Просмотр события.
  • + +
+

Ниже представлен пример открытия приложения «Календарь» на определенной дате.

+
// A date-time specified in milliseconds since the epoch.
+long startMillis;
+...
+Uri.Builder builder = CalendarContract.CONTENT_URI.buildUpon();
+builder.appendPath("time");
+ContentUris.appendId(builder, startMillis);
+Intent intent = new Intent(Intent.ACTION_VIEW)
+    .setData(builder.build());
+startActivity(intent);
+ +

Ниже представлен пример открытия события для его просмотра.

+
long eventID = 208;
+...
+Uri uri = ContentUris.withAppendedId(Events.CONTENT_URI, eventID);
+Intent intent = new Intent(Intent.ACTION_VIEW)
+   .setData(uri);
+startActivity(intent);
+
+ + +

Адаптеры синхронизации

+ + +

Существуют лишь незначительные различия в том, как приложение и адаптер синхронизации +получают доступ к поставщику календаря.

+ +
    +
  • Адаптеру синхронизации необходимо указать, что он является таковым, задав для параметра {@link android.provider.CalendarContract#CALLER_IS_SYNCADAPTER} значение true.
  • + + +
  • Адаптеру синхронизации необходимо предоставить {@link +android.provider.CalendarContract.SyncColumns#ACCOUNT_NAME} и {@link +android.provider.CalendarContract.SyncColumns#ACCOUNT_TYPE} в качестве параметров запроса в URI.
  • + +
  • Адаптер синхронизации имеет доступ на запись к большему числу столбцов, чем приложение или виджет. + Например, приложение может изменять только некоторые характеристики календаря, +такие как название, отображаемое имя, настройки видимости и +синхронизации. Тогда как адаптер синхронизации имеет доступ не только к этим столбцам, но и ко многим другим его характеристикам, +таким как цвет календаря, часовой пояс, уровень доступа, местоположение и т. д. +Однако адаптер синхронизации ограничен задаваемыми им параметрами ACCOUNT_NAME и +ACCOUNT_TYPE.
+ +

Ниже представлен метод, который можно использовать, чтобы получить URI для использования вместе с адаптером синхронизации.

+
 static Uri asSyncAdapter(Uri uri, String account, String accountType) {
+    return uri.buildUpon()
+        .appendQueryParameter(android.provider.CalendarContract.CALLER_IS_SYNCADAPTER,"true")
+        .appendQueryParameter(Calendars.ACCOUNT_NAME, account)
+        .appendQueryParameter(Calendars.ACCOUNT_TYPE, accountType).build();
+ }
+
+

Пример реализации адаптера синхронизации (который не связан с приложением «Календарь») представлен в статье, посвященной +SampleSyncAdapter. diff --git a/docs/html-intl/intl/ru/guide/topics/providers/contacts-provider.jd b/docs/html-intl/intl/ru/guide/topics/providers/contacts-provider.jd new file mode 100644 index 0000000000000000000000000000000000000000..4d07856f250ed4252dca47b98ba925294c6f9b18 --- /dev/null +++ b/docs/html-intl/intl/ru/guide/topics/providers/contacts-provider.jd @@ -0,0 +1,2356 @@ +page.title=Поставщик контактов +@jd:body +

+
+

Краткое описание

+
    +
  • Репозиторий Android с пользовательскими данными.
  • +
  • + Синхронизация со службами в Интернете. +
  • +
  • + Интеграция с потоками данных из социальных сетей. +
  • +
+

Содержание документа

+
    +
  1. + Структура поставщика контактов +
  2. +
  3. + Необработанные контакты +
  4. +
  5. + Данные +
  6. +
  7. + Контакты +
  8. +
  9. + Данные, полученные от адаптеров синхронизации +
  10. +
  11. + Требуемые разрешения +
  12. +
  13. + Профиль пользователя +
  14. +
  15. + Метаданные поставщика контактов +
  16. +
  17. + Доступ к поставщику контактов +
  18. +
  19. +
  20. + Адаптеры синхронизации поставщика контактов +
  21. +
  22. + Потоки данных из социальных сетей +
  23. +
  24. + Дополнительные возможности поставщика контактов +
  25. +
+

Ключевые классы

+
    +
  1. {@link android.provider.ContactsContract.Contacts}
  2. +
  3. {@link android.provider.ContactsContract.RawContacts}
  4. +
  5. {@link android.provider.ContactsContract.Data}
  6. +
  7. {@code android.provider.ContactsContract.StreamItems}
  8. +
+

Образцы кода по теме

+
    +
  1. + +Диспетчер контактов + +
  2. +
  3. + +Пример адаптера синхронизации +
  4. +
+

См. также:

+
    +
  1. + +Основные сведения о поставщике контента + +
  2. +
+
+
+

+ Поставщик контактов представляет собой эффективный и гибкий компонент Android, который управляет +центральным репозиторием устройства, в котором хранятся пользовательские данные. Поставщик контактов — это источник данных, +которые отображаются в приложении «Контакты» на вашем устройстве. Вы также можете получить доступ к этим данным в своем собственном +приложении и организовать обмен такими данными между устройством и службами в Интернете. Поставщик взаимодействует +с широким набором источников данных и пытается организовать управление как можно большим набором данных о каждом человеке, поэтому +организация поставщика довольно сложная. По этой причине API поставщика +содержит широкий набор классов-контрактов и интерфейсов, отвечающих как за получение данных, так и за их +изменение. +

+

+ В этом руководстве рассматриваются следующие вопросы: +

+
    +
  • + основная структура поставщика; +
  • +
  • + порядок получения данных от поставщика; +
  • +
  • + порядок изменения данных в поставщике; +
  • +
  • + порядок записи адаптера синхронизации для синхронизации данных, полученных с вашего сервера, с данными в +поставщике контактов. +
  • +
+

+ При изучении данного материала подразумевается, что вы уже знакомы с основами поставщиков контента Android. Дополнительные сведения +о поставщиках контента Android представлены в руководстве +Основные +сведения о поставщике контента. Пример +адаптера синхронизации +служит примером использования такого приложения для обмена данными между поставщиком +контактов и приложением, размещенным в веб-службах Google. +

+

Структура поставщика контактов

+

+ Поставщик контактов представляет собой поставщик контента Android. Он содержит в себе три типа +данных о пользователе, каждый из которых указан в отдельной таблице, предоставляемой поставщиком, +как показано на рисунке 1. +

+ +

+ Рисунок 1. Структура таблицы поставщика контактов. +

+

+ В качестве имен этих трех таблиц обычно используются названия соответствующих классов-контрактов. Эти классы +определяют константы для URI контента, названий столбцов и значений в столбцах этих таблиц. +

+
+
+ Таблица {@link android.provider.ContactsContract.Contacts} +
+
+ Строки в этой таблице содержат данные о разных пользователях, полученные путем агрегации строк необработанных контактов. +
+
+ Таблица {@link android.provider.ContactsContract.RawContacts} +
+
+ Строки в этой таблице содержат сводные данные о пользователе, относящиеся к пользовательскому аккаунту и его типу. +
+
+ Таблица {@link android.provider.ContactsContract.Data} +
+
+ Строки в этой таблице содержат сведения о необработанных контактах, такие как адреса эл. почты или номера телефонов. +
+
+

+ Другие таблицы, представленные классами-контрактами в {@link android.provider.ContactsContract}, +представляют собой вспомогательные таблицы, которые поставщик контактов использует для управления своими операциями или поддержки +определенных функций, имеющихся в приложении устройства «Контакты» или приложениях для телефонной связи. +

+

Необработанные контакты

+

+ Необработанный контакт представляет собой данные о пользователе, поступающие из одного аккаунта определенного +типа. Поскольку в качестве источника данных о пользователе в поставщике контактов может выступать сразу несколько онлайн-служб, +для одного и того же пользователя в поставщике контактов может существовать несколько необработанных контактов. + Это также позволяет пользователю объединять пользовательские данные из нескольких аккаунтов +одного и того же типа. +

+

+ Большая часть данных необработанного контакта не хранится в таблице +{@link android.provider.ContactsContract.RawContacts}. Вместо этого они хранятся в одной или нескольких +строках в таблице {@link android.provider.ContactsContract.Data}. В каждой строке данных имеется +столбец {@link android.provider.ContactsContract.DataColumns#RAW_CONTACT_ID Data.RAW_CONTACT_ID}, +в котором содержится значение {@code android.provider.BaseColumns#_ID RawContacts._ID} его +родительской строки{@link android.provider.ContactsContract.RawContacts}. +

+

Важные столбцы необработанных контактов

+

+ В таблице 1 указаны столбцы таблицы {@link android.provider.ContactsContract.RawContacts}, +которые имеют большое значение. Обязательно ознакомьтесь с примечаниями, приведенными после этой таблицы. +

+

+ Таблица 1. Важные столбцы необработанных контактов. +

+ + + + + + + + + + + + + + + + + + + + +
Название столбцаИспользованиеПримечания
+ {@link android.provider.ContactsContract.SyncColumns#ACCOUNT_NAME} + + Имя аккаунта для типа аккаунта, который выступает в роли источника данных для необработанного контакта. + Например, имя аккаунта Google является одним из эл. адресов почты Gmail +владельца устройства. Дополнительные сведения +см. в следующей записи +{@link android.provider.ContactsContract.SyncColumns#ACCOUNT_TYPE}. + + Формат этого имени зависит от типа аккаунта. Это не обязательно +адрес эл. почты. +
+ {@link android.provider.ContactsContract.SyncColumns#ACCOUNT_TYPE} + + Тип аккаунта, который выступает в роли источника данных для необработанного контакта. Например, тип аккаунта +Google — com.google. Всегда указывайте тип аккаунта +и идентификатор домена, которым вы владеете или управляете. Это позволит гарантировать уникальность +типа вашего аккаунта. + + У типа аккаунта, выступающего в роли источника данных контактов, обычно имеется связанный с ним адаптер синхронизации, +который синхронизирует данные с поставщиком контактов. +
+ {@link android.provider.ContactsContract.RawContactsColumns#DELETED} + + Флаг deleted для необработанного контакта. + + Этот флаг позволяет поставщику контактов хранить строку внутри себя до тех пор, +пока адаптеры синхронизации не смогут удалить эту строку с серверов, а затем +удалить ее из репозитория. +
+

Примечания

+

+ Ниже представлены важные примечания к таблице +{@link android.provider.ContactsContract.RawContacts}. +

+
    +
  • + Имя необработанного контакта не хранится в его строке в таблице +{@link android.provider.ContactsContract.RawContacts}. Вместо этого оно хранится в +таблице {@link android.provider.ContactsContract.Data} +в строке {@link android.provider.ContactsContract.CommonDataKinds.StructuredName}. У необработанного контакта +имеется в таблице {@link android.provider.ContactsContract.Data} только одна строка такого типа. +
  • +
  • + Внимание! Чтобы использовать в строке необработанного контакта данные собственного аккаунта, строку +, сначала необходимо зарегистрировать его в классе {@link android.accounts.AccountManager}. Для этого предложите +пользователям добавить тип аккаунта и его имя в список аккаунтов. Если +не сделать этого, поставщик контактов автоматически удалит вашу строку необработанного контакта. +

    + Например, если необходимо, чтобы в вашем приложении хранились данные контактов для веб-службы +в домене {@code com.example.dataservice}, и аккаунт пользователя службы + выглядит следующим образом:{@code becky.sharp@dataservice.example.com}, то пользователю сначала необходимо добавить +«тип» аккаунта ({@code com.example.dataservice}) и его «имя» +({@code becky.smart@dataservice.example.com}) прежде чем ваше приложение сможет добавлять строки необработанных контактов. + Это требование можно указать в документации для пользователей, а также можно предложить +пользователю добавить тип и имя своего аккаунта, либо реализовать оба этих варианта. Более подробно типы и имена аккаунтов +рассматриваются в следующем разделе. +

  • +
+

Источники данных необработанных контактов

+

+ Чтобы понять, что такое необработанный контакт, рассмотрим пример с пользователем Emily Dickinson, на устройстве которой имеется три +следующих аккаунта: +

+
    +
  • emily.dickinson@gmail.com;
  • +
  • emilyd@gmail.com;
  • +
  • Аккаунт belle_of_amherst в Twitter
  • +
+

+ Она включила функцию Синхронизировать контакты для всех трех этих аккаунтов +в настройках Аккаунты. +

+

+ Предположим, что Emily Dickinson открывает браузер, входит в Gmail под именем +emily.dickinson@gmail.com, затем открывает +Контакты и добавляет новый контакт Thomas Higginson. Позже она снова входит в Gmail под именем +emilyd@gmail.com и отправляет письмо пользователю Thomas Higginson, который автоматически +добавляется в ее контакты. Также она подписана на новости от colonel_tom (аккаунт пользователя Thomas Higginson в Twitter) в +Twitter. +

+

+ В результате этих действий поставщик контактов создает три необработанных контакта: +

+
    +
  1. + Необработанный контакт Thomas Higginson связан с аккаунтом emily.dickinson@gmail.com. + Тип этого аккаунта — Google. +
  2. +
  3. + Второй необработанный контакт Thomas Higginson связан с аккаунтом emilyd@gmail.com. + Тип этого аккаунта — также Google. Второй необработанный контакт создается даже в том случае, +если его имя полностью совпадает с именем предыдущего контакта, поскольку первый +был добавлен для другого аккаунта. +
  4. +
  5. + Третий необработанный контакт Thomas Higginson связан с аккаунтом belle_of_amherst. Тип этого аккаунта +— Twitter. +
  6. +
+

Данные

+

+ Как уже указывалось ранее, данные необработанного контакта +хранятся в строке {@link android.provider.ContactsContract.Data}, которая связана со значением +_ID необработанного контакта. Благодаря этому для одного необработанного контакта может существовать несколько экземпляров +одного и того же типа данных (например, адресов эл. почты или номеров телефонов). Например, если у контакта +Thomas Higginson для аккаунта {@code emilyd@gmail.com} (строка необработанного контакта Thomas Higginson, +связанная с учетной записью Google emilyd@gmail.com) имеется адрес эл. почты +thigg@gmail.com и служебный адрес эл. почты +thomas.higginson@gmail.com, то поставщик контактов сохраняет две строки +адреса эл. почты и связывает их с этим необработанным контактом. +

+

+ Обратите внимание, что в этой таблице хранятся данные разных типов. Строки со сведениями об отображаемом имени, +номере телефона, адресе эл. почты, почтовом адресе, фото и веб-сайте хранятся в таблице +{@link android.provider.ContactsContract.Data}. Для упрощения управления этими данными в таблице +{@link android.provider.ContactsContract.Data} предусмотрены столбцы с описательными именами, +а также другие столбцы с универсальными именами. Содержимое в столбце с описательным именем имеет то же значение, +независимо от типа данных в строке, тогда как содержимое столбца с универсальным именем +может иметь разное значение в зависимости от типа данных. +

+

Описательные имена столбцов

+

+ Вот некоторые примеры столбцов с описательными именами: +

+
+
+ {@link android.provider.ContactsContract.Data#RAW_CONTACT_ID} +
+
+ Значение в столбце _ID необработанного контакта для этих данных. +
+
+ {@link android.provider.ContactsContract.Data#MIMETYPE} +
+
+ Тип данных, хранящихся в этой строке, в виде настраиваемого типа MIME. Поставщик контактов +использует типы MIME, заданные в подклассах класса +{@link android.provider.ContactsContract.CommonDataKinds}. Эти типы MIME являются открытыми, и +их может использовать любое приложение или адаптер синхронизации, поддерживающие работу с поставщиком контактов. +
+
+ {@link android.provider.ContactsContract.DataColumns#IS_PRIMARY} +
+
+ Если этот тип данных для необработанного контакта встречается несколько раз, то +столбец {@link android.provider.ContactsContract.DataColumns#IS_PRIMARY} помечает флагом +строки данных, в которых содержатся основные данные для этого типа. Например, если +пользователь нажал и удерживает номер телефона контакта и выбрал параметр Использовать по умолчанию, +то в столбце {@link android.provider.ContactsContract.Data} с этим номером телефона +в соответствующем столбце {@link android.provider.ContactsContract.DataColumns#IS_PRIMARY} задается +значение, отличное от нуля. +
+
+

Универсальные имена столбцов

+

+ Существует 15 общедоступных столбцов с универсальными именами (DATA1DATA15) +и четыре дополнительных столбца +(SYNC1SYNC4), которые используются только адаптерами +синхронизации. Константы столбцов с универсальными именами применяются всегда, независимо от типа данных, +содержащихся в столбце. +

+

+ Столбец DATA1 является индексируемым. Поставщик контактов всегда использует этот столбец для +данных, которые, как он ожидает, будут наиболее часто являться целевыми в запросах. Например, +в строке с адресами эл. почты в этом столбце указывается фактический адрес эл. почты. +

+

+ Обычно столбец DATA15 зарезервирован для данных больших двоичных объектов +(BLOB), таких как миниатюры фотографий. +

+

Имена столбцов по типам строк

+

+ Для упрощения работы со столбцами определенного типа строк в поставщике контактов +также предусмотрены константы для имен столбцов по типам строк, которые определены в подклассах класса +{@link android.provider.ContactsContract.CommonDataKinds}. Эти константы просто +присваивают одному и тому же имени столбца различные константы, что позволяет вам получать доступ к данным в строке +определенного типа. +

+

+ Например, класс {@link android.provider.ContactsContract.CommonDataKinds.Email} определяет +константы имени столбца для строки {@link android.provider.ContactsContract.Data}, +в которой имеется тип MIME +{@link android.provider.ContactsContract.CommonDataKinds.Email#CONTENT_ITEM_TYPE +Email.CONTENT_ITEM_TYPE}. В этом классе содержится константа +{@link android.provider.ContactsContract.CommonDataKinds.Email#ADDRESS} для столбца адреса +эл. почты. Фактическое значение +{@link android.provider.ContactsContract.CommonDataKinds.Email#ADDRESS} — data1, которое совпадает +с универсальным именем столбца. +

+

+ Внимание! Не добавляйте свои настраиваемые данные в таблицу +{@link android.provider.ContactsContract.Data} с помощью строки, в которой имеется один из +предварительно заданных поставщиком типов MIME. В противном случае вы можете потерять данные или вызвать неполадки +в работе поставщика. Например, не следует добавлять строку с типом MIME +{@link android.provider.ContactsContract.CommonDataKinds.Email#CONTENT_ITEM_TYPE +Email.CONTENT_ITEM_TYPE}, в которой в столбце +DATA1 вместо адреса эл. почты содержится имя пользователя. Если вы укажете в строке собственный настраиваемый тип MIME, вы можете свободно +указывать собственные имена столбцов по типам строк и использовать их так, как пожелаете. +

+

+ На рисунке 2 показано, как столбцы с описательными именами и столбцы данных отображаются в строке +{@link android.provider.ContactsContract.Data}, а также как имена столбцов по типу строк «накладываются» +на универсальные имена столбцов. +

+How type-specific column names map to generic column names +

+ Рисунок 2. Имена столбцов по типам строк и универсальные имена столбцов. +

+

Классы имен столбцов по типам строк

+

+ В таблице 2 перечислены наиболее часто используемые классы имен столбцов по типам строк. +

+

+ Таблица 2. Классы имен столбцов по типам строк

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Класс сопоставленияТип данныхПримечания
{@link android.provider.ContactsContract.CommonDataKinds.StructuredName}Данные об имени необработанного контакта, связанного с этой строкой данных.У необработанного контакта имеется только одна строка такого типа.
{@link android.provider.ContactsContract.CommonDataKinds.Photo}Основная фотография необработанного контакта, связанного с этой строкой данных.У необработанного контакта имеется только одна строка такого типа.
{@link android.provider.ContactsContract.CommonDataKinds.Email}Адрес эл. почты необработанного контакта, связанного с этой строкой данных.У необработанного контакта может быть несколько адресов эл. почты.
{@link android.provider.ContactsContract.CommonDataKinds.StructuredPostal}Почтовый адрес необработанного контакта, связанного с этой строкой данных.У необработанного контакта может быть несколько почтовых адресов.
{@link android.provider.ContactsContract.CommonDataKinds.GroupMembership}Идентификатор, с помощью которого необработанный контакт связан с одной из групп в поставщике контактов. + Группы представляют собой необязательный компонент для типа аккаунта и имени аккаунта. Дополнительные сведения о +группах представлены в разделе Группы контактов. +
+

Контакты

+

+ Поставщик контактов объединяет строки с необработанными контактами для всех типов аккаунтов и имен аккаунтов +для создания контакта. Это позволяет упростить отображение и изменение всех данных, +собранных в отношении пользователя. Поставщик контактов управляет процессом создания строк +контактов и агрегированием необработанных контактов, у которых имеется строка контакта. Ни приложениям, ни +адаптерам синхронизации не разрешается добавлять контакты, а некоторые столбцы в строке контакта доступны только для чтения. +

+

+ Примечание. Если попытаться добавить контакт в поставщик контактов с помощью метода +{@link android.content.ContentResolver#insert(Uri,ContentValues) insert()}, будет выдано исключение +{@link java.lang.UnsupportedOperationException}. Если вы попытаетесь обновить столбец, +доступный только для чтения, это действие будет проигнорировано. +

+

+ При добавлении нового необработанного контакта, +который не соответствует ни одному из существующих контактов, поставщик контактов создает новый контакт. Поставщик поступает аналогично в случае, если +данные в строке существующего необработанного контакта изменяются таким образом, что они больше не соответствуют контакту, +с которым они ранее были связаны. При создании приложением или адаптером синхронизации нового контакта, +который соответствует существующему контакту, то новый контакт объединяется с +существующим контактом. +

+

+ Поставщик контактов связывает строку контакта с его строками необработанного контакта посредством столбца +_ID строки контакта в таблице +{@link android.provider.ContactsContract.Contacts Contacts}. Столбец CONTACT_ID в таблице необработанных контактов +{@link android.provider.ContactsContract.RawContacts} содержит значения _ID для +строки контактов, связанной с каждой строкой необработанных контактов. +

+

+ В таблице {@link android.provider.ContactsContract.Contacts} также имеется столбец +{@code android.provider.ContactsContract.ContactsColumns#LOOKUP_KEY}, который выступает в роли +«постоянной ссылки» на строку контакта. Поскольку поставщик контактов автоматически сохраняет контакты, +в ответ на агрегирование или синхронизацию он может изменить значение {@code android.provider.BaseColumns#_ID} +строки контакта. Даже если это произойдет, URI контента +{@link android.provider.ContactsContract.Contacts#CONTENT_LOOKUP_URI}, объединенный с +{@code android.provider.ContactsContract.ContactsColumns#LOOKUP_KEY} контакта, будет +по-прежнему указывать на строку контакта, поэтому вы можете смело использовать +{@code android.provider.ContactsContract.ContactsColumns#LOOKUP_KEY} +для сохранения ссылок на «избранные» контакты и др. Столбец имеет собственный формат, +который не связан с форматом столбца{@code android.provider.BaseColumns#_ID}. +

+

+ На рисунке 3 показаны взаимосвязи этих трех основных таблиц друг с другом. +

+Contacts provider main tables +

+ Рисунок 3. Взаимосвязи между таблицами контактов, необработанных контактов и сведений. +

+

Данные, полученные от адаптеров синхронизации

+

+ Пользователь вводит данные контактов прямо на устройстве, однако данные также поступают в поставщик контактов +из веб-служб посредством адаптеров синхронизации, что позволяет автоматизировать +обмен данными между устройством и службами в Интернете. Адаптеры синхронизации выполняются в фоновом режиме под +управлением системы, и для управления данными они вызывают методы +{@link android.content.ContentResolver}. +

+

+ В Android веб-служба, с которой работает адаптер синхронизации, определяется по типу аккаунта. + Каждый адаптер синхронизации работает с одним типом аккаунта, однако он может поддерживать несколько имен аккаунтов +такого типа. Вкратце типы и имена аккаунтов рассматриваются +в разделе Источники данных необработанных контактов. Указанные ниже определения позволяют +более точно охарактеризовать связь между типами и именами аккаунтов и адаптерами синхронизации и службами. +

+
+
+ Тип аккаунта +
+
+ Определяет службу, в которой пользователь хранит данные. В большинстве случаев для работы со +службой пользователю необходимо пройти проверку подлинности. Например, Google Контакты представляет собой тип аккаунтов, обозначаемый кодом +google.com. Это значение соответствует типу аккаунта, используемого +{@link android.accounts.AccountManager}. +
+
+ Имя аккаунта +
+
+ Определяет конкретный аккаунт или имя для входа для типа аккаунта. Аккаунты «Контакты Google» +— это то же, что и аккаунты Google, в качестве имени которых используется адрес эл. почты. + В других службах может использоваться имя пользователя, состоящее из одного слова, или числовой идентификатор. +
+
+

+ Типы аккаунтов не обязательно должны быть уникальными. Пользователь может создать несколько аккаунтов Google Контакты +и загрузить данные из них в поставщик контактов; это может произойти в случае, если у пользователя имеется один набор +персональных контактов для личного аккаунта, и другой набор — для служебного аккаунта. Имена +аккаунтов обычно уникальные. Вместе они формируют определенный поток данных между поставщиком контактов +и внешними службами. +

+

+ Если необходимо передать данные из службы в поставщик контактов, необходимо создать +собственный адаптер синхронизации. Дополнительные сведения об этом представлены в разделе +Адаптеры синхронизации поставщика контактов. +

+

+ На рисунке 4 показано, какую роль выполняет поставщик контактов в потоке передачи данных +о пользователях. В области, отмеченной на рисунке как sync adapters, каждый адаптер промаркирован в соответствии с поддерживаемым им типом аккаунтов. +

+Flow of data about people +

+ Рисунок 4. Поток передачи данных через поставщика контактов. +

+

Требуемые разрешения

+

+ Приложения, которым требуется доступ к поставщику контактов, +должны запросить указанные ниже разрешения. +

+
+
Доступ на чтение одной или нескольких таблиц
+
+ {@link android.Manifest.permission#READ_CONTACTS}, указанный в файле +AndroidManifest.xml с элементом + + <uses-permission> в виде +<uses-permission android:name="android.permission.READ_CONTACTS">. +
+
Доступ на запись в одну или несколько таблиц
+
+ {@link android.Manifest.permission#WRITE_CONTACTS}, указанный в файле +AndroidManifest.xml с элементом + + <uses-permission> в виде +<uses-permission android:name="android.permission.WRITE_CONTACTS">. +
+
+

+ Эти разрешения не распространяются на работу с данными профиля пользователя. Профиль пользователя +и требуемые разрешения для работы с ним рассматриваются в разделе +Профиль пользователя ниже. +

+

+ Помните, что данные о контактах пользователя являются личными и конфиденциальными. Пользователи заботятся о своей конфиденциальности, +поэтому не хотят, чтобы приложения собирали данные о них самих или их контактах. + Если пользователю не до конца ясно, для чего требуется разрешение на доступ к данным контактов, они могут присвоить +вашему приложению низкий рейтинг или вообще откажутся его устанавливать. +

+

Профиль пользователя

+

+ В таблице {@link android.provider.ContactsContract.Contacts} имеется всего одна строка, содержащая +данные профиля для устройства пользователя. Эти данные описывают user устройства, +а не один из контактов пользователя. Строка контактов профиля связана со строкой +необработанных контактов в каждой из систем, в которой используется профиль. + В каждой строке необработанного контакта профиля может содержаться несколько строк данных. Константы для доступа к профилю пользователя +представлены в классе {@link android.provider.ContactsContract.Profile}. +

+

+ Для доступа к профилю пользователя требуются особые разрешения. Кроме разрешений +{@link android.Manifest.permission#READ_CONTACTS} и +{@link android.Manifest.permission#WRITE_CONTACTS}, которые требуются для чтения и записи, для доступа к профилю пользователя необходимы разрешения +{@code android.Manifest.permission#READ_PROFILE} и +{@code android.Manifest.permission#WRITE_PROFILE} +на чтение и запись соответственно. +

+

+ Всегда следует помнить, что профиль пользователя представляет собой конфиденциальную информацию. Разрешение +{@code android.Manifest.permission#READ_PROFILE} предоставляет вам доступ к личной информации на устройстве +пользователя. В описании своего приложения обязательно укажите, для +чего вам требуется доступ к профилю пользователя. +

+

+ Чтобы получить строку контакта с профилем пользователя, вызовите метод +{@link android.content.ContentResolver#query(Uri,String[], String, String[], String) +ContentResolver.query()}. Задайте для URI контента значение +{@link android.provider.ContactsContract.Profile#CONTENT_URI} и не указывайте никаких критериев +выборки. Этот URI контента также можно использовать в качестве основного URI для получения +необработанных контактов или данных профиля. Ниже представлен фрагмент кода для получения данных профиля. +

+
+// Sets the columns to retrieve for the user profile
+mProjection = new String[]
+    {
+        Profile._ID,
+        Profile.DISPLAY_NAME_PRIMARY,
+        Profile.LOOKUP_KEY,
+        Profile.PHOTO_THUMBNAIL_URI
+    };
+
+// Retrieves the profile from the Contacts Provider
+mProfileCursor =
+        getContentResolver().query(
+                Profile.CONTENT_URI,
+                mProjection ,
+                null,
+                null,
+                null);
+
+

+ Примечание. Если при извлечении несколько строк контактов необходимо определить, +какой из них является профилем пользователя, обратитесь к столбцу +{@link android.provider.ContactsContract.ContactsColumns#IS_USER_PROFILE} этой строки. Если в этом столбце +указано значение «1», то в это строке находится профиль пользователя. +

+

Метаданные поставщика контактов

+

+ Поставщик контактов управляет данными, которые используются для отслеживания состояния данных контакта в +репозитории. Эти метаданные о репозитории хранятся в различных местах, включая +строки необработанных контактов, данные и строки таблицы контактов, таблицу +{@link android.provider.ContactsContract.Settings} и таблицу +{@link android.provider.ContactsContract.SyncState}. В таблице ниже +указаны значения каждого из этих фрагментов метаданных. +

+

+ Таблица 3. Метаданные в поставщике контактов

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ТаблицаСтолбецЗначенияОписание
{@link android.provider.ContactsContract.RawContacts}{@link android.provider.ContactsContract.SyncColumns#DIRTY}«0» — с момента последней синхронизации изменения не вносились. + Служит для отметки необработанных контактов, которые были изменены на устройстве и которые необходимо снова синхронизировать с +сервером. Значение устанавливается автоматически поставщиком контактов при обновлении строк приложениями +Android. +

+ Адаптеры синхронизации, которые вносят изменения в необработанные контакты или таблицы данных, должны всегда добавлять к используемому ими URI +контента строку +{@link android.provider.ContactsContract#CALLER_IS_SYNCADAPTER}. Это позволяет предотвратить пометку таких строк поставщиком как «грязных». + В противном случае изменения, внесенные адаптером синхронизации, будут рассматриваться как локальные изменения, +которые подлежат отправке на сервер, даже если источником таких изменений был сам сервер. +

+
«1» — с момента последней синхронизации были внесены изменения, которые необходимо отразить и на сервере.
{@link android.provider.ContactsContract.RawContacts}{@link android.provider.ContactsContract.SyncColumns#VERSION}Номер версии этой строки. + Поставщик контактов автоматически увеличивает это значение при каждом +изменении в строке или в связанных с ним данных. +
{@link android.provider.ContactsContract.Data}{@link android.provider.ContactsContract.DataColumns#DATA_VERSION}Номер версии этой строки. + Поставщик контактов автоматически увеличивает это значение при каждом +изменении в строке данных. +
{@link android.provider.ContactsContract.RawContacts}{@link android.provider.ContactsContract.SyncColumns#SOURCE_ID} + Строковое значение, которое служит уникальным идентификатором данного необработанного контакта в аккаунте, +в котором он был создан. + + Когда адаптер синхронизации создает новый необработанный контакт, в этом столбце следует указать +уникальный идентификатор этого необработанного контакта на сервере. Когда же приложение Android создает +новый необработанный контакт, то приложению следует оставить этот столбец пустым. Это служит сигналом для +адаптера синхронизации, чтобы создать новый необработанный контакт на сервере и +получить значение {@link android.provider.ContactsContract.SyncColumns#SOURCE_ID}. +

+ В частности, идентификатор источника должен быть уникальным для каждого типа аккаунта +и неизменным при синхронизации. +

+
    +
  • + Уникальный: у каждого необработанного контакта для аккаунта должен быть свой собственный идентификатор источника. Если +не применить это требование, у вас обязательно возникнут проблемы с приложением «Контакты». + Обратите внимание, что два необработанных контакта одного и того же типа аккаунта могут +иметь одинаковый идентификатор источника. Например, необработанный контакт Thomas Higginson для +аккаунта {@code emily.dickinson@gmail.com} может иметь такой же +идентификатор источника, что и контакт Thomas Higginson для аккаунта +{@code emilyd@gmail.com}. +
  • +
  • + Стабильный: идентификаторы источников представляют собой неизменную часть данных онлайн-службы для +необработанного контакта. Например, если пользователь выполняет повторную синхронизацию после очистки хранилища контактов в +настройках приложения, идентификаторы источников восстановленных необработанных контактов должны быть такими же, +как и прежде. Если не применить это требование, перестанут +работать ярлыки. +
  • +
+
{@link android.provider.ContactsContract.Groups}{@link android.provider.ContactsContract.GroupsColumns#GROUP_VISIBLE}«0» — контакты, представленные в этой группе, не должны отображаться в интерфейсах пользователя приложений Android. + Этот столбец предназначен для сведений о совместимости с серверами, позволяющими пользователям скрывать контакты +в определенных группах. +
«1» — контакты, представленные в этой группе, могут отображаться в интерфейсах пользователя приложений.
{@link android.provider.ContactsContract.Settings} + {@link android.provider.ContactsContract.SettingsColumns#UNGROUPED_VISIBLE} + «0» — для этого аккаунта и аккаунтов этого типа контакты, не принадлежащие к группе, +не отображаются в интерфейсах пользователя приложений Android. + + По умолчанию контакты скрыты, если ни один из их необработанных контактов не принадлежит группе +(принадлежность необработанного контакта к группе указывается в одном или нескольких строках +{@link android.provider.ContactsContract.CommonDataKinds.GroupMembership} +в таблице {@link android.provider.ContactsContract.Data}). + Установив этот флаг в строке таблицы {@link android.provider.ContactsContract.Settings} +для типа аккаунта и имени аккаунта, вы можете принудительно сделать видимыми контакты, не принадлежащие какой-либо группе. + Один из вариантов использования этого флага — отображение контактов с серверов, на которых не используются группы. +
+ «1» — для этого аккаунта и аккаунтов этого типа контакты, не принадлежащие к группе, +отображаются в интерфейсах пользователя приложений Android. +
{@link android.provider.ContactsContract.SyncState}(все) + Эта таблица используется для хранения метаданных для вашего адаптера синхронизации. + + С помощью этой таблицы вы можете на постоянной основе хранить на устройстве сведения о состоянии синхронизации и другие связанные с +синхронизацией данные. +
+

Доступ к поставщику контактов

+

+ В этом разделе рассматриваются инструкции по получению доступа к данным из поставщика контактов, в частности, +следующие: +

+
    +
  • + запросы объектов; +
  • +
  • + пакетное изменение; +
  • +
  • + получение и изменение данных с помощью намерений; +
  • +
  • + целостность данных. +
  • +
+

+ Дополнительные сведения о внесении изменений с помощью адаптеров синхронизации представлены в разделе +Адаптеры синхронизации поставщика контактов. +

+

Запрос объектов

+

+ Таблицы поставщика контактов имеют иерархическую структуру, поэтому зачастую полезно +извлекать строку и все связанные с ней ее дочерние строки. Например, для отображения +всей информации о пользователе вы, возможно, захотите извлечь все строки +{@link android.provider.ContactsContract.RawContacts} для одной строки +{@link android.provider.ContactsContract.Contacts} или все строки +{@link android.provider.ContactsContract.CommonDataKinds.Email} для одной строки +{@link android.provider.ContactsContract.RawContacts}. Чтобы упростить этот процесс, в поставщике контактов имеются +объекты, которые выступают в роли соединителей базы данных между +таблицами. +

+

+ Объект представляет собой подобие таблицы, состоящей из выбранных столбцов родительской таблицы и ее дочерней таблицы. + При запросе объекта вы предоставляете проекцию и критерии поиска на основе доступных в +объекте столбцов. В результате вы получаете объект {@link android.database.Cursor}, в котором +содержится одна строка для каждой извлеченной строки дочерней таблицы. Например, если запросить объект +{@link android.provider.ContactsContract.Contacts.Entity} для имени контакта и все строки +{@link android.provider.ContactsContract.CommonDataKinds.Email} для всех необработанных контактов, +соответствующих этому имени, вы получите объект {@link android.database.Cursor}, содержащий по одной строке +для каждой строки {@link android.provider.ContactsContract.CommonDataKinds.Email}. +

+

+ Использование объектов упрощает запросы. С помощью объекта можно извлечь сразу все данные для +контакта или необработанного контакта, вместо того, чтобы сначала делать запрос к родительской таблице для получения +идентификатора и последующего запроса к дочерней таблице с использованием полученного идентификатора. Кроме того, поставщик контактов обрабатывает запрос +объекта за одну транзакцию, что гарантирует внутреннюю согласованность полученных +данных. +

+

+ Примечание. Объект обычно содержит не все столбцы +родительской и дочерней таблиц. Если вы попытаетесь изменить имя столбца, который отсутствует в списке +констант имени столбца для объекта, вы получите {@link java.lang.Exception}. +

+

+ Ниже представлен пример кода для получения всех строк необработанного контакта для контакта. Это фрагмент +более крупного приложения, в котором имеются две операции: «основная» и «для получения сведений». Основная операция +служит для получения списка строк контактов; при выборе пользователем контакта операция отправляет его идентификатор в операцию +для получения сведений. Операция для получения сведений использует объект {@link android.provider.ContactsContract.Contacts.Entity} +для отображения всех строк данных, полученных ото всех необработанных контактов, которые связаны с выбранным +контактом. +

+

+ Вот фрагмент кода операции «для получения сведений»: +

+
+...
+    /*
+     * Appends the entity path to the URI. In the case of the Contacts Provider, the
+     * expected URI is content://com.google.contacts/#/entity (# is the ID value).
+     */
+    mContactUri = Uri.withAppendedPath(
+            mContactUri,
+            ContactsContract.Contacts.Entity.CONTENT_DIRECTORY);
+
+    // Initializes the loader identified by LOADER_ID.
+    getLoaderManager().initLoader(
+            LOADER_ID,  // The identifier of the loader to initialize
+            null,       // Arguments for the loader (in this case, none)
+            this);      // The context of the activity
+
+    // Creates a new cursor adapter to attach to the list view
+    mCursorAdapter = new SimpleCursorAdapter(
+            this,                        // the context of the activity
+            R.layout.detail_list_item,   // the view item containing the detail widgets
+            mCursor,                     // the backing cursor
+            mFromColumns,                // the columns in the cursor that provide the data
+            mToViews,                    // the views in the view item that display the data
+            0);                          // flags
+
+    // Sets the ListView's backing adapter.
+    mRawContactList.setAdapter(mCursorAdapter);
+...
+@Override
+public Loader<Cursor> onCreateLoader(int id, Bundle args) {
+
+    /*
+     * Sets the columns to retrieve.
+     * RAW_CONTACT_ID is included to identify the raw contact associated with the data row.
+     * DATA1 contains the first column in the data row (usually the most important one).
+     * MIMETYPE indicates the type of data in the data row.
+     */
+    String[] projection =
+        {
+            ContactsContract.Contacts.Entity.RAW_CONTACT_ID,
+            ContactsContract.Contacts.Entity.DATA1,
+            ContactsContract.Contacts.Entity.MIMETYPE
+        };
+
+    /*
+     * Sorts the retrieved cursor by raw contact id, to keep all data rows for a single raw
+     * contact collated together.
+     */
+    String sortOrder =
+            ContactsContract.Contacts.Entity.RAW_CONTACT_ID +
+            " ASC";
+
+    /*
+     * Returns a new CursorLoader. The arguments are similar to
+     * ContentResolver.query(), except for the Context argument, which supplies the location of
+     * the ContentResolver to use.
+     */
+    return new CursorLoader(
+            getApplicationContext(),  // The activity's context
+            mContactUri,              // The entity content URI for a single contact
+            projection,               // The columns to retrieve
+            null,                     // Retrieve all the raw contacts and their data rows.
+            null,                     //
+            sortOrder);               // Sort by the raw contact ID.
+}
+
+

+ По завершении загрузки {@link android.app.LoaderManager} выполняет обратный вызов метода +{@link android.app.LoaderManager.LoaderCallbacks#onLoadFinished(Loader, D) +onLoadFinished()}. Одним их входящих аргументов для этого метода является +{@link android.database.Cursor} с результатом запроса. В своем приложении вы можете +получить данные из этого объекта {@link android.database.Cursor} для его отображения или дальнейшей работы с ним. +

+

Пакетное изменение

+

+ При каждой возможности данные в поставщике контактов следует вставлять, обновлять и удалять в +«пакетном режиме» путем создания {@link java.util.ArrayList} из объектов +{@link android.content.ContentProviderOperation} и вызова метода +{@link android.content.ContentResolver#applyBatch(String, ArrayList) applyBatch()}. Поскольку +поставщик контактов выполняет все операции в методе +{@link android.content.ContentResolver#applyBatch(String, ArrayList) applyBatch()} за одну +транзакцию, ваши изменения всегда будут находиться в рамках репозитория контактов и +всегда будут согласованными. Пакетное изменение также упрощает вставку необработанного контакта одновременно с его подробными +данными. +

+

+ Примечание. Чтобы изменить отдельный необработанный контакт, рекомендуется отправить намерение в +приложение для работы с контактами на устройстве вместо обработки изменения в вашем приложении. +Более подробно эта операция описана в разделе +Получение и изменение данных с помощью намерений. +

+

Пределы

+

+ Пакетное изменение большого количества операций может заблокировать выполнение других процессов, +что может негативно сказаться на работе пользователя с приложением. Чтобы упорядочить все необходимые изменения +в рамках как можно меньшего количества отдельных списков и при этом предотвратить +блокирование работы системы, следует задать пределы для одной или нескольких операций. + Предел представляет собой объект {@link android.content.ContentProviderOperation}, в качестве значения параметра +{@link android.content.ContentProviderOperation#isYieldAllowed()} которого задано +true. При достижении поставщиком контактов предела он приостанавливает свою работу, +чтобы могли выполняться другие процессы, и закрывает текущую транзакцию. Когда поставщик запускается снова, он +выполняет следующую операцию в {@link java.util.ArrayList} и запускает +новую транзакцию. +

+

+ Использование пределов позволяет за один вызов метода +{@link android.content.ContentResolver#applyBatch(String, ArrayList) applyBatch()} выполнять более одной транзакции. По этой причине +вам следует задать предел для последней операции с набором связанных строк. + Например, необходимо задать предел для последней операции в наборе, которая +добавляет строки необработанного контакта и связанные с ним строки данных, или для последней операции с набором строк, связанных +с одним контактом. +

+

+ Пределы также являются единицами атомарных операций. Все операции доступа в промежутке между двумя пределами завершаются либо +успехом, либо сбоем в рамках одной единицы. Если любой из пределов не задан, наименьшей +атомарной операцией считается весь пакет операций. Использование пределов позволяет предотвратить +снижение производительности системы и одновременно обеспечить выполнение набора +операций на атомарном уровне. +

+

Изменение обратных ссылок

+

+ При вставке новой строки необработанного контакта и связанных с ним рядов данных в виде набора объектов +{@link android.content.ContentProviderOperation} вам приходится связывать строки данных +со строкой необработанного контакта путем вставки значения +{@code android.provider.BaseColumns#_ID} необработанного контакта в виде значения +{@link android.provider.ContactsContract.DataColumns#RAW_CONTACT_ID}. Однако это значение +недоступно, когда вы создаете{@link android.content.ContentProviderOperation} +для строки данных, поскольку вы еще не применили +{@link android.content.ContentProviderOperation} для строки необработанного контакта. Чтобы обойти это ограничение, +в классе {@link android.content.ContentProviderOperation.Builder} предусмотрен метод +{@link android.content.ContentProviderOperation.Builder#withValueBackReference(String, int) withValueBackReference()}. + С помощью этого метода можно вставлять или изменять столбец +с результатом предыдущей операции. +

+

+ В методе {@link android.content.ContentProviderOperation.Builder#withValueBackReference(String, int) withValueBackReference()} +предусмотрено два аргумента: +

+
+
+ key +
+
+ Ключ для пары «ключ-значение». Значением этого аргумента должно быть имя столбца +в таблице, которую вы изменяете. +
+
+ previousResult +
+
+ Индекс значения, начинающийся с «0», в массиве объектов +{@link android.content.ContentProviderResult} из метода +{@link android.content.ContentResolver#applyBatch(String, ArrayList) applyBatch()}. По мере +выполнения пакетных операций результат каждой операции сохраняется в +промежуточном массиве результатов. Значением previousResult является индекс +одного из этих результатов, который извлекается и хранится со значением +key. Благодаря этому можно вставить новую запись необработанного контакта и получить обратно его значение +{@code android.provider.BaseColumns#_ID}, а затем создать «обратную ссылку» на +значение при добавлении строки {@link android.provider.ContactsContract.Data}. +

+ Целиком весь результат создается при первом вызове метода +{@link android.content.ContentResolver#applyBatch(String, ArrayList) applyBatch()}. +Размер результата равен размеру {@link java.util.ArrayList} предоставленных вами объектов +{@link android.content.ContentProviderOperation}. Однако для всех +элементов в массиве результатов присваивается значение null, и при попытке +воспользоваться обратной ссылкой на результат еще не выполненной операции метод +{@link android.content.ContentProviderOperation.Builder#withValueBackReference(String, int) withValueBackReference()} +выдает {@link java.lang.Exception}. + +

+
+
+

+ Ниже представлены фрагменты кода для вставки нового необработанного контакта и его данных в пакетном режиме. Они включают +код, который задает предел и использует обратную ссылку. Эти фрагменты +представляют собой расширенную версию метода createContacEntry(), который входит в класс +ContactAdder в примере приложения + + Contact Manager. +

+

+ Первый фрагмент кода служит для извлечения данных контакта из пользовательского интерфейса. На этом этапе пользователь уже выбрал +аккаунт, для которого необходимо добавить новый необработанный контакт. +

+
+// Creates a contact entry from the current UI values, using the currently-selected account.
+protected void createContactEntry() {
+    /*
+     * Gets values from the UI
+     */
+    String name = mContactNameEditText.getText().toString();
+    String phone = mContactPhoneEditText.getText().toString();
+    String email = mContactEmailEditText.getText().toString();
+
+    int phoneType = mContactPhoneTypes.get(
+            mContactPhoneTypeSpinner.getSelectedItemPosition());
+
+    int emailType = mContactEmailTypes.get(
+            mContactEmailTypeSpinner.getSelectedItemPosition());
+
+

+ В следующем фрагменте кода создается операция для вставки строки необработанного контакта в таблицу +{@link android.provider.ContactsContract.RawContacts}: +

+
+    /*
+     * Prepares the batch operation for inserting a new raw contact and its data. Even if
+     * the Contacts Provider does not have any data for this person, you can't add a Contact,
+     * only a raw contact. The Contacts Provider will then add a Contact automatically.
+     */
+
+     // Creates a new array of ContentProviderOperation objects.
+    ArrayList<ContentProviderOperation> ops =
+            new ArrayList<ContentProviderOperation>();
+
+    /*
+     * Creates a new raw contact with its account type (server type) and account name
+     * (user's account). Remember that the display name is not stored in this row, but in a
+     * StructuredName data row. No other data is required.
+     */
+    ContentProviderOperation.Builder op =
+            ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI)
+            .withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, mSelectedAccount.getType())
+            .withValue(ContactsContract.RawContacts.ACCOUNT_NAME, mSelectedAccount.getName());
+
+    // Builds the operation and adds it to the array of operations
+    ops.add(op.build());
+
+

+ Затем код создает строки данных для строк отображаемого имени, телефона и адреса эл. почты. +

+

+ Каждый объект компонента операции использует метод +{@link android.content.ContentProviderOperation.Builder#withValueBackReference(String, int) withValueBackReference()} +для получения +{@link android.provider.ContactsContract.DataColumns#RAW_CONTACT_ID}. Ссылка возвращается +обратно к объекту {@link android.content.ContentProviderResult} из первой операции, +в результате чего добавляется строка необработанного контакта и возвращается его новое значение +{@code android.provider.BaseColumns#_ID}. После этого каждая строка данных автоматически связывается по своему +{@link android.provider.ContactsContract.DataColumns#RAW_CONTACT_ID} +с новой строкой {@link android.provider.ContactsContract.RawContacts}, которой она принадлежит. +

+

+ Объект {@link android.content.ContentProviderOperation.Builder}, который добавляет строку адреса эл. почты, +помечается флагом с помощью метода {@link android.content.ContentProviderOperation.Builder#withYieldAllowed(boolean) +withYieldAllowed()}, который задает предел: +

+
+    // Creates the display name for the new raw contact, as a StructuredName data row.
+    op =
+            ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
+            /*
+             * withValueBackReference sets the value of the first argument to the value of
+             * the ContentProviderResult indexed by the second argument. In this particular
+             * call, the raw contact ID column of the StructuredName data row is set to the
+             * value of the result returned by the first operation, which is the one that
+             * actually adds the raw contact row.
+             */
+            .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
+
+            // Sets the data row's MIME type to StructuredName
+            .withValue(ContactsContract.Data.MIMETYPE,
+                    ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE)
+
+            // Sets the data row's display name to the name in the UI.
+            .withValue(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, name);
+
+    // Builds the operation and adds it to the array of operations
+    ops.add(op.build());
+
+    // Inserts the specified phone number and type as a Phone data row
+    op =
+            ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
+            /*
+             * Sets the value of the raw contact id column to the new raw contact ID returned
+             * by the first operation in the batch.
+             */
+            .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
+
+            // Sets the data row's MIME type to Phone
+            .withValue(ContactsContract.Data.MIMETYPE,
+                    ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE)
+
+            // Sets the phone number and type
+            .withValue(ContactsContract.CommonDataKinds.Phone.NUMBER, phone)
+            .withValue(ContactsContract.CommonDataKinds.Phone.TYPE, phoneType);
+
+    // Builds the operation and adds it to the array of operations
+    ops.add(op.build());
+
+    // Inserts the specified email and type as a Phone data row
+    op =
+            ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
+            /*
+             * Sets the value of the raw contact id column to the new raw contact ID returned
+             * by the first operation in the batch.
+             */
+            .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
+
+            // Sets the data row's MIME type to Email
+            .withValue(ContactsContract.Data.MIMETYPE,
+                    ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE)
+
+            // Sets the email address and type
+            .withValue(ContactsContract.CommonDataKinds.Email.ADDRESS, email)
+            .withValue(ContactsContract.CommonDataKinds.Email.TYPE, emailType);
+
+    /*
+     * Demonstrates a yield point. At the end of this insert, the batch operation's thread
+     * will yield priority to other threads. Use after every set of operations that affect a
+     * single contact, to avoid degrading performance.
+     */
+    op.withYieldAllowed(true);
+
+    // Builds the operation and adds it to the array of operations
+    ops.add(op.build());
+
+

+ В последнем фрагменте кода представлен вызов метода +{@link android.content.ContentResolver#applyBatch(String, ArrayList) applyBatch()} +для вставки нового необработанного контакта и его строк данных. +

+
+    // Ask the Contacts Provider to create a new contact
+    Log.d(TAG,"Selected account: " + mSelectedAccount.getName() + " (" +
+            mSelectedAccount.getType() + ")");
+    Log.d(TAG,"Creating contact: " + name);
+
+    /*
+     * Applies the array of ContentProviderOperation objects in batch. The results are
+     * discarded.
+     */
+    try {
+
+            getContentResolver().applyBatch(ContactsContract.AUTHORITY, ops);
+    } catch (Exception e) {
+
+            // Display a warning
+            Context ctx = getApplicationContext();
+
+            CharSequence txt = getString(R.string.contactCreationFailure);
+            int duration = Toast.LENGTH_SHORT;
+            Toast toast = Toast.makeText(ctx, txt, duration);
+            toast.show();
+
+            // Log exception
+            Log.e(TAG, "Exception encountered while inserting contact: " + e);
+    }
+}
+
+

+ С помощью пакетных операций можно также реализоватьоптимистическое управление параллелизмом. +Это метод применения транзакций изменения без необходимости блокировать базовый репозиторий. + Чтобы воспользоваться этим методом, необходимо применить транзакцию и проверить наличие других изменений, +которые могли быть внесены в это же время. Если обнаружится, что имеется несогласованное изменение, +транзакцию можно откатить и выполнить повторно. +

+

+ Оптимистическое управление параллелизмом подходит для мобильных устройств, где с системой одновременно взаимодействует только один пользователь, + а одновременный доступ к репозиторию данных нескольких пользователей — довольно редкое явление. Поскольку не применяется блокировка, +экономится ценное время, которое требуется на установку блокировок или ожидания того, когда другие транзакции отменят свои блокировки. +

+

+ Ниже представлен порядок использования оптимистического управления параллелизмом при обновлении одной строки +{@link android.provider.ContactsContract.RawContacts}. +

+
    +
  1. + Извлеките столбец {@link android.provider.ContactsContract.SyncColumns#VERSION} +необработанного контакта, а также другие данные, которые необходимо извлечь. +
  2. +
  3. + Создайте объект {@link android.content.ContentProviderOperation.Builder}, подходящий для +применения ограничения, с помощью метода +{@link android.content.ContentProviderOperation#newAssertQuery(Uri)}. Для URI контента +используйте {@link android.provider.ContactsContract.RawContacts#CONTENT_URI +RawContacts.CONTENT_URI}, +добавив к нему {@code android.provider.BaseColumns#_ID} необработанного контакта. +
  4. +
  5. + Для объекта {@link android.content.ContentProviderOperation.Builder} вызовите метод +{@link android.content.ContentProviderOperation.Builder#withValue(String, Object) +withValue()}, чтобы сравнить столбец {@link android.provider.ContactsContract.SyncColumns#VERSION} +с номером версии, которую вы только что получили. +
  6. +
  7. + Для того же объекта {@link android.content.ContentProviderOperation.Builder} вызовите метод +{@link android.content.ContentProviderOperation.Builder#withExpectedCount(int) +withExpectedCount()}, чтобы убедиться в том, что проверочное утверждение проверяет только одну строка. +
  8. +
  9. + Вызовите метод {@link android.content.ContentProviderOperation.Builder#build()}, чтобы создать объект +{@link android.content.ContentProviderOperation}, затем добавьте этот объект в качестве первого объекта в +{@link java.util.ArrayList}, который вы передаете в метод +{@link android.content.ContentResolver#applyBatch(String, ArrayList) applyBatch()}. +
  10. +
  11. + Примените пакетную транзакцию. +
  12. +
+

+ Если в промежутке между считыванием строки и попыткой ее изменения строка необработанного контакта была обновлена другой +операцией, assert {@link android.content.ContentProviderOperation} +завершится сбоем и в выполнении всего пакета операций будет отказано. Можно выбрать повторное выполнение +пакета или выполнить другое действие. +

+

+ В примере кода ниже демонстрируется, как создать assert +{@link android.content.ContentProviderOperation} после запроса одного необработанного контакта с помощью +{@link android.content.CursorLoader}. +

+
+/*
+ * The application uses CursorLoader to query the raw contacts table. The system calls this method
+ * when the load is finished.
+ */
+public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
+
+    // Gets the raw contact's _ID and VERSION values
+    mRawContactID = cursor.getLong(cursor.getColumnIndex(BaseColumns._ID));
+    mVersion = cursor.getInt(cursor.getColumnIndex(SyncColumns.VERSION));
+}
+
+...
+
+// Sets up a Uri for the assert operation
+Uri rawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, mRawContactID);
+
+// Creates a builder for the assert operation
+ContentProviderOperation.Builder assertOp = ContentProviderOperation.netAssertQuery(rawContactUri);
+
+// Adds the assertions to the assert operation: checks the version and count of rows tested
+assertOp.withValue(SyncColumns.VERSION, mVersion);
+assertOp.withExpectedCount(1);
+
+// Creates an ArrayList to hold the ContentProviderOperation objects
+ArrayList ops = new ArrayList<ContentProviderOperationg>;
+
+ops.add(assertOp.build());
+
+// You would add the rest of your batch operations to "ops" here
+
+...
+
+// Applies the batch. If the assert fails, an Exception is thrown
+try
+    {
+        ContentProviderResult[] results =
+                getContentResolver().applyBatch(AUTHORITY, ops);
+
+    } catch (OperationApplicationException e) {
+
+        // Actions you want to take if the assert operation fails go here
+    }
+
+

Получение и изменение данных с помощью намерений

+

+ С помощью отправки намерения в приложение для работы с контактами, которое имеется на устройстве, можно в обход получить доступ +к поставщику контактов. Намерение запускает пользовательский интерфейс приложения на устройстве, посредством которого пользователь +может работать с контактами. Такой тип доступа позволяет пользователю выполнять следующие действия: +

    +
  • выбор контактов из списка и их возврат в приложение для дальнейшей работы;
  • +
  • изменение данных существующего контакта;
  • +
  • вставка нового необработанного контакта для любых других аккаунтов;
  • +
  • удаление контакта или его данных.
  • +
+

+ Если пользователь вставляет или обновляет данные , вы можете сначала собрать эти данные, а затем отправить их вместе +с намерением. +

+

+ При использовании намерений для доступа к поставщику контактов посредством приложения для работы с контактами, имеющегося на устройстве, вам +не нужно создавать собственный пользовательский интерфейс или код для доступа к поставщику. Также вам +не нужно запрашивать разрешение на чтение или запись в поставщик. Приложение для работы с контактами, имеющееся на устройстве, может +делегировать вам разрешение на чтение контакта, и поскольку вы вносите изменения +в поставщик через другое приложение, вам не нужно разрешение на запись. +

+

+ Более подробно общий процесс отправки намерения для получения доступа к поставщику описан в руководстве +Основные сведения о поставщике контента +в разделе «Доступ к данным посредством намерений». В таблице 4 ниже представлены сводные сведения об операциях, +типе MIME и значениях данных, которые используются для доступных задач. Значения +дополнительных данных, которые можно использовать для +{@link android.content.Intent#putExtra(String, String) putExtra()}, указаны в справочной +документации по {@link android.provider.ContactsContract.Intents.Insert}. +

+

+ Таблица 4. Намерения в поставщике контактов. +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ЗадачаДействиеДанныеТип MIMEПримечания
Выбор контакта из списка{@link android.content.Intent#ACTION_PICK} + Одно из следующего: +
    +
  • +{@link android.provider.ContactsContract.Contacts#CONTENT_URI Contacts.CONTENT_URI} +(отображение списка контактов); +
  • +
  • +{@link android.provider.ContactsContract.CommonDataKinds.Phone#CONTENT_URI Phone.CONTENT_URI} +(отображение номеров телефонов для необработанного контакта); +
  • +
  • +{@link android.provider.ContactsContract.CommonDataKinds.StructuredPostal#CONTENT_URI +StructuredPostal.CONTENT_URI} +(отображение списка почтовых адресов для необработанного контакта); +
  • +
  • +{@link android.provider.ContactsContract.CommonDataKinds.Email#CONTENT_URI Email.CONTENT_URI} +(отображение адресов эл. почты для необработанного контакта). +
  • +
+
+ Не используется + + Отображение списка необработанных контактов или списка данных необработанного контакта (в зависимости от +предоставленного URI контента). +

+ Вызовите метод +{@link android.app.Activity#startActivityForResult(Intent, int) startActivityForResult()}, +который возвращает URI контента для выбранной строки. URI представлен в форме +URI контента таблицы, к которому добавлен LOOKUP_ID строки. + Приложение для работы с контактами, установленное на устройстве, делегирует этому URI контента +разрешения на чтение и запись, которые действуют в течение всего жизненного цикла вашей операции. Дополнительные сведения представлены в статье +Основные +сведения о поставщике контента. +

+
Вставка нового необработанного контакта{@link android.provider.ContactsContract.Intents.Insert#ACTION Insert.ACTION}Н/Д + {@link android.provider.ContactsContract.RawContacts#CONTENT_TYPE +RawContacts.CONTENT_TYPE}, тип MIME для набора необработанных контактов. + + Отображение экрана Добавить контакт в приложении для работы с контактами, которое установлено на устройстве. Отображаются +значения дополнительных данных, добавленных вами в намерение. При отправке с помощью метода +{@link android.app.Activity#startActivityForResult(Intent, int) startActivityForResult()} +URI контента нового добавленного необработанного контакта передается обратно в метод обратного вызова +{@link android.app.Activity#onActivityResult(int, int, Intent) onActivityResult()} +вашей операции в аргументе {@link android.content.Intent} в поле +data. Чтобы получить значение, вызовите метод {@link android.content.Intent#getData()}. +
Изменение контакта{@link android.content.Intent#ACTION_EDIT} + {@link android.provider.ContactsContract.Contacts#CONTENT_LOOKUP_URI} +контакта. Операция редактора разрешит пользователю изменить любые данные, связанные с +этим контактом. + + {@link android.provider.ContactsContract.Contacts#CONTENT_ITEM_TYPE +Contacts.CONTENT_ITEM_TYPE}, один контакт. + Отображение экрана редактирования контакта в приложении для работы с контактами. Отображаются +значения дополнительных данных, добавленных вами в намерение. Когда пользователь нажимает на кнопку Готово для сохранения +внесенных изменений, ваша операция возвращается на передний план. +
Отображение средства выбора, в котором также можно добавлять данные{@link android.content.Intent#ACTION_INSERT_OR_EDIT} + Н/Д + + {@link android.provider.ContactsContract.Contacts#CONTENT_ITEM_TYPE} + + Это намерение также отображает экран средства выбора приложения для работы с контактами. Пользователь +может выбрать контакт для изменения или добавить новый контакт. В зависимости от выбора отображается экран редактирования или добавления контакта, +и отображаются дополнительные данные, +переданные вами в намерение. Если ваше приложение отображает такие данные контакта, как адрес эл. почты или номер телефона, +воспользуйтесь этим намерением, чтобы разрешить пользователю добавлять данные к существующему +контакту. +

+ Примечание. Вам не нужно отправлять значение имени в дополнительные данные этого намерения, +поскольку пользователь всегда выбирает существующее имя или добавляет новое. Кроме того, +если отправить имя, а пользователь решит внести изменения, приложение для работы с контактами +отобразит отправленное вами имя, перезаписав предыдущее значение. Если пользователь +не заметит этого и сохранит изменения, старое значение будет утеряно. +

+
+

+ Приложение для работы с контактами, имеющееся на устройстве, не разрешает вам удалять необработанные контакты или любые его данные с +помощью намерений. Вместо этого для обработки необработанного контакта используйте метод +{@link android.content.ContentResolver#delete(Uri, String, String[]) ContentResolver.delete()} +или {@link android.content.ContentProviderOperation#newDelete(Uri) +ContentProviderOperation.newDelete()}. +

+

+ Ниже представлен фрагмент кода для создания и отправки намерения, который вставляет новый +необработанный контакт и его данные. +

+
+// Gets values from the UI
+String name = mContactNameEditText.getText().toString();
+String phone = mContactPhoneEditText.getText().toString();
+String email = mContactEmailEditText.getText().toString();
+
+String company = mCompanyName.getText().toString();
+String jobtitle = mJobTitle.getText().toString();
+
+// Creates a new intent for sending to the device's contacts application
+Intent insertIntent = new Intent(ContactsContract.Intents.Insert.ACTION);
+
+// Sets the MIME type to the one expected by the insertion activity
+insertIntent.setType(ContactsContract.RawContacts.CONTENT_TYPE);
+
+// Sets the new contact name
+insertIntent.putExtra(ContactsContract.Intents.Insert.NAME, name);
+
+// Sets the new company and job title
+insertIntent.putExtra(ContactsContract.Intents.Insert.COMPANY, company);
+insertIntent.putExtra(ContactsContract.Intents.Insert.JOB_TITLE, jobtitle);
+
+/*
+ * Demonstrates adding data rows as an array list associated with the DATA key
+ */
+
+// Defines an array list to contain the ContentValues objects for each row
+ArrayList<ContentValues> contactData = new ArrayList<ContentValues>();
+
+
+/*
+ * Defines the raw contact row
+ */
+
+// Sets up the row as a ContentValues object
+ContentValues rawContactRow = new ContentValues();
+
+// Adds the account type and name to the row
+rawContactRow.put(ContactsContract.RawContacts.ACCOUNT_TYPE, mSelectedAccount.getType());
+rawContactRow.put(ContactsContract.RawContacts.ACCOUNT_NAME, mSelectedAccount.getName());
+
+// Adds the row to the array
+contactData.add(rawContactRow);
+
+/*
+ * Sets up the phone number data row
+ */
+
+// Sets up the row as a ContentValues object
+ContentValues phoneRow = new ContentValues();
+
+// Specifies the MIME type for this data row (all data rows must be marked by their type)
+phoneRow.put(
+        ContactsContract.Data.MIMETYPE,
+        ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE
+);
+
+// Adds the phone number and its type to the row
+phoneRow.put(ContactsContract.CommonDataKinds.Phone.NUMBER, phone);
+
+// Adds the row to the array
+contactData.add(phoneRow);
+
+/*
+ * Sets up the email data row
+ */
+
+// Sets up the row as a ContentValues object
+ContentValues emailRow = new ContentValues();
+
+// Specifies the MIME type for this data row (all data rows must be marked by their type)
+emailRow.put(
+        ContactsContract.Data.MIMETYPE,
+        ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE
+);
+
+// Adds the email address and its type to the row
+emailRow.put(ContactsContract.CommonDataKinds.Email.ADDRESS, email);
+
+// Adds the row to the array
+contactData.add(emailRow);
+
+/*
+ * Adds the array to the intent's extras. It must be a parcelable object in order to
+ * travel between processes. The device's contacts app expects its key to be
+ * Intents.Insert.DATA
+ */
+insertIntent.putParcelableArrayListExtra(ContactsContract.Intents.Insert.DATA, contactData);
+
+// Send out the intent to start the device's contacts app in its add contact activity.
+startActivity(insertIntent);
+
+

Целостность данных

+

+ Поскольку в репозитории контактов содержится важная и конфиденциальная информация, которая, как ожидает пользователь, верна и +актуальна, в поставщике контактов предусмотрены четко определенные правила для обеспечения целостности данных. При изменении данных контактов вы обязаны +соблюдать эти правила. Вот наиболее +важные из этих правил: +

+
+
+ Всегда добавляйте строку {@link android.provider.ContactsContract.CommonDataKinds.StructuredName} +к каждой добавляемой вами строке {@link android.provider.ContactsContract.RawContacts}. +
+
+ Если в таблице {@link android.provider.ContactsContract.Data} имеется строка {@link android.provider.ContactsContract.RawContacts} +без строки {@link android.provider.ContactsContract.CommonDataKinds.StructuredName}, +то могут возникнуть +проблемы во время агрегирования. +
+
+ Всегда связывайте новые строки {@link android.provider.ContactsContract.Data} с их +родительскими строками {@link android.provider.ContactsContract.RawContacts}. +
+
+ Строка {@link android.provider.ContactsContract.Data}, которая не связана +с {@link android.provider.ContactsContract.RawContacts}, не будет отображаться в +приложении для работы с контактами, имеющемся на устройстве, а также может привести к проблемам с адаптерами синхронизации. +
+
+ Следует изменять данные только тех необработанных контактов, которыми вы владеете. +
+
+ Помните о том, что поставщик контактов обычно управляет данными из +нескольких аккаунтов различных типов или служб в Интернете. Необходимо убедиться в том, что ваше приложение изменяет +или удаляет данные только для тех строк, которые принадлежат вам, а также в том, что оно вставляет данные с использованием только тех типов и имени аккаунта, +которыми вы управляете. +
+
+ Всегда используйте +для центров, URI контента, путей URI, имен столбцов, типов MIME и значений +{@link android.provider.ContactsContract.CommonDataKinds.CommonColumns#TYPE} только те константы, которые определены в классе {@link android.provider.ContactsContract} и его подклассах. +
+
+ Это позволит избежать ошибок. Если какая либо константа устарела, компилятор сообщит об этом +с помощью соответствующего предупреждения. +
+
+

Настраиваемые строки данных

+

+ Создав и используя собственные типы MIME, вы можете вставлять, изменять, удалять и извлекать +в таблице{@link android.provider.ContactsContract.Data} собственные строки данных. Ваши строки +ограничены использованием столбца, который определен в +{@link android.provider.ContactsContract.DataColumns}, однако вы можете сопоставить ваши собственные имена столбцов по типам строк +с именами столбцов по умолчанию. Данные для ваших строк отображаются в приложении для работы с контактами, которое имеется на устройстве, +однако их не удастся изменить или удалить, а также пользователи не смогут +добавить дополнительные данные. Чтобы разрешить пользователям изменять ваши настраиваемые строки данных, необходимо реализовать в вашем приложении операцию +редактора. +

+

+ Для отображения настраиваемых данных укажите файл contacts.xml, содержащий элемент +<ContactsAccountType> и один или несколько его +<ContactsDataKind> дочерних элементов. Дополнительные сведения об этом представлены в разделе +<ContactsDataKind> element. +

+

+ Дополнительные сведения о настраиваемых типах MIME представлены в руководстве +Создание +поставщика контента. +

+

Адаптеры синхронизации поставщика контактов

+

+ Поставщик контактов разработан специально для обработки операций синхронизации +данных контактов между устройством и службой в Интернете. Благодаря этому пользователи +могут загружать существующие данные на новое устройство и отправлять их в новый аккаунт. + Синхронизация также обеспечивает предоставление пользователю всегда актуальных сведений, независимо от +источника их добавления и внесенных в них изменений. Еще одно преимущество синхронизации заключается в том, +что данные контактов доступны даже в том случае, если устройство не подключено к сети. +

+

+ Несмотря на множество различных способов реализации синхронизации данных, в системе Android имеется +подключаемая платформа синхронизации, которая позволяет автоматизировать выполнение следующих задач: +

    + +
  • + проверка доступности сети; +
  • +
  • + планирование и выполнение синхронизации на основе предпочтений пользователя; +
  • +
  • + перезапуск остановленных процессов синхронизации. +
  • +
+

+ Для использования этой платформы вы предоставляете подключаемый модуль адаптера синхронизации. Каждый адаптер синхронизации является уникальным +для службы и поставщика контента, однако способен работать с несколькими аккаунтами в одной службе. В платформе +также предусмотрена возможность использовать несколько адаптеров синхронизации для одной и той же службы или поставщика. +

+

Классы и файлы адаптера синхронизации

+

+ Адаптер синхронизации реализуется как подкласс класса +{@link android.content.AbstractThreadedSyncAdapter} и устанавливается в составе приложения +Android. Система узнает о наличии адаптера синхронизации из элементов в манифесте +вашего приложения, а также из особого файла XML, на который имеется указание в манифесте. В файле XML определяются +тип аккаунта в онлайн-службе и центр поставщика контента, которые вместе служат уникальными +идентификаторами адаптера. Адаптер синхронизации находится в неактивном состоянии до тех пор, пока пользователь не добавит +тип аккаунта и не включит синхронизацию +с поставщиком контента. На этом этапе система вступает в управление адаптером +, при необходимости вызывая его для синхронизации данных между поставщиком контента и сервером. +

+

+ Примечание. Использование типа аккаунта для идентификации адаптера синхронизации +позволяет системе обнаруживать и группировать адаптеры синхронизации, которые обращаются к разным службам +из одной и той же организации. Например, у всех адаптеров синхронизации для онлайн-служб Google +один и тот же тип аккаунта — com.google. Когда пользователи добавляют на свои устройства аккаунт Google, все +установленные адаптеры синхронизации группируются вместе; каждый из адаптеров +синхронизируется только с отдельным поставщиком контента на устройстве. +

+

+ Поскольку в большинстве служб пользователям сначала необходимо подтвердить свою подлинность, прежде чем они смогут получить доступ к данным, +система Android предлагает платформу аутентификации, которая аналогична +платформе адаптера синхронизации и зачастую используется совместно с ней. Платформа аутентификации +использует подключаемые структуры проверки подлинности, которые представляют собой подклассы класса +{@link android.accounts.AbstractAccountAuthenticator}. Такая структура проверяет +подлинность пользователя следующим образом: +

    +
  1. + Сначала собираются сведения об имени пользователя и его пароле или аналогичная информация (учетные данные +пользователя). +
  2. +
  3. + Затем учетные данные оправляются в службу. +
  4. +
  5. + Наконец, изучается ответ, полученный от службы. +
  6. +
+

+ Если служба приняла учетные данные, структура проверки подлинности может +сохранить их для использования в дальнейшем. Благодаря использованию структур проверки подлинности +{@link android.accounts.AccountManager} может предоставить доступ к любым маркерам аутентификации, которые +поддерживает структура проверки подлинности и которые она решает предоставить, например, к маркерам аутентификации OAuth2. +

+

+ Несмотря на то что аутентификация не требуется, она используется большинством служб для работы с контактами. + Тем не менее, вам не обязательно использовать платформу аутентификации Android для проверки подлинности. +

+

Реализация адаптера синхронизации

+

+ Чтобы реализовать адаптер синхронизации для поставщика контактов, начните с создания +приложения Android, которое содержит следующие компоненты. +

+
+
+ Компонент {@link android.app.Service}, который отвечает на запросы системы +для привязки к адаптеру синхронизации. +
+
+ Когда системе требуется запустить синхронизацию, она вызывает метод +{@link android.app.Service#onBind(Intent) onBind()} службы, чтобы получить объект +{@link android.os.IBinder} для адаптера синхронизации. Благодаря этому система +может вызывать методы адаптера между процессами. +

+ В +примере адаптера синхронизации именем класса этой службы является +com.example.android.samplesync.syncadapter.SyncService. +

+
+
+ Адаптер синхронизации, фактически реализованный как конкретный подкласс +класса {@link android.content.AbstractThreadedSyncAdapter}. +
+
+ Этот класс не подходит для загрузки данных с сервера, отправки данных +с устройства и разрешения конфликтов. Основную свою работу адаптер +выполняет в методе {@link android.content.AbstractThreadedSyncAdapter#onPerformSync( +Account, Bundle, String, ContentProviderClient, SyncResult) +onPerformSync()}. Этот класс допускает создание только одного экземпляра. +

+ В +примере адаптера синхронизации адаптер определяется в классе +com.example.android.samplesync.syncadapter.SyncAdapter. +

+
+
+ Подкласс класса {@link android.app.Application}. +
+
+ Этот класс выступает в роли фабрики для единственного экземпляра адаптера синхронизации. Воспользуйтесь методом +{@link android.app.Application#onCreate()}, чтобы создать экземпляр адаптера синхронизации, а затем +предоставьте статический метод get, чтобы возвратить единственный экземпляр в метод +{@link android.app.Service#onBind(Intent) onBind()} службы +адаптера синхронизации. +
+
+ Необязательно: компонент {@link android.app.Service}, который отвечает на запросы +системы для аутентификации пользователей. +
+
+ {@link android.accounts.AccountManager} запускает службу, чтобы начать процесс +аутентификации. Метод {@link android.app.Service#onCreate()} службы создает экземпляр +объекта структуры проверки подлинности. Когда системе требуется запустить аутентификацию аккаунта пользователя для +адаптера синхронизации приложения, она вызывает метод +{@link android.app.Service#onBind(Intent) onBind()} службы, чтобы получить объект +{@link android.os.IBinder} для структуры проверки подлинности. Благодаря этому система +может вызывать методы структуры проверки подлинности между процессами. +

+ В +примере адаптера синхронизации именем класса этой службы является +com.example.android.samplesync.authenticator.AuthenticationService. +

+
+
+ Необязательно: конкретный подкласс класса +{@link android.accounts.AbstractAccountAuthenticator}, который обрабатывает запросы на +аутентификацию. +
+
+ В этом классе имеются методы, которые {@link android.accounts.AccountManager} вызывает +для проверки подлинности учетных данных пользователя на сервере. Подробности процесса +аутентификации значительно различаются в зависимости от технологии, используемой на сервере. Чтобы узнать подробнее об аутентификации, +обратитесь к соответствующей документации к программному обеспечению используемого сервера. +

+ В +примере адаптера синхронизации структура проверки подлинности определяется в классе +com.example.android.samplesync.authenticator.Authenticator. +

+
+
+ Файлы XML, в которых определяются адаптер синхронизация и структура проверки подлинности для системы. +
+
+ Описанные ранее компоненты службы адаптера синхронизации и структуры проверки подлинности определяются +в элементах +<service> +в манифесте приложения. Эти элементы +включают дочерние элементы +<meta-data> +, в которых имеются определенные данные для +системы. +
    +
  • + Элемент +<meta-data> +для службы адаптера синхронизации указывает на файл +XML res/xml/syncadapter.xml. В свою очередь, в этом файле задается +URI веб-службы для синхронизации с поставщиком контактов, +а также тип аккаунта для этой веб-службы. +
  • +
  • + Необязательно: элемент +<meta-data> +для структуры проверки подлинности указывает на файл XML +res/xml/authenticator.xml. В свою очередь, в этом файле задается +тип аккаунта, который поддерживает структура проверки подлинности, а также ресурсы пользовательского интерфейса, +которые отображаются в процессе аутентификации. Тип аккаунта, указанный в этом +элементе, должен совпадать с типом аккаунта, который задан для +адаптера синхронизации. +
  • +
+
+
+

Потоки данных из социальных сетей

+

+ Для управления входящими данными из социальных сетей используются таблицы {@code android.provider.ContactsContract.StreamItems} +и +{@code android.provider.ContactsContract.StreamItemPhotos}. Можно создать адаптер синхронизации, который добавляет поток данных +из вашей собственной сети в эти таблицы, либо вы можете считывать поток данных из этих таблиц и отображать +их в собственном приложении. Можно также реализовать оба этих способа. С помощью этих функций вы можете интегрировать службы социальных сетей +в компоненты Android для работы с социальными сетями. +

+

Текст из потока данных из социальных сетей

+

+ Элементы потока всегда ассоциируются с необработанным контактом. Идентификатор +{@code android.provider.ContactsContract.StreamItemsColumns#RAW_CONTACT_ID} связывается со значением +_ID необработанного контакта. Тип аккаунта и его имя для необработанного +контакта также хранятся в строке элемента потока. +

+

+ Данные из потока следует хранить в следующих столбцах: +

+
+
+ {@code android.provider.ContactsContract.StreamItemsColumns#ACCOUNT_TYPE} +
+
+ Обязательный. Тип аккаунта пользователя для необработанного контакта, связанного с +этим элементом потока. Не забудьте задать это значение при вставке элемента потока. +
+
+ {@code android.provider.ContactsContract.StreamItemsColumns#ACCOUNT_NAME} +
+
+ Обязательный. Имя аккаунта пользователя для необработанного контакта, связанного с +этим элементом потока. Не забудьте задать это значение при вставке элемента потока. +
+
+ Столбцы идентификатора +
+
+ Обязательный. При вставке элемента потока необходимо вставить +указанные ниже столбцы идентификатора. +
    +
  • + {@code android.provider.ContactsContract.StreamItemsColumns#CONTACT_ID}: значение +{@code android.provider.BaseColumns#_ID} для контакта, с которым ассоциирован +этот элемент потока. +
  • +
  • + {@code android.provider.ContactsContract.StreamItemsColumns#CONTACT_LOOKUP_KEY}: значение +{@code android.provider.ContactsContract.ContactsColumns#LOOKUP_KEY} для контакта, с которым ассоциирован +этот элемент потока. +
  • +
  • + {@code android.provider.ContactsContract.StreamItemsColumns#RAW_CONTACT_ID}: значение +{@code android.provider.BaseColumns#_ID} для необработанного контакта, с которым ассоциирован +этот элемент потока. +
  • +
+
+
+ {@code android.provider.ContactsContract.StreamItemsColumns#COMMENTS} +
+
+ Необязательный. В нем хранится сводная информация, которую можно отобразить в начале элемента потока. +
+
+ {@code android.provider.ContactsContract.StreamItemsColumns#TEXT} +
+
+ Текст элемента потока: либо контент, опубликованный источником элемента, +либо описание некоторого действия, сгенерировавшего элемент потока. В этом столбце могут содержаться +любое форматирование и встроенные изображения ресурсов, рендеринг которых можно выполнить с помощью метода +{@link android.text.Html#fromHtml(String) fromHtml()}. Поставщик может обрезать слишком длинный контент +или заменить его часть многоточием, однако он попытается избежать нарушения тегов. +
+
+ {@code android.provider.ContactsContract.StreamItemsColumns#TIMESTAMP} +
+
+ Текстовая строка с информацией о времени вставки или обновления элемента в +миллисекундах от начала отсчета времени. Обслуживанием этого столбца занимаются приложения, которые вставляют или +обновляют элементы потока; поставщик контактов не выполняет +это автоматически. +
+
+

+ Для отображения идентификационной информации для элементов потока воспользуйтесь +{@code android.provider.ContactsContract.StreamItemsColumns#RES_ICON}, +{@code android.provider.ContactsContract.StreamItemsColumns#RES_LABEL} и +{@code android.provider.ContactsContract.StreamItemsColumns#RES_PACKAGE} для связывания с ресурсами +в вашем приложении. +

+

+ В таблице {@code android.provider.ContactsContract.StreamItems} также имеются столбцы +{@code android.provider.ContactsContract.StreamItemsColumns#SYNC1}—{@code android.provider.ContactsContract.StreamItemsColumns#SYNC4}, +которые предназначены исключительно для +адаптеров синхронизации. +

+

Фотографии из потока данных из социальных сетей

+

+ Фотографии, связанные с элементом потока, хранятся в таблице +{@code android.provider.ContactsContract.StreamItemPhotos}. Столбец +{@code android.provider.ContactsContract.StreamItemPhotosColumns#STREAM_ITEM_ID} +в этой таблице связан со столбцом {@code android.provider.BaseColumns#_ID} +в таблице {@code android.provider.ContactsContract.StreamItems}. Ссылки на фотографии хранятся в следующих столбцах +таблицы: +

+
+
+ Столбец {@code android.provider.ContactsContract.StreamItemPhotos#PHOTO} (объект BLOB). +
+
+ Представление фотографии в двоичном формате и с измененным поставщиком размером для ее хранения и отображения. + Этот столбец доступен для обеспечения обратной совместимости с предыдущими версиями поставщика +контактов, которые использовались для хранения фотографий. Однако в текущей версии +поставщика мы не рекомендуем использовать этот столбец для хранения фотографий. Вместо этого воспользуйтесь столбцом +{@code android.provider.ContactsContract.StreamItemPhotosColumns#PHOTO_FILE_ID} или +{@code android.provider.ContactsContract.StreamItemPhotosColumns#PHOTO_URI} (или обоими +столбцами, как описано далее) для хранения фотографий в файле. В этом +столбце теперь хранятся миниатюры фотографий, доступных для чтения. +
+
+ {@code android.provider.ContactsContract.StreamItemPhotosColumns#PHOTO_FILE_ID} +
+
+ Числовой идентификатор фотографии для необработанного контакта. Добавьте это значение к константе +{@link android.provider.ContactsContract.DisplayPhoto#CONTENT_URI DisplayPhoto.CONTENT_URI}, +чтобы получить URI контента для одного файла фотографии, а затем вызовите метод +{@link android.content.ContentResolver#openAssetFileDescriptor(Uri, String) +openAssetFileDescriptor()}, чтобы получить средство обработки файла фотографии. +
+
+ {@code android.provider.ContactsContract.StreamItemPhotosColumns#PHOTO_URI} +
+
+ URI контента, указывающий на файл фотографии, для фотографии, которая представлена этой строкой. + Вызовите метод {@link android.content.ContentResolver#openAssetFileDescriptor(Uri, String) +openAssetFileDescriptor()}, передав в него этот URI, чтобы получить средство обработки файла фотографии. +
+
+

Использование таблиц из потока данных из социальных сетей

+

+ Эти таблицы работают аналогично другим основным таблицам в поставщике контактов, за исключением указанных ниже моментов. +

+
    +
  • + Для работы с этими таблицами требуются дополнительные разрешения на доступ. Для чтения данных из них вашему приложению +должно быть предоставлено разрешение {@code android.Manifest.permission#READ_SOCIAL_STREAM}. Для +изменения таблиц ваше приложение должно иметь разрешение +{@code android.Manifest.permission#WRITE_SOCIAL_STREAM}. +
  • +
  • + Для таблицы {@code android.provider.ContactsContract.StreamItems} существует ограничение на количество строк, +которое можно хранить для каждого необработанного контакта. При достижении этого ограничения +поставщик контактов освобождает место для новых строк элементов потока путем автоматического удаления +строк со самой старой меткой +{@code android.provider.ContactsContract.StreamItemsColumns#TIMESTAMP}. Чтобы получить это ограничение, +запросите URI контента +{@code android.provider.ContactsContract.StreamItems#CONTENT_LIMIT_URI}. Для всех аргументов, +отличных от URI контента, можно оставить значение null. Запрос возвращает +объект Cursor, в котором содержится одна строка с одним столбцом +{@code android.provider.ContactsContract.StreamItems#MAX_ITEMS}. +
  • +
+ +

+ Класс {@code android.provider.ContactsContract.StreamItems.StreamItemPhotos} определяет +дочернюю таблицу объектов {@code android.provider.ContactsContract.StreamItemPhotos}, в которой содержатся +строки для одного элемента потока. +

+

Взаимодействие с потоками данных из социальных сетей

+

+ Управление потоком данных из социальных сетей осуществляется поставщиком контактов совместно с приложением для управления контактами, имеющимся на устройстве. +Такой подход позволяет организовать эффективное использование данных из социальных сетей +с данными о существующих контактах. Доступны указанные ниже функции. +

+
    +
  • + Организовав синхронизацию данных из социальной службы с поставщиком контактов посредством +адаптера синхронизации, вы можете получать данные о недавней активности контактов пользователя и хранить такие данные в таблицах +,{@code android.provider.ContactsContract.StreamItems} +и {@code android.provider.ContactsContract.StreamItemPhotos} для использования в дальнейшем. +
  • +
  • + Помимо регулярной синхронизации, адаптер синхронизации можно настроить на получение +дополнительных данных при выборе пользователем контакта для просмотра. Благодаря этому ваш адаптер синхронизации +может получать фотографии высокого разрешения и самые актуальные элементы потока для контакта. +
  • +
  • + Регистрируя уведомление в приложении для работы с контактами и в поставщике +контактов, вы можетеполучать намерения при просмотре контакта и обновлять на этом этапе +данные о состоянии контакта из вашей службы. Такой подход может обеспечить большее быстродействие и меньший объем +использования полосы пропускания, чем выполнение полной синхронизации с помощью адаптера синхронизации. +
  • +
  • + Пользователи могут добавить контакт в вашу службу социальной сети, обратившись к контакту +в приложении для работы с контактами, которое имеется на устройстве. Это реализуется с помощью функции «пригласить контакт», +для включения которой используется сочетание операции, +которая добавляет существующий контакт в вашу сеть, и файла XML, в котором представлены сведения о вашем приложении для поставщика контактов и приложения для работы с +контактами. +
  • +
+

+ Регулярная синхронизация элементов потока с помощью поставщика контактов выполняется так же, +как и любая другая синхронизация. Дополнительные сведения о синхронизации представлены в разделе +Адаптеры синхронизации поставщика контактов. Регистрация уведомлений +и приглашение контактов рассматриваются в следующих двух разделах. +

+

Регистрация для обработки просмотров контактов в социальных сетях

+

+ Чтобы зарегистрировать адаптер синхронизации для получения уведомлений о просмотрах пользователями контакта, +управление которым осуществляется вашим адаптером синхронизации, выполните указанные ниже действия. +

+
    +
  1. + В каталоге res/xml/ своего проекта создайте файл +contacts.xml. Если у вас уже есть этот файл, переходите к следующему действию. +
  2. +
  3. + В этом файле добавьте элемент +<ContactsAccountType xmlns:android="http://schemas.android.com/apk/res/android">. + Если этот элемент уже существует, можете переходить к следующему действию. +
  4. +
  5. + Чтобы зарегистрировать службу, которой отправляется уведомление при открытии пользователем страницы со сведениями о контакте +в приложении для работы с контактами, которое имеется на устройстве, добавьте атрибут +viewContactNotifyService="serviceclass" к элементу, где +serviceclass — это полное имя класса службы, +которая должна получить намерение из приложения для работы с контактами. Для службы-уведомителя +используйте класс, который является расширением класса {@link android.app.IntentService}, чтобы разрешить службе +получать намерения. Данные во входящем намерении содержат URI контента необработанного +контакта, выбранного пользователем. В службе-уведомителе можно привязать адаптер синхронизации, а затем вызвать его +для обновления данных для необработанного контакта. +
  6. +
+

+ Чтобы зарегистрировать операцию, которую следует вызвать при выборе пользователем элемента потока или фотографии (или обоих элементов), выполните указанные ниже действия. +

+
    +
  1. + В каталоге res/xml/ своего проекта создайте файл +contacts.xml. Если у вас уже есть этот файл, переходите к следующему действию. +
  2. +
  3. + В этом файле добавьте элемент +<ContactsAccountType xmlns:android="http://schemas.android.com/apk/res/android">. + Если этот элемент уже существует, можете переходить к следующему действию. +
  4. +
  5. + Чтобы зарегистрировать одну из ваших операций для обработки выбора пользователем элемента потока +в приложении для работы с контактами, которое имеется на устройстве, добавьте атрибут +viewStreamItemActivity="activityclass" к элементу, где +activityclass — это полное имя класса операции, +которая должна получить намерение из приложения для работы с контактами. +
  6. +
  7. + Чтобы зарегистрировать одну из ваших операций для обработки выбора пользователем фотографии в потоке +в приложении для работы с контактами, которое имеется на устройстве, добавьте атрибут +viewStreamItemPhotoActivity="activityclass" к элементу, где +activityclass — это полное имя класса операции, +которая должна получить намерение из приложения для работы с контактами. +
  8. +
+

+ Дополнительные сведения об элементе <ContactsAccountType> представлены в разделе +элемент <ContactsAccountType>. +

+

+ Данные во входящем намерении содержат URI контента элемента или фотографии, выбранных пользователем. + Чтобы использовать разные операции для текстовых элементов и фотографий, используйте оба атрибута в одном файле. +

+

Взаимодействие со службой социальной сети

+

+ Пользователям не обязательно выходить из приложения для работы с контактами, которое имеется на устройстве, чтобы пригласить контакт на сайт +социальной сети. Вместо этого приложение для работы с контактами может отправить намерение для приглашения +контакта в одну из ваших операций. Для этого выполните указанные ниже действия. +

+
    +
  1. + В каталоге res/xml/ своего проекта создайте файл +contacts.xml. Если у вас уже есть этот файл, переходите к следующему действию. +
  2. +
  3. + В этом файле добавьте элемент +<ContactsAccountType xmlns:android="http://schemas.android.com/apk/res/android">. + Если этот элемент уже существует, можете переходить к следующему действию. +
  4. +
  5. + Добавьте следующие атрибуты: +
      +
    • inviteContactActivity="activityclass";
    • +
    • + inviteContactActionLabel="@string/invite_action_label". +
    • +
    + Значение activityclass представляет собой полное имя класса операции, +которая должна получить намерение. Значениеinvite_action_label +— это текстовая строка, которая отображается в меню Добавить подключение +в приложении для работы с контактами. +
  6. +
+

+ Примечание. ContactsSource — это устаревшее имя тега для +ContactsAccountType, которое больше не используется. +

+

Ссылка contacts.xml

+

+ В файле contacts.xml содержатся элементы XML, которые управляют взаимодействием вашего +адаптера синхронизации и вашего приложения с поставщиком контактов и приложением для работы с контактами. Эти +элементы описаны в следующих разделах. +

+

Элемент <ContactsAccountType>

+

+ Элемент <ContactsAccountType> управляет взаимодействием вашего +приложения с приложением для работы с контактами. Ниже представлен его синтаксис. +

+
+<ContactsAccountType
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        inviteContactActivity="activity_name"
+        inviteContactActionLabel="invite_command_text"
+        viewContactNotifyService="view_notify_service"
+        viewGroupActivity="group_view_activity"
+        viewGroupActionLabel="group_action_text"
+        viewStreamItemActivity="viewstream_activity_name"
+        viewStreamItemPhotoActivity="viewphotostream_activity_name">
+
+

+ находится в: +

+

+ res/xml/contacts.xml +

+

+ может содержать: +

+

+ <ContactsDataKind> +

+

+ Описание +

+

+ Этот элемент объявляет компоненты и элементы пользовательского интерфейса, с помощью которых пользователи могут приглашать свои контакты +в социальную сеть, уведомлять пользователей при обновлении одного из их потоков данных из социальных сетей и +др. +

+

+ Обратите внимание, что префикс атрибута android: необязательно использовать для атрибутов +<ContactsAccountType>. +

+

+ Атрибуты +

+
+
{@code inviteContactActivity}
+
+ Полное имя класса операции в вашем приложении, которую необходимо активировать +при выборе пользователем элемента Добавить подключение в приложении +для работы с контактами, которое имеется на устройстве. +
+
{@code inviteContactActionLabel}
+
+ Текстовая строка, которая отображается для операции, заданной в +{@code inviteContactActivity}, в меню Добавить подключение. + Например, можно указать фразу «Следите за новостями в моей сети». Для этого элемента можно использовать +идентификатор строкового ресурса. +
+
{@code viewContactNotifyService}
+
+ Полное имя класса службы в вашем приложении, которая должна получать +уведомления при просмотре контакта пользователем. Такое уведомление отправляется приложением для работы с контактами, +которое имеется на устройстве; благодаря этому ваше приложение может отложить выполнение операций, требующих обработки большого объема данных, до тех пор +, пока это не потребуется. Например, ваше приложение может реагировать на такое уведомление +путем считывания и отображения фотографии контакта в высоком разрешении и самых актуальных +элементов потока данных из социальной сети. Дополнительные сведения об этой функции представлены в разделе +Взаимодействие с потоками данных из социальных сетей. Пример +службы уведомлений представлен в файле +NotifierService.java в +образце приложенияSampleSyncAdapter. +
+
{@code viewGroupActivity}
+
+ Полное имя класса операции в вашем приложении, которая может отобразить +информацию о группе. При нажатии пользователем на метку группы в приложении для работы с данными, +которое имеется на устройстве, отображается пользовательский интерфейс для этой операции. +
+
{@code viewGroupActionLabel}
+
+ Метка, отображаемая приложением для работы с контактами для элемента пользовательского интерфейса, с помощью которой +пользователь может просмотреть группы в вашем приложении. +

+ Например, если вы установили приложение Google+ на ваше устройство и выполняете синхронизацию +данных в Google+ с приложением для работы с контактами, то круги Google+ будут обозначены в приложении для работы с контактами как +группы на вкладке Группы. При нажатии на на круг +Google+ участники крга отобразятся как группа контактов. В верхней части экрана +находится значок Google+; если нажать на него, управление перейдет в приложение +Google+. В приложении для управления контактами это реализовано с помощью +{@code viewGroupActivity}, в которой значок Google+ используется в качестве значения +{@code viewGroupActionLabel}. +

+

+ Для этого атрибута можно использовать идентификатор строкового ресурса. +

+
+
{@code viewStreamItemActivity}
+
+ Полное имя класса операции в вашем приложении, которую запускает +приложение для работы с контактами, когда пользователь выбирает элемент потока для необработанного контакта. +
+
{@code viewStreamItemPhotoActivity}
+
+ Полное имя класса операции в вашем приложении, которую запускает +приложение для работы с контактами, когда пользователь выбирает фотографию в элементе +потока для необработанного контакта. +
+
+

Элемент <ContactsDataKind>

+

+ Элемент <ContactsDataKind> управляет отображением настраиваемых строк данных вашего +приложения в интерфейсе приложения для работы с контактами, которое имеется на устройстве. Ниже представлен его синтаксис. +

+
+<ContactsDataKind
+        android:mimeType="MIMEtype"
+        android:icon="icon_resources"
+        android:summaryColumn="column_name"
+        android:detailColumn="column_name">
+
+

+ находится в: +

+<ContactsAccountType> +

+ Описание +

+

+ Используйте этот элемент для отображения содержимого настраиваемой строки данных в приложении для работы с контактами как части +сведений о необработанном контакте. Каждый дочерний элемент <ContactsDataKind> +элемента <ContactsAccountType> представляет собой тип настраиваемой строки данных, который +адаптер синхронизации добавляет в таблицу {@link android.provider.ContactsContract.Data}. Для каждого используемого вами настраиваемого типа MIME необходимо добавить один элемент +<ContactsDataKind>. Вам не нужно добавлять элемент +, если у вас имеется настраиваемая строка данных, для которой не требуется отображать данные. +

+

+ Атрибуты +

+
+
{@code android:mimeType}
+
+ Определенные вами настраиваемые типы MIME для одного из ваших типов настраиваемых строк данных в таблице +{@link android.provider.ContactsContract.Data}. Например, значение +vnd.android.cursor.item/vnd.example.locationstatus может быть настраиваемым +типом MIME для строки данных, в которой находятся записи о последнем известном местоположении контакта. +
+
{@code android:icon}
+
+ Графический ресурс +Android, +который приложение для работы с контактами отображает рядом с вашими данными. Используйте его для обозначения того, +что эти данные получены из вашей службы. +
+
{@code android:summaryColumn}
+
+ Имя столбца для первого из двух значений, полученных из строки данных. Значение +отображается в виде первой строки записи в этой строке данных. Первая строка +предназначена для использования в качестве сводных данных, однако она необязательна. См. также +android:detailColumn. +
+
{@code android:detailColumn}
+
+ Имя столбца для второго из двух значений, полученных из строки данных. Значение +отображается в виде второй строки записи в этой строке данных. См. также +{@code android:summaryColumn}. +
+
+

Дополнительные возможности поставщика контактов

+

+ Помимо основных функций, описанных разделах выше, в поставщике +контактов предусмотрены указанные ниже полезные функции для работы с данными контактов. +

+
    +
  • Группы контактов
  • +
  • Функции работы с фотографиями
  • +
+

Группы контактов

+

+ Поставщик контактов может дополнительно отметить коллекции связанных контактов с данными о +группе. Если серверу, который связан с учетной записью пользователя, +требуется сохранить группы, адаптеру синхронизации для типа этого аккаунта следует передать +данные о группах из поставщика контактов на сервер. При добавлении пользователем нового контакта на сервер +и последующем помещении этого контакта в новую группу адаптер синхронизации должен добавить эту новую группу в таблицу +{@link android.provider.ContactsContract.Groups}. Группа или группы, в которые входит необработанный контакт, +хранятся в таблице {@link android.provider.ContactsContract.Data} с использованием типа MIME +{@link android.provider.ContactsContract.CommonDataKinds.GroupMembership}. +

+

+ Если необходимо создать адаптер синхронизации, который будет добавлять данные необработанного контакта с сервера +в поставщик контактов, а вы не используете группы, то вам необходимо указать для поставщика, +чтобы он сделал ваши данные видимыми. В коде, который выполняется при добавлении пользователем +аккаунта на устройство, обновите строку {@link android.provider.ContactsContract.Settings}, +которую поставщик контактов добавляет для этого аккаунта. В этой строке укажите в столбце +{@link android.provider.ContactsContract.SettingsColumns#UNGROUPED_VISIBLE +Settings.UNGROUPED_VISIBLE} значение «1». После этого поставщик контактов всегда будет +делать ваши данные видимыми, даже если вы не используете группы. +

+

Фотографии контактов

+

+ В таблице {@link android.provider.ContactsContract.Data} хранятся фотографии в виде строк +{@link android.provider.ContactsContract.CommonDataKinds.Photo#CONTENT_ITEM_TYPE +Photo.CONTENT_ITEM_TYPE} типа MIME. Столбец +{@link android.provider.ContactsContract.RawContactsColumns#CONTACT_ID} в строке связан со столбцом +{@code android.provider.BaseColumns#_ID} необработанного контакта, которому он принадлежит. + Класс {@link android.provider.ContactsContract.Contacts.Photo} определяет вложенную таблицу +{@link android.provider.ContactsContract.Contacts}, в которой содержится информация об основной фотографии +контакта (которая является основной фотографией основного необработанного контакта этого контакта). Аналогичным образом класс +{@link android.provider.ContactsContract.RawContacts.DisplayPhoto} определяет вложенную таблицу +{@link android.provider.ContactsContract.RawContacts}, в которой содержится информация об основной фотографии +необработанного контакта. +

+

+ В справочной документации по {@link android.provider.ContactsContract.Contacts.Photo} и +{@link android.provider.ContactsContract.RawContacts.DisplayPhoto} содержатся примеры +получения информации о фотографии. К сожалению, отсутствует класс для удобного извлечения миниатюры +основной фотографии необработанного контакта, однако вы можете отправить запрос в таблицу +{@link android.provider.ContactsContract.Data}, выбрать +{@code android.provider.BaseColumns#_ID} необработанного контакта, +{@link android.provider.ContactsContract.CommonDataKinds.Photo#CONTENT_ITEM_TYPE +Photo.CONTENT_ITEM_TYPE} и столбец {@link android.provider.ContactsContract.Data#IS_PRIMARY}, +чтобы найти строку основной фотографии необработанного контакта. +

+

+ Потоки данных из социальных сетей также могут включать фотографии. Они находятся в таблице +{@code android.provider.ContactsContract.StreamItemPhotos}, дополнительные сведения о которой представлены в разделе +Фотографии из потока данных из социальных сетей. +

diff --git a/docs/html-intl/intl/ru/guide/topics/providers/content-provider-basics.jd b/docs/html-intl/intl/ru/guide/topics/providers/content-provider-basics.jd new file mode 100644 index 0000000000000000000000000000000000000000..c912dbc037523bfda5cd1f845c9128dff93bd900 --- /dev/null +++ b/docs/html-intl/intl/ru/guide/topics/providers/content-provider-basics.jd @@ -0,0 +1,1196 @@ +page.title=Основные сведения о поставщике контента +@jd:body + + + +

+ Поставщик контента управляет доступом к центральному репозиторию данных. Поставщик +является компонентом приложения Android, который зачастую имеет собственный пользовательский интерфейс для +работы с данными. Однако поставщики контента предназначены в первую очередь для использования другими приложениями, +которые получают доступ к поставщику посредством клиентского объекта поставщика. Вместе поставщики +и клиенты поставщиков обеспечивают согласованный, стандартный интерфейс к данным, который также обрабатывает +взаимодействие между процессами и обеспечивает защищенный доступ к данным. +

+

+ В этой статье рассматриваются основные сведения, касающиеся следующих тем: +

+
    +
  • принцип работы поставщика контента;
  • +
  • API, используемый для получения данных от поставщика контента;
  • +
  • API, используемый для вставки данных в поставщик контента и их обновления или удаления в нем;
  • +
  • другие функции API, которые упрощают работу с поставщиками.
  • +
+ + +

Обзор

+

+ Поставщик контента предоставляет данные внешним приложениям в виде одной или нескольких таблиц, +аналогичных таблицам в реляционной базе данных. Строка представляет собой экземпляр некоторого типа +собираемых поставщиком данных, а каждый столбец в этой строке — это отдельный элемент данных, +собранных для экземпляра. +

+

+ Примером встроенного поставщика в платформе Android может служить пользовательский словарь, +в котором хранятся данные о написании нестандартных слов, добавленных пользователем. В таблице 1 показано, +как данные могут выглядеть в этой таблице поставщика. +

+

+ Таблица 1. Пример таблицы пользовательского словаря. +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
wordapp idfrequencylocale_ID
mapreduceuser1100en_US1
precompileruser14200fr_FR2
appletuser2225fr_CA3
constuser1255pt_BR4
intuser5100en_UK5
+

+ В каждой строке таблицы 1 представлен экземпляр слова, которое отсутствует +в стандартном словаре. В каждом ее столбце содержатся некоторые данные для слова, +например, данные о языке, на котором это слово было впервые использовано. Заголовки столбцов представляют собой имена столбцов, которые хранятся +в поставщике. Чтобы узнать язык строки, необходимо обратиться к столбцу locale. В этом +поставщике столбец _ID выступает в роли «основного ключа», +который поставщик автоматически сохраняет. +

+

+ Примечание. В поставщике необязательно должен быть основной ключ, а также ему необязательно +использовать_ID в качестве имени столбца основного ключа, если таковой имеется. Однако, если +необходимо привязать данные из поставщика к классу {@link android.widget.ListView}, +один из столбцов должен именоваться _ID. Дополнительные сведения об этом требовании представлены в разделе +Отображение результатов запроса. +

+

Доступ к поставщику

+

+ Для доступа приложения к данным из поставщика контента +используется клиентский объект {@link android.content.ContentResolver}. В этом объекте имеются методы, которые вызывают +идентичные методы в объекте поставщика, который представляет собой экземпляр одного из конкретных +подклассов класса {@link android.content.ContentProvider}. В этих методах +{@link android.content.ContentResolver} представлены основные функции + CRUD (аббревиатура create, retrieve, update, delete [создание, получение, обновление и удаление]) постоянного хранилища. +

+

+ Объект {@link android.content.ContentResolver} в процессе клиентского приложения +и объект {@link android.content.ContentProvider} в приложении, +которое владеет поставщиком, автоматически обрабатывают взаимодействие между процессами. +Объект {@link android.content.ContentProvider} также выступает в роли уровня абстракции между +репозиторием данных и внешним представлением данных в виде таблиц. +

+

+ Примечание. Для доступа к поставщику ваше приложение обычно должно запросить определенные разрешения +в своем файле манифеста. Дополнительные сведения об этом представлены в разделе +Разрешения поставщика контента. +

+

+ Например, чтобы получить из поставщика пользовательского словаря список слов и языков, на которых они представлены, +вызовите метод {@link android.content.ContentResolver#query ContentResolver.query()}. + В свою очередь, метод {@link android.content.ContentResolver#query query()} вызывает метод +{@link android.content.ContentProvider#query ContentProvider.query()}, определенный поставщиком +пользовательского словаря. В примере кода ниже показан вызов метода +{@link android.content.ContentResolver#query ContentResolver.query()}. +

+

+// Queries the user dictionary and returns results
+mCursor = getContentResolver().query(
+    UserDictionary.Words.CONTENT_URI,   // The content URI of the words table
+    mProjection,                        // The columns to return for each row
+    mSelectionClause                    // Selection criteria
+    mSelectionArgs,                     // Selection criteria
+    mSortOrder);                        // The sort order for the returned rows
+
+

+ В таблице 2 указано соответствие аргументов для метода +{@link android.content.ContentResolver#query +query(Uri,projection,selection,selectionArgs,sortOrder)} SQL-инструкции SELECT. +

+

+ Таблица 2. Сравнение метода query() и SQL-запроса. +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Аргумент метода query()Параметр/ключевое слово SELECTПримечания
UriFROM table_nameUri соответствует таблице table_name в поставщике.
projectioncol,col,col,... + projection представляет собой массив столбцов, которые следует включить +в каждую полученную строку. +
selectionWHERE col = valueselection задает критерии для выбора строк.
selectionArgs + (Точный эквивалент отсутствует. В предложении выбора заполнители ? +заменяются аргументами выбора). +
sortOrderORDER BY col,col,... + sortOrder задает порядок отображения строк в возвращаемом объекте +{@link android.database.Cursor}. +
+

URI контента

+

+ URI контента представляет собой URI, который определяет данные в поставщике. URI контента +могут включать символическое имя всего поставщика (его центр) и +имя, которое указывает на таблицу (путь). При вызове +клиентского метода для доступа к таблице в поставщике URI контента этой таблицы выступает в роли одного +из аргументов этого метода. +

+

+ Константа +{@link android.provider.UserDictionary.Words#CONTENT_URI} в предыдущих строках кода содержит URI контента +таблицы words в пользовательском словаре. Объект{@link android.content.ContentResolver} +анализирует центр URI и использует его для «разрешения» поставщика +путем сравнения центра с системной таблицей известных поставщиков. {@link android.content.ContentResolver} +может отправить аргументы запроса в соответствующий +поставщик. +

+

+ {@link android.content.ContentProvider} использует часть URI контента, в которой указан путь, для выбора таблицы +для доступа. В поставщике обычно имеется путь для каждой предоставляемой им таблицы. +

+

+ В предыдущих строках кода полный URI для таблицы words выглядит следующим образом: +

+
+content://user_dictionary/words
+
+

+ Строка user_dictionary ֪– это центр поставщика, а строка +words — это путь к таблице. Строка +content:// (схема) присутствует всегда; +она определяет, что это URI контента. +

+

+ Многие поставщики предоставляют доступ к одной строке в таблице путем добавления идентификатора +в конец URI. Например, чтобы извлечь из пользовательского словаря строку, в столбце _ID которой +указано 4, можно воспользоваться следующим URI контента: +

+
+Uri singleUri = ContentUris.withAppendedId(UserDictionary.Words.CONTENT_URI,4);
+
+

+ Идентификаторы часто используются в случае, когда вы извлекли набор строк и хотите обновить или удалить +одну из них. +

+

+ Примечание. В классах {@link android.net.Uri} и {@link android.net.Uri.Builder} +имеются методы для удобного создания правильно оформленных объектов URI из строк. {@link android.content.ContentUris} +содержит методы для удобного добавления идентификаторов +к URI. В примере кода выше для добавления идентификатора к URI контента UserDictionary используется метод {@link android.content.ContentUris#withAppendedId +withAppendedId()}. +

+ + + +

Получение данных от поставщика

+

+ В это разделе рассматривается порядок получения данных от поставщика на примере +поставщика пользовательского словаря. +

+

+ Для полной ясности в примерах кода, приведенных в этом разделе, методы +{@link android.content.ContentResolver#query ContentResolver.query()} вызываются в потоке пользовательского интерфейса. В реальном +коде запросы следует выполнять асинхронно в отдельном потоке. Одним из способов реализовать +это является использование класса {@link android.content.CursorLoader}, который более подробно описан в +статье +Загрузчики. Кроме того, в этой статье представлены лишь фрагменты кода; они не представляют собой готовое +приложение. +

+

+ Чтобы получить данные из поставщика, выполните указанные ниже основные действия. +

+
    +
  1. + Запросите у поставщика разрешение на чтение. +
  2. +
  3. + Определите код, который отвечает за отправку запроса поставщику. +
  4. +
+

Запрос разрешения на чтение

+

+ Чтобы ваше приложение могло получать данные от поставщика, приложению требуется получить от поставщика разрешение +на чтение. Это разрешение невозможно получить во время выполнения; вместо этого вам необходимо указать, что вам требуется +такое разрешение, в манифесте приложения. Для этого воспользуйтесь элементом +<uses-permission> +и укажите точное название разрешения, +определенное поставщиком. Указав этот элемент в манифесте, вы тем самым запрашиваете +необходимое разрешение для вашего приложения. Когда пользователи устанавливают ваше приложение, они косвенно +получают разрешение по этому запросу. +

+

+ Чтобы узнать точное название разрешения на чтение в используемом поставщике, +а также названия других используемых в нем разрешений на чтение, обратитесь +к документации поставщика. +

+

+ Дополнительные сведения о роли разрешений в получении доступа к поставщику представлены в разделе +Разрешения поставщика контента. +

+

+ Поставщик пользовательского словаря задает разрешение +android.permission.READ_USER_DICTIONARY в своем файле манифеста, +поэтому приложению, которому требуется выполнить чтение данных из поставщика, необходимо запросить именно это разрешение. +

+ +

Создание запроса

+

+ Следующим этапом получения данных от поставщика является создание запроса. В следующем фрагменте кода +задаются некоторые переменные для доступа к поставщику пользовательского словаря: +

+
+
+// A "projection" defines the columns that will be returned for each row
+String[] mProjection =
+{
+    UserDictionary.Words._ID,    // Contract class constant for the _ID column name
+    UserDictionary.Words.WORD,   // Contract class constant for the word column name
+    UserDictionary.Words.LOCALE  // Contract class constant for the locale column name
+};
+
+// Defines a string to contain the selection clause
+String mSelectionClause = null;
+
+// Initializes an array to contain selection arguments
+String[] mSelectionArgs = {""};
+
+
+

+ В следующем фрагменте кода демонстрируется порядок использования метода +{@link android.content.ContentResolver#query ContentResolver.query()} (в качестве примера выступает +поставщик пользовательского словаря): Клиентский запрос поставщика аналогичен SQL-запросу. В нем содержится +набор столбцов, которые возвращаются, набор критериев выборки и порядок сортировки. +

+

+ Набор столбцов, которые должен возвратить запрос, называется проекцией +(переменная mProjection). +

+

+ Выражение, которое задает строки для получения, состоит из предложения выбора +и аргументов выбора. Предложение выбора представляет собой сочетание логических выражений, +имен столбцов и значений (переменная mSelectionClause). Если вместо значения указать подставляемый параметр +?, метод запроса извлекает значение из массива аргументов выбора (переменная +mSelectionArgs). +

+

+ В следующем фрагменте кода, если пользователь не указал слово, то для предложения выбора задается значение +null, а запрос возвращает все слова, имеющиеся в поставщике. Если пользователь указал слово, то для предложения выбора задается значение +UserDictionary.Words.WORD + " = ?", +а для первого элемента в массиве аргументов выбора задается введенное пользователем слово. +

+
+/*
+ * This defines a one-element String array to contain the selection argument.
+ */
+String[] mSelectionArgs = {""};
+
+// Gets a word from the UI
+mSearchString = mSearchWord.getText().toString();
+
+// Remember to insert code here to check for invalid or malicious input.
+
+// If the word is the empty string, gets everything
+if (TextUtils.isEmpty(mSearchString)) {
+    // Setting the selection clause to null will return all words
+    mSelectionClause = null;
+    mSelectionArgs[0] = "";
+
+} else {
+    // Constructs a selection clause that matches the word that the user entered.
+    mSelectionClause = UserDictionary.Words.WORD + " = ?";
+
+    // Moves the user's input string to the selection arguments.
+    mSelectionArgs[0] = mSearchString;
+
+}
+
+// Does a query against the table and returns a Cursor object
+mCursor = getContentResolver().query(
+    UserDictionary.Words.CONTENT_URI,  // The content URI of the words table
+    mProjection,                       // The columns to return for each row
+    mSelectionClause                   // Either null, or the word the user entered
+    mSelectionArgs,                    // Either empty, or the string the user entered
+    mSortOrder);                       // The sort order for the returned rows
+
+// Some providers return null if an error occurs, others throw an exception
+if (null == mCursor) {
+    /*
+     * Insert code here to handle the error. Be sure not to use the cursor! You may want to
+     * call android.util.Log.e() to log this error.
+     *
+     */
+// If the Cursor is empty, the provider found no matches
+} else if (mCursor.getCount() < 1) {
+
+    /*
+     * Insert code here to notify the user that the search was unsuccessful. This isn't necessarily
+     * an error. You may want to offer the user the option to insert a new row, or re-type the
+     * search term.
+     */
+
+} else {
+    // Insert code here to do something with the results
+
+}
+
+

+ Этот запрос аналогичен следующей инструкции SQL: +

+
+SELECT _ID, word, locale FROM words WHERE word = <userinput> ORDER BY word ASC;
+
+

+ В этой инструкции SQL вместо констант класса-контракта используются фактические имена столбцов. +

+

Защита от ввода вредоносного кода

+

+ Если данные, которыми управляет поставщик контента, находятся в базе данных SQL, то включение в необработанные инструкции +SQL внешних ненадежных данных может привести к атаке путем внедрения кода SQL. +

+

+ Рассмотрим следующее предложение выбора: +

+
+// Constructs a selection clause by concatenating the user's input to the column name
+String mSelectionClause =  "var = " + mUserInput;
+
+

+ Если вы используете это предложение, вы разрешаете пользователю связать вашу инструкцию SQL с вредоносным кодом SQL. + Например, пользователь может ввести nothing; DROP TABLE *; для mUserInput, что +приведет к созданию следующего предложения выбора: var = nothing; DROP TABLE *;. Поскольку +предложение выбора выполняется в потоке как инструкция SQL, это может привести к тому, что поставщик удалит все +таблицы в соответствующей базе данных SQLite (если только в поставщик не настроен на отслеживание попыток +внедрить вредоносный код SQL). +

+

+ Чтобы избежать этого, воспользуйтесь предложением выбора, в котором ? выступает в качестве подставляемого +параметра, а также отдельным массивом аргументов выбора. После этого ввод пользователя +будет связан напрямую с запросом и не будет интерпретироваться как часть инструкции SQL. + Поскольку в этом случае введенный пользователем запрос не рассматривается как код SQL, то в него не удастся внедрить вредоносный код SQL. Вместо +объединения, которое следует включить в пользовательский ввод, используйте следующее предложение выбора: +

+
+// Constructs a selection clause with a replaceable parameter
+String mSelectionClause =  "var = ?";
+
+

+ Настройте массив аргументов выбора следующим образом: +

+
+// Defines an array to contain the selection arguments
+String[] selectionArgs = {""};
+
+

+ Укажите значение для массива аргументов выбора: +

+
+// Sets the selection argument to the user's input
+selectionArgs[0] = mUserInput;
+
+

+ Предложение выбора, в котором ? используется в качестве подстановочного параметра, и массив +аргументов выбора представляют собой предпочтительный способ указания выбора, даже если поставщик +не использует базу данных SQL. +

+ +

Отображение результатов запроса

+

+ Клиентский метод {@link android.content.ContentResolver#query ContentResolver.query()} всегда возвращает объект +{@link android.database.Cursor}, содержащий столбцы, указанные в проекции +запроса для строк, которые соответствуют критериям выборки в запросе. Объект +{@link android.database.Cursor} предоставляет прямой доступ на чтение содержащихся в нем строк и +столбцов. С помощью методов {@link android.database.Cursor} можно выполнить итерацию по строкам +в результатах, определить тип данных для каждого столбца, получить данные из столбца, а также проверить другие свойства +результатов. Некоторые реализации объекта {@link android.database.Cursor} автоматически обновляют +объект при изменении данных в поставщике или запускают выполнение методов в объекте-наблюдателе +при изменении объекта{@link android.database.Cursor}, либо выполняют и то, и другое. +

+

+ Примечание. Поставщик может ограничить доступ к столбцам на основе характера +объекта, выполняющего запрос. Например, поставщик контактов ограничивает доступ адаптеров синхронизации к некоторым столбцам, +поэтому он не возвращает их в операцию или службу. +

+

+ Если строки, соответствующие критериям выборки, отсутствуют, поставщик +возвращает объект{@link android.database.Cursor}, в котором для метода +{@link android.database.Cursor#getCount Cursor.getCount()} указано значение «0» (пустой объект cursor). +

+

+ При возникновении внутренней ошибки результаты запроса зависят от определенного поставщика. Поставщик может +возвратитьnull или выдать {@link java.lang.Exception}. +

+

+ Поскольку {@link android.database.Cursor} представляет собой «список» строк, то наилучшим способом отобразить содержимое объекта +{@link android.database.Cursor} будет связать его с {@link android.widget.ListView} +посредством {@link android.widget.SimpleCursorAdapter}. +

+

+ Следующий фрагмент кода является продолжением предыдущего фрагмента. Он создает объект +{@link android.widget.SimpleCursorAdapter}, содержащий объект{@link android.database.Cursor}, +который был получен в запросе, а затем определяет этот объект в качестве адаптера для +{@link android.widget.ListView}: +

+
+// Defines a list of columns to retrieve from the Cursor and load into an output row
+String[] mWordListColumns =
+{
+    UserDictionary.Words.WORD,   // Contract class constant containing the word column name
+    UserDictionary.Words.LOCALE  // Contract class constant containing the locale column name
+};
+
+// Defines a list of View IDs that will receive the Cursor columns for each row
+int[] mWordListItems = { R.id.dictWord, R.id.locale};
+
+// Creates a new SimpleCursorAdapter
+mCursorAdapter = new SimpleCursorAdapter(
+    getApplicationContext(),               // The application's Context object
+    R.layout.wordlistrow,                  // A layout in XML for one row in the ListView
+    mCursor,                               // The result from the query
+    mWordListColumns,                      // A string array of column names in the cursor
+    mWordListItems,                        // An integer array of view IDs in the row layout
+    0);                                    // Flags (usually none are needed)
+
+// Sets the adapter for the ListView
+mWordList.setAdapter(mCursorAdapter);
+
+

+ Примечание. Чтобы вернуть {@link android.widget.ListView} с объектом +{@link android.database.Cursor}, объект cursor должен содержать столбец с именем _ID. + Поэтому показанный ранее запрос извлекает столбец_ID для таблицы +words, даже если {@link android.widget.ListView} не отображает ее. + Данное ограничение также объясняет, почему в каждой таблице поставщика имеется столбец +_ID. +

+ + +

Получение данных из результатов запроса

+

+ Вместо того, чтобы просто отобразить результаты запроса, вы можете использовать их для выполнения других задач. Например, +можно получить написание слов из пользовательского словаря, а затем выполнить их поиск в +других поставщиках. Для этого выполните итерацию по строкам в объекте {@link android.database.Cursor}: +

+
+
+// Determine the column index of the column named "word"
+int index = mCursor.getColumnIndex(UserDictionary.Words.WORD);
+
+/*
+ * Only executes if the cursor is valid. The User Dictionary Provider returns null if
+ * an internal error occurs. Other providers may throw an Exception instead of returning null.
+ */
+
+if (mCursor != null) {
+    /*
+     * Moves to the next row in the cursor. Before the first movement in the cursor, the
+     * "row pointer" is -1, and if you try to retrieve data at that position you will get an
+     * exception.
+     */
+    while (mCursor.moveToNext()) {
+
+        // Gets the value from the column.
+        newWord = mCursor.getString(index);
+
+        // Insert code here to process the retrieved word.
+
+        ...
+
+        // end of while loop
+    }
+} else {
+
+    // Insert code here to report an error if the cursor is null or the provider threw an exception.
+}
+
+

+ Реализации объекта {@link android.database.Cursor} содержат несколько методов get для +получения из объекта различных типов данных. Например, в следующем фрагменте кода используется метод +{@link android.database.Cursor#getString getString()}. В них также имеется метод +{@link android.database.Cursor#getType getType()}, который возвращает значение, указывающее на тип +данных в столбце. +

+ + + +

Разрешения поставщика контента

+

+ Приложение поставщика может задавать разрешения, которые требуются другим приложениям для доступа к +данным в поставщике. Такие разрешения гарантируют, что пользователь знает, к каким +данным приложение будет пытаться получить доступ. На основе требований поставщика другие +приложения запрашивают разрешения, которые требуются им для доступа к поставщику. Конечные пользователи видят +запрошенные разрешения при установке приложения. +

+

+ Если приложение поставщика не задает никаких разрешений, другие приложения не получают доступ к +данным поставщика. Однако компонентам приложения поставщика +всегда предоставлен полный доступ на чтение и запись, независимо от заданных разрешений. +

+

+ Как уже было отмечено ранее, для получения данных из поставщика пользовательского словаря требуется разрешение +android.permission.READ_USER_DICTIONARY. + В поставщике предусмотрено отдельное разрешениеandroid.permission.WRITE_USER_DICTIONARY +для вставки, обновления или удаления данных. +

+

+ Чтобы получить разрешения, необходимые для доступа к поставщику, приложение запрашивает их с помощью элемента +<uses-permission> +в файле манифеста. При установке менеджером пакетов Android приложения пользователю необходимо +утвердить все разрешения, запрашиваемые приложением. В случае утверждения всех разрешений +менеджер пакетов продолжает установку; если же пользователь отклоняет их, менеджер +пакетов отменяет установку. +

+

+ Для запроса доступа на чтение данных в поставщике пользовательского словаря используется +следующий элемент +<uses-permission>: +

+
+    <uses-permission android:name="android.permission.READ_USER_DICTIONARY">
+
+

+ Дополнительные сведения о влиянии разрешений на доступ к поставщику представлены в статье +Безопасность и разрешения. +

+ + + +

Вставка, обновление и удаление данных

+

+ Подобно тому, как вы получаете данные от поставщика, вы также можете можете использовать возможности взаимодействия между клиентом поставщика и объектом +{@link android.content.ContentProvider} поставщика для изменения данных. + Можно вызвать метод объекта {@link android.content.ContentResolver}, указав аргументы, +которые были переданы в соответствующий метод объекта {@link android.content.ContentProvider}. Поставщик и клиент поставщика +автоматически обрабатывают взаимодействие между процессами и обеспечивают безопасность. +

+

Вставка данных

+

+ Для вставки данных в поставщик вызовите +метод +{@link android.content.ContentResolver#insert ContentResolver.insert()}. Этот метод вставляет новую строку в поставщик и возвращает URI контента для этой строки. + В следующем фрагменте кода демонстрируется порядок вставки нового слова в поставщик пользовательского словаря: +

+
+// Defines a new Uri object that receives the result of the insertion
+Uri mNewUri;
+
+...
+
+// Defines an object to contain the new values to insert
+ContentValues mNewValues = new ContentValues();
+
+/*
+ * Sets the values of each column and inserts the word. The arguments to the "put"
+ * method are "column name" and "value"
+ */
+mNewValues.put(UserDictionary.Words.APP_ID, "example.user");
+mNewValues.put(UserDictionary.Words.LOCALE, "en_US");
+mNewValues.put(UserDictionary.Words.WORD, "insert");
+mNewValues.put(UserDictionary.Words.FREQUENCY, "100");
+
+mNewUri = getContentResolver().insert(
+    UserDictionary.Word.CONTENT_URI,   // the user dictionary content URI
+    mNewValues                          // the values to insert
+);
+
+

+ Данные для новой строки поступают в один объект {@link android.content.ContentValues}, +который аналогичен объекту cursor с одной строкой. Столбцы в этом объекте необязательно +должны содержать данные такого же типа, и если вы вообще не собираетесь указывать значение, вы можете задать для столбца значение +null с помощью метода {@link android.content.ContentValues#putNull ContentValues.putNull()}. +

+

+ Код в представленном фрагменте не добавляет столбец _ID, поскольку этот столбец сохраняется +автоматически. Поставщик присваивает уникальное значение _ID каждой +добавляемой строке. Обычно поставщики используют это значение в качестве основного ключа таблицы. +

+

+ URI контента, возвращенный в элементе newUri, служит для идентификации новой добавленной строки +в следующем формате: +

+
+content://user_dictionary/words/<id_value>
+
+

+ <id_value> — это содержимое столбца _ID для новой строки. + Большинство поставщиков автоматически определяют эту форму URI контента, а затем +выполняют запрошенную операцию с требуемой строкой. +

+

+ Чтобы получить значение _ID из возвращенного объекта {@link android.net.Uri}, вызовите метод +{@link android.content.ContentUris#parseId ContentUris.parseId()}. +

+

Обновление данных

+

+ Чтобы обновить строку, используйте объект {@link android.content.ContentValues} с обновленными +значениями (точно так же, как вы это делаете при вставке) и критериями выборки (так же, как и с запросом). + Используемый вами клиентский метод называется +{@link android.content.ContentResolver#update ContentResolver.update()}. Вам не нужно добавлять значения в объект +{@link android.content.ContentValues} для обновляемых столбцов. Чтобы очистить содержимое столбца, задайте значение +null. +

+

+ Следующий фрагмент кода служит для изменения языка во всех строках, где в качестве языка указано en, на +null. Возвращаемое значение представляет собой количество строк, которые были обновлены: +

+
+// Defines an object to contain the updated values
+ContentValues mUpdateValues = new ContentValues();
+
+// Defines selection criteria for the rows you want to update
+String mSelectionClause = UserDictionary.Words.LOCALE +  "LIKE ?";
+String[] mSelectionArgs = {"en_%"};
+
+// Defines a variable to contain the number of updated rows
+int mRowsUpdated = 0;
+
+...
+
+/*
+ * Sets the updated value and updates the selected words.
+ */
+mUpdateValues.putNull(UserDictionary.Words.LOCALE);
+
+mRowsUpdated = getContentResolver().update(
+    UserDictionary.Words.CONTENT_URI,   // the user dictionary content URI
+    mUpdateValues                       // the columns to update
+    mSelectionClause                    // the column to select on
+    mSelectionArgs                      // the value to compare to
+);
+
+

+ Также следует проверить пользовательский ввод при вызове метода +{@link android.content.ContentResolver#update ContentResolver.update()}. Дополнительные сведения об этом +представлены в разделе Защита от ввода вредоносного кода. +

+

Удаление данных

+

+ Удаление данных аналогично получению данных строки: необходимо указать критерии выборки для строк, +которые требуется удалить, после чего клиентский метод возвратит количество удаленных строк. + Ниже представлен фрагмент кода для удаления строк с идентификатором appid user. Метод возвращает +количество удаленных строк. +

+
+
+// Defines selection criteria for the rows you want to delete
+String mSelectionClause = UserDictionary.Words.APP_ID + " LIKE ?";
+String[] mSelectionArgs = {"user"};
+
+// Defines a variable to contain the number of rows deleted
+int mRowsDeleted = 0;
+
+...
+
+// Deletes the words that match the selection criteria
+mRowsDeleted = getContentResolver().delete(
+    UserDictionary.Words.CONTENT_URI,   // the user dictionary content URI
+    mSelectionClause                    // the column to select on
+    mSelectionArgs                      // the value to compare to
+);
+
+

+ Также следует проверить пользовательский ввод при вызове метода +{@link android.content.ContentResolver#delete ContentResolver.delete()}. Дополнительные сведения об этом +представлены в разделе Защита от ввода вредоносного кода. +

+ +

Типы поставщиков данных

+

+ Поставщики контента могут предоставлять различные тип данных. Поставщик пользовательского словаря предоставляет только +текст, но также может предоставлять следующие форматы: +

+
    +
  • + целое число; +
  • +
  • + длинное целое число (long); +
  • +
  • + число с плавающей запятой; +
  • +
  • + длинное число с плавающей запятой (double). +
  • +
+

+ Другим типом данных, предлагаемых поставщиком, является большой двоичный объект (BLOB), реализованный как +64-разрядный массив. Чтобы просмотреть доступные типы данных, обратитесь к методам get класса +{@link android.database.Cursor}. +

+

+ Тип данных для каждого столбца в поставщике обычно указывается в документации к поставщику. + Типы данных для поставщика пользовательского словаря указаны в справочной документации +для класса-контракта {@link android.provider.UserDictionary.Words} (дополнительные сведения о классах-контрактах представлены в разделе +Классы-контракты). + Также определить тип данных можно путем вызова метода {@link android.database.Cursor#getType +Cursor.getType()}. +

+

+ Поставщики также хранят информацию о типе данных MIME для каждого определяемого ими URI контента. Эту информацию +можно использовать для определения того, может ли ваше приложение обрабатывать предлагаемые +поставщиком данные, а также для выбора типа обработки на основе типа MIME. Информация о типе +MIME обычно требуется при работе с поставщиком, который содержит +сложные структуры данных или файлы. Например, в таблице{@link android.provider.ContactsContract.Data} +в поставщике контактов используются типы MIME для отметки типа данных контакта, которые хранятся в каждой +строке. Чтобы получить тип MIME, соответствующий URI контента, вызовите метод +{@link android.content.ContentResolver#getType ContentResolver.getType()}. +

+

+ Синтаксис стандартных и настраиваемых типов MIME описан в +справке по типам MIME. +

+ + + +

Альтернативные формы доступа к поставщику

+

+ При разработке приложения следует учитывать три альтернативных формы доступа к поставщику: +

+
    +
  • + Пакетный доступ: можно создать пакет вызовов доступа с использованием методов в классе +{@link android.content.ContentProviderOperation}, а затем применить их с помощью метода +{@link android.content.ContentResolver#applyBatch ContentResolver.applyBatch()}. +
  • +
  • + Асинхронные запросы: Запросы следует выполнять в отдельном потоке. Одним из способов реализовать это является использование объекта +{@link android.content.CursorLoader}. Примеры этого представлены в +статье +Загрузчики. +
  • +
  • + Доступ к данным с помощью намерений: Несмотря на то, что намерение +невозможно отправить напрямую в поставщик, вы можете отправить запрос в приложение поставщика, в котором обычно +имеется больше возможностей для изменения данных поставщика. +
  • +
+

+ Пакетный доступ и изменение с помощью намерений описаны в следующих разделах. +

+

Пакетный доступ

+

+ Пакетный доступ к поставщику полезно использовать в случаях, когда необходимо вставить большое количество строк, или для вставки +строк в несколько таблиц в рамках одного вызова метода, а также в общих случаях для выполнения ряда +операций на границах процессов в виде транзакции (атомарной операции). +

+

+ Для доступа к поставщику в «пакетном режиме» необходимо создать массив объектов +{@link android.content.ContentProviderOperation}, а затем +отправить их в поставщик контента с помощью метода +{@link android.content.ContentResolver#applyBatch ContentResolver.applyBatch()}. В этот метод необходимо передать +центр поставщика контента, а не определенный URI контента. +Это позволит каждому объекту {@link android.content.ContentProviderOperation} в массиве взаимодействовать +с разными таблицами. Метод {@link android.content.ContentResolver#applyBatch +ContentResolver.applyBatch()} возвращает массив результатов. +

+

+ В описании класса-контракта {@link android.provider.ContactsContract.RawContacts} +также представлен фрагмент кода, в котором демонстрируется вставка в пакетном режиме. В исходном файле ContactAdder.java примера приложения +Диспетчер контактов +имеется пример пакетного +доступа. +

+ +

Доступ к данным с помощью намерений

+

+ Намерения позволяют в обход получать доступ к поставщику контента. Вы можете разрешить пользователям доступ к +данным в поставщике даже в том случае, если у приложения отсутствуют разрешения на доступ, либо путем +получения результирующего намерения от приложения, у которого имеются необходимые разрешения, либо путем активации +приложения, у которого имеются разрешения и которое разрешает пользователю работать с ним. +

+

Получение доступа с временными разрешениями

+

+ Вы можете получить доступ к данным в поставщике контента даже тогда, когда у вас нет необходимых разрешений на доступ +, путем отправки намерения в приложение, у которого есть такие разрешения, и получения +результирующего намерения, которое содержит разрешения URI. + Эти разрешения для определенного URI контента действуют до тех пор, пока не будет завершена операция, получившая +их. Приложение, у которой имеются бессрочные разрешения, предоставляет временные +разрешения путем задания соответствующего флага в результирующем намерении: +

+
    +
  • + Разрешение на чтение: +{@link android.content.Intent#FLAG_GRANT_READ_URI_PERMISSION} +
  • +
  • + Разрешение на запись: +{@link android.content.Intent#FLAG_GRANT_WRITE_URI_PERMISSION} +
  • +
+

+ Примечание. Эти флаги не предоставляют общий доступ на чтение или запись поставщику, +центр которого указан в URI контента. Доступ предоставляется только самому URI. +

+

+ Поставщик определяет разрешения URI для URI контента в своем манифесте с помощью атрибута +android:grantUriPermission +элемента +<provider>, +а также +с помощью дочернего элемента +<grant-uri-permission> +элемента +<provider>. Дополнительные сведения о механизме разрешений URI представлены в статье +Безопасность и разрешения в разделе +Разрешения URI. +

+

+ Например, можно получить данные о контакте из поставщика контактов, даже если у вас нет разрешения +{@link android.Manifest.permission#READ_CONTACTS}. Возможно, это потребуется реализовать +в приложении, которое отправляет электронные поздравления контакту в день его рождения. Вместо запроса +{@link android.Manifest.permission#READ_CONTACTS}, когда вы получаете доступ ко всем контактам пользователя +и всей информации о них, можно предоставить пользователю возможность указать, +какие контакты используются вашим приложением. Для этого воспользуйтесь указанным ниже процессом. +

+
    +
  1. + Ваше приложение отправляет намерение, содержащее действие +{@link android.content.Intent#ACTION_PICK} и тип MIME +{@link android.provider.ContactsContract.RawContacts#CONTENT_ITEM_TYPE} контактов, используя для этого метод +{@link android.app.Activity#startActivityForResult +startActivityForResult()}. +
  2. +
  3. + Поскольку это намерение соответствует условиям отбора намерений для операции выбора +приложения «Контакты», эта операция переходит на передний план. +
  4. +
  5. + В операции выбора пользователь выбирает +контакт для обновления. Когда это происходит, операция выбора вызывает метод +{@link android.app.Activity#setResult setResult(resultcode, intent)} +для создания намерения, которое будет передано обратно в ваше приложение. Намерение содержит URI контента +выбранного пользователем контакта, а также флаги +{@link android.content.Intent#FLAG_GRANT_READ_URI_PERMISSION} дополнительных данных. Эти флаги предоставляют вашему приложению разрешение URI +на чтение данных контакта, на который указывает +URI контента. Затем операция выбора вызывает метод{@link android.app.Activity#finish()}, +чтобы вернуть управление вашему приложению. +
  6. +
  7. + Ваша операция возвращается на передний план, а система вызывает метод +{@link android.app.Activity#onActivityResult onActivityResult()} +вашей операции. Этот метод получает результирующее намерение, созданное операцией выбора в +приложении «Контакты». +
  8. +
  9. + С помощью URI контента из результирующего намерения можно выполнить чтение данных контакта +из поставщика контактов, даже если вы не запрашивали у поставщика постоянный доступ на чтение +в своем манифесте. Можно получить информацию о дне рождения контакта +или сведения о его адресе эл. почты, а затем отправить контакту электронное поздравление. +
  10. +
+

Использование другого приложения

+

+ Простой способ разрешить пользователю изменять данные, на доступ к которым у вас нет доступа +— это активировать приложение, у которого есть такие разрешения, а затем предоставить пользователю возможность выполнять необходимые действия в этом приложении. +

+

+ Например, приложение «Календарь» принимает намерения +{@link android.content.Intent#ACTION_INSERT}, с помощью которого можно активировать +пользовательский интерфейс приложения для вставки. Вы можете передать в это намерение дополнительные данные, которые приложение +использует для заполнения полей в пользовательском интерфейсе. Поскольку синтаксис повторяющихся событий довольно сложный, то события предпочтительно +вставлять в поставщик календаря путем активации приложения «Календарь» с помощью действия +{@link android.content.Intent#ACTION_INSERT} и последующего предоставления пользователю возможности самому вставить событие в этом приложении. +

+ +

Классы-контракты

+

+ Класс-контракт определяет константы, которые обеспечивают для приложений возможность работать с URI контента, именами +столбцов, операциями намерения и другими функциями поставщика контента. Классы-контракты не +включены в поставщик; разработчику поставщика следует определить их и сделать +их доступными для других разработчиков. Многие из поставщиков, включенные в платформу Android, +содержат соответствующие классы-контракты в пакете {@link android.provider}. +

+

+ Например, в поставщике пользовательского календаря имеется класс-контракт +{@link android.provider.UserDictionary}, содержащий константы URI контента и имен столбцов. URI +контента для таблицы words определен в константе +{@link android.provider.UserDictionary.Words#CONTENT_URI UserDictionary.Words.CONTENT_URI}. + В классе {@link android.provider.UserDictionary.Words} также имеются константы имен столбцов, +которые используются в фрагментах кода примера приложения, представленных в этой статье. Например, проекцию запроса +можно определить следующим образом: +

+
+String[] mProjection =
+{
+    UserDictionary.Words._ID,
+    UserDictionary.Words.WORD,
+    UserDictionary.Words.LOCALE
+};
+
+

+ Другим классом-контрактом является класс {@link android.provider.ContactsContract} для поставщика контактов. + В справочной документации к этому классу представлены фрагменты кода примера приложения. Один из его подклассов, +{@link android.provider.ContactsContract.Intents.Insert}, представляет собой класс-контракт, +который содержит константы для намерений и их данных. +

+ + + +

Справка по типам MIME

+

+ Поставщики контента могут возвращать как стандартные типы мультимедиа MIME, так и строки с настраиваемым типом MIME, либо оба этих типа. +

+

+ Типы MIME имеют следующий формат: +

+
+type/subtype
+
+

+ Например, хорошо известный тип MIME text/html имеет тип text и подтип +html. Если поставщик возвращает этот тип URI, это означает, что +строка запроса, в которой используется этот URI, возвратит текста с тегами HTML. +

+

+ Строки с настраиваемым типом MIME, которые также называются типами MIME поставщика, имеют более сложные значения +типов и подтипов. Значение типа всегда следующее: +

+
+vnd.android.cursor.dir
+
+

+ для нескольких строк, или +

+
+vnd.android.cursor.item
+
+

+ для одной строки. +

+

+ Подтип зависит от поставщика. Встроенные поставщики Android обычно содержат простой +подтип. Например, когда приложение «Контакты» создает строку для номера телефона, +оно задает следующий тип MIME в этой строке: +

+
+vnd.android.cursor.item/phone_v2
+
+

+ Обратите внимание, что значение подтипа просто phone_v2. +

+

+ Разработчики поставщиков могут создавать свои собственные шаблоны подтипов на основе +центра и названий таблиц поставщика. Например, рассмотрим поставщик, который содержит расписание движения поездов. + Центром поставщика является com.example.trains, в котором содержатся таблицы +Line1, Line2 и Line3. В ответ на следующий URI контента +

+

+

+content://com.example.trains/Line1
+
+

+ для таблицы Line1 поставщик возвращает следующий тип MIME +

+
+vnd.android.cursor.dir/vnd.example.line1
+
+

+ В ответ на следующий URI контента +

+
+content://com.example.trains/Line2/5
+
+

+ для строки 5 в таблице Line2 поставщик возвращает следующий тип MIME +

+
+vnd.android.cursor.item/vnd.example.line2
+
+

+ В большинстве поставщиков контента определены константы класса-контракта для используемых в них типов MIME. Например, класс-контракт +{@link android.provider.ContactsContract.RawContacts} +поставщика контактов определяет константу +{@link android.provider.ContactsContract.RawContacts#CONTENT_ITEM_TYPE} для типа MIME одной +строки необработанного контакта. +

+

+ URI контента для единичных строк описываются в разделе +URI контента. +

diff --git a/docs/html-intl/intl/ru/guide/topics/providers/content-provider-creating.jd b/docs/html-intl/intl/ru/guide/topics/providers/content-provider-creating.jd new file mode 100644 index 0000000000000000000000000000000000000000..d8f787393eaad757fe1d1d7c5083042b997014d3 --- /dev/null +++ b/docs/html-intl/intl/ru/guide/topics/providers/content-provider-creating.jd @@ -0,0 +1,1214 @@ +page.title=Создание поставщика контента +@jd:body + + + +

+ Поставщик контента управляет доступом к центральному репозиторию данных. Реализация +поставщика включает один или несколько классов в приложении Android, а также элементы +в файле манифеста. Один из классов реализует подкласс +{@link android.content.ContentProvider}, который выступает в роли интерфейса между вашим поставщиком и +другими приложениями. Несмотря на то, что поставщики контента изначально предназначены для предоставления доступа к данным +другим приложениям, в вашем приложении, несомненно, могут содержаться операции, которые разрешают пользователю +запрашивать и изменять данные, управляемые вашим поставщиком. +

+

+ В данной статье представлены базовые инструкции по созданию поставщика контента и список необходимых для этого +API-интерфейсов. +

+ + + +

Подготовка к созданию поставщика

+

+ Прежде чем приступить к созданию поставщика, выполните указанные ниже действия. +

+
    +
  1. + Решите, нужен ли вообще вам поставщик контента. Поставщик +контента требуется в случаях, если вы хотите реализовать в своем приложении одну или несколько следующих функций: +
      +
    • предоставление сложных данных или файлов другим приложениям;
    • +
    • предоставление пользователям возможности копировать сложные данные из вашего приложения в другие приложения;
    • +
    • предоставление настраиваемых поисковых подсказок с помощью платформы поиска.
    • +
    +

    + Вам не нужен поставщик для работы с базой данных SQLite, если ее планируется использовать +исключительно в вашем приложении. +

    +
  2. +
  3. + Если вы еще не приняли окончательное решение, ознакомьтесь со +статьей +Основные сведения о поставщике контента, чтобы узнать подробнее о поставщиках контента. +
  4. +
+

+ После этого можно приступать к созданию поставщика. Для этого выполните указанные ниже действия. +

+
    +
  1. + Спроектируйте базовое хранилище для своих данных. Поставщик контента предоставляет данные двумя способами: +
    +
    + Данные для файлов +
    +
    + Данные, которые обычно поступают в файлы, такие как +фотографии, аудио- или видеоданные. Файлы следует хранить в закрытом +пространстве вашего приложения. В ответ на запрос файла из другого приложения +ваш поставщик может предложить дескриптор для файла. +
    +
    + Структурированные данные +
    +
    + Данные, которые обычно поступают в базу данных, массив или аналогичную структуру. + Данные следует хранить в той форме, которая совместима с таблицами из строк и столбцов. Строка +представляет собой объект, например, пользователя, позицию или учетную единицу. Столбец +представляет собой некоторые данные об объекте, например, имя пользователя или стоимость единицы. Обычно +данные такого типа хранятся в базе данных SQLite, однако вы можете использовать постоянное хранилище +любого типа. Дополнительные сведения о типах хранилищ, доступных в системе +Android, представлены в разделе +Проектирование хранилища данных. +
    +
    +
  2. +
  3. + Определите конкретную реализацию класса {@link android.content.ContentProvider} +и его необходимые методы. Этот класс выступает в роли интерфейса между вашими данными и остальной частью системы +Android. Дополнительные сведения об этом классе представлены в разделе +Реализация класса ContentProvider. +
  4. +
  5. + Определите строку центра поставщика, его URI контента и имена столбцов. Если необходимо, +чтобы приложение поставщика обрабатывало намерения, также необходимо определить действия намерений, дополнительные данные +и флаги. Кроме того, необходимо определить разрешения, которые будут необходимы приложениям для доступа к вашим +данным. Все эти значения следует определить как константы в отдельном +классе-контракте; в дальнейшем этот класс можно предоставить другим разработчикам. Дополнительные сведения о + URI контента представлены в разделе +Проектирование URI контента. + Дополнительные сведения о намерениях представлены в разделе +Намерения и доступ к данным. +
  6. +
  7. + Добавьте другие дополнительные компоненты, например, демонстрационные данные или реализация адаптера +{@link android.content.AbstractThreadedSyncAdapter}, который служит для синхронизации данных между +поставщиком и облаком. +
  8. +
+ + + +

Проектирование хранилища данных

+

+ Поставщик контента представляет собой интерфейс для передачи данных, сохраненных в структурированном формате. Прежде чем создавать +интерфейс, определите способ хранения данных. Данные можно хранить +в любой форме, а затем спроектировать интерфейс для чтения и записи данных при необходимости. +

+

+ В Android имеются некоторые технологии хранения данных: +

+
    +
  • + В системе Android имеется API базы данных SQLite, который используется собственными поставщиками Android для +хранения табличных данных. С помощью класса +{@link android.database.sqlite.SQLiteOpenHelper} можно создавать базы данных, а класс +{@link android.database.sqlite.SQLiteDatabase} представляет собой базовый класс для доступа +к базам данных. +

    + Обратите внимание, что вам не обязательно использовать базу данных для реализации своего репозитория. Поставщик представляет собой +внешний набор таблиц, как в случае с реляционной базой данных, однако это +не является требованием к внутренней реализации поставщика. +

    +
  • +
  • + Для хранения данных файлов в Android предусмотрены различные API-интерфейсы для работы с файлами. + Дополнительные сведения о хранилище файлов представлены в статье +Хранилище данных. Если вы +проектируете поставщик, который предлагает мультимедийные данные, такие как музыка или видео, можно создать поставщик, +объединяющий табличные данные и файлы. +
  • +
  • + Для работы с сетевыми данными используйте классы в {@link java.net} и +{@link android.net}. Вы также можете синхронизировать сетевые данные с локальным +хранилищем данных (например, с базой данных), а затем предоставить такие данные в виде таблиц или файлов. + Такой тип синхронизации демонстрируется в +примере приложения адаптера синхронизации. +
  • +
+

+ Рекомендации по проектированию данных +

+

+ Вот несколько советов и рекомендаций, касающихся проектирования структуры данных поставщика: +

+
    +
  • + В табличных данных всегда должен быть столбец для «основного ключа», который поставщик хранит +в виде уникального числового значения для каждой строки. Вы можете использовать это значение для связывания строки +со строками в других таблицах (используя его в качестве «внешнего ключа»). Несмотря на то, что вы можете использовать любое имя +для этого столбца, рекомендуется указать имя {@link android.provider.BaseColumns#_ID BaseColumns._ID}, +поскольку для связывания результатов запроса поставщика с +{@link android.widget.ListView} необходимо, чтобы один из получаемых столбцов назывался +_ID. +
  • +
  • + Если вы планируете предоставлять растровые изображения или очень большие фрагменты данных для файлов, то данные +следует хранить в файлах, а затем предоставлять их косвенно вместо хранения таких данных прямо в +таблице. В таком случае вам необходимо сообщить пользователям вашего поставщика о том, что для доступа к данным им потребуется воспользоваться методом +{@link android.content.ContentResolver}. +
  • +
  • + Для хранения данных разного размера или с разной структурой используйте тип +BLOB. Например, столбец BLOB можно использовать для хранения +буфера протокола или +структуры JSON. +

    + BLOB также можно использовать для реализации таблицы, не зависящей от схемы. В таблице +такого типа определяются столбец основного ключа, столбец типа MIME и один +или несколько общих столбцов BLOB. На смысл данных в столбцах BLOB +указывает значение в столбце типа MIME. Благодаря этому в одной и той же таблице можно хранить строки +разных типов. Примером таблицы, не зависящей от схемы, может служить таблица с данными поставщика +контента +{@link android.provider.ContactsContract.Data}. +

    +
  • +
+ +

Проектирование URI контента

+

+ URI контента представляет собой URI, который определяет данные в поставщике. URI контента +могут включать символическое имя всего поставщика (его центр) и +имя, которое указывает на таблицу или файл (путь). Дополнительная часть URI с идентификатором +указывает на отдельную строку в таблице. У каждого метода доступа к данным в классе +{@link android.content.ContentProvider} имеется URI контента (в виде аргумента); благодаря этому вы можете +определить таблицу, строку или файл для доступа. +

+

+ Базовые сведения о URI контента представлены в +статье +Основные сведения о поставщике контента. +

+

Проектирование центра поставщика

+

+ У поставщика обычно имеется только один центр, который выступает в качестве его внутреннего имени в системе Android. Во +избежание конфликтов с другими поставщиками в качестве основы центра поставщика должны выступать сведения о владении доменом в Интернете +(в обратном порядке). Поскольку эта рекомендация также применяется и к названиям пакетов Android, +вы можете определить центр своего поставщика в виде расширения названия +пакета, в котором содержится поставщик. Например, если пакет Android называется +com.example.<appname>, то центром +вашего поставщика должен быть com.example.<appname>.provider. +

+

Проектирование структуры пути

+

+ Обычно разработчики создают URI контента на основе центра поставщика, добавляя к нему путь, который указывает +на отдельные таблицы. Например, если имеется две таблицы, table1 и +table2, центр поставщика из предыдущего примера следует объединить для формирования +следующих URI контента: +com.example.<appname>.provider/table1 и +com.example.<appname>.provider/table2. Пути не ограничены +одним сегментом, и не на каждом уровне пути имеется таблица. +

+

Обработка идентификаторов URI контента

+

+ Обычно поставщики предоставляют доступ к одной строке в таблице путем принятия URI контента, +в конце которого указано значение идентификатора строки. Также поставщики обычно проверяют совпадение +значения идентификатора по столбцу _ID в таблице и предоставляют запрашиваемый доступ к +соответствующей строке. +

+

+ Это упрощает создание общего метода проектирования для приложений, получающих доступ к поставщику. Приложение +отправляет запрос поставщику и отображает полученный в результате такого запроса объект {@link android.database.Cursor} +в объекте {@link android.widget.ListView} с помощью {@link android.widget.CursorAdapter}. + Для определения {@link android.widget.CursorAdapter} необходимо, чтобы один из столбцов в объекте +{@link android.database.Cursor} назывался _ID +

+

+ Затем пользователь выбирает в пользовательском интерфейсе одну из отображаемых строк, чтобы просмотреть данные +или изменить их. Приложение получает соответствующую строку из объекта{@link android.database.Cursor} в базовом объекте +{@link android.widget.ListView}, получает значение _ID для этой строки, добавляет его к +URI контента, а затем отправляет поставщику запрос на доступ. Затем поставщик может +запросить или изменить строку, выбранную пользователем. +

+

Шаблоны URI контента

+

+ Чтобы помочь вам в выборе действия для выполнения со входящим URI контента, в API поставщика +имеется класс {@link android.content.UriMatcher}, который сопоставляет шаблоны URI контента с +целочисленными значениями. Такие целочисленные значения можно использовать в операторе switch, +который выбирает подходящее действие для URI контента, которые соответствуют определенному шаблону. +

+

+ Для определения совпадения URI контента с шаблоном используются подстановочные символы: +

+
    +
  • + *: соответствие строке любой длины с любыми допустимыми символами; +
  • +
  • + #: соответствие строке любой длины с цифрами. +
  • +
+

+ В качестве примера для проектирования и написания кода для обработки URI контента +рекомендуется использовать центр поставщикаcom.example.app.provider, который распознает +следующие URI контента, указывающие на таблицы: +

+
    +
  • + content://com.example.app.provider/table1: таблица table1; +
  • +
  • + content://com.example.app.provider/table2/dataset1: таблица +dataset1; +
  • +
  • + content://com.example.app.provider/table2/dataset2: таблица +dataset2; +
  • +
  • + content://com.example.app.provider/table3: таблица table3. +
  • +
+

+ Поставщик также распознает следующие URI контента, если к ним добавлен идентификатор строки (например, +content://com.example.app.provider/table3/1 для строки с +идентификатором1 в таблице table3. +

+

+ Возможно использование следующих шаблонов URI контента: +

+
+
+ content://com.example.app.provider/* +
+
+ Совпадает с любым URI контента в поставщике. +
+
+ content://com.example.app.provider/table2/*: +
+
+ Совпадает с URI контента в таблицах dataset1dataset2, однако не совпадает с URI контента в таблице table1 или +table3. +
+
+ content://com.example.app.provider/table3/#: Совпадает с URI контента +для отдельных строк в таблице table3, такими как +content://com.example.app.provider/table3/6 для строки с идентификатором +6. +
+
+

+ Во фрагменте кода ниже показано, как работают методы в классе {@link android.content.UriMatcher}. + Этот код обрабатывает URI для всей таблицы иначе, чем для URI +для отдельной строки, используя шаблон URI контента +content://<authority>/<path> для таблиц и шаблон +content://<authority>/<path>/<id> — для отдельных строк. +

+

+ Метод {@link android.content.UriMatcher#addURI(String, String, int) addURI()} сопоставляет +центр поставщика и его путь с целочисленным значением. Метод {@link android.content.UriMatcher#match(Uri) +match()} возвращает целочисленное значение для URI. Оператор switch +выбирает, следует ли ему выполнить запрос всей таблицы или только отдельной записи: +

+
+public class ExampleProvider extends ContentProvider {
+...
+    // Creates a UriMatcher object.
+    private static final UriMatcher sUriMatcher;
+...
+    /*
+     * The calls to addURI() go here, for all of the content URI patterns that the provider
+     * should recognize. 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
+     */
+    sUriMatcher.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.
+     */
+    sUriMatcher.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 (sUriMatcher.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 is not recognized, you should do some error handling here.
+        }
+        // call the code to actually do the query
+    }
+
+

+ Другой класс, {@link android.content.ContentUris}, предоставляет удобные методы для работы с частью +id URI контента. Классы {@link android.net.Uri} и +{@link android.net.Uri.Builder} содержат удобные методы для синтаксического анализа существующих объектов +{@link android.net.Uri} и создания новых. +

+ + +

Реализация класса ContentProvider

+

+ Экземпляр класса {@link android.content.ContentProvider} управляет доступом к структурированному набору данных + путем обработки запросов от других приложений. В конечном счете, при всех формах доступа +вызывается метод {@link android.content.ContentResolver}, который затем вызывает конкретный метод +{@link android.content.ContentProvider} для получения доступа. +

+

Необходимые методы

+

+ В абстрактном классе {@link android.content.ContentProvider} определены шесть абстрактных методов, +которые необходимо реализовать в рамках вашего собственного конкретного подкласса. Все указанные ниже методы, кроме +{@link android.content.ContentProvider#onCreate() onCreate()}, вызываются клиентским приложением, +которое пытается получить доступ к вашему поставщику контента. +

+
+
+ {@link android.content.ContentProvider#query(Uri, String[], String, String[], String) +query()} +
+
+ Получение данных от поставщика. Использует аргументы для выбора таблицы для запроса, +строк и столбцов, которые необходимо возвратить, и указания порядка сортировки результатов. + Возвращает данные в виде объекта {@link android.database.Cursor}. +
+
+ {@link android.content.ContentProvider#insert(Uri, ContentValues) insert()} +
+
+ Вставка строки в ваш поставщик. Использует аргументы для выбора +конечной таблицы и получения значений столбца, которые следует использовать. Возвращает URI контента +для новой вставленной строки. +
+
+ {@link android.content.ContentProvider#update(Uri, ContentValues, String, String[]) +update()} +
+
+ Обновление существующих строк в поставщике. Использует аргументы для выбора +таблицы и строк для обновления, а также для получения обновленных значений столбца. Возвращает количество обновленных строк. +
+
+ {@link android.content.ContentProvider#delete(Uri, String, String[]) delete()} +
+
+ Удаление строк из поставщика. Использует аргументы для выбора +таблицы и строк для удаления. Возвращает количество удаленных строк. +
+
+ {@link android.content.ContentProvider#getType(Uri) getType()} +
+
+ Возвращение типа MIME, соответствующего URI контента. Дополнительные сведения об этом методе представлены в разделе +Реализация типов MIME поставщика контента. +
+
+ {@link android.content.ContentProvider#onCreate() onCreate()} +
+
+ Инициализация поставщика. Система Android вызывает этот метод сразу после +создания вашего поставщика. Обратите внимание, что поставщик не будет создан до тех пор, пока объект +{@link android.content.ContentResolver} не прекратит попытки получить доступ к нему. +
+
+

+ Подпись этих методов аналогична подписи для идентичных методов в объекте +{@link android.content.ContentResolver}. +

+

+ При реализации этих методов следует учитывать указанные ниже моменты. +

+
    +
  • + Все эти методы, кроме {@link android.content.ContentProvider#onCreate() onCreate()}, +можно вызвать сразу из нескольких потоков, поэтому они должны быть реализованы с сохранением потокобезопасности. Дополнительные сведения об +использовании нескольких потоков представлены в +статье +Процессы и потоки. +
  • +
  • + Избегайте слишком длинных операций в методе {@link android.content.ContentProvider#onCreate() +onCreate()}. Отложите выполнение задач инициализации до тех пор, пока они не потребуются. + Дополнительные сведения об этом представлены в разделе +Реализация метода onCreate. +
  • +
  • + Несмотря на то, что вы должны реализовать эти методы, ваш код необязательно должен выполнять какие-либо другие действия, кроме +возврата ожидаемого типа данных. Например, может потребоваться, чтобы другие приложения не имели возможности +вставлять данные в некоторые таблицы. Для этого можно игнорировать вызов метода +{@link android.content.ContentProvider#insert(Uri, ContentValues) insert()} и возвратить +0. +
  • +
+

Реализация метода query()

+

+ Метод +{@link android.content.ContentProvider#query(Uri, String[], String, String[], String) +ContentProvider.query()} должен возвращать объект {@link android.database.Cursor}, а при сбое выдавать +{@link java.lang.Exception}. Если в качестве хранилища данных используется база данных SQLite, +можно просто возвратить объект {@link android.database.Cursor}, который был возвращен одним из методов +query() класса {@link android.database.sqlite.SQLiteDatabase}. + Если запрос не соответствует ни одной строке, следует возвратить экземпляр объекта{@link android.database.Cursor}, метод +{@link android.database.Cursor#getCount()} которого возвращает 0. + null следует возвращать только в том случае, если во время обработки запроса произошла внутренняя ошибка. +

+

+ Если вы не используете базу данных SQLite в качестве хранилища данных, обратитесь к одному из конкретных подклассов объекта +{@link android.database.Cursor}. Например, класс {@link android.database.MatrixCursor} +реализует объект cursor, в котором каждая строка представляет собой массив класса {@link java.lang.Object}. С помощью этого класса воспользуйтесь методом +{@link android.database.MatrixCursor#addRow(Object[]) addRow()}, чтобы добавить новую строку. +

+

+ Следует помнить, что система Android должна иметь возможность взаимодействовать с {@link java.lang.Exception} +в пределах процесса. Система Android позволяет это делать для указанных ниже исключений, которые могут быть полезны при обработке +ошибок запросов. +

+
    +
  • + {@link java.lang.IllegalArgumentException} (это исключение можно выдать в случае, +если поставщик получает недопустимый URI контента); +
  • +
  • + {@link java.lang.NullPointerException}. +
  • +
+

Реализация метода insert()

+

+ Метод {@link android.content.ContentProvider#insert(Uri, ContentValues) insert()} добавляет новую +строку в соответствующую строку, используя значения в аргументе +{@link android.content.ContentValues}. Если в аргументе {@link android.content.ContentValues} отсутствует имя столбца, +возможно, потребуется указать для него значение по умолчанию (либо в коде поставщика, либо в +схеме базы данных). +

+

+ Этот метод должен возвращать URI контента для новой строки. Для этого добавьте значение +_ID новой строки (или иной основной ключ) к URI контента таблицы, используя метод +{@link android.content.ContentUris#withAppendedId(Uri, long) withAppendedId()}. +

+

Реализация метода delete()

+

+ Методу {@link android.content.ContentProvider#delete(Uri, String, String[]) delete()} +необязательно фактически удалять строки из вашего хранилища данных. Если для работы с поставщиком используется адаптер синхронизации, +рассмотрите возможность отметки удаленной строки флагом +delete вместо окончательного удаления строки. Адаптер синхронизации может +проверить наличие удаленных строк с флагом delete и удалить их с сервера перед удалением их из поставщика. +

+

Реализация метода update()

+

+ Метод {@link android.content.ContentProvider#update(Uri, ContentValues, String, String[]) +update()} принимает тот же аргумент {@link android.content.ContentValues}, который используется методом +{@link android.content.ContentProvider#insert(Uri, ContentValues) insert()}, и те же аргументы +selection и selectionArgs, которые используются методами +{@link android.content.ContentProvider#delete(Uri, String, String[]) delete()} и +{@link android.content.ContentProvider#query(Uri, String[], String, String[], String) +ContentProvider.query()}. Благодаря этому код можно повторно использовать между данными методами. +

+

Реализация метода onCreate()

+

+ Система Android вызывает метод {@link android.content.ContentProvider#onCreate() +onCreate()} при запуске поставщика. В этом методе следует выполнять только быстро выполняющиеся задачи +инициализации, а создание базы данных и загрузку данных отложить до момента фактического получения +поставщиком запроса на данные. Слишком длинные операции в методе +{@link android.content.ContentProvider#onCreate() onCreate()} приводят к увеличению времени +запуска поставщика. В свою очередь, это увеличивает время отклика поставщика на запросы от других +приложений. +

+

+ Например, если вы используете базу данных SQLite, +в методе +{@link android.content.ContentProvider#onCreate() ContentProvider.onCreate()} можно создать новый объект +{@link android.database.sqlite.SQLiteOpenHelper}, а затем создать таблицы SQL при первом открытии базы данных. Чтобы упростить это, при первом вызове метода +{@link android.database.sqlite.SQLiteOpenHelper#getWritableDatabase +getWritableDatabase()} он автоматически вызывает метод +{@link android.database.sqlite.SQLiteOpenHelper#onCreate(SQLiteDatabase) +SQLiteOpenHelper.onCreate()}. +

+

+ В двух указанных ниже фрагментах кода иллюстрируется взаимодействие между методом +{@link android.content.ContentProvider#onCreate() ContentProvider.onCreate()} и методом +{@link android.database.sqlite.SQLiteOpenHelper#onCreate(SQLiteDatabase) +SQLiteOpenHelper.onCreate()}. В первом фрагменте кода представлена реализация метода +{@link android.content.ContentProvider#onCreate() ContentProvider.onCreate()}: +

+
+public class ExampleProvider extends ContentProvider
+
+    /*
+     * Defines a handle to the database helper object. The MainDatabaseHelper class is defined
+     * in a following snippet.
+     */
+    private MainDatabaseHelper mOpenHelper;
+
+    // Defines the database name
+    private static final String DBNAME = "mydb";
+
+    // Holds the database object
+    private SQLiteDatabase db;
+
+    public boolean onCreate() {
+
+        /*
+         * Creates a new helper object. This method always returns quickly.
+         * Notice that the database itself isn't created or opened
+         * until SQLiteOpenHelper.getWritableDatabase is called
+         */
+        mOpenHelper = new MainDatabaseHelper(
+            getContext(),        // the application context
+            DBNAME,              // the name of the database)
+            null,                // uses the default SQLite cursor
+            1                    // the version number
+        );
+
+        return true;
+    }
+
+    ...
+
+    // Implements the provider's insert method
+    public Cursor insert(Uri uri, ContentValues values) {
+        // Insert code here to determine which table to open, handle error-checking, and so forth
+
+        ...
+
+        /*
+         * Gets a writeable database. This will trigger its creation if it doesn't already exist.
+         *
+         */
+        db = mOpenHelper.getWritableDatabase();
+    }
+}
+
+

+ В следующем фрагменте кода представлена реализация метода +{@link android.database.sqlite.SQLiteOpenHelper#onCreate(SQLiteDatabase) +SQLiteOpenHelper.onCreate()}, включая вспомогательный класс: +

+
+...
+// A string that defines the SQL statement for creating a table
+private static final String SQL_CREATE_MAIN = "CREATE TABLE " +
+    "main " +                       // Table's name
+    "(" +                           // The columns in the table
+    " _ID INTEGER PRIMARY KEY, " +
+    " WORD TEXT"
+    " FREQUENCY INTEGER " +
+    " LOCALE TEXT )";
+...
+/**
+ * Helper class that actually creates and manages the provider's underlying data repository.
+ */
+protected static final class MainDatabaseHelper extends SQLiteOpenHelper {
+
+    /*
+     * Instantiates an open helper for the provider's SQLite data repository
+     * Do not do database creation and upgrade here.
+     */
+    MainDatabaseHelper(Context context) {
+        super(context, DBNAME, null, 1);
+    }
+
+    /*
+     * Creates the data repository. This is called when the provider attempts to open the
+     * repository and SQLite reports that it doesn't exist.
+     */
+    public void onCreate(SQLiteDatabase db) {
+
+        // Creates the main table
+        db.execSQL(SQL_CREATE_MAIN);
+    }
+}
+
+ + + +

Реализация типов MIME для класса ContentProvider

+

+ В классе {@link android.content.ContentProvider} предусмотрены два метода для возврата типов MIME: +

+
+
+ {@link android.content.ContentProvider#getType(Uri) getType()} +
+
+ Один из необходимых методов, который требуется реализовать для каждого поставщика. +
+
+ {@link android.content.ContentProvider#getStreamTypes(Uri, String) getStreamTypes()} +
+
+ Метод, который нужно реализовать в случае, если поставщик предоставляет файлы. +
+
+

Типы MIME для таблиц

+

+ Метод {@link android.content.ContentProvider#getType(Uri) getType()} возвращает объект +{@link java.lang.String} в формате MIME, который описывает тип данных, возвращаемых аргументом +URI контента. Аргумент {@link android.net.Uri} может выступать в качестве шаблона, а не в виде определенного URI; +в этом случае необходимо возвратить тип данных, связанный с URI контента, который соответствует +шаблону. +

+

+ Для общих типов данных, таких как текст, HTML или JPEG, метод +{@link android.content.ContentProvider#getType(Uri) getType()} должен возвращать стандартный тип +MIME. Полный список стандартных типов представлен на +веб-сайте +IANA MIME Media Types. +

+

+ Для URI контента, которые указывают на одну или несколько строк табличных данных, метод +{@link android.content.ContentProvider#getType(Uri) getType()} должен возвращать +тип MIME в формате MIME поставщика, который имеется в системе Android: +

+
    +
  • + Часть типа: vnd +
  • +
  • + Часть подтипа: +
      +
    • + Если шаблон URI предназначен для одной строки: android.cursor.item/ +
    • +
    • + Если шаблон URI предназначен для нескольких строк: android.cursor.dir/ +
    • +
    +
  • +
  • + Часть поставщика: vnd.<name>.<type> +

    + Вы указываете <name> и <type>. + Значение <name> должно быть уникальным глобально, +а значение <type> должно быть уникальным для соответствующего шаблона +URI. В качестве <name> рекомендуется использовать название вашей компании +или часть названия пакета Android вашего приложения. В качестве +<type> рекомендуется использовать строку, которая определяет связанную с +URI таблицу. +

    + +
  • +
+

+ Например, если центром поставщика является +com.example.app.provider, который предоставляет таблицу +table1, то тип MIME для нескольких строк в таблице table1 будет следующим: +

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

+ Для одной строки в таблице table1 тип MIME будет следующим: +

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

Типы MIME для файлов

+

+ Если поставщик предоставляет файлы, необходимо реализовать метод +{@link android.content.ContentProvider#getStreamTypes(Uri, String) getStreamTypes()}. + Этот метод возвращает массив {@link java.lang.String} с типами MIME для файлов, +возвращаемых поставщиком для заданного URI контента. Предлагаемые поставщиком типы следует сортировать с помощью аргумента фильтра типов MIME, +чтобы возвращались только те типы MIME, которые необходимо обработать клиенту. +

+

+ Например, рассмотрим поставщик, который предоставляет фотографии в виде файлов в форматах .jpg, +.png и .gif. + Если приложение вызывает метод {@link android.content.ContentResolver#getStreamTypes(Uri, String) +ContentResolver.getStreamTypes()} со строкой фильтра image/* (нечто вроде «изображения»), +то метод {@link android.content.ContentProvider#getStreamTypes(Uri, String) +ContentProvider.getStreamTypes()} +должен возвращать следующий массив: +

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

+ Если же приложению требуются только файлы .jpg, то оно вызывает метод +{@link android.content.ContentResolver#getStreamTypes(Uri, String) +ContentResolver.getStreamTypes()} со строкой фильтра *\/jpeg; метод +{@link android.content.ContentProvider#getStreamTypes(Uri, String) +ContentProvider.getStreamTypes()} при этом должен возвращать следующее: +

+{"image/jpeg"}
+
+

+ Если поставщик не предоставляет ни один из типов MIME, запрошенных в строке фильтра, то метод +{@link android.content.ContentProvider#getStreamTypes(Uri, String) getStreamTypes()} +должен возвращать null. +

+ + + +

Реализация класса-контракта

+

+ Класс-контракт представляет собой класс public final, в котором содержатся определения констант для +URI, имен столбцов, типов MIME и других метаданных поставщика. Класс +устанавливает контрактные отношения между поставщиком и другими приложениями путем обеспечения +прямого доступа к поставщику даже в случае изменения фактических значений URI, имен столбцов и +т. д. +

+

+ Класс-контракт также полезен для разработчиков тем, что в нем содержатся мнемонические имена для его констант, +благодаря чему снижается риск того, что разработчики воспользуются неправильными значениями для имен столбцов или URI. Поскольку это класс, +он может содержать документацию Javadoc. Интегрированные среды разработки, такие как +Eclipse, могут автоматически заполнять имена констант из класса-контракта и отображать Javadoc для +констант. +

+

+ У разработчиков нет доступа к файлу класса-контракта из вашего приложения, однако они могут +статически скомпилировать класс-контракт в свое приложение из предоставленного вами файла .jar. +

+

+ Примом класса-контракта может служить класс +{@link android.provider.ContactsContract} и его вложенные классы. +

+

Реализация разрешений поставщика контента

+

+ Разрешения и доступ ко всем компонентам системы Android подробно описаны в статье +Безопасность и разрешения. + Кроме того, в статье Хранилище данных +представлены сведения о безопасности и разрешениях, применяемых к различным типам хранилища. + Ниже приведен краткий обзор основных моментов. +

+
    +
  • + По умолчанию файлы с данными хранятся во внутреннем хранилище устройства и доступны только +вашему приложению и поставщику. +
  • +
  • + Создаваемые вами базы данных {@link android.database.sqlite.SQLiteDatabase} также доступны только вашему +приложению и поставщику. +
  • +
  • + Файлы с данными, которые вы сохраняете во внешнем хранилище, по умолчанию являются общедоступными, которые +может считать любой пользователь. Вам не удастся использовать поставщик контента для ограничения доступа к файлам, +которые хранятся во внешнем хранилище, поскольку приложения могут использовать другие вызовы API для их чтения или записи. +
  • +
  • + Вызовы метода для открытия или создания файлов или баз данных SQLite, находящихся во внутреннем хранилище на вашем устройстве, +потенциально могут предоставить всем другим приложениям доступ как на запись, так и на чтение данных. Если вы используете +внутренний файл или базу данных в качестве репозитория поставщика, выполнить чтение или запись данных в котором может +любой пользователь, то разрешений, заданных в манифесте поставщика, будет явно недостаточно для +защиты ваших данных. По умолчанию доступ к файлам и базам данных +во внутреннем хранилище является закрытым, и вам не следует изменять параметры доступа к репозиторию вашего поставщика. +
  • +
+

+ При необходимости использовать разрешения поставщика контента для управления доступом к данным, данные +следует хранить во внутренних файлах, в базах данных SQLite или в облаке (например, +на удаленном сервере), а доступ к файлам и базам данных должен быть предоставлен только вашему приложению. +

+

Реализация разрешений

+

+ Любое приложение может выполнять чтение данных в поставщике или записывать их, даже если соответствующие данные +являются закрытыми, поскольку по умолчанию для поставщика не заданы разрешения. Чтобы изменить эти настройки, +задайте разрешения для поставщика в файле манифеста с помощью атрибутов элемента + + <provider> или его дочерних элементов. Можно задать разрешения, которые применяются ко всему поставщику +или только к определенным таблицам, либо даже только к определенным записям или всему дереву. +

+

+ Для задания разрешений используется один или несколько +элементов + <permission> в файле манифеста. Чтобы разрешения +были уникальными для поставщика, используйте области, аналогичные Java, для атрибута + + android:name. Например, присвойте разрешению на чтение имя +com.example.app.provider.permission.READ_PROVIDER. + +

+

+ Ниже перечислены области разрешений для поставщика, начиная с разрешений, +которые применяются ко всему поставщику, и заканчивая более подробными разрешениями. + Более подробные разрешения имеют преимущество над разрешениями с более широкими областями. +

+
+
+ Единичное разрешение на чтение/запись на уровне поставщика +
+
+ Единичное разрешение, которое управляет доступом как на чтение, так и на запись для всего поставщика, которое задается с помощью атрибута + + android:permission элемента + + <provider>. +
+
+ Отдельное разрешение на чтение/запись на уровне поставщика +
+
+ Разрешение на чтение и запись для всего поставщика. Такие разрешения задаются с помощью атрибутов + + android:readPermission и + + android:writePermission элемента + + <provider>. Они имеют преимущественную силу над разрешением, заданным с помощью атрибута + + android:permission. +
+
+ Разрешение на уровне пути +
+
+ Разрешение на чтение, запись или чтение/запись для URI контента в поставщике. Каждый +URI, которым необходимо управлять, задается с помощью дочернего элемента + + <path-permission> элемента + + <provider>. Для каждого указываемого URI контента можно задать разрешение +на чтение/запись, только чтение или только запись, либо все три разрешения. Разрешения на чтение и +запись имеют преимущественную силу над разрешением на чтение/запись. Кроме того, разрешения на уровне пути +имеют преимущественную силу над разрешениями на уровне поставщика. +
+
+ Временное разрешение +
+
+ Разрешения этого уровня предоставляют приложению временный доступ, даже если у приложения +нет разрешений, которые обычно требуются. Функция временного доступа +ограничивает набор разрешений, которые приложению необходимо запросить в своем +манифесте. Если включены временные разрешения, единственными приложениями, +которым требуются «постоянные» разрешения на работу с поставщиком, являются те, которые непрерывно получают доступ ко всем вашим +данным. +

+ Рассмотрим пример с разрешениями, которые необходимо реализовать для поставщика электронной почты и приложения, когда вам +необходимо разрешить внешнему приложению для просмотра изображений отображать вложенные в письма фотографии +из поставщика. Чтобы предоставить средству просмотра изображений требуемый доступ без запроса разрешений, +задайте временные разрешения для URI контента фотографий. Спроектируйте ваше приложение для работы с электронной почтой таким образом, +чтобы в случаях, когда пользователь желает отобразить фотографию, приложение отправляло намерение, в котором содержится +URI контента фотографии и флаги разрешения для средства просмотра изображений. Затем средство просмотра изображений +может отправить поставщику эл. почты запрос на получение фотографии, даже если у средства просмотра отсутствует обычное +разрешение на чтение данных из поставщика. +

+

+ Чтобы включить временные разрешения, задайте атрибут + + android:grantUriPermissions для элемента + + <provider>, либо добавьте один или несколько дочерних элементов + + <grant-uri-permission>в ваш элемент + + <provider>. Если вы используете временные разрешения, вам необходимо вызывать метод +{@link android.content.Context#revokeUriPermission(Uri, int) +Context.revokeUriPermission()} каждый раз, когда вы осуществляете удаленную поддержку URI контента +из поставщика, а URI контента связан с временным разрешением. +

+

+ Значение атрибута определяет, какая часть поставщика доступна. + Если для атрибута задано значение true, система предоставит временные +разрешения для всего поставщика, отменяя тем самым любые другие разрешения, которые требуются +на уровне поставщика или на уровне пути. +

+

+ Если для флага задано значение false, вам необходимо добавить дочерние элементы + + <grant-uri-permission> в свой элемент + + <provider>. Каждый дочерний элемент задает URI контента, +для которых предоставляется временное разрешение. +

+

+ Чтобы делегировать приложению временный доступ, в намерении должны быть указаны флаги +{@link android.content.Intent#FLAG_GRANT_READ_URI_PERMISSION} или +{@link android.content.Intent#FLAG_GRANT_WRITE_URI_PERMISSION} , либо оба флага. Эти флаги задаются с помощью метода +{@link android.content.Intent#setFlags(int) setFlags()}. +

+

+ Если атрибут + android:grantUriPermissions отсутствует, предполагается, что его значение +false. +

+
+
+ + + + +

Элемент <provider>

+

+ Как и компоненты {@link android.app.Activity} и {@link android.app.Service}, +подкласс класса {@link android.content.ContentProvider} +должен быть определен в файле манифеста приложения с помощью элемента + + <provider>. Ниже указана информация, которую система Android получает из +этого элемента. +

+
+ Центр поставщика +({@code +android:authorities}) +
+
+ Символические имена, которые идентифицируют весь поставщик в системе. Дополнительные сведения об этом атрибуте представлены в +разделе +Проектирование URI контента. +
+
+ Название класса поставщика +( +android:name + ) +
+
+ Класс, который реализует класс {@link android.content.ContentProvider}. Дополнительные сведения об этом классе представлены в +разделе +Реализация класса ContentProvider. +
+
+ Разрешения +
+
+ Атрибуты, которые определяют разрешения, необходимые другим приложениям для +доступа к данным в поставщике: + +

+ Дополнительные сведения о разрешениях и соответствующих атрибутах представлены в +разделе +Реализация разрешений поставщика контента. +

+
+
+ Атрибуты запуска и управления +
+
+ Следующие атрибуты определяют порядок и время запуска поставщика системой Android, характеристики +процесса поставщика, а также другие параметры выполнения: +
    +
  • + + android:enabled: флаг, позволяющий системе запускать поставщик; +
  • +
  • + + android:exported: флаг, позволяющий другим приложениям использовать этот поставщик; +
  • +
  • + + android:initOrder: порядок запуска поставщика +(относительно других поставщиков в одном и том же процессе); +
  • +
  • + + android:multiProcess: флаг, позволяющий системе запускать поставщик +в том же процессе, что и вызывающий клиент; +
  • +
  • + + android:process: название процесса, в котором +запускается поставщик; +
  • +
  • + + android:syncable: флаг, указывающий на то, что данные в поставщике +следует синхронизировать с данными на сервере. +
  • +
+

+ Полная документация по атрибутам представлена в статье, посвященной +элементу + + <provider>. +

+
+
+ Информационные атрибуты +
+
+ Дополнительный значок и метка для поставщика: +
    +
  • + + android:icon: графический ресурс, содержащий значок для поставщика. + Значок отображается рядом с меткой поставщика в списке приложений в разделе +Настройки > Приложения > Все. +
  • +
  • + + android:label: информационная метка с описанием поставщика или его +данных, либо обоих описаний. Метка отображается в списке приложений в разделе +Настройки > Приложения > Все. +
  • +
+

+ Полная документация по атрибутам представлена в статье, посвященной +элементу + <provider>. +

+
+
+ + +

Намерения и доступ к данным

+

+ Приложения могут получать доступ к поставщику контента в обход с помощью объектов {@link android.content.Intent}. + Приложение при этом не вызывает какие-либо методы классов {@link android.content.ContentResolver} или +{@link android.content.ContentProvider}. Вместо этого оно отправляет намерение, запускающе операцию, +которая обычно является частью собственного приложения поставщика. Получение и отображение данных в своем пользовательском интерфейсе +выполняет конечная операция. В зависимости от действия, указанного в намерении, +конечная операция также может предложить пользователю внести изменения в данные поставщика. + В намерении также могут содержаться дополнительные данные, которые конечная операция отображает в +пользовательском интерфейсе; затем пользователю предлагается возможность изменить эти данные, прежде чем использовать их для изменения +данных в поставщике. +

+

+ +

+

+ Возможно, доступ с помощью намерения потребуется использовать для обеспечения целостности данных. Для вставки, +обновления и удаления данных в поставщике может существовать строго определенный программный код, реализующий его функциональные возможности. В +этом случае предоставление другим приложениям прямого доступа для изменения данных +может привести к тому, что данные станут недействительными. Если вы хотите предоставить разработчикам возможность доступа посредством намерений, вам следует тщательно задокументировать такую функцию. + Объясните им, почему доступ посредством намерений через пользовательский интерфейс вашего приложения намного лучше изменения +данных посредством их кода. +

+

+ Обработка входящего намерения для изменения данных поставщика ничем не отличается +от обработки других намерений. Дополнительные сведения об использовании намерений представлены в статье +Объекты Intent и фильтры объектов Intent. +

diff --git a/docs/html-intl/intl/ru/guide/topics/providers/content-providers.jd b/docs/html-intl/intl/ru/guide/topics/providers/content-providers.jd new file mode 100644 index 0000000000000000000000000000000000000000..3d7b6515c8a5240e568a0eab926a0b9243c27a72 --- /dev/null +++ b/docs/html-intl/intl/ru/guide/topics/providers/content-providers.jd @@ -0,0 +1,108 @@ +page.title=Поставщики контента +@jd:body + +

+ Поставщики контента управляют доступом к структурированному набору данных. Они инкапсулируют данные +и предоставляют механизмы обеспечения их безопасности. Поставщики контента представляют собой стандартный +интерфейс для объединения данных в одном процессе с кодом, который выполняется в другом процессе. +

+

+ Когда вам требуется доступ к данным в поставщике контента, используйте объект +{@link android.content.ContentResolver} в интерфейсе +{@link android.content.Context} вашего приложения, чтобы подключиться к поставщику как клиент. + Объект {@link android.content.ContentResolver} взаимодействует с объектом поставщика, который представляет собой экземпляр класса, реализующий объект +{@link android.content.ContentProvider}. Объект +поставщика получает от клиентов запросы данных, выполняет запрашиваемые действия и +возвращает результаты. +

+

+ Вам не нужно разрабатывать собственный поставщик, если вы не планируете предоставлять доступ к своим данным +другим приложениям. Однако вам потребуется собственный поставщик для предоставления настраиваемых +поисковых подсказок в вашем собственном приложении. Вам также потребуется собственный поставщик, если вы хотите копировать и вставлять сложные данные или файлы из своего приложения +в другие приложения. +

+

+ В состав системы Android входят поставщики контента, которые управляют такими данными, как аудио, видео, изображения и +личная контактная информация. Некоторые из поставщиков указаны в справочной документации для +пакета +android.provider + . Работать с этими поставщиками может любое приложение Android +(однако с некоторыми ограничениями). +

+ Ниже перечислены статьи, в которых представлено более подробное описание поставщиков контента. +

+
+
+ +Основные сведения о поставщике контента +
+
+ Сведения о доступе к данным в поставщике контента, которые представлены в таблицах. +
+
+ +Создание поставщика контента +
+
+ Сведения о создании своего собственного поставщика контента. +
+
+ +Поставщик календаря +
+
+ Сведения о доступе к поставщику календаря, который входит в состав платформы Android. +
+
+ +Поставщик контактов +
+
+ Сведения о доступе к поставщику контактов, который входит в состав платформы Android. +
+
diff --git a/docs/html-intl/intl/ru/guide/topics/providers/document-provider.jd b/docs/html-intl/intl/ru/guide/topics/providers/document-provider.jd new file mode 100644 index 0000000000000000000000000000000000000000..c594968490d4bd9db62f851df077e99dc7afa034 --- /dev/null +++ b/docs/html-intl/intl/ru/guide/topics/providers/document-provider.jd @@ -0,0 +1,916 @@ +page.title=Платформа доступа к хранилищу (Storage Access Framework) +@jd:body + + + +

Платформа доступа к хранилищу (Storage Access Framework, SAF) впервые появилась в Android версии 4.4 (API уровня 19). Платформа SAF + облегчает пользователям поиск и открытие документов, изображений и других файлов +в хранилищах всех поставщиков, с которыми они работают. Стандартный удобный интерфейс +позволяет пользователям применять единый для всех приложений и поставщиков способ поиска файлов и доступа к последним добавленным файлам.

+ +

Облачные или локальные службы хранения могут присоединиться к этой экосистеме, реализовав +класс {@link android.provider.DocumentsProvider}, инкапсулирующий их услуги. Клиентские +приложения, которым требуется доступ к документам поставщика, могут интегрироваться с SAF с помощью всего нескольких +строчек кода.

+ +

Платформа SAF включает в себя следующие компоненты:

+ +
    +
  • Поставщик документов—поставщик контента, позволяющий +службе хранения (например, Диск Google) показывать файлы, которыми он управляет. Поставщик документов +реализуется как подкласс класса{@link android.provider.DocumentsProvider}. +Его схема основана на традиционной файловой иерархии, +однако физический способ хранения данных в поставщике документов остается на усмотрении разработчика. + Платформа Android включает в себя несколько встроенных поставщиков документов, таких как +Загрузки, Изображения и Видео.
  • + +
  • Клиентское приложение—пользовательское приложение, вызывающее намерение +{@link android.content.Intent#ACTION_OPEN_DOCUMENT} и/или +{@link android.content.Intent#ACTION_CREATE_DOCUMENT} и принимающее +файлы, возвращаемые поставщиками документов.
  • + +
  • Элемент выбора—системный пользовательский интерфейс, обеспечивающий пользователям доступ к документам у всех +поставщиков документов, которые удовлетворяют критериям поиска, заданным в клиентском приложении.
  • +
+ +

Платформа SAF в числе прочих предоставляет следующие функции:

+
    +
  • позволяет пользователям искать контент у всех поставщиков документов, а не только у одного приложения;
  • +
  • обеспечивает приложению возможность долговременного, постоянного доступа к + документам, принадлежащим поставщику документов. Благодаря такому доступу пользователи могут добавлять, редактировать, + сохранять и удалять файлы, хранящиеся у поставщика;
  • +
  • поддерживает несколько учетных записей и временные корневые каталоги, например, поставщики +на USB-накопителях, которые появляются, только когда накопитель вставлен в порт.
  • +
+ +

Обзор

+ +

В центре платформы SAF находится поставщик контента, являющийся +подклассом класса {@link android.provider.DocumentsProvider}. Внутри поставщика документовданные имеют +структуру традиционной файловой иерархии:

+

data model

+

Рисунок 1. Модель данных поставщика документов. На рисунке Root (Корневой каталог) указывает на один объект Document (Документ), +который затем разветвляется в целое дерево.

+ +

Обратите внимание на следующее.

+
    + +
  • Каждый поставщик документов предоставляет один или несколько +«корневых каталогов», являющихся отправными точками при обходе дерева документов. +Каждый корневой каталог имеет уникальный идентификатор {@link android.provider.DocumentsContract.Root#COLUMN_ROOT_ID} +и указывает на документ (каталог), +представляющий содержимое на уровне ниже корневого. +Корневые каталоги динамичны по своей конструкции, чтобы обеспечивать поддержку таким вариантам использования, как несколько учетных записей, +временные хранилища на USB-нкопителях и возможность для пользователя войти в систему и выйти из нее.
  • + +
  • В каждом корневом каталоге находится один документ. Этот документ указывает на количество документов N +каждый из которых, в свою очередь, может указывать на один или N документов.
  • + +
  • Каждый сервер хранилища показывает +отдельные файлы и каталоги, ссылаясь на них с помощью уникального +идентификатора {@link android.provider.DocumentsContract.Document#COLUMN_DOCUMENT_ID}. +Идентификаторы документов должны быть уникальными и не меняться после присвоения, поскольку они используются для выдачи постоянных +URI, не зависящих от перезагрузки устройства.
  • + + +
  • Документ — это или открываемый файл (имеющий конкретный MIME-тип), или +каталог, содержащий другие документы (с +MIME-типом {@link android.provider.DocumentsContract.Document#MIME_TYPE_DIR}).
  • + +
  • Каждый документ может иметь различные свойства, описываемые флагами +{@link android.provider.DocumentsContract.Document#COLUMN_FLAGS COLUMN_FLAGS}, +такими как{@link android.provider.DocumentsContract.Document#FLAG_SUPPORTS_WRITE}, +{@link android.provider.DocumentsContract.Document#FLAG_SUPPORTS_DELETE} и +{@link android.provider.DocumentsContract.Document#FLAG_SUPPORTS_THUMBNAIL}. +Документ с одним и тем же идентификатором {@link android.provider.DocumentsContract.Document#COLUMN_DOCUMENT_ID} может находиться +в нескольких каталогах.
  • +
+ +

Поток управления

+

Как было сказано выше, модель данных поставщика документов основана на традиционной +файловой иерархии. Однако физический способ хранения данных остается на усмотрение разработчика, при +условии, что к ним можно обращаться через API-интерфейс {@link android.provider.DocumentsProvider}. Например, можно +использовать для данных облачное хранилище на основе тегов.

+ +

На рисунке 2 показан пример того, как приложение для обработки фотографий может использовать SAF +для доступа к сохраненным данным:

+

app

+ +

Рисунок 2. Поток управления Storage Access Framework

+ +

Обратите внимание на следующее.

+
    + +
  • На платформе SAF поставщики и клиенты не взаимодействуют +напрямую. Клиент запрашивает разрешение на взаимодействие +с файлами (то есть, на чтение, редактирование, создание или удаление файлов).
  • + +
  • Взаимодействие начинается, когда приложение (в нашем примере обрабатывающее фотографии) активизирует намерение +{@link android.content.Intent#ACTION_OPEN_DOCUMENT} или {@link android.content.Intent#ACTION_CREATE_DOCUMENT}. Намерение может включать в себя фильтры +для уточнения критериев, например, «предоставить открываемые файлы +с MIME-типом image».
  • + +
  • Когда намерение срабатывает, системный элемент выбора переходит к каждому зарегистрированному поставщику +и показывает пользователю корневые каталоги с контентом, соответствующим запросу.
  • + +
  • Элемент выбора предоставляет пользователю стандартный интерфейс, даже +если поставщики документов значительно различаются. В качестве примера на рисунке 2 +изображены Диск Google, поставщик на USB-накопителе и облачный поставщик.
  • +
+ +

На рисунке 3 показан элемент выбора, в котором пользователь для поиска изображений выбрал учетную запись +Диск Google:

+ +

picker

+ +

Рисунок 3. Элемент выбора

+ +

Когда пользователь выбирает Диск Google, изображения отображаются, как показано на +рисунке 4. С этого момента пользователь может взаимодействовать с ними любыми способами, + которые поддерживаются поставщиком и клиентским приложением. + +

picker

+ +

Рисунок 4. Изображения

+ +

Создание клиентского приложения

+ +

В Android версии 4.3 и ниже для того, чтобы приложение могло получать файл от другого +приложения, оно должно активизировать намерение, например, {@link android.content.Intent#ACTION_PICK} +или {@link android.content.Intent#ACTION_GET_CONTENT}. После этого пользователь должен выбрать +какое-либо одно приложение, чтобы получить файл, а оно должно предоставить пользователю +интерфейс, с помощью которого он сможет выбирать и получать файлы.

+ +

Начиная с Android 4.4 и выше, у разработчика имеется дополнительная возможность — намерение +{@link android.content.Intent#ACTION_OPEN_DOCUMENT}, +которое отображает пользовательский интерфейс элемента выбора, управляемого системой. Этот элемент предоставляет пользователю +обзор всех файлов, доступных в других приложениях. Благодаря этому единому интерфейсу, +пользователь может выбрать файл в любом из поддерживаемых приложений.

+ +

Намерение {@link android.content.Intent#ACTION_OPEN_DOCUMENT} не +является заменой для намерения {@link android.content.Intent#ACTION_GET_CONTENT}. + Разработчику следует использовать то, которое лучше соответствует потребностям приложения:

+ +
    +
  • используйте {@link android.content.Intent#ACTION_GET_CONTENT}, если приложению нужно просто +прочитать или импортировать данные. При таком подходе приложение импортирует копию данных, + например, файл с изображением.
  • + +
  • используйте {@link android.content.Intent#ACTION_OPEN_DOCUMENT}, если +приложению нужна возможность долговременного, постоянного доступа к документам, принадлежащим поставщику +документов. В качестве примера можно назвать редактор фотографий, позволяющий пользователям обрабатывать +изображения, хранящиеся в поставщике документов.
  • + +
+ + +

В этом разделе показано, как написать клиентское приложение, использующее намерения +{@link android.content.Intent#ACTION_OPEN_DOCUMENT} и +{@link android.content.Intent#ACTION_CREATE_DOCUMENT}.

+ + + + +

+В следующем фрагменте кода намерение {@link android.content.Intent#ACTION_OPEN_DOCUMENT} +используется для поиска поставщиков документов, +содержащих файлы изображений:

+ +
private static final int READ_REQUEST_CODE = 42;
+...
+/**
+ * Fires an intent to spin up the "file chooser" UI and select an image.
+ */
+public void performFileSearch() {
+
+    // ACTION_OPEN_DOCUMENT is the intent to choose a file via the system's file
+    // browser.
+    Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
+
+    // Filter to only show results that can be "opened", such as a
+    // file (as opposed to a list of contacts or timezones)
+    intent.addCategory(Intent.CATEGORY_OPENABLE);
+
+    // Filter to show only images, using the image MIME data type.
+    // If one wanted to search for ogg vorbis files, the type would be "audio/ogg".
+    // To search for all documents available via installed storage providers,
+    // it would be "*/*".
+    intent.setType("image/*");
+
+    startActivityForResult(intent, READ_REQUEST_CODE);
+}
+ +

Обратите внимание на следующее.

+
    +
  • Когда приложение активизирует намерение {@link android.content.Intent#ACTION_OPEN_DOCUMENT} +, оно запускает элемент выбора, отображающий всех поставщиков документов, соответствующих заданным критериям.
  • + +
  • Добавление категории {@link android.content.Intent#CATEGORY_OPENABLE} в +фильтры намерения приводит к отображению только тех документов, которые можно открыть, например, файлов с изображениями.
  • + +
  • Оператор {@code intent.setType("image/*")} выполняет дальнейшую фильтрацию, чтобы +отображались только документы с MIME-типом image.
  • +
+ +

Обработка результатов

+ +

Когда пользователь выбирает документ в элементе выбора, +вызывается метод {@link android.app.Activity#onActivityResult onActivityResult()}. +Идентификатор URI, указывающий на выбранный документ, содержится в параметре{@code resultData}. + Чтобы извлечь URI, следует вызвать {@link android.content.Intent#getData getData()}. +Этот URI можно использовать для получения документа, нужного пользователю. Например: +

+ +
@Override
+public void onActivityResult(int requestCode, int resultCode,
+        Intent resultData) {
+
+    // The ACTION_OPEN_DOCUMENT intent was sent with the request code
+    // READ_REQUEST_CODE. If the request code seen here doesn't match, it's the
+    // response to some other intent, and the code below shouldn't run at all.
+
+    if (requestCode == READ_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
+        // The document selected by the user won't be returned in the intent.
+        // Instead, a URI to that document will be contained in the return intent
+        // provided to this method as a parameter.
+        // Pull that URI using resultData.getData().
+        Uri uri = null;
+        if (resultData != null) {
+            uri = resultData.getData();
+            Log.i(TAG, "Uri: " + uri.toString());
+            showImage(uri);
+        }
+    }
+}
+
+ +

Изучение метаданных документа

+ +

Имея в своем распоряжении URI документа, разработчик получает доступ к его метаданным. В следующем +фрагменте кода метаданные документа, определяемого идентификатором URI, считываются и записываются в журнал:

+ +
public void dumpImageMetaData(Uri uri) {
+
+    // The query, since it only applies to a single document, will only return
+    // one row. There's no need to filter, sort, or select fields, since we want
+    // all fields for one document.
+    Cursor cursor = getActivity().getContentResolver()
+            .query(uri, null, null, null, null, null);
+
+    try {
+    // moveToFirst() returns false if the cursor has 0 rows.  Very handy for
+    // "if there's anything to look at, look at it" conditionals.
+        if (cursor != null && cursor.moveToFirst()) {
+
+            // Note it's called "Display Name".  This is
+            // provider-specific, and might not necessarily be the file name.
+            String displayName = cursor.getString(
+                    cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME));
+            Log.i(TAG, "Display Name: " + displayName);
+
+            int sizeIndex = cursor.getColumnIndex(OpenableColumns.SIZE);
+            // If the size is unknown, the value stored is null.  But since an
+            // int can't be null in Java, the behavior is implementation-specific,
+            // which is just a fancy term for "unpredictable".  So as
+            // a rule, check if it's null before assigning to an int.  This will
+            // happen often:  The storage API allows for remote files, whose
+            // size might not be locally known.
+            String size = null;
+            if (!cursor.isNull(sizeIndex)) {
+                // Technically the column stores an int, but cursor.getString()
+                // will do the conversion automatically.
+                size = cursor.getString(sizeIndex);
+            } else {
+                size = "Unknown";
+            }
+            Log.i(TAG, "Size: " + size);
+        }
+    } finally {
+        cursor.close();
+    }
+}
+
+ +

Открытие документа

+ +

Получив URI документа, разработчик может открывать его и в целом +делать с ним всё, что угодно.

+ +

Объект растровых изображений

+ +

Приведем пример кода для открытия объекта {@link android.graphics.Bitmap}:

+ +
private Bitmap getBitmapFromUri(Uri uri) throws IOException {
+    ParcelFileDescriptor parcelFileDescriptor =
+            getContentResolver().openFileDescriptor(uri, "r");
+    FileDescriptor fileDescriptor = parcelFileDescriptor.getFileDescriptor();
+    Bitmap image = BitmapFactory.decodeFileDescriptor(fileDescriptor);
+    parcelFileDescriptor.close();
+    return image;
+}
+
+ +

Обратите внимание, что не следует производить эту операцию в потоке пользовательского интерфейса. Ее нужно выполнять +в фоне, с помощью {@link android.os.AsyncTask}. Когда файл с растровым изображением откроется, его +можно отобразить в виджете {@link android.widget.ImageView}. +

+ +

Получение объекта InputStream

+ +

Далее приведен пример того, как можно получить объект {@link java.io.InputStream} по идентификатору URI. В этом +фрагменте кода строчки файла считываются в объект строкового типа:

+ +
private String readTextFromUri(Uri uri) throws IOException {
+    InputStream inputStream = getContentResolver().openInputStream(uri);
+    BufferedReader reader = new BufferedReader(new InputStreamReader(
+            inputStream));
+    StringBuilder stringBuilder = new StringBuilder();
+    String line;
+    while ((line = reader.readLine()) != null) {
+        stringBuilder.append(line);
+    }
+    fileInputStream.close();
+    parcelFileDescriptor.close();
+    return stringBuilder.toString();
+}
+
+ +

Создание нового документа

+ +

Приложение может создать новый документ в поставщике документов, используя намерение +{@link android.content.Intent#ACTION_CREATE_DOCUMENT} +. Чтобы создать файл, нужно указать в намерении MIME-тип и имя файла, а затем +запустить его с уникальным кодом запроса. Об остальном позаботится платформа:

+ + +
+// Here are some examples of how you might call this method.
+// The first parameter is the MIME type, and the second parameter is the name
+// of the file you are creating:
+//
+// createFile("text/plain", "foobar.txt");
+// createFile("image/png", "mypicture.png");
+
+// Unique request code.
+private static final int WRITE_REQUEST_CODE = 43;
+...
+private void createFile(String mimeType, String fileName) {
+    Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
+
+    // Filter to only show results that can be "opened", such as
+    // a file (as opposed to a list of contacts or timezones).
+    intent.addCategory(Intent.CATEGORY_OPENABLE);
+
+    // Create a file with the requested MIME type.
+    intent.setType(mimeType);
+    intent.putExtra(Intent.EXTRA_TITLE, fileName);
+    startActivityForResult(intent, WRITE_REQUEST_CODE);
+}
+
+ +

После создания нового документа можно получить его URI с помощью +метода {@link android.app.Activity#onActivityResult onActivityResult()}, чтобы иметь возможность +записывать в него данные.

+ +

Удаление документа

+ +

Если у разработчика имеется URI документа, а объект +{@link android.provider.DocumentsContract.Document#COLUMN_FLAGS Document.COLUMN_FLAGS} +этого документа содержит флаг +{@link android.provider.DocumentsContract.Document#FLAG_SUPPORTS_DELETE SUPPORTS_DELETE}, +то документ можно удалить. Например:

+ +
+DocumentsContract.deleteDocument(getContentResolver(), uri);
+
+ +

Редактирование документа

+ +

Платформа SAF позволяет редактировать текстовые документы на месте. +В следующем фрагменте кода активизируется +намерение {@link android.content.Intent#ACTION_OPEN_DOCUMENT}, а +категория {@link android.content.Intent#CATEGORY_OPENABLE} используется, чтобы отображались только +документы, которые можно открыть. Затем производится дальнейшая фильтрация, чтобы отображались только текстовые файлы:

+ +
+private static final int EDIT_REQUEST_CODE = 44;
+/**
+ * Open a file for writing and append some text to it.
+ */
+ private void editDocument() {
+    // ACTION_OPEN_DOCUMENT is the intent to choose a file via the system's
+    // file browser.
+    Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
+
+    // Filter to only show results that can be "opened", such as a
+    // file (as opposed to a list of contacts or timezones).
+    intent.addCategory(Intent.CATEGORY_OPENABLE);
+
+    // Filter to show only text files.
+    intent.setType("text/plain");
+
+    startActivityForResult(intent, EDIT_REQUEST_CODE);
+}
+
+ +

Далее, из метода {@link android.app.Activity#onActivityResult onActivityResult()} +(см. Обработка результатов) можно вызвать код для выполнения редактирования. +В следующем фрагменте кода объект {@link java.io.FileOutputStream} +получен с помощью объекта класса {@link android.content.ContentResolver}. По умолчанию используется режим записи. +Рекомендуется запрашивать минимально необходимые права доступа, поэтому не следует запрашивать +чтение/запись, если приложению требуется только записать файл:

+ +
private void alterDocument(Uri uri) {
+    try {
+        ParcelFileDescriptor pfd = getActivity().getContentResolver().
+                openFileDescriptor(uri, "w");
+        FileOutputStream fileOutputStream =
+                new FileOutputStream(pfd.getFileDescriptor());
+        fileOutputStream.write(("Overwritten by MyCloud at " +
+                System.currentTimeMillis() + "\n").getBytes());
+        // Let the document provider know you're done by closing the stream.
+        fileOutputStream.close();
+        pfd.close();
+    } catch (FileNotFoundException e) {
+        e.printStackTrace();
+    } catch (IOException e) {
+        e.printStackTrace();
+    }
+}
+ +

Удержание прав доступа

+ +

Когда приложение открывает файл для чтения или записи, система предоставляет +ему URI-разрешение на этот файл. Разрешение действует вплоть до перезагрузки устройства. +Предположим, что в графическом редакторе требуется, чтобы у пользователя была возможность +открыть непосредственно в этом приложении последние пять изображений, которые он редактировал. Если он +перезапустил устройство, возникает необходимость снова отсылать его к системному элементу выбора для поиска +файлов. Очевидно, это далеко не идеальный вариант.

+ +

Чтобы избежать такой ситуации, разработчик может удержать права доступа, предоставленные системой +его приложению. Приложение фактически принимает постоянное URI-разрешение, +предлагаемое системой. В результате пользователь получает непрерывный доступ к файлам +из приложения, независимо от перезагрузки устройства:

+ + +
final int takeFlags = intent.getFlags()
+            & (Intent.FLAG_GRANT_READ_URI_PERMISSION
+            | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+// Check for the freshest data.
+getContentResolver().takePersistableUriPermission(uri, takeFlags);
+ +

Остается один заключительный шаг. Можно сохранить последние +URI-идентификаторы, с которыми работало приложение. Однако не исключено, что они потеряют актуальность, поскольку другое приложение +может удалить или модифицировать документ. Поэтому следует всегда вызывать +{@code getContentResolver().takePersistableUriPermission()}, чтобы получать +актуальные данные.

+ +

Создание собственного поставщика документов

+ +

+При разработке приложения, оказывающего услуги по хранению файлов (например, +службы хранения в облаке), можно предоставить доступ к файлам при помощи +SAF, написав собственный поставщик документов. В этом разделе показано, +как это сделать.

+ + +

Манифест

+ +

Чтобы реализовать собственный поставщик документов, необходимо добавить в манифест приложения +следующую информацию:

+
    + +
  • Целевой API-интерфейс уровня 19 или выше.
  • + +
  • Элемент <provider>, в котором объявляется нестандартный поставщик + хранилища.
  • + +
  • Имя поставщика, т. е., имя его класса с именем пакета. +Например: com.example.android.storageprovider.MyCloudProvider.
  • + +
  • Имя центра поставщика, т. е. имя пакета (в этом примере — +com.example.android.storageprovider) с типом поставщика контента +(documents). Например,{@code com.example.android.storageprovider.documents}.
  • + +
  • Атрибут android:exported, установленный в значение "true". + Необходимо экспортировать поставщик, чтобы он был виден другим приложениям.
  • + +
  • Атрибут android:grantUriPermissions, установленный в значение +"true". Этот параметр позволяет системе предоставлять другим приложениям доступ +к контенту поставщика. Обсуждение того, как следует удерживать права доступа +к конкретному документу см. в разделе Удержание прав доступа.
  • + +
  • Разрешение {@code MANAGE_DOCUMENTS}. По умолчанию поставщик доступен +всем. Добавление этого разрешения в манифест делает поставщик доступным только системе. +Это важно для обеспечения безопасности.
  • + +
  • Атрибут {@code android:enabled}, имеющий логическое значение, определенное в файле +ресурсов. Этот атрибут предназначен для отключения поставщика на устройствах под управлением Android версии 4.3 и ниже. + Например: {@code android:enabled="@bool/atLeastKitKat"}. Помимо +включения этого атрибута в манифест, необходимо сделать следующее: +
      +
    • В файл ресурсов {@code bool.xml}, расположенный в каталоге {@code res/values/}, добавить +строчку
      <bool name="atLeastKitKat">false</bool>
    • + +
    • В файл ресурсов {@code bool.xml}, расположенный в каталоге {@code res/values-v19/}, добавить +строчку
      <bool name="atLeastKitKat">true</bool>
    • +
  • + +
  • Фильтр намерения с действием +{@code android.content.action.DOCUMENTS_PROVIDER}, чтобы поставщик +появлялся в элементе выбора, когда система будет искать поставщиков.
  • + +
+

Ниже приведены отрывки из образца манифеста, включающего в себя поставщик:

+ +
<manifest... >
+    ...
+    <uses-sdk
+        android:minSdkVersion="19"
+        android:targetSdkVersion="19" />
+        ....
+        <provider
+            android:name="com.example.android.storageprovider.MyCloudProvider"
+            android:authorities="com.example.android.storageprovider.documents"
+            android:grantUriPermissions="true"
+            android:exported="true"
+            android:permission="android.permission.MANAGE_DOCUMENTS"
+            android:enabled="@bool/atLeastKitKat">
+            <intent-filter>
+                <action android:name="android.content.action.DOCUMENTS_PROVIDER" />
+            </intent-filter>
+        </provider>
+    </application>
+
+</manifest>
+ +

Поддержка устройств под управлением Android версии 4.3 и ниже

+ +

Намерение +{@link android.content.Intent#ACTION_OPEN_DOCUMENT} доступно только +на устройствах с Android версии 4.4 и выше. +Если приложение должно поддерживать {@link android.content.Intent#ACTION_GET_CONTENT}, +чтобы обслуживать устройства, работающие под управлением Android 4.3 и ниже, необходимо +отключить фильтр намерения {@link android.content.Intent#ACTION_GET_CONTENT} в +манифесте для устройств с Android версии 4.4 и выше. Поставщик +документов и намерение {@link android.content.Intent#ACTION_GET_CONTENT} следует считать +взаимоисключающими. Если приложение поддерживает их одновременно, оно +будет появляться в пользовательском интерфейсе системного элемента выбора дважды, предлагая два различных способа доступа +к сохраненным данным. Это запутает пользователей.

+ +

Отключать фильтр намерения +{@link android.content.Intent#ACTION_GET_CONTENT} на устройствах +с Android версии 4.4 и выше рекомендуется следующим образом:

+ +
    +
  1. В файл ресурсов {@code bool.xml}, расположенный в каталоге {@code res/values/}, добавить +следующую строку:
    <bool name="atMostJellyBeanMR2">true</bool>
  2. + +
  3. В файл ресурсов {@code bool.xml}, расположенный в каталоге {@code res/values-v19/}, добавить +следующую строку:
    <bool name="atMostJellyBeanMR2">false</bool>
  4. + +
  5. Добавить +псевдоним +операции, чтобы отключить фильтр намерения {@link android.content.Intent#ACTION_GET_CONTENT} +для версий 4.4 (API уровня 19) и выше. Например: + +
    +<!-- This activity alias is added so that GET_CONTENT intent-filter
    +     can be disabled for builds on API level 19 and higher. -->
    +<activity-alias android:name="com.android.example.app.MyPicker"
    +        android:targetActivity="com.android.example.app.MyActivity"
    +        ...
    +        android:enabled="@bool/atMostJellyBeanMR2">
    +    <intent-filter>
    +        <action android:name="android.intent.action.GET_CONTENT" />
    +        <category android:name="android.intent.category.OPENABLE" />
    +        <category android:name="android.intent.category.DEFAULT" />
    +        <data android:mimeType="image/*" />
    +        <data android:mimeType="video/*" />
    +    </intent-filter>
    +</activity-alias>
    +
    +
  6. +
+

Контракты

+ +

Как правило, при создании нестандартного поставщика контента одной из задач +является реализация классов-контрактов, описанная в руководстве для разработчиков + +Поставщики контента. Класс-контракт представляет собой класс {@code public final}, +в котором содержатся определения констант для URI, имен столбцов, типов MIME и +других метаданных поставщика. Платформа SAF +предоставляет разработчику следующие классы-контракты, так что ему не нужно писать +собственные:

+ +
    +
  • {@link android.provider.DocumentsContract.Document}
  • +
  • {@link android.provider.DocumentsContract.Root}
  • +
+ +

Например, когда +к поставщику документов приходит запрос на документы или корневой каталог, можно возвращать в курсоре следующие столбцы:

+ +
private static final String[] DEFAULT_ROOT_PROJECTION =
+        new String[]{Root.COLUMN_ROOT_ID, Root.COLUMN_MIME_TYPES,
+        Root.COLUMN_FLAGS, Root.COLUMN_ICON, Root.COLUMN_TITLE,
+        Root.COLUMN_SUMMARY, Root.COLUMN_DOCUMENT_ID,
+        Root.COLUMN_AVAILABLE_BYTES,};
+private static final String[] DEFAULT_DOCUMENT_PROJECTION = new
+        String[]{Document.COLUMN_DOCUMENT_ID, Document.COLUMN_MIME_TYPE,
+        Document.COLUMN_DISPLAY_NAME, Document.COLUMN_LAST_MODIFIED,
+        Document.COLUMN_FLAGS, Document.COLUMN_SIZE,};
+
+ +

Создание подкласса класса DocumentsProvider

+ +

Следующим шагом в разработке собственного поставщика документов является создание подкласса +абстрактного класса {@link android.provider.DocumentsProvider}. Как минимум, необходимо +реализовать следующие методы:

+ +
    +
  • {@link android.provider.DocumentsProvider#queryRoots queryRoots()}
  • + +
  • {@link android.provider.DocumentsProvider#queryChildDocuments queryChildDocuments()}
  • + +
  • {@link android.provider.DocumentsProvider#queryDocument queryDocument()}
  • + +
  • {@link android.provider.DocumentsProvider#openDocument openDocument()}
  • +
+ +

Это единственные методы, реализация которых строго обязательна, однако существует +намного больше методов, которые, возможно, тоже придется реализовать. Подробности приводятся в описании класса{@link android.provider.DocumentsProvider} +.

+ +

Реализация метода queryRoots

+ +

Реализация метода {@link android.provider.DocumentsProvider#queryRoots +queryRoots()} должна возвращать объект {@link android.database.Cursor}, указывающий на все +корневые каталоги поставщиков документов, используя столбцы, определенные в +{@link android.provider.DocumentsContract.Root}.

+ +

В следующем фрагменте кода параметр {@code projection} представляет +конкретные поля, нужные вызывающему объекту. В этом коде создается курсор, +и к нему добавляется одна строка, соответствующая одному корневому каталогу (каталогу верхнего уровня), например, +Загрузки или Изображения. Большинство поставщиков имеет только один корневой каталог. Однако ничто не мешает иметь несколько корневых каталогов, +например, при наличии нескольких учетных записей. В этом случае достаточно добавить в +курсор еще одну строку.

+ +
+@Override
+public Cursor queryRoots(String[] projection) throws FileNotFoundException {
+
+    // Create a cursor with either the requested fields, or the default
+    // projection if "projection" is null.
+    final MatrixCursor result =
+            new MatrixCursor(resolveRootProjection(projection));
+
+    // If user is not logged in, return an empty root cursor.  This removes our
+    // provider from the list entirely.
+    if (!isUserLoggedIn()) {
+        return result;
+    }
+
+    // It's possible to have multiple roots (e.g. for multiple accounts in the
+    // same app) -- just add multiple cursor rows.
+    // Construct one row for a root called "MyCloud".
+    final MatrixCursor.RowBuilder row = result.newRow();
+    row.add(Root.COLUMN_ROOT_ID, ROOT);
+    row.add(Root.COLUMN_SUMMARY, getContext().getString(R.string.root_summary));
+
+    // FLAG_SUPPORTS_CREATE means at least one directory under the root supports
+    // creating documents. FLAG_SUPPORTS_RECENTS means your application's most
+    // recently used documents will show up in the "Recents" category.
+    // FLAG_SUPPORTS_SEARCH allows users to search all documents the application
+    // shares.
+    row.add(Root.COLUMN_FLAGS, Root.FLAG_SUPPORTS_CREATE |
+            Root.FLAG_SUPPORTS_RECENTS |
+            Root.FLAG_SUPPORTS_SEARCH);
+
+    // COLUMN_TITLE is the root title (e.g. Gallery, Drive).
+    row.add(Root.COLUMN_TITLE, getContext().getString(R.string.title));
+
+    // This document id cannot change once it's shared.
+    row.add(Root.COLUMN_DOCUMENT_ID, getDocIdForFile(mBaseDir));
+
+    // The child MIME types are used to filter the roots and only present to the
+    //  user roots that contain the desired type somewhere in their file hierarchy.
+    row.add(Root.COLUMN_MIME_TYPES, getChildMimeTypes(mBaseDir));
+    row.add(Root.COLUMN_AVAILABLE_BYTES, mBaseDir.getFreeSpace());
+    row.add(Root.COLUMN_ICON, R.drawable.ic_launcher);
+
+    return result;
+}
+ +

Реализация метода queryChildDocuments

+ +

Реализация метода +{@link android.provider.DocumentsProvider#queryChildDocuments queryChildDocuments()} +должна возвращать объект{@link android.database.Cursor}, указывающий на все файлы в +заданном каталоге, используя столбцы, определенные в +{@link android.provider.DocumentsContract.Document}.

+ +

Этот метод вызывается, когда в интерфейсе элемента выбора пользователь выбирает корневой каталог приложения. +Метод получает документы-потомки каталога на уровне ниже корневого. Его можно вызывать на любом уровне +файловой иерархии, а не только в корневом каталоге. В следующем фрагменте кода +создается курсор с запрошенными столбцами. Затем в него заносится информация о +каждом ближайшем потомке родительского каталога. +Потомком может быть изображение, еще один каталог, в общем, любой файл:

+ +
@Override
+public Cursor queryChildDocuments(String parentDocumentId, String[] projection,
+                              String sortOrder) throws FileNotFoundException {
+
+    final MatrixCursor result = new
+            MatrixCursor(resolveDocumentProjection(projection));
+    final File parent = getFileForDocId(parentDocumentId);
+    for (File file : parent.listFiles()) {
+        // Adds the file's display name, MIME type, size, and so on.
+        includeFile(result, null, file);
+    }
+    return result;
+}
+
+ +

Реализация метода queryDocument

+ +

Реализация метода +{@link android.provider.DocumentsProvider#queryDocument queryDocument()} +должна возвращать объект{@link android.database.Cursor}, указывающий на заданный файл, +используя столбцы, определенные в{@link android.provider.DocumentsContract.Document}. +

+ +

Метод {@link android.provider.DocumentsProvider#queryDocument queryDocument()} +возвращает ту же информацию, которую возвращал +{@link android.provider.DocumentsProvider#queryChildDocuments queryChildDocuments()}, +но для конкретного файла:

+ + +
@Override
+public Cursor queryDocument(String documentId, String[] projection) throws
+        FileNotFoundException {
+
+    // Create a cursor with the requested projection, or the default projection.
+    final MatrixCursor result = new
+            MatrixCursor(resolveDocumentProjection(projection));
+    includeFile(result, documentId, null);
+    return result;
+}
+
+ +

Реализация метода openDocument

+ +

Необходимо реализовать метод {@link android.provider.DocumentsProvider#openDocument +openDocument()}, который возвращает объект {@link android.os.ParcelFileDescriptor}, представляющий +указанный файл. Другие приложения смогут воспользоваться возращенным объектом {@link android.os.ParcelFileDescriptor} +для организации потока данных. Система вызывает этот метод, когда пользователь выбирает файл, +и клиентское приложение запрашивает доступ нему, вызывая +метод {@link android.content.ContentResolver#openFileDescriptor openFileDescriptor()}. +Например:

+ +
@Override
+public ParcelFileDescriptor openDocument(final String documentId,
+                                         final String mode,
+                                         CancellationSignal signal) throws
+        FileNotFoundException {
+    Log.v(TAG, "openDocument, mode: " + mode);
+    // It's OK to do network operations in this method to download the document,
+    // as long as you periodically check the CancellationSignal. If you have an
+    // extremely large file to transfer from the network, a better solution may
+    // be pipes or sockets (see ParcelFileDescriptor for helper methods).
+
+    final File file = getFileForDocId(documentId);
+
+    final boolean isWrite = (mode.indexOf('w') != -1);
+    if(isWrite) {
+        // Attach a close listener if the document is opened in write mode.
+        try {
+            Handler handler = new Handler(getContext().getMainLooper());
+            return ParcelFileDescriptor.open(file, accessMode, handler,
+                        new ParcelFileDescriptor.OnCloseListener() {
+                @Override
+                public void onClose(IOException e) {
+
+                    // Update the file with the cloud server. The client is done
+                    // writing.
+                    Log.i(TAG, "A file with id " +
+                    documentId + " has been closed!
+                    Time to " +
+                    "update the server.");
+                }
+
+            });
+        } catch (IOException e) {
+            throw new FileNotFoundException("Failed to open document with id "
+            + documentId + " and mode " + mode);
+        }
+    } else {
+        return ParcelFileDescriptor.open(file, accessMode);
+    }
+}
+
+ +

Безопасность

+ +

Предположим, что поставщик документов представляет собой защищенную паролем службу хранения в облаке, +а приложение должно убедиться, что пользователь вошел в систему, прежде чем оно предоставит ему доступ к файлам. +Что должно предпринять приложение, если пользователь не выполнил вход? Решение состоит в том, чтобы +реализация метода {@link android.provider.DocumentsProvider#queryRoots +queryRoots()} не возвращала корневых каталогов. Иными словами, это должен быть пустой корневой курсор:

+ +
+public Cursor queryRoots(String[] projection) throws FileNotFoundException {
+...
+    // If user is not logged in, return an empty root cursor.  This removes our
+    // provider from the list entirely.
+    if (!isUserLoggedIn()) {
+        return result;
+}
+
+ +

Следующий шаг состоит в вызове метода {@code getContentResolver().notifyChange()}. +Помните объект {@link android.provider.DocumentsContract}? Воспользуемся им для создания +соответствующего URI. В следующем фрагменте кода система извещается о необходимости опрашивать корневые каталоги +поставщика документов, когда меняется статус входа пользователя в систему. Если пользователь не +выполнил вход, метод {@link android.provider.DocumentsProvider#queryRoots queryRoots()} возвратит +пустой курсор, как показано выше. Это гарантирует, что документы поставщика будут +доступны только пользователям, вошедшим в поставщик.

+ +
private void onLoginButtonClick() {
+    loginOrLogout();
+    getContentResolver().notifyChange(DocumentsContract
+            .buildRootsUri(AUTHORITY), null);
+}
+
\ No newline at end of file diff --git a/docs/html-intl/intl/ru/guide/topics/resources/accessing-resources.jd b/docs/html-intl/intl/ru/guide/topics/resources/accessing-resources.jd new file mode 100644 index 0000000000000000000000000000000000000000..3d59ceb292a2464a2bc16bd88d3426ae00707a9e --- /dev/null +++ b/docs/html-intl/intl/ru/guide/topics/resources/accessing-resources.jd @@ -0,0 +1,337 @@ +page.title=Доступ к ресурсам +parent.title=Ресурсы приложения +parent.link=index.html +@jd:body + +
+
+

Краткое описание

+
    +
  • В коде для создания ссылок на ресурсы можно использовать целочисленные переменные из {@code R.java}, такие как +{@code R.drawable.myimage}
  • +
  • Для создания ссылок на ресурсы из самих ресурсов можно использовать особый синтаксис XML, например {@code +@drawable/myimage}
  • +
  • Также для доступа к ресурсам приложения используются методы класса +{@link android.content.res.Resources}
  • +
+ +

Ключевые классы

+
    +
  1. {@link android.content.res.Resources}
  2. +
+ +

Содержание документа

+
    +
  1. Доступ к ресурсам из кода
  2. +
  3. Доступ к ресурсам из XML +
      +
    1. Ссылка на атрибуты стиля
    2. +
    +
  4. +
  5. Доступ к ресурсам платформы
  6. +
+ +

См. также:

+
    +
  1. Предоставление ресурсов
  2. +
  3. Типы ресурсов
  4. +
+
+
+ + + + +

После того, как вы предоставили ресурс в вашем приложении (см. раздел Предоставление ресурсов), этот ресурс можно применить. Для этого необходимо создать +ссылку на идентификатор ресурса. Для задания всех таких идентификаторов в вашем проекте используется класс {@code R}, который автоматически создается инструментом +{@code aapt}.

+ +

Во время компиляции приложения инструмент {@code aapt} создает класс {@code R}, в котором находятся +идентификаторы для всех ресурсов в каталоге {@code +res/}. Для каждого типа ресурсов предусмотрен подкласс {@code R} (например, +класс {@code R.drawable} для элементов дизайна), а для каждого ресурса указанного типа существует статическая +целочисленная переменная (например, {@code R.drawable.icon}). Эта переменная как раз и служит идентификатором ресурса, которую можно +использовать для его получения.

+ +

Несмотря на то, что в классе {@code R} находятся идентификаторы ресурсов, никогда не обращайтесь к нему +для поиска идентификатора ресурса. Последний состоит из следующих компонентов:

+
    +
  • Тип ресурса: ресурсы объединены по типам, таким как {@code +string}, {@code drawable} и {@code layout}. Дополнительные сведения о различных типах представлены в разделе Типы ресурсов. +
  • +
  • Имя ресурса, в качестве которого выступает либо имя файла +(без расширения), либо значение атрибута XML {@code android:name} (если +ресурс представляет собой простое значение, например строку).
  • +
+ +

Существует два способа доступа к ресурсу.

+
    +
  • Из кода: с помощью статической целочисленной переменной из подкласса вашего класса {@code R} +, например: +
    R.string.hello
    +

    {@code string} — это тип ресурса, а {@code hello} — это его имя. Существует множество API-интерфейсов +Android, которые могут получать доступ к ресурсам, идентификаторы которых указаны в этом формате. См. раздел +Доступ к ресурсам из кода.

    +
  • +
  • Из XML: с помощью особого синтаксиса XML, который также соответствует +идентификатору ресурса, заданному в классе {@code R}, например: +
    @string/hello
    +

    {@code string} — это тип ресурса, а {@code hello} — это его имя. Этот синтаксис можно +использовать в любом фрагменте ресурса XML, где ожидается значение, указанное вами в ресурсе. См. раздел Доступ к ресурсам из XML

    +
  • +
+ + + +

Доступ к ресурсам из кода

+ +

Чтобы использовать ресурс в коде, можно передать идентификатор ресурса в виде параметра метода. Например, с +помощью метода {@link android.widget.ImageView#setImageResource(int) setImageResource()} можно указать использование виджетом {@link android.widget.ImageView} ресурса {@code res/drawable/myimage.png}: +

+
+ImageView imageView = (ImageView) findViewById(R.id.myimageview);
+imageView.setImageResource(R.drawable.myimage);
+
+ +

Отдельные ресурсы также можно получать с помощью методов в классе {@link +android.content.res.Resources}, экземпляр которого можно получить с помощью +метода {@link android.content.Context#getResources()}.

+ + + + +

Синтаксис

+ +

Ниже представлен синтаксис ссылки на ресурс из кода.

+ +
+[<package_name>.]R.<resource_type>.<resource_name>
+
+ +
    +
  • {@code <package_name>} — это имя пакета, в котором находится ресурс (не +требуется при создании ссылок на ресурсы из вашего собственного пакета).
  • +
  • {@code <resource_type>} — это подкласс {@code R} для типа ресурса.
  • +
  • {@code <resource_name>} — это либо имя файла +ресурса (без расширения), либо значение атрибута {@code android:name} в элементе XML (для простых +значений).
  • +
+

Дополнительные сведения о каждом типе +ресурсов и порядке создания ссылок на них см. в разделе Типы ресурсов.

+ + +

Примеры использования

+ +

Существует множество методов, которые могут принимать идентификатор ресурса в виде параметра. Для получения ресурсов можно использовать методы, +представленные в классе {@link android.content.res.Resources}. Можно получить экземпляр {@link +android.content.res.Resources} с помощью {@link android.content.Context#getResources +Context.getResources()}.

+ + +

Вот некоторые примеры доступа к ресурсам из кода:

+ +
+// Load a background for the current screen from a drawable resource
+{@link android.app.Activity#getWindow()}.{@link
+android.view.Window#setBackgroundDrawableResource(int)
+setBackgroundDrawableResource}(R.drawable.my_background_image) ;
+
+// Set the Activity title by getting a string from the Resources object, because
+//  this method requires a CharSequence rather than a resource ID
+{@link android.app.Activity#getWindow()}.{@link android.view.Window#setTitle(CharSequence)
+setTitle}(getResources().{@link android.content.res.Resources#getText(int)
+getText}(R.string.main_title));
+
+// Load a custom layout for the current screen
+{@link android.app.Activity#setContentView(int)
+setContentView}(R.layout.main_screen);
+
+// Set a slide in animation by getting an Animation from the Resources object
+mFlipper.{@link android.widget.ViewAnimator#setInAnimation(Animation)
+setInAnimation}(AnimationUtils.loadAnimation(this,
+        R.anim.hyperspace_in));
+
+// Set the text on a TextView object using a resource ID
+TextView msgTextView = (TextView) findViewById(R.id.msg);
+msgTextView.{@link android.widget.TextView#setText(int)
+setText}(R.string.hello_message);
+
+ + +

Внимание! Никогда не изменяйте файл {@code +R.java} вручную — этот файл создается инструментом {@code aapt} во время компиляции вашего +проекта. Любые внесенные в него изменения перезаписываются после следующей компиляции.

+ + + +

Доступ к ресурсам из XML

+ +

Для задания значений для некоторых атрибутов и элементов XML можно использовать +ссылку на существующий ресурс. Это зачастую требуется при создании файлов макета при +указании строк и изображений для виджетов.

+ +

Например, при добавлении в макет элемента {@link android.widget.Button} необходимо использовать +строковый ресурс для надписи на кнопке:

+ +
+<Button
+    android:layout_width="fill_parent"
+    android:layout_height="wrap_content"
+    android:text="@string/submit" />
+
+ + +

Синтаксис

+ +

Ниже представлен синтаксис ссылки на ресурс из ресурса XML.

+ +
+@[<package_name>:]<resource_type>/<resource_name>
+
+ +
    +
  • {@code <package_name>} — это имя пакета, в котором находится ресурс (не +требуется при создании ссылок на ресурсы из одного и того же пакета).
  • +
  • {@code <resource_type>} — это подкласс +{@code R} для типа ресурса.
  • +
  • {@code <resource_name>} — это либо имя файла +ресурса (без расширения), либо значение атрибута {@code android:name} в элементе XML (для простых +значений).
  • +
+ +

Дополнительные сведения о каждом типе +ресурсов и порядке создания ссылок на них см. в разделе Типы ресурсов.

+ + +

Примеры использования

+ +

В некоторых случаях ресурс необходимо использовать в качестве значения в элементе XML (например, чтобы применить графическое изображение к +виджету), однако вы также можете использовать ресурс в любом фрагменте XML, где ожидается простое значение. Например, +если имеется следующий файл ресурса, включающий цветовой ресурс и строковый ресурс:

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+   <color name="opaque_red">#f00</color>
+   <string name="hello">Hello!</string>
+</resources>
+
+ +

Эти ресурсы можно использовать в следующем файле макета для задания цвета текста и +надписи:

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<EditText xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    android:textColor="@color/opaque_red"
+    android:text="@string/hello" />
+
+ +

В этом случае в ссылке на ресурс не нужно указывать имя пакета, поскольку +ресурсы находятся в вашем собственном пакете. Однако для создания +ссылки на системный ресурс вам потребуется указать имя пакета. Например:

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<EditText xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    android:textColor="@android:color/secondary_text_dark"
+    android:text="@string/hello" />
+
+ +

Примечание. Всегда используйте строковые ресурсы, +поскольку ваше приложение может потребоваться перевести на другие языки. +Сведения о создании альтернативных +ресурсов (например, локализованных строк) представлены в разделе Предоставление альтернативных +ресурсов. В разделе Локализация приведено полное руководство по локализации +приложения.

+ +

Вы даже можете использовать ресурсы в XML для создания псевдонимов. Например, можно создать +элемент дизайна, который будет служить псевдонимом для другого элемента дизайна:

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
+    android:src="@drawable/other_drawable" />
+
+ +

Это может показаться излишним, однако такой подход очень полезен при использовании альтернативных ресурсов. Узнайте подробнее о +создании псевдонимов ресурсов.

+ + + +

Ссылка на атрибуты стиля

+ +

С помощью ресурса атрибута стиля можно создать ссылку на значение +атрибута в текущей примененной теме. Таким образом можно +настраивать внешний вид элементов пользовательского интерфейса, подстраивая их под стандартные варианты элементов оформления +текущей темы, вместо указания жестко заданных значений. Создание ссылки на атрибут стиля +представляет собой своеобразную инструкцию «использовать стиль, заданный этим атрибутом в текущей теме».

+ +

Синтаксис для создания ссылки на атрибут стиля практически идентичен обычному формату +ресурса, только в этом случае вместо символа{@code @} необходимо указать вопросительный знак ({@code ?}), а тип ресурса +вообще необязательно указывать. Например:

+ +
+?[<package_name>:][<resource_type>/]<resource_name>
+
+ +

Ниже представлен пример создания ссылки на атрибут для задания цвета текста в соответствии с +«основным» цветом текста системной темы оформления:

+ +
+<EditText id="text"
+    android:layout_width="fill_parent"
+    android:layout_height="wrap_content"
+    android:textColor="?android:textColorSecondary"
+    android:text="@string/hello_world" />
+
+ +

Здесь атрибут {@code android:textColor} служит для задания имени атрибута стиля +в текущей теме. Теперь в Android используется значение, примененное к атрибуту стиля{@code android:textColorSecondary} +в качестве значения для {@code android:textColor} в этом виджете. Поскольку инструменту для работы с системными +ресурсами известно, что в этом контексте ожидается ресурс атрибута, +вам не нужно явно указывать его тип (который должен быть +?android:attr/textColorSecondary) — тип {@code attr} можно исключить.

+ + + + +

Доступ к ресурсам платформы

+ +

В Android предусмотрен ряд стандартных ресурсов, например, стилей, тем и макетов. Для +доступа к этим ресурсам укажите в ссылке на ресурс имя пакета +android. Например, в Android имеется ресурс макета, который можно использовать для +элементов списка в виджете {@link android.widget.ListAdapter}:

+ +
+{@link android.app.ListActivity#setListAdapter(ListAdapter)
+setListAdapter}(new {@link
+android.widget.ArrayAdapter}<String>(this, android.R.layout.simple_list_item_1, myarray));
+
+ +

В этом примере {@link android.R.layout#simple_list_item_1} представляет собой ресурс макета, определенный +платформой для элементов в виджете {@link android.widget.ListView}. Вы можете использовать его вместо создания +собственных макетов для элементов списка. Дополнительные сведения представлены в руководстве для разработчиков, посвященном +представлению в виде списка.

+ diff --git a/docs/html-intl/intl/ru/guide/topics/resources/overview.jd b/docs/html-intl/intl/ru/guide/topics/resources/overview.jd new file mode 100644 index 0000000000000000000000000000000000000000..c809c85b784b92d527549cb514509a6173af6d9d --- /dev/null +++ b/docs/html-intl/intl/ru/guide/topics/resources/overview.jd @@ -0,0 +1,103 @@ +page.title=Обзор ресурсов +@jd:body + + + + +

Необходимо обязательно экспортировать ресурсы, такие как изображения и строки, из кода +приложения, чтобы можно было обрабатывать их независимо. Кроме того, экспорт +ресурсов позволяет предоставлять альтернативные ресурсы для поддержки конфигураций +конкретных устройств, например, различные языки или размеры экранов. Значение этого возрастает по мере того, как +появляется все больше устройств Android с разными конфигурациями. Чтобы обеспечить +совместимость с различными конфигурациями, необходимо организовать ресурсы +в каталоге {@code res/} проекта с использованием различных подкаталогов для группирования ресурсов по типу и +конфигурации.

+ +
+ +

+Рисунок 1. Два разных устройства, каждое из которых использует макет по умолчанию +(приложение не предоставляет альтернативных макетов).

+
+ +
+ +

+Рисунок 2. Два разных устройства, каждое из которых использует свой макет, разработанный для +экранов разных размеров.

+
+ +

Для ресурсов любого типа можно указать ресурс по умолчанию и несколько +альтернативных ресурсов для приложения:

+
    +
  • Ресурсы по умолчанию должны использоваться независимо от +конфигурации устройства или в том случае, когда отсутствуют альтернативные ресурсы, соответствующие +текущей конфигурации.
  • +
  • Альтернативные ресурсы предназначены для работы с определенными +конфигурациями. Чтобы указать, что группа ресурсов предназначена для определенной конфигурации, +добавьте соответствующий квалификатор к имени каталога.
  • +
+ +

Например, несмотря на то, что макет пользовательского интерфейса по умолчанию +сохранен в каталоге {@code res/layout/}, можно указать другой макет для +использования на экране с альбомной ориентацией, сохранив его в каталоге {@code res/layout-land/} +. Android автоматически применяет соответствующие ресурсы, сопоставляя текущую конфигурацию +устройства с именами каталогов ресурсов.

+ +

На рисунке 1 показано, как система применяет одинаковый макет для +двух разных устройств, когда альтернативные ресурсы отсутствуют. На рисунке 2 показано +то же приложение, когда для больших экранов добавлен альтернативный ресурс макета.

+ +

В следующих документах содержится полное руководство по организации ресурсов приложения, +указания альтернативных ресурсов, доступа к ним из приложения и т. д.:

+ +
+
Предоставление ресурсов
+
Типы ресурсов, которые можно предоставлять в приложении, место их сохранения и способы создания +альтернативных ресурсов для определенных конфигураций устройств.
+
Доступ к ресурсам
+
Способ использования предоставленных ресурсов: путем ссылки на них из кода приложения +или из других ресурсов XML.
+
Обработка изменений в режиме выполнения
+
Управление изменениями конфигурации во время выполнения операции.
+
Локализация
+
Руководство по локализации приложения «снизу вверх» с помощью альтернативных ресурсов. Хотя это лишь +один из примеров использования альтернативных ресурсов, он очень важен для охвата более широкой аудитории +пользователей.
+
Типы ресурсов
+
Ссылка на различные типы ресурсов, которые вы можете предоставлять, с описанием элементов XML, +атрибутов и синтаксиса. Например, эта ссылка показывает, как создать ресурс для меню +, рисунков, анимаций приложения и т. д.
+
+ + diff --git a/docs/html-intl/intl/ru/guide/topics/resources/providing-resources.jd b/docs/html-intl/intl/ru/guide/topics/resources/providing-resources.jd new file mode 100644 index 0000000000000000000000000000000000000000..be0af952896e45dca6dc2a26af70e8ff279a77fe --- /dev/null +++ b/docs/html-intl/intl/ru/guide/topics/resources/providing-resources.jd @@ -0,0 +1,1094 @@ +page.title=Предоставление ресурсов +parent.title=Ресурсы приложения +parent.link=index.html +@jd:body + +
+
+

Краткое описание

+
    +
  • Разные типы ресурсов находятся в разных подкаталогах каталога {@code res/}
  • +
  • Альтернативные ресурсы представляют собой файлы ресурсов для определенных конфигураций
  • +
  • Обязательно включайте ресурсы по умолчанию, чтобы приложение не зависело от конфигураций конкретного +устройства
  • +
+

Содержание документа

+
    +
  1. Группирование типов ресурсов
  2. +
  3. Предоставление альтернативных ресурсов +
      +
    1. Правила квалификатора имени
    2. +
    3. Создание псевдонимов ресурсов
    4. +
    +
  4. +
  5. Обеспечение оптимальной совместимости устройства с ресурсами
  6. +
  7. Как Android находит наиболее подходящий ресурс
  8. +
+ +

См. также:

+
    +
  1. Доступ к ресурсам
  2. +
  3. Типы ресурсов
  4. +
  5. Поддержка +нескольких экранов
  6. +
+
+
+ +

Обязательно необходимо экспортировать из кода ресурсы приложения, такие как изображения и строки, +для последующей их независимой обработки. Следует также обеспечить альтернативные ресурсы для +определенных конфигураций устройств, группируя их в каталогах ресурсов со специальными именами. В +режиме выполнения Android использует соответствующие ресурсы с учетом текущей конфигурации. Например, +можно предоставлять другой макет пользовательского интерфейса в зависимости от размера экрана или различные +строки в зависимости от настройки языка.

+ +

После выполнения экспорта ресурсов приложения можно обращаться к ним +с помощью идентификаторов ресурсов, которые генерируются в классе {@code R} вашего проекта. Использование +ресурсов в приложении рассмотрено в разделе Доступ +к ресурсам. В этом документе показано, как группировать ресурсы в проекте Android и +предоставлять альтернативные ресурсы для определенных конфигураций устройств.

+ + +

Группирование типов ресурсов

+ +

Следует поместить ресурсы каждого типа в определенный подкаталог каталога +{@code res/} вашего проекта. В качестве примера приведена иерархия файлов для простого проекта:

+ +
+MyProject/
+    src/  
+        MyActivity.java  
+    res/
+        drawable/  
+            graphic.png  
+        layout/  
+            main.xml
+            info.xml
+        mipmap/  
+            icon.png 
+        values/  
+            strings.xml  
+
+ +

Как видно в этом примере, каталог {@code res/} содержит все ресурсы (в +подкаталогах): ресурс-изображение, два ресурса-макета, каталоги {@code mipmap/} для значков +запуска и файл строк. Имена каталогов +ресурсов очень важны и описаны в таблице 1.

+ +

Примечание. Подробные сведения об использовании папок множественного отображения см. в разделе +Обзор управления проектами.

+ +

Таблица 1. Каталоги ресурсов, +поддерживаемые в каталоге {@code res/} проекта.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
КаталогТип ресурсов
animator/Файлы XML, которые определяют анимации +свойств.
anim/Файлы XML, которые определяют анимации +преобразований. (Анимации свойств также можно сохранять в этом каталоге, но +для анимаций свойств предпочтительнее использовать каталог {@code animator/}, чтобы различать эти два +типа).
color/Файлы XML, которые определяют список состояний цветов. См. раздел Ресурс списка +состояний цветов
drawable/

Файлы растровых изображений ({@code .png}, {@code .9.png}, {@code .jpg}, {@code .gif}) или файлы XML, которые +составляют следующие подтипы графических ресурсов:

+
    +
  • Файлы растровых изображений
  • +
  • Файлы из девяти фрагментов (растровые изображения с возможностью изменения размера)
  • +
  • Списки состояний
  • +
  • Формы
  • +
  • Графические анимации
  • +
  • Другие графические элементы
  • +
+

См. раздел Графические ресурсы.

+
mipmap/Графические файлы для значков запуска с различной плотностью. Подробные сведения об управлении +значками запуска с помощью папок {@code mipmap/} см. в разделе + Обзор управления проектами.
layout/Файлы XML, которые определяют макет пользовательского интерфейса. + См. раздел Ресурсы макетов.
menu/Файлы XML, которые определяют меню приложения, такие как меню параметров, контекстные меню или вложенные +меню. См. раздел Ресурсы меню.
raw/

Произвольные файлы для сохранения в исходной форме. Чтобы открыть эти ресурсы с помощью +{@link java.io.InputStream}, вызовите {@link android.content.res.Resources#openRawResource(int) +Resources.openRawResource()} с идентификатором ресурса, который имеет вид {@code R.raw.filename}.

+

Однако, если требуется получить доступ к исходным именам файлов и иерархии файлов, можно +сохранять некоторые ресурсы в каталоге {@code +assets/} (вместо каталога {@code res/raw/}). Файлы в каталоге {@code assets/} не получают +идентификатора ресурса, поэтому их чтение возможно только с помощью {@link android.content.res.AssetManager}.

values/

Файлы XML, которые содержат простые значения, такие как строки, целые числа и цвета.

+

Тогда как XML-файлы ресурсов в других подкаталогах каталога {@code res/} определяют отдельные ресурсы +на базе имени файла XML, файлы в каталоге {@code values/} описывают несколько ресурсов. +Для файла в этом каталоге каждый дочерний элемент элемента {@code <resources>} определяет один +ресурс. Например, элемент {@code <string>} создает ресурс +{@code R.string}, а элемент {@code <color>} создает ресурс {@code R.color} +.

+

Так как каждый ресурс определяется с помощью своего собственного элемента XML, можно назначать имя файла +по своему усмотрению и помещать ресурсы разных типов в один файл. Тем не мене, может +появиться необходимость поместить ресурсы отдельных типов в разные файлы. Например, ниже приведены соглашения для имен файлов +ресурсов, которые можно создать в этом каталоге:

+ +

См. разделы Строковые ресурсы, + Ресурсы стиля и + Дополнительные типы ресурсов.

+
xml/Произвольные XML-файлы, которые можно читать в режиме выполнения вызовом метода {@link +android.content.res.Resources#getXml(int) Resources.getXML()}. Здесь должны сохраняться различные файлы конфигурации XML, +например, конфигурация с возможностью поиска. +
+ +

Предупреждение! Не сохраняйте файлы ресурсов непосредственно в +каталоге {@code res/}, так как это вызывает ошибку компилятора.

+ +

Дополнительную информацию об определенных типах ресурсов см. в документации Типы ресурсов.

+ +

Ресурсы, сохраненные в подкаталогах, которые описаны в таблице 1, являются ресурсами +«по умолчанию». Таким образом, эти ресурсы определяют дизайн и содержимое приложения по умолчанию. +Однако различные типы устройств Android могут вызывать различные типы ресурсов. +Например, если устройство оснащено экраном больше нормального, следует предоставить +другие ресурсы макета, которые будут использовать преимущества дополнительного места на экране. Или, если устройство +содержит различные языковые настройки, следует предоставить другие строковые ресурсы, содержащие перевод +текста пользовательского интерфейса. Чтобы предоставить разные ресурсы для разных конфигураций устройств, +необходимо предоставить альтернативные ресурсы в дополнение к ресурсам +по умолчанию.

+ + +

Предоставление альтернативных ресурсов

+ + +
+ +

+Рисунок 1. Два разных устройства, которые используют разные ресурсы макета.

+
+ +

Почти каждое приложение должно предоставлять альтернативные ресурсы, чтобы поддерживать определенные конфигурации +устройств. Например, необходимо включить альтернативные графические ресурсы для экранов с +разной плотностью растра и альтернативные ресурсы для разных языков. В режиме выполнения Android +определяет конфигурацию устройства и загружает соответствующие +ресурсы для приложения.

+ +

Чтобы указать альтернативы для конкретных конфигураций набора ресурсов, выполните следующие действия:

+
    +
  1. Создайте новый каталог в каталоге {@code res/} с именем следующего вида {@code +<имя_ресурса>-<квалификатор_конфигурации>}. +
      +
    • {@code <resources_name>} – имя каталога соответствующих ресурсов +по умолчанию (определено в таблице 1).
    • +
    • {@code <qualifier>} – имя, которое указывает определенную конфигурацию, +для которой должны использоваться эти ресурсы (определено в таблице 2).
    • +
    +

    Можно добавлять несколько квалификаторов {@code <qualifier>}. Разделяйте их +знаком дефиса.

    +

    Предупреждение! При добавлении нескольких квалификаторов необходимо +располагать их в том же порядке, в котором они перечислены в таблице 2. Если порядок квалификаторов нарушен, + ресурсы игнорируются.

    +
  2. +
  3. Сохраните соответствующие альтернативные ресурсы в этом новом каталоге. Файлы ресурсов должны +иметь имена, точно совпадающие с именами файлов ресурсов по умолчанию.
  4. +
+ +

В качестве примера здесь приведено несколько ресурсов по умолчанию и альтернативных ресурсов:

+ +
+res/
+    drawable/   
+        icon.png
+        background.png    
+    drawable-hdpi/  
+        icon.png
+        background.png  
+
+ +

Квалификатор {@code hdpi} указывает, что ресурсы в этом каталоге предназначены для устройств, оснащенных экраном +высокой плотности. Изображения в каждом из этих каталогов для графических объектов имеют размер для определенной плотности +экрана, но имена файлов полностью +совпадают. Таким образом, идентификатор ресурса, который указывает на изображение {@code icon.png} или {@code +background.png}, всегда одинаков, но Android выбирает +версию каждого ресурса, которая оптимально соответствует текущему устройству, сравнивая информацию о конфигурации устройства +с квалификаторами в имени каталога ресурсов.

+ +

Android поддерживает несколько квалификаторов конфигурации, позволяя +добавлять несколько квалификаторов к одному имени каталога, разделяя квалификаторы дефисом. В таблице 2 +перечислены допустимые квалификаторы конфигурации в порядке приоритета — если используется несколько +квалификаторов для каталога ресурсов, необходимо добавлять их к имени каталога в том порядке, в котором +они перечислены в таблице.

+ + +

Таблица 2. Имена квалификаторов +конфигурации.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
КонфигурацияЗначения квалификатораОписание
MCC и MNCПримеры:
+ mcc310
+ mcc310-mnc004
+ mcc208-mnc00
+ и т. д. +
+

Код страны для мобильной связи (MCC), за которым может следовать код сети мобильной связи (MNC) + из SIM-карты устройства. Например, mcc310 – код США для любого поставщика услуг, + mcc310-mnc004 – код США для Verizon и mcc208-mnc00 – код Франции + для Orange.

+

Если в устройстве используется радиосвязь (телефон GSM), значения MCC и MNC добываются + из SIM-карты.

+

Можно также использовать только код MCC (например, для включения в приложении разрешенных в стране +ресурсов). Если требуется указать только язык, используйте квалификатор +язык и регион (обсуждается ниже). Если принято решение использовать квалификатор MCC и +MNC, следует делать это с осторожностью и проверить корректность его работы.

+

См. также поля конфигурации {@link +android.content.res.Configuration#mcc} и {@link +android.content.res.Configuration#mnc}, которые указывают текущий код страны при мобильной связи +и код сети мобильной связи соответственно.

+
Язык и регионПримеры:
+ en
+ fr
+ en-rUS
+ fr-rFR
+ fr-rCA
+ и т. д. +

Язык задается двухбуквенным кодом языка ISO + 639-1, к которому можно добавить двухбуквенный код региона + ISO + 3166-1-alpha-2 (которому предшествует строчная буква "{@code r}"). +

+ Коды не зависят от регистра; префикс {@code r} служит для +обозначения кода региона. + Нельзя указывать только код региона.

+

Он может измениться за время работы +приложения, если пользователь изменяет свой язык в системных настройках. В разделе Обработка изменений в режиме выполнения содержится информация +о воздействии таких изменений на приложение во время выполнения.

+

В разделе Локализация приведено полное руководство по локализации +приложения для других языков.

+

См. также поле конфигурации {@link android.content.res.Configuration#locale}, которое +указывает текущий язык.

+
Направление макетаldrtl
+ ldltr
+

Направление макета для приложения. Квалификатор {@code ldrtl} означает «направление макета справа налево». +Квалификатор {@code ldltr} означает «направление макета слева направо» и используется по умолчанию. +

+

Эти квалификаторы можно применять к любым ресурсам, таким как макеты, графические элементы или значения. +

+

Например, если требуется предоставить специальный макет для арабского языка и +общий макет для других языков, использующих написание «справа налево» (таких как фарси или иврит), используйте следующий код: +

+
+res/
+    layout/   
+        main.xml  (Default layout)
+    layout-ar/  
+        main.xml  (Specific layout for Arabic)
+    layout-ldrtl/  
+        main.xml  (Any "right-to-left" language, except
+                  for Arabic, because the "ar" language qualifier
+                  has a higher precedence.)
+
+

Примечание. Чтобы включить в приложение функцию макета «справа налево», +необходимо установить для параметра {@code + supportsRtl} значение {@code "true"} и для параметра {@code targetSdkVersion} значение 17 или больше.

+

Добавлено в API уровня 17.

+
smallestWidthsw<N>dp

+ Примеры:
+ sw320dp
+ sw600dp
+ sw720dp
+ и т. д. +
+

Основной размер экрана, указывающий минимальный размер доступной +области экрана. Точнее говоря, минимальная ширина устройства – это наименьший из двух размеров экрана: +высоты и ширины (можно также называть ее «меньшей стороной» экрана). Этот квалификатор +позволяет гарантировать, что независимо от текущей ориентации экрана +приложение имеет доступную ширину пользовательского интерфейса не менее {@code <N>} пикселов.

+

Например, если для макета требуется, чтобы минимальный размер области экрана всегда был +не менее 600 пикселов, можно использовать этот квалификатор для создания ресурсов этого макета, {@code +res/layout-sw600dp/}. Система будет использовать эти ресурсы только в том случае, если минимальный размер +доступной области экрана составляет не менее 600 пикселов, независимо от воспринимаемой пользователем +высоты или ширины. Значение минимальной ширины является постоянной характеристикой размера экрана для устройства; минимальная +ширина устройства не изменяется при изменении ориентации экрана.

+

Минимальная ширина устройства учитывает оформление экрана и пользовательский интерфейс системы. Например, +если не экране присутствуют постоянные элементы пользовательского интерфейса, которые занимают пространство вдоль +оси минимальной ширины, система объявляет, что минимальная ширина меньше фактического +размера экрана, так как эти пикселы экрана недоступны для пользовательского интерфейса приложения. Следовательно используемое значение +должно быть фактическим наименьшим размером, который необходим для вашего макета (обычно это значение является +«минимальной шириной», которую поддерживает ваш макет, независимо от текущей ориентации экрана).

+

Здесь приведены некоторые значения, которые можно использовать для экранов обычных размеров:

+
    +
  • 320 для устройств с конфигурациями экрана: +
      +
    • 240x320 ldpi (смартфон QVGA)
    • +
    • 320x480 mdpi (смартфон)
    • +
    • 480x800 hdpi (смартфон высокой плотности)
    • +
    +
  • +
  • 480 для таких экранов, как 480x800 mdpi (планшет/смартфон).
  • +
  • 600 для таких экранов, как 600x1024 mdpi (планшет с диагональю 7").
  • +
  • 720 для таких экранов, как 720x1280 mdpi (планшет с диагональю 10").
  • +
+

Когда приложение предоставляет несколько каталогов ресурсов с разными значениями +квалификатора «минимальная ширина», система использует квалификатор, ближайший +к минимальной ширине устройства, но не превышающий ее.

+

Добавлено в API уровня 13.

+

См. также атрибут {@code +android:requiresSmallestWidthDp}, который объявляет минимальную ширину, совместимую +с вашим приложением, и поле конфигурации {@link +android.content.res.Configuration#smallestScreenWidthDp}, которое содержит значение +минимальной ширины устройства.

+

Дополнительную информацию о разработке для различных экранов и использовании этого +квалификатора см. в руководстве разработчика Поддержка +нескольких экранов.

+
Доступная ширинаw<N>dp

+ Примеры:
+ w720dp
+ w1024dp
+ и т. д. +
+

Указывает минимальную доступную ширину экрана в единицах {@code dp}, для которой должен использоваться ресурс, +заданный значением <N>. Это +значение конфигурации будет изменяться в соответствии с текущей фактической шириной +при изменении альбомной/книжной ориентации.

+

Когда приложение предоставляет несколько каталогов ресурсов с разными значениями +этой конфигурации, система использует ширину, ближайшую к текущей +ширине экрана устройства, но не превышающую ее. Это значение +учитывает оформление экрана, поэтому, если устройство содержит +постоянные элементы пользовательского интерфейса вдоль левого или правого края дисплея, оно +использует значение ширины, которое меньше реального размера экрана: эти элементы +пользовательского интерфейса учитываются и уменьшают пространство, доступное для приложения.

+

Добавлено в API уровня 13.

+

См. также поле конфигурации {@link android.content.res.Configuration#screenWidthDp} +, которое содержит текущую ширину экрана.

+

Дополнительную информацию о разработке для различных экранов и использовании этого +квалификатора см. в руководстве разработчика Поддержка +нескольких экранов.

+
Доступная высотаh<N>dp

+ Примеры:
+ h720dp
+ h1024dp
+ и т. д. +
+

Указывает минимальную доступную высоту экрана в пикселах, для которой должен использоваться ресурс, +заданный значением <N>. Это +значение конфигурации будет изменяться в соответствии с текущей фактической высотой +при изменении альбомной/книжной ориентации.

+

Когда приложение предоставляет несколько каталогов ресурсов с разными значениями +этой конфигурации, система использует высоту, ближайшую к текущей +высоте экрана устройства, но не превышающую ее. Это значение +учитывает оформление экрана, поэтому, если устройство содержит +постоянные элементы пользовательского интерфейса вдоль верхнего или нижнего края дисплея, оно +использует значение высоты, которое меньше реального размера экрана: эти элементы +пользовательского интерфейса учитываются и уменьшают пространство, доступное для приложения. Элементы оформления +экрана, которые не являются постоянными (например, строка состояния телефона может быть +скрыта в полноэкранном режиме), здесь не учитываются; также не учитываются такие элементы +оформления окна, как строка заголовка или строка действий, поэтому приложения должны быть готовы к работе с меньшим +пространством, чем указано. +

Добавлено в API уровня 13.

+

См. также поле конфигурации {@link android.content.res.Configuration#screenHeightDp} +, которое содержит текущую ширину экрана.

+

Дополнительную информацию о разработке для различных экранов и использовании этого +квалификатора см. в руководстве разработчика Поддержка +нескольких экранов.

+
Размер экрана + small
+ normal
+ large
+ xlarge +
+
    +
  • {@code small}: Экраны, подобные по размеру +экрану QVGA низкой плотности. Минимальный размер макета для маленького экрана +составляет приблизительно 320x426 пикселов. Примерами являются экраны QVGA низкой плотности и VGA высокой +плотности.
  • +
  • {@code normal}: Экраны, подобные по размеру +экрану HVGA средней плотности. Минимальный +размер макета для нормального экрана составляет приблизительно 320x470 пикселов. Примерами таких экранов +являются экраны WQVGA низкой плотности, HVGA средней плотности, WVGA +высокой плотности.
  • +
  • {@code large}: Экраны, подобные по размеру +экрану VGA средней плотности. + Минимальный размер макета для большого экрана составляет приблизительно 480x640 пикселов. + Примерами являются экраны VGA и WVGA средней плотности.
  • +
  • {@code xlarge}: Экраны значительно крупнее обычного +экрана HVGA средней плотности. Минимальный размер макета для очень большого экрана составляет +приблизительно 720x960 пикселов. В большинстве случаев устройства с очень большими +экранами слишком велики для карманного использования и, скорее всего, +относятся к планшетам. Добавлено в API уровня 9.
  • +
+

Примечание. Использование квалификатора размера не подразумевает, что +ресурсы предназначены только для экранов этого размера. Если не предусмотрены +альтернативные ресурсы с квалификаторами, лучше подходящими к текущей конфигурации устройства, система может использовать +любые наиболее подходящие ресурсы.

+

Предупреждение! Если все ресурсы используют квалификатор размера, +который превосходит размер текущего экрана, система не будет использовать эти ресурсы, и приложение +аварийно завершится во время выполнения (например, если все ресурсы макета отмечены квалификатором {@code +xlarge}, но устройство оснащено экраном нормального размера).

+

Добавлено в API уровня 4.

+ +

Дополнительную информацию см. в разделе Поддержка нескольких +экранов.

+

См. также поле конфигурации {@link android.content.res.Configuration#screenLayout}, которое +указывает тип размера экрана: маленький, нормальный +или большой.

+
Формат экрана + long
+ notlong +
+
    +
  • {@code long}: Длинные экраны, такие как WQVGA, WVGA, FWVGA
  • +
  • {@code notlong}: Недлинные экраны, такие как QVGA, HVGA и VGA
  • +
+

Добавлено в API уровня 4.

+

Формат основан исключительно на соотношении сторон экрана («длинный» экран шире). Это +не связано с ориентацией экрана.

+

См. также поле конфигурации {@link android.content.res.Configuration#screenLayout}, которое +указывает, является ли экран длинным.

+
Ориентация экрана + port
+ land +
+
    +
  • {@code port}: Устройство в портретной (вертикальной) ориентации
  • +
  • {@code land}: Устройство в книжной (горизонтальной) ориентации
  • + +
+

Ориентация может измениться за время работы приложения, если +пользователь поворачивает экран. В разделе Обработка изменений в режиме выполнения содержится информация +о воздействии таких изменений на приложение во время выполнения.

+

См. также поле конфигурации {@link android.content.res.Configuration#orientation}, которое +указывает текущую ориентацию устройства.

+
Режим пользовательского интерфейса + car
+ desk
+ television
+ appliance + watch +
+
    +
  • {@code car}: Устройство подсоединено к автомобильной док-станции
  • +
  • {@code desk}: Устройство подсоединено к настольной док-станции
  • +
  • {@code television}: Устройство подсоединено к телевизору, обеспечивая +взаимодействие с расстояния «три метра», когда пользовательский интерфейс находится на большом экране, +находящемся вдалеке от пользователя, ориентированное на управление с помощью навигационной клавиши или другого +устройства без указателя
  • +
  • {@code appliance}: Устройство служит в качестве прибора без +дисплея
  • +
  • {@code watch}: Устройство с дисплеем для ношения на запястье
  • +
+

Добавлено в API уровня 8, телевизор добавлен в API 13, часы добавлены в API 20.

+

Информацию о том, как приложение может реагировать на установку устройства в +док-станцию или извлечение из нее, прочитайте документ Определение +и мониторинг типа и состояния подключения к док-станции.

+

Подключение может измениться за время работы приложения, если пользователь помещает устройство +в док-станцию. Некоторые из этих режимов можно включить или отключить с помощью {@link +android.app.UiModeManager}. В разделе Обработка изменений в режиме выполнения содержится +информация о воздействии таких изменений на приложение во время выполнения.

+
Ночной режим + night
+ notnight +
+
    +
  • {@code night}: Ночное время
  • +
  • {@code notnight}: Дневное время
  • +
+

Добавлено в API уровня 8.

+

Этот режим может измениться за время работы, если ночной режим оставлен в +автоматическом режиме (по умолчанию), в котором режим изменяется в зависимости от времени суток. Этот режим можно включить +или отключить с помощью {@link android.app.UiModeManager}. В разделе Обработка изменений в режиме выполнения содержится информация о воздействии таких +изменений на приложение во время выполнения.

+
Плотность пикселов на экране (dpi) + ldpi
+ mdpi
+ hdpi
+ xhdpi
+ xxhdpi
+ xxxhdpi
+ nodpi
+ tvdpi +
+
    +
  • {@code ldpi}: Экраны низкой плотности; приблизительно 120 dpi.
  • +
  • {@code mdpi}: Экраны средней плотности (обычные HVGA); приблизительно +160 dpi.
  • +
  • {@code hdpi}: Экраны высокой плотности; приблизительно 240 dpi.
  • +
  • {@code xhdpi}: Экраны очень высокой плотности; приблизительно 320 dpi. Добавлено в API +уровня 8.
  • +
  • {@code xxhdpi}: Экраны сверхвысокой плотности; приблизительно 480 dpi. Добавлено в API +уровня 16.
  • +
  • {@code xxxhdpi}: Использование исключительно высокой плотности (только значок запуска, см. +примечание +в документе Поддержка нескольких экранов); приблизительно 640 dpi. Добавлено в API +уровня 18.
  • +
  • {@code nodpi}: Этот режим можно использовать для растровых графических ресурсов, которые не требуется масштабировать +в соответствии с плотностью устройства.
  • +
  • {@code tvdpi}: Экраны промежуточной плотности между mdpi и hdpi; приблизительно 213 dpi. Этот режим не считается +«основной» группой плотности. Он главным образом предназначен для телевизоров, и большинство +приложений в нем не нуждается — при условии, что ресурсов mdpi и hdpi достаточно для большинства приложений, и +система будет масштабировать их при необходимости. Этот квалификатор введен в API уровня 13.
  • +
+

Шесть основных уровней плотности соотносятся как 3:4:6:8:12:16 (если игнорировать +плотность tvdpi). Так, растровое изображение 9x9 в ldpi представляется как 12x12 в mdpi, 18x18 в hdpi, 24x24 в xhdpi и т. д. +

+

Если графические ресурсы выглядят недостаточно хорошо на телевизоре или +других определенных устройствах, и хочется попробовать ресурсы tvdpi, используйте масштабный коэффициент 1,33*mdpi. Например, + изображение 100 x 100 пикселов для экранов mdpi должно иметь размер 133 x 133 пиксела для tvdpi.

+

Примечание. Использование квалификатора плотности не подразумевает, что +ресурсы предназначены только для экранов этой плотности. Если не предусмотрены +альтернативные ресурсы с квалификаторами, лучше подходящими к текущей конфигурации устройства, система может использовать +любые наиболее подходящие ресурсы.

+

Дополнительную информацию о том, как обрабатывать +различные плотности экранов, и как Android может масштабировать растровые изображения в соответствии текущей плотностью, см. в разделе Поддержка нескольких +экранов.

+
Тип сенсорного экрана + notouch
+ finger +
+
    +
  • {@code notouch}: Устройство не оснащено сенсорным экраном.
  • +
  • {@code finger}: Устройство оснащено сенсорным экраном, предназначенным +для ввода с помощью пальцев пользователя.
  • +
+

См. также поле конфигурации {@link android.content.res.Configuration#touchscreen}, которое +указывает тип сенсорного экрана устройства.

+
Доступность клавиатуры + keysexposed
+ keyshidden
+ keyssoft +
+
    +
  • {@code keysexposed}: В устройстве доступна клавиатура. Если в устройстве включена +экранная клавиатура (что весьма вероятно), она может использоваться даже в случае, когда аппаратная клавиатура + недоступна пользователю, даже если устройство не имеет аппаратной клавиатуры. Если экранная +клавиатура отсутствует или отключена, это квалификатор используется только в том случае, когда доступна +аппаратная клавиатура.
  • +
  • {@code keyshidden}: Устройство имеет аппаратную клавиатуру, но она +скрыта, и в устройстве не включена экранная клавиатура.
  • +
  • {@code keyssoft}: Экранная клавиатура в устройстве включена независимо от того, +видна она или нет.
  • +
+

Если предоставляются ресурсы keysexposed, но не предоставляются ресурсы keyssoft, +система использует ресурсы keysexposed независимо от видимости +клавиатуры, поскольку в системе включена экранная клавиатура.

+

Это состояние может измениться за время работы приложения, если +пользователь открывает аппаратную клавиатуру. В разделе Обработка изменений в режиме выполнения содержится информация о воздействии таких +изменений на приложение во время выполнения.

+

См. также поля конфигурации {@link +android.content.res.Configuration#hardKeyboardHidden} и {@link +android.content.res.Configuration#keyboardHidden}, которые указывают видимость аппаратной +клавиатуры и видимость клавиатуры любого типа (включая экранную), соответственно.

+
Основной способ ввода текста + nokeys
+ qwerty
+ 12key +
+
    +
  • {@code nokeys}: В устройстве отсутствуют аппаратные клавиши для ввода текста.
  • +
  • {@code qwerty}: Устройство оснащено аппаратной клавиатурой с раскладкой qwerty, независимо от того, видна она +пользователю +или нет.
  • +
  • {@code 12key}: Устройство оснащено аппаратной клавиатурой с 12 клавишами, независимо от того, видна она пользователю +или нет.
  • +
+

См. также поле конфигурации {@link android.content.res.Configuration#keyboard}, которое +указывает основной доступный способ ввода текста.

+
Версия платформы (уровень API)Примеры:
+ v3
+ v4
+ v7
+ и т. д.
+

Уровень API, поддерживаемый устройством. Например, v1 для уровня API +1 (устройства с Android 1.0 или выше) и v4 для уровня API 4 (устройства с Android +1.6 или выше). В документе Уровни API Android содержится дополнительная информация +об этих значениях.

+
+ + +

Примечание. Некоторые квалификаторы конфигурации добавлены после версии Android +1.0, поэтому в некоторых версиях Android поддерживаются не все квалификаторы. При использовании нового квалификатора косвенно +добавляется квалификатор версии платформы, чтобы более старые устройства игнорировали его. Например, при использовании +квалификатора w600dp автоматически добавляется квалификатор v13, так как квалификатор +доступной ширины был новым в API уровня 13. Чтобы исключить какие-либо проблемы, всегда включайте набор +ресурсов по умолчанию (набор ресурсов без квалификаторов). Для получения дополнительной информации см. +раздел Обеспечение оптимальной совместимости устройства с +ресурсами.

+ + + +

Правила квалификатора имени

+ +

Здесь приведены некоторые правила использования имен квалификаторов:

+ +
    +
  • Можно указать несколько квалификаторов для одного набора ресурсов, разделяя их дефисами. Например, + drawable-en-rUS-land применяется к устройствам в США, на английском языке в альбомной +ориентации.
  • +
  • Квалификаторы должны идти в том же порядке, в котором они перечислены в таблице 2. Например: + +
      +
    • Неправильно: drawable-hdpi-port/
    • +
    • Правильно: drawable-port-hdpi/
    • +
    +
  • +
  • Нельзя использовать вложенные каталоги альтернативных ресурсов. Например, нельзя иметь каталог +res/drawable/drawable-en/.
  • +
  • Значения не зависят от регистра букв. Компилятор ресурсов преобразует имена каталогов +в нижний регистр перед обработкой, чтобы избежать проблем в файловых системах, +не учитывающих регистр. Прописные буквы в именах служат исключительно для удобочитаемости.
  • +
  • Поддерживается только одно значение квалификатора каждого типа. Например, если требуется использовать +одинаковые графические файлы для испанского и французского языков, нельзя создавать +каталог с именем drawable-rES-rFR/. Вместо этого необходимо создать два каталога ресурсов, например, +drawable-rES/ и drawable-rFR/, которые содержат соответствующие файлы. +Однако не обязательно фактически копировать одинаковые файлы в оба каталога. Вместо этого +можно создать псевдоним для ресурса. См. раздел Создание +псевдонимов ресурсов ниже.
  • +
+ +

После сохранения альтернативных ресурсов в каталоги с именами +этих квалификаторов Android автоматически применяет ресурсы в приложении на основе текущей конфигурации +устройства. При каждом запросе ресурсов Android проверяет каталоги альтернативных +ресурсов, которые содержат файл запрошенного ресурса, затем находят +наиболее подходящий ресурс (обсуждается ниже). Если нет альтернативных ресурсов, которые +соответствуют конкретной конфигурации устройства, Android использует ресурсы по умолчанию (набор +ресурсов для конкретного типа ресурсов, которые не содержат квалификатора +конфигурации).

+ + + +

Создание псевдонимов ресурсов

+ +

Ресурс, предназначенный для нескольких конфигураций +устройства (но не являющийся ресурсом по умолчанию), следует помещать +только в один каталог альтернативных ресурсов. Вместо этого можно (в некоторых случаях) создать +альтернативный +ресурс, действующий в качестве псевдонима для ресурса, сохраненного в каталоге ресурсов по умолчанию.

+ +

Примечание. Не все ресурсы предлагают механизм, позволяющий +создавать псевдоним для другого ресурса. В частности, анимации, меню, необработанные и другие неустановленные +ресурсы в каталоге {@code xml/} не содержат такой возможности.

+ +

Например, представьте, что имеется значок приложения, {@code icon.png}, и требуется иметь уникальные версии +этого значка для разных языков. Однако в двух языках, канадском английском и канадском французском, требуется +использовать одинаковую версию. Можно предположить, что требуется скопировать одно изображение +в каталоги ресурсов для обоих языков, но +это неверно. Вместо этого можно сохранить изображение для обоих языков, как {@code icon_ca.png} (любое +имя, кроме {@code icon.png}), и поместить его +в каталог по умолчанию {@code res/drawable/}. Затем создайте файл {@code icon.xml} в каталогах {@code +res/drawable-en-rCA/} и {@code res/drawable-fr-rCA/} который ссылается на ресурс {@code icon_ca.png} +с помощью элемента {@code <bitmap>}. Это позволяет хранить только одну версию файла PNG +и два маленьких файла XML, которые указывают на него. (Пример файла XML показан ниже.)

+ + +

Графические объекты

+ +

Чтобы создать псевдоним для существующего графического объекта, используйте элемент {@code <bitmap>}. +Например:

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
+    android:src="@drawable/icon_ca" />
+
+ +

Если сохранить этот файл под именем {@code icon.xml} (в каталоге альтернативных ресурсов, например, +{@code res/drawable-en-rCA/}), он компилируется в ресурс, на который +можно ссылаться с помощью {@code R.drawable.icon}, но фактически он является псевдонимом для ресурса {@code +R.drawable.icon_ca} (который сохранен в каталоге {@code res/drawable/}).

+ + +

Макет

+ +

Чтобы создать псевдоним для существующего макета, используйте элемент {@code <include>} +, заключенный в теги {@code <merge>}. Например:

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<merge>
+    <include layout="@layout/main_ltr"/>
+</merge>
+
+ +

Если сохранить этот файл под именем {@code main.xml}, он компилируется в ресурс, на который можно ссылаться +с помощью {@code R.layout.main}, но фактически он является псевдонимом для ресурса {@code R.layout.main_ltr} +.

+ + +

Строки и другие простые значения

+ +

Чтобы создать псевдоним для существующей строки используйте идентификатор ресурса нужной +строки в качестве значения для новой строки. Например:

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <string name="hello">Hello</string>
+    <string name="hi">@string/hello</string>
+</resources>
+
+ +

Ресурс {@code R.string.hi} теперь является псевдонимом для {@code R.string.hello}.

+ +

Другие простые значения работают +аналогично. Например, цвет:

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <color name="yellow">#f00</color>
+    <color name="highlight">@color/red</color>
+</resources>
+
+ + + + +

Обеспечение оптимальной совместимости устройства с ресурсами

+ +

Для того чтобы приложение поддерживало несколько конфигураций устройств, очень важно +всегда предоставлять ресурсы по умолчанию для каждого типа ресурсов, используемых приложением.

+ +

Например, если приложение поддерживает несколько языков, всегда включайте каталог {@code +values/} (в котором сохранены строки) без квалификатора языка и региона. Если вместо этого поместить все файлы строк +в каталоги с квалификаторами языка и региона, приложение закроется с ошибкой при запуске +на устройстве, на котором установлен язык, отсутствующий в ваших строках. Но как только вы предоставили ресурсы +{@code values/} по умолчанию, приложение будет работать правильно (даже если пользователь не +понимает этого языка, это лучше, чем завершение с ошибкой).

+ +

Таким же образом, если вы предоставляете различные ресурсы макета в зависимости от ориентации экрана, следует +указать одну ориентацию в качестве ориентации по умолчанию. Например, вместо предоставления ресурсов макета в каталоге {@code +layout-land/} для альбомной ориентации и в каталоге {@code layout-port/} для книжной ориентации, оставьте один вариант по умолчанию: например, +{@code layout/} для альбомной и {@code layout-port/} для книжной ориентации.

+ +

Предоставление ресурсов по умолчанию важно не только потому, что приложение сможет работать на конфигурации, +которую вы не предусмотрели, но также и потому, что новые версии Android иногда добавляют +квалификаторы конфигураций, которые не поддерживаются более старыми версиями. Если вы используете новый квалификатор ресурсов, +но поддерживаете совместимость кода с более старыми версиями Android, то при выполнении вашего приложения в более старой версии +Android оно завершится в ошибкой, если вы не предусмотрели ресурсы по умолчанию, так как +оно не может использовать ресурсы, проименованные новым квалификатором. Например, если для параметра {@code +minSdkVersion} установлено значение 4 и вы квалифицировали все графические ресурсы с использованием ночного режима ({@code night} или {@code notnight}, который был добавлен в API +уровня 8), то устройства с API уровня 4 не смогут получить доступ к графическим ресурсам и приложение завершится с ошибкой. В этом +случае, вероятно, следует использовать {@code notnight} в качестве ресурсов по умолчанию и исключить этот +квалификатор, разместив графические ресурсы в каталогах {@code drawable/} или {@code drawable-night/}.

+ +

Поэтому для обеспечения оптимальной совместимости с устройствами обязательно предоставляйте ресурсы +по умолчанию, которые приложение может правильно выполнять. Затем создайте альтернативные +ресурсы для определенных конфигураций устройств с помощью квалификаторов конфигурации.

+ +

Из этого правила есть одно исключение: Если в приложении для параметра {@code minSdkVersion} установлено значение 4 или +выше, не требуется предоставлять графические ресурсы по умолчанию при предоставлении альтернативных графических +ресурсов с квалификатором плотность экрана. Даже без графических ресурсов +по умолчанию Android может найти наиболее подходящую альтернативную плотность экрана и масштабировать +растровые изображения при необходимости. Однако для оптимальной работы на устройствах всех типов следует +предоставить альтернативные графические ресурсы для всех трех типов плотности.

+ + + +

Как Android находит наиболее подходящий ресурс

+ +

Когда вы запрашиваете ресурс, для которого предоставлена альтернатива, Android выбирает +альтернативный ресурс для использования в режиме выполнения в зависимости от текущей конфигурации устройства. Чтобы +продемонстрировать, как Android выбирает альтернативный ресурс, допустим, что имеются следующие каталоги графических ресурсов, +каждый из которых содержит различные версии одинаковых изображений:

+ +
+drawable/
+drawable-en/
+drawable-fr-rCA/
+drawable-en-port/
+drawable-en-notouch-12key/
+drawable-port-ldpi/
+drawable-port-notouch-12key/
+
+ +

И допустим, что устройство имеет следующую конфигурацию:

+ +

+Язык = en-GB
+Ориентация экрана = port
+Плотность пикселов на экране = hdpi
+Тип сенсорного экрана = notouch
+Основной способ ввода текста = 12key +

+ +

Сравнивая конфигурацию устройства с доступными альтернативными ресурсами, Android выбирает +графику из каталога {@code drawable-en-port}.

+ +

Система приходит к решению об используемых ресурсах на основе следующей +логики:

+ + +
+ +

Рисунок 2. Как Android находит наиболее подходящий ресурс. +Структурная схема.

+
+ + +
    +
  1. Исключение файлов ресурсов, которые противоречат конфигурации устройства. +

    Каталог drawable-fr-rCA/ исключается, так как он +противоречит языку en-GB.

    +
    +drawable/
    +drawable-en/
    +drawable-fr-rCA/
    +drawable-en-port/
    +drawable-en-notouch-12key/
    +drawable-port-ldpi/
    +drawable-port-notouch-12key/
    +
    +

    Исключение. Квалификатор плотности пикселов на экране не исключается +вследствие противоречия. Хотя плотность экрана устройства hdpi, +каталог drawable-port-ldpi/ не исключается, так как на этом этапе любая плотность экрана +считается подходящей. Более подробная информация доступна в документе Поддержка нескольких +экранов.

  2. + +
  3. Указание (следующего) квалификатора с высшим приоритетом в списке (таблица 2). +(Начать с MCC, затем двигаться вниз.)
  4. +
  5. Содержат ли какие-либо каталоги ресурсов этот квалификатор?
  6. +
      +
    • Если Нет, вернуться к шагу 2 и найти следующий квалификатор. (В нашем примере +получается ответ «нет», пока не достигнут квалификатор языка.)
    • +
    • Если Да, перейти к шагу 4.
    • +
    + + +
  7. Исключить каталоги ресурсов, которые не содержат этого квалификатора. В данном примере система исключает +все каталоги, которые не содержат квалификатора языка:
  8. +
    +drawable/
    +drawable-en/
    +drawable-en-port/
    +drawable-en-notouch-12key/
    +drawable-port-ldpi/
    +drawable-port-notouch-12key/
    +
    +

    Исключение. Если получен квалификатор плотности пикселов на экране, +Android выбирает вариант, наиболее близко соответствующий плотности экрана устройства. +Как правило, Android предпочитает уменьшать большие исходные изображения, чем увеличивать +мелкие. См. раздел Поддержка нескольких +экранов.

    + + +
  9. Вернуться и повторять шаги 2, 3 и 4, пока не останется только один каталог. В нашем примере следующим +квалификатором, для которого есть совпадения, является ориентация экрана. +Поэтому исключаются ресурсы, не указывающие ориентацию экрана: +
    +drawable-en/
    +drawable-en-port/
    +drawable-en-notouch-12key/
    +
    +

    Остается каталог {@code drawable-en-port}.

    +
  10. +
+ +

Хотя эта процедура выполняется для каждого запрошенного ресурса, система дополнительно оптимизирует +некоторые вопросы. Одна из таких оптимизаций состоит в том, что поскольку конфигурация устройства известна, можно +исключить альтернативные ресурсы, которые не могут подойти. Например, если используется конфигурация с английским +языком ("en"), все каталоги ресурсов, для которых установлен другой квалификатор языка, +никогда не включаются в пул проверяемых ресурсов (хотя +каталоги ресурсов без квалификатора языка включаются).

+ +

При выборе ресурсов на основе квалификаторов размера экрана система будет использовать ресурсы +предназначенные для экрана, меньшего чем текущий экран, если нет более подходящих ресурсов +(например, на экранах большого размера при необходимости будут использоваться ресурсы, предназначенные для экранов нормального размера). Однако, если + единственные доступные ресурсы превосходят размер текущего экрана, система +не будет использовать эти ресурсы, и приложение аварийно завершится, если нет других ресурсов, соответствующих конфигурации +устройства (например, если все ресурсы макета отмечены квалификатором {@code xlarge}, +но устройство оснащено экраном нормального размера).

+ +

Примечание. Приоритет квалификатора (в таблице 2) более важен, +чем число квалификаторов, которые точно соответствуют устройству. Например, на шаге 4 выше, последний +вариант в списке содержит три квалификатора, которые точно соответствуют устройству (ориентация, тип +сенсорного экрана и способ ввода), в то время как drawable-en содержит только один подходящий параметр +(язык). Однако язык имеет более высокий приоритет, чем эти остальные квалификаторы, поэтому +drawable-port-notouch-12key вычеркивается.

+ +

Для получения более подробной информации об использовании ресурсов в приложении перейдите к разделу Доступ к ресурсам.

diff --git a/docs/html-intl/intl/ru/guide/topics/resources/runtime-changes.jd b/docs/html-intl/intl/ru/guide/topics/resources/runtime-changes.jd new file mode 100644 index 0000000000000000000000000000000000000000..5dc59c81559ff46c84e0f789ee74145aa6e703d5 --- /dev/null +++ b/docs/html-intl/intl/ru/guide/topics/resources/runtime-changes.jd @@ -0,0 +1,281 @@ +page.title=Обработка изменений в режиме выполнения +page.tags=операция,жизненный цикл +@jd:body + + + +

Некоторые конфигурации устройств могут изменяться в режиме выполнения +(например, ориентация экрана, доступность клавиатуры и язык). Когда происходит такое изменение, +Android перезапускает выполнение +{@link android.app.Activity} (вызывается {@link android.app.Activity#onDestroy()}, затем {@link +android.app.Activity#onCreate(Bundle) onCreate()}). Поведение при перезапуске позволяет +приложению учитывать новые конфигурации путем автоматической перезагрузки в приложение +альтернативных ресурсов, которые соответствуют новой конфигурации устройства.

+ +

Для правильной обработки перезапуска важно, чтобы операция восстанавливала предыдущее +состояние в течение нормального жизненного цикла +операции, при котором Android вызывает +{@link android.app.Activity#onSaveInstanceState(Bundle) onSaveInstanceState()} перед уничтожением +операции, чтобы вы могли сохранить данные о состоянии приложения. Затем вы можете восстановить состояние +во время выполнения метода {@link android.app.Activity#onCreate(Bundle) onCreate()} или {@link +android.app.Activity#onRestoreInstanceState(Bundle) onRestoreInstanceState()}.

+ +

Чтобы проверить, перезапускается ли приложение в неизмененном состоянии, следует +вызывать изменение конфигурации (например, изменение ориентации экрана) во время выполнения различных +задач в приложении. Приложение должно перезапускаться в любой момент без потери +пользовательских данных и состояния, чтобы обработать события, например, изменение конфигурации или получение пользователем +входящего телефонного звонка с последующим возвращением в приложение после того, как процесс +приложения мог быть уничтожен. Чтобы узнать, как восстановить состояние операции, прочитайте раздел Жизненный цикл операции.

+ +

Однако возможна ситуация, при которой перезапуск приложения и +восстановление значительного объема данных может быть слишком ресурсоемким и может создавать неприятное впечатление у пользователя. В такой ситуации +есть два других варианта:

+ +
    +
  1. Сохранение объекта во время изменения конфигурации +

    Необходимо позволить перезапуск операции при изменении конфигурации, при этом перенести объект +с сохранением состояния в новый экземпляр операции.

    + +
  2. +
  3. Самостоятельная обработка изменения конфигурации +

    Не допускайте перезапуска операции системой во время определенных изменений конфигурации, +но получайте обратный вызов при изменении конфигурации, чтобы при необходимости можно было вручную обновить +операцию.

    +
  4. +
+ + +

Сохранение объекта во время изменения конфигурации

+ +

Если перезапуск операции требует восстановления больших объемов данных, повторного подключения к сети +или выполнения других масштабных действий, то полный перезапуск вследствие изменения конфигурации +может тормозить работу пользователя. Кроме того, может быть невозможно полное восстановление состояния операции + из объекта {@link android.os.Bundle}, который система сохраняет с помощью обратного вызова {@link +android.app.Activity#onSaveInstanceState(Bundle) onSaveInstanceState()}, поскольку он не +предназначен для переноса больших объектов (таких как растровые изображения), и данные в нем должны быть преобразованы в последовательную +форму, а затем обратно, что потребляет много памяти и тормозит изменение конфигурации. В такой ситуации +вы можете облегчить бремя повторной инициализации операции путем сохранения фрагмента {@link +android.app.Fragment} в случае перезапуска операции вследствие изменения конфигурации. Этот фрагмент +может содержать ссылки на объекты с сохранением состояния, которые требуется сохранить.

+ +

Когда система Android завершает операцию вследствие изменения конфигурации, фрагменты +операции, отмеченные для сохранения, не уничтожаются. Такие фрагменты можно добавлять в операцию +для защиты объектов с сохранением состояния.

+ +

Сохранение объектов с сохранением состояния во фрагменте во время изменения конфигурации в режиме выполнения:

+ +
    +
  1. Расширьте класс {@link android.app.Fragment} и объявите ссылки на объекты, +сохраняющие состояние.
  2. +
  3. Вызовите {@link android.app.Fragment#setRetainInstance(boolean)}, когда фрагмент создан. +
  4. +
  5. Добавьте фрагмент в операцию.
  6. +
  7. Используйте {@link android.app.FragmentManager} для извлечения фрагмента при перезапуске +операции.
  8. +
+ +

В качестве привмера приведено определение фрагмента следующим образом:

+ +
+public class RetainedFragment extends Fragment {
+
+    // data object we want to retain
+    private MyDataObject data;
+
+    // this method is only called once for this fragment
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        // retain this fragment
+        setRetainInstance(true);
+    }
+
+    public void setData(MyDataObject data) {
+        this.data = data;
+    }
+
+    public MyDataObject getData() {
+        return data;
+    }
+}
+
+ +

Внимание! Хотя можно сохранить любой объект, +не следует передавать объект, связанный с {@link android.app.Activity}, например, {@link +android.graphics.drawable.Drawable}, {@link android.widget.Adapter}, {@link android.view.View} +или любой другой объект, связанный с {@link android.content.Context}. В этом случае произойдет +утечка всех видов и ресурсов исходного экземпляра операции. (Утечка ресурсов +означает, что приложение удерживает их, и система не может очистить от них память, поэтому +может теряться значительный объем памяти).

+ +

Затем используйте {@link android.app.FragmentManager} для добавления фрагмента в операцию. +Можно получить объект данных из фрагмента, когда операция повторно запускается в результате изменения конфигурации +в режиме выполнения. В качестве примера операция определена следующим образом:

+ +
+public class MyActivity extends Activity {
+
+    private RetainedFragment dataFragment;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.main);
+
+        // find the retained fragment on activity restarts
+        FragmentManager fm = getFragmentManager();
+        dataFragment = (DataFragment) fm.findFragmentByTag(“data”);
+
+        // create the fragment and data the first time
+        if (dataFragment == null) {
+            // add the fragment
+            dataFragment = new DataFragment();
+            fm.beginTransaction().add(dataFragment, “data”).commit();
+            // load the data from the web
+            dataFragment.setData(loadMyData());
+        }
+
+        // the data is available in dataFragment.getData()
+        ...
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+        // store the data in the fragment
+        dataFragment.setData(collectMyLoadedData());
+    }
+}
+
+ +

В этом примере {@link android.app.Activity#onCreate(Bundle) onCreate()} добавляет фрагмент +или восстанавливает ссылку на него. Метод {@link android.app.Activity#onCreate(Bundle) onCreate()} также +хранит объект, сохраняющий состояние, внутри экземпляра фрагмента. +Метод {@link android.app.Activity#onDestroy() onDestroy()} обновляет объект, сохраняющий состояние, внутри +сохраненного экземпляра фрагмента.

+ + + + + +

Самостоятельная обработка изменения конфигурации

+ +

Если приложению не требуется обновление ресурсов во время определенного изменения +конфигурации и имеются ограничения производительности, не разрешающие +перезапуск операции, можно объявить самостоятельную обработку операцией изменения конфигурации +, что запрещает системе перезапускать эту операцию.

+ +

Примечание. Самостоятельная обработка изменения конфигурации может +значительно затруднить использование альтернативных ресурсов, так как система не +применяет их автоматически. Этот подход может применяться в крайнем случае, когда необходимо избежать перезапуска в результате +изменения конфигурации, и для большинства приложений его использование не рекомендуется.

+ +

Для объявления самостоятельной обработки изменения конфигурации операцией отредактируйте соответствующий элемент {@code <activity>} +в файле манифеста и включите в него атрибут {@code +android:configChanges} со значением, представляющим конфигурацию, которую +требуется обрабатывать самостоятельно. Возможные значения перечислены в документации для атрибута {@code +android:configChanges} (наиболее часто используются значения {@code "orientation"} для +предотвращения перезапуска при изменении ориентации экрана и {@code "keyboardHidden"} для предотвращения +перезапуска при изменении доступности клавиатуры). Можно объявить несколько значений конфигурации +в атрибуте, разделяя их символом вертикальной черты {@code |}.

+ +

Например, следующий код манифеста объявляет операцию, которая обрабатывает как +изменение ориентации экрана, так и изменение доступности экрана:

+ +
+<activity android:name=".MyActivity"
+          android:configChanges="orientation|keyboardHidden"
+          android:label="@string/app_name">
+
+ +

Теперь, при изменении одного из этих элементов конфигурации, операция {@code MyActivity} не перезапускается. +Вместо этого {@code MyActivity} получает вызов {@link +android.app.Activity#onConfigurationChanged(Configuration) onConfigurationChanged()}. Этот метод +получает объект {@link android.content.res.Configuration}, в котором указана +новая конфигурация устройства. При считываении полей {@link android.content.res.Configuration} +можно определить новую конфигурацию и внести соответствующие изменения, обновляя +ресурсы, которые используются в интерфейсе. В момент +вызова этого метода объект {@link android.content.res.Resources} операции обновляется +и возвращает ресурсы на основе новой конфигурации, поэтому можно просто +сбросить элементы пользовательского интерфейса без перезапуска операции системой.

+ +

Внимание! Начиная с Android 3.2 (уровень API 13), +«размер экрана» также изменяется при переходе между книжной и альбомной +ориентациями устройства. Таким образом, если в режиме выполнения требуется предотвратить перезапуск вследствие изменения ориентации при разработке для +уровня API 13 или выше (как объявлено в атрибутах {@code minSdkVersion} и {@code targetSdkVersion} +), необходимо включить значение {@code "screenSize"} в дополнение к значению {@code +"orientation"}. То есть необходимо объявить {@code +android:configChanges="orientation|screenSize"}. Однако, если приложение нацелено на уровень API +12 или ниже, ваша операция всегда обрабатывает это изменение конфигурации самостоятельно (это изменение +конфигурации не перезапускает операцию даже при работе на устройстве с Android 3.2 или более поздней версией).

+ +

Например, следующая реализация метода {@link +android.app.Activity#onConfigurationChanged(Configuration) onConfigurationChanged()} +проверяет текущую ориентацию устройства:

+ +
+@Override
+public void onConfigurationChanged(Configuration newConfig) {
+    super.onConfigurationChanged(newConfig);
+
+    // Checks the orientation of the screen
+    if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
+        Toast.makeText(this, "landscape", Toast.LENGTH_SHORT).show();
+    } else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT){
+        Toast.makeText(this, "portrait", Toast.LENGTH_SHORT).show();
+    }
+}
+
+ +

Объект {@link android.content.res.Configuration} представляет все текущие +конфигурации, а не только изменившиеся. В большинстве случаев неважно, +как именно изменилась конфигурация, поэтому можно просто переназначить все ресурсы, предоставляющие альтернативу +для обрабатываемой конфигурации. Например, так как объект {@link +android.content.res.Resources} обновлен, можно сбросить +любые виды {@link android.widget.ImageView} с помощью {@link android.widget.ImageView#setImageResource(int) +setImageResource()} +, и после этого будут использоваться ресурсы, соответствующие новой конфигурации (как описано в разделе Предоставление ресурсов).

+ +

Обратите внимание, что значения из полей {@link +android.content.res.Configuration} являются целыми, которые соответствуют определенным константам +из класса {@link android.content.res.Configuration}. Документацию об используемых константах +для каждого поля см. в соответствующем поле по ссылке {@link +android.content.res.Configuration}.

+ +

Помните! При объявлении операции, обрабатывающей изменение +конфигурации, вы отвечаете за сброс любых элементов, для которых вы предоставили альтернативы. Если вы +объявили, что операция обрабатывает изменение ориентации, и у вас есть изображения, которые должны быть заменены при переходе между +альбомной и книжной ориентацией, вы должны переназначить каждый ресурс каждому элементу во время выполнения метода {@link +android.app.Activity#onConfigurationChanged(Configuration) onConfigurationChanged()}.

+ +

Если вам не требуется обновлять приложение на основе этих изменений +конфигурации, можно вместо этого не реализовывать метод {@link +android.app.Activity#onConfigurationChanged(Configuration) onConfigurationChanged()}. В этом +случае все ресурсы, использованные до изменения конфигурации, продолжают использоваться +, просто не происходит перезапуск операции. Однако ваше приложение обязательно должно быть +способно выключаться и перезапускаться в предыдущем состоянии, поэтому не следует использовать эту технику +как способ избежать сохранения состояния во время нормального жизненного цикла операции. Не только потому, что +есть другие изменения конфигурации, которые приведут к перезапуску приложения, но +также и потому, что вы должны обрабатывать события, например, когда пользователь покидает приложение, и оно +закрывается до возвращения в него пользователя.

+ +

Более подробную информацию об изменениях конфигурации, которые можно обрабатывать внутри операции, см. в документации {@code +android:configChanges} и описании +класса {@link android.content.res.Configuration}.

diff --git a/docs/html-intl/intl/ru/guide/topics/ui/controls.jd b/docs/html-intl/intl/ru/guide/topics/ui/controls.jd new file mode 100644 index 0000000000000000000000000000000000000000..62f4c76686f5ed5c5ca0252594e2178ad8c3e440 --- /dev/null +++ b/docs/html-intl/intl/ru/guide/topics/ui/controls.jd @@ -0,0 +1,90 @@ +page.title=Элементы управления вводом +parent.title=Пользовательский интерфейс +parent.link=index.html +@jd:body + +
+ +
+ +

Элементы управления вводом представляют собой интерактивные компоненты пользовательского интерфейса приложения. В Android имеется +широкий набор элементов управления, которые можно использовать в пользовательском интерфейсе, например, кнопки, текстовые поля, полосы прокрутки, +флажки, кнопки изменения масштаба, переключатели и многие другие элементы.

+ +

Чтобы добавить элемент управления вводом в пользовательский интерфейс, достаточно вставить соответствующий элемент XML в XML-файл макета. Ниже представлен пример макета +с текстовым полем и кнопкой.

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    android:orientation="horizontal">
+    <EditText android:id="@+id/edit_message"
+        android:layout_weight="1"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:hint="@string/edit_message" />
+    <Button android:id="@+id/button_send"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/button_send"
+        android:onClick="sendMessage" />
+</LinearLayout>
+
+ +

Каждый элемент управления вводом поддерживает определенный набор событий ввода, чтобы можно было обрабатывать такие события, как ввод +пользователем текста или нажатие кнопки.

+ + +

Часто используемые элементы управления

+

Ниже представлен список некоторых часто используемых элементов управления, которые можно использовать в приложении. Чтобы узнать подробнее о каждом элементе управления, +перейдите по соответствующей ссылке.

+ +

Примечание. Ниже перечислены далеко не все элементы управления, которые имеются в системе +Android. Все они имеются в пакете {@link android.widget}. Если в вашем приложении требуется реализовать +определенный тип элемента управления вводом, вы можете создать собственные настраиваемые компоненты.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Тип элемента управленияОписаниеСвязанные классы
КнопкаКнопка, которую пользователь может нажать для выполнения действия.{@link android.widget.Button Button}
Текстовое полеРедактируемое текстовое поле. Можно воспользоваться виджетом AutoCompleteTextView, чтобы создать виджет для ввода текста с возможностью автозаполнения с помощью подсказок.{@link android.widget.EditText EditText}, {@link android.widget.AutoCompleteTextView}
ФлажокПереключатель, которым можно воспользоваться для включения или отключения функции или компонента. Флажки следует использовать при отображении группы доступных для выбора параметров, которые не являются взаимоисключающими.{@link android.widget.CheckBox CheckBox}
ПереключательЭтот элемент управления аналогичен флажку, за исключением того, что в группе элементов можно выбрать только один вариант.{@link android.widget.RadioGroup RadioGroup} +
{@link android.widget.RadioButton RadioButton}
Кнопка-переключательКнопка включения/отключения с индикатором.{@link android.widget.ToggleButton ToggleButton}
Раскрывающийся списокРаскрывающийся список параметров, в котором пользователь может выбрать только одно значение.{@link android.widget.Spinner Spinner}
Элементы выбораЭлементы диалогового окна для выбора одного значения из набора с помощью кнопок со стрелками вверх и вниз или с помощью жеста пролистывания. Для ввода значений даты (дня, месяца, года) используйте виджет DatePicker, а для ввода значений времени (часов, минут и указания времени до полудня или после [AM/PM]) — виджет TimePicker. Формат этих виджетов выбирается автоматически на основе региональных настроек на устройстве.{@link android.widget.DatePicker}, {@link android.widget.TimePicker}
diff --git a/docs/html-intl/intl/ru/guide/topics/ui/declaring-layout.jd b/docs/html-intl/intl/ru/guide/topics/ui/declaring-layout.jd new file mode 100644 index 0000000000000000000000000000000000000000..71428f6e1cdd4e407814afc809521c35fa543bdb --- /dev/null +++ b/docs/html-intl/intl/ru/guide/topics/ui/declaring-layout.jd @@ -0,0 +1,492 @@ +page.title=Макеты +page.tags=view,viewgroup +@jd:body + + + +

Макет определяет визуальную структуру пользовательского интерфейса, например, пользовательского интерфейса операции или виджета приложения. +Существует два способа объявить макет:

+
    +
  • Объявление элементов пользовательского интерфейса в XML. В Android имеется удобный справочник XML-элементов +для классов View и их подклассов, например таких, которые используются для виджетов и макетов.
  • +
  • Создание экземпляров элементов во время выполнения. Ваше +приложение может программным образом создавать объекты View и ViewGroup (а также управлять их свойствами).
  • +
+ +

Платформа Android предоставляет вам гибкость при использовании любого из этих способов для объявления пользовательского интерфейса приложения и его управления. Например, вы можете объявить в XML макеты по умолчанию, включая элементы экрана, которые будут отображаться в макетах, и их свойства. Затем вы можете добавить в приложение код, который позволяет изменять состояние объектов на экране (включая объявленные в XML) во время выполнения.

+ + + +

Преимущество объявления пользовательского интерфейса в файле XML заключается в том, что таким образом вы можете более эффективно отделить представление своего приложения от кода, который управляет его поведением. Описания пользовательского интерфейса находятся за пределами кода вашего приложения. Это означает, что вы можете изменять или адаптировать интерфейс без необходимости вносить правки в исходный код и повторно компилировать его. Например, можно создать разные файлы XML макета для экранов разных размеров и разных ориентаций экрана, а также для различных языков. Кроме того, объявление макета в XML упрощает визуализацию структуры пользовательского интерфейса, благодаря чему отладка проблем также становится проще. В данной статье мы научим вас объявлять макет в XML. Если вы +предпочитаете создавать экземпляры объектов View во время выполнения, обратитесь к справочной документации для классов {@link android.view.ViewGroup} и +{@link android.view.View}.

+ +

Как правило, справочник XML-элементов для объявления элементов пользовательского интерфейса точно следует структуре и правилам именования для классов и методов — названия элементов соответствуют названиям классов, а названия атрибутов соответствуют методам. Фактически, соответствие зачастую такое точное, что вы можете с легкостью догадаться, какой атрибут XML соответствует тому или иному методу класса, или какой класс соответствует заданному элементу XML. Однако следует отметить, что не все справочники являются идентичными. В некоторых случаях названия могут несколько отличаться. Например, +у элемента EditText есть атрибут text, который соответствует методу +EditText.setText().

+ +

Совет. Дополнительные сведения о различных типах макетов представлены в разделе +Часто используемые макеты. Также в инструкциях по построению +объектов представления Hello изложены сведения о создании различных макетов.

+ +

Создание XML

+ +

С помощью справочника XML-элементов, который имеется в Android, можно быстро и просто создавать макеты пользовательского интерфейса и содержащиеся в нем элементы, точно так же, как при создании веб-страниц в HTML — с помощью вложенных элементов.

+ +

В каждом файле макета должен быть всего один корневой элемент, в качестве которого должен выступать объект представления (View) или представления группы (ViewGroup). После определения корневого элемента можно приступать к добавлению дополнительных объектов макета или виджетов в качестве дочерних элементов для постепенного формирования иерархии представлений, которая определяет ваш макет. Ниже представлен пример макета XML, в котором используется вертикальный объект {@link android.widget.LinearLayout}, +в котором размещены элементы {@link android.widget.TextView} и {@link android.widget.Button}.

+
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:layout_width="match_parent"
+              android:layout_height="match_parent"
+              android:orientation="vertical" >
+    <TextView android:id="@+id/text"
+              android:layout_width="wrap_content"
+              android:layout_height="wrap_content"
+              android:text="Hello, I am a TextView" />
+    <Button android:id="@+id/button"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Hello, I am a Button" />
+</LinearLayout>
+
+ +

После объявления макета в файле XML сохраните файл с расширением .xml +в каталог res/layout/ своего проекта Android для последующей компиляции.

+ +

Дополнительные сведения о синтаксисе для файла XML макета представлены в документе Ресурсы макета.

+ +

Загрузка ресурса XML

+ +

Во время компиляции приложения каждый файл XML макета компилируется в ресурс +{@link android.view.View}. Вам необходимо загрузить ресурс макета в коде приложения в ходе реализации метода обратного вызова +{@link android.app.Activity#onCreate(android.os.Bundle) Activity.onCreate()}. +Для этого вызовите метод +{@link android.app.Activity#setContentView(int) setContentView()}, передайте в него ссылку на ресурс макета в следующей форме: +R.layout.layout_file_name. +Например, если макет XML сохранен как файл main_layout.xml, загрузить его +для вашей операции необходимо следующим образом:

+
+public void onCreate(Bundle savedInstanceState) {
+    super.onCreate(savedInstanceState);
+    setContentView(R.layout.main_layout);
+}
+
+ +

Метод обратного вызова onCreate() в вашей операции вызывается платформой Android во время +запуска операции (см. сведения о жизненных циклах в +документе +Операции).

+ + +

Атрибуты

+ +

Каждый объект View и ViewGroup поддерживают свои собственные атрибуты XML. +Некоторые атрибуты характерны только для объекта View (например, объект TextView поддерживает атрибут textSize), +однако эти атрибуты также наследуются любыми объектами View, которые могут наследовать этот класс. +Некоторые атрибуты являются общими для всех объектов View, поскольку они наследуются от корневого класса View (такие как атрибут +id). Любые другие атрибуты рассматриваются как «параметры макета». Такие атрибуты +описывают определенные ориентации макета для объекта View, которые заданы +родительским объектом ViewGroup такого объекта.

+ +

Идентификатор

+ +

У любого объекта View может быть связанный с ним целочисленный идентификатор, который служит для обозначения уникальности объекта View в иерархии. +Во время компиляции приложения этот идентификатор используется как целое число, однако идентификатор +обычно назначается в файле XML макета в виде строки в атрибуте id. +Этот атрибут XML является общим для всех объектов View +(определенных классом {@link android.view.View}), который вы будете использовать довольно часто. +Синтаксис для идентификатора внутри тега XML следующий:

+
android:id="@+id/my_button"
+ +

Символ @ в начале строки указывает на то, что обработчику XML следует выполнить синтаксический анализ остальной части +идентификатора, выполнить ее синтаксический анализ и определить ее в качестве ресурса идентификатора. Символ плюса (+) обозначает, что это имя нового ресурса, +который необходимо создать и добавить к нашим ресурсам (в файле R.java). В Android существует ряд других ресурсов +идентификатора. При ссылке на идентификатор ресурса Android вам не нужно указывать символ плюса, +однако необходимо добавить пространство имен пакета android, как указано ниже:

+
android:id="@android:id/empty"
+

После добавления пространства имен пакета android можно сослаться на идентификатор из класса ресурсовandroid.R, +а не из локального класса ресурсов.

+ +

Чтобы создать представления и сослаться на них из приложения, обычно следует выполнить указанные ниже действия.

+
    +
  1. Определите представление или виджет в файле макета и присвойте ему уникальный идентификатор: +
    +<Button android:id="@+id/my_button"
    +        android:layout_width="wrap_content"
    +        android:layout_height="wrap_content"
    +        android:text="@string/my_button_text"/>
    +
    +
  2. +
  3. Затем создайте экземпляр объекта представления и выполните его захват из макета +(обычно с помощью метода {@link android.app.Activity#onCreate(Bundle) onCreate()}): +
    +Button myButton = (Button) findViewById(R.id.my_button);
    +
    +
  4. +
+

Определение идентификаторов для объектов представления имеет важное значение при создании объекта {@link android.widget.RelativeLayout}. +В относительном макете универсальные идентификаторы используются для +расположения представлений относительно друг друга.

+

Идентификатор не обязательно должен быть уникальным в рамках всей иерархии, +а только в той ее части, где вы выполняете поиск (зачастую это может быть как раз вся иерархия, поэтому +при возможности идентификаторы должны быть полностью уникальными).

+ + +

Параметры макета

+ +

Атрибуты макета XML, которые называются layout_something, определяют +параметры макета для объекта представления, подходящие для класса ViewGroup, в котором он находится.

+ +

Каждый класс ViewGroup реализует вложенный класс, который наследует {@link +android.view.ViewGroup.LayoutParams}. В этом подклассе +имеются типы свойств, которые определяют размер и положение каждого дочернего представления, +подходящие для его группы. На рисунке 1 показано, что родительская группа +представлений определяет параметры макета для каждого дочернего представления (включая дочернюю группу представлений).

+ + +

Рисунок 1. Графическое представление иерархии представления с параметрами +макета каждого представления.

+ +

Обратите внимание, что подкласс LayoutParams имеет собственный синтаксис для задания +значений. Каждый дочерний элемент должен определять LayoutParams, которые подходят для его родительского элемента, +тогда как он сам может определять другие LayoutParams для своих дочерних элементов.

+ +

Все группы представлений включают в себя параметры ширины и высоты (layout_width и +layout_height), и каждое представление должно определять их. Многие +LayoutParams также включают дополнительные параметры полей и границ.

+ +

Для параметров ширины и высоты можно указать точные значения, хотя, возможно, +вам не захочется делать это часто. Обычно для задания значений ширины и высоты используется одна из следующих +констант:

+ +
    +
  • wrap_content — размер представления задается по размерам +его содержимого;
  • +
  • match_parent (которая до API уровня 8 называлась fill_parent ) + — размер представления определяется ограничениями, задаваемыми его родительской группой представлений.
  • +
+ +

Как правило, не рекомендуется задавать абсолютные значения ширины и высоты макета +(например, в пикселах). Вместо этого используйте относительные единицы измерения, такие как +пикселы, не зависящие от разрешения экрана (dp), wrap_contentили +match_parentЭто гарантирует одинаковое отображение +вашего приложения на устройствах с экранами разных размеров. +Принятые типы измерения определены в +документе +Доступные ресурсы.

+ + +

Размещение макета

+

+ Представление имеет прямоугольную форму. Расположение представления +определяется его координатами слева и сверху, а его размеры +— параметрами ширины и высоты. Расположение измеряется +в пикселах. +

+ +

+ Расположение представления можно получить путем вызова методов +{@link android.view.View#getLeft()} и {@link android.view.View#getTop()}. Первый возвращает координату слева (по оси X) +для прямоугольника представления. Второй возвращает верхнюю координату + (по оси Y) для прямоугольника представления. Оба этих метода +возвращают расположение представления относительно его родительского элемента. Например, +когда метод getLeft() возвращает 20, это означает, что представление находится на расстоянии 20 пикселов +от левого края его непосредственного родительского элемента. +

+ +

+ Кроме того, имеется несколько удобных методов +({@link android.view.View#getRight()} и {@link android.view.View#getBottom()}), которые позволяют избежать лишних вычислений. + Эти методы возвращают координаты правого и нижнего краев +прямоугольника представления. Например, вызов метода {@link android.view.View#getRight()} +аналогичен следующему вычислению: getLeft() + getWidth(). +

+ + +

Размер, отступ и поля

+

+ Размер представления выражается его шириной и высотой. Фактически, представление +обладает двумя парами значений «ширина-высота». +

+ +

+ Первая пара — это измеренная ширина и +измеренная высота. Эти размеры определяют размер представления +в границах своего родительского элемента. Измеренные +размеры можно получить, вызвав методы {@link android.view.View#getMeasuredWidth()} +и{@link android.view.View#getMeasuredHeight()}. +

+ +

+ Вторая пара значений — это просто ширина и высота (иногда они называются +чертежная ширина и чертежная высота). Эти размеры +определяют фактический размер представления на экране после разметки во время их +отрисовки. Эти значения могут отличаться от +измеренных ширины и высоты, хотя это и не обязательно. Значения ширины и высоты можно получить, вызвав методы +{@link android.view.View#getWidth()} и {@link android.view.View#getHeight()}. +

+ +

+ При измерении своих размеров представление учитывает заполнение. Отступ +выражается в пикселах для левой, верхней, правой и нижней частей представления. + Отступ можно использовать для смещения содержимого представления на определенное количество +пикселов. Например, значение отступа слева, равное 2, приведет к тому, что содержимое представления будет смещено +на 2 пиксела вправо от левого края представления. Для задания отступов можно использовать метод +{@link android.view.View#setPadding(int, int, int, int)}. Чтобы запросить отступ, используются методы +{@link android.view.View#getPaddingLeft()}, {@link android.view.View#getPaddingTop()}, +{@link android.view.View#getPaddingRight()} и {@link android.view.View#getPaddingBottom()}. +

+ +

+ Даже если представление может определить отступ, в нем отсутствует поддержка +полей. Такая возможность имеется у группы представлений. Дополнительные сведения представлены в справке по объектам +{@link android.view.ViewGroup} и +{@link android.view.ViewGroup.MarginLayoutParams}. +

+ +

Дополнительные сведения о размерах представлены в разделе +Значения размеров. +

+ + + + + + + + + + + +

Часто используемые макеты

+ +

В каждом подклассе класса {@link android.view.ViewGroup} имеется уникальный способ отображения +вложенных в него представлений. Ниже представлены некоторые из наиболее часто используемых типов макетов, которые входят в состав платформы +Android.

+ +

Примечание. Несмотря на то, что для формирования пользовательского интерфейса один +макет может содержать один или несколько вложенных макетов, рекомендуется использовать как можно более простую +иерархию макетов. Чем меньше в макете вложенных элементов, тем быстрее выполняется его отрисовка (горизонтальная иерархия +представлений намного лучше вертикальной).

+ + + + +
+

Линейный макет

+ +

Макет, в котором дочерние элементы представлены в горизонтальных или вертикальных столбцах. Если длина окна больше длины экрана, в нем +создается полоса прокрутки.

+
+ +
+

Относительный макет

+ +

В этом макете можно задавать расположение дочерних объектов относительно друг друга (дочерний элемент А находится +слева от дочернего элемента Б) или относительно родительского объекта (выравнивание относительно верхнего края родительского элемента).

+
+ +
+

Представление веб-страницы

+ +

Отображение веб-страниц.

+
+ + + + +

Создание макетов с помощью адаптера

+ +

Если содержимое макета является динамическим или не определено заранее, можно использовать макет, который создает подклассы класса +{@link android.widget.AdapterView} для заполнения макета представлениями во время выполнения. Подкласс класса +{@link android.widget.AdapterView} использует {@link android.widget.Adapter} для +привязки данных к своему макету. {@link android.widget.Adapter} выступает в качестве посредника между источником +данных и макетом {@link android.widget.AdapterView} — {@link android.widget.Adapter} +извлекает данные (из источника, например, массива или запроса к базе данных) и преобразует каждую запись +в представление, которое можно добавить в макет {@link android.widget.AdapterView}.

+ +

Часто используемые макеты, для создания которых можно использовать адаптер, включают в себя следующие:

+ +
+

Представление в виде списка

+ +

Отображение списка в один столбец с возможностью прокрутки.

+
+ +
+

Представление в виде сетки

+ +

Отображение сетки из столбцов и строк с возможностью прокрутки.

+
+ + + +

Заполнение представления адаптера данными

+ +

Чтобы заполнить адаптер {@link android.widget.AdapterView}, такой как {@link android.widget.ListView} или +{@link android.widget.GridView}, свяжите экземпляр {@link android.widget.AdapterView} с приложением +{@link android.widget.Adapter}, которое извлекает данные из внешнего источника и создает объект {@link +android.view.View}, который представляет каждую запись данных.

+ +

В Android предусмотрено несколько подклассов адаптера {@link android.widget.Adapter}, которые полезно использовать для +извлечения данных различных видов и создания представлений для {@link android.widget.AdapterView}. Вот +два наиболее часто используемых адаптера:

+ +
+
{@link android.widget.ArrayAdapter}
+
Этот адаптер используется в случае, когда в качестве источника данных выступает массив. По умолчанию {@link +android.widget.ArrayAdapter} создает представление для каждого элемента массива путем вызова метода {@link +java.lang.Object#toString()} для каждого элемента и помещения его содержимого в объект {@link +android.widget.TextView}. +

Например, если имеется массив строк, которые необходимо отобразить в объекте {@link +android.widget.ListView}, запустите новый {@link android.widget.ArrayAdapter} с помощью +конструктора, чтобы указать макет для каждой строки и массива строк:

+
+ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
+        android.R.layout.simple_list_item_1, myStringArray);
+
+

Аргументы для такого конструктора следующие:

+
    +
  • ваше приложение {@link android.content.Context};
  • +
  • макет, в котором содержится объект {@link android.widget.TextView} для каждой строки в массиве;
  • +
  • массив строк.
  • +
+

Затем достаточно вызвать метод +{@link android.widget.ListView#setAdapter setAdapter()} для своего объекта {@link android.widget.ListView}:

+
+ListView listView = (ListView) findViewById(R.id.listview);
+listView.setAdapter(adapter);
+
+ +

Чтобы настроить внешний вид каждого элемента, можно переопределить метод {@link +java.lang.Object#toString()} для объектов в массиве. Либо можно создать представление для каждого элемента, +который отличается от{@link android.widget.TextView} (например, если для каждого элемента массива требуется объект +{@link android.widget.ImageView}), наследовать класс {@link +android.widget.ArrayAdapter} и переопределить метод {@link android.widget.ArrayAdapter#getView +getView()}, чтобы возвратить требуемый тип представления для каждого элемента.

+ +
+ +
{@link android.widget.SimpleCursorAdapter}
+
Этот адаптер используется в случае, когда в качестве источника данных выступает объект {@link android.database.Cursor}. Если +используется {@link android.widget.SimpleCursorAdapter}, необходимо указать макет, который будет +использоваться для каждой строки в объекте {@link android.database.Cursor}, а также в какие представления макета необходимо вставить столбцы в объекте {@link android.database.Cursor} +. Например, если необходимо создать список +имен людей с их номерами телефонов, можно выполнить запрос, который возвращает объект {@link +android.database.Cursor}, содержащий строку для каждого человека и столбцы для имен и номеров +телефонов. Затем создается массив строк с указанием столбцов из объекта {@link +android.database.Cursor}, которые необходимо поместить в макет для каждого результата, а также массив целых чисел, в котором указаны +соответствующие представления для вставки каждого столбца:

+
+String[] fromColumns = {ContactsContract.Data.DISPLAY_NAME,
+                        ContactsContract.CommonDataKinds.Phone.NUMBER};
+int[] toViews = {R.id.display_name, R.id.phone_number};
+
+

При создании экземпляра объекта {@link android.widget.SimpleCursorAdapter} передайте в него макет, +который будет использоваться для каждого результата, объект {@link android.database.Cursor} с результатами и два следующих массива:

+
+SimpleCursorAdapter adapter = new SimpleCursorAdapter(this,
+        R.layout.person_name_and_number, cursor, fromColumns, toViews, 0);
+ListView listView = getListView();
+listView.setAdapter(adapter);
+
+

Затем {@link android.widget.SimpleCursorAdapter} создает представление для каждой строки в объекте +{@link android.database.Cursor} с помощью предоставленного макета путем вставки каждого элемента {@code +fromColumns} в соответствующее представление {@code toViews}.

.
+
+ + +

Если в течение жизненного цикла вашего приложения вы вносите изменения в соответствующие данные, чтение которых +выполняет адаптер, вам следует вызвать метод {@link android.widget.ArrayAdapter#notifyDataSetChanged()}. Это позволит +уведомить связанное представление о том, что данные были изменены и ему требуется выполнить обновление.

+ + + +

Обработка нажатий

+ +

Чтобы организовать реагирование на нажатие каждого элемента в {@link android.widget.AdapterView}, +реализуйте интерфейс {@link android.widget.AdapterView.OnItemClickListener}. Например:

+ +
+// Create a message handling object as an anonymous class.
+private OnItemClickListener mMessageClickedHandler = new OnItemClickListener() {
+    public void onItemClick(AdapterView parent, View v, int position, long id) {
+        // Do something in response to the click
+    }
+};
+
+listView.setOnItemClickListener(mMessageClickedHandler);
+
+ + + diff --git a/docs/html-intl/intl/ru/guide/topics/ui/dialogs.jd b/docs/html-intl/intl/ru/guide/topics/ui/dialogs.jd new file mode 100644 index 0000000000000000000000000000000000000000..515ecc6a0130d97cf6cdc40b7d4e7a6be4980b88 --- /dev/null +++ b/docs/html-intl/intl/ru/guide/topics/ui/dialogs.jd @@ -0,0 +1,798 @@ +page.title=Диалоговые окна +page.tags=alertdialog,dialogfragment + +@jd:body + + + + + +

Диалоговое окно — это небольшое окно, которое предлагает пользователю +принять решение или ввести дополнительную информацию. Диалоговое окно не заполняет весь экран и, +как правило, используется при модальных событиях, для продолжения которых требуется действие пользователя.

+ +
+

Дизайн диалогового окна

+

Подробную информацию о дизайне диалоговых окон, включая рекомендации + для вашего языка см. в Руководстве по дизайну диалоговых окон.

+
+ + + +

Класс {@link android.app.Dialog} — это базовый класс для создания диалоговых окон, но +реализовывать напрямую класс {@link android.app.Dialog} не рекомендуется. +Вместо этого следует использовать один из следующих подклассов:

+
+
{@link android.app.AlertDialog}
+
Диалоговое окно, в котором могут отображаться заголовок, кнопки вплоть до трех штук, список + из выбираемых элементов либо пользовательский макет.
+
{@link android.app.DatePickerDialog} или {@link android.app.TimePickerDialog}
+
Диалоговое окно с предопределенным пользовательским интерфейсом, с помощью которого пользователь указывает значения даты или времени.
+
+ + + +

Эти классы определяют стиль и структуру вашего диалогового окна, однако следует +использовать{@link android.support.v4.app.DialogFragment} в качестве контейнера вашего диалогового окна. +Класс {@link android.support.v4.app.DialogFragment} предоставляет все функции, +необходимые для создания диалогового окна и управления его внешним видом, вместо вызова методов +к объекту{@link android.app.Dialog}.

+ +

Использование {@link android.support.v4.app.DialogFragment} для управления диалоговым окном +обеспечивает корректную обработку событий жизненного цикла +, таких как нажатие пользователем кнопки Назад или поворот экрана. С помощью класса {@link +android.support.v4.app.DialogFragment} также происходит повторное использование пользовательского интерфейса диалогового окна в качестве +встраиваемого компонента в пользовательский интерфейс более высокого уровня — подобно традиционному классу {@link +android.support.v4.app.Fragment} (например, когда необходимо различное отображение пользовательского интерфеса диалогового окна +на больших и маленьких экранах).

+ +

В следующих разделах руководства описано использование {@link +android.support.v4.app.DialogFragment} в сочетании с объектом {@link android.app.AlertDialog} +. Если необходимо создать элемент выбора даты или времени, обратитесь к руководству +Элементы выбора.

+ +

Примечание: +Поскольку класс {@link android.app.DialogFragment} изначально включен в +Android 3.0 (уровень API 11), в настоящем документе описывается использование класса {@link +android.support.v4.app.DialogFragment}, предоставляемого в Библиотеке поддержки. После добавления этой библиотеки +в приложение появится возможность использовать{@link android.support.v4.app.DialogFragment} и множество других +API на устройствах, работающих на Android 1.6 и выше. Если минимальная версия вашего приложения поддерживает +уровень API 11 и выше, можно использовать фреймворк-версию {@link +android.app.DialogFragment}, но следует иметь в виду, что данный документ ссылается на API + со вспомогательными библиотеками. При использовании вспомогательной библиотеки +необходимо импортировать класс android.support.v4.app.DialogFragment +, а не android.app.DialogFragment.

+ + +

Создание фрагмента диалогового окна

+ +

Вы можете реализовать широкие возможности дизайна диалоговых окон, включая создание +пользовательских макетов, а также пути, описанные в руководстве по дизайну Диалоговых окон +—путем расширения +{@link android.support.v4.app.DialogFragment} и создания{@link android.app.AlertDialog} +в методе обратного вызова {@link android.support.v4.app.DialogFragment#onCreateDialog +onCreateDialog()}.

+ +

Например, имеется базовый класс {@link android.app.AlertDialog}, управляемый в рамках +{@link android.support.v4.app.DialogFragment}:

+ +
+public class FireMissilesDialogFragment extends DialogFragment {
+    @Override
+    public Dialog onCreateDialog(Bundle savedInstanceState) {
+        // Use the Builder class for convenient dialog construction
+        AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+        builder.setMessage(R.string.dialog_fire_missiles)
+               .setPositiveButton(R.string.fire, new DialogInterface.OnClickListener() {
+                   public void onClick(DialogInterface dialog, int id) {
+                       // FIRE ZE MISSILES!
+                   }
+               })
+               .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
+                   public void onClick(DialogInterface dialog, int id) {
+                       // User cancelled the dialog
+                   }
+               });
+        // Create the AlertDialog object and return it
+        return builder.create();
+    }
+}
+
+ +
+ +

Рисунок 1. +Диалоговое окно с сообщением и двумя кнопками действия.

+
+ +

Итак, после создания экземпляра этого класса и вызова {@link +android.support.v4.app.DialogFragment#show show()} к этому объекту появляется диалоговое окно, +показанное на рисунке 1.

+ +

В следующем разделе предоставлена более подробная информация об использовании API {@link android.app.AlertDialog.Builder} +для создания диалоговых окон.

+ +

В зависимости от сложности создаваемого диалогового окна возможно реализовать множество других методов +обратного вызова в {@link android.support.v4.app.DialogFragment}, включая все базовые +методы жизненных циклов фрагментов. + + + + + +

Создание диалогового окна оповещения

+ + +

С помощью класса {@link android.app.AlertDialog} создается многообразие решений касательно внешнего вида диалогового окна +, и зачастую этого класса вполне достаточно. +Как показано на рис. 2, диалоговое окно состоит из трех областей:

+ +
+ +

Рисунок 2. Макет диалогового окна.

+
+ +
    +
  1. Заголовок +

    Это дополнительная возможность, которая используется только в случае, если область содержимого + занята подробным сообщением, списком или пользовательским макетом. Если необходимо отобразить + простое сообщение или вопрос (как, например, в диалоге на рисунке 1), заголовок не нужен.

  2. +
  3. Область содержимого +

    Здесь может отображаться сообщение, список или другой пользовательский макет.

  4. +
  5. Кнопки действия +

    В диалоговом окне не должно содержаться более трех кнопок действия.

  6. +
+ +

Класс {@link android.app.AlertDialog.Builder} +предоставляет API, с помощью которых можно создавать {@link android.app.AlertDialog} +с этими видами содержимого, включая пользовательский макет.

+ +

Создание {@link android.app.AlertDialog}:

+ +
+// 1. Instantiate an {@link android.app.AlertDialog.Builder} with its constructor
+AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+
+// 2. Chain together various setter methods to set the dialog characteristics
+builder.setMessage(R.string.dialog_message)
+       .setTitle(R.string.dialog_title);
+
+// 3. Get the {@link android.app.AlertDialog} from {@link android.app.AlertDialog.Builder#create()}
+AlertDialog dialog = builder.create();
+
+ +

В следующих главах показано, как определять различные атрибуты диалоговых окон с помощью класса +{@link android.app.AlertDialog.Builder}.

+ + + + +

Добавление кнопок

+ +

Для добавления кнопок, изображенных на рисунке 2, +вызывайте методы {@link android.app.AlertDialog.Builder#setPositiveButton setPositiveButton()} и +{@link android.app.AlertDialog.Builder#setNegativeButton setNegativeButton()}:

+ +
+AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+// Add the buttons
+builder.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
+           public void onClick(DialogInterface dialog, int id) {
+               // User clicked OK button
+           }
+       });
+builder.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
+           public void onClick(DialogInterface dialog, int id) {
+               // User cancelled the dialog
+           }
+       });
+// Set other dialog properties
+...
+
+// Create the AlertDialog
+AlertDialog dialog = builder.create();
+
+ +

Методы set...Button() предполагают заголовок для кнопки (реализуемый +через строковый ресурс) и +{@link android.content.DialogInterface.OnClickListener}, который определяет действие, +следующее за нажатием кнопки пользователем.

+ +

Реализована возможность добавлять три различных вида кнопок действий:

+
+
Положительные
+
Используются для подтверждения и продолжения дейстия (кнопка «ОК»).
+
Отрицательные
+
Используются для отмены действия.
+
Нейтральные
+
Используются в случаях, когда пользователь может не желать продолжить действие, + но при этом необязательно хочет его отменить. Появляется между положительными и отрицательнымиI + кнопками. Примером такого действия может быть «Напомнить позже».
+
+ +

Можно добавлять только одну кнопку каждого вида в {@link +android.app.AlertDialog}. Это означает, что нельзя использовать более одной «положительной» кнопки.

+ + + +
+ +

Рисунок 3. +Диалоговое окно с заголовком и списком.

+
+ +

Добавление списка

+ +

В API {@link android.app.AlertDialog} реализована возможность использования трех видов списков:

+
    +
  • Традиционный список с выбором одного варианта
  • +
  • Интерактивный список с выбором одного варианта (переключатели)
  • +
  • Интерактивный список с выбором нескольких вариантов (флажки)
  • +
+ +

Для создания списка с выбором одного варианта, как на рисунке 3, +используйте метод{@link android.app.AlertDialog.Builder#setItems setItems()}:

+ +
+@Override
+public Dialog onCreateDialog(Bundle savedInstanceState) {
+    AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+    builder.setTitle(R.string.pick_color)
+           .setItems(R.array.colors_array, new DialogInterface.OnClickListener() {
+               public void onClick(DialogInterface dialog, int which) {
+               // The 'which' argument contains the index position
+               // of the selected item
+           }
+    });
+    return builder.create();
+}
+
+ +

Поскольку список отображается в области содержимого диалогового окна, +диалоговое окно не может показать одновременно сообщение и список, поэтому необходимо задать заголовок +диалогового окна с помощью {@link android.app.AlertDialog.Builder#setTitle setTitle()}. +Для указания элементов списка необходимо вызвать {@link +android.app.AlertDialog.Builder#setItems setItems()}, передающий указатель. +В качестве другого варианта можно указать список с помощью {@link +android.app.AlertDialog.Builder#setAdapter setAdapter()}. Наполнение списка +динамическими данными (например, из базы данных) происходит с помощью {@link android.widget.ListAdapter}.

+ +

Если вы предпочтете реализовать список с помощью {@link android.widget.ListAdapter}, +рекомендуется использовать {@link android.support.v4.content.Loader}, чтобы содержимое загружалось +асинхронно. Подробно этот процесс описан далее в руководстве по +Созданию макетов +с помощью адаптера и загрузчиков +.

+ +

Примечание: По умолчанию нажатие по элементу списка отменяет диалоговое окно, +за исключением случаев, когда используется один из следующих интерактивных списков.

+ +
+ +

Рисунок 4. +Список с несколькими вариантами ответов.

+
+ + +

Добавление интерактивного списка с одним или несколькими вариантами ответов

+ +

Для добавления списка с несколькими вариантами ответов (флажки) или +списка с одним вариантом ответа (переключатели) используйте методы +{@link android.app.AlertDialog.Builder#setMultiChoiceItems(Cursor,String,String, +DialogInterface.OnMultiChoiceClickListener) setMultiChoiceItems()} или +{@link android.app.AlertDialog.Builder#setSingleChoiceItems(int,int,DialogInterface.OnClickListener) +setSingleChoiceItems()} соответственно.

+ +

Например, таким образом можно создать список с несколькими вариантами ответов, как на +рисунке 4, который сохраняет выбранные +элементы в {@link java.util.ArrayList}:

+ +
+@Override
+public Dialog onCreateDialog(Bundle savedInstanceState) {
+    mSelectedItems = new ArrayList();  // Where we track the selected items
+    AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+    // Set the dialog title
+    builder.setTitle(R.string.pick_toppings)
+    // Specify the list array, the items to be selected by default (null for none),
+    // and the listener through which to receive callbacks when items are selected
+           .setMultiChoiceItems(R.array.toppings, null,
+                      new DialogInterface.OnMultiChoiceClickListener() {
+               @Override
+               public void onClick(DialogInterface dialog, int which,
+                       boolean isChecked) {
+                   if (isChecked) {
+                       // If the user checked the item, add it to the selected items
+                       mSelectedItems.add(which);
+                   } else if (mSelectedItems.contains(which)) {
+                       // Else, if the item is already in the array, remove it 
+                       mSelectedItems.remove(Integer.valueOf(which));
+                   }
+               }
+           })
+    // Set the action buttons
+           .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
+               @Override
+               public void onClick(DialogInterface dialog, int id) {
+                   // User clicked OK, so save the mSelectedItems results somewhere
+                   // or return them to the component that opened the dialog
+                   ...
+               }
+           })
+           .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
+               @Override
+               public void onClick(DialogInterface dialog, int id) {
+                   ...
+               }
+           });
+
+    return builder.create();
+}
+
+ +

Несмотря на то, что и традиционный список, и список с переключателями +предполагают действие по выбору одного элемента, вам необходимо использовать {@link +android.app.AlertDialog.Builder#setSingleChoiceItems(int,int,DialogInterface.OnClickListener) +setSingleChoiceItems()}, чтобы сохранить выбор пользователя. +Это значит, что при повторном открытии диалогового окна будет отображаться текущий выбор пользователя, +а затем создается список с переключателями.

+ + + + + +

Создание пользовательского макета

+ +
+ +

Рисунок 5. Пользовательский макет диалогового окна.

+
+ +

Если в диалоговом окне необходим пользовательский макет, нужно создать макет и добавить его в +{@link android.app.AlertDialog} путем вызова {@link +android.app.AlertDialog.Builder#setView setView()} в объекте {@link +android.app.AlertDialog.Builder}.

+ +

По умолчанию пользовательский мает заполняет окно диалога, при это все равно можно +использовать методы {@link android.app.AlertDialog.Builder} для добавления кнопок и заголовка.

+ +

В качестве примера на рисунке 5 приведен файл макета для диалогового окна.

+ +

res/layout/dialog_signin.xml

+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content">
+    <ImageView
+        android:src="@drawable/header_logo"
+        android:layout_width="match_parent"
+        android:layout_height="64dp"
+        android:scaleType="center"
+        android:background="#FFFFBB33"
+        android:contentDescription="@string/app_name" />
+    <EditText
+        android:id="@+id/username"
+        android:inputType="textEmailAddress"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="16dp"
+        android:layout_marginLeft="4dp"
+        android:layout_marginRight="4dp"
+        android:layout_marginBottom="4dp"
+        android:hint="@string/username" />
+    <EditText
+        android:id="@+id/password"
+        android:inputType="textPassword"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="4dp"
+        android:layout_marginLeft="4dp"
+        android:layout_marginRight="4dp"
+        android:layout_marginBottom="16dp"
+        android:fontFamily="sans-serif"
+        android:hint="@string/password"/>
+</LinearLayout>
+
+ +

Совет. По умолчанию при настройке элемента {@link android.widget.EditText} + для типа ввода {@code "textPassword"} используется семейство шрифтов фиксированной ширины, поэтому +необходимо изменить семейство шрифтов на{@code "sans-serif"}, чтобы в обоих текстовых полях использовались +одинаковые стили шрифта.

+ +

Для применения макета в вашем {@link android.support.v4.app.DialogFragment} +вам понадобится {@link android.view.LayoutInflater} с +{@link android.app.Activity#getLayoutInflater()} и вызов +{@link android.view.LayoutInflater#inflate inflate()}, где первым параметром будет являться +ID ресурса макета, а вторым параметром — исходный вид макета. +Затем можно вызвать{@link android.app.AlertDialog#setView setView()} +для размещения макета в диалоговом окне.

+ +
+@Override
+public Dialog onCreateDialog(Bundle savedInstanceState) {
+    AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+    // Get the layout inflater
+    LayoutInflater inflater = getActivity().getLayoutInflater();
+
+    // Inflate and set the layout for the dialog
+    // Pass null as the parent view because its going in the dialog layout
+    builder.setView(inflater.inflate(R.layout.dialog_signin, null))
+    // Add action buttons
+           .setPositiveButton(R.string.signin, new DialogInterface.OnClickListener() {
+               @Override
+               public void onClick(DialogInterface dialog, int id) {
+                   // sign in the user ...
+               }
+           })
+           .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
+               public void onClick(DialogInterface dialog, int id) {
+                   LoginDialogFragment.this.getDialog().cancel();
+               }
+           });      
+    return builder.create();
+}
+
+ +
+

Совет. Если необходимо пользовательское диалоговое окно, +можно отображать {@link android.app.Activity} в качестве диалога +вместо API {@link android.app.Dialog}. Нужно создать операцию и установить тему +{@link android.R.style#Theme_Holo_Dialog Theme.Holo.Dialog} +в элементе манифеста{@code +<операция>}:

+ +
+<activity android:theme="@android:style/Theme.Holo.Dialog" >
+
+

Готово. Операция теперь отображается в диалоговом окне, а не в полноэкранном режиме.

+
+ + + +

Передача событий обратно в основное диалоговое приложение

+ +

Когда пользователь нажимает одну из кнопок действий в диалоговом окне, либо выбирает элемент из списка, + {@link android.support.v4.app.DialogFragment} может самостоятельно произвести необходимое +действие, однако зачастую вам может понадобиться доставить информацию о событии операции или фрагменту, которые +открыли диалоговое окно. Для этого нобходимо определить интерфейс метода для каждого типа события нажатия. +Затем этот интерфейс применяется в основном компоненте приложения, которое +получает информацию о событиях из диалогового окна.

+ +

Например, {@link android.support.v4.app.DialogFragment} определяет +интерфейс, по который доставляет события обратно в основной компонент операции:

+ +
+public class NoticeDialogFragment extends DialogFragment {
+    
+    /* The activity that creates an instance of this dialog fragment must
+     * implement this interface in order to receive event callbacks.
+     * Each method passes the DialogFragment in case the host needs to query it. */
+    public interface NoticeDialogListener {
+        public void onDialogPositiveClick(DialogFragment dialog);
+        public void onDialogNegativeClick(DialogFragment dialog);
+    }
+    
+    // Use this instance of the interface to deliver action events
+    NoticeDialogListener mListener;
+    
+    // Override the Fragment.onAttach() method to instantiate the NoticeDialogListener
+    @Override
+    public void onAttach(Activity activity) {
+        super.onAttach(activity);
+        // Verify that the host activity implements the callback interface
+        try {
+            // Instantiate the NoticeDialogListener so we can send events to the host
+            mListener = (NoticeDialogListener) activity;
+        } catch (ClassCastException e) {
+            // The activity doesn't implement the interface, throw exception
+            throw new ClassCastException(activity.toString()
+                    + " must implement NoticeDialogListener");
+        }
+    }
+    ...
+}
+
+ +

Операция, выполняемая диалоговым окном, создает экземпляр диалогового окна +с помощью конструктора фрагментов диалогового окна и получает события +диалога с помощью реализации интерфейса {@code NoticeDialogListener}:

+ +
+public class MainActivity extends FragmentActivity
+                          implements NoticeDialogFragment.NoticeDialogListener{
+    ...
+    
+    public void showNoticeDialog() {
+        // Create an instance of the dialog fragment and show it
+        DialogFragment dialog = new NoticeDialogFragment();
+        dialog.show(getSupportFragmentManager(), "NoticeDialogFragment");
+    }
+
+    // The dialog fragment receives a reference to this Activity through the
+    // Fragment.onAttach() callback, which it uses to call the following methods
+    // defined by the NoticeDialogFragment.NoticeDialogListener interface
+    @Override
+    public void onDialogPositiveClick(DialogFragment dialog) {
+        // User touched the dialog's positive button
+        ...
+    }
+
+    @Override
+    public void onDialogNegativeClick(DialogFragment dialog) {
+        // User touched the dialog's negative button
+        ...
+    }
+}
+
+ +

Поскольку выполняемая операция реализуется через {@code NoticeDialogListener} +с помощью метода обратного вызова + {@link android.support.v4.app.Fragment#onAttach onAttach()}, во фрагменте диалога могут использоваться +методы обратного вызова интерфейса для доставки событий нажатий к операциям:

+ +
+public class NoticeDialogFragment extends DialogFragment {
+    ...
+
+    @Override
+    public Dialog onCreateDialog(Bundle savedInstanceState) {
+        // Build the dialog and set up the button click handlers
+        AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+        builder.setMessage(R.string.dialog_fire_missiles)
+               .setPositiveButton(R.string.fire, new DialogInterface.OnClickListener() {
+                   public void onClick(DialogInterface dialog, int id) {
+                       // Send the positive button event back to the host activity
+                       mListener.onDialogPositiveClick(NoticeDialogFragment.this);
+                   }
+               })
+               .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
+                   public void onClick(DialogInterface dialog, int id) {
+                       // Send the negative button event back to the host activity
+                       mListener.onDialogNegativeClick(NoticeDialogFragment.this);
+                   }
+               });
+        return builder.create();
+    }
+}
+
+ + + +

Отображение диалогового окна

+ +

Для отображения диалогового окна необходимо создать экземпляр {@link +android.support.v4.app.DialogFragment} и вызвать {@link android.support.v4.app.DialogFragment#show +show()}, передавая {@link android.support.v4.app.FragmentManager} и наименование тега +для фрагмента диалога.

+ +

Можно получить {@link android.support.v4.app.FragmentManager} путем вызова +{@link android.support.v4.app.FragmentActivity#getSupportFragmentManager()} из + {@link android.support.v4.app.FragmentActivity} или {@link +android.support.v4.app.Fragment#getFragmentManager()} из {@link +android.support.v4.app.Fragment}. Пример:

+ +
+public void confirmFireMissiles() {
+    DialogFragment newFragment = new FireMissilesDialogFragment();
+    newFragment.show(getSupportFragmentManager(), "missiles");
+}
+
+ +

Второй аргумент, {@code "missiles"}, — это уникальное наименование тега, которое система использует для сохранения +и восстановления состояния фрагмента, когда это необходимо. С помощью этого тега также можно управлять +фрагментом путем вызова {@link android.support.v4.app.FragmentManager#findFragmentByTag +findFragmentByTag()}.

+ + + + +

Отображение диалогового окна в полноэкранном режиме или в виде встроенного фрагмента

+ +

Вам может понадобиться макет пользовательского интерфейса, в котором в некоторых ситуациях часть пользовательского интерфейса должна появляться как диалоговое окно +, отображаемое в полноэкранном режиме либо в виде встроенного фрагмента (возможно, в зависимости от того, +маленький или большой экран у устройства). С помощью класса {@link android.support.v4.app.DialogFragment} +обеспечивается гибкость решения, поскольку он может вести себя как встраиваемый {@link +android.support.v4.app.Fragment}.

+ +

Тем не менее, в этом случае нельзя использовать{@link android.app.AlertDialog.Builder AlertDialog.Builder} +или другие объекты {@link android.app.Dialog} для построения диалогового окна. Если +необходимо сделать {@link android.support.v4.app.DialogFragment} +встраиваемым, нужно определить пользовательский интерфейс диалогового окна в макете методом обратного вызова +{@link android.support.v4.app.DialogFragment#onCreateView +onCreateView()}.

+ +

В качестве примера приведен {@link android.support.v4.app.DialogFragment}, который появляется либо в виде +диалогового окна, либо в виде встраиваемого фрагмента (используя макет с наименованием purchase_items.xml):

+ +
+public class CustomDialogFragment extends DialogFragment {
+    /** The system calls this to get the DialogFragment's layout, regardless
+        of whether it's being displayed as a dialog or an embedded fragment. */
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+            Bundle savedInstanceState) {
+        // Inflate the layout to use as dialog or embedded fragment
+        return inflater.inflate(R.layout.purchase_items, container, false);
+    }
+  
+    /** The system calls this only when creating the layout in a dialog. */
+    @Override
+    public Dialog onCreateDialog(Bundle savedInstanceState) {
+        // The only reason you might override this method when using onCreateView() is
+        // to modify any dialog characteristics. For example, the dialog includes a
+        // title by default, but your custom layout might not need it. So here you can
+        // remove the dialog title, but you must call the superclass to get the Dialog.
+        Dialog dialog = super.onCreateDialog(savedInstanceState);
+        dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
+        return dialog;
+    }
+}
+
+ +

Приведен пример кода, реализующего принятие решения об отображении фргмента в качестве диалогового окна +или полноэкранного пользовательского интерфейса на основе размера экрана:

+ +
+public void showDialog() {
+    FragmentManager fragmentManager = getSupportFragmentManager();
+    CustomDialogFragment newFragment = new CustomDialogFragment();
+    
+    if (mIsLargeLayout) {
+        // The device is using a large layout, so show the fragment as a dialog
+        newFragment.show(fragmentManager, "dialog");
+    } else {
+        // The device is smaller, so show the fragment fullscreen
+        FragmentTransaction transaction = fragmentManager.beginTransaction();
+        // For a little polish, specify a transition animation
+        transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
+        // To make it fullscreen, use the 'content' root view as the container
+        // for the fragment, which is always the root view for the activity
+        transaction.add(android.R.id.content, newFragment)
+                   .addToBackStack(null).commit();
+    }
+}
+
+ +

Подробные сведения о выполнении операций с фрагментами приведены в руководстве +Фрагменты.

+ +

В приведенном примере mIsLargeLayout булеан указывает, должно ли текущее устройство использовать + большой макет приложения (и отображать фрагмент как диалоговое окно, а не +в полноэкранном режиме). Лучшим способом установить такой вид булеана является объявление +значения булевой переменнойальтернативным значением для других размеров экранов. В качестве примера приведены два +варианта булевых ресурсов для различных размеров экранов:

+ +

res/values/bools.xml

+
+<!-- Default boolean values -->
+<resources>
+    <bool name="large_layout">false</bool>
+</resources>
+
+ +

res/values-large/bools.xml

+
+<!-- Large screen boolean values -->
+<resources>
+    <bool name="large_layout">true</bool>
+</resources>
+
+ +

Затем можно инизиализировать значение {@code mIsLargeLayout} в течение выполнения метода операции +{@link android.app.Activity#onCreate onCreate()}:

+ +
+boolean mIsLargeLayout;
+
+@Override
+public void onCreate(Bundle savedInstanceState) {
+    super.onCreate(savedInstanceState);
+    setContentView(R.layout.activity_main);
+
+    mIsLargeLayout = getResources().getBoolean(R.bool.large_layout);
+}
+
+ + + +

Отображение операции в качестве диалога на больших экранах

+ +

Вместо отображения диалогового окна в полноэкранном режиме на экранах малого размера можно +отображать {@link android.app.Activity} в качестве диалогового окна на +экранах большого размера. Выбор зависит от дизайна приложения, но +отображение операции в качестве диалогового окна имеет смысл, когда приложение предназначено для использования на малых +экранах, и необходимо улучшить взаимодейтсвие с ним на планшетах, показывая кратковременные операции +в качестве диалогового окна.

+ +

Для отображения операции в качестве диалогового окна только на больших экранах +необходимо применить тему {@link android.R.style#Theme_Holo_DialogWhenLarge Theme.Holo.DialogWhenLarge} +к элементу манифеста {@code +<операция>}:

+ +
+<activity android:theme="@android:style/Theme.Holo.DialogWhenLarge" >
+
+ +

Подробная информация о темах операций приведена в руководстве Стили и темы.

+ + + +

Закрытие диалогового окна

+ +

Когда пользователь нажимает кнопки, созданные с помощью +{@link android.app.AlertDialog.Builder}, система закрывает диалоговое окно самостоятельно.

+ +

Система также закрывает диалоговое окно, когда пользователь нажимает на элемент списка в диалоговом окне, за исключением +списков с переключателями или флажками. В иных случаях можно вручную закрыть диалоговое окно +путем вызова {@link android.support.v4.app.DialogFragment#dismiss()} в {@link +android.support.v4.app.DialogFragment}.

+ +

В случае, если необходимо произвести определенные +действия после закрытия диалогового окна, можно реализовать метод {@link +android.support.v4.app.DialogFragment#onDismiss onDismiss()} в {@link +android.support.v4.app.DialogFragment}.

+ +

Также можно отменить диалоговое окно. Это особое событие, возникающее, когда пользователь +покинул диалоговое окно, не завершив задачу. Так происходит, когда пользователь нажимает кнопку +Назад, касается экрана за областью диалогового окна, +либо когда задано {@link android.app.Dialog#cancel()} в {@link +android.app.Dialog} (например, в качестве отклика на нажатие кнопки «Отмена» в диалоговом окне).

+ +

Как показано в примере выше, можно ответить на событие отмены с помощью +{@link android.support.v4.app.DialogFragment#onCancel onCancel()} в классе {@link +android.support.v4.app.DialogFragment}.

+ +

Примечание: Система вызывает +{@link android.support.v4.app.DialogFragment#onDismiss onDismiss()} при каждом событии, +которое вызывается методом обратного вызова{@link android.support.v4.app.DialogFragment#onCancel onCancel()}. Тем не менее, +при вызове{@link android.app.Dialog#dismiss Dialog.dismiss()} или {@link +android.support.v4.app.DialogFragment#dismiss DialogFragment.dismiss()}, +система вызывает {@link android.support.v4.app.DialogFragment#onDismiss onDismiss()} , а +не {@link android.support.v4.app.DialogFragment#onCancel onCancel()}. Поэтому в общих случаях +вызов{@link android.support.v4.app.DialogFragment#dismiss dismiss()} производится при нажатии пользователем +положительной кнопки в диалоговом окне, а после диалоговое окно закрывается.

+ + diff --git a/docs/html-intl/intl/ru/guide/topics/ui/menus.jd b/docs/html-intl/intl/ru/guide/topics/ui/menus.jd new file mode 100644 index 0000000000000000000000000000000000000000..2f3ce1eb95804630313b6f6d576a3c9a24226190 --- /dev/null +++ b/docs/html-intl/intl/ru/guide/topics/ui/menus.jd @@ -0,0 +1,1031 @@ +page.title=Меню +parent.title=Пользовательский интерфейс +parent.link=index.html +@jd:body + + + +

Меню являются стандартным компонентом пользовательского интерфейса в приложениях многих типов. Для обеспечения привычной +и единообразной технологии работы с приложением следует представлять действия пользователя и другие варианты выбора в своих операциях +с помощью API-интерфейсов класса {@link android.view.Menu}.

+ +

Начиная с версии Android 3.0 (уровень API 11) в устройствах, работающих под управлением Android, +наличие отдельной кнопки Меню больше не требуется. С учетом этого изменения приложения для Android должны перестать +зависеть от традиционной панели меню из 6 пунктов. Вместо нее в них должна быть строка действий с часто используемыми +действиями пользователя.

+ +

Несмотря на то что оформление и поведение некоторых пунктов меню изменились, семантика для определения +набора действий и вариантов по-прежнему основана на API-интерфейсах класса {@link android.view.Menu}. В этом +руководстве рассказывается, как создавать три основополагающих типа меню или представлений действий в системе +Android всех версий:

+ +
+
Меню параметров и строка действий
+
Пункты меню параметров представляют собой основные варианты выбора действий в пределах +операции. Именно здесь следует размещать действия, которые затрагивают приложение в целом, например: +"Поиск", "Составить сообщение эл. почты" и "Настройки". +

При разработке приложений для версии Android 2.3 или более ранних версий пользователи могут +открыть панель меню параметров нажатием кнопки Меню.

+

В версии Android 3.0 и последующих версиях пункты меню параметров размещаются в строке действий в виде сочетания отображаемых на экране вариантов +действий и раскрывающегося списка дополнительных вариантов выбора. Начиная с Android 3.0 кнопка Меню больше не используется (на некоторых +устройствах +ее нет), поэтому для предоставления доступа к действиям и другим вариантам выбора вам следует перейти к использованию +строки действий.

+

См. раздел Создание меню параметров

+
+ +
Контекстное меню и режим контекстных действий
+ +
Контекстное меню ― это плавающее меню, которое открывается, когда +пользователь длительно нажимает на элемент. В нем содержатся действия, которые затрагивают выбранный контент или +контекстный кадр. +

При разработке приложения для версии Android 3.0 или выше вместо этого для обеспечения действий с выбранным контентом следует использовать режим контекстных действий. В этом режиме +в строке, расположенной вверху экрана, отображаются пункты действий, затрагивающие выбранный контент, причем пользователь может +выбрать сразу несколько элементов.

+

См. раздел Создание контекстного меню

+
+ +
Всплывающее меню
+
Во всплывающем меню отображается вертикальный список пунктов, который привязан к представлению, +вызвавшему меню. Он хорошо подходит для предоставления возможности дополнительных вариантов действий, относящихся к определенному контенту или +для выдачи вариантов для второй части команды. Действия во всплывающем меню +не должны напрямую затрагивать соответствующий контент — для этого предназначены контекстные +действия. Всплывающее меню предназначено для расширенных действий, относящихся к областям контента в вашей +операции. +

См. раздел Создание всплывающего меню

+
+
+ + + +

Определение меню в файле XML

+ +

Для определения пунктов меню всех типов в Android используется стандартный формат XML. +Вместо того чтобы создавать меню в коде своей операции, определять меню и все его пункты следует в + ресурсе меню формата XML. После этого +ресурс меню можно будет загружать как объект {@link android.view.Menu} в свои операции или +фрагменты.

+ +

Использовать ресурсы меню рекомендуется по нескольким причинам:

+
    +
  • в XML проще визуализировать структуру меню;
  • +
  • это позволяет отделить контент для меню от кода, определяющего работу приложения;
  • +
  • это позволяет создавать альтернативные варианты меню для разных версий платформы, +размеров экрана и других конфигураций путем использования структуры ресурсов приложения.
  • +
+ +

Чтобы определить меню, создайте файл XML в папке res/menu/ +вашего проекта и постройте меню со следующими элементами:

+
+
<menu>
+
Определяет класс {@link android.view.Menu}, который является контейнером для пунктов меню. Элемент +<menu> должен быть корневым узлом файла, в котором может находиться один или несколько элементов +<item> и <group>.
+ +
<item>
+
Создает класс {@link android.view.MenuItem}, который представляет один пункт меню. Этот +элемент может содержать вложенный элемент <menu> для создания вложенных меню.
+ +
<group>
+
Необязательный, невидимый контейнер для элементов {@code <item>}. Он позволяет +разделять пункты меню на категории и назначать им одинаковые свойства, такие как активное состояние и видимость. Подробные +сведения изложены в разделе Создание групп меню.
+
+ + +

Вот пример меню с именем game_menu.xml:

+
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:id="@+id/new_game"
+          android:icon="@drawable/ic_new_game"
+          android:title="@string/new_game"
+          android:showAsAction="ifRoom"/>
+    <item android:id="@+id/help"
+          android:icon="@drawable/ic_help"
+          android:title="@string/help" />
+</menu>
+
+ +

Элемент <item> поддерживает несколько атрибутов, с помощью которых можно определить внешний вид и +поведение пункта меню. Пункты приведенного выше меню имеют следующие атрибуты:

+ +
+
{@code android:id}
+
Идентификатор ресурса, который является уникальным для этого пункта, что позволяет приложению распознавать пункт, +когда его выбирает пользователь.
+
{@code android:icon}
+
Ссылка на графический элемент, который будет использоваться в качестве значка пункта меню.
+
{@code android:title}
+
Ссылка на строку, которая будет использоваться в качестве названия пункта меню.
+
{@code android:showAsAction}
+
Указывает, когда и как этот пункт должен отображаться в строке действий.
+
+ +

Это самые важные атрибуты, которые следует использовать, но есть также множество других атрибутов. +Сведения обо всех поддерживаемых атрибутах см. в документе Ресурс меню.

+ +

К пункту любого меню (кроме вложенного меню) можно прикрепить вложенное меню, добавив элемент {@code <menu>} +в качестве дочернего элемента {@code <item>}. Вложенные меню полезны, когда в приложении имеется множество +функций, которые можно разделить на категории подобно строке меню приложения для ПК ("Файл", +"Правка", "Вид" и т. д.). Например:

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:id="@+id/file"
+          android:title="@string/file" >
+        <!-- "file" submenu -->
+        <menu>
+            <item android:id="@+id/create_new"
+                  android:title="@string/create_new" />
+            <item android:id="@+id/open"
+                  android:title="@string/open" />
+        </menu>
+    </item>
+</menu>
+
+ +

Для использования меню в операции необходимо загрузить ресурс меню (преобразовать ресурс XML +в программируемый объект) с помощью метода {@link android.view.MenuInflater#inflate(int,Menu) +MenuInflater.inflate()}. В приведенных далее разделах рассказывается, как загружать меню +каждого типа.

+ + + +

Создание меню параметров

+ +
+ +

Рисунок 1. Меню параметров в +браузере на Android 2.3.

+
+ +

В меню параметров следует размещать действия и другие варианты выбора, которые имеют отношение к +контексту текущей операции, например: "Поиск", "Составить сообщение эл. почты" и "Настройки".

+ +

Место, где отображаются на экране пункты вашего меню параметров, определяется версией платформы, для которой +разработано приложение.

+ +
    +
  • Если приложение написано для версии Android 2.3.x (уровень API 10) или +более ранней, содержимое вашего меню параметров отображается внизу экрана, когда пользователь +нажимает кнопку Меню, как показано на рисунке 1. Когда меню открывается, первой видимой частью является меню +значков, +в котором имеется шесть пунктов. Если в вашем меню больше шести пунктов, система Android разместит +шестой и остальные пункты в дополнительном меню, которое пользователь может открыть, выбрав вариант + Еще.
  • + +
  • Если приложение предназначено для версии Android 3.0 (уровень API 11) и +более поздних, пункты меню параметров будут отображаться в строке действий. По умолчанию система +размещает все действия на панели дополнительных вариантов, которые пользователь может открыть с помощью значка дополнительных действий, расположенного +с правой стороны строки действий (либо нажатием кнопки Меню, если на устройстве есть такая кнопка). Чтобы +обеспечить +быстрый доступ к важным действиям, можно принудительно разместить несколько пунктов меню в строке действий, добавив +{@code android:showAsAction="ifRoom"} к соответствующим элементам {@code <item>} (см. рисунок +2).

    Подробные сведения о пунктах действий и других особенностях строки действий см. в руководстве Строка действий.

    +

    Примечание. Даже если ваше приложение не предназначено для работы в версии Android 3.0 или +более поздней, можно создать собственный макет со строкой действий, чтобы реализовать похожий эффект. Пример того, как можно +поддерживать работу строки действий в старых версиях Android см. в образце кода, иллюстрирующем + Совместимость строки действий.

    +
  • +
+ + +

Рисунок 2. Строка действий из приложения Honeycomb Gallery, содержащая +вкладки навигации и пункт включения камеры (а также кнопку открытия дополнительной панели действий).

+ +

Объявлять пункты меню параметров можно либо из подкласса {@link android.app.Activity}, +либо из подкласса {@link android.app.Fragment}. Если и ваша операция, и фрагменты +объявляют пункты меню параметров, в пользовательском интерфейсе они объединяются. Сначала отображаются пункты +операции, а за ними следуют пункты фрагментов в том порядке, в котором каждый фрагмент добавляется в +операцию. При необходимости можно изменить порядок следования пунктов меню с помощью атрибута {@code android:orderInCategory}, +указываемого в каждом {@code <item>}, который требуется переместить.

+ +

Чтобы указать меню параметров для операции, переопределите {@link +android.app.Activity#onCreateOptionsMenu(Menu) onCreateOptionsMenu()} (фрагменты предоставляют собственный обратный +вызов {@link android.app.Fragment#onCreateOptionsMenu onCreateOptionsMenu()}). В этом +методе можно загрузить собственный ресурс меню (определенный в XML) в класс {@link +android.view.Menu}, имеющийся в обратном вызове. Например:

+ +
+@Override
+public boolean onCreateOptionsMenu(Menu menu) {
+    MenuInflater inflater = {@link android.app.Activity#getMenuInflater()};
+    inflater.inflate(R.menu.game_menu, menu);
+    return true;
+}
+
+ +

Пункты меню также можно добавлять с помощью {@link android.view.Menu#add(int,int,int,int) +add()}, а получать их с помощью {@link android.view.Menu#findItem findItem()} для пересмотра их +свойств с помощью API-интерфейсов {@link android.view.MenuItem}.

+ +

Если ваше приложение предназначено для версии Android 2.3.x или более ранней, система вызывает метод {@link +android.app.Activity#onCreateOptionsMenu(Menu) onCreateOptionsMenu()} для создания меню параметров, +когда пользователь открывает это меню впервые. Если приложение предназначено для версии Android 3.0 и или более поздней, система +вызывает метод{@link android.app.Activity#onCreateOptionsMenu(Menu) onCreateOptionsMenu()} при +запуске операции, чтобы отобразить пункты в строке действий.

+ + + +

Обработка нажатий

+ +

Когда пользователь выбирает пункт меню параметров (в том числе пункты действий из строки действий), +система вызывает метод {@link android.app.Activity#onOptionsItemSelected(MenuItem) +onOptionsItemSelected()} вашей операции. Этот метод передает выбранный класс {@link android.view.MenuItem}. Идентифицировать +пункт меню можно, вызвав метод {@link android.view.MenuItem#getItemId()}, который возвращает уникальный +идентификатор пункта меню (определенный атрибутом {@code android:id} из ресурса меню или +целым числом, переданным методу {@link android.view.Menu#add(int,int,int,int) add()}). Этот идентификатор +можно сопоставить с известными пунктами меню, чтобы выполнить соответствующее действие. Например:

+ +
+@Override
+public boolean onOptionsItemSelected(MenuItem item) {
+    // Handle item selection
+    switch (item.getItemId()) {
+        case R.id.new_game:
+            newGame();
+            return true;
+        case R.id.help:
+            showHelp();
+            return true;
+        default:
+            return super.onOptionsItemSelected(item);
+    }
+}
+
+ +

Когда пункт меню успешно обработан, возвращается {@code true}. Если пункт меню не +обрабатывается, следует вызвать реализацию суперкласса {@link +android.app.Activity#onOptionsItemSelected(MenuItem) onOptionsItemSelected()} (реализация +по умолчанию возвращает значение false).

+ +

Если в вашей операции имеются фрагменты, система сначала вызовет метод {@link +android.app.Activity#onOptionsItemSelected(MenuItem) onOptionsItemSelected()} для операции, а затем +будет вызывать этот метод для каждого фрагмента (в том порядке, в котором они были добавлены), пока он не возвратит +значение{@code true} или не закончатся фрагменты.

+ +

Совет. В Android 3.0 появилась возможность определять в XML поведение +при нажатии для пунктов меню с помощью атрибута {@code android:onClick}. Значением этого +атрибута должно быть имя метода, определенное операцией с помощью меню. Этот метод +должен быть общедоступным и принимать один параметр {@link android.view.MenuItem}, — когда система +вызывает этот метод, она передает ему выбранный пункт меню. Подробные сведения и пример см. в документе Ресурс меню.

+ +

Совет. Если в приложении предусмотрено несколько операций и +в некоторых из них имеются одинаковые меню параметров, рассмотрите возможность создания +операции, которая будет использовать исключительно методы {@link android.app.Activity#onCreateOptionsMenu(Menu) +onCreateOptionsMenu()} и {@link android.app.Activity#onOptionsItemSelected(MenuItem) +onOptionsItemSelected()}. Затем распространите этот класс на все операции, у которых должно быть +одинаковое меню параметров. Таким образом можно управлять одним набором кода для обработки действий +меню, а каждый класс-потомок при этом будет наследовать режимы работы меню. +Если требуется добавить пункты меню в одну из операций-потомков, +переопределите метод {@link android.app.Activity#onCreateOptionsMenu(Menu) +onCreateOptionsMenu()} в этой операции. Вызовите метод {@code super.onCreateOptionsMenu(menu)}, с тем чтобы +создать первоначальные пункты меню, а затем добавьте новые пункты меню с помощью метода {@link +android.view.Menu#add(int,int,int,int) menu.add()}. Также можно переопределять режимы работы +суперкласса для отдельных пунктов меню.

+ + +

Изменение пунктов меню во время выполнения

+ +

После того как система вызовет метод {@link android.app.Activity#onCreateOptionsMenu(Menu) +onCreateOptionsMenu()}, она сохранит заполненный вами экземпляр {@link android.view.Menu} и +будет вызывать метод {@link android.app.Activity#onCreateOptionsMenu(Menu) onCreateOptionsMenu()} +,только если меню по каким-то причинам станет некорректным. Однако метод {@link +android.app.Activity#onCreateOptionsMenu(Menu) onCreateOptionsMenu()} следует использовать только для создания начального +состояния меню, а не для внесения в него изменений в течение жизненного цикла операции.

+ +

Если вам требуется изменять меню параметров в зависимости от +событий, которые возникают в течение жизненного цикла операции, сделать это можно в +методе{@link android.app.Activity#onPrepareOptionsMenu(Menu) onPrepareOptionsMenu()}. Этот +метод передает объект {@link android.view.Menu} в том виде, в котором он в данный момент существует. Его-то и можно изменить +путем, например, добавления, удаления или отключения пунктов меню. (Фрагменты также предоставляют обратный вызов {@link +android.app.Fragment#onPrepareOptionsMenu onPrepareOptionsMenu()}.)

+ +

В версии Android 2.3.x или более ранней система вызывает метод {@link +android.app.Activity#onPrepareOptionsMenu(Menu) +onPrepareOptionsMenu()} каждый раз, когда пользователь открывает меню параметров (нажимает кнопку Меню +).

+ +

В версии Android 3.0 и последующих версиях считается, что меню параметров всегда открыто, когда пункты меню +приведены в строке действий. Когда возникает событие и требуется обновить меню, следует +вызвать метод {@link android.app.Activity#invalidateOptionsMenu invalidateOptionsMenu()}, чтобы запросить у +системы вызов метода {@link android.app.Activity#onPrepareOptionsMenu(Menu) onPrepareOptionsMenu()}.

+ +

Примечание. +Никогда не следует изменять пункты меню параметров с учетом класса {@link android.view.View}, действующего +в данный момент. В сенсорном режиме (когда пользователь не использует трекбол или кнопки направления движения) фокус +не может переводиться на представления, поэтому никогда не следует использовать фокус в качестве основы для изменения +пунктов меню параметров. Если вы желаете предоставить пункты меню, которые зависят от контекста {@link +android.view.View}, используйте контекстное меню.

+ + + + +

Создание контекстного меню

+ +
+ +

Рисунок 3. Снимки экрана с плавающим контекстным меню (слева) +и строкой контекстных действий (справа).

+
+ +

В контекстном меню содержатся действия, которые затрагивают определенный элемент или контекстный кадр в пользовательском интерфейсе. Контекстное +меню можно создать для любого представления, но чаще всего они используются для элементов из {@link +android.widget.ListView}, {@link android.widget.GridView} или других групп представлений, в которых +пользователь может выполнять действия непосредственно с каждым элементом.

+ +

Существует два способа предоставления возможности контекстных действий:

+
    +
  • В плавающем контекстном меню. Меню отображается в виде +плавающего списка пунктов меню (наподобие диалогового окна), когда пользователь длительно нажимает на экран (нажимает и +удерживает нажатым) в представлении, которое объявляет поддержку контекстного меню. Пользователи могут каждый раз выполнять контекстное +действие только с одним элементом.
  • + +
  • В режиме контекстных действий. Этот режим является системной реализацией +{@link android.view.ActionMode}, которая отображает строку контекстных действий вверху +экрана с пунктами действий, которые затрагивают выбранные элементы. Когда этот режим активен, пользователи +могут одновременно выполнять действие с несколькими элементами (если это допускается приложением).
  • +
+ +

Примечание. Режим контекстных действий поддерживается в версии Android 3.0 (уровень API +11) и последующих версиях. Если этот режим предусмотрен, именно его рекомендуется использовать для отображения контекстных +действий. Если ваше приложение поддерживает версии ниже 3.0, то для этих устройств следует вернуться к плавающему +контекстному меню.

+ + +

Создание плавающего контекстного меню

+ +

Программирование плавающего контекстного меню

+
    +
  1. Зарегистрируйте класс {@link android.view.View}, с которым следует связать контекстное меню, +вызвав метод {@link android.app.Activity#registerForContextMenu(View) registerForContextMenu()} и передав +ему {@link android.view.View}. +

    Если операция использует {@link android.widget.ListView} или {@link android.widget.GridView} и +требуется, чтобы каждый элемент предоставлял одинаковое контекстное меню, зарегистрируйте все элементы для контекстного меню, +передав {@link android.widget.ListView} или {@link android.widget.GridView} методу {@link +android.app.Activity#registerForContextMenu(View) registerForContextMenu()}.

    +
  2. + +
  3. Реализуйте метод {@link +android.view.View.OnCreateContextMenuListener#onCreateContextMenu onCreateContextMenu()} в + {@link android.app.Activity} или {@link android.app.Fragment}. +

    Когда зарегистрированное представление примет событие длительного нажатия, система вызовет ваш метод {@link +android.view.View.OnCreateContextMenuListener#onCreateContextMenu onCreateContextMenu()} +. Именно здесь определяются пункты меню. Делается это обычно путем загрузки ресурса меню. Например: +

    +
    +@Override
    +public void onCreateContextMenu(ContextMenu menu, View v,
    +                                ContextMenuInfo menuInfo) {
    +    super.onCreateContextMenu(menu, v, menuInfo);
    +    MenuInflater inflater = getMenuInflater();
    +    inflater.inflate(R.menu.context_menu, menu);
    +}
    +
    + +

    {@link android.view.MenuInflater} позволяет загружать контекстное меню из ресурса меню. В число параметров метода +обратного вызова входят {@link android.view.View}, +выбранный пользователем, и объект{@link android.view.ContextMenu.ContextMenuInfo}, который предоставляет +дополнительную информацию о выбранном элементе. Если в вашей операции есть несколько представлений и все они предоставляют +разные контекстные меню, то с помощью этих параметров можно определять, какое контекстное меню +загружать.

    +
  4. + +
  5. Реализуйте метод {@link android.app.Activity#onContextItemSelected(MenuItem) +onContextItemSelected()}. +

    Когда пользователь выбирает пункт меню, система вызывает этот метод, с тем чтобы вы могли выполнить +соответствующее действие. Например:

    + +
    +@Override
    +public boolean onContextItemSelected(MenuItem item) {
    +    AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo();
    +    switch (item.getItemId()) {
    +        case R.id.edit:
    +            editNote(info.id);
    +            return true;
    +        case R.id.delete:
    +            deleteNote(info.id);
    +            return true;
    +        default:
    +            return super.onContextItemSelected(item);
    +    }
    +}
    +
    + +

    Метод {@link android.view.MenuItem#getItemId()} запрашивает идентификатор для +выбранного пункта меню. Идентификаторы должны быть назначены каждому пункту меню в файле XML с помощью атрибута {@code +android:id}, как описано в разделе Определение меню в файле +XML.

    + +

    Когда пункт меню успешно обработан, возвращается {@code true}. Если пункт меню не +обрабатывается, следует передать его реализации суперкласса. Если в вашей операции есть фрагменты, +то она получит этот обратный вызов первой. Вызывая суперкласс, когда пункт меню не обрабатывается, система +передает событие соответствующему методу обратного вызова в каждом фрагменте по одному (в порядке +добавления каждого фрагмента), пока не будет возвращено значение {@code true} или {@code false}. (Реализация +{@link android.app.Activity} и {@code android.app.Fragment} по умолчанию возвращает {@code +false}, поэтому, когда событие не обрабатывается, следует всегда вызывать суперкласс.)

    +
  6. +
+ + +

Использование режима контекстных действий

+ +

Режим контекстных действий представляет собой системную реализацию класса {@link android.view.ActionMode}, которая +направляет пользователя на выполнение контекстных действий при взаимодействии с приложением. Когда +пользователь использует этот режим, выбирая элемент, вверху экрана открывается строка контекстных действий, +содержащая действия, которые пользователь может выполнить с выбранными в данный момент элементами. В этом режиме +пользователь может выбирать несколько элементов (если это допускается приложением), снимать выделения с элементов и продолжать +навигацию в операции (в тех пределах, в которых это поддерживается приложением). Режим контекстных действий отключается, +а строка контекстных действий исчезает, когда пользователь снимет выделение со всех элементов, нажмет кнопку НАЗАД +или выберет действие Готово, расположенное с левой стороны строки.

+ +

Примечание. Строка контекстных действий не обязательно бывает +связана со строкой действий. Они работают +независимо друг от друга, даже несмотря на то, что визуально строка контекстных действий занимает положение +строки действий.

+ +

Если вы разрабатываете приложение для версии Android 3.0 (уровень API 11) или последующих версий, то +обычно вместо плавающего контекстного меню вам следует использовать контекстные действия.

+ +

Для представлений, которые предоставляют возможность контекстные действия, обычно следует вызывать режим контекстных действий +при возникновении одного из двух (или сразу обоих) событий:

+
    +
  • пользователь длительно нажимает в представлении;
  • +
  • пользователь устанавливает флажок или выбирает другой подобный компонент пользовательского интерфейса в представлении.
  • +
+ +

То, каким образом ваше представление вызывает режим контекстных действий и определяет поведение каждого +действия, зависит от вас. Есть два базовых варианта:

+
    +
  • для контекстных действий в отдельных, произвольных представлениях;
  • +
  • для пакетных контекстных действий с группами элементов в {@link +android.widget.ListView} или {@link android.widget.GridView} (что позволяет пользователю выбирать по несколько +элементов и выполнять действие с ними всеми).
  • +
+ +

В следующих разделах описывается конфигурация, необходимая для каждого из этих вариантов.

+ + +

Включение режима контекстных действий для отдельных представлений

+ +

Если вам требуется вызывать режим контекстных действий, только когда пользователь выбирает определенные +представления, то вам следует:

+
    +
  1. Реализовать интерфейс {@link android.view.ActionMode.Callback}. В его методах обратного вызова вы +можете указать действия для строки контекстных действий, реагировать на нажатия пунктов действий и +обрабатывать другие события жизненного цикла для режима действий.
  2. +
  3. Вызывайте {@link android.app.Activity#startActionMode startActionMode()}, когда требуется показать +строку (например, когда пользователь выполняет длительное нажатие представления).
  4. +
+ +

Например:

+ +
    +
  1. Реализуйте интерфейс {@link android.view.ActionMode.Callback ActionMode.Callback}: +
    +private ActionMode.Callback mActionModeCallback = new ActionMode.Callback() {
    +
    +    // Called when the action mode is created; startActionMode() was called
    +    @Override
    +    public boolean onCreateActionMode(ActionMode mode, Menu menu) {
    +        // Inflate a menu resource providing context menu items
    +        MenuInflater inflater = mode.getMenuInflater();
    +        inflater.inflate(R.menu.context_menu, menu);
    +        return true;
    +    }
    +
    +    // Called each time the action mode is shown. Always called after onCreateActionMode, but
    +    // may be called multiple times if the mode is invalidated.
    +    @Override
    +    public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
    +        return false; // Return false if nothing is done
    +    }
    +
    +    // Called when the user selects a contextual menu item
    +    @Override
    +    public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
    +        switch (item.getItemId()) {
    +            case R.id.menu_share:
    +                shareCurrentItem();
    +                mode.finish(); // Action picked, so close the CAB
    +                return true;
    +            default:
    +                return false;
    +        }
    +    }
    +
    +    // Called when the user exits the action mode
    +    @Override
    +    public void onDestroyActionMode(ActionMode mode) {
    +        mActionMode = null;
    +    }
    +};
    +
    + +

    Обратите внимание, что эти обратные вызовы событий почти точно такие же, как и обратные вызовы для меню параметров. Отличаются они только тем, что каждый из них также передает объект {@link +android.view.ActionMode}, связанный с событием. С помощью API-интерфейсов {@link +android.view.ActionMode} можно вносить различные изменения в CAB, например, указывать другой заголовок и +подзаголовок с помощью {@link android.view.ActionMode#setTitle setTitle()} и {@link +android.view.ActionMode#setSubtitle setSubtitle()} (удобно для указания количества выбранных +элементов).

    + +

    Также обратите внимание, что приведенный выше образец кода задает для переменной {@code mActionMode} значение null, когда +режим действия прекращает свое существование. Далее вы узнаете, каким образом он инициализируется и чем может быть +полезно сохранение составной переменной в операции или фрагменте.

    +
  2. + +
  3. Для включения режима контекстных действий, когда это необходимо, +например, в ответ на длительное нажатие {@link +android.view.View}, вызывайте {@link android.app.Activity#startActionMode startActionMode()}:

    + +
    +someView.setOnLongClickListener(new View.OnLongClickListener() {
    +    // Called when the user long-clicks on someView
    +    public boolean onLongClick(View view) {
    +        if (mActionMode != null) {
    +            return false;
    +        }
    +
    +        // Start the CAB using the ActionMode.Callback defined above
    +        mActionMode = getActivity().startActionMode(mActionModeCallback);
    +        view.setSelected(true);
    +        return true;
    +    }
    +});
    +
    + +

    При вызове метода {@link android.app.Activity#startActionMode startActionMode()} система возвращает +созданный класс {@link android.view.ActionMode}. Сохранив его в составной переменной, вы сможете +вносить изменения в строку контекстных действий в ответ на другие события. В приведенном выше образце кода +{@link android.view.ActionMode} используется для того, чтобы экземпляр {@link android.view.ActionMode} +не создавался повторно, если он уже активен. Достигается это путем проверки, имеет ли элемент значение null перед запуском +режима действий.

    +
  4. +
+ + + +

Включение пакетных контекстных действий в ListView или GridView

+ +

Если при наличии набора элементов в {@link android.widget.ListView} или {@link +android.widget.GridView} (либо другом расширении {@link android.widget.AbsListView}) требуется +разрешить пользователям выполнять пакетные действия, следует:

+ +
    +
  • реализовать интерфейс {@link android.widget.AbsListView.MultiChoiceModeListener} и задать его +для группы представлений с помощью метода {@link android.widget.AbsListView#setMultiChoiceModeListener +setMultiChoiceModeListener()}; в методах обратного вызова приемника событий вы можете указывать действия +для строки контекстных действий, реагировать на нажатия пунктов действий и обрабатывать другие обратные вызовы, +унаследованные от интерфейса {@link android.view.ActionMode.Callback};
  • + +
  • вызвать метод {@link android.widget.AbsListView#setChoiceMode setChoiceMode()} с аргументом {@link +android.widget.AbsListView#CHOICE_MODE_MULTIPLE_MODAL}.
  • +
+ +

Например:

+ +
+ListView listView = getListView();
+listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL);
+listView.setMultiChoiceModeListener(new MultiChoiceModeListener() {
+
+    @Override
+    public void onItemCheckedStateChanged(ActionMode mode, int position,
+                                          long id, boolean checked) {
+        // Here you can do something when items are selected/de-selected,
+        // such as update the title in the CAB
+    }
+
+    @Override
+    public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
+        // Respond to clicks on the actions in the CAB
+        switch (item.getItemId()) {
+            case R.id.menu_delete:
+                deleteSelectedItems();
+                mode.finish(); // Action picked, so close the CAB
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    @Override
+    public boolean onCreateActionMode(ActionMode mode, Menu menu) {
+        // Inflate the menu for the CAB
+        MenuInflater inflater = mode.getMenuInflater();
+        inflater.inflate(R.menu.context, menu);
+        return true;
+    }
+
+    @Override
+    public void onDestroyActionMode(ActionMode mode) {
+        // Here you can make any necessary updates to the activity when
+        // the CAB is removed. By default, selected items are deselected/unchecked.
+    }
+
+    @Override
+    public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
+        // Here you can perform updates to the CAB due to
+        // an {@link android.view.ActionMode#invalidate} request
+        return false;
+    }
+});
+
+ +

Готово. Теперь, когда пользователь выберет элемент с помощью длительного нажатия, система вызовет метод {@link +android.widget.AbsListView.MultiChoiceModeListener#onCreateActionMode onCreateActionMode()} +и отобразит строку контекстных действий с указанными действиями. Пока строка +контекстных действий отображается, пользователи могут выбирать дополнительные элементы.

+ +

В некоторых случаях, когда контекстные действия содержат общие пункты, можно +добавить флажок или другой подобный элемент пользовательского интерфейса, с помощью которого пользователи могут выбирать пункты, поскольку они +могут не обнаружить варианта с длительным нажатием. Когда пользователь устанавливает флажок, можно +вызывать режим контекстных действий, переводя соответствующий элемент списка в выбранное +состояние с помощью {@link android.widget.AbsListView#setItemChecked setItemChecked()}.

+ + + + +

Создание всплывающего меню

+ +
+ +

Рисунок 4. Всплывающее меню в приложении Gmail, привязанное к расположенной вверху справа кнопке открытия панели +дополнительных пунктов.

+
+ +

{@link android.widget.PopupMenu} является модальным меню, привязанным к {@link android.view.View}. +Оно отображается ниже представления, к которому привязано, если там есть место, либо поверх него. Варианты использования:

+
    +
  • Предоставление меню с дополнительными пунктами для действий, которые относятся к определенному контенту (например, +к заголовкам сообщений Gmail, показанным на рисунке 4). +

    Примечание. Оно отличается от контекстного меню, которое +обычно используется для действий, затрагивающих выбранный контент. Для действий, которые затрагивают выбранный +контент, используйте режим контекстных действий или плавающее контекстное меню.

  • +
  • Предоставление второй части командной последовательности (например, кнопки, обозначенной как "Добавить", +которая открывает всплывающее меню с различными вариантами добавления);
  • +
  • Предоставление раскрывающегося меню наподобие {@link android.widget.Spinner}, которое не сохраняет +постоянное выделение.
  • +
+ + +

Примечание. Использование класса {@link android.widget.PopupMenu} поддерживается на уровне API + 11 и выше.

+ +

Если для определения меню используется XML, вот каким образом можно показать всплывающее меню:

+
    +
  1. Создайте экземпляр класса {@link android.widget.PopupMenu} с помощью его конструктора, принимающий +текущие {@link android.content.Context} и {@link android.view.View} приложения, к которым +должно быть привязано меню.
  2. +
  3. С помощью {@link android.view.MenuInflater} загрузите свой ресурс меню в объект {@link +android.view.Menu}, возвращенный методом {@link +android.widget.PopupMenu#getMenu() PopupMenu.getMenu()}. На API уровня 14 и выше вместо этого можно использовать +{@link android.widget.PopupMenu#inflate PopupMenu.inflate()}.
  4. +
  5. Вызовите метод {@link android.widget.PopupMenu#show() PopupMenu.show()}.
  6. +
+ +

Например, вот кнопка с атрибутом {@link android.R.attr#onClick android:onClick}, +которая показывает всплывающее меню:

+ +
+<ImageButton
+    android:layout_width="wrap_content" 
+    android:layout_height="wrap_content" 
+    android:src="@drawable/ic_overflow_holo_dark"
+    android:contentDescription="@string/descr_overflow_button"
+    android:onClick="showPopup" />
+
+ +

После этого операция сможет показать вот такое всплывающее меню:

+ +
+public void showPopup(View v) {
+    PopupMenu popup = new PopupMenu(this, v);
+    MenuInflater inflater = popup.getMenuInflater();
+    inflater.inflate(R.menu.actions, popup.getMenu());
+    popup.show();
+}
+
+ +

На API уровня 14 и выше можно объединить две строки, которые загружают меню, с помощью {@link +android.widget.PopupMenu#inflate PopupMenu.inflate()}.

+ +

Меню закрывается, когда пользователь выбирает один из пунктов или касается экрана за пределами области +меню. Прослушивать событие закрытия меню можно с помощью {@link +android.widget.PopupMenu.OnDismissListener}.

+ +

Обработка нажатий

+ +

Для выполнения +действия, когда пользователь выбирает пункт меню, необходимо реализовать интерфейс {@link +android.widget.PopupMenu.OnMenuItemClickListener} и зарегистрировать его в своем {@link +android.widget.PopupMenu}, вызвав метод {@link android.widget.PopupMenu#setOnMenuItemClickListener +setOnMenuItemclickListener()}. Когда пользователь выбирает пункт меню, система выполняет обратный вызов {@link +android.widget.PopupMenu.OnMenuItemClickListener#onMenuItemClick onMenuItemClick()} в +вашем интерфейсе.

+ +

Например:

+ +
+public void showMenu(View v) {
+    PopupMenu popup = new PopupMenu(this, v);
+
+    // This activity implements OnMenuItemClickListener
+    popup.setOnMenuItemClickListener(this);
+    popup.inflate(R.menu.actions);
+    popup.show();
+}
+
+@Override
+public boolean onMenuItemClick(MenuItem item) {
+    switch (item.getItemId()) {
+        case R.id.archive:
+            archive(item);
+            return true;
+        case R.id.delete:
+            delete(item);
+            return true;
+        default:
+            return false;
+    }
+}
+
+ + +

Создание групп меню

+ +

Группа меню ― это набор пунктов меню с рядом общих характеристик. С помощью группы +можно:

+
    +
  • показывать или скрывать все пункты с помощью {@link android.view.Menu#setGroupVisible(int,boolean) +setGroupVisible()};
  • +
  • включать или отключать все пункты с помощью {@link android.view.Menu#setGroupEnabled(int,boolean) +setGroupEnabled()};
  • +
  • Указывать, можно ли помечать все пункты, с помощью {@link +android.view.Menu#setGroupCheckable(int,boolean,boolean) setGroupCheckable()}.
  • +
+ +

Для создания группы необходимо вложить элементы {@code <item>} в элемент {@code <group>} +в своем ресурсе меню либо указать идентификатор группы с помощью метода {@link +android.view.Menu#add(int,int,int,int) add()}.

+ +

Вот пример ресурса меню, в котором имеется группа:

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:id="@+id/menu_save"
+          android:icon="@drawable/menu_save"
+          android:title="@string/menu_save" />
+    <!-- menu group -->
+    <group android:id="@+id/group_delete">
+        <item android:id="@+id/menu_archive"
+              android:title="@string/menu_archive" />
+        <item android:id="@+id/menu_delete"
+              android:title="@string/menu_delete" />
+    </group>
+</menu>
+
+ +

Элементы, находящиеся в группе, отображаются на одном уровне с первым элементом — все три пункта +меню являются элементами одного уровня. Однако можно изменить характеристики двух +пунктов из группы, указав ссылку на идентификатор группы и воспользовавшись приведенными выше методами. Кроме того, +система никогда не будет разделять сгруппированные пункты. Например, если объявить {@code +android:showAsAction="ifRoom"} для каждого пункта, то они оба будут отображены либо в строке +действий, либо в дополнительных действиях.

+ + +

Использование пунктов меню, которые можно пометить

+ +
+ +

Рисунок 5. Снимок экрана с вложенным меню, пункты которого можно +пометить.

+
+ +

Такое меню можно использовать в качестве интерфейса для включения и отключения тех или иных параметров. При этом для автономных +параметров используется флажок, а для групп +взаимоисключающих вариантов ― переключатель. На рисунке 5 показано вложенное меню с пунктами, которые можно выбирать с помощью +переключателей.

+ +

Примечание. Пункты в меню значков (из меню параметров) не могут +отображать флажки или переключатели. Если вы решите сделать так, чтобы пункты меню значков можно было помечать, +вам необходимо будет вручную указать помеченное состояние путем замены значка и/или теста +при каждом изменении состояния.

+ +

Определять возможность помечать отдельные пункты меню можно с помощью атрибута {@code +android:checkable} в элементе {@code <item>}, а для всей группы это делается с помощью +атрибута {@code android:checkableBehavior} в элементе {@code <group>}. Например +, все пункты этой группы меню можно помечать с помощью переключателей:

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+    <group android:checkableBehavior="single">
+        <item android:id="@+id/red"
+              android:title="@string/red" />
+        <item android:id="@+id/blue"
+              android:title="@string/blue" />
+    </group>
+</menu>
+
+ +

Атрибут {@code android:checkableBehavior} принимает один из трех параметров: +

+
{@code single}
+
Только один пункт из группы можно пометить (переключатель)
+
{@code all}
+
Все пункты можно пометить (флажки)
+
{@code none}
+
Пометить нельзя ни один пункт
+
+ +

Для того чтобы применить к пункту помеченное состояние по умолчанию, служит атрибут {@code android:checked} в +элементе {@code <item>}, а изменить его в коде можно с помощью метода {@link +android.view.MenuItem#setChecked(boolean) setChecked()}.

+ +

Когда выбирается пункт, который может быть помечен, система вызывает соответствующий ему метод обратного вызова +(например {@link android.app.Activity#onOptionsItemSelected(MenuItem) onOptionsItemSelected()}). Именно +здесь необходимо задать состояние флажка, поскольку флажок или переключатель не +изменяет свое состояние автоматически. Запросить текущее состояние пункта (в котором он находился до того, как был +выбран пользователем) можно с помощью{@link android.view.MenuItem#isChecked()}, а затем задать помеченное состояние с помощью +{@link android.view.MenuItem#setChecked(boolean) setChecked()}. Например:

+ +
+@Override
+public boolean onOptionsItemSelected(MenuItem item) {
+    switch (item.getItemId()) {
+        case R.id.vibrate:
+        case R.id.dont_vibrate:
+            if (item.isChecked()) item.setChecked(false);
+            else item.setChecked(true);
+            return true;
+        default:
+            return super.onOptionsItemSelected(item);
+    }
+}
+
+ +

Если помеченное состояние не установить этим способом, то, когда пользователь выберет пункт, его отображаемое состояние (флажок или +переключатель) не +изменится. Если же помеченное состояние установить, операция сохранит его +для пункта, с тем чтобы, когда пользователь откроет это меню, он увидел, +что галочка поставлена.

+ +

Примечание. +Пункты меню, которые можно пометить галочкой, предназначены для использования только в рамках одного сеанса. Они не сохраняются после +прекращения существования приложения. Если имеются настройки приложения, которые требуется сохранить для пользователя, +делать это следует с помощью общих настроек.

+ + + +

Добавление пунктов меню на основе объектов Intent

+ +

Иногда требуется, чтобы пункт меню запускал операцию с помощью объекта {@link android.content.Intent} +(это может быть операция как из вашего, так и из другого приложения). Когда вам известен объект Intent, который +требуется использовать, и у вас есть определенный пункт меню, который должен инициировать этот объект Intent, можно выполнить объект +Intent с помощью {@link android.app.Activity#startActivity(Intent) startActivity()} во время +выполнения соответствующего метода обратного вызова, запускаемого при выборе пункта меню (например, обратного вызова {@link +android.app.Activity#onOptionsItemSelected(MenuItem) onOptionsItemSelected()}).

+ +

Однако если вы не уверены, что на устройстве пользователя +есть приложение, которое может обработать этот объект Intent, добавление пункта меню, который его вызывает, может привести +к тому, что он не будет работать, поскольку объект Intent может не быть передан в +операцию. Чтобы решить эту проблему, Android позволяет динамически добавлять в меню пункты, +когда система Android обнаруживает на устройстве операции, которые могут обработать ваш объект Intent.

+ +

Добавление пунктов меню на основе имеющихся операций, которые принимают объект Intent:

+
    +
  1. Определите объект +с категорией {@link android.content.Intent#CATEGORY_ALTERNATIVE} и/или +{@link android.content.Intent#CATEGORY_SELECTED_ALTERNATIVE}, а также с любыми другими условиями.
  2. +
  3. Вызовите метод {@link +android.view.Menu#addIntentOptions(int,int,int,ComponentName,Intent[],Intent,int,MenuItem[]) +Menu.addIntentOptions()}. Затем Android выполнит поиск приложений, которые могут выполнить этот объект Intent, +и добавит их в ваше меню.
  4. +
+ +

При отсутствии установленных приложений, +которые удовлетворяют требованиям объекта Intent, ни одного пункта меню добавлено не будет.

+ +

Примечание. +{@link android.content.Intent#CATEGORY_SELECTED_ALTERNATIVE} используется для обработки элемента, выбранного +в данный момент на экране. Поэтому его следует использовать только при создании меню в {@link +android.app.Activity#onCreateContextMenu(ContextMenu,View,ContextMenuInfo) +onCreateContextMenu()}.

+ +

Например:

+ +
+@Override
+public boolean onCreateOptionsMenu(Menu menu){
+    super.onCreateOptionsMenu(menu);
+
+    // Create an Intent that describes the requirements to fulfill, to be included
+    // in our menu. The offering app must include a category value of Intent.CATEGORY_ALTERNATIVE.
+    Intent intent = new Intent(null, dataUri);
+    intent.addCategory(Intent.CATEGORY_ALTERNATIVE);
+
+    // Search and populate the menu with acceptable offering applications.
+    menu.addIntentOptions(
+         R.id.intent_group,  // Menu group to which new items will be added
+         0,      // Unique item ID (none)
+         0,      // Order for the items (none)
+         this.getComponentName(),   // The current activity name
+         null,   // Specific items to place first (none)
+         intent, // Intent created above that describes our requirements
+         0,      // Additional flags to control items (none)
+         null);  // Array of MenuItems that correlate to specific items (none)
+
+    return true;
+}
+ +

Каждая обнаруженная операция, в которой имеется фильтр Intent, соответствующий данному объекту Intent, добавляется в виде +пункта меню. Для этого значение из элемента android:label фильтра Intent используется в качестве +заголовка пункта меню, а значок приложения ― в качестве значка этого пункта меню. Метод +{@link android.view.Menu#addIntentOptions(int,int,int,ComponentName,Intent[],Intent,int,MenuItem[]) +addIntentOptions()} возвращает количество добавленных пунктов меню.

+ +

Примечание. При вызове метода {@link +android.view.Menu#addIntentOptions(int,int,int,ComponentName,Intent[],Intent,int,MenuItem[]) +addIntentOptions()} он переопределяет все пункты меню по группе меню, указанной в первом +аргументе.

+ + +

Предоставление возможности добавить свою операцию в другие меню

+ +

Вы также можете предоставить другим приложениям возможность воспользоваться своей операцией, с тем чтобы ваше +приложение могло быть включено в меню других приложений (процедура, обратная описанной выше).

+ +

Чтобы операция могла быть включена в меню других приложений, необходимо определить фильтр +Intent обычным образом, но непременно включить в него значения {@link android.content.Intent#CATEGORY_ALTERNATIVE} +и/или {@link android.content.Intent#CATEGORY_SELECTED_ALTERNATIVE} для категории фильтра +Intent. Например:

+
+<intent-filter label="@string/resize_image">
+    ...
+    <category android:name="android.intent.category.ALTERNATIVE" />
+    <category android:name="android.intent.category.SELECTED_ALTERNATIVE" />
+    ...
+</intent-filter>
+
+ +

Подробные сведения о написании фильтров Intent см. в документе +Объекты Intent и фильтры объектов Intent.

+ +

Образец приложения, в котором используется эта методика, см. в образце кода +Note +Pad.

diff --git a/docs/html-intl/intl/ru/guide/topics/ui/notifiers/notifications.jd b/docs/html-intl/intl/ru/guide/topics/ui/notifiers/notifications.jd new file mode 100644 index 0000000000000000000000000000000000000000..d072b77eae24106212d9eb8aa0baabbc700f9d6d --- /dev/null +++ b/docs/html-intl/intl/ru/guide/topics/ui/notifiers/notifications.jd @@ -0,0 +1,979 @@ +page.title=Уведомления +@jd:body + +
+
+

Содержание документа

+
    +
  1. Рекомендации по разработке
  2. +
  3. Создание уведомления +
      +
    1. Обязательное содержимое уведомления
    2. +
    3. Необязательные содержимое и настройки уведомления
    4. +
    5. Действия уведомлений
    6. +
    7. Приоритет уведомлений
    8. +
    9. Создание простого уведомления
    10. +
    11. Применение расширенного макета к уведомлению
    12. +
    13. Вопросы совместимости
    14. +
    +
  4. +
  5. Управление уведомлениями +
      +
    1. Обновление уведомлений
    2. +
    3. Удаление уведомлений
    4. +
    +
  6. +
  7. Сохранение навигации при запуске операции +
      +
    1. Настройка PendingIntent обычной операции
    2. +
    3. Настройка PendingIntent особой операции
    4. +
    +
  8. +
  9. Отображение хода выполнения в уведомлении +
      +
    1. Отображение индикатора хода выполнения фиксированной продолжительности
    2. +
    3. Отображение непрерывного индикатора операции
    4. +
    +
  10. +
  11. Метаданные уведомления
  12. +
  13. Уведомления heads-up
  14. +
  15. Уведомления на экране блокировки
  16. +
      +
    1. Настройка видимости
    2. +
    3. Управление воспроизведением мультимедиа на экране блокировки
    4. +
    +
  17. Нестандартные макеты уведомлений
  18. +
+ +

Основные классы

+
    +
  1. {@link android.app.NotificationManager}
  2. +
  3. {@link android.support.v4.app.NotificationCompat}
  4. +
+

Видеоролики

+
    +
  1. + + Уведомления в 4.1 +
  2. +
+

См. также

+
    +
  1. + Дизайн Android: уведомления +
  2. +
+
+
+

+ Уведомление ― это сообщение, которое может быть выведено на экран за пределами обычного пользовательского + интерфейса приложения. Когда вы сообщаете системе о необходимости выдать уведомление, оно сначала отображается в виде значка в + области уведомлений. Чтобы просмотреть подробные сведения об уведомлении, пользователь открывает + панель уведомлений. И областью уведомлений, и панелью уведомлений + управляет система, а пользователь может их просматривать в любое время. +

+ +

+ Рисунок 1. Уведомления в области уведомлений. +

+ +

+ Рисунок 2. Уведомления в панели уведомлений. +

+ +

Примечание. Если не указано иное, в этом руководстве речь идет о классе +{@link android.support.v4.app.NotificationCompat.Builder NotificationCompat.Builder} +в версии 4 вспомогательной библиотеки. +Класс{@link android.app.Notification.Builder Notification.Builder} был добавлен в Android +3.0 (уровень API 11).

+ +

Рекомендации по разработке

+ +

Поскольку уведомления являются как важной составной частью пользовательского интерфейса Android, для них имеются собственные инструкции по проектированию. +Появившиеся в Android 5.0 (уровень API 21) значительные изменения дизайна имеют особо важное +значение, поэтому для получения более подробной информации вам следует ознакомиться с учебником по интерфейсу Material Design +. Чтобы узнать, как проектировать уведомления и взаимодействие с ними, прочитайте руководство по проектированию +Уведомления.

+ +

Создание уведомления

+ +

Информация о пользовательском интерфейсе и действия для уведомления указываются в объекте +{@link android.support.v4.app.NotificationCompat.Builder NotificationCompat.Builder}. +Чтобы создать само уведомление, вызывается метод +{@link android.support.v4.app.NotificationCompat.Builder#build NotificationCompat.Builder.build()}, +который возвращает объект {@link android.app.Notification}, содержащий заданные вами спецификации. Чтобы выдать +уведомление, объект {@link android.app.Notification} передается в систему путем вызова метода +{@link android.app.NotificationManager#notify NotificationManager.notify()}.

+ +

Обязательное содержимое уведомления

+

+ Объект {@link android.app.Notification} должен содержать следующие элементы: +

+
    +
  • + небольшой значок, заданный с помощью + {@link android.support.v4.app.NotificationCompat.Builder#setSmallIcon setSmallIcon()}; +
  • +
  • + заголовок, заданный с помощью + {@link android.support.v4.app.NotificationCompat.Builder#setContentTitle setContentTitle()}; +
  • +
  • + подробный текст, заданный с помощью + {@link android.support.v4.app.NotificationCompat.Builder#setContentText setContentText()}. +
  • +
+

Необязательные содержимое и настройки уведомления

+

+ Все прочие настройки и содержимое уведомления являются необязательными. Подробные сведения о них + см. в справочной документации по {@link android.support.v4.app.NotificationCompat.Builder}. +

+ +

Действия уведомлений

+

+ Несмотря на то что действия не являются обязательными, в уведомление необходимо добавить хотя бы одно из них. + Действие позволяет пользователю перейти из уведомления прямо к + операции {@link android.app.Activity} из вашего приложения, где они могут просмотреть одно или несколько событий + либо выполнить какую-либо другую работу. +

+

+ Уведомление может предоставлять возможность выполгить несколько действий. Следует всегда определять действие, которое + вызывается, когда пользователь нажимает уведомление; обычно это действие открывает + операцию {@link android.app.Activity} из вашего приложения. В уведомление также можно добавлять кнопки, + которые выполняют дополнительные действия, например отключение сигнала будильника или немедленный ответ на текстовое + сообщение; эта функция поддерживается начиная с версии Android 4.1. Если используются дополнительные кнопки действий, то также + необходимо сделать их функции доступными в операции {@link android.app.Activity} из вашего приложения (подробные + сведения см. в разделе Вопросы совместимости). +

+

+ Внутри класса {@link android.app.Notification} само действие определяется + объектом {@link android.app.PendingIntent}, содержащим объект + {@link android.content.Intent}, который запускает + операцию {@link android.app.Activity} из вашего приложения. Чтобы связать объект + {@link android.app.PendingIntent} с жестом, вызовите соответствующий метод + {@link android.support.v4.app.NotificationCompat.Builder}. Например, если вам требуется запустить + операцию {@link android.app.Activity}, когда пользователь нажимает текст уведомления в + панели уведомлений, вы добавляете объект {@link android.app.PendingIntent} путем вызова метода + {@link android.support.v4.app.NotificationCompat.Builder#setContentIntent setContentIntent()}. +

+

+ Запуск операции {@link android.app.Activity}, когда пользователь нажимает уведомление, является + наиболее распространенным вариантом действия. Операцию {@link android.app.Activity} также можно запускать, когда пользователь + закрывает уведомление. В версии Android 4.1 или более поздних версиях запускать + операцию {@link android.app.Activity} можно с помощью кнопки действия. Подробные сведения см. в справочном руководстве по классу + {@link android.support.v4.app.NotificationCompat.Builder}. +

+ +

Приоритет уведомлений

+

+ При желании уведомлению можно задать приоритет. Приоритет действует + как подсказка пользовательскому интерфейсу устройства о том, каким образом следует выводить уведомление. + Чтобы задать приоритет уведомления, вызовите метод {@link + android.support.v4.app.NotificationCompat.Builder#setPriority(int) + NotificationCompat.Builder.setPriority()} и передайте ему одну из констант приоритетов {@link + android.support.v4.app.NotificationCompat}. Имеется + пять уровней приоритета, начиная от {@link + android.support.v4.app.NotificationCompat#PRIORITY_MIN} (-2) и до {@link + android.support.v4.app.NotificationCompat#PRIORITY_MAX} (2). Если приоритет не задан, + то по умолчанию он будет иметь значение {@link + android.support.v4.app.NotificationCompat#PRIORITY_DEFAULT} (0). +

+

Сведения о присвоении подходящего уровня приоритета см. в разделе "Правильная настройка + приоритета уведомления и управление им" в руководстве "Разработка уведомлений" +. +

+ +

Создание простого уведомления

+

+ Следующий фрагмент кода иллюстрирует простое уведомление, указывающее операцию, которую нужно будет открыть, когда + пользователь нажмет уведомление. Обратите внимание, что этот код создает объект + {@link android.support.v4.app.TaskStackBuilder} и использует его для создания объекта + {@link android.app.PendingIntent} для действия. Более подробно этот шаблон описан + в разделе + "Сохранение навигации при запуске операции": +

+
+NotificationCompat.Builder mBuilder =
+        new NotificationCompat.Builder(this)
+        .setSmallIcon(R.drawable.notification_icon)
+        .setContentTitle("My notification")
+        .setContentText("Hello World!");
+// Creates an explicit intent for an Activity in your app
+Intent resultIntent = new Intent(this, ResultActivity.class);
+
+// The stack builder object will contain an artificial back stack for the
+// started Activity.
+// This ensures that navigating backward from the Activity leads out of
+// your application to the Home screen.
+TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);
+// Adds the back stack for the Intent (but not the Intent itself)
+stackBuilder.addParentStack(ResultActivity.class);
+// Adds the Intent that starts the Activity to the top of the stack
+stackBuilder.addNextIntent(resultIntent);
+PendingIntent resultPendingIntent =
+        stackBuilder.getPendingIntent(
+            0,
+            PendingIntent.FLAG_UPDATE_CURRENT
+        );
+mBuilder.setContentIntent(resultPendingIntent);
+NotificationManager mNotificationManager =
+    (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+// mId allows you to update the notification later on.
+mNotificationManager.notify(mId, mBuilder.build());
+
+

Готово. Теперь пользователь получит уведомление.

+ +

Применение расширенного макета к уведомлению

+

+ Чтобы уведомление отображалось в расширенном виде, сначала создайте объект + {@link android.support.v4.app.NotificationCompat.Builder} с требуемыми параметрами + обычного представления. Затем вызовите метод {@link android.support.v4.app.NotificationCompat.Builder#setStyle + Builder.setStyle()}, первым аргументом которого должен быть объект расширенного макета. +

+

+ Помните, что расширенные уведомления не поддерживаются на платформах версии более ранней, чем Android 4.1. Сведения + о том, как обрабатывать уведомления для версий платформы Android 4.1 и более ранних, см. в + разделе Вопросы совместимости. +

+

+ Например, следующий фрагмент кода демонстрирует, каким образом следует изменить уведомление, созданное + в предыдущем фрагменте, чтобы использовать расширенный макет: +

+
+NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(this)
+    .setSmallIcon(R.drawable.notification_icon)
+    .setContentTitle("Event tracker")
+    .setContentText("Events received")
+NotificationCompat.InboxStyle inboxStyle =
+        new NotificationCompat.InboxStyle();
+String[] events = new String[6];
+// Sets a title for the Inbox in expanded layout
+inboxStyle.setBigContentTitle("Event tracker details:");
+...
+// Moves events into the expanded layout
+for (int i=0; i < events.length; i++) {
+
+    inboxStyle.addLine(events[i]);
+}
+// Moves the expanded layout object into the notification object.
+mBuilder.setStyle(inBoxStyle);
+...
+// Issue the notification here.
+
+ +

Вопросы совместимости

+ +

+ Не все функции уведомлений поддерживаются в той или иной версии платформы, даже несмотря на то, что + методы для их задания имеются в классе вспомогательной библиотеки + {@link android.support.v4.app.NotificationCompat.Builder NotificationCompat.Builder}. + Например, кнопки действий, которые зависят от расширенных уведомлений, отображаются только в версии Android + 4.1 и последующих версиях, поскольку сами расширенные уведомления поддерживаются только начиная с версии + Android 4.1. +

+

+ Для обеспечения наилучшей совместимости создавать уведомления следует с помощью класса + {@link android.support.v4.app.NotificationCompat NotificationCompat} и его подклассов, + в частности {@link android.support.v4.app.NotificationCompat.Builder + NotificationCompat.Builder}. Кроме того, при реализации уведомления придерживайтесь вот такого процесса: +

+
    +
  1. + Предоставляйте все функции уведомления всем пользователям независимо от используемой ими + версии. Для этого убедитесь, что все функции вызываются из + операции {@link android.app.Activity} в вашем приложении. Возможно, для этого вам потребуется добавить новый объект + {@link android.app.Activity}. +

    + Например, если требуется использовать + {@link android.support.v4.app.NotificationCompat.Builder#addAction addAction()} для + создания элемента управления, который останавливает и запускает воспроизведение мультимедиа, сначала реализуйте этот + элемент управления в операции {@link android.app.Activity} из вашего приложения. +

    +
  2. +
  3. + Обеспечьте доступ к этой функции операции {@link android.app.Activity} всех пользователей, + сделав так, чтобы эта операция запускалась, когда пользователь нажимает уведомление. Для этого + создайте объект {@link android.app.PendingIntent} + для операции {@link android.app.Activity}. Вызовите + {@link android.support.v4.app.NotificationCompat.Builder#setContentIntent + setContentIntent()}, чтобы добавить объект {@link android.app.PendingIntent} в уведомление. +
  4. +
  5. + Затем добавьте в свое уведомление требуемые функции расширенного уведомления. Помните, + что любая добавляемая функция должна быть предусмотрена в операции {@link android.app.Activity}, + которая запускается, когда пользователь нажимает уведомление. +
  6. +
+ + + + +

Управление уведомлениями

+

+ Когда уведомление требуется выдать несколько раз для однотипных событий, не следует + создавать совершенно новое уведомление. Вместо этого рассмотрите возможность обновить + предыдущее уведомление либо путем изменения некоторых его значений, либо путем добавления в него значений, либо обоими этими способами. +

+

+ Например, Gmail уведомляет пользователя о поступлении новых сообщений электронной почты путем увеличения счетчика + непрочитанных сообщений и добавления в уведомления кратких сведений о каждом из них. Это называется + "укладкой уведомлений в стопку" (stacking) и подробно описано в руководстве + "Разработка уведомлений". +

+

+ Примечание. Для этой функции Gmail требуется расширенный макет папки входящих сообщений, который + входит в состав функции расширенных уведомлений, поддержка которой предусмотрена начиная с версии Android 4.1. +

+

+ В разделах ниже описано, как обновлять уведомления, а также как удалять их. +

+

Обновление уведомлений

+

+ Чтобы настроить уведомление таким образом, что его впоследствии можно было обновлять, его следует выдавать с идентификатором уведомления путем + вызова метода {@link android.app.NotificationManager#notify(int, android.app.Notification) NotificationManager.notify()}. + Чтобы изменить это уведомление, после того как оно выдано, + обновите или создайте объект {@link android.support.v4.app.NotificationCompat.Builder}, + постройте на его основе объект {@link android.app.Notification} и выдайте + объект {@link android.app.Notification} с тем же идентификатором, который использовался ранее. Если + предыдущее уведомление все еще отображается на экране, система обновит его с использованием содержимого + объекта{@link android.app.Notification}. Если предыдущее уведомление было закрыто, то + вместо него будет создано новое уведомление. +

+

+ Следующий фрагмент кода демонстрирует уведомление, которое обновляется с учетом + количества произошедших событий. Он накладывает уведомления друг на друга (укладывает в стопку), отображая сводную информацию: +

+
+mNotificationManager =
+        (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+// Sets an ID for the notification, so it can be updated
+int notifyID = 1;
+mNotifyBuilder = new NotificationCompat.Builder(this)
+    .setContentTitle("New Message")
+    .setContentText("You've received new messages.")
+    .setSmallIcon(R.drawable.ic_notify_status)
+numMessages = 0;
+// Start of a loop that processes data and then notifies the user
+...
+    mNotifyBuilder.setContentText(currentText)
+        .setNumber(++numMessages);
+    // Because the ID remains unchanged, the existing notification is
+    // updated.
+    mNotificationManager.notify(
+            notifyID,
+            mNotifyBuilder.build());
+...
+
+ + +

Удаление уведомлений

+

+ Уведомления отображаются на экране, пока не произойдет одно из следующих событий: +

+
    +
  • + пользователь закроет уведомления по одному или командой "Очистить все" (если + уведомление можно очистить); +
  • +
  • + пользователь нажмет уведомление, а вы вызвали метод + {@link android.support.v4.app.NotificationCompat.Builder#setAutoCancel setAutoCancel()}, когда + создавали уведомление; +
  • +
  • + вы вызовете метод {@link android.app.NotificationManager#cancel(int) cancel()} для уведомления с определенным + идентификатором. Кроме того, этот метод удаляет текущие уведомления; +
  • +
  • + вы вызовете метод {@link android.app.NotificationManager#cancelAll() cancelAll()}, который удаляет + все ранее выданные вами уведомления. +
  • +
+ + +

Сохранение навигации при запуске операции

+

+ Когда вы запускаете операцию {@link android.app.Activity} из уведомления, вам необходимо сохранить + привычные пользователю приемы навигации. При нажатии "Назад" пользователь должен возвращаться назад в + обычном потоке операций приложения вплоть до главного экрана, а при нажатии "Последние" операция + {@link android.app.Activity} должна быть отображена как отдельная задача. Чтобы сохранить приемы навигации, операцию + {@link android.app.Activity} следует запускать в новой задаче. Как настроить объект + {@link android.app.PendingIntent} таким образом, чтобы получить новую задачу, зависит от типа операции + {@link android.app.Activity}, которую вы запускаете. В целом есть две ситуации: +

+
+
+ Обычная операция +
+
+ Вы запускаете операцию {@link android.app.Activity}, которая является частью обычного потока + работы приложения. В этом случае настройте объект {@link android.app.PendingIntent} на + запуск новой задачи и предоставьте объекту {@link android.app.PendingIntent} стек переходов назад, + который воспроизводит обычную работу приложения в ситуации, когда пользователь нажимает "Назад" . +

+ Это можно увидеть на примере уведомлений приложения Gmail. При нажатии уведомления для + одного сообщения электронной почты отображается само сообщение. При нажатии Назад вы переходите + назад по представлениям Gmail вплоть до главного экрана точно так же, как если бы вы вошли в Gmail с + главного экрана, а не из уведомления. +

+

+ Происходит это независимо от того, в каком приложении вы находились в тот момент, когда нажали + уведомление. Например, если при составлении сообщения в Gmail вы нажмете + уведомление об одном сообщении электронной почты, вы сразу же перейдете в это сообщение. При нажатии "Назад" + вы перейдете в папку входящих сообщений, а затем на главный экран, а не в + сообщение, которое составляли. +

+
+
+ Особая операция +
+
+ Пользователь может увидеть эту операцию {@link android.app.Activity}, только если она запущена из уведомления. + В некотором смысле операция {@link android.app.Activity} расширяет уведомление путем предоставления + информации, которую было бы сложно отобразить в самом уведомлении. В этом случае + настройте объект {@link android.app.PendingIntent} на запуск в новой задаче. При этом создавать + стек переходов назад не требуется, поскольку запущенная операция {@link android.app.Activity} не является частью + потока операций приложения. При нажатии "Назад" пользователь все же перейдет на + главный экран. +
+
+ +

Настройка PendingIntent обычной операции

+

+ Чтобы настроить объект {@link android.app.PendingIntent}, который непосредственно запускает операцию + {@link android.app.Activity}, выполните следующие шаги: +

+
    +
  1. + Определите иерархию операций {@link android.app.Activity} своего приложения в файле манифеста. +
      +
    1. + Добавьте поддержку для версии Android 4.0.3 и более ранних версий. Для этого укажите родительский объект операции + {@link android.app.Activity}, которую запускаете, добавив элемент +<meta-data> + в качестве дочернего для элемента +<activity>. +

      + Для этого элемента задайте +android:name="android.support.PARENT_ACTIVITY". + Задайте +android:value="<parent_activity_name>", + где <parent_activity_name> ― это значение +android:name + для родительского элемента +<activity> +. В качестве примера см. следующий код XML. +

      +
    2. +
    3. + Также добавьте поддержку для версии Android 4.1 и более поздних версий. Для этого добавьте атрибут +android:parentActivityName + в элемент +<activity> + запускаемой операции{@link android.app.Activity}. +
    4. +
    +

    + Итоговый код XML должен выглядеть следующим образом: +

    +
    +<activity
    +    android:name=".MainActivity"
    +    android:label="@string/app_name" >
    +    <intent-filter>
    +        <action android:name="android.intent.action.MAIN" />
    +        <category android:name="android.intent.category.LAUNCHER" />
    +    </intent-filter>
    +</activity>
    +<activity
    +    android:name=".ResultActivity"
    +    android:parentActivityName=".MainActivity">
    +    <meta-data
    +        android:name="android.support.PARENT_ACTIVITY"
    +        android:value=".MainActivity"/>
    +</activity>
    +
    +
  2. +
  3. + Создайте стек переходов назад, основанный на объекте {@link android.content.Intent}, который запускает операцию + {@link android.app.Activity}: +
      +
    1. + Создайте объект {@link android.content.Intent}, который запускает операцию {@link android.app.Activity}. +
    2. +
    3. + Создайте построитель стека, вызвав метод {@link android.app.TaskStackBuilder#create + TaskStackBuilder.create()}. +
    4. +
    5. + Добавьте стек переходов назад в построитель стеков путем вызова метода + {@link android.support.v4.app.TaskStackBuilder#addParentStack addParentStack()}. + Для каждой операции {@link android.app.Activity} из иерархии, определенной в + файле манифеста, в стеке переходов назад имеется объект {@link android.content.Intent}, который + запускает {@link android.app.Activity}. Этот метод также добавляет флаги, которые запускают + стек в новой задаче. +

      + Примечание. Несмотря на то что аргумент + {@link android.support.v4.app.TaskStackBuilder#addParentStack addParentStack()} + является ссылкой на запускаемую операцию {@link android.app.Activity}, при вызове этого метода + не добавляется объект {@link android.content.Intent}, который запускает операцию + {@link android.app.Activity}. Это делается на следующем шаге. +

      +
    6. +
    7. + Добавьте объект {@link android.content.Intent}, который запускает операцию {@link android.app.Activity} + из уведомления, путем вызова метода + {@link android.support.v4.app.TaskStackBuilder#addNextIntent addNextIntent()}. + Передайте объект {@link android.content.Intent}, созданный вами на первом шаге, в качестве + аргумента методу + {@link android.support.v4.app.TaskStackBuilder#addNextIntent addNextIntent()}. +
    8. +
    9. + При необходимости добавьте аргументы в объекты {@link android.content.Intent} из + стека путем вызова метода {@link android.support.v4.app.TaskStackBuilder#editIntentAt + TaskStackBuilder.editIntentAt()}. Иногда это требуется для того, чтобы + целевая операция {@link android.app.Activity} отображала значимые данные, когда пользователь переходит + в нее с помощью действия "Назад". +
    10. +
    11. + Получите объект {@link android.app.PendingIntent} для этого стека переходов назад путем вызова метода + {@link android.support.v4.app.TaskStackBuilder#getPendingIntent getPendingIntent()}. + Затем этот объект {@link android.app.PendingIntent} можно будет использовать в качестве аргумента для метода + {@link android.support.v4.app.NotificationCompat.Builder#setContentIntent + setContentIntent()}. +
    12. +
    +
  4. +
+

+ Следующий фрагмент кода демонстрирует этот процесс: +

+
+...
+Intent resultIntent = new Intent(this, ResultActivity.class);
+TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);
+// Adds the back stack
+stackBuilder.addParentStack(ResultActivity.class);
+// Adds the Intent to the top of the stack
+stackBuilder.addNextIntent(resultIntent);
+// Gets a PendingIntent containing the entire back stack
+PendingIntent resultPendingIntent =
+        stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
+...
+NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
+builder.setContentIntent(resultPendingIntent);
+NotificationManager mNotificationManager =
+    (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+mNotificationManager.notify(id, builder.build());
+
+ +

Настройка PendingIntent особой операции

+

+ В приведенном далее разделе описывается настройка объекта + {@link android.app.PendingIntent} для особой операции. +

+

+ Особой операции {@link android.app.Activity} не требуется стек перехода назад, поэтому не нужно + определять иерархию объектов {@link android.app.Activity} в файле манифеста и + вызывать + метод {@link android.support.v4.app.TaskStackBuilder#addParentStack addParentStack()} для построения + стека перехода назад. Вместо этого в файле манифеста задайте параметры задачи {@link android.app.Activity} + и создайте объект {@link android.app.PendingIntent} путем вызова метода + {@link android.app.PendingIntent#getActivity getActivity()}: +

+
    +
  1. + Добавьте следующие атрибуты в элемент +<activity> + в файле манифеста для операции {@link android.app.Activity} +
    +
    +android:name="activityclass" +
    +
    + Полное имя класса операции. +
    +
    +android:taskAffinity="" +
    +
    + В сочетании с + флагом {@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK FLAG_ACTIVITY_NEW_TASK}, + который вы задали в коде, это гарантирует, что данная операция {@link android.app.Activity} не + перейдет в задачу приложения, используемую по умолчанию. Любые существующие задачи, имеющие + отношение к используемой по умолчанию задаче приложения, затронуты не будут. +
    +
    +android:excludeFromRecents="true" +
    +
    + Исключает новую задачу из списка "Последние",с тем чтобы пользователь не мог случайно + вернуться в нее. +
    +
    +

    + Данный элемент показан в этом фрагменте кода: +

    +
    +<activity
    +    android:name=".ResultActivity"
    +...
    +    android:launchMode="singleTask"
    +    android:taskAffinity=""
    +    android:excludeFromRecents="true">
    +</activity>
    +...
    +
    +
  2. +
  3. + Построение и выдача уведомления: +
      +
    1. + Создайте объект {@link android.content.Intent}, который запускает операцию + {@link android.app.Activity}. +
    2. +
    3. + Настройте операцию {@link android.app.Activity}, запускаемую в новой пустой задаче, путем вызова метода + {@link android.content.Intent#setFlags setFlags()} с флагами + {@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK FLAG_ACTIVITY_NEW_TASK} + и + {@link android.content.Intent#FLAG_ACTIVITY_CLEAR_TASK FLAG_ACTIVITY_CLEAR_TASK}. +
    4. +
    5. + Задайте для объекта {@link android.content.Intent} любые другие требуемые параметры. +
    6. +
    7. + Создайте объект {@link android.app.PendingIntent} из объекта {@link android.content.Intent} + путем вызова метода {@link android.app.PendingIntent#getActivity getActivity()}. + Затем этот объект {@link android.app.PendingIntent} можно будет использовать в качестве аргумента для метода + {@link android.support.v4.app.NotificationCompat.Builder#setContentIntent + setContentIntent()}. +
    8. +
    +

    + Следующий фрагмент кода демонстрирует этот процесс: +

    +
    +// Instantiate a Builder object.
    +NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
    +// Creates an Intent for the Activity
    +Intent notifyIntent =
    +        new Intent(this, ResultActivity.class);
    +// Sets the Activity to start in a new, empty task
    +notifyIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
    +                        | Intent.FLAG_ACTIVITY_CLEAR_TASK);
    +// Creates the PendingIntent
    +PendingIntent notifyPendingIntent =
    +        PendingIntent.getActivity(
    +        this,
    +        0,
    +        notifyIntent,
    +        PendingIntent.FLAG_UPDATE_CURRENT
    +);
    +
    +// Puts the PendingIntent into the notification builder
    +builder.setContentIntent(notifyPendingIntent);
    +// Notifications are issued by sending them to the
    +// NotificationManager system service.
    +NotificationManager mNotificationManager =
    +    (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
    +// Builds an anonymous Notification object from the builder, and
    +// passes it to the NotificationManager
    +mNotificationManager.notify(id, builder.build());
    +
    +
  4. +
+ + +

Отображение хода выполнения в уведомлении

+

+ Уведомления могут содержать индикатор хода выполнения с эффектом анимации, который показывает пользователям состояние + текущей операции. Если имеется возможность оценить, сколько времени занимает операция и какой процент ее объема + уже завершен, используйте "определенную" форму индикатора + (индикатор хода выполнения). Если продолжительность операции оценить невозможно, используйте + "неопределенную" форму индикатора (индикатор операции). +

+

+ Индикаторы хода выполнения отображаются с помощью реализации класса + {@link android.widget.ProgressBar} платформы. +

+

+ Для использования индикатора хода выполнения на платформах начиная с Android 4.0 вызовите метод + {@link android.support.v4.app.NotificationCompat.Builder#setProgress setProgress()}. Для + предыдущих версий необходимо будет создать собственный нестандартный макет уведомлений, который + содержит представление {@link android.widget.ProgressBar}. +

+

+ В приведенных далее разделах описывается отображение хода выполнения в уведомлении с помощью + {@link android.support.v4.app.NotificationCompat.Builder#setProgress setProgress()}. +

+ +

Отображение индикатора хода выполнения фиксированной продолжительности

+

+ Чтобы вывести на экран определенный индикатор хода выполнения, добавьте его в свое уведомление, вызвав метод + {@link android.support.v4.app.NotificationCompat.Builder#setProgress + setProgress(max, progress, false)}, а затем выдайте уведомление. По мере выполнения + увеличивайте значение progress и обновляйте уведомление. По окончании операции + progress должен быть равен max. Стандартный способ вызова метода + {@link android.support.v4.app.NotificationCompat.Builder#setProgress setProgress()} + заключается в следующем: задать значение max равным 100 с последующим увеличением progress в виде + величины "процента выполнения" операции. +

+

+ По окончании операции можно оставить этот индикатор хода выполнения на экране или удалить его. В + любом случае не забывайте обновить текст уведомления, чтобы указать, что операция выполнена. + Чтобы удалить индикатор выполнения, вызовите метод + {@link android.support.v4.app.NotificationCompat.Builder#setProgress + setProgress(0, 0, false)}. Например: +

+
+...
+mNotifyManager =
+        (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+mBuilder = new NotificationCompat.Builder(this);
+mBuilder.setContentTitle("Picture Download")
+    .setContentText("Download in progress")
+    .setSmallIcon(R.drawable.ic_notification);
+// Start a lengthy operation in a background thread
+new Thread(
+    new Runnable() {
+        @Override
+        public void run() {
+            int incr;
+            // Do the "lengthy" operation 20 times
+            for (incr = 0; incr <= 100; incr+=5) {
+                    // Sets the progress indicator to a max value, the
+                    // current completion percentage, and "determinate"
+                    // state
+                    mBuilder.setProgress(100, incr, false);
+                    // Displays the progress bar for the first time.
+                    mNotifyManager.notify(0, mBuilder.build());
+                        // Sleeps the thread, simulating an operation
+                        // that takes time
+                        try {
+                            // Sleep for 5 seconds
+                            Thread.sleep(5*1000);
+                        } catch (InterruptedException e) {
+                            Log.d(TAG, "sleep failure");
+                        }
+            }
+            // When the loop is finished, updates the notification
+            mBuilder.setContentText("Download complete")
+            // Removes the progress bar
+                    .setProgress(0,0,false);
+            mNotifyManager.notify(ID, mBuilder.build());
+        }
+    }
+// Starts the thread by calling the run() method in its Runnable
+).start();
+
+ + +

Отображение непрерывного индикатора операции

+

+ Для отображения неопределенного индикатора операции добавьте его в свое уведомление с помощью метода + {@link android.support.v4.app.NotificationCompat.Builder#setProgress setProgress(0, 0, true)} + (два первых аргумента игнорируются) и выдайте уведомление. В результате будет показан индикатор, + который будет иметь такой же стиль, что и индикатор хода выполнения, за исключением того, что его анимация будет непрерывной. +

+

+ Выдайте уведомление в начале операции. Анимация будет воспроизводиться до тех пор, пока вы не + измените уведомление. Когда операция будет выполнена, вызовите метод + {@link android.support.v4.app.NotificationCompat.Builder#setProgress setProgress(0, 0, false)}, + после чего обновите уведомление, удалив индикатор операции. + Всегда поступайте таким образом. В противном случае анимация будет воспроизводиться и после завершения операции. Также + не забывайте изменить текст уведомления, чтобы указать, что операция выполнена. +

+

+ Чтобы посмотреть, как работают индикаторы операций, перейдите к предыдущему фрагменту кода. Найдите следующие строки: +

+
+// Sets the progress indicator to a max value, the current completion
+// percentage, and "determinate" state
+mBuilder.setProgress(100, incr, false);
+// Issues the notification
+mNotifyManager.notify(0, mBuilder.build());
+
+

+ Замените их вот этими строками: +

+
+ // Sets an activity indicator for an operation of indeterminate length
+mBuilder.setProgress(0, 0, true);
+// Issues the notification
+mNotifyManager.notify(0, mBuilder.build());
+
+ +

Метаданные уведомления

+ +

Уведомления можно сортировать в соответствии с метаданными, которые назначаются с помощью +следующих методов {@link android.support.v4.app.NotificationCompat.Builder}:

+ +
    +
  • метод {@link android.support.v4.app.NotificationCompat.Builder#setCategory(java.lang.String) setCategory()} + информирует систему о том, как обрабатывать уведомления вашего приложения, когда устройство находится в режиме приоритета + (например, если ваше уведомление сообщает о входящем вызове, мгновенном сообщении или сигнале будильника);
  • +
  • метод{@link android.support.v4.app.NotificationCompat.Builder#setPriority(int) setPriority()} вызывает + отображение уведомлений, в поле приоритета которых задано значение {@code PRIORITY_MAX} или {@code PRIORITY_HIGH} +, в небольшом плавающем окне, если уведомление также сопровождается звуковым сигналом или вибрацией;
  • +
  • метод {@link android.support.v4.app.NotificationCompat.Builder#addPerson(java.lang.String) addPerson()} + позволяет добавить к уведомлению список людей. С его помощью ваше уведомление может сигнализировать + системе о том, что она должна сгруппировать уведомления от указанных людей или считать уведомления + от этих людей более важными.
  • +
+ +
+ +

+ Рисунок 3. Полноэкранная операция, отображающая уведомление heads-up +

+
+ +

Уведомления heads-up

+ +

В Android 5.0 (уровень API 21) уведомления могут отображаться в небольшом плавающем окне +(оно также называется уведомлением heads-up), когда устройство активно +(то есть устройство разблокировано и его экран включен). Эти уведомления +выглядят так же, как компактная форма вашего уведомления, за исключением того, что в +уведомлениях heads-up также отображаются кнопки действий. Пользователи могут выполнять действия или закрывать +уведомление heads-up, не покидая текущее приложение.

+ +

Примеры ситуаций, в которых могут быть вызваны уведомления heads-up:

+ +
    +
  • операция пользователя выполняется в полноэкранном режиме (приложение использует +{@link android.app.Notification#fullScreenIntent}) или;
  • +
  • уведомление имеет высокий приоритет и использует рингтоны или + вибрацию.
  • +
+ +

Уведомления на экране блокировки

+ +

С выходом версии Android 5.0 (уровень API 21) уведомления теперь могут отображаться на экране +блокировки. С помощью этой функции можно выводит на экран элементы управления мультимедиа и другие стандартные +действия. В настройках пользователи могут выбрать, следует ли отображать уведомления на экране блокировки, а +вы можете указать, будет ли уведомление из вашего приложения видно на нем.

+ +

Настройка видимости

+ +

Ваше приложение может определять объем информации, отображаемой в уведомлениях, которые выводятся на экране +блокировки. Метод {@link android.support.v4.app.NotificationCompat.Builder#setVisibility(int) setVisibility()} +вызывается для того, чтобы указать одно из следующих значений:

+ +
    +
  • {@link android.support.v4.app.NotificationCompat#VISIBILITY_PUBLIC} показывает полное содержимое + уведомления;
  • +
  • {@link android.support.v4.app.NotificationCompat#VISIBILITY_SECRET} не отображает какую-либо часть + этого уведомления на экране блокировки;
  • +
  • {@link android.support.v4.app.NotificationCompat#VISIBILITY_PRIVATE} показывает базовую информацию, + такую как значок уведомления и заголовок его содержимого, но скрывает полное содержимое уведомления.
  • +
+ +

Когда задается значение {@link android.support.v4.app.NotificationCompat#VISIBILITY_PRIVATE}, вы также можете +предоставить альтернативную версию содержимого уведомления, в который скрыты некоторые сведения. Например, +приложение по работе с СМС может выводить уведомление, в котором говорится У вас 3 новых текстовых сообщения, а содержимое + и отправители сообщений скрыты. Чтобы предоставить возможность такого альтернативного уведомления, сначала создайте его +с помощью {@link android.support.v4.app.NotificationCompat.Builder}. При создании +частного объекта уведомления прикрепите к нему альтернативное уведомление, воспользовавшись методом +{@link android.support.v4.app.NotificationCompat.Builder#setPublicVersion(android.app.Notification) setPublicVersion()} +.

+ +

Управление воспроизведением мультимедиа на экране блокировки

+ +

В версии Android 5.0 (API уровня 21) на экране блокировки больше не отображаются элементы управления воспроизведением мультимедиа, +основанные на классе {@link android.media.RemoteControlClient}, использование которого прекращено. Вместо этого используйте +шаблон {@link android.app.Notification.MediaStyle} с методом +{@link android.app.Notification.Builder#addAction(android.app.Notification.Action) addAction()} +, который преобразует действия в доступные для нажатия значки.

+ +

Примечание. Шаблон и метод {@link android.app.Notification.Builder#addAction(android.app.Notification.Action) addAction()} +не входят в состав вспомогательной библиотеки, поэтому эти функции работают только в версии Android 5.0 и +последующих версиях.

+ +

Чтобы отобразить элементы управления мультимедиа на экране блокировки в Android 5.0, задайте для видимости +значение {@link android.support.v4.app.NotificationCompat#VISIBILITY_PUBLIC}, выполнив описанную выше процедуру. Затем добавьте +действия и задайте шаблон {@link android.app.Notification.MediaStyle}, как описано в +следующем образце кода:

+ +
+Notification notification = new Notification.Builder(context)
+    // Show controls on lock screen even when user hides sensitive content.
+    .setVisibility(Notification.VISIBILITY_PUBLIC)
+    .setSmallIcon(R.drawable.ic_stat_player)
+    // Add media control buttons that invoke intents in your media service
+    .addAction(R.drawable.ic_prev, "Previous", prevPendingIntent) // #0
+    .addAction(R.drawable.ic_pause, "Pause", pausePendingIntent)  // #1
+    .addAction(R.drawable.ic_next, "Next", nextPendingIntent)     // #2
+    // Apply the media style template
+    .setStyle(new Notification.MediaStyle()
+    .setShowActionsInCompactView(1 /* #1: pause button */)
+    .setMediaSession(mMediaSession.getSessionToken())
+    .setContentTitle("Wonderful music")
+    .setContentText("My Awesome Band")
+    .setLargeIcon(albumArtBitmap)
+    .build();
+
+ +

Примечание. Прекращение использования класса {@link android.media.RemoteControlClient} +имеет и другие последствия для управления мультимедиа. Подробные сведения о новых API-интерфейсах для управления сеансами воспроизведения мультимедиа см. в разделе +Управление воспроизведением мультимедиа +.

+ + + +

Нестандартные макеты уведомлений

+

+ Платформа уведомлений позволяет задавать нестандартные макеты уведомлений, которые + определяют внешний вид уведомлений в объекте {@link android.widget.RemoteViews}. + Уведомления с нестандартным макетом — такие же, как обычные уведомления, но в их основе лежит класс + {@link android.widget.RemoteViews}, определенный в файле XML макета. +

+

+ Высота изображения, которую можно получить с использованием нестандартного макета уведомления, зависит от представления уведомления. Обычные + макеты представления ограничены по высоте 64 пикселами, а расширенные — 256 пикселами. +

+

+ Чтобы определить нестандартный макет уведомления, начните с инициализации объекта + {@link android.widget.RemoteViews}, который загружает файл XML макета. Затем, + вместо вызова методов, например + {@link android.support.v4.app.NotificationCompat.Builder#setContentTitle setContentTitle()}, + вызовите {@link android.support.v4.app.NotificationCompat.Builder#setContent setContent()}. Чтобы задать + сведения о содержимом в нестандартном уведомлении, используйте методы из + {@link android.widget.RemoteViews}, чтобы определить значения для дочерних представлений: +

+
    +
  1. + Создайте макет XML для уведомления в отдельном файле. Можно использовать любое имя файла + по своему усмотрению, однако расширение должно быть .xml +
  2. +
  3. + В своем приложении с помощью методов {@link android.widget.RemoteViews} определите значки и текст + уведомления. Поместите этот объект {@link android.widget.RemoteViews} в свой + {@link android.support.v4.app.NotificationCompat.Builder}, вызвав метод + {@link android.support.v4.app.NotificationCompat.Builder#setContent setContent()}. Не следует + задавать фон {@link android.graphics.drawable.Drawable} для объекта + {@link android.widget.RemoteViews}, поскольку цвет текста может стать нечитаемым. +
  4. +
+

+ В классе {@link android.widget.RemoteViews} также имеются методы, с помощью которых можно легко + добавить {@link android.widget.Chronometer} или {@link android.widget.ProgressBar} + в свой макет уведомления. Подробные сведения о создании нестандартных макетов для + уведомлений см. в справочной документации по {@link android.widget.RemoteViews}. +

+

+ Внимание! При использовании нестандартного макета уведомлений следует особое внимание уделять + обеспечению совместимости такого макета с разными вариантами ориентации устройства и разрешения экрана. Этот + совет относится ко всем макетам View, но особенно важен он для уведомлений, поскольку + размер панели уведомлений весьма ограничен. Не следует делать свои нестандартные макеты слишком + сложными и обязательно нужно тестировать их на различных конфигурациях устройств. +

+ +

Использование ресурсов стиля для текста нестандартного уведомления

+

+ Для форматирования текста нестандартного уведомления всегда используйте ресурсы стиля. Цвет фона + уведомления может меняться на разных устройствах и в разных версиях системы, а использование ресурсов стиля + позволяет это учесть. Начиная с версии Android 2.3 система определяет стиль для + текста уведомления со стандартным макетом. Если вы используете тот же стиль в приложениях, предназначенных для версии Android + 2.3 и последующих версий, то вы гарантируете видимость своего текста на фоне экрана. +

diff --git a/docs/html-intl/intl/ru/guide/topics/ui/overview.jd b/docs/html-intl/intl/ru/guide/topics/ui/overview.jd new file mode 100644 index 0000000000000000000000000000000000000000..0e9628b6d0162705ac7fa50fee351681c46b8bfc --- /dev/null +++ b/docs/html-intl/intl/ru/guide/topics/ui/overview.jd @@ -0,0 +1,71 @@ +page.title=Обзор пользовательского интерфейса +@jd:body + + +

Все элементы интерфейса пользователя в приложении Android создаются с помощью объектов {@link android.view.View} и +{@link android.view.ViewGroup}. Объект {@link android.view.View} формирует +на экране элемент, с которым пользователь может взаимодействовать. Объект {@link android.view.ViewGroup} содержит +другие объекты {@link android.view.View} (и {@link android.view.ViewGroup}) для +определения макета интерфейса.

+ +

Android предоставляет коллекцию подклассов {@link android.view.View} и {@link +android.view.ViewGroup}, которая включает в себя обычные элементы ввода (такие как кнопки и текстовые +поля) и различные модели макет (такие как линейный или относительный макет).

+ + +

Макеты пользовательского интерфейса

+ +

Пользовательский интерфейс для каждого компонента вашего приложения определяется с помощью иерархии объектов {@link +android.view.View} и {@link android.view.ViewGroup}, как показано на рисунке 1. Каждая группа просмотра +представляет собой невидимый контейнер, в котором объединены дочерние виды, причем дочерние виды могут представлять собой элементы +ввода или другие виджеты, которые +составляют часть пользовательского интерфейса. Эта древовидная иерархия может быть настолько простой или сложной, насколько +требуется (чем проще, тем лучше для производительности).

+ + +

Рисунок 1. Иллюстрация иерархии, которая определяет +макет интерфейса.

+ +

Чтобы объявить свой макет, можно создать экземпляры объектов {@link android.view.View} в коде и запустить построение +дерева, но самый простой и наиболее эффективный способ — определение макета с помощью файла XML. +XML позволяет создавать удобочитаемую структуру макета, подобно HTML.

+ +

Имя элемента XML для вида соответствует классу Android, к которому от относится. Так, элемент +<TextView> создает виджет {@link android.widget.TextView} в пользовательском интерфейсе, +а элемент <LinearLayout> создает группу просмотра {@link android.widget.LinearLayout} +.

+ +

Например, простой вертикальный макет с текстом и кнопкой выглядит следующим образом:

+
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:layout_width="fill_parent" 
+              android:layout_height="fill_parent"
+              android:orientation="vertical" >
+    <TextView android:id="@+id/text"
+              android:layout_width="wrap_content"
+              android:layout_height="wrap_content"
+              android:text="I am a TextView" />
+    <Button android:id="@+id/button"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="I am a Button" />
+</LinearLayout>
+
+ +

При загрузке ресурсов макетов в приложение Android инициализирует каждый узел макета в +объект режима выполнения, который можно использовать для определения дополнительного поведения, запроса состояния объекта или изменения +макета.

+ +

Полное руководство по созданию макета пользовательского интерфейса см. в документе Макеты +XML. + + +

Компоненты пользовательского интерфейса

+ +

Не обязательно создавать все элементы пользовательского интерфейса с помощью объектов {@link android.view.View} и {@link +android.view.ViewGroup}. Android предоставляет несколько компонентов приложений, которые содержат +стандартный макет пользовательского интерфейса, где остается лишь определить содержимое. Каждый из этих компонентов пользовательского интерфейса +содержит уникальный набор API, который описан в соответствующих документах, таких как Строка действий, Диалоги и Уведомления о состоянии.

+ + diff --git a/docs/html-intl/intl/ru/guide/topics/ui/settings.jd b/docs/html-intl/intl/ru/guide/topics/ui/settings.jd new file mode 100644 index 0000000000000000000000000000000000000000..4325439721deb860fac3aca4c3127dce0047d70f --- /dev/null +++ b/docs/html-intl/intl/ru/guide/topics/ui/settings.jd @@ -0,0 +1,1202 @@ +page.title=Настройки +page.tags=preference,preferenceactivity,preferencefragment + +@jd:body + + +
+
+ +

Содержание документа

+
    +
  1. Обзор +
      +
    1. Предпочтения
    2. +
    +
  2. +
  3. Определение предпочтений в XML +
      +
    1. Создание групп настроек
    2. +
    3. Использование намерений
    4. +
    +
  4. +
  5. Создание операции предпочтений
  6. +
  7. Использование фрагментов предпочтений
  8. +
  9. Значения настроек по умолчанию
  10. +
  11. Использование заголовков предпочтений +
      +
    1. Создание файла заголовков
    2. +
    3. Отображение заголовков
    4. +
    5. Поддержка старых версий посредством заголовков предпочтений
    6. +
    +
  12. +
  13. Чтение предпочтений +
      +
    1. Отслеживание изменений предпочтений
    2. +
    +
  14. +
  15. Контроль использования сети
  16. +
  17. Построение пользовательского предпочтения +
      +
    1. Указание пользовательского интерфейса
    2. +
    3. Сохранение значения настройки
    4. +
    5. Инициализация текущего значения
    6. +
    7. Предоставление значения по умолчанию
    8. +
    9. Сохранение и восстановление состояния предпочтений
    10. +
    +
  18. +
+ +

Основные классы

+
    +
  1. {@link android.preference.Preference}
  2. +
  3. {@link android.preference.PreferenceActivity}
  4. +
  5. {@link android.preference.PreferenceFragment}
  6. +
+ + +

См. также:

+
    +
  1. Руководство по дизайну настроек
  2. +
+
+
+ + + + +

В приложениях часто содержатся настройки, которые позволяют пользователю изменять возможности и поведение приложения. Например, +некоторые приложения позволяют пользователям включать и выключать уведомления или указывать частоту синхронизации +данных приложения с облаком.

+ +

Если вы хотите предоставить настройки для вашего приложения, вы должны использовать +API-интерфейсы {@link android.preference.Preference} системы Android для построения интерфейса, согласованного с +привычным для пользователей других приложений Android (включая системные настройки). В этом документе показано, +как построить настройки вашего приложения посредством API-интерфейсов {@link android.preference.Preference}.

+ +
+

Дизайн настроек

+

Подробную информацию о дизайне настроек см. в руководстве по дизайну настроек.

+
+ + + +

Рисунок 1. Снимки экранов настроек приложения Android +для обмена сообщениями. Выбор элемента, заданного посредством {@link android.preference.Preference}, +открывает интерфейс для изменения значения.

+ + + + +

Обзор

+ +

Вместо использования отображаемых объектов {@link android.view.View} для построения пользовательского интерфейса, настройки создаются +с помощью различных подклассов класса {@link android.preference.Preference}, который вы +объявляете в XML-файле.

+ +

Объект {@link android.preference.Preference} является строительным блоком для отдельной +настройки. Каждый объект {@link android.preference.Preference} отображается в виде элемента в списке и предоставляет +соответствующий пользовательский интерфейс для изменения настройки пользователями. Например, {@link +android.preference.CheckBoxPreference} создает элемент списка, который показывает флажок, а {@link +android.preference.ListPreference} создает элемент, который открывает диалоговое окно со списком вариантов для выбора.

+ +

Каждый добавляемый вами объект {@link android.preference.Preference} имеет соответствующую пару «ключ-значение», +которую система использует для сохранения настройки в файле {@link android.content.SharedPreferences} +значений настроек вашего приложения по умолчанию. Когда пользователь изменяет настройку, система обновляет соответствующее +значение в файле {@link android.content.SharedPreferences}. Вам потребуется +напрямую взаимодействовать с файлом, связанным с {@link android.content.SharedPreferences}, только в случае, +когда нужно прочитать значение для определения поведения вашего приложения на основе пользовательских настроек.

+ +

Значение, сохраненное в {@link android.content.SharedPreferences} для каждой настройки, может относиться к одному из +следующих типов данных:

+ +
    +
  • Логическое значение
  • +
  • Число с плавающей точкой
  • +
  • Целое число
  • +
  • Длинное целое число
  • +
  • Строка
  • +
  • Строка {@link java.util.Set}
  • +
+ +

Поскольку пользовательский интерфейс настроек вашего приложения создается посредством объектов {@link android.preference.Preference}, +а не +объектов {@link android.view.View}, вам потребуется использовать специализированные подклассы {@link android.app.Activity} или +{@link android.app.Fragment} для отображения настроек из списка:

+ +
    +
  • Если ваше приложение поддерживает версии Android старше 3.0 (API уровня 10 и ниже), для построения операции +необходимо наследовать класс {@link android.preference.PreferenceActivity}.
  • +
  • В операционных системах Android 3.0 и более поздних версиях вы должны вместо этого использовать традиционный класс {@link android.app.Activity}, +который содержит объект {@link android.preference.PreferenceFragment} для отображения настроек вашего приложения. +Однако, когда у вас есть несколько групп настроек, вы можете также +использовать {@link android.preference.PreferenceActivity} для создания макета с двумя панелями для больших экранов.
  • +
+ +

Настройка объекта {@link android.preference.PreferenceActivity} и экземпляров {@link +android.preference.PreferenceFragment} описана в разделах Создание операции предпочтения и Использование +фрагментов предпочтений.

+ + +

Предпочтения

+ +

Каждая настройка для вашего приложения представлена конкретным подклассом класса {@link +android.preference.Preference}. Каждый подкласс содержит набор основных свойств, которые позволяют вам +указывать, например, заголовок для настройки и ее значение по умолчанию. Каждый подкласс также содержит +собственные специализированные свойства и пользовательский интерфейс. В качестве примера на рисунке 1 показан снимок экрана настроек +приложения Android для обмена сообщениями. Каждый элемент списка на экране настроек возвращается отдельным объектом {@link +android.preference.Preference}.

+ +

Ниже приведены самые распространенные предпочтения:

+ +
+
{@link android.preference.CheckBoxPreference}
+
Отображает элемент с флажком для настройки, которая может быть включена или выключена. Сохраненное +значение является логическим (true, если флажок установлен).
+ +
{@link android.preference.ListPreference}
+
Открывает диалоговое окно со списком переключателей. Сохраненное значение +может относиться к одному из поддерживаемых типов значений (перечисленных выше).
+ +
{@link android.preference.EditTextPreference}
+
Открывает диалоговое окно с виджетом {@link android.widget.EditText}. Сохраненное значение — {@link +java.lang.String}.
+
+ +

См. класс {@link android.preference.Preference}, который содержит список всех остальных подклассов и их +соответствующих свойств.

+ +

Конечно, встроенные классы не обеспечивают всех потребностей, и вашему приложению может понадобиться +что-либо более специализированное. Например, в настоящее время система не предоставляет класс {@link +android.preference.Preference} для выбора числа или даты. Поэтому вам может потребоваться определить +свой собственный подкласс {@link android.preference.Preference}. См. раздел Построение пользовательского предпочтения.

+ + + +

Определение предпочтений в XML

+ +

Хотя вы можете создавать новые экземпляры объектов {@link android.preference.Preference} в режиме выполнения, вы должны +определить список настроек в файле XML с иерархией +объектов {@link android.preference.Preference}. Использование файла XML для определения вашей коллекции настроек предпочтительней, поскольку файл +обладает удобочитаемой структурой, которую легко обновлять. Кроме того, настройки вашего приложения +обычно определены заранее, хотя у вас сохраняется возможность изменять коллекцию в режиме выполнения.

+ +

Каждый подкласс класса {@link android.preference.Preference} может быть объявлен посредством элемента XML, +который соответствует имени класса, например, {@code <CheckBoxPreference>}.

+ +

Вы должны сохранить файл XML в каталоге {@code res/xml/}. Хотя вы можете назвать файл любым +именем, традиционно его называют {@code preferences.xml}. Обычно вам требуется лишь один файл, +поскольку ветви иерархии (которые открывают собственный список настроек) объявлены с помощью вложенных +экземпляров {@link android.preference.PreferenceScreen}.

+ +

Примечание. Если вы хотите создать макет с несколькими панелями для ваших +настроек, вам потребуются отдельные файлы XML для каждого фрагмента.

+ +

Корневой узел XML-файла должен быть элементом {@link android.preference.PreferenceScreen +<PreferenceScreen>}. Внутри этого элемента вы добавляете каждый элемент {@link +android.preference.Preference}. Каждый дочерний элемент, который вы добавляете внутри элемента +{@link android.preference.PreferenceScreen <PreferenceScreen>}, отображается в виде одного +пункта в списке настроек.

+ +

Например:

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <CheckBoxPreference
+        android:key="pref_sync"
+        android:title="@string/pref_sync"
+        android:summary="@string/pref_sync_summ"
+        android:defaultValue="true" />
+    <ListPreference
+        android:dependency="pref_sync"
+        android:key="pref_syncConnectionType"
+        android:title="@string/pref_syncConnectionType"
+        android:dialogTitle="@string/pref_syncConnectionType"
+        android:entries="@array/pref_syncConnectionTypes_entries"
+        android:entryValues="@array/pref_syncConnectionTypes_values"
+        android:defaultValue="@string/pref_syncConnectionTypes_default" />
+</PreferenceScreen>
+
+ +

В этом примере есть {@link android.preference.CheckBoxPreference} и {@link +android.preference.ListPreference}. Оба содержат следующие три атрибута:

+ +
+
{@code android:key}
+
Этот атрибут необходим для предпочтений, которые сохраняют значение данных. Он задает уникальный +ключ (строку), который использует система при сохранении значения этой настройки в {@link +android.content.SharedPreferences}. +

Этот атрибут не является обязательным только когда предпочтение представляет собой +{@link android.preference.PreferenceCategory} или {@link android.preference.PreferenceScreen}, либо +предпочтение указывает намерение {@link android.content.Intent} для вызова (посредством элемента {@code <intent>}) или фрагмент {@link android.app.Fragment} для отображения (с помощью атрибута {@code +android:fragment}).

+
+
{@code android:title}
+
Этот атрибут предоставляет имя настройки, отображаемое для пользователя.
+
{@code android:defaultValue}
+
Этот атрибут указывает исходное значение, которое система должна установить в файле {@link +android.content.SharedPreferences}. Вы должны указать значения по умолчанию для всех +настроек.
+
+ +

Для получения информации обо всех других поддерживаемых атрибутов см. документацию {@link +android.preference.Preference} (и соответствующий подкласс).

+ + +
+ +

Рисунок 2. Создание категорий + с заголовками.
1. Категория задана элементом {@link +android.preference.PreferenceCategory <PreferenceCategory>}.
2. Заголовок +задан посредством атрибута {@code android:title}.

+
+ + +

Когда список ваших настроек содержит более 10 элементов, вы, вероятно, захотите добавить заголовки для +определения групп настроек или отобразить эти группы +на отдельном экране. Эти возможности описаны в следующих разделах.

+ + +

Создание групп настроек

+ +

Если вы представляете список из 10 или более настроек, пользователям +может быть трудно их просматривать, воспринимать и обрабатывать. Это можно исправить, +разделив некоторые или все настройки на группы, что эффективно преобразует один длинный список в несколько +более коротких списков. Группа связанных настроек может быть представлена одним из двух способов:

+ + + +

Вы можете пользоваться одним или обоими из этих методов группировки для организации настроек в вашем приложении. Принимая +решение об используемом варианте и о разделении настроек на группы, вы должны следовать инструкциям в разделе +Настройки руководства «Дизайн для Android».

+ + +

Использование заголовков

+ +

Если вы хотите создать разделители с заголовками между группами настроек (как показано на рисунке 2), +поместите каждую группу объектов {@link android.preference.Preference} внутри {@link +android.preference.PreferenceCategory}.

+ +

Например:

+ +
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <PreferenceCategory 
+        android:title="@string/pref_sms_storage_title"
+        android:key="pref_key_storage_settings">
+        <CheckBoxPreference
+            android:key="pref_key_auto_delete"
+            android:summary="@string/pref_summary_auto_delete"
+            android:title="@string/pref_title_auto_delete"
+            android:defaultValue="false"... />
+        <Preference 
+            android:key="pref_key_sms_delete_limit"
+            android:dependency="pref_key_auto_delete"
+            android:summary="@string/pref_summary_delete_limit"
+            android:title="@string/pref_title_sms_delete"... />
+        <Preference 
+            android:key="pref_key_mms_delete_limit"
+            android:dependency="pref_key_auto_delete"
+            android:summary="@string/pref_summary_delete_limit"
+            android:title="@string/pref_title_mms_delete" ... />
+    </PreferenceCategory>
+    ...
+</PreferenceScreen>
+
+ + +

Использование подэкранов

+ +

Если вы хотите поместить группу настроек на подэкран (как показано на рисунке 3), поместите каждую группу +объектов {@link android.preference.Preference} внутри {@link +android.preference.PreferenceScreen}.

+ + +

Рисунок 3. Создание подэкранов. Элемент {@code +<PreferenceScreen>} +создает пункт, при выборе которого открывается отдельный список вложенных настроек.

+ +

Например:

+ +
+<PreferenceScreen  xmlns:android="http://schemas.android.com/apk/res/android">
+    <!-- opens a subscreen of settings -->
+    <PreferenceScreen
+        android:key="button_voicemail_category_key"
+        android:title="@string/voicemail"
+        android:persistent="false">
+        <ListPreference
+            android:key="button_voicemail_provider_key"
+            android:title="@string/voicemail_provider" ... />
+        <!-- opens another nested subscreen -->
+        <PreferenceScreen
+            android:key="button_voicemail_setting_key"
+            android:title="@string/voicemail_settings"
+            android:persistent="false">
+            ...
+        </PreferenceScreen>
+        <RingtonePreference
+            android:key="button_voicemail_ringtone_key"
+            android:title="@string/voicemail_ringtone_title"
+            android:ringtoneType="notification" ... />
+        ...
+    </PreferenceScreen>
+    ...
+</PreferenceScreen>
+
+ + +

Использование намерений

+ +

В некоторых случаях может потребоваться, чтобы элемент предпочтений открывал другую операцию, а не +экран настроек, например, веб-браузер для просмотра веб-страницы. Чтобы вызвать {@link +android.content.Intent}, когда пользователь выбирает элемент предпочтений, добавьте элемент {@code <intent>} +в качестве дочернего элемента соответствующего элемента {@code <Preference>}.

+ +

Например, здесь показано использование элемента предпочтений для открытия веб-страницы:

+ +
+<Preference android:title="@string/prefs_web_page" >
+    <intent android:action="android.intent.action.VIEW"
+            android:data="http://www.example.com" />
+</Preference>
+
+ +

Вы можете создавать неявные и явные намерения с помощью следующих атрибутов:

+ +
+
{@code android:action}
+
Назначаемое действие, как +в методе {@link android.content.Intent#setAction setAction()}.
+
{@code android:data}
+
Назначаемые данные, как в методе {@link android.content.Intent#setData setData()}.
+
{@code android:mimeType}
+
Назначаемый тип MIME, как +в методе {@link android.content.Intent#setType setType()}.
+
{@code android:targetClass}
+
Часть имени компонента, означающая класс, как в методе {@link android.content.Intent#setComponent +setComponent()}.
+
{@code android:targetPackage}
+
Пакетная часть имени компонента, как в методе {@link +android.content.Intent#setComponent setComponent()}.
+
+ + + +

Создание операции предпочтений

+ +

Для отображения ваших настроек в операции наследуйте класс {@link +android.preference.PreferenceActivity}. Это наследование традиционного класса {@link +android.app.Activity}, который отображает список настроек на основе иерархии объектов {@link +android.preference.Preference}. {@link android.preference.PreferenceActivity} +автоматически сохраняет настройки, связанные с каждым объектом {@link +android.preference.Preference}, когда пользователь вносит изменения.

+ +

Примечание. При разработке приложения для версии Android 3.0 +или выше вместо этого следует использовать {@link android.preference.PreferenceFragment}. Прочитайте следующий раздел +Использование фрагментов предпочтений.

+ +

Запомните самое важное: не загружайте макет отображаемых объектов во время обратного вызова {@link +android.preference.PreferenceActivity#onCreate onCreate()}. Вместо этого вызовите {@link +android.preference.PreferenceActivity#addPreferencesFromResource addPreferencesFromResource()} для +добавления предпочтений, объявленных в XML-файле для операции. Например, здесь приведен + минимальный код, необходимый для работы {@link android.preference.PreferenceActivity}:

+ +
+public class SettingsActivity extends PreferenceActivity {
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        addPreferencesFromResource(R.xml.preferences);
+    }
+}
+
+ +

Этого кода действительно достаточно для некоторых приложений, поскольку как только пользователь изменяет предпочтение, +система сохраняет изменения в файле {@link android.content.SharedPreferences} по умолчанию, который +другие компоненты вашего приложения могут читать, когда требуется проверить пользовательские настройки. Однако многим приложениям +требуется немного больше кода, чтобы отслеживать изменения, происходящие с предпочтениями. +Информацию об отслеживании изменений в файле {@link android.content.SharedPreferences} +см. в разделе Чтение предпочтений.

+ + + + +

Использование фрагментов предпочтений

+ +

При разработке приложений для Android 3.0 (API уровня 11) и более поздних версий необходимо использовать {@link +android.preference.PreferenceFragment} для отображения списка +объектов {@link android.preference.Preference}. Вы можете добавить {@link android.preference.PreferenceFragment} в любую операцию, при этом +необязательно использовать {@link android.preference.PreferenceActivity}.

+ +

Фрагменты обеспечивают более +универсальную архитектуру для вашего приложения по сравнению с использованием отдельных операций, вне зависимости от типа +создаваемой операции. Фактически, для управления отображением ваших настроек мы предлагаем вам использовать {@link +android.preference.PreferenceFragment} вместо {@link +android.preference.PreferenceActivity} при каждой возможности.

+ +

Ваша реализация {@link android.preference.PreferenceFragment} может содержать просто +определение метода {@link android.preference.PreferenceFragment#onCreate onCreate()} для загрузки +файла предпочтений посредством {@link android.preference.PreferenceFragment#addPreferencesFromResource +addPreferencesFromResource()}. Например:

+ +
+public static class SettingsFragment extends PreferenceFragment {
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        // Load the preferences from an XML resource
+        addPreferencesFromResource(R.xml.preferences);
+    }
+    ...
+}
+
+ +

Затем вы можете добавить этот фрагмент в операцию {@link android.app.Activity}, как вы сделали бы это для любого другого фрагмента +{@link android.app.Fragment}. Например:

+ +
+public class SettingsActivity extends Activity {
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        // Display the fragment as the main content.
+        getFragmentManager().beginTransaction()
+                .replace(android.R.id.content, new SettingsFragment())
+                .commit();
+    }
+}
+
+ +

Примечание. Фрагмент {@link android.preference.PreferenceFragment} не содержит +собственного объекта {@link android.content.Context}. Если вам требуется объект {@link android.content.Context}, +вы можете вызвать{@link android.app.Fragment#getActivity()}. Однако разработчик должен быть внимательным и вызывать метод +{@link android.app.Fragment#getActivity()} только в том случае, когда фрагмент прикреплен к операции. Если +фрагмент еще не прикреплен или был откреплен в конце его жизненного цикла, метод {@link +android.app.Fragment#getActivity()} вернет null.

+ + +

Установка значений по умолчанию

+ +

Вероятно, создаваемые вами предпочтения определяют важное поведение вашего приложения, поэтому +необходимо инициализировать соответствующий файл {@link android.content.SharedPreferences}, +записав в него значения по умолчанию для каждого предпочтения {@link android.preference.Preference} при первом запуске вашего +приложения пользователем.

+ +

В первую очередь необходимо указать значение по умолчанию для каждого объекта {@link +android.preference.Preference} +в вашем XML-файле посредством атрибута {@code android:defaultValue}. Значение может относиться к любому +типу данных, подходящему для соответствующего объекта {@link android.preference.Preference}. Например: +

+ +
+<!-- default value is a boolean -->
+<CheckBoxPreference
+    android:defaultValue="true"
+    ... />
+
+<!-- default value is a string -->
+<ListPreference
+    android:defaultValue="@string/pref_syncConnectionTypes_default"
+    ... />
+
+ +

Затем из метода {@link android.app.Activity#onCreate onCreate()} основной операции вашего приложения +(и из любой другой операции, через которую пользователь может войти в ваше приложение +в первый раз) вызовите {@link android.preference.PreferenceManager#setDefaultValues +setDefaultValues()}:

+ +
+PreferenceManager.setDefaultValues(this, R.xml.advanced_preferences, false);
+
+ +

Вызов этого метода при выполнении {@link android.app.Activity#onCreate onCreate()} гарантирует, что ваше +приложение правильно инициализируется и получит настройки по умолчанию, которые могут потребоваться вашему приложению +для определенного поведения (например, следует ли загружать данные при работе +в сотовой сети).

+ +

Этот метод имеет три аргумента:

+
    +
  • {@link android.content.Context} вашего приложения.
  • +
  • Идентификатор ресурса для XML-файла предпочтений, для которого вы хотите установить значения по умолчанию.
  • +
  • Логическое значение, которое указывает, требуется ли значения по умолчанию устанавливать более одного раза. +

    При значении false система устанавливает значения по умолчанию только в том случае, если этот метод никогда не вызывался ранее +(или атрибут {@link android.preference.PreferenceManager#KEY_HAS_SET_DEFAULT_VALUES} +в файле общих предпочтений по умолчанию имеет значение false).

  • +
+ +

Когда для третьего аргумента установлено значение false, вы можете вызывать этот метод +при каждом запуске операции, не опасаясь перезаписи сохраненных пользовательских предпочтений из-за их сброса в состояние +по умолчанию. Однако, если установить для этого аргумента значение true, вы будете перезаписывать все предыдущие +значения значениями по умолчанию.

+ + + +

Использование заголовков предпочтений

+ +

В редких случаях может потребоваться такая структура настроек, при которой на первом экране отображается только +список подэкранов (например, как в приложении системных настроек, +показанных на рисунках 4 и 5). При разработке такого дизайна для Android 3.0 и более поздних версий вы должны +использовать новую возможность Android 3.0 — «заголовки», вместо создания подэкранов посредством вложенных +элементов {@link android.preference.PreferenceScreen}.

+ +

Чтобы создать настройки с заголовками, выполните следующие действия:

+
    +
  1. Выделите каждую группу настроек в отдельный экземпляр {@link +android.preference.PreferenceFragment}. Таким образом, каждая группа настроек должна иметь отдельный +XML-файл.
  2. +
  3. Создайте XML-файл заголовков, в котором перечислены все группы настроек и объявления, какой фрагмент +содержит соответствующий список настроек.
  4. +
  5. Наследуйте класс {@link android.preference.PreferenceActivity}, который будет содержать ваши настройки.
  6. +
  7. Реализуйте обратный вызов {@link +android.preference.PreferenceActivity#onBuildHeaders onBuildHeaders()}, чтобы указать +файл заголовков.
  8. +
+ +

Огромное преимущество использования этого дизайна состоит в том, что при запуске на больших экранах {@link android.preference.PreferenceActivity} +автоматически создает макет с двумя панелями, показанный на рисунке 4.

+ +

Даже если ваше приложение поддерживает версии Android старше 3.0, вы можете создать +приложение, использующее {@link android.preference.PreferenceFragment} для двухпанельного представления на +новых устройствах и поддерживающее традиционную многоэкранную иерархию на более старых +устройствах (см. раздел Поддержка старых версий посредством +заголовков предпочтений).

+ + +

Рисунок 4. Двухпанельный макет с заголовками.
1. Заголовки +определяются посредством XML-файла заголовков.
2. Каждая группа настроек определяется с помощью фрагмента +{@link android.preference.PreferenceFragment}, который указывается элементом {@code <header>} +в файле заголовков.

+ + +

Рисунок 5. Смартфон с заголовками настроек. При выборе +пункта вместо заголовков отображается соответствующий +фрагмент {@link android.preference.PreferenceFragment}.

+ + +

Создание файла заголовков

+ +

Каждая группа настроек в вашем списке заголовков указывается отдельным элементом {@code <header>} +внутри корневого элемента {@code <preference-headers>}. Например:

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<preference-headers xmlns:android="http://schemas.android.com/apk/res/android">
+    <header 
+        android:fragment="com.example.prefs.SettingsActivity$SettingsFragmentOne"
+        android:title="@string/prefs_category_one"
+        android:summary="@string/prefs_summ_category_one" />
+    <header 
+        android:fragment="com.example.prefs.SettingsActivity$SettingsFragmentTwo"
+        android:title="@string/prefs_category_two"
+        android:summary="@string/prefs_summ_category_two" >
+        <!-- key/value pairs can be included as arguments for the fragment. -->
+        <extra android:name="someKey" android:value="someHeaderValue" />
+    </header>
+</preference-headers>
+
+ +

Посредством атрибута {@code android:fragment} каждый заголовок объявляет экземпляр фрагмента {@link +android.preference.PreferenceFragment}, который должен открываться при выборе этого заголовка пользователем.

+ +

Элемент {@code <extras>} позволяет передавать пары «ключ-значение» фрагменту в объекте {@link +android.os.Bundle}. Фрагмент может извлекать аргументы путем вызова метода {@link +android.app.Fragment#getArguments()}. Вы можете передавать аргументы фрагменту по различным причинам, +но хорошим поводом является повторное использование одного и того же подкласса {@link +android.preference.PreferenceFragment} для каждой группы и использование аргументов для указания +XML-файла предпочтений, который должен быть загружен фрагментом.

+ +

Например, здесь приведен фрагмент, который можно использовать повторно для нескольких групп настроек, когда каждый +заголовок определяет аргумент {@code <extra>} с ключом {@code "settings"}:

+ +
+public static class SettingsFragment extends PreferenceFragment {
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        String settings = getArguments().getString("settings");
+        if ("notifications".equals(settings)) {
+            addPreferencesFromResource(R.xml.settings_wifi);
+        } else if ("sync".equals(settings)) {
+            addPreferencesFromResource(R.xml.settings_sync);
+        }
+    }
+}
+
+ + + +

Отображение заголовков

+ +

Чтобы отобразить заголовки предпочтений, вы должны реализовать метод обратного вызова {@link +android.preference.PreferenceActivity#onBuildHeaders onBuildHeaders()} и вызвать +{@link android.preference.PreferenceActivity#loadHeadersFromResource +loadHeadersFromResource()}. Например:

+ +
+public class SettingsActivity extends PreferenceActivity {
+    @Override
+    public void onBuildHeaders(List<Header> target) {
+        loadHeadersFromResource(R.xml.preference_headers, target);
+    }
+}
+
+ +

Когда пользователь выбирает пункт в списке заголовков, система открывает связанный {@link +android.preference.PreferenceFragment}.

+ +

Примечание. При использовании заголовков предпочтений ваш подкласс {@link +android.preference.PreferenceActivity} не должен реализовывать метод {@link +android.preference.PreferenceActivity#onCreate onCreate()}, поскольку единственной обязательной задачей +операции является загрузка заголовков.

+ + +

Поддержка старых версий с заголовками предпочтений

+ +

Если ваше приложение поддерживает версии Android старше 3.0, вы можете использовать заголовки для +предоставления двухпанельного макета при работе на Android 3.0 или более поздней версии. Достаточно создать +дополнительный XML-файл настроек, использующий базовые элементы {@link android.preference.Preference +<Preference>}, которые ведут себя аналогично пунктам заголовка (для использования в более старых версиях +Android).

+ +

Вместо открытия новых экранов {@link android.preference.PreferenceScreen} каждый из элементов {@link +android.preference.Preference <Preference>} отправляет намерение {@link android.content.Intent} в +{@link android.preference.PreferenceActivity} с указанием XML-файла предпочтений +для загрузки.

+ +

В качестве примера приведен XML-файл для заголовков предпочтений, который используется в Android версии 3.0 +и более поздних версий ({@code res/xml/preference_headers.xml}):

+ +
+<preference-headers xmlns:android="http://schemas.android.com/apk/res/android">
+    <header 
+        android:fragment="com.example.prefs.SettingsFragmentOne"
+        android:title="@string/prefs_category_one"
+        android:summary="@string/prefs_summ_category_one" />
+    <header 
+        android:fragment="com.example.prefs.SettingsFragmentTwo"
+        android:title="@string/prefs_category_two"
+        android:summary="@string/prefs_summ_category_two" />
+</preference-headers>
+
+ +

А здесь представлен файл предпочтений, который содержит те же самые заголовки для версий старше +Android 3.0 ({@code res/xml/preference_headers_legacy.xml}):

+ +
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <Preference 
+        android:title="@string/prefs_category_one"
+        android:summary="@string/prefs_summ_category_one"  >
+        <intent 
+            android:targetPackage="com.example.prefs"
+            android:targetClass="com.example.prefs.SettingsActivity"
+            android:action="com.example.prefs.PREFS_ONE" />
+    </Preference>
+    <Preference 
+        android:title="@string/prefs_category_two"
+        android:summary="@string/prefs_summ_category_two" >
+        <intent 
+            android:targetPackage="com.example.prefs"
+            android:targetClass="com.example.prefs.SettingsActivity"
+            android:action="com.example.prefs.PREFS_TWO" />
+    </Preference>
+</PreferenceScreen>
+
+ +

Так как поддержка {@code <preference-headers>} была добавлена в версии Android 3.0, система вызывает +{@link android.preference.PreferenceActivity#onBuildHeaders onBuildHeaders()} в методе {@link +android.preference.PreferenceActivity} только при работе в Android версии 3.0 или более поздней версии. Чтобы загрузить +«старый» файл заголовков ({@code preference_headers_legacy.xml}), вы должны проверить версию Android +и, если версия старше Android 3.0 ({@link +android.os.Build.VERSION_CODES#HONEYCOMB}), вызвать {@link +android.preference.PreferenceActivity#addPreferencesFromResource addPreferencesFromResource()} для +загрузки старого файла заголовков. Например:

+ +
+@Override
+public void onCreate(Bundle savedInstanceState) {
+    super.onCreate(savedInstanceState);
+    ...
+
+    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
+        // Load the legacy preferences headers
+        addPreferencesFromResource(R.xml.preference_headers_legacy);
+    }
+}
+
+// Called only on Honeycomb and later
+@Override
+public void onBuildHeaders(List<Header> target) {
+   loadHeadersFromResource(R.xml.preference_headers, target);
+}
+
+ +

Остается обработать намерение {@link android.content.Intent}, переданное +в операцию, чтобы идентифицировать файл предпочтений для загрузки. Поэтому извлеките операцию намерения и сравните ее +с известными строками действия, которые вы использовали в тегах {@code <intent>} XML-файла предпочтений:

+ +
+final static String ACTION_PREFS_ONE = "com.example.prefs.PREFS_ONE";
+...
+
+@Override
+public void onCreate(Bundle savedInstanceState) {
+    super.onCreate(savedInstanceState);
+
+    String action = getIntent().getAction();
+    if (action != null && action.equals(ACTION_PREFS_ONE)) {
+        addPreferencesFromResource(R.xml.preferences);
+    }
+    ...
+
+    else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
+        // Load the legacy preferences headers
+        addPreferencesFromResource(R.xml.preference_headers_legacy);
+    }
+}
+
+ +

При этом помните, что последующие вызовы {@link +android.preference.PreferenceActivity#addPreferencesFromResource addPreferencesFromResource()} будут помещать +все предпочтения в один список, поэтому обязательно используйте операторы else-if, чтобы обеспечить только +однократный вызов метода при изменении условий.

+ + + + + +

Чтение предпочтений

+ +

По умолчанию все предпочтения вашего приложения сохраняются в файле, который доступен из любого места +вашего приложения посредством вызова статического метода {@link +android.preference.PreferenceManager#getDefaultSharedPreferences +PreferenceManager.getDefaultSharedPreferences()}. Он возвращает объект {@link +android.content.SharedPreferences}, содержащий все пары «ключ-значение», связанные +с объектами {@link android.preference.Preference}, использованными в вашей операции {@link +android.preference.PreferenceActivity}.

+ +

В качестве примера показано чтение одного из значений предпочтений из любой другой операции в вашем +приложении:

+ +
+SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this);
+String syncConnPref = sharedPref.getString(SettingsActivity.KEY_PREF_SYNC_CONN, "");
+
+ + + +

Отслеживание изменений предпочтений

+ +

Существует несколько причин, по которым вы можете захотеть получать уведомления, как только пользователь изменяет одно из +предпочтений. Чтобы получать обратный вызов при изменении любого из предпочтений, +реализуйте интерфейс {@link android.content.SharedPreferences.OnSharedPreferenceChangeListener +SharedPreference.OnSharedPreferenceChangeListener} и зарегистрируйте приемник для объекта +{@link android.content.SharedPreferences} посредством вызова {@link +android.content.SharedPreferences#registerOnSharedPreferenceChangeListener +registerOnSharedPreferenceChangeListener()}.

+ +

Этот интерфейс содержит только один метод обратного вызова, {@link +android.content.SharedPreferences.OnSharedPreferenceChangeListener#onSharedPreferenceChanged +onSharedPreferenceChanged()}, и вы, вероятно, сочтете его самым простым способом реализации интерфейса в составе своей +операции. Например:

+ +
+public class SettingsActivity extends PreferenceActivity
+                              implements OnSharedPreferenceChangeListener {
+    public static final String KEY_PREF_SYNC_CONN = "pref_syncConnectionType";
+    ...
+
+    public void onSharedPreferenceChanged(SharedPreferences sharedPreferences,
+        String key) {
+        if (key.equals(KEY_PREF_SYNC_CONN)) {
+            Preference connectionPref = findPreference(key);
+            // Set summary to be the user-description for the selected value
+            connectionPref.setSummary(sharedPreferences.getString(key, ""));
+        }
+    }
+}
+
+ +

В этом примере метод проверяет, выполнено ли изменение настройки для известного ключа предпочтений. Он +вызывает {@link android.preference.PreferenceActivity#findPreference findPreference()} для получения объекта +{@link android.preference.Preference}, который был изменен, поэтому он может изменить сводку пункта +, описывающего выбор пользователя. То есть, когда настройка представляет собой {@link +android.preference.ListPreference} или другую настройку с несколькими вариантами выбора, при изменении этой настройки вы должны вызвать {@link +android.preference.Preference#setSummary setSummary()} для отображения +текущего состояния (например, настройка спящего режима, показанная на рисунке 5).

+ +

Примечание. В соответствии с рекомендациями раздела Настройки руководства «Дизайн для Android», мы рекомендуем вам обновлять +сводку для {@link android.preference.ListPreference} при каждом изменении предпочтения пользователем, +чтобы описать текущую настройку.

+ +

Для правильного управления жизненным циклом в операции мы рекомендуем вам регистрировать или отменять регистрацию +вашего приемника {@link android.content.SharedPreferences.OnSharedPreferenceChangeListener} во время выполнения обратных вызовов {@link +android.app.Activity#onResume} и {@link android.app.Activity#onPause} соответственно:

+ +
+@Override
+protected void onResume() {
+    super.onResume();
+    getPreferenceScreen().getSharedPreferences()
+            .registerOnSharedPreferenceChangeListener(this);
+}
+
+@Override
+protected void onPause() {
+    super.onPause();
+    getPreferenceScreen().getSharedPreferences()
+            .unregisterOnSharedPreferenceChangeListener(this);
+}
+
+ +

Внимание! Когда вы вызываете приемник {@link +android.content.SharedPreferences#registerOnSharedPreferenceChangeListener +registerOnSharedPreferenceChangeListener()}, диспетчер предпочтений не +сохраняет строгую ссылку на приемник. Вы должны сохранить строгую +ссылку на приемник, в противном случае она будет чувствительной к очистке памяти. Мы +рекомендуем хранить ссылку на приемник в данных экземпляра объекта +, который будет существовать, пока вам нужен приемник.

+ +

Например, в следующем коде вызывающий объект не сохраняет +ссылку на приемник. В результате этого приемник будет удален при очистке памяти +и через некоторое время приведет к сбою:

+ +
+prefs.registerOnSharedPreferenceChangeListener(
+  // Bad! The listener is subject to garbage collection!
+  new SharedPreferences.OnSharedPreferenceChangeListener() {
+  public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
+    // listener implementation
+  }
+});
+
+ +

Вместо этого сохраните ссылку на приемник в поле данных экземпляра объекта +, который будет существовать, пока нужен приемник:

+ +
+SharedPreferences.OnSharedPreferenceChangeListener listener =
+    new SharedPreferences.OnSharedPreferenceChangeListener() {
+  public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
+    // listener implementation
+  }
+};
+prefs.registerOnSharedPreferenceChangeListener(listener);
+
+ +

Контроль использования сети

+ + +

Начиная с версии Android 4.0, системное приложение «Настройки» позволяет пользователям просматривать использование +сетевых данных приложениями, работающими на переднем плане и в фоновом режиме. После этого пользователи могут +отключить использование данных в фоновом режиме для отдельных приложений. Для того, чтобы пользователи не отключали доступ вашего приложения к данным +в фоновом режиме, вы должны эффективно использовать подключение в режиме передачи данных и предоставить +пользователям возможность настройки использования данных вашим приложением посредством настроек приложения.

+ +

Например, вы можете позволить пользователям управлять частотой синхронизации данных приложения, выполнением загрузки +только в режиме подключения по Wi-Fi, использованием данных в роуминге и т. д. Когда +эти возможности управления доступны, пользователи с меньшей вероятностью отключат доступ вашего приложения к данным, +когда оно достигает установленных в системных настройках лимитов, поскольку вместо отключения они могут +точно контролировать объем данных, который использует ваше приложение.

+ +

После добавления необходимых предпочтений в вашу операцию {@link android.preference.PreferenceActivity} +для управления поведением вашего приложения в отношении данных вы должны добавить фильтр намерений для {@link +android.content.Intent#ACTION_MANAGE_NETWORK_USAGE} в вашем файле манифеста. Например:

+ +
+<activity android:name="SettingsActivity" ... >
+    <intent-filter>
+       <action android:name="android.intent.action.MANAGE_NETWORK_USAGE" />
+       <category android:name="android.intent.category.DEFAULT" />
+    </intent-filter>
+</activity>
+
+ +

Этот фильтр манифеста указывает системе, что эта операция управляет использованием +данных вашим приложением. Так, когда пользователь проверяет объем использованных приложением данных в системном приложении +«Настройки», отображается кнопка Просмотреть настройки приложения, которая запускает вашу операцию +{@link android.preference.PreferenceActivity}, чтобы пользователь мог уточнить, сколько данных использует +ваше приложение.

+ + + + + + + +

Построение пользовательского предпочтения

+ +

Система Android содержит множество подклассов {@link android.preference.Preference}, которые +позволяют вам строить пользовательский интерфейс для нескольких различных типов настроек. +Тем не менее, вы можете обнаружить, что для нужной вам настройки нет встроенного решения, например, для выбора +числа или даты. В таком случае вам потребуется создать нестандартное предпочтение путем наследования +класса {@link android.preference.Preference} или одного из других подклассов.

+ +

При наследовании класса {@link android.preference.Preference} нужно выполнить несколько +важных пунктов:

+ +
    +
  • Укажите пользовательский интерфейс, который должен отображаться при выборе этой настройки пользователем.
  • +
  • При необходимости сохраните значение настройки.
  • +
  • Инициализируйте {@link android.preference.Preference} текущим значением (или значением по умолчанию), +когда предпочтение отображается.
  • +
  • Укажите значение по умолчанию в ответ на запрос системы.
  • +
  • Если {@link android.preference.Preference} содержит свой собственный пользовательский интерфейс (например, диалоговое окно), сохраните +и восстановите состояние для обработки изменений жизненного цикла (например, когда пользователь поворачивает экран).
  • +
+ +

В следующих разделах описано выполнение каждой из этих задач.

+ + + +

Указание пользовательского интерфейса

+ +

Если вы наследуете класс {@link android.preference.Preference} непосредственно, вы должны реализовать метод +{@link android.preference.Preference#onClick()}, чтобы задать действие, происходящее при выборе +пункта пользователем. Однако большая часть нестандартных настроек наследует {@link android.preference.DialogPreference}, чтобы +отобразить диалоговое окно, что упрощает процедуру. Когда вы наследуете {@link +android.preference.DialogPreference}, вы должны вызвать {@link +android.preference.DialogPreference#setDialogLayoutResource setDialogLayoutResourcs()}, находясь в конструкторе +класса, чтобы указать макет диалогового окна.

+ +

В качестве примера показан конструктор нестандартного диалогового окна {@link +android.preference.DialogPreference}, в котором объявляется макет и указывается текст для +положительной и отрицательной кнопок диалога по умолчанию:

+ +
+public class NumberPickerPreference extends DialogPreference {
+    public NumberPickerPreference(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        
+        setDialogLayoutResource(R.layout.numberpicker_dialog);
+        setPositiveButtonText(android.R.string.ok);
+        setNegativeButtonText(android.R.string.cancel);
+        
+        setDialogIcon(null);
+    }
+    ...
+}
+
+ + + +

Сохранение значения настройки

+ +

Вы можете сохранить значение настройки в любой момент, вызвав один из методов {@code persist*()} класса {@link +android.preference.Preference}, например, {@link +android.preference.Preference#persistInt persistInt()}, если настройка имеет целое значение, или +{@link android.preference.Preference#persistBoolean persistBoolean()} для сохранения логического значения.

+ +

Примечание. Каждое предпочтение {@link android.preference.Preference} может сохранять только один +тип данных, поэтому вы должны использовать метод {@code persist*()}, соответствующий типу данных, используемых вашим +пользовательским предпочтением {@link android.preference.Preference}.

+ +

Выбор метода сохранения настройки может зависеть от наследованного класса {@link +android.preference.Preference}. Если вы наследуете {@link +android.preference.DialogPreference}, вы должны сохранять значение только при закрытии диалога +с положительным результатом (пользователь нажал кнопку «OK»).

+ +

Когда {@link android.preference.DialogPreference} закрывается, система вызывает метод {@link +android.preference.DialogPreference#onDialogClosed onDialogClosed()}. Этот метод содержит +логический аргумент, который указывает, является ли результат пользователя «положительным» — если аргумент имеет значение +true, значит пользователь выбрал положительную кнопку и вы должны сохранить новое значение. Например: +

+ +
+@Override
+protected void onDialogClosed(boolean positiveResult) {
+    // When the user selects "OK", persist the new value
+    if (positiveResult) {
+        persistInt(mNewValue);
+    }
+}
+
+ +

В этом примере mNewValue — это член класса, который содержит текущее значение +настройки. При вызове {@link android.preference.Preference#persistInt persistInt()} значение сохраняется +в файл {@link android.content.SharedPreferences} (с автоматическим использованием ключа, +указанного в XML-файле для этого предпочтения {@link android.preference.Preference}).

+ + +

Инициализация текущего значения

+ +

Когда система добавляет ваше предпочтение {@link android.preference.Preference} на экран, она +вызывает метод {@link android.preference.Preference#onSetInitialValue onSetInitialValue()}, чтобы уведомить вас, +имеет ли настройка сохраненное значение. Если сохраненного значения нет, этот вызов предоставляет вам +значение по умолчанию.

+ +

Метод {@link android.preference.Preference#onSetInitialValue onSetInitialValue()} передает +логическое значение, restorePersistedValue, чтобы показать, было ли уже сохранено значение +для настройки. Если значение равно true, вы должны извлечь сохраненное значение, вызвав +один из методов {@code getPersisted*()} класса {@link +android.preference.Preference}, например, {@link +android.preference.Preference#getPersistedInt getPersistedInt()} для целого значения. Обычно +требуется извлечь сохраненное значение, чтобы можно было правильно обновить пользовательский интерфейс для отражения +ранее сохраненного значения.

+ +

Если restorePersistedValue имеет значение false, вы +должны использовать значение по умолчанию, которое передается во втором аргументе.

+ +
+@Override
+protected void onSetInitialValue(boolean restorePersistedValue, Object defaultValue) {
+    if (restorePersistedValue) {
+        // Restore existing state
+        mCurrentValue = this.getPersistedInt(DEFAULT_VALUE);
+    } else {
+        // Set default state from the XML attribute
+        mCurrentValue = (Integer) defaultValue;
+        persistInt(mCurrentValue);
+    }
+}
+
+ +

Каждый метод {@code getPersisted*()} содержит аргумент, который указывает +значение по умолчанию на случай, когда действительно нет сохраненного значения, или не существует ключ. В +приведенном выше примере локальная константа служит для указания значения по умолчанию на случай, если {@link +android.preference.Preference#getPersistedInt getPersistedInt()} не может вернуть сохраненное значение.

+ +

Внимание! Вы не можете использовать +defaultValue в качестве значения по умолчанию в методе {@code getPersisted*()}, так как +его значение всегда равно null, когда restorePersistedValue имеет значение true.

+ + +

Предоставление значения по умолчанию

+ +

Если экземпляр вашего класса {@link android.preference.Preference} указывает значение по умолчанию +(с помощью атрибута {@code android:defaultValue}), +система вызывает {@link android.preference.Preference#onGetDefaultValue +onGetDefaultValue()}, когда она создает экземпляр объекта для извлечения значения. Вы должны реализовать +этот метод, чтобы сохранить значение по умолчанию для системы в {@link +android.content.SharedPreferences}. Например:

+ +
+@Override
+protected Object onGetDefaultValue(TypedArray a, int index) {
+    return a.getInteger(index, DEFAULT_VALUE);
+}
+
+ +

Аргументы метода предоставляют все необходимое: массив атрибутов и указатель +положения {@code android:defaultValue}, который вы должны извлечь. Причина, по которой вы должны +реализовать этот метод, чтобы извлечь значение по умолчанию из атрибута, состоит в том, что вы должны указать +локальное значение по умолчанию для атрибута в случае, когда значение не определено.

+ + + +

Сохранение и восстановление состояния предпочтений

+ +

Как и {@link android.view.View} в макете, ваш подкласс {@link android.preference.Preference} +отвечает за сохранение и восстановление своего состояния в случае перезапуска операции или фрагмента +(например, когда пользователь поворачивает экран). Чтобы правильно сохранять и восстанавливать +состояние вашего класса {@link android.preference.Preference}, вы должны реализовать +методы обратного вызова жизненного цикла {@link android.preference.Preference#onSaveInstanceState +onSaveInstanceState()} и {@link +android.preference.Preference#onRestoreInstanceState onRestoreInstanceState()}.

+ +

Состояние вашего {@link android.preference.Preference} определяется объектом, который реализует +интерфейс {@link android.os.Parcelable}. Система Android предоставляет вам такой объект +в качестве начальной точки для определения вашего объекта состояния: класс {@link +android.preference.Preference.BaseSavedState}.

+ +

Чтобы определить, как ваш класс {@link android.preference.Preference} сохраняет свое состояние, вы должны +наследовать класс {@link android.preference.Preference.BaseSavedState}. Вы должны переопределить лишь + несколько методов и определить объект +{@link android.preference.Preference.BaseSavedState#CREATOR}.

+ +

Для большинства приложений вы можете скопировать следующую реализацию и просто изменить строки, которые +обрабатывают {@code value}, если ваш подкласс {@link android.preference.Preference} сохраняет типы +данных, отличные от целых.

+ +
+private static class SavedState extends BaseSavedState {
+    // Member that holds the setting's value
+    // Change this data type to match the type saved by your Preference
+    int value;
+
+    public SavedState(Parcelable superState) {
+        super(superState);
+    }
+
+    public SavedState(Parcel source) {
+        super(source);
+        // Get the current preference's value
+        value = source.readInt();  // Change this to read the appropriate data type
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        super.writeToParcel(dest, flags);
+        // Write the preference's value
+        dest.writeInt(value);  // Change this to write the appropriate data type
+    }
+
+    // Standard creator object using an instance of this class
+    public static final Parcelable.Creator<SavedState> CREATOR =
+            new Parcelable.Creator<SavedState>() {
+
+        public SavedState createFromParcel(Parcel in) {
+            return new SavedState(in);
+        }
+
+        public SavedState[] newArray(int size) {
+            return new SavedState[size];
+        }
+    };
+}
+
+ +

После добавления показанной выше реализации {@link android.preference.Preference.BaseSavedState} +в ваше приложение (обычно в качестве подкласса вашего подкласса {@link android.preference.Preference}), вам +потребуется реализовать методы {@link android.preference.Preference#onSaveInstanceState +onSaveInstanceState()} и {@link +android.preference.Preference#onRestoreInstanceState onRestoreInstanceState()} для вашего +подкласса {@link android.preference.Preference}.

+ +

Например:

+ +
+@Override
+protected Parcelable onSaveInstanceState() {
+    final Parcelable superState = super.onSaveInstanceState();
+    // Check whether this Preference is persistent (continually saved)
+    if (isPersistent()) {
+        // No need to save instance state since it's persistent,
+        // use superclass state
+        return superState;
+    }
+
+    // Create instance of custom BaseSavedState
+    final SavedState myState = new SavedState(superState);
+    // Set the state's value with the class member that holds current
+    // setting value
+    myState.value = mNewValue;
+    return myState;
+}
+
+@Override
+protected void onRestoreInstanceState(Parcelable state) {
+    // Check whether we saved the state in onSaveInstanceState
+    if (state == null || !state.getClass().equals(SavedState.class)) {
+        // Didn't save the state, so call superclass
+        super.onRestoreInstanceState(state);
+        return;
+    }
+
+    // Cast state to custom BaseSavedState and pass to superclass
+    SavedState myState = (SavedState) state;
+    super.onRestoreInstanceState(myState.getSuperState());
+    
+    // Set this Preference's widget to reflect the restored state
+    mNumberPicker.setValue(myState.value);
+}
+
+ diff --git a/docs/html-intl/intl/ru/guide/topics/ui/ui-events.jd b/docs/html-intl/intl/ru/guide/topics/ui/ui-events.jd new file mode 100644 index 0000000000000000000000000000000000000000..cd2b4813cf83461610203b1e8ea8f6647e5b9677 --- /dev/null +++ b/docs/html-intl/intl/ru/guide/topics/ui/ui-events.jd @@ -0,0 +1,291 @@ +page.title=События ввода +parent.title=Пользовательский интерфейс +parent.link=index.html +@jd:body + + + +

В системе Android предусмотрено несколько способов перехвата событий взаимодействия пользователя с вашим приложением. +Что касается событий в пользовательском интерфейсе, подход состоит в том, чтобы захватывать события из +определенного представления объекта, с которым взаимодействует пользователь. Класс отображаемых объектов (View) содержит средства, позволяющие делать это.

+ +

В различных классах View, которые вы будете использовать для создания макета, вы заметите несколько общедоступных методов обратного вызова, +полезных для работы с событиями пользовательского интерфейса. Эти методы вызываются платформой Android, когда +с этим объектом выполняется соответствующее действие. Например, когда пользователь касается отображаемого объекта (кнопки), +вызывается метод onTouchEvent() этого объекта. Однако для перехвата этого события вы должны +наследовать класс и переопределить метод. Однако наследование каждого отображаемого объекта +для обработки такого события было бы неудобно. Именно поэтому класс View также содержит +набор вложенных интерфейсов с обратными вызовами, которые можно задать значительно проще. Эти интерфейсы, +которые называются приемниками событий, и служат перехватчиками действий пользователя с вашим пользовательским интерфейсом.

+ +

Несмотря на то, что вы будете чаще использовать приемники событий для перехвата действий пользователя, может +наступить момент, когда вам не захочется наследовать класс View, чтобы создать нестандартный компонент. +Возможно, вы захотите наследовать класс {@link android.widget.Button}, +чтобы сделать нечто более необычное. В этом случае вы сможете определить поведение события по умолчанию для своего +класса с помощью обработчиков событий класса.

+ + +

Приемники событий

+ +

Приемник событий — это интерфейс в классе {@link android.view.View}, который содержит один +метод обратного вызова. Эти методы будут вызываться платформой Android, когда в результате взаимодействия пользователя с объектом +пользовательского интерфейса срабатывает отображаемый объект View, в котором зарегистрирован приемник.

+ +

Интерфейсы, включенные в приемник события, представляют собой следующие методы обратного вызова:

+ +
+
onClick()
+
Из объекта {@link android.view.View.OnClickListener}. + Этот метод вызывается, когда пользователь касается элемента + (в режиме касания), или переводит фокус на элемент с помощью клавиш перемещения или трекбола и +нажимает соответствующую клавишу «ввода» или нажимает на трекбол.
+
onLongClick()
+
Из объекта {@link android.view.View.OnLongClickListener}. + Этот метод вызывается, когда пользователь касается элемента и удерживает его (в режиме касания), +или переводит фокус на элемент с помощью клавиш перемещения или трекбола и +нажимает и удерживает соответствующую клавишу «ввода» или трекбол (в течение одной секунды).
+
onFocusChange()
+
Из объекта {@link android.view.View.OnFocusChangeListener}. + Этот метод вызывается, когда пользователь перемещается в элемент или из него с помощью клавиш перемещения или трекбола.
+
onKey()
+
Из объекта {@link android.view.View.OnKeyListener}. + Этот метод вызывается, когда пользователь переносит фокус на элемент и нажимает или отпускает аппаратную клавишу на устройстве.
+
onTouch()
+
Из объекта {@link android.view.View.OnTouchListener}. + Этот метод вызывается, когда пользователь выполняет действие, считающееся событием касания, например, нажимает, отпускает +или выполняет любой жест на экране (в пределах границ элемента).
+
onCreateContextMenu()
+
Из объекта {@link android.view.View.OnCreateContextMenuListener}. +Этот метод вызывается, когда создается контекстное меню (в результате длительного «длительного нажатия»). См. обсуждение +контекстных меню в руководстве +для разработчиков Меню.
+
+ +

Эти методы являются единственными составными частями соответствующих интерфейсов. Чтобы определить один из этих методов +и обрабатывать события, реализуйте вложенный интерфейс в вашем процесс или определите его, как анонимный класс. +Затем передайте экземпляр реализации +в соответствующий метод View.set...Listener(). (Например, вызовите +{@link android.view.View#setOnClickListener(View.OnClickListener) setOnClickListener()} +и передайте ему свою реализацию {@link android.view.View.OnClickListener OnClickListener}.)

+ +

В следующем примере показано, как зарегистрировать приемник события «по клику» (on-click) для кнопки.

+ +
+// Create an anonymous implementation of OnClickListener
+private OnClickListener mCorkyListener = new OnClickListener() {
+    public void onClick(View v) {
+      // do something when the button is clicked
+    }
+};
+
+protected void onCreate(Bundle savedValues) {
+    ...
+    // Capture our button from layout
+    Button button = (Button)findViewById(R.id.corky);
+    // Register the onClick listener with the implementation above
+    button.setOnClickListener(mCorkyListener);
+    ...
+}
+
+ +

Возможно, еще удобнее реализовать OnClickListener как часть вашей операции. +При этом исключается дополнительная нагрузка класса и выделение объекта. Например:

+
+public class ExampleActivity extends Activity implements OnClickListener {
+    protected void onCreate(Bundle savedValues) {
+        ...
+        Button button = (Button)findViewById(R.id.corky);
+        button.setOnClickListener(this);
+    }
+
+    // Implement the OnClickListener callback
+    public void onClick(View v) {
+      // do something when the button is clicked
+    }
+    ...
+}
+
+ +

Обратите внимание, что обратный вызов onClick() в примере выше не +содержит возвращаемого значения, но некоторые другие методы приемника события должны возвращать логическое значение. Причина +зависит от события. Для некоторых методов, которые возвращают значения, причина описана ниже:

+
    +
  • {@link android.view.View.OnLongClickListener#onLongClick(View) onLongClick()} — +этот метод возвращает логическое значение, указывающее, что вы обработали это событие и его более не следует хранить. +А именно, верните значение true, чтобы указать, что вы обработали событие и его следует остановить; +верните значение false, если вы не обработали его и/или событие должно продолжаться для любых других +приемников события on-click.
  • +
  • {@link android.view.View.OnKeyListener#onKey(View,int,KeyEvent) onKey()} — +этот метод возвращает логическое значение, указывающее, что вы обработали это событие и его более не следует хранить. + А именно, верните значение true, чтобы указать, что вы обработали событие и его следует остановить; +верните значение false, если вы не обработали его и/или событие должно продолжаться для любых других +приемников события on-click.
  • +
  • {@link android.view.View.OnTouchListener#onTouch(View,MotionEvent) onTouch()} — +этот метод возвращает логическое значение, указывающее, обработал ли ваш приемник это событие. Важно, что +это событие может повлечь несколько действий, следующих друг за другом. Поэтому, если вы возвращаете ложь при приеме + события нажатия, вы указываете, что вы не обработали событие и не +интересуетесь последующими действиями в результате этого события. Соответственно, этот метод не будет вызываться для любых других действий +в рамках этого события, таких как жесты или возможное действие отпускания.
  • +
+ +

Помните, что события аппаратных клавиш всегда попадают в отображаемый объект View, который находится в фокусе. Они отправляются, начиная с верха +в иерархии отображаемых объектов, затем ниже, пока не достигнут соответствующего назначения. Если ваш отображаемый объект (или дочерний объект вашего отображаемого объекта) +находится в фокусе, вы можете видеть, как событие перемещается сквозь метод {@link android.view.View#dispatchKeyEvent(KeyEvent) +dispatchKeyEvent()}. В качестве альтернативы к перехвату событий клавиш через отображаемый объект View можно также получать +все события внутри вашей операции с помощью методов {@link android.app.Activity#onKeyDown(int,KeyEvent) onKeyDown()}{@link android.app.Activity#onKeyUp(int,KeyEvent) onKeyUp()}.

+ +

Кроме того, размышляя о вводе текста для приложения, помните, что многие устройства поддерживают только программные методы +ввода. Такие методы не обязательно основаны на нажатиях клавиш. Некоторые могут использовать голосовой ввод, рукописный ввод и т. д. Даже если +метод ввода представлен интерфейсом, подобным клавиатуре, он обычно не приводит к запуску семейства +событий{@link android.app.Activity#onKeyDown(int,KeyEvent) onKeyDown()}. Не следует создавать +пользовательский интерфейс, для управления которым требуется нажимать определенные клавиши, кроме случаев, когда вы хотите ограничить использование приложения только устройствами +с аппаратной клавиатурой. В частности, не полагайтесь на эти методы для подтверждения ввода, когда пользователь нажимает +клавишу «Ввод». Вместо этого используйте такие действия, как {@link android.view.inputmethod.EditorInfo#IME_ACTION_DONE}, чтобы подать методу ввода сигнал +о том, какой реакции ожидает приложение, чтобы он мог соответственно изменить свой пользовательский интерфейс. Избегайте предположений +о работе программного метода ввода и доверьте ему ввод уже отформатированного текста в ваше приложение.

+ +

Примечание. Система Android сначала будет вызывать обработчики событий, а затем соответствующие обработчики по умолчанию +из определения класса. Поэтому возврат значения истина из этих приемников событий будет останавливать +передачу события остальным приемникам событий, а также будет блокировать обратный вызов +обработчика событий по умолчанию в отображаемом объекте. Поэтому проверьте, что вы хотите прервать событие, когда вы возвращаете значение true.

+ + +

Обработчики событий

+ +

Если вы создаете нестандартный компонент из отображаемого объекта, вы сможете определить несколько методов обратного вызова, +используемых как обработчики события по умолчанию. +В документе Нестандартные +компоненты можно узнать о некоторых общих обратных вызовах, используемых для обработки событий, +включая следующие:

+
    +
  • {@link android.view.View#onKeyDown} — вызывается при возникновении нового события клавиши.
  • +
  • {@link android.view.View#onKeyUp} — вызывается при возникновении события отпускания клавиши.
  • +
  • {@link android.view.View#onTrackballEvent} — вызывается при возникновении события перемещения трекбола.
  • +
  • {@link android.view.View#onTouchEvent} — вызывается при возникновении события жеста на сенсорном экране.
  • +
  • {@link android.view.View#onFocusChanged} — вызывается, когда отображаемый объект получает или теряет фокус.
  • +
+

Существует несколько других методов, о которых следует знать и которые не являются частью класса View, +но могут напрямую влиять на доступные вам способы обработки событий. Поэтому, управляя более сложными событиями внутри +макета, учитывайте и другие методы:

+
    +
  • {@link android.app.Activity#dispatchTouchEvent(MotionEvent) + Activity.dispatchTouchEvent(MotionEvent)} — этот метод позволяет вашей операции {@link + android.app.Activity} перехватывать все события касаний перед их отправкой в окно.
  • +
  • {@link android.view.ViewGroup#onInterceptTouchEvent(MotionEvent) + ViewGroup.onInterceptTouchEvent(MotionEvent)} — этот метод позволяет объекту {@link + android.view.ViewGroup} просматривать события перед их отправкой в дочерние отображаемые объекты.
  • +
  • {@link android.view.ViewParent#requestDisallowInterceptTouchEvent(boolean) + ViewParent.requestDisallowInterceptTouchEvent(boolean)} — вызовите этот метод +в родительском отображаемом объекте, чтобы указать ему, что он не должен перехватывать события касания с помощью {@link + android.view.ViewGroup#onInterceptTouchEvent(MotionEvent)}.
  • +
+ +

Режим касания

+

+Когда пользователь перемещается по пользовательскому интерфейсу с помощью клавиш перемещения или трекбола, необходимо +передавать фокус действующим элементам (таким как кнопки), чтобы пользователь мог видеть, +какой элемент будет принимать ввод. Однако, если устройство поддерживает сенсорный ввод, и пользователь +начинает взаимодействовать с интерфейсом, прикасаясь к его элементам, исчезает необходимость +выделять элементы или передавать фокус определенному отображаемому объекту. Следовательно, существует режим +взаимодействия, который называется «режимом касания». +

+

+Как только пользователь касается экрана, устройство, поддерживающее сенсорный ввод, +переходит в режим касания. Начиная с этого момента, фокус передается только тем отображаемым объектам, для которых +{@link android.view.View#isFocusableInTouchMode} имеет значение true, таким как виджеты редактирования текста. +Другие отображаемые объекты, которых можно коснуться, например, кнопки, не будут получать фокус при касании. Нажатие будет +просто запускать их приемники событий on-click. +

+

+В любой момент, когда пользователь нажимает клавишу перемещения или выполняет прокрутку трекболом, устройство +выходит из режима касания и находит отображаемый объект, которому передается фокус. Теперь пользователь может возобновить взаимодействие +с пользовательским интерфейсом без касания экрана. +

+

+Состояние режима касания поддерживается во всей системе (для всех окон и операций). +Чтобы узнать текущее состояние, можно вызвать +{@link android.view.View#isInTouchMode} и посмотреть, находится ли устройство в режиме касания. +

+ + +

Фокус обработки

+ +

В ответ на пользовательский ввод система обрабатывает обычное перемещение фокуса. +Сюда относится изменение фокуса, когда отображаемые объекты удаляются или скрываются, а также когда становятся доступными +новые отображаемые объекты. Отображаемые объекты сообщают о своей готовности получить фокус +с помощью метода {@link android.view.View#isFocusable()}. Чтобы изменить способность объекта View получать +фокус, вызовите {@link android.view.View#setFocusable(boolean) setFocusable()}. В режиме касания +можно узнать способность отображаемого объекта View получать фокус с помощью метода {@link android.view.View#isFocusableInTouchMode()}. +Для изменения вызовите {@link android.view.View#setFocusableInTouchMode(boolean) setFocusableInTouchMode()}. +

+ +

Перемещение фокуса основано на алгоритме, который находит ближайший элемент +в заданном направлении. В редких случаях алгоритм по умолчанию может не совпадать +с поведением, которое предполагает разработчик. В этих ситуациях можно задать +явное переопределение с помощью следующих атрибутов XML в файле макета: +nextFocusDown, nextFocusLeft, nextFocusRightи +nextFocusUp. Добавьте один из этих атрибутов в отображаемый объект View, из которого +выходит фокус. Задайте значение атрибута для идентификатора объекта View, +в который следует передать фокус. Например:

+
+<LinearLayout
+    android:orientation="vertical"
+    ... >
+  <Button android:id="@+id/top"
+          android:nextFocusUp="@+id/bottom"
+          ... />
+  <Button android:id="@+id/bottom"
+          android:nextFocusDown="@+id/top"
+          ... />
+</LinearLayout>
+
+ +

Обычно в этом вертикальном макете перемещение вверх из первой кнопки не должно приводить к перемещению, +как и перемещение вниз из второй кнопки. Теперь, когда верхняя кнопка +задает для нижней кнопки атрибут nextFocusUp (и наоборот), фокус будет перемещаться +циклически сверху вниз и снизу вверх.

+ +

Если вы хотите объявить, что отображаемый объект в вашем пользовательском интерфейсе способен получать фокус (тогда как обычно он не может получать фокус), +добавьте XML-атрибут android:focusable в объект View в объявлении макета. +Установите для него значение true. Можно также объявить объект View, +способным получать фокус в режиме касания с помощью android:focusableInTouchMode.

+

Чтобы запросить передачу фокуса определенному отображаемому объекту, вызовите {@link android.view.View#requestFocus()}.

+

Чтобы перехватывать события фокуса (получать уведомления, когда отображаемый объект получает или теряет фокус), используйте метод +{@link android.view.View.OnFocusChangeListener#onFocusChange(View,boolean) onFocusChange()}, +который обсуждается в разделе Приемники событий выше.

+ + + + diff --git a/docs/html-intl/intl/ru/sdk/index.jd b/docs/html-intl/intl/ru/sdk/index.jd new file mode 100644 index 0000000000000000000000000000000000000000..06496a4e8b86b1a4bc075845791b3659f2b74138 --- /dev/null +++ b/docs/html-intl/intl/ru/sdk/index.jd @@ -0,0 +1,487 @@ +page.title=Загрузка Android Studio и инструментов SDK +page.tags=sdk, android studio +page.template=sdk +header.hide=1 +page.metaDescription=Загрузите официальные средства разработки Android для создания приложений для смартфонов, планшетов, носимых устройств, телевизоров и многих других устройств под управлением ОС Android. + +studio.version=1.1.0 + +studio.linux_bundle_download=android-studio-ide-135.1740770-linux.zip +studio.linux_bundle_bytes=259336386 +studio.linux_bundle_checksum=e8d166559c50a484f83ebfec6731cc0e3f259208 + +studio.mac_bundle_download=android-studio-ide-135.1740770-mac.dmg +studio.mac_bundle_bytes=261303345 +studio.mac_bundle_checksum=f9745d0fec1eefd498f6160a2d6a1b5247d4cda3 + +studio.win_bundle_exe_download=android-studio-bundle-135.1740770-windows.exe +studio.win_bundle_exe_bytes=856233768 +studio.win_bundle_exe_checksum=7484b9989d2914e1de30995fbaa97a271a514b3f + +studio.win_notools_exe_download=android-studio-ide-135.1740770-windows.exe +studio.win_notools_exe_bytes=242135128 +studio.win_notools_exe_checksum=5ea77661cd2300cea09e8e34f4a2219a0813911f + +studio.win_bundle_download=android-studio-ide-135.1740770-windows.zip +studio.win_bundle_bytes=261667054 +studio.win_bundle_checksum=e903f17cc6a57c7e3d460c4555386e3e65c6b4eb + + + +sdk.linux_download=android-sdk_r24.1.2-linux.tgz +sdk.linux_bytes=168121693 +sdk.linux_checksum=68980e4a26cca0182abb1032abffbb72a1240c51 + +sdk.mac_download=android-sdk_r24.1.2-macosx.zip +sdk.mac_bytes=89151287 +sdk.mac_checksum=00e43ff1557e8cba7da53e4f64f3a34498048256 + +sdk.win_download=android-sdk_r24.1.2-windows.zip +sdk.win_bytes=159778618 +sdk.win_checksum=704f6c874373b98e061fe2e7eb34f9fcb907a341 + +sdk.win_installer=installer_r24.1.2-windows.exe +sdk.win_installer_bytes=111364285 +sdk.win_installer_checksum=e0ec864efa0e7449db2d7ed069c03b1f4d36f0cd + + + + + + +@jd:body + + + + + + + +
+ + + + + + + + + +
+ +
 
+ + + +
+ +

Android Studio

+ +

Официальная среда разработки Android

+ +
    +
  • Среда разработки Android Studio
  • +
  • Инструменты Android SDK
  • +
  • Платформа Android 5.0 (Lollipop)
  • +
  • Системный образ Android 5.0 с API Google для эмулятора
  • +
+ + +Загрузить Android Studio + + +

+Чтобы загрузить Android Studio или отдельные инструменты SDK, посетите веб-сайт developer.android.com/sdk/ +

+ + + +
+ + + + +

Интеллектуальный редактор программного кода

+ +
+ +
+ +
+

В основе Android Studio лежит интеллектуальный редактор исходного кода, предлагающий такие возможности, как расширенное + автозавершение кода, его реструктуризация и анализ.

+

Этот редактор поможет вам повысить эффективность разработки приложений для Android.

+
+ + + + + +

Шаблоны программного кода и интеграция с GitHub

+ +
+ +
+ +
+

Новые мастеры создания проектов в еще большей степени упрощают работу с новыми проектами.

+ +

Создавая новый проект, используйте шаблоны программного кода для таких общих фрагментов, как элементы для навигации по приложению и представления, +а также импортируйте примеры кода Google прямо из GitHub.

+
+ + + + +

Разработка приложений с поддержкой экранов разного размера

+ +
+ +
+ +
+

Создавайте приложения для смартфонов и планшетов под управлением Android, для Android Wear +, Android TV, Android Auto и даже Google Glass.

+

Благодаря новому представлению Android Project и поддержке модулей в Android Studio вы можете с легкостью +управлять своими проектами и ресурсами. +

+ + + + +

Виртуальные устройства на любой вкус и цвет

+ +
+ +
+ +
+

В состав Android Studio входит оптимизированный эмулятор.

+

Обновленный и оптимизированный диспетчер виртуальных устройств порадует вас широким набором +предварительно настроенных профилей устройств Android.

+
+ + + + +

+Эволюция сборок Android благодаря Gradle

+ +
+ +
+ +
+

Создавайте несколько APK для ваших приложений с различными функциональными возможностями — в рамках одного и того же проекта.

+

Управляйте зависимостями между приложениями с помощью Maven.

+

Создавайте APK в Android Studio или прямо в командной строке.

+
+ + + + +

Подробнее об Android Studio

+
+ +Загрузить Android Studio + +
    +
  • Создано на основе IntelliJ IDEA Community Edition, популярной среды разработки Java от JetBrains.
  • +
  • Гибкая система сборки на основе Gradle.
  • +
  • Различные варианты сборки и методы создания файлов APK.
  • +
  • Расширенная поддержка шаблонов для служб Google и устройств различных типов.
  • +
  • Функциональный редактор макетов с поддержкой редактирования тем оформления.
  • +
  • Инструментарий lint для решения проблем с производительностью, удобством использования, совместимостью версий и т. д.
  • +
  • ProGuard и возможности подписи приложений.
  • +
  • Встроенная поддержка Google Cloud Platform, обеспечивающая удобство интеграции со службами Google Cloud + Messaging и App Engine.
  • +
+ +

+Подробные сведения о возможностях Android Studio +изложены в руководстве Android Studio Basics.

+
+ + +

Если вы используете Eclipse с ADT, вам следует знать, что Android Studio теперь является официальной средой разработки для +Android, поэтому переходите на Android Studio, чтобы всегда иметь под рукой новейшие +инструменты разработки приложений. Сведения о том, как перенести свои проекты, +приведены в документе Переход на Android +Studio.

+ + + + + + + +

Требования к системе

+ +

Windows

+ +
    +
  • Microsoft® Windows® 8/7/Vista/2003 (32- или 64-разрядная версия)
  • +
  • ОЗУ не менее 2 ГБ (рекомендуется 4 ГБ)
  • +
  • 400 МБ свободного места на жестком диске
  • +
  • Не менее 1 ГБ для Android SDK, системных образов эмулятора и кэшированных файлов
  • +
  • Экран с разрешением не менее 1280 x 800 точек
  • +
  • Java Development Kit (JDK) 7
  • +
  • Дополнительно для эмулятора с ускорителем: Процессор Intel® с поддержкой Intel® VT-x, Intel® EM64T +(Intel® 64) и функцией XD-Bit
  • +
+ + +

Mac OS X

+ +
    +
  • Mac® OS X® 10.8.5 или более поздней версии вплоть до 10.9 (Mavericks)
  • +
  • ОЗУ не менее 2 ГБ (рекомендуется 4 ГБ)
  • +
  • 400 МБ свободного места на жестком диске
  • +
  • Не менее 1 ГБ для Android SDK, системных образов эмулятора и кэшированных файлов
  • +
  • Экран с разрешением не менее 1280 x 800 точек
  • +
  • Java Runtime Environment (JRE) 6
  • +
  • Java Development Kit (JDK) 7
  • +
  • Дополнительно для эмулятора с ускорителем: Процессор Intel® с поддержкой Intel® VT-x, Intel® EM64T +(Intel® 64) и функцией XD-Bit
  • +
+ +

В Mac OS для оптимизации отображения шрифтов Android Studio рекомендуется запускать с +Java Runtime Environment (JRE) 6. Впоследствии проекты можно настроить на использование Java Development Kit (JDK) 6 или JDK 7.

+ + + +

Linux

+ +
    +
  • Рабочий стол GNOME или KDE
  • +
  • Библиотека GNU C (glibc) 2.15 или более поздней версии
  • +
  • ОЗУ не менее 2 ГБ (рекомендуется 4 ГБ)
  • +
  • 400 МБ свободного места на жестком диске
  • +
  • Не менее 1 ГБ для Android SDK, системных образов эмулятора и кэшированных файлов
  • +
  • Экран с разрешением не менее 1280 x 800 точек
  • +
  • Oracle® Java Development Kit (JDK) 7
  • +
+

Работа Android Studio протестирована в ОС Ubuntu® 14.04, Trusty Tahr (64-разрядная версия с поддержкой запуска +32-разрядных приложений).

+ + + + +

Другие варианты загрузки

+ + diff --git a/docs/html-intl/intl/ru/sdk/installing/adding-packages.jd b/docs/html-intl/intl/ru/sdk/installing/adding-packages.jd new file mode 100644 index 0000000000000000000000000000000000000000..81bfe1804e720baa395d5df481afc8fe76249e8f --- /dev/null +++ b/docs/html-intl/intl/ru/sdk/installing/adding-packages.jd @@ -0,0 +1,227 @@ +page.title=Добавление пакетов SDK + +page.tags=менеджер sdk +helpoutsWidget=true + +@jd:body + + + + +

+По умолчанию SDK Android включает в себя не все инструменты, которые необходимы для того, чтобы приступить к разработке приложений. +Инструменты, платформы и другие компоненты представлены в Android SDK в виде отдельных пакетов, которые при +необходимости можно загрузить с помощью +менеджера SDK Android. +Поэтому, прежде чем приступить к работе, в пакет SDK Android необходимо добавить некоторые дополнительные пакеты.

+ +

Для добавления пакетов необходимо запустить менеджер SDK. Существует несколько способов запуска менеджера.

+
    +
  • В Android Studio щелкните элемент SDK Manager + на панели инструментов.
  • +
  • Для тех, кто не пользуется Android Studio: +
      +
    • Windows: Дважды щелкните файл SDK Manager.exe, который находится в корневом каталоге пакета Android +SDK.
    • +
    • Mac/Linux: Откройте окно терминала и перейдите в каталог tools/ пакета Android +SDK, после чего выполните команду android sdk.
    • +
    +
  • +
+ +

При первом запуске менеджера SDK по умолчанию +выбраны всего несколько пакетов. Оставьте выбор по умолчанию, однако убедитесь в том, что в них имеется все необходимое +для начала работы. Для этого выполните указанные ниже действия.

+ + +
    +
  1. +

    Загрузите актуальные инструменты SDK

    + + + +

    Во время установки SDK Android +необходимо как минимум загрузить актуальные инструменты и платформу Android.

    +
      +
    1. Откройте каталог Tools и выберите следующее: +
        +
      • Инструменты Android SDK;
      • +
      • Инструменты платформы Android SDK;
      • +
      • Инструменты сборки Android SDK (последнюю версию).
      • +
      +
    2. +
    3. Откройте первую папку Android X.X (последней версии) и выберите следующее: +
        +
      • Платформа SDK
      • +
      • системный образ для эмулятора, например
        + ARM EABI v7a System Image.
      • +
      +
    4. +
    +
  2. + +
  3. +

    Загрузите вспомогательную библиотеку для дополнительных API-интерфейсов

    + + + +

    Во вспомогательной библиотеке Android +представлен широкий набор API-интерфейсов, которые совместимы с большинством версий ОС Android.

    + +

    Откройте каталог Extras (Дополнения) и выберите следующее:

    +
      +
    • Android Support Repository (Репозиторий вспомогательных библиотек Android);
    • +
    • Вспомогательная библиотека Android
    • +
    + +

     

    +

     

    + +
  4. + + +
  5. +

    Загрузите службы Google Play, чтобы получить доступ к еще большему количеству API-интерфейсов

    + + + +

    Для разработки приложений с помощью API-интерфейсов Google вам потребуется пакет служб Google Play.

    +

    Откройте каталог Extras (Дополнения) и выберите следующее:

    +
      +
    • Google Repository (Репозиторий Google);
    • +
    • Google Play services (службы Google Play).
    • +
    + +

    Примечание. API-интерфейсы служб Google Play доступны не на всех устройствах +Android, однако предлагаются на всех устройствах с доступом к магазину Google Play. Для использования +этих API-интерфейсов в эмуляторе Android необходимо также установить системный образ API-интерфейсов Google, +который находится в менеджере SDK в папке актуальной версии Android X.X.

    +
  6. + + +
  7. +

    Установите пакеты

    +

    После выбора всех необходимых пакетов можно продолжить установку.

    +
      +
    1. Нажмите кнопку Install X packages.
    2. +
    3. В появившемся окне дважды щелкните имя каждого пакета, находящегося в области слева, +чтобы принять условия лицензии для каждого из них.
    4. +
    5. Нажмите кнопку Install.
    6. +
    +

    В нижней части окна менеджера SDK находится индикатор загрузки. + Не закрывайте менеджер SDK, поскольку это приведет к отмене процесса загрузки.

    +
  8. + +
  9. +

    Приступайте к созданию приложений

    + +

    После загрузки необходимых пакетов в SDK Android вы можете приступать к созданию приложений для +Android. По мере выхода новых инструментов и других API-интерфейсов просто запустите менеджер SDK +и загрузите новые пакеты.

    + +

    Вот некоторые варианты того, как можно приступить к работе:

    + +
    +
    +

    Для новичков

    +

    Если вы делаете только первые шаги в разработке приложений Android, рекомендуем ознакомиться с основами приложений Android и обратиться к +руководству по созданию своего первого приложения.

    + +
    +
    +

    Создание приложений для носимых устройств

    +

    Если вы готовы приступить к созданию приложений для носимых устройств Android, ознакомьтесь с руководством по +созданию приложений для ОС Android Wear.

    + +
    +
    +

    Использование API-интерфейсов Google

    +

    Чтобы начать работу с API-интерфейсами Google, такими как Карты или +службы Google Play, рекомендуем обратиться к руководству по +настройке служб Google +Play.

    + +
    +
    + + +
  10. + +
+ + diff --git a/docs/html-intl/intl/vi/guide/components/activities.jd b/docs/html-intl/intl/vi/guide/components/activities.jd new file mode 100644 index 0000000000000000000000000000000000000000..83e7669a7f7676028c73e69744a67ca905a7e419 --- /dev/null +++ b/docs/html-intl/intl/vi/guide/components/activities.jd @@ -0,0 +1,756 @@ +page.title=Các hoạt động +page.tags=hoạt động,ý định +@jd:body + + + + + +

{@link android.app.Activity} là một thành phần ứng dụng cung cấp một màn hình mà với nó +người dùng có thể tương tác để thực hiện một điều gì đó, chẳng hạn như quay số điện thoại, chụp ảnh, gửi e-mail hoặc +xem bản đồ. Mỗi hoạt động được cho trong một cửa sổ là nơi để vẽ giao diện người dùng của nó. Cửa sổ này +thường lấp đầy màn hình, nhưng có thể nhỏ hơn màn hình và nổi bên trên các cửa sổ +khác.

+ +

Ứng dụng thường bao gồm nhiều hoạt động được liên kết lỏng lẻo +với nhau. Thường thì một hoạt động trong một ứng dụng sẽ được quy định là hoạt động "chính", nó được +trình bày trước người dùng khi khởi chạy ứng dụng lần đầu. Sau đó, mỗi +hoạt động có thể bắt đầu một hoạt động khác để thực hiện các hành động khác nhau. Mỗi khi một hoạt động +mới bắt đầu, hoạt động trước đó sẽ bị dừng lại, nhưng hệ thống vẫn giữ nguyên hoạt động +trong một ngăn xếp ("back stack"). Khi một hoạt động mới bắt đầu, nó được đẩy lên ngăn xếp và +chiếm lấy tiêu điểm của người dùng. Ngăn xếp sẽ tuân theo cơ chế xếp chồng cơ bản "vào cuối, ra đầu", +vì thế, khi người dùng kết thúc hoạt động hiện tại và nhấn nút Quay lại, nó +sẽ được đẩy ra khỏi ngăn xếp (và bị hủy) và hoạt động trước đó sẽ tiếp tục. (Ngăn xếp được +đề cập kỹ hơn trong tài liệu Tác vụ +và Ngăn Xếp.)

+ +

Khi một hoạt động bị dừng vì một hoạt động mới bắt đầu, nó được thông báo về sự thay đổi trạng thái này +qua các phương pháp gọi lại vòng đời của hoạt động. +Có một vài phương pháp gọi lại vòng đời mà một hoạt động có thể nhận, do một thay đổi về +trạng thái của nó—dù hệ thống đang tạo, dừng hay tiếp tục nó, hay hủy nó—và +mỗi lần gọi lại cho bạn cơ hội thực hiện công việc cụ thể +phù hợp với sự thay đổi trạng thái đó. Ví dụ, khi bị dừng, hoạt động của bạn sẽ giải phóng mọi +đối tượng lớn, chẳng hạn như các kết nối mạng hoặc cơ sở dữ liệu. Khi hoạt động tiếp tục, bạn có thể +thu lại những tài nguyên cần thiết và tiếp tục những hành động bị gián đoạn. Những chuyển tiếp trạng thái này +đều là một phần của vòng đời hoạt động.

+ +

Phần còn lại của tài liệu này bàn đến những nội dung cơ bản về cách xây dựng và sử dụng một hoạt động, +bao gồm một nội dung đề cập đầy đủ về cách vận hành của vòng đời hoạt động, để bạn có thể quản lý tốt +sự chuyển tiếp giữa các trạng thái hoạt động khác nhau.

+ + + +

Tạo một Hoạt động

+ +

Để tạo một hoạt động, bạn phải tạo một lớp con của {@link android.app.Activity} (hoặc +một lớp con hiện tại của nó). Trong lớp con của mình, bạn cần triển khai các phương pháp gọi lại mà hệ thống +gọi khi hoạt động chuyển tiếp giữa các trạng thái khác nhau trong vòng đời, chẳng hạn như khi +hoạt động đang được tạo, dừng, tiếp tục, hoặc hủy. Hai phương pháp gọi lại quan trọng nhất +là:

+ +
+
{@link android.app.Activity#onCreate onCreate()}
+
Bạn phải triển khai phương pháp này. Hệ thống gọi phương pháp này khi tạo hoạt động +của bạn. Trong quá trình thực hiện của mình, bạn nên khởi chạy những thành phần thiết yếu cho hoạt động +của mình. + Quan trọng nhất, đây là lúc bạn phải gọi {@link android.app.Activity#setContentView + setContentView()} để định nghĩa bố trí cho giao diện người dùng của hoạt động.
+
{@link android.app.Activity#onPause onPause()}
+
Hệ thống gọi phương pháp này là dấu hiệu đầu tiên về việc người dùng đang rời khỏi hoạt động +của bạn (mặc dù không phải lúc nào cũng có nghĩa rằng hoạt động đang bị hủy). Trường hợp này thường là khi bạn +định thực hiện bất kỳ thay đổi nào vẫn cần có hiệu lực ngoài phiên của người dùng hiện thời (vì +người dùng có thể không quay lại).
+
+ +

Có một vài phương pháp gọi lại vòng đời khác mà bạn nên sử dụng để đem đến +một trải nghiệm người dùng mượt mà giữa các hoạt động và xử lý những gián đoạn bất ngờ khiến hoạt động của bạn +bị dừng và thậm chí bị hủy. Tất cả phương pháp gọi lại vòng đời được bàn sau trong phần +nói về Quản lý Vòng đời của Hoạt động.

+ + + +

Triển khai một giao diện người dùng

+ +

Giao diện người dùng cho một hoạt động sẽ được cung cấp theo phân cấp dạng xem—đối tượng được suy ra +từ lớp {@link android.view.View}. Mỗi chế độ xem kiểm soát một không gian chữ nhật riêng +trong cửa sổ của hoạt động và có thể phản hồi trước tương tác của người dùng. Ví dụ, chế độ xem có thể là +một nút khởi xướng một hành động khi người dùng chạm vào nó.

+ +

Android cung cấp nhiều chế độ xem sẵn có mà bạn có thể sử dụng để thiết kế và tổ chức cho bố trí +của mình. "Widget" là những chế độ xem cung cấp những phần tử trực quan (và tương tác) cho màn hình, chẳng hạn như +nút, trường văn bản, hộp kiểm, hay chỉ là một hình ảnh. "Bố trí" là những chế độ xem được suy ra từ {@link +android.view.ViewGroup} cung cấp một mô hình bố trí duy nhất cho các chế độ xem con của nó, chẳng hạn như bố trí +tuyến tính, bố trí lưới, hoặc bố trí tương đối. Bạn cũng có thể chia thành lớp con {@link android.view.View} và các lớp +{@link android.view.ViewGroup} (hoặc các lớp con hiện tại) để tạo widget và +bố trí của chính mình và áp dụng chúng vào bố trí hoạt động của bạn.

+ +

Cách phổ biến nhất để định nghĩa một bố trí bằng cách sử dụng các chế độ xem là dùng một tệp bố trí XML được lưu trong tài nguyên ứng dụng +của bạn. Bằng cách này, bạn có thể duy trì thiết kế giao diện người dùng của mình độc lập với +mã nguồn định nghĩa hành vi của hoạt động. Bạn có thể đặt bố trí làm UI cho hoạt động +của mình bằng {@link android.app.Activity#setContentView(int) setContentView()}, chuyển +ID tài nguyên cho bố trí. Tuy nhiên, bạn cũng có thể tạo {@link android.view.View} mới trong mã hoạt động +của mình và xây dựng một cấp bậc chế độ xem bằng cách chèn các {@link +android.view.View} mới vào một {@link android.view.ViewGroup}, sau đó sử dụng bố trí đó bằng cách chuyển root +{@link android.view.ViewGroup} sang {@link android.app.Activity#setContentView(View) +setContentView()}.

+ +

Để biết thông tin về việc tạo một giao diện người dùng, hãy xem tài liệu Giao diện Người dùng.

+ + + +

Khai báo hoạt động trong bản kê khai

+ +

Bạn phải khai báo hoạt động của mình trong tệp bản kê khai để hoạt động +có thể truy cập được vào hệ thống. Để khai báo hoạt động của mình, hãy mở tệp bản kê khai của bạn và thêm một phần tử {@code <activity>} +làm con của phần tử {@code <application>} +. Ví dụ:

+ +
+<manifest ... >
+  <application ... >
+      <activity android:name=".ExampleActivity" />
+      ...
+  </application ... >
+  ...
+</manifest >
+
+ +

Có vài thuộc tính khác mà bạn có thể nêu trong phần tử này, để định nghĩa các thuộc tính +như nhãn cho hoạt động, biểu tượng cho hoạt động, hoặc chủ đề mô tả kiểu UI của +hoạt động. Thuộc tính {@code android:name} +là thuộc tính bắt buộc duy nhất—nó quy định tên lớp của hoạt động. Một khi +bạn phát hành ứng dụng của mình, bạn không nên thay đổi tên này, vì nếu bạn làm vậy, bạn có thể làm hỏng +một số tính năng, chẳng hạn như các lối tắt của ứng dụng (hãy đọc bài đăng trên blog, Những Điều +Không Thay Đổi Được).

+ +

Xem tài liệu tham khảo phần tử {@code <activity>} +để biết thêm thông tin về việc khai báo hoạt động của bạn trong bản kê khai.

+ + +

Sử dụng các bộ lọc ý định

+ +

Một phần tử {@code +<activity>} cũng có thể quy định các bộ lọc ý định khác nhau—bằng cách sử dụng phần tử {@code +<intent-filter>} —để khai báo cách thức mà các thành phần khác của ứng dụng có thể +kích hoạt nó.

+ +

Khi bạn tạo một ứng dụng mới bằng cách sử dụng các công cụ SDK của Android, hoạt động chương trình nhỏ +được tạo cho bạn sẽ tự động bao gồm một bộ lọc ý định khai báo hoạt động +phản hồi lại hành động "chính" và nên được đặt trong thể loại "trình khởi chạy". Bộ lọc ý định +trông như thế này:

+ +
+<activity android:name=".ExampleActivity" android:icon="@drawable/app_icon">
+    <intent-filter>
+        <action android:name="android.intent.action.MAIN" />
+        <category android:name="android.intent.category.LAUNCHER" />
+    </intent-filter>
+</activity>
+
+ +

Phần tử {@code +<action>} quy định rằng đây là điểm mục nhập "chính" đối với ứng dụng. Phần tử {@code +<category>} quy định rằng hoạt động này nên được liệt kê trong trình khởi chạy ứng dụng của hệ thống +(để cho phép người dùng khởi chạy hoạt động này).

+ +

Nếu bạn có ý định cho ứng dụng của mình được độc lập và không cho phép các ứng dụng khác +kích hoạt các hoạt động của nó, vậy bạn không cần bất kỳ bộ lọc ý định nào khác. Chỉ một hoạt động nên có +hành động "chính" và thể loại "trình khởi chạy" như trong ví dụ trước. Những hoạt động mà +bạn không muốn cung cấp sẵn cho các ứng dụng khác không nên có bộ lọc ý định và bạn có thể +tự mình bắt đầu chúng bằng cách sử dụng các ý định rõ ràng (như được đề cập trong phần sau).

+ +

Tuy nhiên, nếu bạn muốn hoạt động của mình phản hồi lại những ý định ngầm mà được chuyển giao từ +các ứng dụng khác (và chính bạn), thì bạn phải định nghĩa các bộ lọc ý định bổ sung cho hoạt động +của mình. Với mỗi loại ý định mà bạn muốn phản hồi, bạn phải nêu một {@code +<intent-filter>} bao gồm một phần tử +{@code +<action>} và, không bắt buộc, một phần tử {@code +<category>} và/hoặc một phần tử {@code +<data>}. Những phần tử này quy định loại ý định mà hoạt động của bạn có thể +phản hồi.

+ +

Để biết thêm thông tin về cách thức các hoạt động của bạn có thể phản hồi lại ý định, hãy xem tài liệu Ý định và Bộ lọc Ý định +.

+ + + +

Bắt đầu một Hoạt động

+ +

Bạn có thể bắt đầu một hoạt động khác bằng cách gọi {@link android.app.Activity#startActivity + startActivity()}, chuyển cho nó một {@link android.content.Intent} mà mô tả hoạt động bạn +muốn bắt đầu. Ý định này sẽ quy định hoặc hoạt động chính xác mà bạn muốn bắt đầu hoặc mô tả + loại hành động mà bạn muốn thực hiện (và hệ thống lựa chọn hoạt động phù hợp cho bạn, +thậm chí +có thể từ một ứng dụng khác). Một ý định cũng có thể mang theo lượng nhỏ dữ liệu sẽ được + sử dụng bởi hoạt động được bắt đầu.

+ +

Khi đang làm việc trong ứng dụng của chính mình, bạn thường sẽ cần khởi chạy một hoạt động đã biết. + Bạn có thể làm vậy bằng cách tạo một ý định trong đó quy định rõ hoạt động bạn muốn bắt đầu, +sử dụng tên lớp đó. Ví dụ, sau đây là cách một hoạt động bắt đầu một hoạt động khác có tên {@code +SignInActivity}:

+ +
+Intent intent = new Intent(this, SignInActivity.class);
+startActivity(intent);
+
+ +

Tuy nhiên, ứng dụng của bạn cũng có thể muốn thực hiện một số hành động, chẳng hạn như gửi một e-mail, tin nhắn + văn bản, hoặc cập nhật trạng thái, bằng cách sử dụng dữ liệu từ hoạt động của bạn. Trong trường hợp này, ứng dụng của bạn có thể + không có các hoạt động của chính nó để thực hiện những hành động đó, vì vậy, thay vào đó, bạn có thể tận dụng những hoạt động + được cung cấp bởi các ứng dụng khác trên thiết bị mà có thể thực hiện hành động cho bạn. Đây là lúc +ý định thực sự có giá trị—bạn có thể tạo một ý định mô tả một hành động bạn muốn +thực hiện và hệ thống + sẽ khởi chạy hoạt động phù hợp đó từ một ứng dụng khác. Nếu có + nhiều hoạt động mà có thể xử lý ý định, vậy người dùng có thể chọn hoạt động nào sẽ sử dụng. Ví + dụ, nếu bạn muốn cho phép người dùng gửi e-mail, bạn có thể tạo + ý định sau:

+ +
+Intent intent = new Intent(Intent.ACTION_SEND);
+intent.putExtra(Intent.EXTRA_EMAIL, recipientArray);
+startActivity(intent);
+
+ +

{@link android.content.Intent#EXTRA_EMAIL} phụ được thêm vào ý định là một mảng xâu của + các địa chỉ e-mail mà e-mail sẽ được gửi tới. Khi một ứng dụng e-mail phản hồi + ý định này, nó đọc mảng xâu được cung cấp trong phần phụ và đặt nó vào trường "đến" của mẫu soạn thảo + e-mail. Trong trường hợp này, hoạt động của ứng dụng e-mail bắt đầu và khi người dùng + làm xong, hoạt động của bạn sẽ tiếp tục.

+ + + + +

Bắt đầu một hoạt động cho một kết quả

+ +

Đôi khi bạn có thể muốn nhận được một kết quả từ hoạt động mà bạn bắt đầu. Trong trường hợp đó, +hãy bắt đầu hoạt động bằng cách gọi {@link android.app.Activity#startActivityForResult + startActivityForResult()} (thay vì {@link android.app.Activity#startActivity + startActivity()}). Rồi để nhận được kết quả từ hoạt động +sau đó, hãy triển khai phương pháp gọi lại {@link android.app.Activity#onActivityResult onActivityResult()} +. Khi hoạt động sau đó diễn ra xong, nó trả về một kết quả trong một {@link +android.content.Intent} cho phương pháp {@link android.app.Activity#onActivityResult onActivityResult()} +của bạn.

+ +

Ví dụ, bạn có thể muốn người dùng chọn một trong các liên lạc của họ, vì vậy hoạt động của bạn có thể +làm gì đó với thông tin trong liên lạc đó. Đây là cách bạn có thể tạo một ý định như vậy và +xử lý kết quả:

+ +
+private void pickContact() {
+    // Create an intent to "pick" a contact, as defined by the content provider URI
+    Intent intent = new Intent(Intent.ACTION_PICK, Contacts.CONTENT_URI);
+    startActivityForResult(intent, PICK_CONTACT_REQUEST);
+}
+
+@Override
+protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+    // If the request went well (OK) and the request was PICK_CONTACT_REQUEST
+    if (resultCode == Activity.RESULT_OK && requestCode == PICK_CONTACT_REQUEST) {
+        // Perform a query to the contact's content provider for the contact's name
+        Cursor cursor = getContentResolver().query(data.getData(),
+        new String[] {Contacts.DISPLAY_NAME}, null, null, null);
+        if (cursor.moveToFirst()) { // True if the cursor is not empty
+            int columnIndex = cursor.getColumnIndex(Contacts.DISPLAY_NAME);
+            String name = cursor.getString(columnIndex);
+            // Do something with the selected contact's name...
+        }
+    }
+}
+
+ +

Ví dụ này thể hiện lô-gic cơ bản mà bạn sẽ sử dụng trong phương pháp {@link +android.app.Activity#onActivityResult onActivityResult()} của mình để xử lý một +kết quả hoạt động. Điều kiện đầu tiên kiểm tra xem yêu cầu có thành công không—nếu có thì +{@code resultCode} sẽ là {@link android.app.Activity#RESULT_OK}—và liệu yêu cầu +mà kiểm tra này đang phản hồi có được biết hay không—trong trường hợp này, {@code requestCode} phù hợp với +tham số thứ hai được gửi bằng {@link android.app.Activity#startActivityForResult +startActivityForResult()}. Từ đó, mã xử lý kết quả hoạt động bằng cách truy vấn +dữ liệu được trả về trong {@link android.content.Intent} (tham số {@code data}).

+ +

Điều xảy ra đó là, {@link +android.content.ContentResolver} sẽ thực hiện một truy vấn đối với nhà cung cấp nội dung, truy vấn này trả về một +{@link android.database.Cursor} cho phép đọc dữ liệu được truy vấn. Để biết thêm thông tin, hãy xem tài liệu +Trình cung cấp Nội dung.

+ +

Để biết thêm thông tin về việc sử dụng ý định, hãy xem tài liệu Ý định và Bộ lọc +Ý định.

+ + +

Tắt một Hoạt động

+ +

Bạn có thể tắt một hoạt động bằng cách gọi phương pháp {@link android.app.Activity#finish +finish()} của nó. Bạn cũng có thể tắt một hoạt động riêng mà trước đó bạn đã bắt đầu bằng cách gọi +{@link android.app.Activity#finishActivity finishActivity()}.

+ +

Lưu ý: Trong hầu hết trường hợp, bạn không nên kết thúc một hoạt động một cách rõ ràng +bằng cách sử dụng những phương pháp này. Như đề cập trong phần sau về vòng đời của hoạt động, hệ thống +Android quản lý tuổi thọ của một hoạt động cho bạn, vì vậy bạn không cần kết thúc các hoạt động +của chính mình. Việc gọi những phương pháp này có thể ảnh hưởng tiêu cực tới trải nghiệm người dùng +kỳ vọng và chỉ nên được sử dụng khi bạn tuyệt đối không muốn người dùng quay lại thực thể này của +hoạt động.

+ + +

Quản lý Vòng đời của Hoạt động

+ +

Việc quản lý vòng đời các hoạt động của bạn bằng cách triển khai các phương pháp gọi lại +rất quan trọng đối với việc xây dựng một ứng dụng mạnh +và linh hoạt. Vòng đời của một hoạt động trực tiếp bị ảnh hưởng bởi sự liên kết giữa nó với +các hoạt động khác, tác vụ của nó và ngăn xếp (back stack).

+ +

Về cơ bản, một hoạt động có thể tồn tại ở ba trạng thái:

+ +
+
Tiếp tục
+
Hoạt động ở tiền cảnh của màn hình và có tiêu điểm của người dùng. (Trạng thái này +đôi khi cũng được gọi là "đang chạy".)
+ +
Tạm dừng
+
Một hoạt động khác ở tiền cảnh và có tiêu điểm, nhưng hoạt động này vẫn hiển thị. Cụ thể, +một hoạt động khác hiển thị ở trên hoạt động này và hoạt động đó trong suốt một phần hoặc không +che toàn bộ màn hình. Trạng thái tạm dừng hoàn toàn đang hoạt động (đối tượng {@link android.app.Activity} +được giữ lại trong bộ nhớ, nó duy trì tất cả thông tin về trạng thái và thành viên, và vẫn gắn với +trình quản lý cửa sổ), nhưng có thể bị hệ thống tắt bỏ trong trường hợp bộ nhớ cực kỳ thấp.
+ +
Dừng
+
Hoạt động bị che khuất hoàn toàn bởi một hoạt động khác (hoạt động hiện đang +“dưới nền"). Hoạt động dừng cũng vẫn đang hoạt động ({@link android.app.Activity} +đối tượng được giữ lại trong bộ nhớ, nó duy trì tất cả thông tin về trạng thái và thành viên, nhưng không +gắn với trình quản lý cửa sổ). Tuy nhiên, hoạt động không còn hiển thị với người dùng nữa và hệ thống +có thể tắt bỏ hoạt động này khi cần bộ nhớ ở nơi khác.
+
+ +

Nếu một hoạt động bị tạm dừng hoặc dừng, hệ thống có thể bỏ nó khỏi bộ nhớ hoặc bằng cách yêu cầu nó +kết thúc (gọi phương pháp {@link android.app.Activity#finish finish()} của nó), hoặc đơn giản là tắt bỏ tiến trình +của hoạt động. Khi hoạt động được mở lại (sau khi bị kết thúc hoặc tắt bỏ), nó phải được tạo +lại hoàn toàn.

+ + + +

Triển khai gọi lại vòng đời

+ +

Khi một hoạt động chuyển tiếp vào ra các trạng thái khác nhau nêu trên, nó được thông báo +thông qua các phương pháp gọi lại. Tất cả phương pháp gọi lại đều là những móc (hook) mà bạn +có thể khống chế để làm công việc phù hợp khi trạng thái hoạt động của bạn thay đổi. Hoạt động khung sau +bao gồm từng phương pháp trong các phương pháp vòng đời cơ bản:

+ + +
+public class ExampleActivity extends Activity {
+    @Override
+    public void {@link android.app.Activity#onCreate onCreate}(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        // The activity is being created.
+    }
+    @Override
+    protected void {@link android.app.Activity#onStart onStart()} {
+        super.onStart();
+        // The activity is about to become visible.
+    }
+    @Override
+    protected void {@link android.app.Activity#onResume onResume()} {
+        super.onResume();
+        // The activity has become visible (it is now "resumed").
+    }
+    @Override
+    protected void {@link android.app.Activity#onPause onPause()} {
+        super.onPause();
+        // Another activity is taking focus (this activity is about to be "paused").
+    }
+    @Override
+    protected void {@link android.app.Activity#onStop onStop()} {
+        super.onStop();
+        // The activity is no longer visible (it is now "stopped")
+    }
+    @Override
+    protected void {@link android.app.Activity#onDestroy onDestroy()} {
+        super.onDestroy();
+        // The activity is about to be destroyed.
+    }
+}
+
+ +

Lưu ý: Việc bạn triển khai những phương pháp vòng đời này phải luôn +gọi triển khai siêu lớp trước khi làm bất kỳ công việc nào, như minh họa trong các ví dụ bên trên.

+ +

Cùng nhau, những phương pháp này định nghĩa toàn bộ vòng đời của một hoạt động. Bằng việc triển khai những phương pháp +này, bạn có thể theo dõi ba vòng lặp lồng nhau trong vòng đời của hoạt động:

+ +
    +
  • Toàn bộ vòng đời của một hoạt động sẽ xảy ra từ thời điểm lệnh gọi đến {@link +android.app.Activity#onCreate onCreate()} cho tới thời điểm lệnh gọi đến {@link +android.app.Activity#onDestroy}. Hoạt động của bạn nên thực hiện thiết lập +trạng thái "chung" (chẳng hạn như định nghĩa bố trí) trong {@link android.app.Activity#onCreate onCreate()}, và +giải phóng tất cả tài nguyên còn lại trong {@link android.app.Activity#onDestroy}. Ví dụ, nếu hoạt động của bạn +có một luồng đang chạy ngầm để tải xuống dữ liệu từ mạng, nó có thể tạo +luồng đó trong {@link android.app.Activity#onCreate onCreate()} rồi dừng luồng trong {@link +android.app.Activity#onDestroy}.
  • + +
  • Vòng đời hiển thị của một hoạt động xảy ra từ thời điểm lệnh gọi đến {@link +android.app.Activity#onStart onStart()} cho tới lệnh gọi đến {@link +android.app.Activity#onStop onStop()}. Trong thời gian này, người dùng có thể thấy hoạt động +trên màn hình và tương tác với nó. Ví dụ, {@link android.app.Activity#onStop onStop()} được gọi +khi một hoạt động mới bắt đầu và không còn hiển thị nữa. Giữa hai phương pháp này, bạn có thể +duy trì các tài nguyên cần để cho người dùng thấy hoạt động. Ví dụ, bạn có thể đăng ký một +{@link android.content.BroadcastReceiver} trong {@link +android.app.Activity#onStart onStart()} để theo dõi các thay đổi tác động tới UI của mình, và bỏ đăng ký +nó trong {@link android.app.Activity#onStop onStop()} khi người dùng không còn thấy thứ bạn đang +hiển thị nữa. Hệ thống có thể gọi {@link android.app.Activity#onStart onStart()} và {@link +android.app.Activity#onStop onStop()} nhiều lần trong suốt vòng đời của hoạt động, khi đó +hoạt động luân chuyển giữa trạng thái hiển thị và ẩn với người dùng.

  • + +
  • Vòng đời ở tiền cảnh của một hoạt động xảy ra từ thời điểm lệnh gọi đến {@link +android.app.Activity#onResume onResume()} cho tới thời điểm lệnh gọi đến {@link android.app.Activity#onPause +onPause()}. Trong thời gian này, hoạt động sẽ ở phía trước tất cả hoạt động khác trên màn hình và có +tiêu điểm đầu vào của người dùng. Hoạt động có thể thường xuyên chuyển tiếp vào và ra tiền cảnh—ví +dụ, {@link android.app.Activity#onPause onPause()} được gọi khi thiết bị vào trạng thái ngủ hoặc +khi một hộp thoại xuất hiện. Vì trạng thái này có thể chuyển tiếp thường xuyên, mã trong hai phương pháp này nên +tương đối nhẹ để tránh chuyển tiếp chậm khiến người dùng phải đợi.

  • +
+ +

Hình 1 minh họa những vòng lặp này và các đường dẫn mà một hoạt động có thể diễn ra giữa các trạng thái. +Hình chữ nhật đại diện cho các phương pháp gọi lại bạn có thể triển khai để thực hiện thao tác khi +hoạt động chuyển tiếp giữa những trạng thái này.

+ + +

Hình 1. Vòng đời của hoạt động.

+ +

Những phương pháp gọi lại vòng đời này cũng được liệt kê trong bảng 1, trong đó mô tả từng phương pháp +gọi lại một cách chi tiết hơn và xác định từng phương pháp +trong vòng đời tổng thể của hoạt động, bao gồm việc hệ thống có thể tắt bỏ hoạt động hay không sau khi +phương pháp gọi lại hoàn tất.

+ +

Bảng 1. Tóm tắt các phương pháp gọi lại +trong vòng đời của hoạt động.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Phương pháp Mô tả Có thể tắt bỏ sau? Tiếp theo
{@link android.app.Activity#onCreate onCreate()}Được gọi khi hoạt động mới được tạo. + Đây là lúc bạn nên thực hiện tất cả thiết lập cố định thông thường của mình — + tạo chế độ xem, kết ghép dữ liệu với danh sách, v.v. Phương pháp này được chuyển cho + một đối tượng Gói chứa trạng thái trước đây của hoạt động, nếu trạng thái + đó được thu lại (xem phần Lưu Trạng thái Hoạt động, + ở đoạn sau). +

Luôn được theo sau bởi {@code onStart()}.

Không{@code onStart()}
    {@link android.app.Activity#onRestart +onRestart()}Được gọi sau khi hoạt động đã được dừng, ngay trước khi hoạt động được + bắt đầu lại. +

Luôn được theo sau bởi {@code onStart()}

Không{@code onStart()}
{@link android.app.Activity#onStart onStart()}Được gọi ngay trước khi hoạt động hiển thị trước người dùng. +

Được theo sau bởi {@code onResume()} nếu hoạt động vào + tiền cảnh, hoặc {@code onStop()} nếu hoạt động bị ẩn.

Không{@code onResume()}
hoặc
{@code onStop()}
    {@link android.app.Activity#onResume onResume()}Được gọi ngay trước khi hoạt động bắt đầu + tương tác với người dùng. Tại điểm này, hoạt động nằm ở + trên cùng của chồng hoạt động, trong đó mục nhập của người dùng sẽ đến hoạt động này. +

Luôn được theo sau bởi {@code onPause()}.

Không{@code onPause()}
{@link android.app.Activity#onPause onPause()}Được gọi khi hệ thống sắp bắt đầu tiếp tục một hoạt động + khác. Phương pháp này thường được sử dụng để thực hiện các thay đổi chưa lưu cho + dữ liệu liên tục, dừng các hoạt ảnh và những việc khác mà có thể tiêu tốn công suất + CPU, v.v. Nó sẽ thực hiện rất nhanh, vì + hoạt động tiếp theo sẽ không được tiếp tục tới khi nó trở lại. +

Được theo sau hoặc bởi {@code onResume()} nếu hoạt động + trở lại phía trước, hoặc bởi {@code onStop()} nếu nó + không hiển thị với người dùng.

{@code onResume()}
hoặc
{@code onStop()}
{@link android.app.Activity#onStop onStop()}Được gọi khi hoạt động không còn hiển thị với người dùng. Điều này + có thể xảy ra vì nó đang bị hủy, hoặc vì một hoạt động khác + (đang tồn tại hoặc mới) đã được tiếp tục và đang che khuất nó. +

Được theo sau hoặc bởi {@code onRestart()} nếu + hoạt động đang quay lại để tương tác với người dùng, hoặc bởi + {@code onDestroy()} nếu hoạt động này sẽ đi mất.

{@code onRestart()}
hoặc
{@code onDestroy()}
{@link android.app.Activity#onDestroy +onDestroy()}Được gọi trước khi hoạt động bị hủy. Đây là lần gọi cuối cùng + mà hoạt động sẽ nhận được. Nên gọi nó hoặc vì + hoạt động đang kết thúc (ai đó đã gọi {@link android.app.Activity#finish + finish()} trên nó), hoặc vì hệ thống đang tạm thời hủy thực thể này của + hoạt động để tiết kiệm bộ nhớ trống. Bạn có thể phân biệt + những những kịch bản này bằng phương pháp {@link + android.app.Activity#isFinishing isFinishing()}.không có gì
+ +

Cột ghi "Có thể tắt bỏ sau?" cho biết liệu hệ thống có thể +tắt bỏ tiến trình đang lưu trữ hoạt động vào bất cứ lúc nào sau khi phương pháp trả về, mà không +thực hiện một dòng mã khác của hoạt động hay không. Ba phương pháp được ghi là "có": ({@link +android.app.Activity#onPause +onPause()}, {@link android.app.Activity#onStop onStop()}, và {@link android.app.Activity#onDestroy +onDestroy()}). Vì {@link android.app.Activity#onPause onPause()} là phương pháp đầu tiên +trong ba phương pháp, sau khi hoạt động được tạo, {@link android.app.Activity#onPause onPause()} là +phương pháp cuối cùng được bảo đảm sẽ được gọi trước khi tiến trình có thể bị tắt bỏ—nếu +hệ thống phải khôi phục bộ nhớ trong một tình huống khẩn cấp, khi đó {@link +android.app.Activity#onStop onStop()} và {@link android.app.Activity#onDestroy onDestroy()} có thể +không được gọi. Vì thế, bạn nên sử dụng {@link android.app.Activity#onPause onPause()} để ghi +dữ liệu cố định quan trọng (chẳng hạn như những chỉnh sửa của người dùng) vào thiết bị lưu trữ. Tuy nhiên, bạn nên chọn lọc +thông tin nào phải được giữ lại trong {@link android.app.Activity#onPause onPause()}, vì bất kỳ +thủ tục chặn nào trong phương pháp này cũng chặn chuyển tiếp sang hoạt động kế tiếp và làm chậm trải nghiệm +của người dùng.

+ +

Những phương pháp được ghi "Không" trong cột Có thể tắt bỏ sẽ bảo vệ tiến trình đang lưu trữ +hoạt động khỏi bị tắt bỏ từ thời điểm chúng được gọi. Vì thế, một hoạt động có thể tắt bỏ được +từ thời điểm {@link android.app.Activity#onPause onPause()} trở về tới thời điểm +{@link android.app.Activity#onResume onResume()} sẽ được gọi. Nó sẽ không thể lại tắt bỏ được tới khi +{@link android.app.Activity#onPause onPause()} lại được gọi và trả về.

+ +

Lưu ý: Một hoạt động mà không thể "tắt bỏ được" về mặt kỹ thuật bởi +định nghĩa này trong bảng 1 vẫn có thể bị hệ thống tắt bỏ—nhưng điều đó chỉ xảy ra trong +những hoàn cảnh cực đoan khi không còn giải pháp nào khác. Thời điểm một hoạt động có thể bị tắt bỏ được +đề cập kỹ hơn trong tài liệu Tiến trình và +Luồng.

+ + +

Lưu trạng thái của hoạt động

+ +

Phần giới thiệu về Quản lý Vòng đời của Hoạt động có đề cập sơ qua +rằng +khi một hoạt động bị tạm dừng hoặc dừng, trạng thái của hoạt động đó sẽ được giữ lại. Điều này đúng vì +đối tượng {@link android.app.Activity} vẫn được giữ trong bộ nhớ khi nó bị tạm dừng hoặc +dừng—tất cả thông tin về các thành viên và trạng thái hiện tại của nó vẫn hoạt động. Vì thế, bất kỳ thay đổi nào +mà người dùng đã thực hiện trong hoạt động đều được giữ lại sao cho khi hoạt động trở về +tiền cảnh (khi nó "tiếp tục"), thì những thay đổi này vẫn còn đó.

+ +

Tuy nhiên, khi hệ thống hủy một hoạt động để khôi phục bộ nhớ, đối tượng {@link +android.app.Activity} bị hủy, vì vậy hệ thống không thể đơn thuần tiếp tục hoạt động với trạng thái +không bị ảnh hưởng. Thay vào đó, hệ thống phải tạo lại đối tượng {@link android.app.Activity} nếu người dùng +điều hướng trở lại nó. Tuy vậy, người dùng không biết +rằng hệ thống đã hủy hoạt động và tạo lại nó và, vì thế, có thể +cho rằng hoạt động sẽ vẫn nguyên như cũ. Trong tình huống này, bạn có thể đảm bảo rằng +thông tin quan trọng về trạng thái của hoạt động được giữ nguyên bằng cách triển khai một phương pháp gọi lại +bổ sung cho phép bạn lưu thông tin về trạng thái của hoạt động của mình: {@link +android.app.Activity#onSaveInstanceState onSaveInstanceState()}.

+ +

Hệ thống gọi {@link android.app.Activity#onSaveInstanceState onSaveInstanceState()} +trước khi khiến hoạt động dễ bị hủy. Hệ thống chuyển cho phương pháp này +một {@link android.os.Bundle} trong đó bạn có thể lưu +thông tin trạng thái về hoạt động như cặp tên giá trị, bằng cách sử dụng các phương pháp như {@link +android.os.Bundle#putString putString()} và {@link +android.os.Bundle#putInt putInt()}. Sau đó, nếu hệ thống tắt bỏ tiến trình ứng dụng của bạn +và người dùng điều hướng trở lại hoạt động của bạn, hệ thống sẽ tạo lại hoạt động đó và +chuyển {@link android.os.Bundle} cho cả {@link android.app.Activity#onCreate onCreate()} và {@link +android.app.Activity#onRestoreInstanceState onRestoreInstanceState()}. Sử dụng một trong +hai phương pháp này, bạn có thể trích xuất trạng thái đã lưu của mình từ {@link android.os.Bundle} và khôi phục +trạng thái của hoạt động. Nếu không có thông tin trạng thái để khôi phục, khi đó {@link +android.os.Bundle} được chuyển cho bạn sẽ rỗng (là trường hợp khi hoạt động được tạo +lần đầu).

+ + +

Hình 2. Hai cách mà theo đó một hoạt động trở về tiêu điểm +của người dùng với trạng thái không thay đổi: hoặc hoạt động bị hủy, rồi tạo lại và hoạt động phải khôi phục +trạng thái đã lưu trước đó, hoặc hoạt động bị dừng, rồi tiếp tục và trạng thái của hoạt động +giữ nguyên không đổi.

+ +

Lưu ý: Không có gì bảo đảm rằng {@link +android.app.Activity#onSaveInstanceState onSaveInstanceState()} sẽ được gọi trước khi hoạt động +của bạn bị hủy, vì có những trường hợp mà sẽ không cần lưu trạng thái +(chẳng hạn như khi người dùng rời bỏ hoạt động của bạn bằng cách sử dụng nút Quay lại, vì người dùng +rõ ràng +đang đóng hoạt động). Nếu hệ thống gọi {@link android.app.Activity#onSaveInstanceState +onSaveInstanceState()}, nó làm vậy trước {@link +android.app.Activity#onStop onStop()} và có thể trước cả {@link android.app.Activity#onPause +onPause()}.

+ +

Tuy nhiên, ngay cả khi bạn không làm gì và không triển khai {@link +android.app.Activity#onSaveInstanceState onSaveInstanceState()}, một phần trạng thái của hoạt động được khôi phục +bởi việc lớp {@link android.app.Activity} triển khai mặc định {@link +android.app.Activity#onSaveInstanceState onSaveInstanceState()}. Cụ thể, triển khai +mặc định sẽ gọi phương pháp {@link +android.view.View#onSaveInstanceState onSaveInstanceState()} tương ứng cho mọi {@link +android.view.View} trong bố trí, nó cho phép mỗi chế độ xem cung cấp thông tin về chính nó +mà sẽ được lưu. Gần như mọi widget trong khuôn khổ Android đều triển khai phương pháp này nếu +phù hợp, sao cho mọi thay đổi hiển thị đối với UI đều tự động được lưu và khôi phục khi hoạt động +của bạn được tạo lại. Ví dụ, widget {@link android.widget.EditText} lưu mọi văn bản +do người dùng điền vào và widget {@link android.widget.CheckBox} lưu sẽ thông tin cho dù đã được kiểm tra +hay chưa. Việc duy nhất bạn cần làm đó là cung cấp một ID duy nhất (với thuộc tính {@code android:id} +) cho mỗi widget bạn muốn lưu trạng thái của nó. Nếu một widget không có ID thì hệ thống +không thể lưu trạng thái của nó.

+ + + +

Mặc dù việc triển khai mặc định {@link +android.app.Activity#onSaveInstanceState onSaveInstanceState()} lưu thông tin hữu ích về +UI hoạt động của bạn, bạn có thể vẫn cần khống chế nó để lưu thêm thông tin. +Ví dụ, bạn có thể cần lưu các giá trị thành viên đã thay đổi trong vòng đời của hoạt động (mà +có thể tương quan với các giá trị được khôi phục trong UI, nhưng các thành viên nắm giữ giá trị UI đó không được +khôi phục theo mặc định).

+ +

Vì việc triển khai mặc định {@link +android.app.Activity#onSaveInstanceState onSaveInstanceState()} giúp lưu trạng thái của UI, nếu +bạn khống chế phương pháp để lưu thêm thông tin trạng thái, bạn nên luôn luôn gọi +triển khai siêu lớp của {@link android.app.Activity#onSaveInstanceState onSaveInstanceState()} +trước khi thực hiện bất kỳ công việc nào. Tương tự, bạn cũng nên gọi triển khai siêu lớp {@link +android.app.Activity#onRestoreInstanceState onRestoreInstanceState()} nếu bạn khống chế nó, để +triển khai mặc định có thể khôi phục các trạng thái xem.

+ +

Lưu ý: Vì {@link android.app.Activity#onSaveInstanceState +onSaveInstanceState()} không đảm bảo +sẽ được gọi, bạn chỉ nên sử dụng nó để ghi trạng thái giao thời của hoạt động (trạng thái của +UI)—bạn không nên sử dụng nó để lưu giữ dữ liệu liên tục. Thay vào đó, bạn nên sử dụng {@link +android.app.Activity#onPause onPause()} để lưu giữ dữ liệu liên tục (chẳng hạn như dữ liệu mà nên được lưu +vào một cơ sở dữ liệu) khi người dùng rời bỏ hoạt động.

+ +

Một cách hay để kiểm tra khả năng khôi phục trạng thái của ứng dụng của bạn đó là chỉ cần xoay +thiết bị sao cho hướng màn hình thay đổi. Khi hướng màn hình thay đổi, hệ thống +hủy và tạo lại hoạt động để áp dụng các tài nguyên thay thế mà có thể có sẵn +cho cấu hình màn hình mới. Chỉ với lý do này mà một điều rất quan trọng đó là hoạt động của bạn +hoàn toàn khôi phục trạng thái của mình khi nó được tạo lại, vì người dùng thường xoay màn hình trong khi +sử dụng ứng dụng.

+ + +

Xử lý thay đổi về cấu hình

+ +

Một số cấu hình thiết bị có thể thay đổi trong thời gian chạy (chẳng hạn như hướng màn hình, sự sẵn có +của bàn phím, và ngôn ngữ). Khi sự thay đổi đó diễn ra, Android tạo lại hoạt động đang chạy +(hệ thống gọi {@link android.app.Activity#onDestroy}, rồi ngay lập tức gọi {@link +android.app.Activity#onCreate onCreate()}). Hành vi này +được thiết kế để giúp ứng dụng của bạn điều chỉnh theo những cấu hình mới bằng cách tự động tải lại ứng dụng +của bạn bằng các tài nguyên thay thế mà bạn đã cung cấp (chẳng hạn như bố trí khác cho +các hướng và kích cỡ màn hình khác).

+ +

Nếu bạn thiết kế hoạt động của mình một cách phù hợp để xử lý khởi động lại do thay đổi hướng màn hình và +khôi phục trạng thái hoạt động như nêu trên, ứng dụng của bạn sẽ linh hoạt hơn trước +những sự kiện bất ngờ khác trong vòng đời của hoạt động.

+ +

Cách tốt nhất để xử lý khởi động lại đó là + lưu và khôi phục trạng thái hoạt động của bạn bằng cách sử dụng {@link + android.app.Activity#onSaveInstanceState onSaveInstanceState()} và {@link +android.app.Activity#onRestoreInstanceState onRestoreInstanceState()} (hoặc {@link +android.app.Activity#onCreate onCreate()}), như đã đề cập trong phần trước.

+ +

Để biết thêm thông tin về những thay đổi cấu hình xảy ra tại thời điểm chạy và cách bạn có thể xử lý +chúng, hãy đọc hướng dẫn Xử lý +Thay đổi trong Thời gian chạy.

+ + + +

Điều phối hoạt động

+ +

Khi một hoạt động bắt đầu một hoạt động khác, cả hai đều trải qua những chuyển tiếp vòng đời. Hoạt động thứ nhất +tạm dừng và dừng (tuy nhiên, nó sẽ không dừng nếu vẫn hiển thị được dưới nền), trong khi hoạt động kia +được tạo. Trong trường hợp những hoạt động này chia sẻ dữ liệu được lưu vào đĩa hoặc nơi khác, điều quan trọng là +phải hiểu rằng hoạt động thứ nhất không bị dừng hoàn toàn trước khi hoạt động thứ hai được tạo. +Thay vào đó, tiến trình bắt đầu hoạt động thứ hai chồng lấp với tiến trình dừng hoạt động +thứ nhất.

+ +

Thứ tự gọi lại vòng đời được định nghĩa rõ, cụ thể là khi hai hoạt động trong cùng tiến trình +và hoạt động này bắt đầu hoạt động kia. Sau đây là thứ tự thao tác diễn ra khi Hoạt động +A bắt đầu Hoạt động B:

+ +
    +
  1. Phương pháp {@link android.app.Activity#onPause onPause()} của Hoạt động A thực thi.
  2. + +
  3. {@link android.app.Activity#onCreate onCreate()} của Hoạt động B, {@link +android.app.Activity#onStart onStart()}, và các phương pháp {@link android.app.Activity#onResume onResume()} +thực thi theo trình tự. (Hoạt động B lúc này có tiêu điểm của người dùng.)
  4. + +
  5. Sau đó, nếu Hoạt động A không còn hiển thị trên màn hình, phương pháp {@link +android.app.Activity#onStop onStop()} của nó sẽ thực thi.
  6. +
+ +

Trình tự gọi lại vòng đời có thể dự đoán này cho phép bạn quản lý chuyển tiếp +thông tin từ hoạt động này sang hoạt động khác. Ví dụ, nếu bạn phải ghi vào một cơ sở dữ liệu khi +hoạt động thứ nhất dừng sao cho hoạt động theo sau có thể đọc nó, khi đó bạn nên ghi vào +cơ sở dữ liệu trong khi {@link android.app.Activity#onPause onPause()} thay vì trong khi {@link +android.app.Activity#onStop onStop()}.

+ + diff --git a/docs/html-intl/intl/vi/guide/components/bound-services.jd b/docs/html-intl/intl/vi/guide/components/bound-services.jd new file mode 100644 index 0000000000000000000000000000000000000000..7a2ddbaf6321e02d0d9db6d39674685f581f80ac --- /dev/null +++ b/docs/html-intl/intl/vi/guide/components/bound-services.jd @@ -0,0 +1,658 @@ +page.title=Dịch vụ Gắn kết +parent.title=Dịch vụ +parent.link=services.html +@jd:body + + +
+
    +

    Trong tài liệu này

    +
      +
    1. Nội dung Cơ bản
    2. +
    3. Tạo một Dịch vụ Gắn kết +
        +
      1. Mở rộng lớp Trình gắn kết
      2. +
      3. Sử dụng một Hàm nhắn tin
      4. +
      +
    4. +
    5. Gắn kết với một Dịch vụ
    6. +
    7. Quản lý Vòng đời của một Dịch vụ Gắn kết
    8. +
    + +

    Lớp khóa

    +
      +
    1. {@link android.app.Service}
    2. +
    3. {@link android.content.ServiceConnection}
    4. +
    5. {@link android.os.IBinder}
    6. +
    + +

    Mẫu

    +
      +
    1. {@code + RemoteService}
    2. +
    3. {@code + LocalService}
    4. +
    + +

    Xem thêm

    +
      +
    1. Dịch vụ
    2. +
    +
+ + +

Dịch vụ gắn kết là máy chủ trong một giao diện máy khách-máy chủ. Dịch vụ gắn kết cho phép các thành phần +(chẳng hạn như các hoạt động) gắn kết với dịch vụ, gửi yêu cầu, nhận phản hồi, và thậm chí thực hiện +truyền thông liên tiến trình (IPC). Dịch vụ gắn kết thường chỉ hoạt động khi nó phục vụ một thành phần +ứng dụng khác và không chạy ngầm mãi liên tục.

+ +

Tài liệu này cho bạn biết cách tạo một dịch vụ gắn kết, bao gồm cách gắn kết +với dịch vụ từ các thành phần ứng dụng khác. Tuy nhiên, bạn cũng nên tham khảo tài liệu Dịch vụ để biết thêm thông tin +về các dịch vụ nói chung, chẳng hạn như cách gửi thông báo từ một dịch vụ, đặt +dịch vụ để chạy trong tiền cảnh, và nhiều nội dung khác.

+ + +

Nội dung Cơ bản

+ +

Dịch vụ gắn kết là một sự triển khai lớp {@link android.app.Service} cho phép +các ứng dụng khác gắn kết và tương tác với nó. Để thực hiện gắn kết cho một +dịch vụ, bạn phải triển khai phương pháp gọi lại {@link android.app.Service#onBind onBind()}. Phương pháp này +trả về một đối tượng {@link android.os.IBinder} định nghĩa giao diện lập trình mà +các máy khách có thể sử dụng để tương tác với dịch vụ.

+ + + +

Một máy khách có thể gắn kết với dịch vụ bằng cách gọi {@link android.content.Context#bindService +bindService()}. Khi làm vậy, nó phải cung cấp việc triển khai {@link +android.content.ServiceConnection}, có chức năng theo dõi kết nối với dịch vụ. Phương pháp {@link +android.content.Context#bindService bindService()} trả về ngay lập tức mà không có giá trị, nhưng +khi hệ thống Android tạo kết nối giữa +máy khách và dịch vụ, nó gọi {@link +android.content.ServiceConnection#onServiceConnected onServiceConnected()} trên {@link +android.content.ServiceConnection}, để giao {@link android.os.IBinder} mà +máy khách có thể sử dụng để giao tiếp với dịch vụ.

+ +

Nhiều máy khách có thể kết nối với dịch vụ đồng thời. Tuy nhiên, hệ thống sẽ gọi phương pháp +{@link android.app.Service#onBind onBind()} của dịch vụ của bạn để truy xuất {@link android.os.IBinder} chỉ +khi máy khách đầu tiên gắn kết. Sau đó, hệ thống sẽ giao cùng một {@link android.os.IBinder} đó cho bất kỳ +máy khách bổ sung nào có gắn kết mà không gọi lại {@link android.app.Service#onBind onBind()}.

+ +

Khi máy khách cuối cùng bỏ gắn kết với dịch vụ, hệ thống sẽ hủy dịch vụ (trừ khi dịch vụ +cũng được bắt đầu bởi {@link android.content.Context#startService startService()}).

+ +

Khi bạn triển khai dịch vụ gắn kết của mình, phần quan trọng nhất là định nghĩa giao diện +mà phương pháp gọi lại {@link android.app.Service#onBind onBind()} của bạn sẽ trả về. Có một vài +cách khác nhau mà bạn có thể định nghĩa giao diện {@link android.os.IBinder} của dịch vụ của mình và phần +sau đây sẽ bàn về từng kỹ thuật.

+ + + +

Tạo một Dịch vụ Gắn kết

+ +

Khi tạo một dịch vụ thực hiện gắn kết, bạn phải nêu một {@link android.os.IBinder} +cung cấp giao diện lập trình mà các máy khách có thể sử dụng để tương tác với dịch vụ. Có +ba cách bạn có thể định nghĩa giao diện:

+ +
+
Mở rộng lớp Trình gắn kết
+
Nếu dịch vụ của bạn chỉ riêng cho ứng dụng của chính bạn và chạy trong cùng tiến trình như máy khách +(điều này thường hay gặp), bạn nên tạo giao diện của mình bằng cách mở rộng lớp {@link android.os.Binder} +và trả về một thực thể của nó từ +{@link android.app.Service#onBind onBind()}. Máy khách nhận được {@link android.os.Binder} và +có thể sử dụng nó để trực tiếp truy cập các phương pháp công khai có sẵn trong triển khai {@link android.os.Binder} +hoặc thậm chí trong {@link android.app.Service}. +

Nên áp dụng kỹ thuật này khi dịch vụ của bạn chỉ là một trình thực hiện chạy ngầm cho ứng dụng +của chính bạn. Lý do duy nhất bạn không nên tạo giao diện của mình bằng cách này đó là +dịch vụ của bạn được sử dụng bởi các ứng dụng khác hoặc giữa những tiến trình khác nhau.

+ +
Sử dụng một Hàm nhắn tin
+
Nếu bạn cần giao diện của mình thực hiện các tiến trình khác nhau, bạn có thể tạo +một giao diện cho dịch vụ bằng {@link android.os.Messenger}. Bằng cách này, dịch vụ +định nghĩa một {@link android.os.Handler} phản hồi các loại đối tượng {@link +android.os.Message} khác nhau. {@link android.os.Handler} +này là cơ sở cho một {@link android.os.Messenger} mà sau đó có thể chia sẻ một {@link android.os.IBinder} +với máy khách, cho phép máy khách gửi lệnh tới dịch vụ bằng cách sử dụng các đối tượng {@link +android.os.Message}. Ngoài ra, máy khách có thể định nghĩa {@link android.os.Messenger} của +chính nó để dịch vụ có thể gửi lại thông báo. +

Đây là cách đơn giản nhất để thực hiện truyền thông liên tiến trình (IPC), vì {@link +android.os.Messenger} xếp hàng tất cả yêu cầu thành một luồng duy nhất sao cho bạn không phải thiết kế +dịch vụ của mình an toàn với luồng.

+
+ +
Sử dụng AIDL
+
AIDL (Ngôn ngữ Định nghĩa Giao diện Android) thực hiện tất cả công việc để phân tách đối tượng thành +các phần tử mà hệ điều hành có thể hiểu được và ghép nối chúng qua các tiến trình để thực hiện +IPC. Bằng cách sử dụng {@link android.os.Messenger}, kỹ thuật trước đó thực tế được dựa trên AIDL như là +cấu trúc cơ bản của nó. Như đã đề cập bên trên, {@link android.os.Messenger} tạo một hàng chờ +gồm tất cả yêu cầu của máy khách trong một luồng duy nhất, vì thế dịch vụ nhận được từng yêu cầu một. Tuy nhiên, nếu +bạn muốn dịch vụ xử lý nhiều yêu cầu đồng thời, bạn có thể sử dụng AIDL +trực tiếp. Trong trường hợp này, dịch vụ của bạn phải có khả năng tạo đa luồng và được xây dựng an toàn với luồng. +

Để sử dụng AIDL trực tiếp, bạn phải +tạo một tệp {@code .aidl} định nghĩa giao diện lập trình. Các công cụ SDK Android sử dụng tệp +này để khởi tạo một lớp tóm tắt (abstract class) nhằm triển khai giao diện và xử lý IPC, mà sau đó +bạn có thể mở rộng trong dịch vụ của mình.

+
+
+ +

Lưu ý: Hầu hết ứng dụng không nên sử dụng AIDL để +tạo một dịch vụ gắn kết, vì nó có thể yêu cầu khả năng tạo đa luồng và +có thể dẫn đến việc triển khai phức tạp hơn. Như vậy, AIDL không phù hợp với hầu hết ứng dụng +và tài liệu này không bàn về cách sử dụng nó cho dịch vụ của bạn. Nếu bạn chắc chắn rằng mình cần +sử dụng AIDL trực tiếp, hãy xem tài liệu AIDL +.

+ + + + +

Mở rộng lớp Trình gắn kết

+ +

Nếu dịch vụ của bạn chỉ được sử dụng bởi ứng dụng cục bộ và không cần làm việc qua nhiều tiến trình, +khi đó bạn có thể triển khai lớp {@link android.os.Binder} của chính mình để cung cấp quyền truy cập +trực tiếp cho máy khách của bạn để truy nhập các phương pháp công khai trong dịch vụ.

+ +

Lưu ý: Cách này chỉ có tác dụng nếu máy khách và dịch vụ nằm trong cùng +ứng dụng và tiến trình, là trường hợp phổ biến nhất. Ví dụ, cách này sẽ hoạt động tốt đối với một ứng dụng +nhạc cần gắn kết một hoạt động với dịch vụ của chính nó đang phát nhạc +chạy ngầm.

+ +

Sau đây là cách thiết lập:

+
    +
  1. Trong dịch vụ của bạn, hãy tạo một thực thể {@link android.os.Binder} mà hoặc: +
      +
    • chứa các phương pháp công khai mà máy khách có thể gọi
    • +
    • trả về thực thể {@link android.app.Service} hiện tại, trong đó có các phương pháp công khai mà +máy khách có thể gọi
    • +
    • hoặc, trả về một thực thể của một lớp khác được lưu trữ bởi dịch vụ bằng các phương pháp công khai mà +máy khách có thể gọi
    • +
    +
  2. Trả về thực thể {@link android.os.Binder} này từ phương pháp gọi lại {@link +android.app.Service#onBind onBind()}.
  3. +
  4. Trong máy khách, nhận {@link android.os.Binder} từ phương pháp gọi lại {@link +android.content.ServiceConnection#onServiceConnected onServiceConnected()} và +thực hiện gọi tới dịch vụ gắn kết bằng cách sử dụng các phương pháp đã nêu.
  5. +
+ +

Lưu ý: Lý do dịch vụ và máy khách phải ở trong cùng +ứng dụng đó là máy khách có thể đổi kiểu đối tượng được trả về và gọi các API của nó một cách phù hợp. Dịch vụ +và máy khách cũng phải ở trong cùng tiến trình, vì kỹ thuật này không thực hiện bất kỳ thao tác +ghép nối qua các tiến trình nào.

+ +

Ví dụ, sau đây là một dịch vụ cung cấp cho máy khách quyền truy cập các phương pháp trong dịch vụ thông qua +việc triển khai {@link android.os.Binder}:

+ +
+public class LocalService extends Service {
+    // Binder given to clients
+    private final IBinder mBinder = new LocalBinder();
+    // Random number generator
+    private final Random mGenerator = new Random();
+
+    /**
+     * Class used for the client Binder.  Because we know this service always
+     * runs in the same process as its clients, we don't need to deal with IPC.
+     */
+    public class LocalBinder extends Binder {
+        LocalService getService() {
+            // Return this instance of LocalService so clients can call public methods
+            return LocalService.this;
+        }
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return mBinder;
+    }
+
+    /** method for clients */
+    public int getRandomNumber() {
+      return mGenerator.nextInt(100);
+    }
+}
+
+ +

{@code LocalBinder} cung cấp phương pháp {@code getService()} cho máy khách để truy xuất +thực thể hiện tại của {@code LocalService}. Điều này cho phép máy khách gọi các phương pháp công khai trong +dịch vụ. Ví dụ, máy khách có thể gọi {@code getRandomNumber()} từ dịch vụ.

+ +

Sau đây là một hoạt động gắn kết với {@code LocalService} và sẽ gọi {@code getRandomNumber()} +khi nhấp vào nút:

+ +
+public class BindingActivity extends Activity {
+    LocalService mService;
+    boolean mBound = false;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.main);
+    }
+
+    @Override
+    protected void onStart() {
+        super.onStart();
+        // Bind to LocalService
+        Intent intent = new Intent(this, LocalService.class);
+        bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
+    }
+
+    @Override
+    protected void onStop() {
+        super.onStop();
+        // Unbind from the service
+        if (mBound) {
+            unbindService(mConnection);
+            mBound = false;
+        }
+    }
+
+    /** Called when a button is clicked (the button in the layout file attaches to
+      * this method with the android:onClick attribute) */
+    public void onButtonClick(View v) {
+        if (mBound) {
+            // Call a method from the LocalService.
+            // However, if this call were something that might hang, then this request should
+            // occur in a separate thread to avoid slowing down the activity performance.
+            int num = mService.getRandomNumber();
+            Toast.makeText(this, "number: " + num, Toast.LENGTH_SHORT).show();
+        }
+    }
+
+    /** Defines callbacks for service binding, passed to bindService() */
+    private ServiceConnection mConnection = new ServiceConnection() {
+
+        @Override
+        public void onServiceConnected(ComponentName className,
+                IBinder service) {
+            // We've bound to LocalService, cast the IBinder and get LocalService instance
+            LocalBinder binder = (LocalBinder) service;
+            mService = binder.getService();
+            mBound = true;
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName arg0) {
+            mBound = false;
+        }
+    };
+}
+
+ +

Mẫu trên cho thấy cách mà máy khách gắn kết với dịch vụ bằng cách sử dụng triển khai +{@link android.content.ServiceConnection} và gọi lại {@link +android.content.ServiceConnection#onServiceConnected onServiceConnected()}. Phần tiếp theo +cung cấp thêm thông tin về tiến trình gắn kết này với dịch vụ.

+ +

Lưu ý: Ví dụ trên không công khai bỏ gắn kết khỏi dịch vụ, +nhưng tất cả máy khách cần bỏ gắn kết tại một thời điểm phù hợp (chẳng hạn như khi hoạt động tạm dừng).

+ +

Để biết thêm mã ví dụ, hãy xem lớp {@code +LocalService.java} và lớp {@code +LocalServiceActivities.java} trong ApiDemos.

+ + + + + +

Sử dụng một Hàm nhắn tin

+ + + +

Nếu bạn cần dịch vụ của mình giao tiếp với các tiến trình từ xa, khi đó bạn có thể sử dụng một +{@link android.os.Messenger} để cung cấp giao diện cho dịch vụ của mình. Kỹ thuật này cho phép +bạn thực hiện truyền thông liên tiến trình (IPC) mà không cần sử dụng AIDL.

+ +

Sau đây là tóm tắt cách sử dụng {@link android.os.Messenger}:

+ +
    +
  • Dịch vụ triển khai {@link android.os.Handler} để nhận lệnh gọi lại cho mỗi +lệnh gọi từ một máy khách.
  • +
  • {@link android.os.Handler} được sử dụng để tạo một đối tượng {@link android.os.Messenger} +(là một tham chiếu tới {@link android.os.Handler}).
  • +
  • {@link android.os.Messenger} tạo một {@link android.os.IBinder} mà dịch vụ +trả về máy khách từ {@link android.app.Service#onBind onBind()}.
  • +
  • Máy khách sử dụng {@link android.os.IBinder} để khởi tạo {@link android.os.Messenger} +(tham chiếu tới {@link android.os.Handler} của dịch vụ), mà máy khách sử dụng để gửi các đối tượng +{@link android.os.Message} tới dịch vụ.
  • +
  • Dịch vụ nhận được từng {@link android.os.Message} trong {@link +android.os.Handler} của mình—cụ thể là theo phương pháp {@link android.os.Handler#handleMessage +handleMessage()}.
  • +
+ + +

Theo cách này, không có "phương pháp" nào để máy khách gọi đối với dịch vụ. Thay vào đó, máy khách +gửi “thông báo” (đối tượng {@link android.os.Message}) mà dịch vụ nhận được trong +{@link android.os.Handler} của mình.

+ +

Sau đây là một dịch vụ ví dụ đơn giản sử dụng một giao diện {@link android.os.Messenger}:

+ +
+public class MessengerService extends Service {
+    /** Command to the service to display a message */
+    static final int MSG_SAY_HELLO = 1;
+
+    /**
+     * Handler of incoming messages from clients.
+     */
+    class IncomingHandler extends Handler {
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_SAY_HELLO:
+                    Toast.makeText(getApplicationContext(), "hello!", Toast.LENGTH_SHORT).show();
+                    break;
+                default:
+                    super.handleMessage(msg);
+            }
+        }
+    }
+
+    /**
+     * Target we publish for clients to send messages to IncomingHandler.
+     */
+    final Messenger mMessenger = new Messenger(new IncomingHandler());
+
+    /**
+     * When binding to the service, we return an interface to our messenger
+     * for sending messages to the service.
+     */
+    @Override
+    public IBinder onBind(Intent intent) {
+        Toast.makeText(getApplicationContext(), "binding", Toast.LENGTH_SHORT).show();
+        return mMessenger.getBinder();
+    }
+}
+
+ +

Để ý rằng phương pháp {@link android.os.Handler#handleMessage handleMessage()} trong +{@link android.os.Handler} là nơi dịch vụ nhận được {@link android.os.Message} +đến và quyết định việc cần làm dựa trên thành viên {@link android.os.Message#what}.

+ +

Tất cả việc mà một máy khách cần làm đó là tạo một {@link android.os.Messenger} dựa trên {@link +android.os.IBinder} được dịch vụ trả về và gửi một thông báo bằng cách sử dụng {@link +android.os.Messenger#send send()}. Ví dụ, sau đây là một hoạt động đơn giản gắn kết với dịch vụ +và gửi tin nhắn {@code MSG_SAY_HELLO} cho dịch vụ:

+ +
+public class ActivityMessenger extends Activity {
+    /** Messenger for communicating with the service. */
+    Messenger mService = null;
+
+    /** Flag indicating whether we have called bind on the service. */
+    boolean mBound;
+
+    /**
+     * Class for interacting with the main interface of the service.
+     */
+    private ServiceConnection mConnection = new ServiceConnection() {
+        public void onServiceConnected(ComponentName className, IBinder service) {
+            // This is called when the connection with the service has been
+            // established, giving us the object we can use to
+            // interact with the service.  We are communicating with the
+            // service using a Messenger, so here we get a client-side
+            // representation of that from the raw IBinder object.
+            mService = new Messenger(service);
+            mBound = true;
+        }
+
+        public void onServiceDisconnected(ComponentName className) {
+            // This is called when the connection with the service has been
+            // unexpectedly disconnected -- that is, its process crashed.
+            mService = null;
+            mBound = false;
+        }
+    };
+
+    public void sayHello(View v) {
+        if (!mBound) return;
+        // Create and send a message to the service, using a supported 'what' value
+        Message msg = Message.obtain(null, MessengerService.MSG_SAY_HELLO, 0, 0);
+        try {
+            mService.send(msg);
+        } catch (RemoteException e) {
+            e.printStackTrace();
+        }
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.main);
+    }
+
+    @Override
+    protected void onStart() {
+        super.onStart();
+        // Bind to the service
+        bindService(new Intent(this, MessengerService.class), mConnection,
+            Context.BIND_AUTO_CREATE);
+    }
+
+    @Override
+    protected void onStop() {
+        super.onStop();
+        // Unbind from the service
+        if (mBound) {
+            unbindService(mConnection);
+            mBound = false;
+        }
+    }
+}
+
+ +

Để ý rằng ví dụ này không cho biết cách mà dịch vụ có thể phản hồi máy khách. Nếu bạn muốn dịch vụ +phản hồi, khi đó bạn cũng cần tạo một {@link android.os.Messenger} trong máy khách. Sau đó +khi máy khách nhận được lệnh gọi lại {@link android.content.ServiceConnection#onServiceConnected +onServiceConnected()}, nó sẽ gửi một {@link android.os.Message} tới dịch vụ, trong đó bao gồm +{@link android.os.Messenger} của máy khách trong tham số {@link android.os.Message#replyTo} +của phương pháp {@link android.os.Messenger#send send()}.

+ +

Bạn có thể xem một ví dụ về cách cung cấp tính năng nhắn tin hai chiều trong {@code +MessengerService.java} (dịch vụ) và các mẫu {@code +MessengerServiceActivities.java} (máy khách).

+ + + + + +

Gắn kết với một Dịch vụ

+ +

Các thành phần ứng dụng (máy khách) có thể gắn kết với một dịch vụ bằng cách gọi +{@link android.content.Context#bindService bindService()}. Hệ thống Android +khi đó sẽ gọi phương pháp {@link android.app.Service#onBind +onBind()} của dịch vụ, nó trả về một {@link android.os.IBinder} để tương tác với dịch vụ.

+ +

Việc gắn kết diễn ra không đồng bộ. {@link android.content.Context#bindService +bindService()} trả về ngay lập tức và không trả {@link android.os.IBinder} về +máy khách. Để nhận một {@link android.os.IBinder}, máy khách phải tạo một thực thể của {@link +android.content.ServiceConnection} và chuyển nó cho {@link android.content.Context#bindService +bindService()}. {@link android.content.ServiceConnection} bao gồm một phương pháp gọi lại mà hệ thống +gọi để gửi {@link android.os.IBinder}.

+ +

Lưu ý: Chỉ các hoạt động, dịch vụ, và trình cung cấp nội dung mới có thể gắn kết +với một dịch vụ—bạn không thể gắn kết với một dịch vụ từ một hàm nhận quảng bá (broadcast receiver).

+ +

Vì vậy, để gắn kết với một dịch vụ từ máy khách của mình, bạn phải:

+
    +
  1. Triển khai {@link android.content.ServiceConnection}. +

    Việc triển khai của bạn phải khống chế hai phương pháp gọi lại:

    +
    +
    {@link android.content.ServiceConnection#onServiceConnected onServiceConnected()}
    +
    Hệ thống gọi phương pháp này để gửi {@link android.os.IBinder} được trả về bởi +phương pháp {@link android.app.Service#onBind onBind()} của dịch vụ.
    +
    {@link android.content.ServiceConnection#onServiceDisconnected +onServiceDisconnected()}
    +
    Hệ thống Android gọi phương pháp này khi kết nối với dịch vụ bị mất +đột ngột, chẳng hạn như khi dịch vụ bị lỗi hoặc bị tắt bỏ. Phương pháp này không được gọi khi +máy khách bỏ gắn kết.
    +
    +
  2. +
  3. Gọi {@link +android.content.Context#bindService bindService()}, chuyển việc triển khai {@link +android.content.ServiceConnection}.
  4. +
  5. Khi hệ thống gọi phương pháp gọi lại {@link android.content.ServiceConnection#onServiceConnected +onServiceConnected()} của bạn, bạn có thể bắt đầu thực hiện các lệnh gọi tới dịch vụ bằng các phương pháp +được định nghĩa bởi giao diện.
  6. +
  7. Để ngắt kết nối khỏi dịch vụ, hãy gọi {@link +android.content.Context#unbindService unbindService()}. +

    Khi máy khách của bạn bị hủy, nó sẽ bỏ gắn kết khỏi dịch vụ, nhưng bạn nên luôn bỏ gắn kết +khi bạn đã tương tác xong với dịch vụ hoặc khi hoạt động của bạn tạm dừng sao cho dịch vụ có thể +tắt khi không dùng đến. (Thời điểm phù hợp để gắn kết và bỏ gắn kết được đề cập +kỹ hơn ở bên dưới.)

    +
  8. +
+ +

Ví dụ, đoạn mã HTML sau sẽ kết nối máy khách với dịch vụ được tạo bên trên bằng cách +mở rộng lớp Trình gắn kết, vì vậy tất cả những việc mà nó phải làm là đổi kiểu +{@link android.os.IBinder} được trả về thành lớp {@code LocalService} và yêu cầu thực thể {@code +LocalService}:

+ +
+LocalService mService;
+private ServiceConnection mConnection = new ServiceConnection() {
+    // Called when the connection with the service is established
+    public void onServiceConnected(ComponentName className, IBinder service) {
+        // Because we have bound to an explicit
+        // service that is running in our own process, we can
+        // cast its IBinder to a concrete class and directly access it.
+        LocalBinder binder = (LocalBinder) service;
+        mService = binder.getService();
+        mBound = true;
+    }
+
+    // Called when the connection with the service disconnects unexpectedly
+    public void onServiceDisconnected(ComponentName className) {
+        Log.e(TAG, "onServiceDisconnected");
+        mBound = false;
+    }
+};
+
+ +

Với {@link android.content.ServiceConnection} này, máy khách có thể gắn kết với một dịch vụ bằng cách chuyển +nó cho {@link android.content.Context#bindService bindService()}. Ví dụ:

+ +
+Intent intent = new Intent(this, LocalService.class);
+bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
+
+ +
    +
  • Tham số đầu tiên của {@link android.content.Context#bindService bindService()} là một +{@link android.content.Intent} trong đó nêu rõ tên của các dịch vụ sẽ gắn kết (mặc dù ý định +có thể ngầm hiểu).
  • +
  • Tham số thứ hai là đối tượng {@link android.content.ServiceConnection}.
  • +
  • Tham số thứ ba là một cờ cho biết các tùy chọn cho gắn kết. Nên luôn luôn là {@link +android.content.Context#BIND_AUTO_CREATE} để tạo dịch vụ nếu nó chưa hoạt động. +Các giá trị có thể khác là {@link android.content.Context#BIND_DEBUG_UNBIND} +và {@link android.content.Context#BIND_NOT_FOREGROUND}, hoặc {@code 0} trong trường hợp không có.
  • +
+ + +

Lưu ý bổ sung

+ +

Sau đây là một số lưu ý quan trọng về việc gắn kết với một dịch vụ:

+
    +
  • Bạn nên luôn bẫy các lỗi ngoại lệ {@link android.os.DeadObjectException} phát sinh khi +kết nối bị đứt. Đây là lỗi ngoại lệ duy nhất phát sinh bởi các phương pháp từ xa.
  • +
  • Các đối tượng được xem là tham chiếu khắp các tiến trình.
  • +
  • Bạn nên luôn ghép đôi gắn kết và bỏ gắn kết trong khi +khớp những khoảnh khắc kết nối và đứt kết nối trong vòng đời của máy khách. Ví dụ: +
      +
    • Nếu bạn chỉ cần tương tác với dịch vụ trong khi hoạt động của bạn hiển thị, bạn +nên gắn kết trong khi {@link android.app.Activity#onStart onStart()} và bỏ gắn kết trong khi {@link +android.app.Activity#onStop onStop()}.
    • +
    • Nếu bạn muốn hoạt động của mình nhận được phản hồi ngay cả trong khi bị dừng khi đang +dưới nền, khi đó bạn có thể gắn kết trong khi {@link android.app.Activity#onCreate onCreate()} và bỏ gắn kết +trong khi {@link android.app.Activity#onDestroy onDestroy()}. Chú ý rằng điều này hàm ý rằng hoạt động +của bạn cần sử dụng dịch vụ trong toàn bộ thời gian khi nó đang chạy (ngay cả khi chạy ngầm), do đó nếu +dịch vụ ở trong một tiến trình khác thì bạn hãy tăng trọng số của tiến trình và khả năng hệ thống +tắt bỏ tiến trình đó sẽ cao hơn.
    • +
    +

    Lưu ý: Thông thường bạn không nên gắn kết và bỏ gắn kết +trong khi {@link android.app.Activity#onResume onResume()} và {@link +android.app.Activity#onPause onPause()} cho hoạt động của mình, vì những lệnh gọi lại này diễn ra tại mọi thời điểm chuyển tiếp vòng đời +và bạn nên duy trì xử lý tại những thời điểm chuyển tiếp này ở mức tối thiểu. Đồng thời, nếu +nhiều hoạt động trong ứng dụng của bạn gắn kết với cùng dịch vụ và có sự chuyển tiếp giữa +hai trong số những hoạt động đó, dịch vụ có thể bị hủy và tạo lại khi hoạt động hiện tại bỏ gắn kết +(trong khi tạm dừng) trước khi hoạt động tiếp theo gắn kết (trong khi tiếp tục). (Sự chuyển tiếp hoạt động này đối với cách mà các hoạt động +phối hợp vòng đời của chúng được mô tả trong tài liệu Hoạt động +.)

    +
+ +

Để biết thêm mã ví dụ, thể hiện cách gắn kết với một dịch vụ, hãy xem lớp {@code +RemoteService.java} trong ApiDemos.

+ + + + + +

Quản lý Vòng đời của một Dịch vụ Gắn kết

+ +

Khi một dịch vụ bị bỏ gắn kết khỏi tất cả máy khách, hệ thống Android sẽ hủy nó (trừ khi nó cũng +được bắt đầu bằng {@link android.app.Service#onStartCommand onStartCommand()}). Như vậy, bạn không phải + quản lý vòng đời dịch vụ của mình nếu nó thuần túy là một +dịch vụ gắn kết—hệ thống Android sẽ quản lý nó cho bạn dựa trên việc nó có gắn kết với bất kỳ máy khách nào không.

+ +

Tuy nhiên, nếu bạn chọn triển khai phương pháp gọi lại {@link android.app.Service#onStartCommand +onStartCommand()}, vậy thì bạn phải dừng dịch vụ một cách tường minh, vì dịch vụ +lúc này đang được coi là được bắt đầu. Trong trường hợp này, dịch vụ sẽ chạy cho tới khi dịch vụ +tự dừng bằng {@link android.app.Service#stopSelf()} hoặc một thành phần khác sẽ gọi {@link +android.content.Context#stopService stopService()}, bất kể nó có gắn kết với bất kỳ máy khách +nào không.

+ +

Ngoài ra, nếu dịch vụ của bạn được bắt đầu và chấp nhận gắn kết, lúc đó khi hệ thống gọi +phương pháp {@link android.app.Service#onUnbind onUnbind()} của bạn, bạn có thể tùy chọn trả về +{@code true} nếu bạn muốn nhận một lệnh gọi tới {@link android.app.Service#onRebind +onRebind()} vào lần tới khi một máy khách gắn kết với dịch vụ (thay vì nhận một lệnh gọi tới {@link +android.app.Service#onBind onBind()}). {@link android.app.Service#onRebind +onRebind()} sẽ trả về rỗng, nhưng máy khách vẫn nhận được {@link android.os.IBinder} trong gọi lại +{@link android.content.ServiceConnection#onServiceConnected onServiceConnected()} của mình. +Hình 1 bên dưới minh họa lô-gic cho loại vòng đời này.

+ + + +

Hình 1. Vòng đời của một dịch vụ được bắt đầu +và cũng cho phép gắn kết.

+ + +

Để biết thêm thông tin về vòng đời của một dịch vụ được bắt đầu, hãy xem tài liệu Dịch vụ.

+ + + + diff --git a/docs/html-intl/intl/vi/guide/components/fragments.jd b/docs/html-intl/intl/vi/guide/components/fragments.jd new file mode 100644 index 0000000000000000000000000000000000000000..95d9c76337fc86b75f7dfda9a5e8d3ba749d37d5 --- /dev/null +++ b/docs/html-intl/intl/vi/guide/components/fragments.jd @@ -0,0 +1,812 @@ +page.title=Phân đoạn +parent.title=Hoạt động +parent.link=activities.html +@jd:body + + + +

{@link android.app.Fragment} biểu diễn một hành vi hay một phần giao diện người dùng trong một +{@link android.app.Activity}. Bạn có thể kết hợp nhiều phân đoạn trong một hoạt động duy nhất để xây dựng một +UI nhiều bảng và sử dụng lại phân đoạn trong nhiều hoạt động. Bạn có thể coi phân đoạn như là một +phần mô-đun của một hoạt động, có vòng đời của chính nó, nhận các sự kiện đầu vào của chính nó, và +bạn có thể thêm hoặc gỡ bỏ trong khi hoạt động đang chạy (kiểu như một "hoạt động con" mà +bạn có thể sử dụng lại trong các hoạt động khác nhau).

+ +

Phân đoạn phải luôn được nhúng trong một hoạt động và vòng đời của phân đoạn bị ảnh hưởng trực tiếp bởi +vòng đời của hoạt động chủ. Ví dụ, khi hoạt động bị tạm dừng, tất cả +phân đoạn trong nó cũng vậy, và khi hoạt động bị hủy, tất cả phân đoạn cũng vậy. Tuy nhiên, trong khi một +hoạt động đang chạy (nó ở trong trạng thái vòng đời được tiếp tục), bạn có thể +thao tác từng phân đoạn độc lập, chẳng hạn như thêm hay xóa chúng. Khi bạn thực hiện một +giao tác phân đoạn, bạn cũng có thể thêm nó vào một ngăn xếp được quản lý bởi +hoạt động đó—từng mục nhập vào ngăn xếp trong hoạt động là một bản ghi giao tác phân đoạn +đã xảy ra. Ngăn xếp cho phép người dùng đảo ngược một giao tác phân đoạn (điều hướng ngược lại), +bằng cách nhấn nút Quay lại.

+ +

Khi bạn thêm một phân đoạn như một phần trong bố trí hoạt động của mình, nó sẽ ở trong một {@link +android.view.ViewGroup} bên trong phân cấp dạng xem của hoạt động đó và phân đoạn này sẽ định nghĩa bố trí +dạng xem của chính nó. +Bạn có thể chèn một phân đoạn vào bố trí hoạt động của mình bằng cách khai báo phân đoạn trong tệp +bố trí của hoạt động, dưới dạng một phần tử {@code <fragment>}, hoặc từ mã ứng dụng của bạn bằng cách thêm nó vào một +{@link android.view.ViewGroup} hiện hữu. Tuy nhiên, không bắt buộc phải có một phân đoạn là một bộ phận của bố trí hoạt động +; bạn cũng có thể sử dụng một phân đoạn mà không cần UI của chính nó như một trình thực hiện vô hình cho hoạt động +.

+ +

Tài liệu này mô tả cách xây dựng ứng dụng của bạn để sử dụng phân đoạn, bao gồm +cách các phân đoạn có thể duy trì trạng thái của chúng khi được thêm vào ngăn xếp của hoạt động, chia sẻ +các sự kiện với hoạt động và các phân đoạn khác trong hoạt động, đóng góp vào thanh hành động của hoạt động +và nhiều thông tin khác.

+ + +

Triết lý Thiết kế

+ +

Android giới thiệu phân đoạn trong phiên bản Android 3.0 (API mức 11), chủ yếu nhằm hỗ trợ +các thiết kế UI động và linh hoạt hơn trên màn hình lớn, chẳng hạn như máy tính bảng. Vì +màn hình của máy tính bảng lớn hơn nhiều màn hình của thiết bị cầm tay, có nhiều khoảng trống hơn để kết hợp và +trao đổi các thành phần UI. Phân đoạn cho phép những thiết kế như vậy mà không cần bạn phải quản lý những thay đổi +phức tạp về phân cấp dạng xem. Bằng cách chia bố trí của một hoạt động thành các phân đoạn, bạn có thể +sửa đổi diện mạo của hoạt động vào thời gian chạy và giữ những thay đổi đó trong một ngăn xếp +được quản lý bởi hoạt động.

+ +

Ví dụ, một ứng dụng tin tức có thể sử dụng một phân đoạn để hiển thị một danh sách bài viết ở +bên trái và một phân đoạn khác để hiển thị một bài viết ở bên phải—cả hai phân đoạn đều xuất hiện trong một +hoạt động, bên cạnh nhau, và từng phân đoạn có tập phương pháp gọi lại vòng đời riêng và xử lý +các sự kiện nhập liệu người dùng riêng của mình. Vì thế, thay vì sử dụng một hoạt động để chọn một bài viết và một +hoạt động khác để đọc bài viết, người dùng có thể chọn một bài viết và đọc nó trong cùng +hoạt động, như được minh họa trong bố trí máy tính bảng trong hình 1.

+ +

Bạn nên thiết kế từng phân đoạn như một thành phần hoạt động dạng mô-đun và có thể sử dụng lại. Đó là bởi +mỗi phân đoạn sẽ định nghĩa bố trí và hành vi của chính nó với các phương pháp gọi lại vòng đời của chính nó, bạn có thể +bao gồm một phân đoạn trong nhiều hoạt động, vì thế bạn nên thiết kế để tái sử dụng và tránh trực tiếp +thao tác một phân đoạn từ một phân đoạn khác. Điều này đặc biệt quan trọng vì một phân đoạn +mô-đun cho phép bạn thay đổi kết hợp phân đoạn của mình cho các kích cỡ màn hình khác nhau. Khi thiết kế +ứng dụng của bạn để hỗ trợ cả máy tính bảng và thiết bị cầm tay, bạn có thể sử dụng lại phân đoạn của mình trong các cấu hình +bố trí khác nhau nhằm tối ưu hóa trải nghiệm người dùng dựa trên không gian màn hình có sẵn. Ví +dụ, trên một thiết bị cầm tay, có thể cần phải tách riêng các phân đoạn để cung cấp một UI đơn bảng khi mà +không thể làm vừa khít nhiều hơn một phân đoạn trong cùng hoạt động.

+ + +

Hình 1. Ví dụ về cách hai mô-đun UI được định nghĩa +bởi các phân đoạn có thể được kết hợp thành một hoạt động đối với thiết kế máy tính bảng, nhưng được tách riêng đối với +thiết kế thiết bị cầm tay.

+ +

Ví dụ—để tiếp tục với ví dụ về ứng dụng tin tức—ứng dụng có thể nhúng +hai phân đoạn trong Hoạt động A, khi đang chạy trên một thiết bị có kích cỡ máy tính bảng. Tuy nhiên, trên một +màn hình kích cỡ thiết bị cầm tay, không có đủ khoảng trống cho cả hai phân đoạn, vì thế Hoạt động A chỉ +bao gồm phân đoạn cho danh sách bài viết, và khi người dùng chọn một bài viết, nó sẽ khởi động +Hoạt động B, hoạt động này chứa phân đoạn thứ hai là đọc bài viết. Vì thế, ứng dụng +hỗ trợ cả máy tính bảng và thiết bị cầm tay bằng cách sử dụng lại các phân đoạn theo các cách kết hợp khác nhau như được minh họa trong +hình 1.

+ +

Để biết thêm thông tin về việc thiết kế ứng dụng của bạn bằng các cách kết hợp phân đoạn khác nhau cho +cấu hình màn hình khác nhau, hãy xem hướng dẫn Hỗ trợ Máy tính bảng và Thiết bị cầm tay.

+ + + +

Tạo một Phân đoạn

+ +
+ +

Hình 2. Vòng đời của một phân đoạn (trong khi hoạt động +của nó đang chạy).

+
+ +

Để tạo một phân đoạn, bạn phải tạo một lớp con của {@link android.app.Fragment} (hoặc +một lớp con hiện tại của nó). Lớp {@link android.app.Fragment} có mã trông rất giống +một {@link android.app.Activity}. Nó chứa các phương pháp gọi lại tương tự như hoạt động, chẳng +hạn như {@link android.app.Fragment#onCreate onCreate()}, {@link android.app.Fragment#onStart onStart()}, +{@link android.app.Fragment#onPause onPause()}, và {@link android.app.Fragment#onStop onStop()}. Trên +thực tế, nếu bạn đang chuyển đổi một ứng dụng Android hiện tại để sử dụng các phân đoạn, bạn có thể chỉ cần di chuyển +mã khỏi các phương pháp gọi lại của hoạt động của bạn vào các phương pháp gọi lại tương ứng của phân đoạn +của bạn.

+ +

Thường thì ít nhất bạn nên triển khai các phương pháp vòng đời sau:

+ +
+
{@link android.app.Fragment#onCreate onCreate()}
+
Hệ thống sẽ gọi phương pháp này khi tạo phân đoạn. Trong triển khai của mình, bạn nên +khởi tạo các thành phần thiết yếu của phân đoạn mà bạn muốn giữ lại khi phân đoạn +bị tạm dừng hoặc dừng hẳn, sau đó tiếp tục.
+
{@link android.app.Fragment#onCreateView onCreateView()}
+
Hệ thống sẽ gọi phương pháp này khi đến lúc phân đoạn vẽ giao diện người dùng của nó +lần đầu tiên. Để vẽ một UI cho phân đoạn của mình, bạn phải trả về một {@link android.view.View} từ phương pháp +này, đây là gốc của bố trí phân đoạn của bạn. Bạn có thể trả về giá trị rỗng nếu phân đoạn không +cung cấp UI.
+
{@link android.app.Activity#onPause onPause()}
+
Hệ thống gọi phương pháp này là dấu hiệu đầu tiên về việc người dùng đang rời khỏi +phân đoạn (mặc dù không phải lúc nào cũng có nghĩa rằng phân đoạn đang bị hủy). Trường hợp này thường là khi bạn +định thực hiện bất kỳ thay đổi nào vẫn cần có hiệu lực ngoài phiên của người dùng hiện thời (vì +người dùng có thể không quay lại).
+
+ +

Phần lớn ứng dụng nên triển khai ít nhất ba phương pháp sau đối với mọi phân đoạn, nhưng có một vài +phương pháp gọi lại khác mà bạn cũng nên sử dụng để xử lý các giai đoạn khác nhau trong +vòng đời của phân đoạn. Tất cả phương pháp gọi lại vòng đời được đề cập chi tiết hơn trong phần +về Xử lý Vòng đời của Phân đoạn.

+ + +

Cũng có một vài lớp con mà bạn có thể muốn mở rộng thay vì lớp cơ bản {@link +android.app.Fragment}:

+ +
+
{@link android.app.DialogFragment}
+
Hiển thị một hộp thoại trôi nổi. Sử dụng lớp này để tạo một hộp thoại là một phương án hay cho việc sử dụng các phương pháp trình trợ giúp +hộp thoại trong lớp {@link android.app.Activity}, vì bạn có thể +kết hợp một hộp thoại phân đoạn vào ngăn xếp của các phân đoạn được quản lý bởi hoạt động, +cho phép người dùng trả về một phân đoạn bị bỏ.
+ +
{@link android.app.ListFragment}
+
Hiển thị một danh sách các mục được quản lý bởi một trình điều hợp (chẳng hạn như một {@link +android.widget.SimpleCursorAdapter}), tương tự như {@link android.app.ListActivity}. Nó cung cấp +một vài phương pháp để quản lý một dạng xem danh sách, chẳng hạn như phương pháp gọi lại {@link +android.app.ListFragment#onListItemClick(ListView,View,int,long) onListItemClick()} để +xử lý các sự kiện nhấp.
+ +
{@link android.preference.PreferenceFragment}
+
Hiển thị một phân cấp các đối tượng {@link android.preference.Preference} dưới dạng một danh sách, tương tự như +{@link android.preference.PreferenceActivity}. Điều này hữu ích khi tạo một hoạt động "thiết đặt" +cho ứng dụng của bạn.
+
+ + +

Thêm một giao diện người dùng

+ +

Phân đoạn thường được sử dụng như một phần giao diện người dùng của hoạt động và đóng góp bố trí của +chính nó cho hoạt động.

+ +

Để cung cấp một bố trí cho một phân đoạn, bạn phải triển khai phương pháp gọi lại {@link +android.app.Fragment#onCreateView onCreateView()}, phương pháp này được hệ thống Android gọi +khi đến lúc phân đoạn vẽ bố trí của nó. Việc bạn triển khai phương pháp này phải trả về một +{@link android.view.View} là phần gốc cho bố trí phân đoạn của bạn.

+ +

Lưu ý: Nếu phân đoạn của bạn là một lớp con của {@link +android.app.ListFragment}, triển khai mặc định sẽ trả về một {@link android.widget.ListView} từ +{@link android.app.Fragment#onCreateView onCreateView()}, vì thế bạn không cần triển khai nó.

+ +

Để trả về một bố trí từ {@link +android.app.Fragment#onCreateView onCreateView()}, bạn có thể bung nó từ một tài nguyên bố trí được định nghĩa trong XML. Để +giúp bạn làm vậy, {@link android.app.Fragment#onCreateView onCreateView()} cung cấp một đối tượng +{@link android.view.LayoutInflater}.

+ +

Ví dụ, sau đây là một lớp con của {@link android.app.Fragment} với chức năng nạp một bố trí từ tệp +{@code example_fragment.xml}:

+ +
+public static class ExampleFragment extends Fragment {
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+                             Bundle savedInstanceState) {
+        // Inflate the layout for this fragment
+        return inflater.inflate(R.layout.example_fragment, container, false);
+    }
+}
+
+ + + +

Tham số {@code container} được chuyển tới {@link android.app.Fragment#onCreateView +onCreateView()} là {@link android.view.ViewGroup} mẹ (tức bố trí của hoạt động), trong đó +bố trí phân đoạn của bạn +sẽ được chèn vào. Tham số {@code savedInstanceState} là một {@link android.os.Bundle} có chức năng +cung cấp dữ liệu về thực thể trước đó của phân đoạn, nếu phân đoạn đang được tiếp tục +(việc khôi phục trạng thái được bàn kỹ hơn trong phần về Xử lý +Vòng đời của Phân đoạn).

+ +

Phương pháp {@link android.view.LayoutInflater#inflate(int,ViewGroup,boolean) inflate()} có +ba tham đối:

+
    +
  • ID tài nguyên của bố trí mà bạn muốn bung.
  • +
  • {@link android.view.ViewGroup} là mẹ của bố trí được bung. Việc chuyển {@code +container} có vai trò quan trọng để hệ thống áp dụng các tham số bố trí cho dạng xem gốc của bố trí +được bung, được quy định bởi dạng xem mẹ là nơi mà nó diễn ra trong đó.
  • +
  • Một boolean cho biết bố trí được bung có nên được gắn với {@link +android.view.ViewGroup} (tham số thứ hai) trong khi bung hay không. (Trong trường hợp này, điều này là +sai vì hệ thống đã đang chèn bố trí được bung vào {@code +container}—việc chuyển đúng sẽ tạo ra một nhóm dạng xem thừa trong bố trí cuối cùng.)
  • +
+ +

Giờ bạn đã thấy cách tạo một phân đoạn nhằm cung cấp một bố trí. Tiếp theo, bạn cần thêm +phân đoạn vào hoạt động của mình.

+ + + +

Thêm một phân đoạn vào một hoạt động

+ +

Thường thì một phân đoạn đóng góp một phần UI vào hoạt động chủ, nó được nhúng như một phần +trong phân cấp dạng xem tổng thể của hoạt động. Có hai cách mà bạn có thể thêm một phân đoạn vào bố trí +của hoạt động:

+ +
    +
  • Khai báo phân đoạn bên trong tệp bố trí của hoạt động. +

    Trong trường hợp này, bạn có thể +chỉ định các tính chất bố trí cho phân đoạn như thể nó là một dạng xem. Ví dụ, sau đây là tệp bố trí +cho một hoạt động có hai phân đoạn:

    +
    +<?xml version="1.0" encoding="utf-8"?>
    +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    +    android:orientation="horizontal"
    +    android:layout_width="match_parent"
    +    android:layout_height="match_parent">
    +    <fragment android:name="com.example.news.ArticleListFragment"
    +            android:id="@+id/list"
    +            android:layout_weight="1"
    +            android:layout_width="0dp"
    +            android:layout_height="match_parent" />
    +    <fragment android:name="com.example.news.ArticleReaderFragment"
    +            android:id="@+id/viewer"
    +            android:layout_weight="2"
    +            android:layout_width="0dp"
    +            android:layout_height="match_parent" />
    +</LinearLayout>
    +
    +

    Thuộc tính {@code android:name} trong {@code <fragment>} sẽ chỉ định lớp {@link +android.app.Fragment} để khởi tạo trong bố trí.

    + +

    Khi hệ thống tạo bố trí hoạt động này, nó sẽ khởi tạo từng phân đoạn được chỉ định trong bố trí +và gọi ra phương pháp {@link android.app.Fragment#onCreateView onCreateView()} cho từng phân đoạn, +để truy xuất bố trí của từng phân đoạn. Hệ thống sẽ chèn {@link android.view.View} được trả về bởi phân đoạn +trực tiếp thế chỗ phần tử {@code <fragment>}.

    + +
    +

    Lưu ý: Mỗi phân đoạn yêu cầu một mã định danh duy nhất +mà hệ thống có thể sử dụng để khôi phục phân đoạn nếu hoạt động bị khởi động lại (và bạn có thể sử dụng để +nắm bắt phân đoạn sẽ thực hiện giao tác, chẳng hạn như gỡ bỏ nó). Có ba cách để cung cấp ID cho một +phân đoạn:

    +
      +
    • Cung cấp thuộc tính {@code android:id} với một ID duy nhất.
    • +
    • Cung cấp thuộc tính {@code android:tag} với một xâu duy nhất.
    • +
    • Nếu bạn không cung cấp được thuộc tính nào, hệ thống sẽ sử dụng ID của dạng xem +của bộ chứa.
    • +
    +
    +
  • + +
  • Hoặc, bằng cách lập trình, thêm phân đoạn vào một {@link android.view.ViewGroup} hiện hữu. +

    Vào bất cứ lúc nào trong khi hoạt động của bạn đang chạy, bạn có thể thêm phân đoạn vào bố trí hoạt động của mình. Bạn +chỉ cần chỉ định một {@link +android.view.ViewGroup} là nơi mà bạn sẽ đặt phân đoạn vào.

    +

    Để thực hiện giao tác phân đoạn trong hoạt động của mình (chẳng hạn như thêm, gỡ bỏ, hay thay thế một +phân đoạn), bạn phải sử dụng các API từ {@link android.app.FragmentTransaction}. Bạn có thể nhận một thực thể +của {@link android.app.FragmentTransaction} từ {@link android.app.Activity} của mình như sau:

    + +
    +FragmentManager fragmentManager = {@link android.app.Activity#getFragmentManager()}
    +FragmentTransaction fragmentTransaction = fragmentManager.{@link android.app.FragmentManager#beginTransaction()};
    +
    + +

    Sau đó, bạn có thể thêm một phân đoạn bằng cách sử dụng phương pháp {@link +android.app.FragmentTransaction#add(int,Fragment) add()}, chỉ định phân đoạn sẽ thêm và +dạng xem mà bạn sẽ chèn nó vào. Ví dụ:

    + +
    +ExampleFragment fragment = new ExampleFragment();
    +fragmentTransaction.add(R.id.fragment_container, fragment);
    +fragmentTransaction.commit();
    +
    + +

    Tham đối đầu tiên được chuyển cho {@link android.app.FragmentTransaction#add(int,Fragment) add()} +là {@link android.view.ViewGroup}, là nơi mà phân đoạn sẽ được đặt vào, được chỉ định bởi +ID tài nguyên, và tham đối thứ hai là phân đoạn cần thêm.

    +

    Sau khi bạn đã thực hiện các thay đổi của mình bằng +{@link android.app.FragmentTransaction}, bạn phải +gọi {@link android.app.FragmentTransaction#commit} để các thay đổi có hiệu lực.

    +
  • +
+ + +

Thêm một phân đoạn không có UI

+ +

Các ví dụ nêu trên cho biết cách thêm một phân đoạn vào hoạt động của bạn để cung cấp một UI. Tuy nhiên, +bạn cũng có thể sử dụng một phân đoạn để cung cấp một hành vi chạy ngầm cho hoạt động mà không cần đưa +UI bổ sung.

+ +

Để thêm một phân đoạn không có UI, hãy thêm phân đoạn từ hoạt động đang bằng cách sử dụng {@link +android.app.FragmentTransaction#add(Fragment,String)} (cung cấp một "tag" xâu duy nhất cho phân đoạn +, thay vì một ID dạng xem). Làm vậy sẽ thêm phân đoạn, nhưng vì không liên kết với một dạng xem +trong bố trí hoạt động, nó sẽ không nhận được lệnh gọi tới {@link +android.app.Fragment#onCreateView onCreateView()}. Vì thế, bạn không cần triển khai phương pháp đó.

+ +

Việc cung cấp tag xâu cho phân đoạn không chỉ áp dụng cho các phân đoạn không có UI—bạn cũng có thể +cung cấp tag xâu cho phân đoạn có UI—nhưng nếu phân đoạn không có +UI, khi đó, tag xâu là cách duy nhất để nhận biết nó. Nếu sau này bạn muốn nhận phân đoạn từ +hoạt động, bạn cần sử dụng {@link android.app.FragmentManager#findFragmentByTag +findFragmentByTag()}.

+ +

Để biết ví dụ về hoạt động sử dụng phân đoạn như một trình thực hiện nền, không có UI, hãy xem mẫu {@code +FragmentRetainInstance.java}, mẫu này có trong các mẫu SDK (có sẵn thông qua +Trình quản lý SDK Android) và nằm trên hệ thống của bạn như là +<sdk_root>/APIDemos/app/src/main/java/com/example/android/apis/app/FragmentRetainInstance.java.

+ + + +

Quản lý Phân đoạn

+ +

Để quản lý các phân đoạn trong hoạt động của mình, bạn cần sử dụng {@link android.app.FragmentManager}. Để +có nó, hãy gọi {@link android.app.Activity#getFragmentManager()} từ hoạt động của bạn.

+ +

Một số việc bạn có thể làm với {@link android.app.FragmentManager} bao gồm:

+ +
    +
  • Nhận các phân đoạn tồn tại trong hoạt động, bằng {@link +android.app.FragmentManager#findFragmentById findFragmentById()} (đối với các phân đoạn cung cấp UI trong +bố trí hoạt động) hoặc {@link android.app.FragmentManager#findFragmentByTag +findFragmentByTag()} (đối với các phân đoạn có hoặc không cung cấp UI).
  • +
  • Lấy phân đoạn ra khỏi ngăn xếp, bằng {@link +android.app.FragmentManager#popBackStack()} (mô phỏng một câu lệnh Quay lại của người dùng).
  • +
  • Đăng ký một đối tượng theo dõi cho những thay đổi đối với ngăn xếp, bằng {@link +android.app.FragmentManager#addOnBackStackChangedListener addOnBackStackChangedListener()}.
  • +
+ +

Để biết thêm thông tin về những phương pháp này và phương pháp khác, hãy tham khảo tài liệu lớp {@link +android.app.FragmentManager}.

+ +

Như minh họa trong phần trước, bạn cũng có thể sử dụng {@link android.app.FragmentManager} +để mở một {@link android.app.FragmentTransaction}, nó cho phép bạn thực hiện các giao tác, ví dụ như +thêm hoặc gỡ bỏ phân đoạn.

+ + +

Thực hiện Giao tác Phân đoạn

+ +

Một tính năng tuyệt vời khi sử dụng phân đoạn trong hoạt động của bạn đó là khả năng thêm, gỡ bỏ, thay thế, +và thực hiện các hành động khác với chúng, để hồi đáp lại tương tác của người dùng. Mỗi tập hợp thay đổi mà bạn +thực thi cho hoạt động được gọi là một giao tác và bạn có thể thực hiện một giao tác bằng cách sử dụng các API trong {@link +android.app.FragmentTransaction}. Bạn cũng có thể lưu từng giao tác vào một ngăn xếp được quản lý bởi +hoạt động, cho phép người dùng điều hướng ngược lại thông qua những thay đổi phân đoạn (tương tự như điều hướng +ngược lại thông qua hoạt động).

+ +

Bạn có thể thu được một thực thể của {@link android.app.FragmentTransaction} từ {@link +android.app.FragmentManager} như sau:

+ +
+FragmentManager fragmentManager = {@link android.app.Activity#getFragmentManager()};
+FragmentTransaction fragmentTransaction = fragmentManager.{@link android.app.FragmentManager#beginTransaction()};
+
+ +

Mỗi giao tác là một tập hợp những thay đổi mà bạn muốn thực hiện tại cùng thời điểm. Bạn có thể thiết lập +tất cả thay đổi mà mình muốn thực hiện đối với một giao tác cho trước bằng cách sử dụng các phương pháp như {@link +android.app.FragmentTransaction#add add()}, {@link android.app.FragmentTransaction#remove remove()}, +và {@link android.app.FragmentTransaction#replace replace()}. Sau đó, để áp dụng giao tác +cho hoạt động, bạn phải gọi {@link android.app.FragmentTransaction#commit()}.

+ + +

Trước khi bạn gọi {@link +android.app.FragmentTransaction#commit()}, tuy nhiên, bạn có thể muốn gọi {@link +android.app.FragmentTransaction#addToBackStack addToBackStack()}, để thêm giao tác +vào một ngăn xếp của các giao tác phân đoạn. Ngăn xếp này được quản lý bởi hoạt động và cho phép +người dùng trở về trạng thái phân đoạn trước đó, bằng cách nhấp nút Quay lại.

+ +

Ví dụ, sau đây là cách bạn có thể thay thế phân đoạn này bằng phân đoạn khác, và giữ nguyên +trạng thái trước đó của ngăn xếp:

+ +
+// Create new fragment and transaction
+Fragment newFragment = new ExampleFragment();
+FragmentTransaction transaction = getFragmentManager().beginTransaction();
+
+// Replace whatever is in the fragment_container view with this fragment,
+// and add the transaction to the back stack
+transaction.replace(R.id.fragment_container, newFragment);
+transaction.addToBackStack(null);
+
+// Commit the transaction
+transaction.commit();
+
+ +

Trong ví dụ này, {@code newFragment} thay thế mọi phân đoạn (nếu có) hiện đang ở trong +bộ chứa bố trí được nhận biết bởi ID {@code R.id.fragment_container}. Bằng cách gọi {@link +android.app.FragmentTransaction#addToBackStack addToBackStack()}, giao tác thay thế +được lưu vào ngăn xếp, vì thế người dùng có thể đảo ngược giao tác và mang +giao tác trước đó trở lại bằng cách nhấn nút Quay lại.

+ +

Nếu bạn thêm nhiều thay đổi vào giao tác (chẳng hạn như một {@link +android.app.FragmentTransaction#add add()} khác hoặc {@link android.app.FragmentTransaction#remove +remove()}) và gọi {@link +android.app.FragmentTransaction#addToBackStack addToBackStack()}, khi đó, tất cả thay đổi được áp dụng +trước khi bạn gọi {@link android.app.FragmentTransaction#commit commit()} đều được thêm vào +ngăn xếp như một giao tác riêng lẻ và nút Quay lại sẽ đảo ngược tất cả cùng nhau.

+ +

Thứ tự mà bạn thêm thay đổi vào một {@link android.app.FragmentTransaction} không quan trọng, +ngoại trừ:

+
    +
  • Bạn phải gọi {@link android.app.FragmentTransaction#commit()} cuối cùng
  • +
  • Nếu bạn thêm nhiều phân đoạn vào cùng bộ chứa, khi đó thứ tự mà +bạn thêm chúng sẽ xác định thứ tự chúng xuất hiện trong phân cấp dạng xem
  • +
+ +

Nếu bạn không gọi {@link android.app.FragmentTransaction#addToBackStack(String) +addToBackStack()} khi thực hiện một giao tác để xóa một phân đoạn, khi đó, phân đoạn đó sẽ bị +hủy khi giao tác được thực hiện và người dùng không thể điều hướng trở lại nó. Trong khi đó, nếu bạn +gọi {@link android.app.FragmentTransaction#addToBackStack(String) addToBackStack()} khi +gỡ bỏ một phân đoạn, khi đó phân đoạn bị dừng và sẽ được khôi phục nếu người dùng điều hướng +trở lại.

+ +

Mẹo: Với mỗi giao tác phân đoạn, bạn có thể áp dụng một hoạt ảnh +chuyển tiếp bằng cách gọi {@link android.app.FragmentTransaction#setTransition setTransition()} trước khi +thực thi.

+ +

Việc gọi {@link android.app.FragmentTransaction#commit()} không thực hiện giao tác +ngay lập tức. Thay vào đó, nó lập lịch biểu để chạy trên luồng UI của hoạt động (luồng "chính") ngay khi +luồng có thể làm vậy. Tuy nhiên, nếu cần, bạn có thể gọi {@link +android.app.FragmentManager#executePendingTransactions()} từ luồng UI của mình để ngay lập tức thực hiện +các giao tác được gửi bởi {@link android.app.FragmentTransaction#commit()}. Làm vậy +thường không cần thiết trừ khi giao tác đó là phụ thuộc cho các tác vụ ở những luồng khác.

+ +

Chú ý: Bạn có thể thực thi một giao tác bằng cách sử dụng {@link +android.app.FragmentTransaction#commit commit()} chỉ trước khi hoạt động lưu +trạng thái của nó (khi người dùng rời khỏi hoạt động). Nếu bạn định thực thi sau thời điểm đó sẽ phát sinh một lỗi +ngoại lệ. Nguyên nhân là vì trạng thái sau khi thực thi có thể bị mất nếu hoạt động +cần được khôi phục. Đối với những trường hợp mà bạn có thể mất thực thi, hãy sử dụng {@link +android.app.FragmentTransaction#commitAllowingStateLoss()}.

+ + + + +

Giao tiếp với Hoạt động

+ +

Mặc dù {@link android.app.Fragment} được triển khai như một đối tượng độc lập với +{@link android.app.Activity} và có thể được sử dụng bên trong nhiều hoạt động, một thực thể đã cho của +phân đoạn sẽ được gắn kết trực tiếp với hoạt động chứa nó.

+ +

Cụ thể, phân đoạn có thể truy cập thực thể {@link android.app.Activity} bằng {@link +android.app.Fragment#getActivity()} và dễ dàng thực hiện các tác vụ như tìm một dạng xem trong bố trí +hoạt động:

+ +
+View listView = {@link android.app.Fragment#getActivity()}.{@link android.app.Activity#findViewById findViewById}(R.id.list);
+
+ +

Tương tự, hoạt động của bạn có thể gọi ra các phương pháp trong phân đoạn bằng cách thu được một tham chiếu tới +{@link android.app.Fragment} từ {@link android.app.FragmentManager}, bằng cách sử dụng {@link +android.app.FragmentManager#findFragmentById findFragmentById()} hoặc {@link +android.app.FragmentManager#findFragmentByTag findFragmentByTag()}. Ví dụ:

+ +
+ExampleFragment fragment = (ExampleFragment) getFragmentManager().findFragmentById(R.id.example_fragment);
+
+ + +

Tạo gọi lại sự kiện cho hoạt động

+ +

Trong một số trường hợp, bạn có thể cần một phân đoạn để chia sẻ sự kiện với hoạt động. Một cách hay để làm điều này +đó là định nghĩa một giao diện gọi lại bên trong phân đoạn và yêu cầu hoạt động chủ triển khai +nó. Khi hoạt động nhận được một lệnh gọi lại thông qua giao diện, nó có thể chia sẻ thông tin với +các phân đoạn khác trong bố trí nếu cần.

+ +

Ví dụ, nếu một ứng dụng tin tức có hai phân đoạn trong một hoạt động—một để hiển thị danh sách +bài viết (phân đoạn A) và một để hiển thị một bài viết (phân đoạn B)—khi đó, phân đoạn A phải thông báo với +hoạt động khi nào thì một mục danh sách được chọn để nó có thể yêu cầu phân đoạn B hiển thị bài viết đó. Trong +trường hợp này, giao diện {@code OnArticleSelectedListener} sẽ được khai báo bên trong phân đoạn A:

+ +
+public static class FragmentA extends ListFragment {
+    ...
+    // Container Activity must implement this interface
+    public interface OnArticleSelectedListener {
+        public void onArticleSelected(Uri articleUri);
+    }
+    ...
+}
+
+ +

Khi đó, hoạt động lưu trữ phân đoạn sẽ triển khai giao diện {@code OnArticleSelectedListener} +và +khống chế {@code onArticleSelected()} để thông báo với phân đoạn B về sự kiện từ phân đoạn A. Để đảm bảo +rằng hoạt động chủ triển khai giao diện này, phương pháp gọi lại {@link +android.app.Fragment#onAttach onAttach()} của phân đoạn A (mà hệ thống gọi khi thêm +phân đoạn vào hoạt động) sẽ khởi tạo một thực thể của {@code OnArticleSelectedListener} bằng cách +đổi kiểu {@link android.app.Activity} mà được chuyển vào {@link android.app.Fragment#onAttach +onAttach()}:

+ +
+public static class FragmentA extends ListFragment {
+    OnArticleSelectedListener mListener;
+    ...
+    @Override
+    public void onAttach(Activity activity) {
+        super.onAttach(activity);
+        try {
+            mListener = (OnArticleSelectedListener) activity;
+        } catch (ClassCastException e) {
+            throw new ClassCastException(activity.toString() + " must implement OnArticleSelectedListener");
+        }
+    }
+    ...
+}
+
+ +

Nếu hoạt động chưa triển khai giao diện, khi đó phân đoạn sẽ đưa ra lỗi +{@link java.lang.ClassCastException}. +Nếu thành công, thành viên {@code mListener} giữ một tham chiếu tới triển khai +{@code OnArticleSelectedListener}của hoạt động, sao cho phân đoạn A có thể chia sẻ sự kiện với hoạt động bằng cách gọi các phương pháp +được định nghĩa bởi giao diện {@code OnArticleSelectedListener}. Ví dụ, nếu phân đoạn A là một +phần mở rộng của {@link android.app.ListFragment}, mỗi lần +người dùng nhấp vào một mục danh sách, hệ thống sẽ gọi ra {@link android.app.ListFragment#onListItemClick +onListItemClick()} trong phân đoạn, và nó lại gọi {@code onArticleSelected()} để chia sẻ +sự kiện với hoạt động:

+ +
+public static class FragmentA extends ListFragment {
+    OnArticleSelectedListener mListener;
+    ...
+    @Override
+    public void onListItemClick(ListView l, View v, int position, long id) {
+        // Append the clicked item's row ID with the content provider Uri
+        Uri noteUri = ContentUris.{@link android.content.ContentUris#withAppendedId withAppendedId}(ArticleColumns.CONTENT_URI, id);
+        // Send the event and Uri to the host activity
+        mListener.onArticleSelected(noteUri);
+    }
+    ...
+}
+
+ +

Tham số {@code id} được chuyển vào {@link +android.app.ListFragment#onListItemClick onListItemClick()} là ID hàng của mục được nhấp, +nó được sử dụng bởi hoạt động (hoặc phân đoạn kia) để tải bài viết từ {@link +android.content.ContentProvider} của ứng dụng.

+ +

Bạn có thể xem thêm thông tin về +cách sử dụng một trình cung cấp nội dung trong tài liệu Trình cung cấp Nội dung.

+ + + +

Thêm mục vào Thanh Hành động

+ +

Phân đoạn của bạn có thể đóng góp các mục menu vào Menu Tùy chọn của hoạt động (và tiếp đó là cả Thanh Hành động) bằng cách triển khai +{@link android.app.Fragment#onCreateOptionsMenu(Menu,MenuInflater) onCreateOptionsMenu()}. Tuy nhiên, để +phương pháp này nhận lệnh gọi, bạn phải gọi {@link +android.app.Fragment#setHasOptionsMenu(boolean) setHasOptionsMenu()} trong khi {@link +android.app.Fragment#onCreate(Bundle) onCreate()}, để cho biết rằng phân đoạn +sẽ muốn thêm mục vào Menu Tùy chọn (nếu không, phân đoạn sẽ không nhận được lệnh gọi tới +{@link android.app.Fragment#onCreateOptionsMenu onCreateOptionsMenu()}).

+ +

Bất kỳ mục nào mà bạn thêm vào Menu Tùy chọn sau đó từ phân đoạn đều được nối với các mục menu +hiện tại. Phân đoạn cũng nhận các lệnh gọi lại tới {@link +android.app.Fragment#onOptionsItemSelected(MenuItem) onOptionsItemSelected()} khi một mục menu +được chọn.

+ +

Bạn cũng có thể đăng ký một dạng xem trong bố trí phân đoạn của mình để cung cấp một menu ngữ cảnh bằng cách gọi {@link +android.app.Fragment#registerForContextMenu(View) registerForContextMenu()}. Khi người dùng mở +menu ngữ cảnh, phân đoạn nhận một lệnh gọi tới {@link +android.app.Fragment#onCreateContextMenu(ContextMenu,View,ContextMenu.ContextMenuInfo) +onCreateContextMenu()}. Khi người dùng chọn một mục, phân đoạn nhận được một lệnh gọi tới {@link +android.app.Fragment#onContextItemSelected(MenuItem) onContextItemSelected()}.

+ +

Lưu ý: Mặc dù phân đoạn của bạn nhận được một lệnh gọi khi chọn mục +đối với từng mục menu mà nó thêm, trước tiên hoạt động sẽ nhận phương pháp gọi lại tương ứng khi người dùng +chọn một mục menu. Nếu việc triển khai gọi lại khi chọn mục của hoạt động không +xử lý mục được chọn, khi đó sự kiện được chuyển sang phương pháp gọi lại của phân đoạn. Điều này đúng đối với +Menu Tùy chọn và các menu ngữ cảnh.

+ +

Để biết thêm thông tin về các menu, xem các hướng dẫn cho nhà phát triển MenuThanh Hành động.

+ + + + +

Xử lý Vòng đời của Phân đoạn

+ +
+ +

Hình 3. Ảnh hưởng của vòng đời hoạt động tới vòng đời +của phân đoạn.

+
+ +

Việc quản lý vòng đời của một phân đoạn rất giống với quản lý vòng đời của một hoạt động. Giống như +hoạt động, phân đoạn có thể tồn tại ở ba trạng thái:

+ +
+
Tiếp tục
+
Phân đoạn hiển thị trong hoạt động đang chạy.
+ +
Tạm dừng
+
Một hoạt động khác ở trong tiền cảnh và có tiêu điểm, nhưng hoạt động mà phân đoạn +này nằm trong vẫn hiển thị (hoạt động tiền cảnh mờ một phần hoặc không +che phủ toàn bộ màn hình).
+ +
Dừng
+
Phân đoạn không hiển thị. Hoặc là hoạt động chủ đã bị dừng hoặc +phân đoạn đã được gỡ bỏ khỏi hoạt động, nhưng được thêm vào ngăn xếp. Phân đoạn dừng +vẫn còn hoạt động (tất cả thông tin về trạng thái và thành viên đều được hệ thống giữ lại). Tuy nhiên, nó không còn +hiển thị với người dùng nữa và sẽ bị tắt bỏ nếu hoạt động bị tắt bỏ.
+
+ +

Cũng như một hoạt động, bạn có thể giữ lại trạng thái của một phân đoạn bằng cách sử dụng {@link +android.os.Bundle}, trong trường hợp tiến trình của hoạt động bị tắt bỏ và bạn cần khôi phục +trạng thái của phân đoạn khi hoạt động được tạo lại. Bạn có thể lưu trạng thái trong phương pháp gọi lại {@link +android.app.Fragment#onSaveInstanceState onSaveInstanceState()} của phân đoạn và khôi phục nó trong +hoặc {@link android.app.Fragment#onCreate onCreate()}, {@link +android.app.Fragment#onCreateView onCreateView()}, hoặc {@link +android.app.Fragment#onActivityCreated onActivityCreated()}. Để biết thêm thông tin về việc lưu +trạng thái, xem tài liệu Hoạt động +.

+ +

Sự khác nhau quan trọng nhất trong vòng đời giữa một hoạt động và một phân đoạn đó là cách chúng +được lưu trữ trong ngăn xếp tương ứng. Hoạt động được đặt vào một ngăn xếp gồm nhiều hoạt động +, được quản lý bởi hệ thống theo mặc định khi bị dừng (sao cho người dùng có thể điều hướng lại +nó bằng nút Quay lại như được đề cập trong Tác vụ và Ngăn xếp). +Tuy nhiên, phân đoạn chỉ được đặt vào một ngăn xếp do hoạt động chủ quản lý khi bạn +yêu cầu rõ ràng rằng trường hợp đó phải được lưu bằng cách gọi {@link +android.app.FragmentTransaction#addToBackStack(String) addToBackStack()} trong một giao tác +gỡ bỏ phân đoạn.

+ +

Nếu không thì việc quản lý vòng đời của phân đoạn rất giống với việc quản lý vòng đời +của hoạt động. Vì thế, những nội dung áp dụng cho quản lý vòng đời của +hoạt động cũng áp dụng cho phân đoạn. Tuy nhiên, việc mà bạn cũng cần phải hiểu đó là cách +vòng đời của hoạt động ảnh hưởng tới vòng đời của phân đoạn.

+ +

Chú ý: Nếu bạn cần một đối tượng {@link android.content.Context} trong +{@link android.app.Fragment}của mình, bạn có thể gọi {@link android.app.Fragment#getActivity()}. +Tuy nhiên, nhớ chỉ được gọi {@link android.app.Fragment#getActivity()} khi phân đoạn được gắn với +một hoạt động. Khi phân đoạn chưa được gắn, hoặc bị gỡ trong khi kết thúc +vòng đời của nó, {@link android.app.Fragment#getActivity()} sẽ trả về rỗng.

+ + +

Phối hợp với vòng đời của hoạt động

+ +

Vòng đời của hoạt động mà phân đoạn có ở trong đó sẽ trực tiếp ảnh hưởng tới vòng đời của phân đoạn +, sao cho mỗi lệnh gọi lại vòng đời cho hoạt động đó sẽ dẫn tới một lệnh gọi lại tương tự cho từng +phân đoạn. Ví dụ, khi hoạt động nhận được {@link android.app.Activity#onPause}, mỗi +phân đoạn trong hoạt động sẽ nhận được {@link android.app.Fragment#onPause}.

+ +

Tuy nhiên, các phân đoạn có thêm một vài lệnh gọi lại vòng đời nhằm xử lý tương tác duy nhất với +hoạt động để thực hiện các hành động như xây dựng và hủy UI của phân đoạn. Những phương pháp gọi lại +bổ sung này là:

+ +
+
{@link android.app.Fragment#onAttach onAttach()}
+
Được gọi khi phân đoạn đã được liên kết với hoạt động {@link +android.app.Activity} được chuyển ở đây).
+
{@link android.app.Fragment#onCreateView onCreateView()}
+
Được gọi khi tạo phân cấp dạng xem được liên kết với phân đoạn.
+
{@link android.app.Fragment#onActivityCreated onActivityCreated()}
+
Được gọi khi phương pháp {@link android.app.Activity#onCreate +onCreate()} của hoạt động đã trả về.
+
{@link android.app.Fragment#onDestroyView onDestroyView()}
+
Được gọi khi phân cấp dạng xem được liên kết với phân đoạn đang được gỡ bỏ.
+
{@link android.app.Fragment#onDetach onDetach()}
+
Được gọi khi phân đoạn đang được bỏ liên kết khỏi hoạt động.
+
+ +

Tiến trình vòng đời của một phân đoạn, do bị ảnh hưởng bởi hoạt động chủ của nó, được minh họa +bởi hình 3. Trong hình này, bạn có thể thấy cách thức mỗi trạng thái nối tiếp nhau của hoạt động sẽ xác định +các phương pháp gọi lại nào mà một phân đoạn có thể nhận được. Ví dụ, khi hoạt động đã nhận được lệnh gọi lại {@link +android.app.Activity#onCreate onCreate()} của nó, phân đoạn trong hoạt động sẽ nhận được không quá +lệnh gọi lại {@link android.app.Fragment#onActivityCreated onActivityCreated()}.

+ +

Sau khi hoạt động đạt trạng thái tiếp tục, bạn có thể tự do thêm và gỡ bỏ phân đoạn vào +hoạt động. Vì thế, chỉ trong khi hoạt động ở trạng thái tiếp tục thì vòng đời của một phân đoạn +mới có thể thay đổi độc lập.

+ +

Tuy nhiên, khi hoạt động rời khỏi trạng thái tiếp tục, phân đoạn lại bị hoạt động đẩy qua vòng đời +của mình.

+ + + + +

Ví dụ

+ +

Để kết hợp mọi nội dung được đề cập trong tài liệu này, sau đây là một ví dụ về hoạt động +sử dụng hai phân đoạn để tạo một bố trí hai bảng. Hoạt động bên dưới bao gồm một phân đoạn để +hiển thị danh sách các vở kịch của Shakespeare và một phân đoạn khác để hiển thị tóm tắt về vở kịch khi được chọn +từ danh sách. Nó cũng minh họa cách cung cấp các cấu hình phân đoạn khác nhau, +dựa trên cấu hình màn hình.

+ +

Lưu ý: Mã nguồn hoàn chỉnh cho hoạt động này có sẵn trong +{@code +FragmentLayout.java}.

+ +

Hoạt động chính áp dụng một bố trí theo cách thông thường, trong {@link +android.app.Activity#onCreate onCreate()}:

+ +{@sample development/samples/ApiDemos/src/com/example/android/apis/app/FragmentLayout.java main} + +

Bố trí được áp dụng là {@code fragment_layout.xml}:

+ +{@sample development/samples/ApiDemos/res/layout-land/fragment_layout.xml layout} + +

Khi sử dụng bố trí này, hệ thống sẽ khởi tạo {@code TitlesFragment} (liệt kê tên +các vở kịch) ngay khi hoạt động nạp bố trí, trong khi {@link android.widget.FrameLayout} +(nơi sẽ xuất hiện phân đoạn hiển thị tóm tắt về vở kịch) chiếm khoảng trống phía bên phải của +màn hình, nhưng ban đầu vẫn trống. Như bạn sẽ thấy bên dưới, mãi tới khi người dùng chọn một mục +từ danh sách thì một phân đoạn mới được đặt vào {@link android.widget.FrameLayout}.

+ +

Tuy nhiên, không phải tất cả cấu hình màn hình đều đủ rộng để hiển thị cả danh sách +các vở kịch và tóm tắt bên cạnh nhau. Vì thế, bố trí trên chỉ được sử dụng cho cấu hình +màn hình khổ ngang bằng cách lưu nó dưới dạng {@code res/layout-land/fragment_layout.xml}.

+ +

Vì thế, khi màn hình hướng đứng, hệ thống sẽ áp dụng bố trí sau, nó +được lưu tại {@code res/layout/fragment_layout.xml}:

+ +{@sample development/samples/ApiDemos/res/layout/fragment_layout.xml layout} + +

Bố trí này chỉ bao gồm {@code TitlesFragment}. Điều này có nghĩa là, khi thiết bị ở +hướng đứng, chỉ danh sách tên vở kịch được hiển thị. Vì thế, khi người dùng nhấp vào một +mục danh sách trong cấu hình này, ứng dụng sẽ bắt đầu một hoạt động mới để hiển thị tóm tắt, +thay vì tải một phân đoạn thứ hai.

+ +

Tiếp theo, bạn có thể thấy cách hoàn thành điều này trong các lớp phân đoạn. Đầu tiên là {@code +TitlesFragment}, hiển thị danh sách tên các vở kịch của Shakespeare. Phân đoạn này sẽ mở rộng {@link +android.app.ListFragment} và dựa vào nó để xử lý hầu hết công việc về dạng xem danh sách.

+ +

Khi bạn kiểm tra đoạn mã này, hãy để ý rằng có hai hành vi có thể khi người dùng nhấp vào một +mục danh sách: phụ thuộc vào bố trí nào trong hai bố trí đang hiện hoạt, nó có thể hoặc tạo và hiển thị một phân đoạn +mới để hiển thị chi tiết trong cùng hoạt động (thêm phân đoạn vào {@link +android.widget.FrameLayout}), hoặc bắt đầu một hoạt động mới (tại đó phân đoạn có thể được hiển thị).

+ +{@sample development/samples/ApiDemos/src/com/example/android/apis/app/FragmentLayout.java titles} + +

Phân đoạn thứ hai, {@code DetailsFragment} sẽ hiển thị tóm tắt vở kịch cho mục được chọn từ +danh sách trong {@code TitlesFragment}:

+ +{@sample development/samples/ApiDemos/src/com/example/android/apis/app/FragmentLayout.java details} + +

Nhớ lại ở lớp {@code TitlesFragment} rằng, nếu người dùng nhấp vào một mục danh sách và bố trí +hiện tại không có dạng xem {@code R.id.details} (là nơi mà +{@code DetailsFragment} thuộc về), khi đó, ứng dụng sẽ bắt đầu hoạt động {@code DetailsActivity} +để hiển thị nội dung của mục đó.

+ +

Sau đây là {@code DetailsActivity}, nó chỉ đơn thuần nhúng {@code DetailsFragment} để hiển thị +tóm tắt vở kịch được chọn khi màn hình ở hướng đứng:

+ +{@sample development/samples/ApiDemos/src/com/example/android/apis/app/FragmentLayout.java +details_activity} + +

Lưu ý rằng hoạt động này tự kết thúc nếu cấu hình là khổ ngang, sao cho hoạt động +chính có thể chiếm lấy và hiển thị {@code DetailsFragment} bên cạnh {@code TitlesFragment}. +Điều này có thể xảy ra nếu người dùng bắt đầu {@code DetailsActivity} ở dạng hướng đứng, nhưng +sau đó xoay thành khổ ngang (làm vậy sẽ bắt đầu lại hoạt động hiện tại).

+ + +

Để biết thêm mẫu sử dụng phân đoạn (và toàn bộ tệp nguồn cho ví dụ này), +hãy xem ứng dụng mẫu API Demos có sẵn trong +ApiDemos (có thể tải xuống từ Thành phần SDK Mẫu).

+ + diff --git a/docs/html-intl/intl/vi/guide/components/fundamentals.jd b/docs/html-intl/intl/vi/guide/components/fundamentals.jd new file mode 100644 index 0000000000000000000000000000000000000000..4b70140723d95e8d2352647c66b03680078d4c4d --- /dev/null +++ b/docs/html-intl/intl/vi/guide/components/fundamentals.jd @@ -0,0 +1,480 @@ +page.title=Đại cương về Ứng dụng +@jd:body + + + +

Ứng dụng Android được viết bằng ngôn ngữ lập trình Java. Bộ công cụ SDK Android sẽ biên dịch +mã của bạn—cùng với bất kỳ tệp dữ liệu và tài nguyên nào—vào một APK: một gói Android, +đó là một tệp lưu trữ có hậu tố {@code .apk}. Một tệp APK chứa tất cả nội dung +của một ứng dụng Android và là tệp mà các thiết bị dựa trên nền tảng Android sử dụng để cài đặt ứng dụng.

+ +

Sau khi được cài đặt lên một thiết bị, từng ứng dụng Android sẽ ở bên trong hộp cát bảo mật của chính nó:

+ +
    +
  • Hệ điều hành Android là một hệ thống Linux đa người dùng trong đó mỗi ứng dụng là một +người dùng khác nhau.
  • + +
  • Theo mặc định, hệ thống gán cho từng ứng dụng một ID người dùng Linux duy nhất (ID chỉ được sử dụng bởi +hệ thống và không xác định đối với ứng dụng). Hệ thống sẽ đặt quyền cho tất cả tệp trong một ứng dụng +sao cho chỉ ID người dùng được gán cho ứng dụng đó mới có thể truy cập chúng.
  • + +
  • Mỗi tiến trình có máy ảo (VM) riêng của mình, vì thế mã của một ứng dụng sẽ chạy độc lập với +các ứng dụng khác.
  • + +
  • Theo mặc định, mọi ứng dụng chạy trong tiến trình Linux của chính nó. Android khởi động tiến trình khi bất kỳ +thành phần nào của ứng dụng cần được thực thi, sau đó tắt tiến trình khi không còn +cần nữa hoặc khi hệ thống phải khôi phục bộ nhớ cho các ứng dụng khác.
  • +
+ +

Bằng cách này, hệ thống Android triển khai nguyên tắc đặc quyền ít nhất. Cụ thể, +theo mặc định, mỗi ứng dụng chỉ có thể truy cập vào các thành phần mà nó cần để thực hiện công việc của mình và +không hơn. Điều này tạo ra một môi trường rất bảo mật mà trong đó một ứng dụng không thể truy cập các bộ phận của +hệ thống mà nó không được cấp quyền.

+ +

Tuy nhiên, có nhiều cách để một ứng dụng chia sẻ dữ liệu với các ứng dụng khác và để một +ứng dụng truy cập vào các dịch vụ của hệ thống:

+ +
    +
  • Có thể sắp xếp để hai ứng dụng chia sẻ cùng ID người dùng Linux, trong trường hợp đó +chúng có thể truy cập các tệp của nhau. Để tiết kiệm tài nguyên của hệ thống, các ứng dụng có +cùng ID người dùng cũng có thể sắp xếp để chạy trong cùng tiến trình Linux và chia sẻ cùng VM (các +ứng dụng cũng phải được ký bằng cùng chứng chỉ).
  • +
  • Một ứng dụng có thể yêu cầu quyền truy cập dữ liệu của thiết bị chẳng hạn như +danh bạ của người dùng, tin nhắn SMS, thiết bị lưu trữ gắn được (thẻ SD), máy ảnh, Bluetooth và nhiều nữa. Tất cả +quyền ứng dụng đều phải được cấp bởi người dùng tại thời điểm cài đặt.
  • +
+ +

Đó là nội dung cơ bản về cách mà một ứng dụng Android tồn tại trong hệ thống. Phần còn lại của +tài liệu này giới thiệu với bạn về:

+
    +
  • Các thành phần khuôn khổ cốt lõi định nghĩa ứng dụng của bạn.
  • +
  • Tệp bản kê khai mà trong đó bạn khai báo các thành phần và tính năng yêu cầu của thiết bị cho ứng dụng +của bạn.
  • +
  • Các tài nguyên tách riêng với mã ứng dụng và cho phép ứng dụng của bạn +tối ưu hóa hành vi của nó cho nhiều loại cấu hình thiết bị đa dạng.
  • +
+ + + +

Thành phần của Ứng dụng

+ +

Thành phần của ứng dụng là những khối dựng thiết yếu của một ứng dụng Android. Mỗi +thành phần là một điểm khác nhau mà qua đó hệ thống có thể vào ứng dụng của bạn. Không phải tất cả +thành phần đều là các điểm nhập thực tế cho người dùng và một số phụ thuộc vào nhau, nhưng mỗi thành phần tồn tại +như một thực thể riêng và đóng một vai trò riêng—mỗi thành phần là một khối dựng duy nhất +giúp định nghĩa hành vi chung của ứng dụng của bạn.

+ +

Có bốn loại thành phần ứng dụng khác nhau. Mỗi loại có một mục đích riêng +và có một vòng đời riêng, xác định cách thành phần được tạo lập và hủy.

+ +

Sau đây là bốn loại thành phần ứng dụng:

+ +
+ +
Hoạt động
+ +
Một hoạt động biểu diễn một màn hình đơn với một giao diện người dùng. Ví dụ, +một ứng dụng e-mail có thể có một hoạt động với chức năng hiển thị một danh sách +e-mail mới, một hoạt động khác để soạn e-mail, và một hoạt động khác để đọc e-mail. Mặc dù +các hoạt động cùng nhau tạo thành một trải nghiệm người dùng gắn kết trong ứng dụng e-mail, mỗi hoạt động +lại độc lập với nhau. Như vậy, một ứng dụng khác có thể khởi động bất kỳ hoạt động nào +trong số này (nếu ứng dụng e-mail cho phép nó). Ví dụ, một ứng dụng máy ảnh có thể khởi động +hoạt động trong ứng dụng e-mail có chức năng soạn thư mới, để người dùng chia sẻ một bức ảnh. + +

Hoạt động được triển khai như một lớp con của {@link android.app.Activity} và bạn có thể tìm hiểu thêm +về nó trong hướng dẫn dành cho nhà phát triển Hoạt động +.

+
+ + +
Dịch vụ
+ +
Một dịch vụ là một thành phần chạy ngầm để thực hiện các thao tác +chạy lâu hoặc để thực hiện công việc cho các tiến trình từ xa. Dịch vụ +không cung cấp giao diện người dùng. Ví dụ, một dịch vụ có thể phát nhạc dưới nền trong khi +người dùng đang ở một ứng dụng khác, hoặc nó có thể tải dữ liệu qua mạng mà không +chặn người dùng tương tác với hoạt động. Một thành phần khác, chẳng hạn như một hoạt động, có thể khởi động +dịch vụ và để nó chạy hoặc gắn kết với nó để tương tác với nó. + +

Dịch vụ được triển khai như một lớp con của {@link android.app.Service} và bạn có thể tìm hiểu +thêm về nó trong hướng dẫn cho nhà phát triển Dịch vụ +.

+
+ + +
Trình cung cấp Nội dung
+ +
Một trình cung cấp nội dung sẽ quản lý một tập dữ liệu ứng dụng được chia sẻ. Bạn có thể lưu trữ dữ liệu trong +hệ thống tệp, một cơ sở dữ liệu SQLite, trên web, hay bất kỳ vị trí lưu trữ liên tục nào khác mà +ứng dụng của bạn có thể truy cập. Thông qua trình cung cấp nội dung, các ứng dụng khác có thể truy vấn hay thậm chí sửa đổi +dữ liệu (nếu trình cung cấp nội dung cho phép). Ví dụ, hệ thống Android cung cấp một trình cung cấp +nội dung có chức năng quản lý thông tin danh bạ của người dùng. Như vậy, bất kỳ ứng dụng nào có các quyền +phù hợp đều có thể truy vấn bất kỳ phần nào của trình cung cấp nội dung (chẳng hạn như {@link +android.provider.ContactsContract.Data}) để đọc và ghi thông tin về một người cụ thể. + +

Trình cung cấp nội dung cũng hữu ích với việc đọc và ghi dữ liệu riêng tư đối với +ứng dụng của bạn và không được chia sẻ. Ví dụ, ứng dụng mẫu Note Pad sử dụng một +trình cung cấp nội dung để lưu các ghi chú.

+ +

Trình cung cấp nội dung được triển khai như một lớp con của {@link android.content.ContentProvider} +và phải triển khai một tập các API tiêu chuẩn cho phép các ứng dụng khác thực hiện +giao tác. Để biết thêm thông tin, xem hướng dẫn cho nhà phát triển Trình cung cấp Nội dung +.

+
+ + +
Hàm nhận quảng bá
+ +
Một hàm nhận quảng bá (broadcast receiver) là một thành phần có chức năng hồi đáp lại các thông báo +quảng bá trên toàn hệ thống. Nhiều quảng bá khởi nguồn từ hệ thống—ví dụ, một quảng bá thông báo +rằng màn hình đã tắt, pin yếu, hoặc một bức ảnh được chụp. +Các ứng dụng cũng có thể khởi tạo quảng bá—ví dụ như để các ứng dụng khác biết rằng +một phần dữ liệu đã được tải xuống thiết bị và có sẵn để họ sử dụng. Mặc dù các hàm nhận quảng bá +không hiển thị giao diện người dùng, chúng có thể tạo một thông báo thanh trạng thái +để cảnh báo người tiếp nhận khi xảy ra một sự kiện quảng bá. Tuy nhiên trường hợp phổ biến hơn đó là hàm nhận quảng bá chỉ +là một "cổng kết nối" tới các thành phần khác và nhằm mục đích thực hiện lượng công việc rất ít. Ví +dụ, nó có thể khởi tạo một dịch vụ để thực hiện một số công việc dựa trên sự kiện. + +

Hàm nhận quảng bá được triển khai như một lớp con của {@link android.content.BroadcastReceiver} +và mỗi quảng bá được chuyển giao như một đối tượng {@link android.content.Intent}. Để biết thêm thông tin, +hãy xem lớp {@link android.content.BroadcastReceiver}.

+
+ +
+ + + +

Một khía cạnh độc đáo trong thiết kế hệ thống Android đó là bất kỳ ứng dụng nào cũng có thể khởi động một thành phần của +ứng dụng khác. Ví dụ, nếu bạn muốn người dùng chụp +ảnh bằng máy ảnh của thiết bị, có thể có một ứng dụng khác có chức năng đó và +ứng dụng của bạn có thể sử dụng nó thay vì phát triển một hoạt động để tự chụp ảnh. Bạn không +cần tích hợp hay thậm chí là liên kết với mã từ ứng dụng của máy ảnh. +Thay vào đó, bạn đơn giản có thể khởi động hoạt động đó trong ứng dụng máy ảnh có chức năng +chụp ảnh. Khi hoàn thành, ảnh thậm chí được trả về ứng dụng của bạn để bạn có thể sử dụng nó. Đối với người dùng, +có vẻ như máy ảnh là một bộ phận thực sự trong ứng dụng của bạn.

+ +

Khi hệ thống khởi động một thành phần, nó sẽ khởi động tiến trình cho ứng dụng đó (nếu tiến trình không +đang chạy) và khởi tạo các lớp cần thiết cho thành phần. Ví dụ, nếu ứng dụng +của bạn khởi động hoạt động trong ứng dụng máy ảnh có chức năng chụp ảnh, hoạt động đó +sẽ chạy trong tiến trình thuộc về ứng dụng máy ảnh chứ không chạy trong tiến trình của ứng dụng của bạn. +Vì thế, không như ứng dụng trên hầu hết các hệ thống khác, ứng dụng Android không có một điểm nhập +duy nhất (ví dụ, không có chức năng {@code main()}).

+ +

Vì hệ thống chạy từng ứng dụng trong một tiến trình riêng với các quyền của tệp mà +hạn chế truy cập vào các ứng dụng khác, ứng dụng của bạn không thể trực tiếp kích hoạt một thành phần từ +một ứng dụng khác. Tuy nhiên, hệ thống Android có thể. Vì thế, để kích hoạt một thành phần trong +một ứng dụng khác, bạn phải chuyển giao một thông báo tới hệ thống trong đó nêu rõ ý định của bạn để +khởi động một thành phần cụ thể. Sau đó, hệ thống sẽ kích hoạt thành phần cho bạn.

+ + +

Kích hoạt Thành phần

+ +

Ba trong bốn loại thành phần—hoạt động, dịch vụ và +hàm nhận quảng bá—sẽ được kích hoạt bằng một thông báo không đồng bộ gọi là ý định. +Ý định sẽ gắn kết từng thành phần với nhau vào thời gian chạy (bạn có thể nghĩ chúng như là +các hàm nhắn tin có chức năng yêu cầu một hành động từ các thành phần khác), dù thành phần đó thuộc +về ứng dụng của bạn hay ứng dụng khác.

+ +

Một ý định được tạo thành bằng một đối tượng {@link android.content.Intent}, nó định nghĩa một thông báo để +kích hoạt một thành phần cụ thể hoặc một loại thành phần cụ thể—tương ứng, một ý định +có thể biểu thị hoặc không biểu thị.

+ +

Đối với các hoạt động và dịch vụ, ý định có chức năng định nghĩa một hành động sẽ thực hiện (ví dụ, "xem" hoặc +"gửi" gì đó) và có thể chỉ định URI của dữ liệu để hành động dựa trên đó (ngoài những điều khác mà +thành phần được khởi động có thể cần biết). Ví dụ, một ý định có thể truyền tải một yêu cầu +để một hoạt động hiển thị một hình ảnh hay mở một trang web. Trong một số trường hợp, bạn có thể khởi động một +hoạt động để nhận kết quả, trong trường hợp đó, hoạt động cũng trả về +kết quả trong một {@link android.content.Intent} (ví dụ, bạn có thể phát hành một ý định để cho phép +người dùng chọn một liên lạc cá nhân và yêu cầu trả nó về cho bạn—ý định trả về bao gồm một +URI chỉ đến liên lạc được chọn).

+ +

Đối với hàm nhận quảng bá, ý định chỉ định nghĩa +thông báo đang được quảng bá (ví dụ, một quảng bá để báo rằng pin của thiết bị yếu +sẽ chỉ bao gồm một xâu hành động chỉ báo rằng "pin yếu").

+ +

Loại thành phần còn lại, trình cung cấp nội dung, không được kích hoạt bởi ý định. Thay vào đó, nó được +kích hoạt khi được nhằm tới bởi một yêu cầu từ một {@link android.content.ContentResolver}. Bộ giải quyết +nội dung xử lý tất cả giao tác trực tiếp với trình cung cấp nội dung sao cho thành phần mà +đang thực hiện giao tác với trình cung cấp sẽ không cần mà thay vào đó gọi các phương pháp trên đối tượng {@link +android.content.ContentResolver}. Điều này để lại một lớp tóm tắt giữa trình cung cấp +nội dung và thành phần yêu cầu thông tin (để bảo mật).

+ +

Có các phương pháp riêng để kích hoạt từng loại thành phần:

+
    +
  • Bạn có thể khởi động một hoạt động (hoặc giao cho nó việc gì mới để làm) bằng cách +chuyển một {@link android.content.Intent} đến {@link android.content.Context#startActivity +startActivity()} hoặc {@link android.app.Activity#startActivityForResult startActivityForResult()} +(khi bạn muốn hoạt động trả về một kết quả).
  • +
  • Bạn có thể khởi động một dịch vụ (hoặc gửi chỉ dẫn mới tới một dịch vụ đang diễn ra) bằng cách +chuyển một {@link android.content.Intent} đến {@link android.content.Context#startService +startService()}. Hoặc bạn có thể gắn kết với dịch vụ bằng cách chuyển một {@link android.content.Intent} đến +{@link android.content.Context#bindService bindService()}.
  • +
  • Bạn có thể khởi tạo một quảng bá bằng cách chuyển {@link android.content.Intent} tới các phương pháp như +{@link android.content.Context#sendBroadcast(Intent) sendBroadcast()}, {@link +android.content.Context#sendOrderedBroadcast(Intent, String) sendOrderedBroadcast()}, hoặc {@link +android.content.Context#sendStickyBroadcast sendStickyBroadcast()}.
  • +
  • Bạn có thể thực hiện một truy vấn tới một trình cung cấp nội dung bằng cách gọi {@link +android.content.ContentProvider#query query()} trên một {@link android.content.ContentResolver}.
  • +
+ +

Để biết thêm thông tin về việc sử dụng ý định, hãy xem tài liệu Ý định và Bộ lọc +Ý định. Bạn cũng có thể xem thêm thông tin về việc kích hoạt các thành phần cụ thể +trong những tài liệu sau: Hoạt động, Dịch vụ, {@link +android.content.BroadcastReceiver} và Trình cung cấp Nội dung.

+ + +

Tệp Bản kê khai

+ +

Trước khi hệ thống Android có thể khởi động một thành phần ứng dụng, hệ thống phải biết rằng +thành phần đó tồn tại bằng cách đọc tệp {@code AndroidManifest.xml} của ứng dụng (tệp +"bản kê khai"). Ứng dụng của bạn phải khai báo tất cả thành phần của nó trong tệp này, nó phải nằm ở gốc của +thư mục dự án của ứng dụng.

+ +

Bản kê khai làm nhiều việc bên cạnh việc khai báo các thành phần của ứng dụng, +chẳng hạn như:

+
    +
  • Xác định bất kỳ quyền của người dùng nào mà ứng dụng yêu cầu, chẳng hạn như truy cập Internet hay +truy cập đọc vào danh bạ của người dùng.
  • +
  • Khai báo Mức API +tối thiểu mà ứng dụng yêu cầu dựa trên những API mà ứng dụng sử dụng.
  • +
  • Khai báo các tính năng phần cứng và phần mềm được sử dụng hoặc yêu cầu bởi ứng dụng, chẳng hạn như máy ảnh, +dịch vụ Bluetooth, hoặc màn hình cảm ứng đa điểm.
  • +
  • Các thư viện API mà ứng dụng cần được liên kết với (ngoài các API khuôn khổ +Android), chẳng hạn như thư viện Google Maps +.
  • +
  • Và hơn thế nữa
  • +
+ + +

Khai báo các thành phần

+ +

Nhiệm vụ chính của bản kê khai là thông báo cho hệ thống về các thành phần của ứng dụng. Ví +dụ, một tệp bản kê khai có thể khai báo một hoạt động như sau:

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<manifest ... >
+    <application android:icon="@drawable/app_icon.png" ... >
+        <activity android:name="com.example.project.ExampleActivity"
+                  android:label="@string/example_label" ... >
+        </activity>
+        ...
+    </application>
+</manifest>
+ +

Trong phần tử <application> +, thuộc tính {@code android:icon} sẽ trỏ đến các tài nguyên cho một biểu tượng có chức năng nhận biết +ứng dụng.

+ +

Trong phần tử <activity>, +thuộc tính {@code android:name} quy định tên lớp hoàn toàn đủ tiêu chuẩn của lớp con {@link +android.app.Activity} và các thuộc tính {@code android:label} quy định một xâu +để sử dụng làm nhãn hiển thị với người dùng đối với hoạt động.

+ +

Bạn phải khai báo tất cả thành phần của ứng dụng như sau:

+
    +
  • Các phần tử <activity> +cho hoạt động
  • +
  • Các phần tử <service> cho +dịch vụ
  • +
  • Các phần tử <receiver> +cho hàm nhận quảng bá
  • +
  • Các phần tử <provider> +cho trình cung cấp nội dung
  • +
+ +

Các hoạt động, dịch vụ và trình cung cấp nội dung mà bạn bao gồm trong nguồn của mình nhưng không khai báo +trong bản kê khai sẽ không hiển thị với hệ thống và hệ quả là không bao giờ chạy được. Tuy nhiên, +hàm nhận +quảng bá có thể hoặc được khai báo trong bản kê khai hoặc được tạo linh hoạt trong mã (dạng đối tượng +{@link android.content.BroadcastReceiver}) và được đăng ký với hệ thống bằng cách gọi +{@link android.content.Context#registerReceiver registerReceiver()}.

+ +

Để tìm hiểu thêm về cách cấu trúc tệp bản kê khai cho ứng dụng của mình, hãy xem tài liệu Tệp AndroidManifest.xml +.

+ + + +

Khai báo các khả năng của thành phần

+ +

Như đã nêu bên trên trong phần Kích hoạt các Thành phần, bạn có thể sử dụng một +{@link android.content.Intent} để khởi động các hoạt động, dịch vụ và hàm nhận quảng bá. Bạn có thể làm vậy bằng cách +công khai chỉ định thành phần đích (sử dụng tên lớp thành phần) trong ý định. Tuy nhiên, +sức mạnh thực sự của ý định nằm trong khái niệm ý định không biểu thị. Ý định không biểu thị +đơn thuần mô tả kiểu hành động cần thực hiện (và có thể có cả dữ liệu mà bạn muốn +thực hiện hành động) và cho phép hệ thống tìm một thành phần trên thiết bị có khả năng thực hiện +hành động và khởi động nó. Nếu có nhiều thành phần có thể thực hiện hành động được mô tả bởi +ý định, khi đó người dùng chọn ý định sẽ sử dụng.

+ +

Cách hệ thống nhận biết các thành phần có khả năng hồi đáp lại một ý định là bằng cách so sánh +ý định nhận được với các bộ lọc ý định được cung cấp trong tệp bản kê khai của các ứng dụng khác trên +thiết bị.

+ +

Khi bạn khai báo một hoạt động trong bản kê khai ứng dụng của mình, bạn có thể tùy chọn bao gồm +các bộ lọc ý định có chức năng khai báo các khả năng của hoạt động sao cho nó có thể hồi đáp lại ý định +từ các ứng dụng khác. Bạn có thể khai báo một bộ lọc ý định cho thành phần của mình bằng cách +thêm một phần tử {@code +<intent-filter>} làm con của phần tử công khai của thành phần đó.

+ +

Ví dụ, nếu bạn đã xây dựng một ứng dụng e-mail có một hoạt động soạn e-mail mới, bạn có thể +khai báo bộ lọc ý định đó để trả lời các ý định "gửi" (để gửi một e-mail mới) như sau:

+
+<manifest ... >
+    ...
+    <application ... >
+        <activity android:name="com.example.project.ComposeEmailActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.SEND" />
+                <data android:type="*/*" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </activity>
+    </application>
+</manifest>
+
+ +

Sau đó, nếu một ứng dụng khác tạo một ý định với hành động {@link +android.content.Intent#ACTION_SEND} và chuyển nó cho {@link android.app.Activity#startActivity +startActivity()}, hệ thống có thể khởi động hoạt động của bạn để người dùng có thể soạn thảo và gửi một +e-mail.

+ +

Để tìm hiểu thêm về việc tạo các bộ lọc ý định, hãy xem tài liệu Ý định và Bộ lọc Ý định. +

+ + + +

Khai báo các yêu cầu của ứng dụng

+ +

Có nhiều loại thiết bị dựa trên nền tảng Android và không phải tất cả chúng đều cung cấp +các tính năng và khả năng như nhau. Để tránh việc ứng dụng của bạn bị cài đặt trên các thiết bị +thiếu những tính năng mà ứng dụng của bạn cần, điều quan trọng là bạn phải định nghĩa rõ ràng một hồ sơ cho +các kiểu thiết bị mà ứng dụng của bạn hỗ trợ bằng cách khai báo các yêu cầu về thiết bị và phần mềm trong tệp +bản kê khai của mình. Hầu hết những khai báo này đều chỉ mang tính chất thông báo và hệ thống không đọc +chúng, nhưng các dịch vụ bên ngoài như Google Play thì có đọc để cung cấp tính năng lọc +cho người dùng khi họ tìm kiếm ứng dụng từ thiết bị của mình.

+ +

Ví dụ, nếu ứng dụng của bạn yêu cầu máy ảnh và sử dụng các API được giới thiệu trong Android 2.1 (API Mức 7), +bạn cần khai báo những điều này như yêu cầu trong tệp bản kê khai của mình như sau:

+ +
+<manifest ... >
+    <uses-feature android:name="android.hardware.camera.any"
+                  android:required="true" />
+    <uses-sdk android:minSdkVersion="7" android:targetSdkVersion="19" />
+    ...
+</manifest>
+
+ +

Lúc này, những thiết bị mà không có máy ảnh và có một phiên bản +Android thấp hơn 2.1 sẽ không thể cài đặt ứng dụng của bạn từ Google Play.

+ +

Tuy nhiên, bạn cũng có thể khai báo rằng ứng dụng của bạn sử dụng máy ảnh, nhưng không +yêu cầu nó. Trong trường hợp đó, ứng dụng của bạn phải đặt thuộc tính {@code required} +thành {@code "false"} và kiểm tra tại thời gian chạy xem +thiết bị có máy ảnh không và vô hiệu hóa bất kỳ tính năng máy ảnh nào cho phù hợp.

+ +

Bạn có thể tìm hiểu thêm thông tin về cách bạn có thể quản lý tính tương thích của ứng dụng của bạn với các thiết bị khác nhau +trong tài liệu Tính tương thích với Thiết bị +.

+ + + +

Tài nguyên Ứng dụng

+ +

Một ứng dụng Android được soạn không chỉ có mã—nó còn yêu cầu các tài nguyên +tách riêng với mã nguồn, chẳng hạn như hình ảnh, tệp âm thanh và bất kỳ thứ gì liên quan tới trình chiếu +trực quan của ứng dụng. Ví dụ, bạn nên định nghĩa các hoạt cảnh, menu, kiểu, màu sắc, +và bố trí của giao diện người dùng của hoạt động bằng các tệp XML. Việc sử dụng các tài nguyên ứng dụng giúp dễ dàng +cập nhật các đặc điểm khác nhau trong ứng dụng của bạn mà không sửa đổi mã và—bằng cách cung cấp +các tập hợp tài nguyên thay thế—cho phép bạn tối ưu hóa ứng dụng của mình cho nhiều loại +cấu hình thiết bị (chẳng hạn như ngôn ngữ và kích cỡ màn hình khác nhau).

+ +

Đối với mọi tài nguyên mà bạn bao gồm trong dự án Android của mình, bộ công cụ xây dựng SDK định nghĩa một ID số nguyên +duy nhất mà bạn có thể sử dụng để tham chiếu tài nguyên từ mã ứng dụng của mình hoặc từ +các tài nguyên khác được định nghĩa trong XML. Ví dụ, nếu ứng dụng của bạn chứa một tệp hình ảnh có tên {@code +logo.png} (được lưu trong thư mục {@code res/drawable/}), bộ công cụ SDK sẽ khởi tạo một ID tài nguyên +đặt tên là {@code R.drawable.logo} mà bạn có thể sử dụng để tham chiếu hình ảnh và chèn nó vào trong giao diện người dùng +của mình.

+ +

Một trong những khía cạnh quan trọng nhất của việc cung cấp tài nguyên tách riêng với mã nguồn của bạn +là khả năng cho phép bạn cung cấp các tài nguyên thay thế cho các +cấu hình thiết bị khác nhau. Ví dụ, bằng cách định nghĩa các xâu UI trong XML, bạn có thể biên dịch xâu sang +các ngôn ngữ khác và lưu các xâu đó vào tệp riêng. Sau đó, dựa vào một hạn định ngôn ngữ +mà bạn nối với tên của thư mục tài nguyên (chẳng hạn như {@code res/values-fr/} đối với các giá trị xâu +tiếng Pháp) và thiết đặt ngôn ngữ của người dùng, hệ thống Android sẽ áp dụng các xâu ngôn ngữ phù hợp +cho UI của bạn.

+ +

Android hỗ trợ nhiều hạn định khác nhau cho các tài nguyên thay thế của bạn. Hạn định +là một xâu ngắn mà bạn bao gồm trong tên của các thư mục tài nguyên của mình nhằm +định nghĩa cấu hình thiết bị cho những tài nguyên đó nên được sử dụng. Lấy một +ví dụ khác, bạn nên thường xuyên tạo các bố trí khác nhau cho hoạt động của mình, tùy vào hướng và kích cỡ +màn hình của thiết bị. Ví dụ, khi màn hình thiết bị ở hướng +đứng (cao), bạn có thể muốn một bố trí có các nút thẳng đứng, nhưng khi màn hình ở hướng +khổ ngang (rộng), các nút nên được căn ngang. Để thay đổi bố trí +tùy vào hướng, bạn có thể định nghĩa hai bố trí khác nhau và áp dụng hạn định +phù hợp cho tên thư mục của từng bố trí. Sau đó, hệ thống sẽ tự động áp dụng bố trí +phù hợp tùy thuộc vào hướng hiện tại của thiết bị.

+ +

Để biết thêm thông tin về các loại tài nguyên khác nhau mà bạn có thể bao gồm trong ứng dụng của mình và cách +tạo các tài nguyên thay thế cho những cấu hình thiết bị khác nhau, hãy đọc Cung cấp Tài nguyên.

+ + + +
+
+

Tiếp tục đọc về:

+
+
Ý định và Bộ lọc Ý định +
+
Thông tin về cách sử dụng các API {@link android.content.Intent} để + kích hoạt các thành phần của ứng dụng, chẳng hạn như hoạt động và dịch vụ, và cách tạo các thành phần cho ứng dụng của bạn + có sẵn để cho các ứng dụng khác sử dụng.
+
Hoạt động
+
Thông tin về cách tạo một thực thể của lớp {@link android.app.Activity}, + có chức năng cung cấp một màn hình riêng trong ứng dụng của bạn với một giao diện người dùng.
+
Cung cấp Tài nguyên
+
Thông tin về cách các ứng dụng Android được cấu trúc để tách riêng các tài nguyên ứng dụng khỏi + mã ứng dụng, bao gồm cách bạn có thể cung cấp các tài nguyên thay thế cho những + cấu hình thiết bị cụ thể. +
+
+
+
+

Bạn cũng có thể quan tâm tới:

+
+
Tính tương thích của Thiết bị
+
Thông tin về Android hoạt động trên các loại thiết bị khác nhau và giới thiệu + về cách bạn có thể tối ưu hóa ứng dụng của mình cho từng thiết bị hoặc hạn chế tính sẵn có của ứng dụng của bạn + đối với các thiết bị khác nhau.
+
Quyền của Hệ thống
+
Thông tin về cách Android hạn chế truy cập của ứng dụng vào một số API nhất định bằng một hệ thống + quyền cần có sự đồng ý của người dùng cho phép ứng dụng của bạn có thể sử dụng các API đó.
+
+
+
+ diff --git a/docs/html-intl/intl/vi/guide/components/index.jd b/docs/html-intl/intl/vi/guide/components/index.jd new file mode 100644 index 0000000000000000000000000000000000000000..966597d999c20cb89afb91b2f9fc86486d6d7a60 --- /dev/null +++ b/docs/html-intl/intl/vi/guide/components/index.jd @@ -0,0 +1,57 @@ +page.title=Thành phần Ứng dụng +page.landing=true +page.landing.intro=Khuôn khổ ứng dụng của Android cho phép bạn tạo lập nhiều ứng dụng đa dạng và sáng tạo bằng cách sử dụng một tập hợp các thành phần có thể tái sử dụng. Phần này giải thích cách bạn có thể xây dựng các thành phần định nghĩa các khối dựng cho ứng dụng của mình và cách kết nối chúng với nhau bằng cách sử dụng ý định. +page.metaDescription=Khuôn khổ ứng dụng của Android cho phép bạn tạo lập nhiều ứng dụng đa dạng và sáng tạo bằng cách sử dụng một tập hợp các thành phần có thể tái sử dụng. Phần này giải thích cách bạn có thể xây dựng các thành phần định nghĩa các khối dựng cho ứng dụng của mình và cách kết nối chúng với nhau bằng cách sử dụng ý định. +page.landing.image=images/develop/app_components.png +page.image=images/develop/app_components.png + +@jd:body + +
+ + + + + +
diff --git a/docs/html-intl/intl/vi/guide/components/intents-filters.jd b/docs/html-intl/intl/vi/guide/components/intents-filters.jd new file mode 100644 index 0000000000000000000000000000000000000000..cdc623f8e8ce93efd553d20db62992eb2d5dc041 --- /dev/null +++ b/docs/html-intl/intl/vi/guide/components/intents-filters.jd @@ -0,0 +1,899 @@ +page.title=Ý định và Bộ lọc Ý định +page.tags="IntentFilter" +@jd:body + + + + + + +

{@link android.content.Intent} là một đối tượng nhắn tin mà bạn có thể sử dụng để yêu cầu một hành động +từ một thành phần ứng dụng khác. +Mặc dù các ý định sẽ tạo điều kiện cho giao tiếp giữa các thành phần bằng một vài cách, có ba +trường hợp sử dụng cơ bản:

+ +
    +
  • Để bắt đầu một hoạt động: +

    {@link android.app.Activity} biểu diễn một màn hình đơn trong một ứng dụng. Bạn có thể bắt đầu một thực thể +mới của một {@link android.app.Activity} bằng cách chuyển {@link android.content.Intent} +sang {@link android.content.Context#startActivity startActivity()}. {@link android.content.Intent} +mô tả hoạt động cần bắt đầu và mang theo mọi dữ liệu cần thiết.

    + +

    Nếu bạn muốn nhận một kết quả từ hoạt động khi nó hoàn thành, +hãy gọi {@link android.app.Activity#startActivityForResult +startActivityForResult()}. Hoạt động của bạn nhận được kết quả +dưới dạng một đối tượng {@link android.content.Intent} riêng biệt trong lệnh gọi lại {@link +android.app.Activity#onActivityResult onActivityResult()} của hoạt động của bạn. +Để biết thêm thông tin, hãy xem hướng dẫn Hoạt động.

  • + +
  • Để bắt đầu một dịch vụ: +

    {@link android.app.Service} là một thành phần có chức năng thực hiện các thao tác dưới nền +mà không cần giao diện người dùng. Bạn có thể bắt đầu một dịch vụ để thực hiện một thao tác một lần +(chẳng hạn như tải xuống một tệp) bằng cách chuyển {@link android.content.Intent} +tới {@link android.content.Context#startService startService()}. {@link android.content.Intent} +mô tả dịch vụ cần bắt đầu và mang theo mọi dữ liệu cần thiết.

    + +

    Nếu dịch vụ được thiết kế với một giao diện máy khách-máy chủ, bạn có thể gắn kết với dịch vụ +từ một thành phần khác bằng cách chuyển {@link android.content.Intent} sang {@link +android.content.Context#bindService bindService()}. Để biết thêm thông tin, hãy xem hướng dẫn Dịch vụ.

  • + +
  • Để chuyển một quảng bá: +

    Quảng bá là một tin nhắn mà bất kỳ ứng dụng nào cũng có thể nhận được. Hệ thống sẽ chuyển các quảng bá +khác nhau tới các sự kiện hệ thống, chẳng hạn như khi hệ thống khởi động hoặc thiết bị bắt đầu sạc. +Bạn có thể chuyển một quảng bá tới các ứng dụng khác bằng cách chuyển một {@link android.content.Intent} +tới {@link android.content.Context#sendBroadcast(Intent) sendBroadcast()}, +{@link android.content.Context#sendOrderedBroadcast(Intent, String) +sendOrderedBroadcast()}, hoặc {@link +android.content.Context#sendStickyBroadcast sendStickyBroadcast()}.

    +
  • +
+ + + + +

Các Loại Ý định

+ +

Có hai loại ý định:

+ +
    +
  • Ý định biểu thị quy định thành phần cần bắt đầu theo tên (tên lớp hoàn toàn đạt tiêu chuẩn +). Thường bạn sẽ sử dụng một ý định biểu thị để bắt đầu một thành phần trong +ứng dụng của chính mình, vì bạn biết tên lớp của hoạt động hay dịch vụ mà mình muốn bắt đầu. Ví dụ +, bắt đầu một hoạt động mới để hồi đáp một hành động của người dùng hay bắt đầu một dịch vụ để tải xuống +tệp dưới nền.
  • + +
  • Ý định không biểu thị không chỉ định một thành phần cụ thể mà thay vào đó, sẽ khai báo một hành động thông thường +cần thực hiện, cho phép một thành phần từ một ứng dụng khác xử lý nó. Ví dụ, nếu bạn muốn +hiển thị cho người dùng một vị trí trên bản đồ, bạn có thể sử dụng một ý định không biểu thị để yêu cầu một ứng dụng +có khả năng khác hiển thị một vị trí được chỉ định trên bản đồ.
  • +
+ +

Khi bạn tạo một ý định biểu thị để bắt đầu một hoạt động hoặc dịch vụ, hệ thống ngay lập tức +sẽ bắt đầu thành phần ứng dụng được quy định trong đối tượng {@link android.content.Intent}.

+ +
+ +

Hình 1. Minh họa về cách một ý định không biểu thị được +chuyển thông qua hệ thống để bắt đầu một hoạt động khác: [1] Hoạt động A tạo một +{@link android.content.Intent} bằng một mô tả hành động và chuyển nó cho {@link +android.content.Context#startActivity startActivity()}. [2] Hệ thống Android tìm kiếm tất cả +ứng dụng xem có một bộ lọc ý định khớp với ý định đó không. Khi tìm thấy kết quả khớp, [3] hệ thống +sẽ bắt đầu hoạt động so khớp đó (Hoạt động B) bằng cách gọi ra phương pháp {@link +android.app.Activity#onCreate onCreate()} và chuyển nó cho {@link android.content.Intent}. +

+
+ +

Khi bạn tạo một ý định không biểu thị, hệ thống Android sẽ tìm kiếm thành phần phù hợp để bắt đầu +bằng cách so sánh nội dung của ý định với các bộ lọc ý định được khai báo trong tệp bản kê khai của các ứng dụng khác trên +thiết bị. Nếu ý định khớp với một bộ lọc ý định, hệ thống sẽ bắt đầu thành phần đó và chuyển cho nó +đối tượng {@link android.content.Intent}. Nếu có nhiều bộ lọc ý định tương thích, hệ thống +sẽ hiển thị một hộp thoại để người dùng có thể chọn ứng dụng sẽ sử dụng.

+ +

Bộ lọc ý định là một biểu thức trong tệp bản kê khai của một ứng dụng, có chức năng +chỉ định loại ý định mà thành phần +muốn nhận. Ví dụ, bằng cách khai báo một bộ lọc ý định cho một hoạt động, +bạn giúp các ứng dụng khác có thể trực tiếp bắt đầu hoạt động của mình với một loại ý định nhất định. +Tương tự, nếu bạn không khai báo bất kỳ bộ lọc ý định nào cho một hoạt động, khi đó nó chỉ có thể +được bắt đầu bằng một ý định biểu thị.

+ +

Chú ý: Để đảm bảo ứng dụng của bạn được bảo mật, luôn sử dụng một ý định +biểu thị khi bắt đầu một {@link android.app.Service} và không được +khai báo bộ lọc ý định cho các dịch vụ của bạn. Việc sử dụng một ý định không biểu thị để bắt đầu một dịch vụ sẽ là một nguy cơ +về bảo mật vì bạn không thể chắc chắn dịch vụ nào sẽ hồi đáp ý định đó, +và người dùng không thể thấy dịch vụ nào bắt đầu. Bắt đầu với Android 5.0 (API mức 21), hệ thống +sẽ đưa ra lỗi ngoại lệ nếu bạn gọi {@link android.content.Context#bindService bindService()} +bằng một ý định không biểu thị.

+ + + + + +

Xây dựng một Ý định

+ +

Đối tượng {@link android.content.Intent} mang thông tin mà hệ thống Android sử dụng +để xác định thành phần nào sẽ bắt đầu (chẳng hạn như tên thành phần chính xác hoặc thể loại +thành phần mà sẽ nhận ý định), cộng với thông tin mà thành phần nhận sử dụng để +thực hiện hành động cho phù hợp (chẳng hạn như hành động sẽ thực hiện và dữ liệu để dựa vào đó mà thực hiện).

+ + +

Thông tin chính chứa trong một {@link android.content.Intent} như sau:

+ +
+ +
Tên thành phần
+
Tên của thành phần sẽ bắt đầu. + +

Nội dung này không bắt buộc, nhưng đó là một thông tin trọng yếu để khiến một ý định trở nên +biểu thị, có nghĩa là ý định nên chỉ được chuyển tới thành phần ứng dụng +được xác định bởi tên thành phần đó. Nếu thiếu một tên thành phần, ý định trở thành không biểu thị và hệ thống +sẽ quyết định thành phần nào nhận ý định đó dựa trên các thông tin còn lại của ý định +(chẳng hạn như hành động, dữ liệu và thể loại—được mô tả bên dưới). Vì vậy, nếu bạn bắt đầu một thành phần +cụ thể trong ứng dụng của mình, bạn nên chỉ định tên thành phần.

+ +

Lưu ý: Khi bắt đầu một {@link android.app.Service}, bạn nên +luôn chỉ định tên thành phần. Nếu không, bạn không thể chắc chắn dịch vụ nào +sẽ hồi đáp ý định và người dùng không thể thấy dịch vụ nào bắt đầu.

+ +

Trường này của {@link android.content.Intent} là một đối tượng +{@link android.content.ComponentName} mà bạn có thể chỉ định bằng cách sử dụng một tên lớp +hoàn toàn đủ tiêu chuẩn của thành phần đích, bao gồm tên gói của ứng dụng. Ví dụ, +{@code com.example.ExampleActivity}. Bạn có thể đặt tên thành phần bằng {@link +android.content.Intent#setComponent setComponent()}, {@link android.content.Intent#setClass +setClass()}, {@link android.content.Intent#setClassName(String, String) setClassName()}, hoặc bằng + hàm dựng {@link android.content.Intent}.

+ +
+ +

Hành động
+
Một xâu quy định hành động thông thường sẽ thực hiện (chẳng hạn như xem hoặc chọn). + +

Trong trường hợp một ý định quảng bá, đây là hành động đã diễn ra và đang được báo cáo. +Hành động này quyết định phần lớn cách thức xác định cấu trúc phần còn lại của ý định—đặc biệt là +những gì chứa trong dữ liệu và phụ thêm. + +

Bạn có thể quy định các hành động của chính mình để các ý định bên trong ứng dụng của bạn sử dụng (hoặc để +các ứng dụng khác sử dụng nhằm gọi ra các thành phần trong ứng dụng của mình), nhưng bạn nên thường xuyên sử dụng hằng số hành động +được định nghĩa bởi lớp {@link android.content.Intent} hoặc các lớp khuôn khổ khác. Sau đây là một số +hành động thường dùng để bắt đầu một hoạt động:

+ +
+
{@link android.content.Intent#ACTION_VIEW}
+
Sử dụng hành động này trong một ý định với {@link + android.content.Context#startActivity startActivity()} khi bạn có một số thông tin mà + một hoạt động có thể hiển thị cho người dùng, chẳng hạn như ảnh để xem trong một ứng dụng bộ sưu tập ảnh, hay địa chỉ để + xem trong ứng dụng bản đồ.
+ +
{@link android.content.Intent#ACTION_SEND}
+
Còn được biết đến như là ý định "chia sẻ", bạn nên sử dụng kiểu này trong một ý định với {@link + android.content.Context#startActivity startActivity()} khi bạn có một số dữ liệu mà người dùng có thể + chia sẻ thông qua một ứng dụng khác, chẳng hạn như một ứng dụng e-mail hay ứng dụng chia sẻ mạng xã hội.
+
+ +

Xem tham chiếu lớp {@link android.content.Intent} để biết thêm +hằng số có chức năng định nghĩa các hành động thông thường. Những hành động khác được định nghĩa +ở phần khác trong khuôn khổ Android, chẳng hạn như trong {@link android.provider.Settings} đối với những hành động +có chức năng mở màn hình cụ thể trong ứng dụng Cài đặt của hệ thống.

+ +

Bạn có thể quy định hành động cho một ý định với {@link android.content.Intent#setAction +setAction()} hoặc với một hàm dựng {@link android.content.Intent}.

+ +

Nếu bạn định nghĩa các hành động của chính mình, nhớ nêu tên gói ứng dụng của bạn +làm tiền tố. Ví dụ:

+
static final String ACTION_TIMETRAVEL = "com.example.action.TIMETRAVEL";
+
+ +
Dữ liệu
+
URI (một đối tượng {@link android.net.Uri}) tham chiếu dữ liệu sẽ được hành động dựa trên nó và/hoặc kiểu MIME +của dữ liệu đó. Kiểu dữ liệu được cung cấp thường sẽ bị chi phối bởi hành động của ý định. Ví +dụ, nếu hành động là {@link android.content.Intent#ACTION_EDIT}, dữ liệu cần chứa +URI của tài liệu cần chỉnh sửa. + +

Khi tạo một ý định, +một điều thường quan trọng đó là quy định kiểu dữ liệu (kiểu MIME của nó) ngoài URI của nó. +Ví dụ, một hoạt động có thể hiển thị hình ảnh có thể sẽ không +phát được tệp âm thanh, ngay cả khi định dạng URI có thể tương tự. +Vì thế, việc quy định kiểu MIME cho dữ liệu của bạn sẽ giúp hệ thống +Android tìm được thành phần tốt nhất để nhận ý định của bạn. +Tuy nhiên, kiểu MIME đôi khi có thể được suy ra từ URI—cụ thể, khi dữ liệu là một URI +{@code content:}, có chức năng cho biết dữ liệu nằm trên thiết bị và được kiểm soát bởi một +{@link android.content.ContentProvider}, điều này khiến kiểu MIME của dữ liệu hiển thị đối với hệ thống.

+ +

Để chỉ đặt URI dữ liệu, hãy gọi {@link android.content.Intent#setData setData()}. +Để chỉ đặt kiểu MIME, hãy gọi {@link android.content.Intent#setType setType()}. Nếu cần, bạn +bạn có thể công khai đặt cả hai với {@link +android.content.Intent#setDataAndType setDataAndType()}.

+ +

Chú ý: Nếu bạn muốn đặt cả URI và kiểu MIME, +không gọi {@link android.content.Intent#setData setData()} và +{@link android.content.Intent#setType setType()} vì chúng sẽ vô hiệu hóa giá trị của nhau. +Luôn sử dụng {@link android.content.Intent#setDataAndType setDataAndType()} để đặt cả +URI và kiểu MIME.

+
+ +

Thể loại
+
Một xâu chứa thông tin bổ sung về kiểu thành phần +sẽ xử lý ý định. Trong một ý định có thể chứa +nhiều mô tả thể loại, nhưng hầu hết các ý định lại không yêu cầu thể loại. +Sau đây là một số thể loại thường gặp: + +
+
{@link android.content.Intent#CATEGORY_BROWSABLE}
+
Hoạt động mục tiêu cho phép chính nó được bắt đầu bởi một trình duyệt web để hiển thị dữ liệu + được một liên kết tham chiếu—chẳng hạn như một hình ảnh hay thư e-mail. +
+
{@link android.content.Intent#CATEGORY_LAUNCHER}
+
Hoạt động là hoạt động ban đầu của một tác vụ và được liệt kê trong + trình khởi chạy ứng dụng của hệ thống. +
+
+ +

Xem mô tả lớp {@link android.content.Intent} để biết danh sách đầy đủ về các +thể loại.

+ +

Bạn có thể quy định một thể loại bằng {@link android.content.Intent#addCategory addCategory()}.

+
+
+ + +

Những tính chất này được liệt kê ở trên (tên thành phần, hành động, dữ liệu và thể loại) biểu hiện các +đặc điểm xác định của một ý định. Bằng cách đọc những tính chất này, hệ thống Android +có thể quyết định nó sẽ bắt đầu thành phần ứng dụng nào.

+ +

Tuy nhiên, một ý định có thể mang thông tin bổ sung không ảnh hưởng tới +cách nó được giải quyết đối với một thành phần ứng dụng. Một ý định cũng có thể cung cấp:

+ +
+
Phụ thêm
+
Các cặp khóa-giá trị mang thông tin bổ sung cần thiết để hoàn thành hành động được yêu cầu. +Giống như việc một số hành động sử dụng các kiểu URI dữ liệu riêng, một số hành động cũng sử dụng các phần phụ thêm riêng. + +

Bạn có thể thêm dữ liệu phụ thêm bằng các phương pháp {@link android.content.Intent#putExtra putExtra()} khác nhau, +mỗi phương pháp chấp nhận hai tham số: tên khóa và giá trị. +Bạn cũng có thể tạo một đối tượng {@link android.os.Bundle} bằng tất cả dữ liệu phụ thêm, sau đó chèn + {@link android.os.Bundle} vào {@link android.content.Intent} bằng {@link +android.content.Intent#putExtras putExtras()}.

+ +

Ví dụ, khi tạo một ý định để gửi một e-mail bằng +{@link android.content.Intent#ACTION_SEND}, bạn có thể chỉ định người nhận "tới" bằng khóa +{@link android.content.Intent#EXTRA_EMAIL}, và chỉ định "chủ đề" bằng khóa +{@link android.content.Intent#EXTRA_SUBJECT}.

+ +

Lớp {@link android.content.Intent} quy định nhiều hằng số {@code EXTRA_*} cho +các kiểu dữ liệu chuẩn hóa. Nếu bạn cần khai báo các khóa phụ thêm của riêng mình (cho những ý định +mà ứng dụng của bạn nhận), hãy chắc chắn nêu tên gói ứng dụng của bạn +làm tiền tố. Ví dụ:

+
static final String EXTRA_GIGAWATTS = "com.example.EXTRA_GIGAWATTS";
+
+ +
Cờ
+
Cờ được định nghĩa trong lớp {@link android.content.Intent} có chức năng như siêu dữ liệu cho +ý định. Cờ có thể chỉ lệnh hệ thống Android về cách khởi chạy một hoạt động (ví dụ, hoạt động sẽ thuộc về +tác vụ nào +) và cách xử lý sau khi nó được khởi chạy (ví dụ, nó có thuộc về danh sách hoạt động +gần đây hay không). + +

Để biết thêm thông tin, hãy xem phương pháp {@link android.content.Intent#setFlags setFlags()}.

+
+ +
+ + + + +

Ví dụ về ý định biểu thị

+ +

Ý định biểu thị là ý định mà bạn sử dụng để khởi chạy một thành phần ứng dụng cụ thể +chẳng hạn như một hoạt động hay dịch vụ cụ thể trong ứng dụng của bạn. Để tạo một ý định biểu thị, hãy định nghĩa +tên thành phần cho đối tượng {@link android.content.Intent} —tất cả các +tính chất ý định khác đều không bắt buộc.

+ +

Ví dụ, nếu bạn đã xây dựng một dịch vụ trong ứng dụng của mình, đặt tên là {@code DownloadService}, +được thiết kế để tải xuống một tệp từ web, bạn có thể bắt đầu nó bằng mã sau:

+ +
+// Executed in an Activity, so 'this' is the {@link android.content.Context}
+// The fileUrl is a string URL, such as "http://www.example.com/image.png"
+Intent downloadIntent = new Intent(this, DownloadService.class);
+downloadIntent.setData({@link android.net.Uri#parse Uri.parse}(fileUrl));
+startService(downloadIntent);
+
+ +

Hàm dựng {@link android.content.Intent#Intent(Context,Class)} +cung cấp cho ứng dụng {@link android.content.Context} và thành phần +một đối tượng {@link java.lang.Class}. Như vậy, +ý định này rõ ràng sẽ bắt đầu lớp {@code DownloadService} trong ứng dụng.

+ +

Để biết thêm thông tin về việc xây dựng và bắt đầu một dịch vụ, hãy xem hướng dẫn +Dịch vụ.

+ + + + +

Ví dụ về ý định không biểu thị

+ +

Ý định không biểu thị quy định một hành động mà có thể gọi ra bất kỳ ứng dụng nào trên thiết bị mà có +khả năng thực hiện hành động đó. Việc sử dụng ý định không biểu thị có ích khi ứng dụng của bạn không thể thực hiện +hành động, nhưng các ứng dụng khác có thể và bạn muốn người dùng chọn ứng dụng sẽ sử dụng.

+ +

Ví dụ, nếu bạn có nội dung mà mình muốn người dùng chia sẻ với người khác, hãy tạo một ý định +với hành động {@link android.content.Intent#ACTION_SEND} và +bổ sung phần phụ thêm quy định nội dung sẽ chia sẻ. Khi bạn gọi +{@link android.content.Context#startActivity startActivity()} bằng ý định đó, người dùng có thể +chọn một ứng dụng để chia sẻ nội dung thông qua đó.

+ +

Chú ý: Có thể là người dùng sẽ không có bất kỳ +ứng dụng nào xử lý được ý định không biểu thị mà bạn gửi tới {@link android.content.Context#startActivity +startActivity()}. Nếu chuyện đó xảy ra, phương pháp gọi sẽ thất bại và ứng dụng của bạn sẽ gặp lỗi. Để xác minh rằng +một hoạt động sẽ nhận được ý định, hãy gọi {@link android.content.Intent#resolveActivity +resolveActivity()} trên đối tượng {@link android.content.Intent} của bạn. Nếu kết quả không rỗng +thì có ít nhất một ứng dụng có thể xử lý ý định và sẽ an toàn nếu gọi +{@link android.content.Context#startActivity startActivity()}. Nếu kết quả rỗng, +bạn không nên sử dụng ý định và, nếu có thể, bạn nên vô hiệu hóa tính năng phát hành +ý định.

+ + +
+// Create the text message with a string
+Intent sendIntent = new Intent();
+sendIntent.setAction(Intent.ACTION_SEND);
+sendIntent.putExtra(Intent.EXTRA_TEXT, textMessage);
+sendIntent.setType("text/plain");
+
+// Verify that the intent will resolve to an activity
+if (sendIntent.resolveActivity(getPackageManager()) != null) {
+    startActivity(sendIntent);
+}
+
+ +

Lưu ý: Trong trường hợp này, URI không được sử dụng, nhưng kiểu dữ liệu của ý định +sẽ được khai báo để quy định nội dung được thực hiện bởi phần phụ thêm.

+ + +

Khi {@link android.content.Context#startActivity startActivity()} được gọi, hệ thống +sẽ kiểm tra tất cả ứng dụng đã cài đặt để xác định những ứng dụng có thể xử lý kiểu ý định này (một +ý định với hành động {@link android.content.Intent#ACTION_SEND} và có mang dữ liệu +"văn bản/thuần"). Nếu chỉ có một ứng dụng có thể xử lý nó, ứng dụng đó sẽ mở ngay lập tức và được cấp cho +ý định. Nếu có nhiều hoạt động chấp nhận ý định, hệ thống +sẽ hiển thị một hộp thoại để người dùng có thể chọn ứng dụng sẽ sử dụng.

+ + +
+ +

Hình 2. Hộp thoại bộ chọn.

+
+ +

Bắt buộc một bộ chọn ứng dụng

+ +

Khi có nhiều hơn một ứng dụng hồi đáp ý định không biểu thị của bạn, +người dùng có thể chọn ứng dụng nào sẽ sử dụng và đặt ứng dụng đó làm lựa chọn mặc định cho +hành động. Điều này tốt khi thực hiện một hành động mà người dùng +có thể muốn sử dụng ứng dụng tương tự từ lúc này trở đi, chẳng hạn như khi mở một trang web (người dùng +thường thích ưu tiên sử dụng chỉ một trình duyệt web).

+ +

Tuy nhiên, nếu nhiều ứng dụng có thể hồi đáp ý định và người dùng có thể muốn sử dụng mỗi +lần một ứng dụng khác, bạn nên công khai hiển thị một hộp thoại bộ chọn. Hộp thoại bộ chọn yêu cầu +người dùng phải chọn ứng dụng sẽ sử dụng mỗi lần cho hành động (người dùng không thể chọn một ứng dụng mặc định cho +hành động). Ví dụ, khi ứng dụng của bạn thực hiện "chia sẻ" với hành động {@link +android.content.Intent#ACTION_SEND}, người dùng có thể muốn chia sẻ bằng cách sử dụng một ứng dụng khác tùy vào +tình hình thực tế của họ, vì thế bạn nên luôn sử dụng hộp thoại bộ chọn như minh họa trong hình 2.

+ + + + +

Để hiển thị bộ chọn, hãy tạo một {@link android.content.Intent} bằng cách sử dụng {@link +android.content.Intent#createChooser createChooser()} và chuyển nó sang {@link +android.app.Activity#startActivity startActivity()}. Ví dụ:

+ +
+Intent sendIntent = new Intent(Intent.ACTION_SEND);
+...
+
+// Always use string resources for UI text.
+// This says something like "Share this photo with"
+String title = getResources().getString(R.string.chooser_title);
+// Create intent to show the chooser dialog
+Intent chooser = Intent.createChooser(sendIntent, title);
+
+// Verify the original intent will resolve to at least one activity
+if (sendIntent.resolveActivity(getPackageManager()) != null) {
+    startActivity(chooser);
+}
+
+ +

Một hộp thoại hiển thị với một danh sách ứng dụng hồi đáp lại ý định được chuyển sang phương pháp {@link +android.content.Intent#createChooser createChooser()} và sử dụng văn bản được cung cấp làm +tiêu đề của hộp thoại.

+ + + + + + + + + +

Nhận một Ý định Không biểu thị

+ +

Để quảng cáo những ý định không biểu thị mà ứng dụng của bạn có thể nhận, hãy khai báo một hoặc nhiều bộ lọc ý định cho +từng thành phần ứng dụng của bạn với một phần tử {@code <intent-filter>} +trong tệp bản kê khai của mình. +Mỗi bộ lọc ý định sẽ quy định loại ý định mà nó chấp nhận dựa trên hành động, +dữ liệu và thể loại của ý định. Hệ thống sẽ chỉ chuyển một ý định không biểu thị tới thành phần ứng dụng của bạn nếu +ý định đó có thể chuyển qua một trong các bộ lọc ý định của bạn.

+ +

Lưu ý: Ý định biểu thị luôn được chuyển tới mục tiêu của mình, +không phụ thuộc vào bất kỳ bộ lọc ý định nào mà thành phần khai báo.

+ +

Một thành phần ứng dụng nên khai báo các bộ lọc riêng cho từng công việc duy nhất mà nó có thể thực hiện. +Ví dụ, một hoạt động trong một ứng dụng bộ sưu tập ảnh có thể có hai bộ lọc: một bộ lọc +để xem một hình ảnh và một bộ lọc để chỉnh sửa một hình ảnh. Khi hoạt động bắt đầu, +nó sẽ kiểm tra {@link android.content.Intent} và quyết định cách xử lý dựa trên thông tin +trong {@link android.content.Intent} (chẳng hạn như có hiển thị các điều khiển của trình chỉnh sửa hoặc không).

+ +

Mỗi bộ lọc ý định sẽ được định nghĩa bởi một phần tử {@code <intent-filter>} +trong tệp bản kê khai của ứng dụng, được lồng trong thành phần ứng dụng tương ứng (chẳng hạn như +một phần tử {@code <activity>} +). Bên trong {@code <intent-filter>}, +bạn có thể quy định loại ý định sẽ chấp nhận bằng cách sử dụng một hoặc nhiều +phần tử trong ba phần tử sau:

+ +
+
{@code <action>}
+
Khai báo hành động ý định được chấp nhận, trong thuộc tính {@code name}. Giá trị + phải là giá trị xâu ký tự của một hành động chứ không phải hằng số lớp.
+
{@code <data>}
+
Khai báo kiểu dữ liệu được chấp nhận, bằng cách sử dụng một hoặc nhiều thuộc tính quy định + các khía cạnh của URI dữ liệu (scheme, host, port, + path, v.v.) và kiểu MIME.
+
{@code <category>}
+
Khai báo thể loại ý định được chấp nhận, trong thuộc tính {@code name}. Giá trị + phải là giá trị xâu ký tự của một hành động chứ không phải hằng số lớp. + +

Lưu ý: Để nhận các ý định không biểu thị, bạn + phải nêu thể loại + {@link android.content.Intent#CATEGORY_DEFAULT} trong bộ lọc ý định. Các phương pháp + {@link android.app.Activity#startActivity startActivity()} và + {@link android.app.Activity#startActivityForResult startActivityForResult()} xử lý tất cả ý định + như thể chúng khai báo thể loại {@link android.content.Intent#CATEGORY_DEFAULT}. + Nếu bạn không khai báo thể loại này trong bộ lọc ý định của mình, không có ý định không biểu thị nào sẽ phân giải thành + hoạt động của bạn.

+
+
+ +

Ví dụ, sau đây là một khai báo hoạt động với một bộ lọc ý định để nhận một ý định +{@link android.content.Intent#ACTION_SEND} khi kiểu dữ liệu là văn bản:

+ +
+<activity android:name="ShareActivity">
+    <intent-filter>
+        <action android:name="android.intent.action.SEND"/>
+        <category android:name="android.intent.category.DEFAULT"/>
+        <data android:mimeType="text/plain"/>
+    </intent-filter>
+</activity>
+
+ +

Không sao nếu tạo một bộ lọc chứa nhiều hơn một thực thể của +{@code <action>}, +{@code <data>}, hoặc +{@code <category>}. +Nếu làm vậy, bạn chỉ cần chắc chắn rằng thành phần có thể xử lý bất kỳ và tất cả các cách kết hợp +những phần tử bộ lọc đó.

+ +

Khi bạn muốn xử lý nhiều kiểu ý định, nhưng chỉ theo các cách kết hợp cụ thể giữa +hành động, dữ liệu và kiểu thể loại, khi đó bạn cần tạo nhiều bộ lọc ý định.

+ + + + +

Ý định không biểu thị sẽ được kiểm tra dựa trên một bộ lọc bằng cách so sánh ý định với từng phần tử trong số +ba phần tử. Để được chuyển tới thành phần, ý định phải vượt qua tất cả ba lần kiểm tra. +Nếu không khớp với thậm chí chỉ một lần thì hệ thống Android sẽ không chuyển ý định tới +thành phần. Tuy nhiên, vì một thành phần có thể có nhiều bộ lọc ý định, ý định mà không chuyển qua +một trong các bộ lọc của thành phần có thể chuyển qua trên một bộ lọc khác. +Bạn có thể tìm hiểu thêm thông tin về cách hệ thống giải quyết ý định trong phần bên dưới +về Giải quyết Ý định.

+ +

Chú ý: Để tránh vô ý chạy +{@link android.app.Service} của một ứng dụng khác, hãy luôn sử dụng một ý định biểu thị để bắt đầu dịch vụ của chính bạn và không được +khai báo các bộ lọc ý định cho dịch vụ của bạn.

+ +

Lưu ý: +Đối với tất cả hoạt động, bạn phải khai báo các bộ lọc ý định của mình trong một tệp bản kê khai. +Tuy nhiên, các bộ lọc cho hàm nhận quảng bá có thể được đăng ký linh hoạt bằng cách gọi +{@link android.content.Context#registerReceiver(BroadcastReceiver, IntentFilter, String, +Handler) registerReceiver()}. Sau đó, bạn có thể bỏ đăng ký hàm nhận đó bằng {@link +android.content.Context#unregisterReceiver unregisterReceiver()}. Làm vậy sẽ cho phép ứng dụng của bạn +lắng nghe các quảng bá cụ thể chỉ trong một khoảng thời gian xác định trong khi ứng dụng của bạn +đang chạy.

+ + + + + + + +

Ví dụ về bộ lọc

+ +

Để hiểu hơn về một số hành vi của bộ lọc ý định, hãy xem đoạn mã HTML sau +từ tệp bản kê khai của một ứng dụng chia sẻ mạng xã hội.

+ +
+<activity android:name="MainActivity">
+    <!-- This activity is the main entry, should appear in app launcher -->
+    <intent-filter>
+        <action android:name="android.intent.action.MAIN" />
+        <category android:name="android.intent.category.LAUNCHER" />
+    </intent-filter>
+</activity>
+
+<activity android:name="ShareActivity">
+    <!-- This activity handles "SEND" actions with text data -->
+    <intent-filter>
+        <action android:name="android.intent.action.SEND"/>
+        <category android:name="android.intent.category.DEFAULT"/>
+        <data android:mimeType="text/plain"/>
+    </intent-filter>
+    <!-- This activity also handles "SEND" and "SEND_MULTIPLE" with media data -->
+    <intent-filter>
+        <action android:name="android.intent.action.SEND"/>
+        <action android:name="android.intent.action.SEND_MULTIPLE"/>
+        <category android:name="android.intent.category.DEFAULT"/>
+        <data android:mimeType="application/vnd.google.panorama360+jpg"/>
+        <data android:mimeType="image/*"/>
+        <data android:mimeType="video/*"/>
+    </intent-filter>
+</activity>
+
+ +

Hoạt động thứ nhất, {@code MainActivity}, là điểm mục nhập chính của ứng dụng—hoạt động này +sẽ mở khi người dùng khởi tạo ban đầu ứng dụng bằng biểu tượng trình khởi chạy:

+
    +
  • Hành động {@link android.content.Intent#ACTION_MAIN} thể hiện + đây là điểm mục nhập chính và không yêu cầu bất kỳ dữ liệu ý định nào.
  • +
  • Thể loại {@link android.content.Intent#CATEGORY_LAUNCHER} cho biết rằng biểu tượng + của hoạt động này nên được đặt trong trình khởi chạy ứng dụng của hệ thống. Nếu phần tử {@code <activity>} + không quy định một biểu tượng bằng {@code icon}, khi đó hệ thống sẽ sử dụng biểu tượng từ phần tử {@code <application>} +.
  • +
+

Hai nội dung này phải được ghép đôi cùng nhau để hoạt động xuất hiện trong trình khởi chạy ứng dụng.

+ +

Hoạt động thứ hai, {@code ShareActivity}, có mục đích để tạo điều kiện chia sẻ nội dung văn bản và +phương tiện. Mặc dù người dùng có thể nhập hoạt động này bằng cách điều hướng tới nó từ {@code MainActivity}, +họ cũng có thể nhập {@code ShareActivity} trực tiếp từ một ứng dụng khác mà phát hành +ý định không biểu thị khớp với một trong hai bộ lọc ý định.

+ +

Lưu ý: Kiểu MIME, +{@code +application/vnd.google.panorama360+jpg}, là một kiểu dữ liệu đặc biệt quy định +ảnh chụp toàn cảnh mà bạn có thể xử lý bằng các API Google +panorama.

+ + + + + + + + + + + + + +

Sử dụng một Ý định Chờ

+ +

Đối tượng {@link android.app.PendingIntent} là một trình bao bọc xung quanh một đối tượng {@link +android.content.Intent}. Mục đích chính của một {@link android.app.PendingIntent} + là cấp quyền cho một ứng dụng ngoài +để sử dụng {@link android.content.Intent} chứa trong nó như thể nó được thực thi từ tiến trình +của chính ứng dụng của bạn.

+ +

Các trường hợp sử dụng chính đối với một ý định chờ bao gồm:

+
    +
  • Khai báo một ý định cần được thực thi khi người dùng thực hiện một hành động bằng Thông báo của bạn + ({@link android.app.NotificationManager} + của hệ thống Android thực thi {@link android.content.Intent}). +
  • Khai báo một ý định cần được thực thi khi người dùng thực hiện một hành động bằng + App Widget của bạn + (ứng dụng màn hình Trang chủ thực thi {@link android.content.Intent}). +
  • Khai báo một ý định cần được thực thi tại một thời điểm xác định trong tương lai ( +{@link android.app.AlarmManager} của hệ thống Android thực thi {@link android.content.Intent}). +
+ +

Vì mỗi đối tượng {@link android.content.Intent} được thiết kế để được xử lý bởi một +loại thành phần ứng dụng cụ thể (hoặc là {@link android.app.Activity}, {@link android.app.Service}, hay + {@link android.content.BroadcastReceiver}), vì thế {@link android.app.PendingIntent} cũng +phải được tạo lập với cân nhắc tương tự. Khi sử dụng một ý định chờ, ứng dụng của bạn sẽ không +thực thi ý định bằng một lệnh gọi chẳng hạn như {@link android.content.Context#startActivity +startActivity()}. Thay vào đó, bạn phải khai báo loại thành phần theo ý định khi bạn tạo lập +{@link android.app.PendingIntent} bằng cách gọi phương pháp trình tạo lập tương ứng:

+ +
    +
  • {@link android.app.PendingIntent#getActivity PendingIntent.getActivity()} đối với một + {@link android.content.Intent} mà bắt đầu {@link android.app.Activity}.
  • +
  • {@link android.app.PendingIntent#getService PendingIntent.getService()} đối với một + {@link android.content.Intent} mà bắt đầu {@link android.app.Service}.
  • +
  • {@link android.app.PendingIntent#getBroadcast PendingIntent.getBroadcast()} đối với một + {@link android.content.Intent} mà bắt đầu {@link android.content.BroadcastReceiver}.
  • +
+ +

Trừ khi ứng dụng của bạn đang nhận ý định chờ từ các ứng dụng khác, +các phương pháp để tạo lập {@link android.app.PendingIntent} trên là những phương pháp +{@link android.app.PendingIntent} duy nhất mà bạn sẽ cần.

+ +

Mỗi phương pháp sẽ lấy ứng dụng {@link android.content.Context} hiện tại, +{@link android.content.Intent} mà bạn muốn bao bọc, và một hoặc nhiều cờ quy định +cách thức sử dụng ý định (chẳng hạn như ý định có thể được sử dụng nhiều hơn một lần hay không).

+ +

Bạn có thể tham khảo thêm thông tin về việc sử dụng ý định chờ trong tài liệu cho từng +trường hợp sử dụng tương ứng chẳng hạn như trong hướng dẫn về API Thông báo +và App Widgets.

+ + + + + + + +

Giải quyết Ý định

+ + +

Khi hệ thống nhận được một ý định không biểu thị nhằm bắt đầu một hoạt động, nó sẽ tìm +hoạt động tốt nhất cho ý định đó bằng cách so sánh ý định với các bộ lọc ý định dựa trên ba phương diện:

+ +
    +
  • Hành động của ý định +
  • Dữ liệu của ý định (cả URI và kiểu dữ liệu) +
  • Thể loại của ý định +
+ +

Các phần sau mô tả cách một ý định được so khớp với (các) thành phần phù hợp +về phương diện bộ lọc ý định được khai báo như thế nào trong tệp bản kê khai của một ứng dụng.

+ + +

Kiểm tra hành động

+ +

Để quy định các hành động của ý định được chấp nhận, một bộ lọc ý định có thể khai báo 0 phần tử +{@code +<action>} hoặc nhiều hơn. Ví dụ:

+ +
+<intent-filter>
+    <action android:name="android.intent.action.EDIT" />
+    <action android:name="android.intent.action.VIEW" />
+    ...
+</intent-filter>
+
+ +

Để vượt qua bộ lọc này, hành động được quy định trong {@link android.content.Intent} + phải khớp với một trong các hành động được liệt kê trong bộ lọc.

+ +

Nếu bộ lọc không liệt kê bất kỳ hành động nào thì sẽ không có gì để +ý định so khớp, vì thế tất cả ý định sẽ không vượt qua kiểm tra. Tuy nhiên, nếu một {@link android.content.Intent} +không quy định một hành động, nó sẽ vượt qua kiểm tra (miễn là bộ lọc +chứa ít nhất một hành động).

+ + + +

Kiểm tra thể loại

+ +

Để quy định các thể loại của ý định được chấp nhận, một bộ lọc ý định có thể khai báo 0 phần tử +{@code +<category>} hoặc nhiều hơn. Ví dụ:

+ +
+<intent-filter>
+    <category android:name="android.intent.category.DEFAULT" />
+    <category android:name="android.intent.category.BROWSABLE" />
+    ...
+</intent-filter>
+
+ +

Để một ý định vượt qua kiểm tra thể loại, mỗi thể loại trong {@link android.content.Intent} +phải khớp với một thể loại trong bộ lọc. Trường hợp ngược lại là không cần thiết—bộ lọc ý định có thể +khai báo nhiều thể loại hơn được quy định trong {@link android.content.Intent} và +{@link android.content.Intent} sẽ vẫn vượt qua. Vì thế, ý định không có thể loại +luôn vượt qua kiểm tra này, không phụ thuộc vào những thể loại nào được khai báo trong bộ lọc.

+ +

Lưu ý: +Android sẽ tự động áp dụng thể loại {@link android.content.Intent#CATEGORY_DEFAULT} +cho tất cả ý định không biểu thị được chuyển tới {@link +android.content.Context#startActivity startActivity()} và {@link +android.app.Activity#startActivityForResult startActivityForResult()}. +Vì thế, nếu bạn muốn hoạt động của mình nhận ý định không biểu thị, nó phải +nêu một thể loại cho {@code "android.intent.category.DEFAULT"} trong các bộ lọc ý định của mình (như +được minh họa trong ví dụ {@code <intent-filter>} trước đó.

+ + + +

Kiểm tra dữ liệu

+ +

Để quy định dữ liệu của ý định được chấp nhận, một bộ lọc ý định có thể khai báo 0 phần tử +{@code +<data>} hoặc nhiều hơn. Ví dụ:

+ +
+<intent-filter>
+    <data android:mimeType="video/mpeg" android:scheme="http" ... />
+    <data android:mimeType="audio/mpeg" android:scheme="http" ... />
+    ...
+</intent-filter>
+
+ +

Mỗi phần tử <data> +có thể quy định một cấu trúc URI và kiểu dữ liệu (kiểu phương tiện MIME). Có các thuộc tính +riêng — {@code scheme}, {@code host}, {@code port}, +và {@code path} — cho từng phần của URI: +

+ +

{@code <scheme>://<host>:<port>/<path>}

+ +

+Ví dụ: +

+ +

{@code content://com.example.project:200/folder/subfolder/etc}

+ +

Trong URI này, lược đồ là {@code content}, máy chủ là {@code com.example.project}, +cổng là {@code 200}, và đường dẫn là {@code folder/subfolder/etc}. +

+ +

Mỗi thuộc tính sau đều không bắt buộc trong một phần tử {@code <data>}, +nhưng có sự phụ thuộc mang tính chất tuyến tính:

+
    +
  • Nếu không quy định một lược đồ thì máy chủ bị bỏ qua.
  • +
  • Nếu không quy định một máy chủ thì cổng bị bỏ qua.
  • +
  • Nếu không quy định cả lược đồ và máy chủ thì đường dẫn bị bỏ qua.
  • +
+ +

Khi URI trong một ý định được so sánh với đặc tả URI trong một bộ lọc, +nó chỉ được so sánh với các bộ phận của URI được nêu trong bộ lọc. Ví dụ:

+
    +
  • Nếu một bộ lọc chỉ quy định một lược đồ, tất cả URI có lược đồ đó sẽ khớp +với bộ lọc.
  • +
  • Nếu một bộ lọc quy định một lược đồ và thẩm quyền nhưng không có đường dẫn, tất cả URI +với cùng lược đồ và thẩm quyền sẽ thông qua bộ lọc, không phụ thuộc vào đường dẫn của nó.
  • +
  • Nếu bộ lọc quy định một lược đồ, thẩm quyền và đường dẫn, chỉ những URI có cùng lược đồ, +thẩm quyền và đường dẫn mới thông qua bộ lọc.
  • +
+ +

Lưu ý: Đặc tả đường dẫn có thể +chứa một ký tự đại diện dấu sao (*) để yêu cầu chỉ khớp một phần với tên đường dẫn.

+ +

Kiểm tra dữ liệu so sánh cả URI và kiểu MIME trong ý định với một URI +và kiểu MIME được quy định trong bộ lọc. Các quy tắc như sau: +

+ +
    +
  1. Một ý định mà không chứa URI cũng như kiểu MIME sẽ chỉ vượt qua +kiểm tra nếu bộ lọc không quy định bất kỳ URI hay kiểu MIME nào.
  2. + +
  3. Một ý định chứa URI nhưng không có kiểu MIME (không biểu thị cũng như suy luận được từ +URI) sẽ chỉ vượt qua kiểm tra nếu URI của nó khớp với định dạng URI của bộ lọc +và bộ lọc tương tự không quy định một kiểu MIME.
  4. + +
  5. Một ý định chứa kiểu MIME nhưng không chứa URI sẽ chỉ vượt qua kiểm tra +nếu bộ lọc liệt kê cùng kiểu MIME và không quy định một định dạng URI.
  6. + +
  7. Ý định mà chứa cả URI và kiểu MIME (hoặc biểu thị hoặc suy ra được từ +URI) sẽ chỉ vượt qua phần kiểu MIME của kiểm tra nếu kiểu đó +khớp với kiểu được liệt kê trong bộ lọc. Nó vượt qua phần URI của kiểm tra +nếu URI của nó khớp với một URI trong bộ lọc hoặc nếu nó có một {@code content:} +hoặc {@code file:} URI và bộ lọc không quy định một URI. Nói cách khác, +một thành phần được giả định là hỗ trợ dữ liệu {@code content:} và {@code file:} nếu +bộ lọc của nó liệt kê chỉ một kiểu MIME.

  8. +
+ +

+Quy tắc cuối cùng này, quy tắc (d), phản ánh kỳ vọng +rằng các thành phần có thể nhận được dữ liệu cục bộ từ một tệp hoặc trình cung cấp nội dung. +Vì thế, các bộ lọc của chúng có thể chỉ liệt kê một kiểu dữ liệu và không cần công khai +nêu tên {@code content:} và các lược đồ {@code file:}. +Đây là một trường hợp điển hình. Phần tử {@code <data>} như + sau, ví dụ, sẽ thông báo cho Android biết rằng thành phần có thể nhận được dữ liệu ảnh từ một trình cung cấp +nội dung và sẽ hiển thị nó: +

+ +
+<intent-filter>
+    <data android:mimeType="image/*" />
+    ...
+</intent-filter>
+ +

+Vì hầu hết dữ liệu có sẵn đều được cấp phát bởi các trình cung cấp nội dung, những bộ lọc mà +quy định một kiểu dữ liệu chứ không phải URI có lẽ là phổ biến nhất. +

+ +

+Một cấu hình phổ biến khác đó là các bộ lọc có một lược đồ và một kiểu dữ liệu. Ví +dụ, một phần tử {@code <data>} +như sau thông báo cho Android rằng +thành phần có thể truy xuất dữ liệu video từ mạng để thực hiện hành động: +

+ +
+<intent-filter>
+    <data android:scheme="http" android:type="video/*" />
+    ...
+</intent-filter>
+ + + +

So khớp ý định

+ +

Các ý định được so khớp với các bộ lọc ý định không chỉ để khám phá một thành phần +mục tiêu cần kích hoạt, mà còn để khám phá điều gì đó về tập hợp +các thành phần trên thiết bị. Ví dụ, ứng dụng Trang chủ đưa trình khởi chạy ứng dụng +vào bằng cách tìm tất cả hoạt động có bộ lọc ý định mà quy định hành động +{@link android.content.Intent#ACTION_MAIN} và thể loại +{@link android.content.Intent#CATEGORY_LAUNCHER}.

+ +

Ứng dụng của bạn có thể sử dụng so khớp ý định theo cách tương tự. +{@link android.content.pm.PackageManager} có một tập hợp các phương pháp{@code query...()} +trả về tất cả thành phần có thể chấp nhận một ý định cụ thể, và +một chuỗi các phương pháp {@code resolve...()} tương tự để xác định thành phần +tốt nhất nhằm hồi đáp lại một ý định. Ví dụ, +{@link android.content.pm.PackageManager#queryIntentActivities +queryIntentActivities()} sẽ trả về một danh sách tất cả hoạt động có thể thực hiện +ý định được chuyển qua như một tham đối, và {@link +android.content.pm.PackageManager#queryIntentServices +queryIntentServices()} trả về một danh sách dịch vụ tương tự. +Cả hai phương pháp đều không kích hoạt các thành phần; chúng chỉ liệt kê những thành phần +có thể hồi đáp. Có một phương pháp tương tự, +{@link android.content.pm.PackageManager#queryBroadcastReceivers +queryBroadcastReceivers()}, dành cho hàm nhận quảng bá. +

+ + + + diff --git a/docs/html-intl/intl/vi/guide/components/loaders.jd b/docs/html-intl/intl/vi/guide/components/loaders.jd new file mode 100644 index 0000000000000000000000000000000000000000..b6d277f3d527702311e547f5702106bcca55bfbd --- /dev/null +++ b/docs/html-intl/intl/vi/guide/components/loaders.jd @@ -0,0 +1,494 @@ +page.title=Trình tải +parent.title=Hoạt động +parent.link=activities.html +@jd:body +
+
+

Trong tài liệu này

+
    +
  1. Tổng quan về API Trình tải
  2. +
  3. Sử dụng các Trình tải trong một Ứng dụng +
      +
    1. +
    2. Khởi động một Trình tải
    3. +
    4. Khởi động lại một Trình tải
    5. +
    6. Sử dụng các Phương pháp Gọi lại LoaderManager
    7. +
    +
  4. +
  5. Ví dụ +
      +
    1. Thêm Ví dụ
    2. +
    +
  6. +
+ +

Lớp khóa

+
    +
  1. {@link android.app.LoaderManager}
  2. +
  3. {@link android.content.Loader}
  4. + +
+ +

Các mẫu liên quan

+
    +
  1. +LoaderCursor
  2. +
  3. +LoaderThrottle
  4. +
+
+
+ +

Được giới thiệu trong Android 3.0, trình tải giúp việc tải dữ liệu không đồng bộ +trong một hoạt động hoặc phân đoạn trở nên dễ dàng. Trình tải có những đặc điểm sau:

+
    +
  • Chúng sẵn có cho mọi {@link android.app.Activity} và {@link +android.app.Fragment}.
  • +
  • Chúng cung cấp khả năng tải dữ liệu không đồng bộ.
  • +
  • Chúng theo dõi nguồn dữ liệu của mình và chuyển giao kết quả mới khi nội dung +thay đổi.
  • +
  • Chúng tự động kết nối lại với con chạy của trình tải cuối cùng khi được +tạo lại sau khi cấu hình thay đổi. Vì thế, chúng không cần truy vấn lại dữ liệu +của mình.
  • +
+ +

Tổng quan về API Trình tải

+ +

Có nhiều lớp và giao diện có thể có liên quan trong khi sử dụng +các trình tải trong một ứng dụng. Chúng được tóm tắt trong bảng này.

+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Lớp/Giao diệnMô tả
{@link android.app.LoaderManager}Một lớp tóm tắt được liên kết với {@link android.app.Activity} hoặc +{@link android.app.Fragment} để quản lý một hoặc nhiều thực thể {@link +android.content.Loader}. Nó giúp ứng dụng quản lý +các thao tác chạy lâu hơn cùng với vòng đời {@link android.app.Activity} +hoặc {@link android.app.Fragment}; công dụng phổ biến nhất của lớp này là khi dùng với +{@link android.content.CursorLoader}, tuy nhiên, các ứng dụng được tự do ghi +trình tải của chính mình để tải các kiểu dữ liệu khác. +
+
+ Chỉ có một {@link android.app.LoaderManager} trên mỗi hoạt động hoặc phân đoạn. Nhưng một {@link android.app.LoaderManager} có thể có +nhiều trình tải.
{@link android.app.LoaderManager.LoaderCallbacks}Một giao diện gọi lại để một máy khách tương tác với {@link +android.app.LoaderManager}. Ví dụ, bạn sử dụng phương pháp gọi lại {@link +android.app.LoaderManager.LoaderCallbacks#onCreateLoader onCreateLoader()} +để tạo một trình tải mới.
{@link android.content.Loader}Một lớp tóm tắt có vai trò thực hiện việc tải dữ liệu không đồng bộ. Đây là +lớp cơ bản cho một trình tải. Thông thường, bạn sẽ sử dụng {@link +android.content.CursorLoader}, nhưng bạn có thể triển khai lớp con của chính mình. Trong khi +các trình tải đang hoạt động, chúng sẽ theo dõi nguồn dữ liệu của mình và chuyển giao +kết quả mới khi nội dung thay đổi.
{@link android.content.AsyncTaskLoader}Trình tải tóm tắt có chức năng cung cấp {@link android.os.AsyncTask} để thực hiện công việc.
{@link android.content.CursorLoader}Một lớp con của {@link android.content.AsyncTaskLoader} có chức năng truy vấn +{@link android.content.ContentResolver} và trả về một {@link +android.database.Cursor}. Lớp này triển khai giao thức {@link +android.content.Loader} theo một cách chuẩn hóa để truy vấn các con chạy, +xây dựng trên {@link android.content.AsyncTaskLoader} để thực hiện truy vấn con chạy +trên một luồng nền sao cho nó không chặn UI của ứng dụng. Sử dụng +trình tải này là cách tốt nhất để tải dữ liệu không đồng bộ từ một {@link +android.content.ContentProvider}, thay vì phải thực hiện một truy vấn được quản lý thông qua +phân đoạn hoặc các API của hoạt động.
+ +

Các lớp và giao diện trong bảng trên là những thành phần thiết yếu +mà bạn sẽ sử dụng để triển khai một trình tải trong ứng dụng của mình. Bạn sẽ không cần tất cả chúng +cho từng trình tải mà bạn tạo lập, nhưng bạn sẽ luôn cần một tham chiếu tới {@link +android.app.LoaderManager} để khởi tạo một trình tải và triển khai +một lớp {@link android.content.Loader} chẳng hạn như {@link +android.content.CursorLoader}. Các phần sau đây trình bày với bạn cách sử dụng những +lớp và giao diện này trong một ứng dụng.

+ +

Sử dụng các Trình tải trong một Ứng dụng

+

Phần này mô tả cách sử dụng các trình tải trong một ứng dụng Android. Một +ứng dụng sử dụng trình tải thường bao gồm:

+
    +
  • Một {@link android.app.Activity} hoặc {@link android.app.Fragment}.
  • +
  • Một thực thể của {@link android.app.LoaderManager}.
  • +
  • Một {@link android.content.CursorLoader} để tải dữ liệu được dự phòng bởi một {@link +android.content.ContentProvider}. Hoặc cách khác, bạn có thể triển khai lớp con +của {@link android.content.Loader} hoặc {@link android.content.AsyncTaskLoader} của chính mình để tải +dữ liệu từ một số nguồn khác.
  • +
  • Một triển khai cho {@link android.app.LoaderManager.LoaderCallbacks}. +Đây là nơi bạn tạo trình tải mới và quản lý các tham chiếu của mình tới các +trình tải hiện có.
  • +
  • Một cách để hiển thị dữ liệu của trình tải, chẳng hạn như {@link +android.widget.SimpleCursorAdapter}.
  • +
  • Một nguồn dữ liệu, chẳng hạn như một {@link android.content.ContentProvider}, khi sử dụng một +{@link android.content.CursorLoader}.
  • +
+

Khởi động một Trình tải

+ +

{@link android.app.LoaderManager} quản lý một hoặc nhiều thực thể {@link +android.content.Loader} trong một {@link android.app.Activity} hoặc +{@link android.app.Fragment}. Chỉ có một {@link +android.app.LoaderManager} trên mỗi hoạt động hoặc phân đoạn.

+ +

Thông thường, bạn +sẽ khởi tạo một {@link android.content.Loader} bên trong phương pháp {@link +android.app.Activity#onCreate onCreate()} của hoạt động, hoặc trong phương pháp +{@link android.app.Fragment#onActivityCreated onActivityCreated()} của phân đoạn. Bạn +làm điều này như sau:

+ +
// Prepare the loader.  Either re-connect with an existing one,
+// or start a new one.
+getLoaderManager().initLoader(0, null, this);
+ +

Phương pháp {@link android.app.LoaderManager#initLoader initLoader()} sẽ lấy những +tham số sau:

+
    +
  • Một ID duy nhất xác định trình tải. Trong ví dụ này, ID là 0.
  • +
  • Các tham đối tùy chọn để cung cấp cho trình tải khi +xây dựng (null trong ví dụ này).
  • + +
  • Triển khai {@link android.app.LoaderManager.LoaderCallbacks}, phương pháp mà +{@link android.app.LoaderManager} gọi để báo cáo các sự kiện trình tải. Trong ví dụ này +, lớp cục bộ triển khai giao diện {@link +android.app.LoaderManager.LoaderCallbacks}, vì thế nó chuyển một tham chiếu +tới chính nó, {@code this}.
  • +
+

Lệnh gọi {@link android.app.LoaderManager#initLoader initLoader()} đảm bảo rằng một trình tải +được khởi tạo và hiện hoạt. Nó có hai kết quả có thể xảy ra:

+
    +
  • Nếu trình tải được quy định bởi ID đã tồn tại, trình tải được tạo lập cuối cùng +sẽ được sử dụng lại.
  • +
  • Nếu trình tải được quy định bởi ID không tồn tại, +{@link android.app.LoaderManager#initLoader initLoader()} sẽ kích khởi phương pháp +{@link android.app.LoaderManager.LoaderCallbacks}{@link android.app.LoaderManager.LoaderCallbacks#onCreateLoader onCreateLoader()}. +Đây là nơi bạn triển khai mã để khởi tạo và trả về một trình tải mới. +Để bàn thêm, hãy xem phần onCreateLoader.
  • +
+

Dù trong trường hợp nào, triển khai {@link android.app.LoaderManager.LoaderCallbacks} +đã cho được liên kết với trình tải, và sẽ được gọi khi +trạng thái của trình tải thay đổi. Nếu tại điểm thực hiện lệnh gọi này, hàm gọi đang trong trạng thái +được khởi động của nó, và trình tải được yêu cầu đã tồn tại và đã khởi tạo +dữ liệu của nó, khi đó hệ thống sẽ gọi {@link +android.app.LoaderManager.LoaderCallbacks#onLoadFinished onLoadFinished()} +ngay lập tức (trong khi {@link android.app.LoaderManager#initLoader initLoader()}), +vì thế bạn phải sẵn sàng khi điều này xảy ra. Xem +onLoadFinished để thảo luận thêm về lệnh gọi lại này

+ +

Lưu ý rằng phương pháp {@link android.app.LoaderManager#initLoader initLoader()} +sẽ trả về {@link android.content.Loader} đã được tạo lập, nhưng bạn không +cần bắt lại một tham chiếu tới nó. {@link android.app.LoaderManager} tự động quản lý +vòng đời của trình tải. {@link android.app.LoaderManager} +khởi động và dừng tải khi cần và duy trì trạng thái của trình tải +và nội dung đi kèm của nó. Như hàm ý, bạn hiếm khi tương tác trực tiếp với các trình tải +(thông qua một ví dụ về việc sử dụng các phương pháp trình tải để tinh chỉnh hành vi +của một trình tải, hãy xem ví dụ LoaderThrottle). +Bạn thường sử dụng nhất là các phương pháp {@link +android.app.LoaderManager.LoaderCallbacks} để can thiệp vào tiến trình tải +khi diễn ra một sự kiện đặc biệt. Để thảo luận thêm về chủ đề này, hãy xem phần Sử dụng Phương pháp Gọi lại LoaderManager.

+ +

Khởi động lại một Trình tải

+ +

Khi bạn sử dụng {@link android.app.LoaderManager#initLoader initLoader()}, như +trình bày bên trên, nó sử dụng một trình tải hiện hữu với ID được quy định nếu có. +Nếu không có, nó sẽ tạo một trình tải. Nhưng đôi khi bạn muốn bỏ dữ liệu cũ của mình +và bắt đầu lại.

+ +

Để bỏ dữ liệu cũ của mình, hãy sử dụng {@link +android.app.LoaderManager#restartLoader restartLoader()}. Ví dụ, việc +triển khai {@link android.widget.SearchView.OnQueryTextListener} này sẽ khởi động lại +trình tải khi truy vấn của người dùng thay đổi. Trình tải cần được khởi động lại sao cho +nó có thể sử dụng bộ lọc tìm kiếm được điều chỉnh để thực hiện một truy vấn mới:

+ +
+public boolean onQueryTextChanged(String newText) {
+    // Called when the action bar search text has changed.  Update
+    // the search filter, and restart the loader to do a new query
+    // with this filter.
+    mCurFilter = !TextUtils.isEmpty(newText) ? newText : null;
+    getLoaderManager().restartLoader(0, null, this);
+    return true;
+}
+ +

Sử dụng các Phương pháp Gọi lại LoaderManager

+ +

{@link android.app.LoaderManager.LoaderCallbacks} là một giao diện gọi lại +cho phép một máy khách tương tác với {@link android.app.LoaderManager}.

+

Các trình tải, đặc biệt là {@link android.content.CursorLoader}, được kỳ vọng sẽ +giữ lại dữ liệu của chúng sau khi bị dừng. Điều này cho phép ứng dụng giữ lại +dữ liệu của chúng qua hoạt động hoặc các phương pháp {@link android.app.Activity#onStop +onStop()} và {@link android.app.Activity#onStart onStart()} của phân đoạn, sao cho khi +người dùng quay lại một ứng dụng, họ không phải chờ dữ liệu +tải lại. Bạn sử dụng các phương pháp {@link android.app.LoaderManager.LoaderCallbacks} +khi cần biết khi nào thì nên tạo một trình tải mới, và để thông báo với ứng dụng khi nào + thì đến lúc để dừng sử dụng dữ liệu của một trình tải.

+ +

{@link android.app.LoaderManager.LoaderCallbacks} bao gồm những phương pháp +sau:

+
    +
  • {@link android.app.LoaderManager.LoaderCallbacks#onCreateLoader onCreateLoader()} — +Khởi tạo và trả về một {@link android.content.Loader} mới cho ID đã cho. +
+
    +
  • {@link android.app.LoaderManager.LoaderCallbacks#onLoadFinished onLoadFinished()} +— Được gọi khi một trình tải được tạo trước đó đã hoàn tất việc tải. +
+
    +
  • {@link android.app.LoaderManager.LoaderCallbacks#onLoaderReset onLoaderReset()} + — Được gọi khi một trình tải được tạo trước đó đang được đặt lại, vì thế mà khiến dữ liệu +của nó không sẵn có. +
  • +
+

Những phương pháp này được mô tả chi tiết hơn trong các phần sau.

+ +

onCreateLoader

+ +

Khi bạn định truy cập một trình tải (ví dụ, thông qua {@link +android.app.LoaderManager#initLoader initLoader()}), nó kiểm tra xem +trình tải được quy định bởi ID có tồn tại không. Nếu không, nó sẽ kích khởi phương pháp {@link +android.app.LoaderManager.LoaderCallbacks} {@link +android.app.LoaderManager.LoaderCallbacks#onCreateLoader onCreateLoader()}. Đây +là lúc bạn tạo một trình tải mới. Thông thường sẽ có một {@link +android.content.CursorLoader}, nhưng bạn có thể triển khai lớp con {@link +android.content.Loader} của chính mình.

+ +

Trong ví dụ này, phương pháp gọi lại {@link +android.app.LoaderManager.LoaderCallbacks#onCreateLoader onCreateLoader()} +sẽ tạo một {@link android.content.CursorLoader}. Bạn phải xây dựng +{@link android.content.CursorLoader} bằng cách sử dụng phương pháp hàm dựng của nó mà yêu cầu +trọn bộ thông tin cần thiết để thực hiện một truy vấn tới {@link +android.content.ContentProvider}. Cụ thể, nó cần:

+
    +
  • uri — URI của nội dung cần truy xuất.
  • +
  • dự thảo — Một danh sách các cột sẽ trả về. Việc chuyển +null sẽ trả về tất cả cột, điều này không hiệu quả.
  • +
  • lựa chọn — Một bộ lọc khai báo các hàng nào sẽ trả về, +có định dạng như một mệnh đề SQL WHERE (không gồm chính mệnh đề WHERE). Việc chuyển +null sẽ trả về tất cả hàng cho URI đã cho.
  • +
  • selectionArgs — Bạn có thể thêm ?s vào lựa chọn, +chúng sẽ được thay thế bằng các giá trị từ selectionArgs, theo thứ tự xuất hiện trong +lựa chọn. Giá trị sẽ được gắn kết thành các Xâu.
  • +
  • sortOrder — Cách sắp xếp thứ tự các hàng, được định dạng như một mệnh đề SQL +ORDER BY (không bao gồm chính mệnh đề ORDER BY). Việc chuyển null sẽ +sử dụng thứ tự sắp xếp mặc định, điều này có thể dẫn đến kết quả không theo thứ tự.
  • +
+

Ví dụ:

+
+ // If non-null, this is the current filter the user has provided.
+String mCurFilter;
+...
+public Loader<Cursor> onCreateLoader(int id, Bundle args) {
+    // This is called when a new Loader needs to be created.  This
+    // sample only has one Loader, so we don't care about the ID.
+    // First, pick the base URI to use depending on whether we are
+    // currently filtering.
+    Uri baseUri;
+    if (mCurFilter != null) {
+        baseUri = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI,
+                  Uri.encode(mCurFilter));
+    } else {
+        baseUri = Contacts.CONTENT_URI;
+    }
+
+    // Now create and return a CursorLoader that will take care of
+    // creating a Cursor for the data being displayed.
+    String select = "((" + Contacts.DISPLAY_NAME + " NOTNULL) AND ("
+            + Contacts.HAS_PHONE_NUMBER + "=1) AND ("
+            + Contacts.DISPLAY_NAME + " != '' ))";
+    return new CursorLoader(getActivity(), baseUri,
+            CONTACTS_SUMMARY_PROJECTION, select, null,
+            Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC");
+}
+

onLoadFinished

+ +

Phương pháp này được gọi khi một trình tải được tạo trước đó đã hoàn thành việc tải của mình. +Phương pháp này được bảo đảm sẽ được gọi trước khi giải phóng dữ liệu cuối cùng +được cung cấp cho trình tải này. Tại điểm này, bạn nên loại bỏ mọi trường hợp sử dụng +dữ liệu cũ (do nó sẽ được giải phóng sớm), nhưng không nên +tự mình giải phóng dữ liệu do trình tải sở hữu dữ liệu và sẽ đảm nhận việc này.

+ + +

Trình tải sẽ giải phóng dữ liệu sau khi nó biết ứng dụng đang không còn +sử dụng nó nữa. Ví dụ, nếu dữ liệu là một con chạy từ một {@link +android.content.CursorLoader}, bạn không nên tự mình gọi {@link +android.database.Cursor#close close()} trên dữ liệu đó. Nếu con chạy đang được đặt +trong một {@link android.widget.CursorAdapter}, bạn nên sử dụng phương pháp {@link +android.widget.SimpleCursorAdapter#swapCursor swapCursor()} sao cho + {@link android.database.Cursor} cũ không bị đóng. Ví dụ:

+ +
+// This is the Adapter being used to display the list's data.
SimpleCursorAdapter mAdapter; +... + +public void onLoadFinished(Loader<Cursor> loader, Cursor data) { + // Swap the new cursor in.  (The framework will take care of closing the + // old cursor once we return.) + mAdapter.swapCursor(data); +}
+ +

onLoaderReset

+ +

Phương pháp này được gọi khi một trình tải được tạo trước đó đang được đặt lại, vì thế mà khiến +dữ liệu của nó không sẵn có. Lệnh gọi lại này cho phép bạn tìm hiểu xem khi nào thì dữ liệu +sẽ được giải phóng để bạn có thể loại bỏ tham chiếu của mình tới nó.  

+

Sự triển khai này gọi ra +{@link android.widget.SimpleCursorAdapter#swapCursor swapCursor()} +với một giá trị null:

+ +
+// This is the Adapter being used to display the list's data.
+SimpleCursorAdapter mAdapter;
+...
+
+public void onLoaderReset(Loader<Cursor> loader) {
+    // This is called when the last Cursor provided to onLoadFinished()
+    // above is about to be closed.  We need to make sure we are no
+    // longer using it.
+    mAdapter.swapCursor(null);
+}
+ + +

Ví dụ

+ +

Lấy một ví dụ, sau đây là triển khai đầy đủ của {@link +android.app.Fragment} có chức năng hiển thị một {@link android.widget.ListView} chứa +kết quả của một truy vấn đối với trình cung cấp nội dung danh bạ. Nó sử dụng một {@link +android.content.CursorLoader} để quản lý truy vấn trên trình cung cấp.

+ +

Để một ứng dụng truy cập danh bạ của một người dùng, như minh họa trong ví dụ này, bản kê khai +của nó phải bao gồm quyền +{@link android.Manifest.permission#READ_CONTACTS READ_CONTACTS}.

+ +
+public static class CursorLoaderListFragment extends ListFragment
+        implements OnQueryTextListener, LoaderManager.LoaderCallbacks<Cursor> {
+
+    // This is the Adapter being used to display the list's data.
+    SimpleCursorAdapter mAdapter;
+
+    // If non-null, this is the current filter the user has provided.
+    String mCurFilter;
+
+    @Override public void onActivityCreated(Bundle savedInstanceState) {
+        super.onActivityCreated(savedInstanceState);
+
+        // Give some text to display if there is no data.  In a real
+        // application this would come from a resource.
+        setEmptyText("No phone numbers");
+
+        // We have a menu item to show in action bar.
+        setHasOptionsMenu(true);
+
+        // Create an empty adapter we will use to display the loaded data.
+        mAdapter = new SimpleCursorAdapter(getActivity(),
+                android.R.layout.simple_list_item_2, null,
+                new String[] { Contacts.DISPLAY_NAME, Contacts.CONTACT_STATUS },
+                new int[] { android.R.id.text1, android.R.id.text2 }, 0);
+        setListAdapter(mAdapter);
+
+        // Prepare the loader.  Either re-connect with an existing one,
+        // or start a new one.
+        getLoaderManager().initLoader(0, null, this);
+    }
+
+    @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+        // Place an action bar item for searching.
+        MenuItem item = menu.add("Search");
+        item.setIcon(android.R.drawable.ic_menu_search);
+        item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
+        SearchView sv = new SearchView(getActivity());
+        sv.setOnQueryTextListener(this);
+        item.setActionView(sv);
+    }
+
+    public boolean onQueryTextChange(String newText) {
+        // Called when the action bar search text has changed.  Update
+        // the search filter, and restart the loader to do a new query
+        // with this filter.
+        mCurFilter = !TextUtils.isEmpty(newText) ? newText : null;
+        getLoaderManager().restartLoader(0, null, this);
+        return true;
+    }
+
+    @Override public boolean onQueryTextSubmit(String query) {
+        // Don't care about this.
+        return true;
+    }
+
+    @Override public void onListItemClick(ListView l, View v, int position, long id) {
+        // Insert desired behavior here.
+        Log.i("FragmentComplexList", "Item clicked: " + id);
+    }
+
+    // These are the Contacts rows that we will retrieve.
+    static final String[] CONTACTS_SUMMARY_PROJECTION = new String[] {
+        Contacts._ID,
+        Contacts.DISPLAY_NAME,
+        Contacts.CONTACT_STATUS,
+        Contacts.CONTACT_PRESENCE,
+        Contacts.PHOTO_ID,
+        Contacts.LOOKUP_KEY,
+    };
+    public Loader<Cursor> onCreateLoader(int id, Bundle args) {
+        // This is called when a new Loader needs to be created.  This
+        // sample only has one Loader, so we don't care about the ID.
+        // First, pick the base URI to use depending on whether we are
+        // currently filtering.
+        Uri baseUri;
+        if (mCurFilter != null) {
+            baseUri = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI,
+                    Uri.encode(mCurFilter));
+        } else {
+            baseUri = Contacts.CONTENT_URI;
+        }
+
+        // Now create and return a CursorLoader that will take care of
+        // creating a Cursor for the data being displayed.
+        String select = "((" + Contacts.DISPLAY_NAME + " NOTNULL) AND ("
+                + Contacts.HAS_PHONE_NUMBER + "=1) AND ("
+                + Contacts.DISPLAY_NAME + " != '' ))";
+        return new CursorLoader(getActivity(), baseUri,
+                CONTACTS_SUMMARY_PROJECTION, select, null,
+                Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC");
+    }
+
+    public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
+        // Swap the new cursor in.  (The framework will take care of closing the
+        // old cursor once we return.)
+        mAdapter.swapCursor(data);
+    }
+
+    public void onLoaderReset(Loader<Cursor> loader) {
+        // This is called when the last Cursor provided to onLoadFinished()
+        // above is about to be closed.  We need to make sure we are no
+        // longer using it.
+        mAdapter.swapCursor(null);
+    }
+}
+

Thêm Ví dụ

+ +

Có một vài mẫu khác trong ApiDemos để +minh họa cách sử dụng các trình tải:

+
    +
  • +LoaderCursor — Một phiên bản hoàn chỉnh của +đoạn mã HTML trình bày ở trên.
  • +
  • LoaderThrottle — Một ví dụ về cách sử dụng điều chỉnh để giảm +số truy vấn mà một trình cung cấp nội dung thực hiện khi dữ liệu của nó thay đổi.
  • +
+ +

Để biết thông tin về việc tải xuống và cài đặt các mẫu SDK, hãy xem phần Tải +Mẫu.

+ diff --git a/docs/html-intl/intl/vi/guide/components/processes-and-threads.jd b/docs/html-intl/intl/vi/guide/components/processes-and-threads.jd new file mode 100644 index 0000000000000000000000000000000000000000..390ca156a1f05f4025232d1fc7cf58f12697363a --- /dev/null +++ b/docs/html-intl/intl/vi/guide/components/processes-and-threads.jd @@ -0,0 +1,411 @@ +page.title=Tiến trình và Luồng +page.tags=vòng đời, nền + +@jd:body + + + +

Khi một thành phần ứng dụng bắt đầu và ứng dụng không có bất kỳ thành phần nào khác +đang chạy, hệ thống Android sẽ khởi động một tiến trình Linux mới cho ứng dụng bằng một luồng +thực thi đơn lẻ. Theo mặc định, tất cả thành phần của cùng ứng dụng sẽ chạy trong cùng tiến trình và luồng +(được gọi là luồng "chính"). Nếu một thành phần ứng dụng bắt đầu và đã tồn tại một tiến trình +cho ứng dụng đó (bởi một thành phần khác từ ứng dụng đã tồn tại), khi đó thành phần được +bắt đầu bên trong tiến trình đó và sử dụng cùng luồng thực thi. Tuy nhiên, bạn có thể sắp xếp cho +các thành phần khác nhau trong ứng dụng của mình để chạy trong các tiến trình riêng biệt, và bạn có thể tạo thêm +luồng cho bất kỳ tiến trình nào.

+ +

Tài liệu này trình bày về cách các tiến trình và luồng vận hành trong một ứng dụng Android.

+ + +

Tiến trình

+ +

Theo mặc định, tất cả thành phần của cùng ứng dụng sẽ chạy trong cùng tiến trình và hầu hết các ứng dụng +sẽ không thay đổi điều này. Tuy nhiên, nếu bạn thấy rằng mình cần kiểm soát một thành phần +cụ thể thuộc về một tiến trình nào đó, bạn có thể làm vậy trong tệp bản kê khai.

+ +

Mục nhập bản kê khai đối với mỗi loại phần tử thành phần—{@code +<activity>}, {@code +<service>}, {@code +<receiver>}, và {@code +<provider>}—sẽ hỗ trợ một thuộc tính {@code android:process} mà có thể quy định một +tiến trình mà thành phần đó sẽ chạy trong đó. Bạn có thể đặt thuộc tính này sao cho từng thành phần chạy +trong chính tiến trình của nó hoặc sao cho một số thành phần chia sẻ một tiến trình trong khi các thành phần khác thì không. Bạn cũng có thể đặt +{@code android:process} sao cho các thành phần của những ứng dụng khác nhau chạy trong cùng +tiến trình—với điều kiện rằng ứng dụng chia sẻ cùng ID người dùng Linux và được ký bằng cùng các +chứng chỉ.

+ +

Phần tử {@code +<application>} cũng hỗ trợ một thuộc tính {@code android:process}, để đặt một +giá trị mặc định áp dụng cho tất cả thành phần.

+ +

Android có thể quyết định tắt một tiến trình tại một thời điểm nào đó, khi bộ nhớ thấp và theo yêu cầu của các +tiến trình khác đang phục vụ người dùng tức thì hơn. Các thành phần +ứng dụng đang chạy trong tiến trình bị tắt bỏ thì sau đó sẽ bị hủy. Tiến trình được +khởi động lại cho những thành phần đó khi lại có việc cho chúng thực hiện.

+ +

Khi quyết định bỏ những tiến trình nào, hệ thống Android sẽ cân nhắc tầm quan trọng tương đối so với +người dùng. Ví dụ, hệ thống sẵn sàng hơn khi tắt một tiến trình lưu trữ các hoạt động không còn +hiển thị trên màn hình, so với một tiến trình lưu trữ các hoạt động đang hiển thị. Vì thế, quyết định về việc có +chấm dứt một tiến trình hay không phụ thuộc vào trạng thái của các thành phần đang chạy trong tiến trình đó. Các quy tắc +được sử dụng để quyết định sẽ chấm dứt tiến trình nào được trình bày ở bên dưới.

+ + +

Vòng đời tiến trình

+ +

Hệ thống Android cố gắng duy trì một tiến trình ứng dụng lâu nhất có thể, nhưng +cuối cùng thì nó cũng cần loại bỏ các tiến trình cũ để lấy lại bộ nhớ cho các tiến trình mới hoặc quan trọng hơn. Để +xác định giữ lại những tiến trình nào +và loại bỏ những tiến trình nào, hệ thống sẽ đặt từng tiến trình vào một "phân cấp tầm quan trọng" dựa trên +những thành phần đang chạy trong tiến trình và trạng thái của những thành phần đó. Những tiến trình có tầm quan trọng +thấp nhất bị loại bỏ trước, rồi đến những tiến trình có tầm quan trọng thấp thứ hai, và cứ thế tiếp tục, miễn là còn cần thiết +để khôi phục tài nguyên của hệ thống.

+ +

Có năm cấp trong phân cấp tầm quan trọng. Danh sách sau trình bày các loại +tiến trình khác nhau theo thứ tự tầm quan trọng (tiến trình thứ nhất là quan trọng nhất và được +tắt bỏ sau cùng):

+ +
    +
  1. Tiến trình tiền cảnh +

    Một tiến trình được yêu cầu cho việc mà người dùng đang thực hiện. Một + tiến trình được coi là đang trong tiền cảnh nếu bất kỳ điều nào sau đây là đúng:

    + +
      +
    • Nó lưu trữ một {@link android.app.Activity} mà người dùng đang tương tác với (phương pháp của {@link +android.app.Activity}, {@link android.app.Activity#onResume onResume()}, đã được +gọi).
    • + +
    • Nó lưu trữ một {@link android.app.Service} gắn kết với hoạt động mà người dùng đang +tương tác với.
    • + +
    • Nó lưu trữ một {@link android.app.Service} đang chạy "trong tiền cảnh"—mà +dịch vụ đã gọi {@link android.app.Service#startForeground startForeground()}. + +
    • Nó lưu trữ một {@link android.app.Service} mà đang thực thi một trong các lệnh +gọi lại của vòng đời của nó ({@link android.app.Service#onCreate onCreate()}, {@link android.app.Service#onStart +onStart()}, hoặc {@link android.app.Service#onDestroy onDestroy()}).
    • + +
    • Nó lưu trữ một {@link android.content.BroadcastReceiver} mà đang thực thi phương pháp {@link + android.content.BroadcastReceiver#onReceive onReceive()} của nó.
    • +
    + +

    Nhìn chung, tại bất kỳ thời điểm xác định nào cũng chỉ tồn tại một vài tiến trình tiền cảnh. Chúng chỉ bị tắt bỏ +như một giải pháp cuối cùng—nếu bộ nhớ quá thấp tới mức chúng đều không thể tiếp tục chạy được. Nhìn chung, tại thời điểm +đó, thiết bị đã đạt tới trạng thái phân trang bộ nhớ, vì thế việc tắt bỏ một số tiến trình tiền cảnh là +bắt buộc để đảm bảo giao diện người dùng có phản hồi.

  2. + +
  3. Tiến trình hiển thị +

    Một tiến trình mà không có bất kỳ thành phần tiền cảnh nào, nhưng vẫn có thể + ảnh hưởng tới nội dung mà người dùng nhìn thấy trên màn hình. Một tiến trình được coi là hiển thị nếu một trong hai + điều kiện sau là đúng:

    + +
      +
    • Nó lưu trữ một {@link android.app.Activity} mà không nằm trong tiền cảnh, nhưng vẫn +hiển thị với người dùng (phương pháp {@link android.app.Activity#onPause onPause()} của nó đã được gọi). +Điều này có thể xảy ra, ví dụ, nếu hoạt động tiền cảnh đã bắt đầu một hộp thoại, nó cho phép +hoạt động trước được nhìn thấy phía sau nó.
    • + +
    • Nó lưu trữ một {@link android.app.Service} được gắn kết với một hoạt động +hiển thị (hoặc tiền cảnh).
    • +
    + +

    Một tiến trình tiền cảnh được coi là cực kỳ quan trọng và sẽ không bị tắt bỏ trừ khi làm vậy +là bắt buộc để giữ cho tất cả tiến trình tiền cảnh chạy.

    +
  4. + +
  5. Tiến trình dịch vụ +

    Một tiến trình mà đang chạy một dịch vụ đã được bắt đầu bằng phương pháp {@link +android.content.Context#startService startService()} và không rơi vào một trong hai +thể loại cao hơn. Mặc dù tiến trình dịch vụ không trực tiếp gắn với bất kỳ thứ gì mà người dùng thấy, chúng +thường đang làm những việc mà người dùng quan tâm đến (chẳng hạn như phát nhạc chạy ngầm hoặc +tải xuống dữ liệu trên mạng), vì thế hệ thống vẫn giữ chúng chạy trừ khi không có đủ bộ nhớ để +duy trì chúng cùng với tất cả tiến trình tiền cảnh và hiển thị.

    +
  6. + +
  7. Tiến trình nền +

    Một tiến trình lưu trữ một hoạt động mà hiện tại không hiển thị với người dùng (phương pháp +{@link android.app.Activity#onStop onStop()} của hoạt động đã được gọi). Những tiến trình này không có tác động +trực tiếp tới trải nghiệm người dùng, và hệ thống có thể bỏ chúng đi vào bất cứ lúc nào để lấy lại bộ nhớ cho một +tiến trình tiền cảnh, +hiển thị hoặc dịch vụ. Thường thì có nhiều tiến trình ngầm đang chạy, vì thế chúng được giữ +trong một danh sách LRU (ít sử dụng gần đây nhất) để đảm bảo rằng tiến trình với hoạt động +mà người dùng nhìn thấy gần đây nhất là tiến trình cuối cùng sẽ bị tắt bỏ. Nếu một hoạt động triển khai các phương pháp vòng đời của nó +đúng cách, và lưu trạng thái hiện tại của nó, việc tắt bỏ tiến trình của hoạt động đó sẽ không có ảnh hưởng có thể thấy được tới +trải nghiệm người dùng, vì khi người dùng điều hướng lại hoạt động đó, hoạt động sẽ khôi phục +tất cả trạng thái hiển thị của nó. Xem tài liệu Hoạt động +để biết thông tin về việc lưu và khôi phục trạng thái.

    +
  8. + +
  9. Tiến trình trống +

    Một tiến trình mà không giữ bất kỳ thành phần ứng dụng hiện hoạt nào. Lý do duy nhất để giữ cho +kiểu tiến trình này hoạt động đó là nhằm mục đích lưu bộ nhớ ẩn, để cải thiện thời gian khởi động vào lần tới khi thành phần +cần chạy trong nó. Hệ thống thường tắt bỏ những tiến trình này để cân bằng tài nguyên tổng thể +của hệ thống giữa các bộ đệm ẩn tiến trình và bộ đệm ẩn nhân liên quan.

    +
  10. +
+ + +

Android xếp hạng một tiến trình ở mức cao nhất mà nó có thể, dựa vào tầm quan trọng của +các thành phần đang hoạt động trong tiến trình đó. Ví dụ, nếu một tiến trình lưu giữ một dịch vụ và hoạt động +hiển thị, tiến trình đó sẽ được xếp hạng là tiến trình hiển thị chứ không phải tiến trình dịch vụ.

+ +

Ngoài ra, xếp hạng của một tiến trình có thể tăng bởi các tiến trình khác phụ thuộc vào +nó—một tiến trình mà đang phục vụ một tiến trình khác không thể bị xếp thấp hơn tiến trình mà nó +đang phục vụ. Ví dụ, nếu một trình cung cấp nội dung trong tiến trình A đang phục vụ một máy khách trong tiến trình B, hoặc nếu một +dịch vụ trong tiến trình A được gắn kết với một thành phần trong tiến trình B, ít nhất tiến trình A sẽ luôn được coi +là quan trọng như tiến trình B.

+ +

Do một tiến trình đang chạy một dịch vụ được xếp hạng cao hơn một tiến trình có các hoạt động nền, +một hoạt động mà khởi động một thao tác nhấp giữ có thể làm tốt việc khởi động một dịch vụ cho thao tác đó, thay vì +chỉ tạo một luồng trình thực hiện—nhất là khi thao tác đó sẽ có thể diễn ra lâu hơn hoạt động. +Ví dụ, một hoạt động mà đang tải một ảnh lên một trang web nên bắt đầu một dịch vụ để thực hiện +việc tải lên sao cho việc tải lên có thể tiếp tục chạy ngầm ngay cả khi người dùng rời khỏi hoạt động. +Việc sử dụng một dịch vụ sẽ bảo đảm rằng thao tác ít nhất sẽ có mức ưu tiên như "tiến trình dịch vụ", +không phụ thuộc vào điều xảy ra với hoạt động. Đây cũng chính là lý do hàm nhận quảng bá nên +sử dụng dịch vụ thay vì chỉ đưa các thao tác tốn thời gian vào một luồng.

+ + + + +

Luồng

+ +

Khi một ứng dụng được khởi chạy, hệ thống sẽ tạo một luồng thực thi cho ứng dụng, +gọi là luồng "chính." Luồng này rất quan trọng bởi nó phụ trách phân phối các sự kiện tới +những widget giao diện người dùng phù hợp, bao gồm các sự kiện vẽ. Nó cũng là luồng mà +trong đó ứng dụng của bạn tương tác với các thành phần từ bộ công cụ UI của Android (các thành phần từ các gói {@link +android.widget} và {@link android.view}). Như vậy, luồng chính đôi khi cũng được gọi là +luồng UI.

+ +

Hệ thống không tạo một luồng riêng cho từng thực thể của thành phần. Tất cả +thành phần chạy trong cùng tiến trình đều được khởi tạo trong luồng UI, và các lệnh gọi của hệ thống tới +từng thành phần được phân phối từ luồng đó. Hệ quả là các phương pháp hồi đáp lại lệnh +gọi lại của hệ thống (chẳng hạn như {@link android.view.View#onKeyDown onKeyDown()} để báo cáo hành động của người dùng +hoặc một phương pháp gọi lại vòng đời) sẽ luôn chạy trong luồng UI của tiến trình.

+ +

Ví dụ, khi người dùng chạm vào một nút trên màn hình, luồng UI của ứng dụng của bạn sẽ phân phối +sự kiện chạm tới widget, đến lượt mình, widget sẽ đặt trạng thái được nhấn và đăng một yêu cầu vô hiệu hóa tới +hàng đợi sự kiện. Luồng UI loại yêu cầu khỏi hàng đợi và thông báo với widget rằng nó nên tự vẽ lại +.

+ +

Khi ứng dụng của bạn thực hiện công việc nặng để hồi đáp tương tác của người dùng, mô hình luồng đơn nhất +này có thể dẫn đến hiệu năng kém trừ khi bạn triển khai ứng dụng của mình một cách phù hợp. Cụ thể, nếu +mọi thứ đang xảy ra trong luồng UI, việc thực hiện những thao tác kéo dài như truy cập mạng hay +truy vấn cơ sở dữ liệu sẽ chặn toàn bộ UI. Khi luồng bị chặn, không sự kiện nào có thể được phân phối, +bao gồm cả sự kiện vẽ. Từ phương diện của người dùng, ứng dụng +có vẻ như đang bị treo. Thậm chí tệ hơn, nếu luồng UI bị chặn trong lâu hơn vài giây +(hiện tại là khoảng 5 giây), người dùng sẽ được hiển thị hộp thoại không phổ biến "ứng dụng +không phản hồi" (ANR). Khi đó, người dùng có thể quyết định thoát ứng dụng của mình và gỡ cài đặt nó +nếu họ không thoải mái.

+ +

Ngoài ra, bộ công cụ UI của Android không an toàn với luồng. Vì vậy, bạn không được thao tác +UI của mình từ một luồng trình thực hiện—bạn phải thực hiện tất cả thao tác đối với giao diện người dùng của mình từ luồng +UI. Vì vậy, có hai quy tắc đơn giản đối với mô hình luồng đơn lẻ của Android:

+ +
    +
  1. Không được chặn luồng UI +
  2. Không được truy cập bộ công cụ UI của Android từ bên ngoài luồng UI +
+ +

Luồng trình thực hiện

+ +

Vì mô hình luồng đơn lẻ nêu trên, điều thiết yếu đối với tính phản hồi của UI +ứng dụng của bạn đó là bạn không được chặn luồng UI. Nếu bạn có thao tác cần thực hiện +không mang tính chất tức thời, bạn nên đảm bảo thực hiện chúng trong các luồng riêng (luồng “chạy ngầm" hoặc +"trình thực hiện").

+ +

Ví dụ, bên dưới là một số mã cho một đối tượng theo dõi nhấp có chức năng tải xuống một hình ảnh từ một luồng +riêng và hiển thị nó trong một {@link android.widget.ImageView}:

+ +
+public void onClick(View v) {
+    new Thread(new Runnable() {
+        public void run() {
+            Bitmap b = loadImageFromNetwork("http://example.com/image.png");
+            mImageView.setImageBitmap(b);
+        }
+    }).start();
+}
+
+ +

Thoạt đầu, điều này có vẻ như diễn ra ổn thỏa, vì nó tạo một luồng mới để xử lý thao tác +mạng. Tuy nhiên, nó vi phạm quy tắc thứ hai của mô hình luồng đơn nhất: không được truy cập +bộ công cụ UI của Android từ bên ngoài luồng UI—mẫu này sửa đổi {@link +android.widget.ImageView} từ luồng trình thực hiện thay vì từ luồng UI. Điều này có thể dẫn đến +hành vi bất ngờ, không được định nghĩa mà có thể gây khó khăn và tốn thời gian theo dõi.

+ +

Để sửa vấn đề này, Android giới thiệu một vài cách để truy cập luồng UI từ các luồng +khác. Sau đây là một danh sách các phương pháp có thể trợ giúp:

+ +
    +
  • {@link android.app.Activity#runOnUiThread(java.lang.Runnable) +Activity.runOnUiThread(Runnable)}
  • +
  • {@link android.view.View#post(java.lang.Runnable) View.post(Runnable)}
  • +
  • {@link android.view.View#postDelayed(java.lang.Runnable, long) View.postDelayed(Runnable, +long)}
  • +
+ +

Ví dụ, bạn có thể sửa mã trên bằng cách sử dụng phương pháp {@link +android.view.View#post(java.lang.Runnable) View.post(Runnable)}:

+ +
+public void onClick(View v) {
+    new Thread(new Runnable() {
+        public void run() {
+            final Bitmap bitmap = loadImageFromNetwork("http://example.com/image.png");
+            mImageView.post(new Runnable() {
+                public void run() {
+                    mImageView.setImageBitmap(bitmap);
+                }
+            });
+        }
+    }).start();
+}
+
+ +

Giờ thì triển khai này đã an toàn với luồng: thao tác mạng được thực hiện từ một luồng riêng +trong khi {@link android.widget.ImageView} được thao tác từ luồng UI.

+ +

Tuy nhiên, khi mà sự phức tạp của thao tác tăng lên, kiểu mã này có thể bị phức tạp hóa và +khó duy trì. Để xử lý những tương tác phức tạp hơn bằng một luồng trình thực hiện, bạn có thể cân nhắc +sử dụng một {@link android.os.Handler} trong luồng trình thực hiện của mình, để xử lý các thư được chuyển từ luồng +UI. Mặc dù vậy, giải pháp tốt nhất là mở rộng lớp {@link android.os.AsyncTask}, +điều này sẽ đơn giản hóa việc thực thi các tác vụ của luồng trình thực hiện cần tương tác với UI.

+ + +

Sử dụng AsyncTask

+ +

{@link android.os.AsyncTask} cho phép bạn thực hiện công việc không đồng bộ trên giao diện +người dùng của mình. Nó thực hiện các thao tác chặn trong một luồng trình thực hiện rồi phát hành kết quả trên +luồng UI mà không yêu cầu bạn tự xử lý các luồng và/hoặc trình xử lý.

+ +

Để sử dụng nó, bạn phải tạo lớp con {@link android.os.AsyncTask} và triển khai phương pháp gọi lại {@link +android.os.AsyncTask#doInBackground doInBackground()}, phương pháp này chạy trong một tập hợp +các luồng chạy ngầm. Để cập nhật UI của mình, bạn nên triển khai {@link +android.os.AsyncTask#onPostExecute onPostExecute()}, nó sẽ mang lại kết quả từ {@link +android.os.AsyncTask#doInBackground doInBackground()} và chạy trong luồng UI, vì thế bạn có thể nâng cấp +UI của mình một cách an toàn. Sau đó, bạn có thể chạy tác vụ bằng cách gọi {@link android.os.AsyncTask#execute execute()} +từ luồng UI.

+ +

Ví dụ, bạn có thể triển khai ví dụ trước bằng cách sử dụng {@link android.os.AsyncTask} theo +cách này:

+ +
+public void onClick(View v) {
+    new DownloadImageTask().execute("http://example.com/image.png");
+}
+
+private class DownloadImageTask extends AsyncTask<String, Void, Bitmap> {
+    /** The system calls this to perform work in a worker thread and
+      * delivers it the parameters given to AsyncTask.execute() */
+    protected Bitmap doInBackground(String... urls) {
+        return loadImageFromNetwork(urls[0]);
+    }
+    
+    /** The system calls this to perform work in the UI thread and delivers
+      * the result from doInBackground() */
+    protected void onPostExecute(Bitmap result) {
+        mImageView.setImageBitmap(result);
+    }
+}
+
+ +

Lúc này, UI an toàn và mã đơn giản hơn, vì nó tách riêng công việc thành +phần sẽ được thực hiện trên một luồng trình thực hiện và phần sẽ được thực hiện trên luồng UI.

+ +

Bạn nên đọc tài liệu tham khảo {@link android.os.AsyncTask}để hiểu đầy đủ về +cách sử dụng lớp này, nhưng sau đây là phần trình bày tổng quan nhanh về hoạt động của nó:

+ +
    +
  • Bạn có thể quy định loại tham số, các giá trị tiến độ, và giá trị +cuối cùng của tác vụ, bằng cách sử dụng các kiểu chung
  • +
  • Phương pháp {@link android.os.AsyncTask#doInBackground doInBackground()} sẽ tự động thực thi +trên một luồng trình thực hiện
  • +
  • {@link android.os.AsyncTask#onPreExecute onPreExecute()}, {@link +android.os.AsyncTask#onPostExecute onPostExecute()}, và {@link +android.os.AsyncTask#onProgressUpdate onProgressUpdate()} đều được gọi ra trên luồng UI
  • +
  • Giá trị được trả về bởi {@link android.os.AsyncTask#doInBackground doInBackground()} được gửi tới +{@link android.os.AsyncTask#onPostExecute onPostExecute()}
  • +
  • Bạn có thể gọi {@link android.os.AsyncTask#publishProgress publishProgress()} vào bất cứ lúc nào trong {@link +android.os.AsyncTask#doInBackground doInBackground()} để thực thi {@link +android.os.AsyncTask#onProgressUpdate onProgressUpdate()} trên luồng UI
  • +
  • Bạn có thể hủy bỏ tác vụ vào bất cứ lúc nào từ bất kỳ luồng nào
  • +
+ +

Chú ý: Một vấn đề khác mà bạn có thể gặp phải khi sử dụng một luồng +trình thực hiện đó là những lần khởi động lại bất ngờ trong hoạt động của bạn do một thay đổi trong cấu hình thời gian chạy +(chẳng hạn như khi người dùng thay đổi hướng màn hình), điều này có thể làm hỏng luồng trình thực hiện của bạn. Để +xem cách bạn có thể duy trì tác vụ của mình khi diễn ra một trong những lần khởi động lại này và cách hủy bỏ tác vụ cho phù hợp +khi hoạt động bị hủy, hãy xem mã nguồn cho ứng dụng mẫu Shelves.

+ + +

Phương pháp an toàn với luồng

+ +

Trong một số tình huống, các phương pháp bạn triển khai có thể được gọi ra từ nhiều hơn một luồng, và vì thế +phải được ghi sao cho an toàn với luồng.

+ +

Điều này chủ yếu đúng với các phương pháp mà có thể được gọi từ xa—chẳng hạn như các phương pháp trong một dịch vụ gắn kết. Khi một lệnh gọi trên một +phương pháp được triển khai trong một {@link android.os.IBinder} khởi đầu trong cùng tiến trình mà +{@link android.os.IBinder IBinder} đang chạy, phương pháp đó sẽ được thực thi trong luồng của hàm gọi. +Tuy nhiên, khi lệnh gọi khởi đầu trong một tiến trình khác, phương pháp sẽ được thực thi trong một luồng được chọn từ +một tập hợp các luồng mà hệ thống duy trì trong cùng tiến trình như {@link android.os.IBinder +IBinder} (nó không được thực thi trong luồng UI của tiến trình). Ví dụ, trong khi phương pháp +{@link android.app.Service#onBind onBind()} của dịch vụ sẽ được gọi từ luồng UI của tiến trình +của dịch vụ, các phương pháp được triển khai trong đối tượng mà {@link android.app.Service#onBind +onBind()} trả về (ví dụ, một lớp con triển khai các phương pháp RPC) sẽ được gọi từ các luồng +trong tập hợp. Vì một dịch vụ có thể có nhiều hơn một máy khách, nhiều hơn một luồng tập hợp có thể sử dụng +cùng một phương pháp {@link android.os.IBinder IBinder} tại cùng một thời điểm. Vì thế, các phương pháp {@link android.os.IBinder +IBinder} phải được triển khai sao cho an toàn với luồng.

+ +

Tương tự, một trình cung cấp nội dung có thể nhận các yêu cầu dữ liệu khởi nguồn trong các tiến trình khác. +Mặc dù các lớp {@link android.content.ContentResolver} và {@link android.content.ContentProvider} +ẩn đi chi tiết về cách truyền thông liên tiến trình được quản lý, các phương pháp {@link +android.content.ContentProvider} hồi đáp những yêu cầu đó—các phương pháp {@link +android.content.ContentProvider#query query()}, {@link android.content.ContentProvider#insert +insert()}, {@link android.content.ContentProvider#delete delete()}, {@link +android.content.ContentProvider#update update()}, và {@link android.content.ContentProvider#getType +getType()}—được gọi từ một tập hợp luồng trong tiến trình của trình cung cấp nội dung, chứ không phải luồng +UI cho tiến trình đó. Vì những phương pháp này có thể được gọi từ bất kỳ số lượng luồng nào tại +cùng thời điểm, chúng cũng phải được triển khai sao cho an toàn với luồng.

+ + +

Truyền thông Liên Tiến trình

+ +

Android cung cấp một cơ chế cho truyền thông liên tiến trình (IPC) bằng cách sử dụng các lệnh gọi thủ tục từ xa +(RPC), trong đó một phương pháp được gọi bởi một hoạt động hoặc thành phần ứng dụng khác, nhưng được thực thi +từ xa (trong một tiến trình khác), với bất kỳ kết quả nào được trả về +hàm gọi. Điều này đòi hỏi việc phân tích một lệnh gọi phương pháp và dữ liệu của nó về cấp độ mà hệ điều hành +có thể hiểu được, truyền phát nó từ tiến trình và khoảng trống địa chỉ cục bộ đến tiến trình và +khoảng trống địa chỉ từ xa, sau đó tổ hợp lại và phát hành lại lệnh gọi ở đó. Sau đó, các giá trị trả về được +phát theo hướng ngược lại. Android cung cấp tất cả mã để thực hiện những giao tác +IPC này, vì thế bạn có thể tập trung vào việc định nghĩa và triển khai giao diện lập trình RPC.

+ +

Để thực hiện IPC, ứng dụng của bạn phải liên kết với một dịch vụ, bằng cách sử dụng {@link +android.content.Context#bindService bindService()}. Để biết thêm thông tin, hãy xem hướng dẫn cho nhà phát triển Dịch vụ.

+ + + diff --git a/docs/html-intl/intl/vi/guide/components/recents.jd b/docs/html-intl/intl/vi/guide/components/recents.jd new file mode 100644 index 0000000000000000000000000000000000000000..0a176145f9caa72d9b2b19552d2da69ac5340016 --- /dev/null +++ b/docs/html-intl/intl/vi/guide/components/recents.jd @@ -0,0 +1,256 @@ +page.title=Màn hình Tổng quan +page.tags="recents","overview" + +@jd:body + + + +

Màn hình tổng quan (còn được gọi là màn hình gần đây, danh sách tác vụ gần đây, hay ứng dụng gần đây) +là một UI cấp hệ thống liệt kê các +hoạt độngtác vụ mới được truy cập gần đây. Người dùng +có thể điều hướng qua danh sách này và chọn một tác vụ để tiếp tục, hoặc người dùng có thể loại bỏ một tác vụ khỏi +danh sách bằng cách trượt nhanh nó đi. Với việc phát hành Android 5.0 (API mức 21), nhiều thực thể của +hoạt động tương tự chứa các tài liệu khác nhau có thể xuất hiện dưới dạng các tác vụ trong màn hình tổng quan. Ví dụ, +Google Drive có thể có một tác vụ cho từng tài liệu trong một vài tài liệu Google. Mỗi tài liệu xuất hiện thành một +tác vụ trong màn hình tổng quan.

+ + +

Hình 1. Màn hình tổng quan hiển thị ba tài liệu Google Drive +, mỗi tài liệu được biểu diễn như một tác vụ riêng.

+ +

Thường thì bạn sẽ cho phép hệ thống định nghĩa cách tác vụ và +hoạt động của mình được biểu diễn như thế nào trong màn hình tổng quan, và bạn không cần sửa đổi hành vi này. +Tuy nhiên, ứng dụng của bạn có thể xác định cách thức và thời gian các hoạt động xuất hiện trong màn hình tổng quan. Lớp +{@link android.app.ActivityManager.AppTask} cho phép bạn quản lý tác vụ, và cờ hoạt động của +lớp {@link android.content.Intent} cho phép bạn quy định khi nào thì một hoạt động được thêm hoặc loại bỏ khỏi +màn hình tổng quan. Đồng thời, thuộc tính +<activity> cho phép bạn đặt hành vi trong bản kê khai.

+ +

Thêm Tác vụ vào Màn hình Tổng quan

+ +

Sử dụng cờ của lớp {@link android.content.Intent} để thêm một tác vụ cho phép kiểm soát nhiều hơn +đối với thời điểm và cách thức một tài liệu được mở hoặc mở lại trong màn hình tổng quan. Khi sử dụng các thuộc tính +<activity> +, bạn có thể chọn giữa luôn mở tài liệu trong một tác vụ mới hoặc sử dụng lại một +tác vụ hiện có cho tài liệu.

+ +

Sử dụng cờ Ý định để thêm một tác vụ

+ +

Khi tạo một tài liệu mới cho hoạt động của bạn, bạn gọi phương pháp +{@link android.app.ActivityManager.AppTask#startActivity(android.content.Context, android.content.Intent, android.os.Bundle) startActivity()} +của lớp {@link android.app.ActivityManager.AppTask}, chuyển cho nó ý định có +chức năng khởi chạy hoạt động. Để chèn một ngắt lô-gic sao cho hệ thống coi hoạt động của bạn như một tác vụ +mới trong màn hình tổng quan, hãy chuyển cờ {@link android.content.Intent#FLAG_ACTIVITY_NEW_DOCUMENT} +trong phương pháp {@link android.content.Intent#addFlags(int) addFlags()} của {@link android.content.Intent} +có chức năng khởi chạy hoạt động.

+ +

Lưu ý: Cờ {@link android.content.Intent#FLAG_ACTIVITY_NEW_DOCUMENT} +thay thế cờ {@link android.content.Intent#FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET}, +được rút bớt kể từ phiên bản Android 5.0 (API mức 21).

+ +

Nếu bạn đặt cờ {@link android.content.Intent#FLAG_ACTIVITY_MULTIPLE_TASK} khi tạo +tài liệu mới, hệ thống sẽ luôn tạo một tác vụ mới lấy hoạt động mục tiêu đó làm gốc. +Thiết đặt này cho phép mở cùng tài liệu trong nhiều hơn một tác vụ. Đoạn mã sau thể hiện +cách mà hoạt động chính thực hiện điều này:

+ +

+DocumentCentricActivity.java

+
+public void createNewDocument(View view) {
+      final Intent newDocumentIntent = newDocumentIntent();
+      if (useMultipleTasks) {
+          newDocumentIntent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
+      }
+      startActivity(newDocumentIntent);
+  }
+
+  private Intent newDocumentIntent() {
+      boolean useMultipleTasks = mCheckbox.isChecked();
+      final Intent newDocumentIntent = new Intent(this, NewDocumentActivity.class);
+      newDocumentIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
+      newDocumentIntent.putExtra(KEY_EXTRA_NEW_DOCUMENT_COUNTER, incrementAndGet());
+      return newDocumentIntent;
+  }
+
+  private static int incrementAndGet() {
+      Log.d(TAG, "incrementAndGet(): " + mDocumentCounter);
+      return mDocumentCounter++;
+  }
+}
+
+ +

Lưu ý: Các hoạt động được khởi chạy bằng cờ {@code FLAG_ACTIVITY_NEW_DOCUMENT} +phải có giá trị thuộc tính {@code android:launchMode="standard"} (mặc định) được đặt trong +bản kê khai.

+ +

Khi hoạt động chính khởi chạy một hoạt động mới, hệ thống sẽ tìm kiếm thông qua các tác vụ hiện tại để +xem tác vụ nào có ý định khớp với tên thành phần ý định và dữ liệu Ý định cho hoạt động đó. Nếu tác vụ +không được tìm thấy, hoặc ý định chứa cờ {@link android.content.Intent#FLAG_ACTIVITY_MULTIPLE_TASK} +thì một tác vụ mới sẽ được tạo lấy hoạt động làm gốc. Nếu tìm thấy, nó sẽ mang tác vụ đó +tới phía trước và chuyển ý định mới tới {@link android.app.Activity#onNewIntent onNewIntent()}. +Hoạt động mới sẽ nhận ý định và tạo một tài liệu mới trong màn hình tổng quan, như trong ví dụ +sau:

+ +

+NewDocumentActivity.java

+
+@Override
+protected void onCreate(Bundle savedInstanceState) {
+    super.onCreate(savedInstanceState);
+    setContentView(R.layout.activity_new_document);
+    mDocumentCount = getIntent()
+            .getIntExtra(DocumentCentricActivity.KEY_EXTRA_NEW_DOCUMENT_COUNTER, 0);
+    mDocumentCounterTextView = (TextView) findViewById(
+            R.id.hello_new_document_text_view);
+    setDocumentCounterText(R.string.hello_new_document_counter);
+}
+
+@Override
+protected void onNewIntent(Intent intent) {
+    super.onNewIntent(intent);
+    /* If FLAG_ACTIVITY_MULTIPLE_TASK has not been used, this activity
+    is reused to create a new document.
+     */
+    setDocumentCounterText(R.string.reusing_document_counter);
+}
+
+ + +

Sử dụng thuộc tính hoạt động để thêm một tác vụ

+ +

Một hoạt động cũng có thể quy định trong bản kê khai của nó rằng nó luôn khởi chạy vào một tác vụ mới bằng cách sử dụng +thuộc tính <activity> +, +{@code android:documentLaunchMode}. Thuộc tính này có bốn giá trị tạo ra hiệu ứng +sau khi người dùng mở một tài liệu bằng ứng dụng:

+ +
+
"{@code intoExisting}"
+
Hoạt động sử dụng lại một tác vụ hiện có cho tài liệu. Điều này giống như khi thiết đặt cờ + {@link android.content.Intent#FLAG_ACTIVITY_NEW_DOCUMENT} mà không thiết đặt cờ + {@link android.content.Intent#FLAG_ACTIVITY_MULTIPLE_TASK}, như được mô tả trong phần + Sử dụng cờ Ý định để thêm một tác vụ bên trên.
+ +
"{@code always}"
+
Hoạt động tạo một tác vụ mới cho tài liệu, ngay cả khi tài liệu đã được mở. Sử dụng + giá trị này giống như thiết đặt cả cờ {@link android.content.Intent#FLAG_ACTIVITY_NEW_DOCUMENT} + và {@link android.content.Intent#FLAG_ACTIVITY_MULTIPLE_TASK}.
+ +
"{@code none”}"
+
Hoạt động không tạo một tác vụ mới cho tài liệu. Màn hình tổng quan xử lý hoạt động + như theo mặc định: nó hiển thị một tác vụ đơn lẻ cho ứng dụng, tác vụ này + tiếp tục từ bất kỳ hoạt động nào mà người dùng đã gọi ra cuối cùng.
+ +
"{@code never}"
+
Hoạt động không tạo một tác vụ mới cho tài liệu. Việc thiết đặt giá trị này sẽ khống chế + hành vi của {@link android.content.Intent#FLAG_ACTIVITY_NEW_DOCUMENT} + và cờ {@link android.content.Intent#FLAG_ACTIVITY_MULTIPLE_TASK}, nếu một trong hai được đặt + trong ý định, và màn hình tổng quan sẽ hiển thị một tác vụ đơn lẻ cho ứng dụng, tác vụ này tiếp tục từ + bất kỳ hoạt động nào mà người dùng đã gọi ra cuối cùng.
+
+ +

Lưu ý: Đối với những giá trị ngoài {@code none} và {@code never} +hoạt động phải được định nghĩa bằng {@code launchMode="standard"}. Nếu thuộc tính này không được quy định thì +{@code documentLaunchMode="none"} sẽ được sử dụng.

+ +

Loại bỏ Tác vụ

+ +

Theo mặc định, một tác vụ tài liệu sẽ tự động được loại bỏ khỏi màn hình tổng quan khi hoạt động của nó +hoàn thành. Bạn có thể khống chế hành vi này bằng lớp {@link android.app.ActivityManager.AppTask}, +bằng một cờ {@link android.content.Intent}, hoặc bằng một thuộc tính +<activity>.

+ +

Bạn có thể luôn loại trừ hoàn toàn một tác vụ khỏi màn hình tổng quan bằng cách thiết đặt thuộc tính +<activity> +, +{@code android:excludeFromRecents} thành {@code true}.

+ +

Bạn có thể thiết đặt số lượng tác vụ tối đa mà ứng dụng của bạn có thể bao gồm trong màn hình tổng quan bằng cách đặt thuộc tính + <activity> + {@code android:maxRecents} + thành một giá trị số nguyên. Mặc định là 16. Khi đạt được số tác vụ tối đa, +tác vụ ít sử dụng gần đây nhất sẽ bị loại bỏ khỏi màn hình tổng quan. Giá trị tối đa {@code android:maxRecents} +bằng 50 (25 trên các thiết bị có bộ nhớ thấp); giá trị thấp hơn 1 không hợp lệ.

+ +

Sử dụng lớp AppTask để loại bỏ tác vụ

+ +

Trong hoạt động mà tạo một tác vụ mới trong màn hình tổng quan, bạn có thể +quy định khi nào thì loại bỏ tác vụ và hoàn thành tất cả các hoạt động gắn liền với nó bằng cách gọi +phương pháp {@link android.app.ActivityManager.AppTask#finishAndRemoveTask() finishAndRemoveTask()}.

+ +

+NewDocumentActivity.java

+
+public void onRemoveFromRecents(View view) {
+    // The document is no longer needed; remove its task.
+    finishAndRemoveTask();
+}
+
+ +

Lưu ý: Sử dụng phương pháp +{@link android.app.ActivityManager.AppTask#finishAndRemoveTask() finishAndRemoveTask()} +sẽ khống chế việc sử dụng tag {@link android.content.Intent#FLAG_ACTIVITY_RETAIN_IN_RECENTS}, +như được trình bày ở bên dưới.

+ +

Giữ lại tác vụ đã hoàn thành

+ +

Nếu bạn muốn giữ lại một tác vụ trong màn hình tổng quan, ngay cả khi hoạt động của nó đã hoàn thành, hãy chuyển +cờ {@link android.content.Intent#FLAG_ACTIVITY_RETAIN_IN_RECENTS} trong phương pháp +{@link android.content.Intent#addFlags(int) addFlags()} của Ý định mà khởi chạy hoạt động.

+ +

+DocumentCentricActivity.java

+
+private Intent newDocumentIntent() {
+    final Intent newDocumentIntent = new Intent(this, NewDocumentActivity.class);
+    newDocumentIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT |
+      android.content.Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS);
+    newDocumentIntent.putExtra(KEY_EXTRA_NEW_DOCUMENT_COUNTER, incrementAndGet());
+    return newDocumentIntent;
+}
+
+ +

Để đạt được cùng kết quả như vậy, hãy đặt thuộc tính +<activity> + +{@code android:autoRemoveFromRecents} thành {@code false}. Giá trị mặc định bằng {@code true} +đối với các hoạt động tài liệu, và {@code false} đối với các hoạt động thông thường. Việc sử dụng thuộc tính này sẽ khống chế +cờ {@link android.content.Intent#FLAG_ACTIVITY_RETAIN_IN_RECENTS}, như đã trình bày trước đó.

+ + + + + + + diff --git a/docs/html-intl/intl/vi/guide/components/services.jd b/docs/html-intl/intl/vi/guide/components/services.jd new file mode 100644 index 0000000000000000000000000000000000000000..9e3e6c75eccf589d42e2f92fb79be58ff0060f9d --- /dev/null +++ b/docs/html-intl/intl/vi/guide/components/services.jd @@ -0,0 +1,813 @@ +page.title=Dịch vụ +@jd:body + + + + +

{@link android.app.Service} là một thành phần ứng dụng có khả năng thực hiện +các thao tác chạy kéo dài trong nền và không cung cấp giao diện người dùng. Một +thành phần ứng dụng khác có thể bắt đầu một dịch vụ và nó sẽ tiếp tục chạy ngầm ngay cả khi người dùng +chuyển sang một ứng dụng khác. Ngoài ra, một thành phần có thể gắn kết với một dịch vụ để +tương tác với nó và thậm chí thực hiện truyền thông liên tiến trình (IPC). Ví dụ, một dịch vụ có thể +xử lý các giao dịch mạng, phát nhạc, thực hiện I/O tệp, hoặc tương tác với một trình cung cấp nội dung, tất cả +đều xuất phát từ nền.

+ +

Về cơ bản, một dịch vụ có thể có hai dạng:

+ +
+
Được bắt đầu
+
Dịch vụ có dạng "được bắt đầu" khi một thành phần ứng dụng (chẳng hạn như một hoạt động) bắt đầu nó bằng cách +gọi {@link android.content.Context#startService startService()}. Sau khi được bắt đầu, dịch vụ +có thể chạy ngầm vô thời hạn, ngay cả khi thành phần bắt đầu nó bị hủy. Thông thường, +dịch vụ được bắt đầu sẽ thực hiện một thao tác đơn lẻ và không trả về kết quả cho hàm gọi. +Ví dụ, nó có thể tải xuống hoặc tải lên một tệp thông qua mạng. Khi thao tác được hoàn thành, dịch vụ +tự nó sẽ dừng lại.
+
Gắn kết
+
Dịch vụ có dạng "gắn kết" khi một thành phần ứng dụng gắn kết với nó bằng cách gọi {@link +android.content.Context#bindService bindService()}. Dịch vụ gắn kết sẽ đưa ra +một giao diện máy khách-máy chủ cho phép các thành phần tương tác với dịch vụ, gửi yêu cầu, nhận kết quả, và thậm chí +làm vậy thông qua truyền thông liên tiến trình (IPC). Dịch vụ gắn kết chỉ chạy trong khi +một thành phần ứng dụng khác được gắn kết với nó. Nhiều thành phần có thể gắn kết cùng lúc với dịch vụ, +nhưng khi tất cả bị bỏ gắn kết thì dịch vụ sẽ bị hủy.
+
+ +

Mặc dù tài liệu này thường đề cập tới hai loại dịch vụ riêng rẽ, dịch vụ +của bạn có thể hoạt động theo cả hai cách—nó có thể được bắt đầu (để chạy vô thời hạn) và cũng cho phép gắn kết. +Đó đơn giản là vấn đề bạn có triển khai một cặp phương pháp gọi lại hay không: {@link +android.app.Service#onStartCommand onStartCommand()} để cho phép thành phần bắt đầu nó và {@link +android.app.Service#onBind onBind()} để cho phép nó gắn kết.

+ +

Không phụ thuộc vào việc ứng dụng của bạn được bắt đầu, gắn kết, hay cả hai, bất kỳ thành phần ứng dụng nào +cũng có thể sử dụng dịch vụ (thậm chí từ một ứng dụng riêng biệt), giống như cách mà bất kỳ thành phần nào cũng có thể sử dụng +một hoạt động—bằng cách bắt đầu nó bằng một {@link android.content.Intent}. Tuy nhiên, bạn có thể khai báo +dịch vụ là riêng tư trong tệp bản kê khai, và chặn truy cập từ các ứng dụng khác. Điều này +được trình bày kỹ hơn trong phần về Khai báo dịch vụ trong +bản kê khai.

+ +

Chú ý: Một dịch vụ chạy trong +luồng chính của tiến trình lưu trữ của nó—dịch vụ không tạo luồng của chính nó +và không chạy trong một tiến trình riêng biệt (trừ khi bạn quy định khác). Điều này có nghĩa +là, nếu dịch vụ của bạn định thực hiện bất kỳ công việc nặng nào đối với CPU hay chặn các thao tác (chẳng hạn như phát lại MP3 +hay kết nối mạng), bạn nên tạo một luồng mới bên trong dịch vụ để thực hiện công việc đó. Bằng cách sử dụng +một luồng riêng biệt, bạn sẽ giảm rủi ro gặp lỗi Ứng dụng Không Hồi đáp (ANR) và luồng chính của ứng dụng có thể +vẫn dành riêng cho tương tác giữa người dùng với các hoạt động của bạn.

+ + +

Nội dung Cơ bản

+ + + +

Để tạo một dịch vụ, bạn phải tạo một lớp con của {@link android.app.Service} (hoặc một +trong các lớp con hiện tại của nó). Trong triển khai của mình, bạn cần khống chế một số phương pháp gọi lại có chức năng +xử lý những khía cạnh chính trong vòng đời của dịch vụ và cung cấp một cơ chế để các thành phần gắn kết với +dịch vụ đó, nếu phù hợp. Những phương pháp gọi lại quan trọng nhất mà bạn nên khống chế là:

+ +
+
{@link android.app.Service#onStartCommand onStartCommand()}
+
Hệ thống sẽ gọi phương pháp này khi một thành phần khác, chẳng hạn như một hoạt động, +yêu cầu dịch vụ phải được bắt đầu, bằng cách gọi {@link android.content.Context#startService +startService()}. Sau khi phương pháp này thực thi, dịch vụ sẽ được bắt đầu và có thể chạy vô thời hạn trong +nền. Nếu bạn triển khai điều này, bạn có trách nhiệm dừng dịch vụ khi +công việc của nó được hoàn thành, bằng cách gọi {@link android.app.Service#stopSelf stopSelf()} hoặc {@link +android.content.Context#stopService stopService()}. (Nếu chỉ muốn cung cấp khả năng gắn kết, bạn không +cần triển khai phương pháp này.)
+
{@link android.app.Service#onBind onBind()}
+
Hệ thống sẽ gọi phương pháp này khi một thành phần khác muốn gắn kết với +dịch vụ (chẳng hạn như để thực hiện RPC), bằng cách gọi {@link android.content.Context#bindService +bindService()}. Trong triển khai phương pháp này của mình, bạn phải cung cấp một giao diện mà các máy khách +sử dụng để giao tiếp với dịch vụ, bằng cách trả về {@link android.os.IBinder}. Bạn phải luôn +triển khai phương pháp này, nhưng nếu bạn không muốn cho phép gắn kết thì bạn nên trả về rỗng.
+
{@link android.app.Service#onCreate()}
+
Hệ thống sẽ gọi phương pháp này khi dịch vụ được tạo lập lần đầu, để thực hiện quy trình thiết lập một lần +(trước khi nó có thể gọi hoặc {@link android.app.Service#onStartCommand onStartCommand()} hoặc +{@link android.app.Service#onBind onBind()}). Nếu dịch vụ đã đang chạy, phương pháp này sẽ không được +gọi.
+
{@link android.app.Service#onDestroy()}
+
Hệ thống sẽ gọi phương pháp này khi dịch vụ không còn được sử dụng và đang bị hủy. +Dịch vụ của bạn sẽ triển khai phương pháp này để dọn dẹp mọi tài nguyên như luồng, đối tượng theo dõi +được đăng ký, hàm nhận, v.v... Đây là lệnh gọi cuối cùng mà dịch vụ nhận được.
+
+ +

Nếu một thành phần bắt đầu dịch vụ bằng cách gọi {@link +android.content.Context#startService startService()} (kết quả là một lệnh gọi tới {@link +android.app.Service#onStartCommand onStartCommand()}), khi đó dịch vụ +sẽ vẫn chạy tới khi tự nó dừng bằng {@link android.app.Service#stopSelf()} hoặc một +thành phần khác dừng nó bằng cách gọi {@link android.content.Context#stopService stopService()}.

+ +

Nếu một thành phần gọi +{@link android.content.Context#bindService bindService()} để tạo dịch vụ (và {@link +android.app.Service#onStartCommand onStartCommand()} không được gọi), khi đó dịch vụ sẽ chỉ chạy +khi nào mà thành phần đó còn gắn kết với nó. Sau khi dịch vụ được bỏ gắn kết khỏi tất cả máy khách, hệ thống +sẽ hủy nó.

+ +

Hệ thống Android sẽ buộc dừng một dịch vụ chỉ khi bộ nhớ thấp và nó phải khôi phục tài nguyên +của hệ thống cho hoạt động có tiêu điểm của người dùng. Nếu dịch vụ gắn kết với một hoạt động mà có tiêu điểm +của người dùng, khi đó sẽ có ít khả năng nó sẽ bị tắt bỏ hơn, và nếu dịch vụ được khai báo là chạy trong tiền cảnh (đề cập sau), khi đó nó sẽ hầu như không bao giờ bị tắt bỏ. +Mặt khác, nếu dịch vụ được bắt đầu và chạy trong thời gian dài, hệ thống sẽ hạ thấp vị trí của nó +trong danh sách tác vụ chạy ngầm qua thời gian và dịch vụ sẽ rất có thể bị +tắt bỏ—nếu dịch vụ của bạn được bắt đầu, khi đó bạn phải thiết kế nó để +xử lý việc khởi động lại do hệ thống một cách uyển chuyển. Nếu hệ thống tắt bỏ dịch vụ của bạn, nó sẽ khởi động lại dịch vụ ngay khi tài nguyên +có sẵn trở lại (mặc dù điều này cũng phụ thuộc vào giá trị mà bạn trả về từ {@link +android.app.Service#onStartCommand onStartCommand()}, vấn đề này sẽ được bàn sau). Để biết thêm thông tin +về thời điểm mà hệ thống có thể hủy một dịch vụ, hãy xem tài liệu Tiến trình và Luồng +.

+ +

Trong những phần sau, bạn sẽ thấy cách bạn có thể tạo từng loại dịch vụ và cách sử dụng +nó từ các thành phần ứng dụng khác.

+ + + +

Khai báo một dịch vụ trong bản kê khai

+ +

Giống như hoạt động (và các thành phần khác), bạn phải khai báo tất cả dịch vụ trong tệp bản kê khai +của ứng dụng của mình.

+ +

Để khai báo dịch vụ của bạn, hãy thêm một phần tử {@code <service>} làm +con của phần tử {@code <application>} +. Ví dụ:

+ +
+<manifest ... >
+  ...
+  <application ... >
+      <service android:name=".ExampleService" />
+      ...
+  </application>
+</manifest>
+
+ +

Xem tham chiếu phần tử {@code <service>} +để biết thêm thông tin về việc khai báo dịch vụ của bạn trong bản kê khai.

+ +

Có các thuộc tính khác mà bạn có thể bao gồm trong phần tử {@code <service>} để +định nghĩa các tính chất chẳng hạn như những quyền cần để bắt đầu dịch vụ và tiến trình mà +dịch vụ sẽ chạy trong đó. Thuộc tính {@code android:name} +là thuộc tính bắt buộc duy nhất—nó quy định tên lớp của dịch vụ. Một khi +bạn phát hành ứng dụng của mình, bạn không nên thay đổi tên này, vì nếu bạn làm vậy, bạn sẽ gặp rủi ro làm gãy +mã do sự phụ thuộc vào các ý định biểu thị để bắt đầu hoặc gắn kết dịch vụ (đọc bài đăng blog, Những Điều +Không Thay Đổi Được). + +

Để đảm bảo ứng dụng của bạn được bảo mật, luôn sử dụng một ý định biểu thị khi bắt đầu hoặc gắn kết +{@link android.app.Service} của bạn và không được khai báo bộ lọc ý định cho dịch vụ. Nếu +điều trọng yếu là bạn phải cho phép một chút không rõ ràng về dịch vụ nào sẽ bắt đầu, bạn có thể +cung cấp bộ lọc ý định cho dịch vụ của mình và loại bỏ tên thành phần khỏi {@link +android.content.Intent}, nhưng sau đó bạn có thể đặt gói cho ý định bằng {@link +android.content.Intent#setPackage setPackage()}, điều này cung cấp sự không rõ ràng vừa đủ cho +dịch vụ mục tiêu đó.

+ +

Ngoài ra, bạn có thể đảm bảo rằng dịch vụ của mình chỉ sẵn có cho ứng dụng của bạn bằng cách +đưa vào thuộc tính {@code android:exported} +và đặt nó thành {@code "false"}. Điều này sẽ dừng việc các ứng dụng khác bắt đầu +dịch vụ của bạn, ngay cả khi sử dụng một ý định biểu thị.

+ + + + +

Tạo một Dịch vụ được Bắt đầu

+ +

Dịch vụ được bắt đầu là dịch vụ mà một thành phần khác bắt đầu bằng cách gọi {@link +android.content.Context#startService startService()}, kết quả là một lệnh gọi tới phương pháp +{@link android.app.Service#onStartCommand onStartCommand()} của dịch vụ.

+ +

Khi một dịch vụ được bắt đầu, nó có một vòng đời độc lập với +thành phần đã bắt đầu nó và dịch vụ có thể chạy ngầm vô thời hạn, ngay cả khi +thành phần bắt đầu nó bị hủy. Như vậy, dịch vụ sẽ tự dừng khi làm xong công việc của nó +bằng cách gọi {@link android.app.Service#stopSelf stopSelf()}, hoặc một thành phần khác có thể dừng nó +bằng cách gọi {@link android.content.Context#stopService stopService()}.

+ +

Một thành phần ứng dụng chẳng hạn như một hoạt động có thể bắt đầu dịch vụ bằng cách gọi {@link +android.content.Context#startService startService()} và chuyển một {@link android.content.Intent} +trong đó quy định dịch vụ và bao gồm bất kỳ dữ liệu nào để cho dịch vụ sử dụng. Dịch vụ sẽ nhận +{@link android.content.Intent} này trong phương pháp {@link android.app.Service#onStartCommand +onStartCommand()}.

+ +

Ví dụ, giả sử một hoạt động cần lưu một số dữ liệu vào cơ sở dữ liệu trực tuyến. Hoạt động có thể +bắt đầu một dịch vụ đồng hành và truyền cho nó dữ liệu để lưu bằng cách chuyển một ý định tới {@link +android.content.Context#startService startService()}. Dịch vụ sẽ nhận ý định trong {@link +android.app.Service#onStartCommand onStartCommand()}, kết nối với Internet và thực hiện +giao tác cơ sở dữ liệu. Khi giao tác được thực hiện, dịch vụ sẽ tự dừng lại và nó bị +hủy.

+ +

Chú ý: Một dịch vụ sẽ chạy trong cùng tiến trình như ứng dụng +mà nó được khai báo trong đó và trong luồng chính của ứng dụng đó theo mặc định. Vì vậy, nếu dịch vụ của bạn +thực hiện các thao tác tăng cường hoặc chặn trong khi người dùng tương tác với một hoạt động từ cùng +ứng dụng, dịch vụ sẽ làm chậm hiệu năng của hoạt động. Để tránh tác động tới hiệu năng của +ứng dụng, bạn nên bắt đầu một luồng mới bên trong dịch vụ.

+ +

Thông thường, có hai lớp mà bạn có thể mở rộng để tạo một dịch vụ được bắt đầu:

+
+
{@link android.app.Service}
+
Đây là lớp cơ bản cho tất cả dịch vụ. Khi bạn mở rộng lớp này, điều quan trọng là +bạn tạo một luồng mới để thực hiện tất cả công việc của dịch vụ trong đó, do dịch vụ sử dụng luồng chính +của ứng dụng của bạn, theo mặc định, điều này có thể làm chậm hiệu năng của bất kỳ hoạt động nào mà ứng dụng +của bạn đang chạy.
+
{@link android.app.IntentService}
+
Đây là một lớp con của {@link android.app.Service} có chức năng sử dụng một luồng trình thực hiện để xử lý tất cả +yêu cầu bắt đầu một cách lần lượt. Đây là lựa chọn tốt nhất nếu bạn không yêu cầu dịch vụ của mình +xử lý đồng thời nhiều yêu cầu. Tất cả những gì bạn cần làm đó là triển khai {@link +android.app.IntentService#onHandleIntent onHandleIntent()}, nó sẽ nhận ý định cho mỗi +yêu cầu bắt đầu để bạn có thể thực hiện công việc chạy ngầm.
+
+ +

Các phần sau mô tả cách bạn có thể triển khai dịch vụ của mình bằng cách sử dụng một trong các cách cho những lớp +này.

+ + +

Mở rộng lớp IntentService

+ +

Vì phần lớn các dịch vụ được bắt đầu không cần xử lý nhiều yêu cầu một cách đồng thời +(điều này thực sự có thể là một kịch bản tạo đa luồng nguy hiểm), có lẽ tốt nhất là nếu bạn +triển khai dịch vụ của mình bằng cách sử dụng lớp {@link android.app.IntentService}.

+ +

{@link android.app.IntentService} làm điều sau đây:

+ +
    +
  • Tạo một luồng trình thực hiện mặc định để thực thi tất cả ý định được chuyển tới {@link +android.app.Service#onStartCommand onStartCommand()} tách riêng với luồng +chính của ứng dụng của bạn.
  • +
  • Tạo một hàng đợi công việc để chuyển lần lượt từng ý định tới triển khai {@link +android.app.IntentService#onHandleIntent onHandleIntent()} của bạn, vì thế bạn không bao giờ phải +lo lắng về vấn đề tạo đa luồng.
  • +
  • Dừng dịch vụ sau khi tất cả yêu cầu bắt đầu đều đã được xử lý, vì thế bạn không bao giờ phải gọi +{@link android.app.Service#stopSelf}.
  • +
  • Cung cấp triển khai mặc định của {@link android.app.IntentService#onBind onBind()} mà +trả về rỗng.
  • +
  • Cung cấp triển khai mặc định của {@link android.app.IntentService#onStartCommand +onStartCommand()} mà gửi ý định tới hàng đợi công việc rồi tới triển khai {@link +android.app.IntentService#onHandleIntent onHandleIntent()} của bạn.
  • +
+ +

Tất cả đều nói lên một thực tế rằng tất cả những việc bạn cần làm đó là triển khai {@link +android.app.IntentService#onHandleIntent onHandleIntent()} để thực hiện công việc mà +máy khách cung cấp. (Mặc dù bạn cũng cần cung cấp một hàm dựng nhỏ cho dịch vụ.)

+ +

Sau đây là ví dụ về triển khai {@link android.app.IntentService}:

+ +
+public class HelloIntentService extends IntentService {
+
+  /**
+   * A constructor is required, and must call the super {@link android.app.IntentService#IntentService}
+   * constructor with a name for the worker thread.
+   */
+  public HelloIntentService() {
+      super("HelloIntentService");
+  }
+
+  /**
+   * The IntentService calls this method from the default worker thread with
+   * the intent that started the service. When this method returns, IntentService
+   * stops the service, as appropriate.
+   */
+  @Override
+  protected void onHandleIntent(Intent intent) {
+      // Normally we would do some work here, like download a file.
+      // For our sample, we just sleep for 5 seconds.
+      long endTime = System.currentTimeMillis() + 5*1000;
+      while (System.currentTimeMillis() < endTime) {
+          synchronized (this) {
+              try {
+                  wait(endTime - System.currentTimeMillis());
+              } catch (Exception e) {
+              }
+          }
+      }
+  }
+}
+
+ +

Đó là tất cả những gì bạn cần: một hàm dựng và triển khai {@link +android.app.IntentService#onHandleIntent onHandleIntent()}.

+ +

Nếu bạn quyết định cũng khống chế các phương pháp gọi lại khác, chẳng hạn như {@link +android.app.IntentService#onCreate onCreate()}, {@link +android.app.IntentService#onStartCommand onStartCommand()}, hoặc {@link +android.app.IntentService#onDestroy onDestroy()}, hãy nhớ gọi ra siêu triển khai, sao +cho {@link android.app.IntentService} có thể xử lý hợp lý vòng đời của luồng trình thực hiện.

+ +

Ví dụ, {@link android.app.IntentService#onStartCommand onStartCommand()} phải trả về +triển khai mặc định (đó là cách mà ý định được chuyển tới {@link +android.app.IntentService#onHandleIntent onHandleIntent()}):

+ +
+@Override
+public int onStartCommand(Intent intent, int flags, int startId) {
+    Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show();
+    return super.onStartCommand(intent,flags,startId);
+}
+
+ +

Bên cạnh {@link android.app.IntentService#onHandleIntent onHandleIntent()}, phương pháp duy nhất mà +từ đó bạn không cần gọi siêu lớp là {@link android.app.IntentService#onBind +onBind()} (nhưng bạn chỉ cần triển khai điều đó nếu dịch vụ của bạn cho phép gắn kết).

+ +

Trong phần tiếp theo, bạn sẽ thấy cách mà cùng loại dịch vụ được triển khai khi mở rộng +lớp {@link android.app.Service} cơ sở, nó có nhiều mã hơn nhưng có thể +phù hợp nếu bạn cần xử lý các yêu cầu bắt đầu đồng thời.

+ + +

Mở rộng lớp Dịch vụ

+ +

Như bạn thấy trong phần trước, sử dụng {@link android.app.IntentService} giúp việc +triển khai một dịch vụ được bắt đầu của bạn trở nên rất đơn giản. Tuy nhiên, nếu bạn cần dịch vụ của mình +thực hiện tạo đa luồng (thay vì xử lý các yêu cầu bắt đầu thông qua một hàng đợi công việc), khi đó bạn +có thể mở rộng lớp {@link android.app.Service} để xử lý từng ý định.

+ +

Để so sánh, đoạn mã mẫu sau là triển khai lớp {@link +android.app.Service} mà thực hiện chính xác cùng công việc như ví dụ bên trên bằng cách sử dụng {@link +android.app.IntentService}. Cụ thể, đối với mỗi yêu cầu bắt đầu, nó sẽ sử dụng một luồng trình thực hiện để thực hiện +công việc và chỉ xử lý lần lượt từng yêu cầu một.

+ +
+public class HelloService extends Service {
+  private Looper mServiceLooper;
+  private ServiceHandler mServiceHandler;
+
+  // Handler that receives messages from the thread
+  private final class ServiceHandler extends Handler {
+      public ServiceHandler(Looper looper) {
+          super(looper);
+      }
+      @Override
+      public void handleMessage(Message msg) {
+          // Normally we would do some work here, like download a file.
+          // For our sample, we just sleep for 5 seconds.
+          long endTime = System.currentTimeMillis() + 5*1000;
+          while (System.currentTimeMillis() < endTime) {
+              synchronized (this) {
+                  try {
+                      wait(endTime - System.currentTimeMillis());
+                  } catch (Exception e) {
+                  }
+              }
+          }
+          // Stop the service using the startId, so that we don't stop
+          // the service in the middle of handling another job
+          stopSelf(msg.arg1);
+      }
+  }
+
+  @Override
+  public void onCreate() {
+    // Start up the thread running the service.  Note that we create a
+    // separate thread because the service normally runs in the process's
+    // main thread, which we don't want to block.  We also make it
+    // background priority so CPU-intensive work will not disrupt our UI.
+    HandlerThread thread = new HandlerThread("ServiceStartArguments",
+            Process.THREAD_PRIORITY_BACKGROUND);
+    thread.start();
+
+    // Get the HandlerThread's Looper and use it for our Handler
+    mServiceLooper = thread.getLooper();
+    mServiceHandler = new ServiceHandler(mServiceLooper);
+  }
+
+  @Override
+  public int onStartCommand(Intent intent, int flags, int startId) {
+      Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show();
+
+      // For each start request, send a message to start a job and deliver the
+      // start ID so we know which request we're stopping when we finish the job
+      Message msg = mServiceHandler.obtainMessage();
+      msg.arg1 = startId;
+      mServiceHandler.sendMessage(msg);
+
+      // If we get killed, after returning from here, restart
+      return START_STICKY;
+  }
+
+  @Override
+  public IBinder onBind(Intent intent) {
+      // We don't provide binding, so return null
+      return null;
+  }
+
+  @Override
+  public void onDestroy() {
+    Toast.makeText(this, "service done", Toast.LENGTH_SHORT).show();
+  }
+}
+
+ +

Như bạn có thể thấy, có nhiều việc hơn nhiều so với việc sử dụng {@link android.app.IntentService}.

+ +

Tuy nhiên, do bạn tự mình xử lý từng lệnh gọi đến {@link android.app.Service#onStartCommand +onStartCommand()}, bạn có thể thực hiện nhiều yêu cầu một cách đồng thời. Đó không phải là việc +mà ví dụ này làm, nhưng nếu đó là việc bạn muốn, vậy bạn có thể tạo một luồng mới cho từng +yêu cầu và ngay lập tức trả chúng về (thay vì đợi tới khi yêu cầu trước hoàn thành).

+ +

Để ý rằng phương pháp {@link android.app.Service#onStartCommand onStartCommand()} phải trả về một +số nguyên. Số nguyên là một giá trị mô tả cách hệ thống nên tiếp tục dịch vụ trong +trường hợp hệ thống tắt bỏ nó (như được đề cập ở trên, triển khai mặc định cho {@link +android.app.IntentService} sẽ xử lý điều này cho bạn dù bạn có thể sửa đổi nó). Giá trị trả về +từ {@link android.app.Service#onStartCommand onStartCommand()} phải là một trong các +hằng số sau:

+ +
+
{@link android.app.Service#START_NOT_STICKY}
+
Nếu hệ thống tắt bỏ dịch vụ sau khi {@link android.app.Service#onStartCommand +onStartCommand()} trả về, không được tạo lại dịch vụ đó, trừ khi có các ý định +đang chờ để được chuyển. Đây là lựa chọn an toàn nhất để tránh chạy dịch vụ của bạn khi không cần thiết +và khi ứng dụng của bạn có thể đơn thuần khởi động lại bất kỳ công việc chưa hoàn thành nào.
+
{@link android.app.Service#START_STICKY}
+
Nếu hệ thống tắt bỏ dịch vụ sau khi {@link android.app.Service#onStartCommand +onStartCommand()} trả về, hãy tạo lại dịch vụ và gọi {@link +android.app.Service#onStartCommand onStartCommand()}, nhưng không chuyển lại ý định cuối cùng. +Thay vào đó, hệ thống sẽ gọi {@link android.app.Service#onStartCommand onStartCommand()} bằng một +ý định rỗng, trừ khi có các ý định đang chờ để bắt đầu dịch vụ, trong trường hợp đó, +những ý định này sẽ được chuyển. Điều này phù hợp với các trình phát phương tiện (hoặc dịch vụ tương tự) mà không +đang thực thi lệnh, nhưng đang chạy vô thời hạn và chờ một tác vụ.
+
{@link android.app.Service#START_REDELIVER_INTENT}
+
Nếu hệ thống tắt bỏ dịch vụ sau khi {@link android.app.Service#onStartCommand +onStartCommand()} trả về, hãy tạo lại dịch vụ và gọi {@link +android.app.Service#onStartCommand onStartCommand()} bằng ý định cuối cùng được chuyển tới +dịch vụ. Mọi ý định chờ đều được chuyển lần lượt. Điều này phù hợp với các dịch vụ đang +chủ động thực hiện một công việc mà nên được tiếp tục ngay lập tức, chẳng hạn như tải xuống một tệp.
+
+

Để biết thêm chi tiết về những giá trị trả về này, hãy xem tài liệu tham khảo được liên kết cho từng +hằng số.

+ + + +

Bắt đầu một Dịch vụ

+ +

Bạn có thể bắt đầu một dịch vụ từ một hoạt động hoặc thành phần ứng dụng khác bằng cách chuyển một +{@link android.content.Intent} (quy định dịch vụ sẽ bắt đầu) đến {@link +android.content.Context#startService startService()}. Hệ thống Android sẽ gọi phương pháp {@link +android.app.Service#onStartCommand onStartCommand()} của dịch vụ và chuyển cho nó {@link +android.content.Intent}. (Bạn tuyệt đối không nên trực tiếp gọi {@link android.app.Service#onStartCommand +onStartCommand()}.)

+ +

Ví dụ, một hoạt động có thể bắt đầu dịch vụ ví dụ trong phần trước ({@code +HelloSevice}) bằng cách sử dụng một ý định biểu thị với {@link android.content.Context#startService +startService()}:

+ +
+Intent intent = new Intent(this, HelloService.class);
+startService(intent);
+
+ +

Phương pháp {@link android.content.Context#startService startService()} ngay lập tức trả về và +hệ thống Android sẽ gọi phương pháp {@link android.app.Service#onStartCommand +onStartCommand()} của dịch vụ. Nếu dịch vụ không đang chạy, trước tiên hệ thống sẽ gọi {@link +android.app.Service#onCreate onCreate()}, rồi gọi {@link android.app.Service#onStartCommand +onStartCommand()}.

+ +

Nếu dịch vụ cũng không cung cấp khả năng gắn kết, ý định được chuyển bằng {@link +android.content.Context#startService startService()} sẽ là phương thức giao tiếp duy nhất giữa +thành phần ứng dụng và dịch vụ. Tuy nhiên, nếu bạn muốn dịch vụ gửi một kết quả trở lại, khi đó +máy khách mà bắt đầu dịch vụ có thể tạo một {@link android.app.PendingIntent} cho một quảng bá +(bằng {@link android.app.PendingIntent#getBroadcast getBroadcast()}) và chuyển nó tới dịch vụ +trong {@link android.content.Intent} mà bắt đầu dịch vụ. Khi đó, dịch vụ có thể sử dụng +quảng bá để chuyển kết quả.

+ +

Nhiều yêu cầu bắt đầu dịch vụ sẽ dẫn đến nhiều lệnh gọi tương ứng tới +{@link android.app.Service#onStartCommand onStartCommand()} của dịch vụ. Tuy nhiên, chỉ có một yêu cầu dừng +dịch vụ (bằng {@link android.app.Service#stopSelf stopSelf()} hoặc {@link +android.content.Context#stopService stopService()}) là bắt buộc để dừng nó.

+ + +

Dừng một dịch vụ

+ +

Dịch vụ được bắt đầu phải quản lý vòng đời của chính nó. Cụ thể, hệ thống không dừng +hay hủy dịch vụ trừ khi nó phải khôi phục bộ nhớ của hệ thống và dịch vụ +sẽ tiếp tục chạy sau khi {@link android.app.Service#onStartCommand onStartCommand()} trả về. Vì vậy, +dịch vụ phải tự dừng bằng cách gọi {@link android.app.Service#stopSelf stopSelf()} hoặc một thành phần +khác có thể dừng nó bằng cách gọi {@link android.content.Context#stopService stopService()}.

+ +

Sau khi được yêu cầu dừng bằng {@link android.app.Service#stopSelf stopSelf()} hoặc {@link +android.content.Context#stopService stopService()}, hệ thống sẽ hủy dịch vụ ngay khi +có thể.

+ +

Tuy nhiên, nếu dịch vụ của bạn xử lý nhiều yêu cầu {@link +android.app.Service#onStartCommand onStartCommand()} đồng thời, khi đó bạn không nên dừng +dịch vụ khi bạn đã hoàn thành xử lý yêu cầu bắt đầu, vì bạn có thể đã nhận được một +yêu cầu bắt đầu mới kể từ thời điểm đó (dừng khi kết thúc yêu cầu thứ nhất sẽ chấm dứt yêu cầu thứ hai). Để tránh +vấn đề này, bạn có thể sử dụng {@link android.app.Service#stopSelf(int)} để đảm bảo rằng yêu cầu +dừng dịch vụ của bạn luôn được dựa trên yêu cầu bắt đầu gần đây nhất. Cụ thể, khi bạn gọi {@link +android.app.Service#stopSelf(int)}, bạn sẽ chuyển ID của yêu cầu bắt đầu (startId +được chuyển tới {@link android.app.Service#onStartCommand onStartCommand()}) mà yêu cầu dừng của bạn +tương ứng với. Khi đó, nếu dịch vụ đã nhận được một yêu cầu bắt đầu mới trước khi bạn có thể gọi {@link +android.app.Service#stopSelf(int)}, vậy ID sẽ không khớp và dịch vụ sẽ không dừng.

+ +

Chú ý: Điều quan trọng là ứng dụng của bạn dừng dịch vụ của nó +khi nó hoàn thành xong công việc để tránh lãng phí tài nguyên của hệ thống và tốn pin. Nếu cần, +các thành phần khác có thể dừng dịch vụ bằng cách gọi {@link +android.content.Context#stopService stopService()}. Ngay cả khi bạn kích hoạt gắn kết cho dịch vụ, +bạn phải luôn tự mình dừng dịch vụ nếu dịch vụ đã nhận được lệnh gọi tới {@link +android.app.Service#onStartCommand onStartCommand()}.

+ +

Để biết thêm thông tin về vòng đời của một dịch vụ, hãy xem phần bên dưới về Quản lý Vòng đời của một Dịch vụ.

+ + + +

Tạo một Dịch vụ Gắn kết

+ +

Dịch vụ gắn kết là một dịch vụ cho phép các thành phần ứng dụng gắn kết với nó bằng cách gọi {@link +android.content.Context#bindService bindService()} để tạo một kết nối lâu dài +(và thường không cho phép các thành phần bắt đầu nó bằng cách gọi {@link +android.content.Context#startService startService()}).

+ +

Bạn nên tạo một dịch vụ gắn kết khi muốn tương tác với dịch vụ từ hoạt động +và các thành phần khác trong ứng dụng của mình hoặc để hiển thị một số tính năng trong ứng dụng của bạn cho +các ứng dụng khác thông qua truyền thông liên tiến trình (IPC).

+ +

Để tạo một dịch vụ gắn kết, bạn phải triển khai phương pháp gọi lại {@link +android.app.Service#onBind onBind()} để trả về một {@link android.os.IBinder} mà +định nghĩa giao diện cho giao tiếp với dịch vụ đó. Khi đó, các thành phần ứng dụng khác có thể gọi +{@link android.content.Context#bindService bindService()} để truy xuất giao diện và +bắt đầu các phương pháp gọi trên dịch vụ. Dịch vụ tồn tại chỉ nhằm phục vụ thành phần ứng dụng mà +được gắn kết với nó, vì thế khi không có thành phần được gắn kết với dịch vụ, hệ thống sẽ hủy nó +(bạn không cần dừng một dịch vụ gắn kết theo cách phải làm khi dịch vụ được bắt đầu +thông qua {@link android.app.Service#onStartCommand onStartCommand()}).

+ +

Để tạo một dịch vụ gắn kết, điều đầu tiên bạn phải làm là định nghĩa giao diện quy định +cách thức mà một máy khách có thể giao tiếp với dịch vụ. Giao diện giữa dịch vụ và +máy khách này phải là một triển khai {@link android.os.IBinder} và được dịch vụ của bạn phải +trả về từ phương pháp gọi lại {@link android.app.Service#onBind +onBind()}. Sau khi máy khách nhận được {@link android.os.IBinder}, nó có thể bắt đầu +tương tác với dịch vụ thông qua giao diện đó.

+ +

Nhiều máy khách có thể gắn kết với dịch vụ đồng thời. Khi một máy khách hoàn thành tương tác với +dịch vụ, nó sẽ gọi {@link android.content.Context#unbindService unbindService()} để bỏ gắn kết. Sau khi +không còn máy khách nào được gắn kết với dịch vụ, hệ thống sẽ hủy dịch vụ.

+ +

Có nhiều cách để triển khai một dịch vụ gắn kết và triển khai sẽ phức tạp +hơn so với dịch vụ được bắt đầu, vì thế nội dung bàn về dịch vụ gắn kết được trình bày trong một +tài liệu riêng về Dịch vụ Gắn kết.

+ + + +

Gửi Thông báo tới Người dùng

+ +

Sau khi chạy, một dịch vụ có thể thông báo cho người dùng về sự kiện bằng cách sử dụng Thông báo Cửa sổ hoặc Thông báo Thanh Trạng thái.

+ +

Thông báo cửa sổ là một thông báo xuất hiện một lúc trên bề mặt của cửa sổ hiện tại +rồi biến mất, trong khi thông báo thanh trạng thái cung cấp một biểu tượng trong thanh trạng thái cùng một +thông báo, người dùng có thể chọn nó để thực hiện một hành động (chẳng hạn như bắt đầu một hoạt động).

+ +

Thông thường thông báo thanh trạng thái là kỹ thuật tốt nhất khi một công việc nền nào đó đã hoàn thành +(chẳng hạn như một tệp đã hoàn thành +việc tải xuống) và lúc này người dùng có thể hành động dựa trên nó. Khi người dùng chọn thông báo từ dạng xem mở rộng +, thông báo có thể bắt đầu một hoạt động (chẳng hạn như xem tệp được tải xuống).

+ +

Xem hướng dẫn dành cho nhà phát triển Thông báo Cửa sổ hoặc Thông báo Thanh Trạng thái +để biết thêm thông tin.

+ + + +

Chạy một Dịch vụ trong Tiền cảnh

+ +

Dịch vụ tiền cảnh là một dịch vụ được coi là điều mà +người dùng đang chủ động quan tâm, vì thế nó không được đề nghị để hệ thống tắt bỏ khi bộ nhớ thấp. Dịch vụ +tiền cảnh phải cung cấp một thông báo cho thanh trạng thái, nó được đặt dưới tiêu đề +"Đang diễn ra", điều này có nghĩa là thông báo không thể loại bỏ được trừ khi dịch vụ +bị dừng hoặc loại bỏ khỏi tiền cảnh.

+ +

Ví dụ, một trình chơi nhạc đang phát nhạc từ một dịch vụ nên được đặt để chạy trong +tiền cảnh, vì người dùng rõ ràng ý thức được +hoạt động của nó. Thông báo trong thanh trạng thái có thể cho biết bài hát đang chơi và cho phép +người dùng khởi chạy một hoạt động để tương tác với trình chơi nhạc.

+ +

Để yêu cầu dịch vụ của bạn chạy trong tiền cảnh, hãy gọi {@link +android.app.Service#startForeground startForeground()}. Phương pháp này dùng hai tham số: một số nguyên +để xác định duy nhất thông báo và {@link +android.app.Notification} cho thanh trạng thái. Ví dụ:

+ +
+Notification notification = new Notification(R.drawable.icon, getText(R.string.ticker_text),
+        System.currentTimeMillis());
+Intent notificationIntent = new Intent(this, ExampleActivity.class);
+PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);
+notification.setLatestEventInfo(this, getText(R.string.notification_title),
+        getText(R.string.notification_message), pendingIntent);
+startForeground(ONGOING_NOTIFICATION_ID, notification);
+
+ +

Chú ý: ID số nguyên mà bạn cấp cho {@link +android.app.Service#startForeground startForeground()} không được bằng 0.

+ + +

Để xóa bỏ dịch vụ khỏi tiền cảnh, hãy gọi {@link +android.app.Service#stopForeground stopForeground()}. Phương pháp này dùng một boolean, cho biết +có loại bỏ cả thông báo thanh trạng thái hay không. Phương pháp này không dừng +dịch vụ. Tuy nhiên, nếu bạn dừng dịch vụ trong khi nó vẫn đang chạy trong tiền cảnh, khi đó thông báo +cũng bị loại bỏ.

+ +

Để biết thêm thông tin về thông báo, hãy xem phần Tạo Thông báo +Thanh Trạng thái.

+ + + +

Quản lý Vòng đời của một Dịch vụ

+ +

Vòng đời của một dịch vụ đơn giản hơn nhiều so với vòng đời của một hoạt động. Tuy nhiên, một điều thậm chí còn quan trọng hơn +đó là bạn phải thật chú ý tới cách dịch vụ của bạn được tạo và hủy, bởi một dịch vụ +có thể chạy ngầm mà người dùng không biết.

+ +

Vòng đời của dịch vụ—từ khi nó được tạo tới khi nó bị hủy—có thể đi theo hai +con đường khác nhau:

+ +
    +
  • Dịch vụ được bắt đầu +

    Dịch vụ được tạo khi một thành phần khác gọi {@link +android.content.Context#startService startService()}. Sau đó, dịch vụ sẽ chạy vô thời hạn và phải +tự dừng bằng cách gọi {@link +android.app.Service#stopSelf() stopSelf()}. Một thành phần khác cũng có thể dừng +dịch vụ bằng cách gọi {@link android.content.Context#stopService +stopService()}. Khi dịch vụ bị dừng, hệ thống sẽ hủy nó.

  • + +
  • Dịch vụ gắn kết +

    Dịch vụ được tạo khi một thành phần khác (máy khách) gọi {@link +android.content.Context#bindService bindService()}. Khi đó, máy khách giao tiếp với dịch vụ +thông qua một giao diện {@link android.os.IBinder}. Máy khách có thể đóng kết nối bằng cách gọi +{@link android.content.Context#unbindService unbindService()}. Nhiều máy khách có thể gắn kết với +cùng dịch vụ và khi tất cả chúng bỏ gắn kết, hệ thống sẽ hủy dịch vụ. (Dịch vụ +không cần tự mình dừng.)

  • +
+ +

Hai con đường này hoàn toàn riêng biệt. Cụ thể, bạn có thể gắn kết với một dịch vụ đã +được bắt đầu bằng {@link android.content.Context#startService startService()}. Ví dụ, một dịch vụ +nhạc nền có thể được bắt đầu bằng cách gọi {@link android.content.Context#startService +startService()} bằng một {@link android.content.Intent} mà sẽ nhận biết nhạc để phát. Sau đó, +có thể là khi người dùng muốn thực thi một quyền điều khiển đối với trình phát đó hoặc lấy thông tin về +bài hát đang phát, hoạt động có thể gắn kết với dịch vụ bằng cách gọi {@link +android.content.Context#bindService bindService()}. Trong những trường hợp như vậy, {@link +android.content.Context#stopService stopService()} hoặc {@link android.app.Service#stopSelf +stopSelf()} không thực sự dừng dịch vụ tới khi tất cả máy khách bỏ gắn kết.

+ + +

Triển khai gọi lại vòng đời

+ +

Giống như một hoạt động, dịch vụ có các phương pháp gọi lại vòng đời mà bạn có thể triển khai để theo dõi +những thay đổi về trạng thái của dịch vụ và thực hiện công việc tại những thời điểm phù hợp. Dịch vụ khung sau +minh họa từng phương pháp vòng đời:

+ +
+public class ExampleService extends Service {
+    int mStartMode;       // indicates how to behave if the service is killed
+    IBinder mBinder;      // interface for clients that bind
+    boolean mAllowRebind; // indicates whether onRebind should be used
+
+    @Override
+    public void {@link android.app.Service#onCreate onCreate}() {
+        // The service is being created
+    }
+    @Override
+    public int {@link android.app.Service#onStartCommand onStartCommand}(Intent intent, int flags, int startId) {
+        // The service is starting, due to a call to {@link android.content.Context#startService startService()}
+        return mStartMode;
+    }
+    @Override
+    public IBinder {@link android.app.Service#onBind onBind}(Intent intent) {
+        // A client is binding to the service with {@link android.content.Context#bindService bindService()}
+        return mBinder;
+    }
+    @Override
+    public boolean {@link android.app.Service#onUnbind onUnbind}(Intent intent) {
+        // All clients have unbound with {@link android.content.Context#unbindService unbindService()}
+        return mAllowRebind;
+    }
+    @Override
+    public void {@link android.app.Service#onRebind onRebind}(Intent intent) {
+        // A client is binding to the service with {@link android.content.Context#bindService bindService()},
+        // after onUnbind() has already been called
+    }
+    @Override
+    public void {@link android.app.Service#onDestroy onDestroy}() {
+        // The service is no longer used and is being destroyed
+    }
+}
+
+ +

Lưu ý: Không như các phương pháp gọi lại vòng đời của hoạt động, bạn +không phải gọi triển khai siêu lớp với những phương pháp gọi lại này.

+ + +

Hình 2. Vòng đời dịch vụ. Sơ đồ phía bên trái +minh họa vòng đời khi dịch vụ được tạo bằng {@link android.content.Context#startService +startService()} và sơ đồ phía bên phải minh họa vòng đời khi dịch vụ được tạo +bằng {@link android.content.Context#bindService bindService()}.

+ +

Bằng việc triển khai những phương pháp này, bạn có thể theo dõi hai vòng lặp lồng nhau trong vòng đời của dịch vụ:

+ +
    +
  • Toàn bộ vòng đời của một dịch vụ xảy ra giữa thời điểm gọi {@link +android.app.Service#onCreate onCreate()} và thời điểm {@link +android.app.Service#onDestroy} trả về. Giống như hoạt động, dịch vụ thực hiện thiết lập ban đầu của nó trong +{@link android.app.Service#onCreate onCreate()} và giải phóng tất cả tài nguyên còn lại trong {@link +android.app.Service#onDestroy onDestroy()}. Ví dụ, một +dịch vụ phát lại nhạc có thể tạo luồng mà tại đó nhạc sẽ được phát trong {@link +android.app.Service#onCreate onCreate()}, sau đó dừng luồng trong {@link +android.app.Service#onDestroy onDestroy()}. + +

    Các phương pháp {@link android.app.Service#onCreate onCreate()} và {@link android.app.Service#onDestroy +onDestroy()} được gọi cho tất cả dịch vụ, dù +chúng được tạo bởi {@link android.content.Context#startService startService()} hay {@link +android.content.Context#bindService bindService()}.

  • + +
  • Vòng đời hiện hoạt của một dịch vụ sẽ bắt đầu bằng một lệnh gọi đến hoặc {@link +android.app.Service#onStartCommand onStartCommand()} hoặc {@link android.app.Service#onBind onBind()}. +Mỗi phương pháp sẽ được giao {@link +android.content.Intent} mà được chuyển tương ứng cho hoặc {@link android.content.Context#startService +startService()} hoặc {@link android.content.Context#bindService bindService()}. +

    Nếu dịch vụ được bắt đầu, vòng đời hiện hoạt sẽ chấm dứt tại cùng thời điểm khi toàn bộ vòng đời +chấm dứt (dịch vụ sẽ vẫn hiện hoạt ngay cả sau khi {@link android.app.Service#onStartCommand +onStartCommand()} trả về). Nếu dịch vụ bị gắn kết, vòng đời hiện hoạt sẽ chấm dứt khi {@link +android.app.Service#onUnbind onUnbind()} trả về.

    +
  • +
+ +

Lưu ý: Mặc dù dịch vụ được bắt đầu bị dừng bởi một lệnh gọi đến +hoặc {@link android.app.Service#stopSelf stopSelf()} hoặc {@link +android.content.Context#stopService stopService()}, sẽ không có một lệnh gọi lại tương ứng cho +dịch vụ (không có lệnh gọi lại {@code onStop()}). Vì thế, trừ khi dịch vụ được gắn kết với một máy khách, +hệ thống sẽ hủy nó khi dịch vụ bị dừng—{@link +android.app.Service#onDestroy onDestroy()} là lệnh gọi lại duy nhất nhận được.

+ +

Hình 2 minh họa các phương pháp gọi lại điển hình cho một dịch vụ. Mặc dù hình tách riêng +các dịch vụ được tạo bởi {@link android.content.Context#startService startService()} với các dịch vụ +được tạo bởi {@link android.content.Context#bindService bindService()}, hãy +ghi nhớ rằng bất kỳ dịch vụ nào, dù được bắt đầu như thế nào, đều có thể cho phép máy khách gắn kết với nó. +Vì thế, một dịch vụ được bắt đầu từ đầu bằng {@link android.app.Service#onStartCommand +onStartCommand()} (bởi một máy khách gọi {@link android.content.Context#startService startService()}) +vẫn có thể nhận một lệnh gọi đến {@link android.app.Service#onBind onBind()} (khi máy khách gọi +{@link android.content.Context#bindService bindService()}).

+ +

Để biết thêm thông tin về việc tao một dịch vụ có tính năng gắn kết, hãy xem tài liệu Dịch vụ Gắn kết, +trong đó có thêm thông tin về phương pháp gọi lại {@link android.app.Service#onRebind onRebind()} +trong phần về Quản lý Vòng đời của +một Dịch vụ Gắn kết.

+ + + diff --git a/docs/html-intl/intl/vi/guide/components/tasks-and-back-stack.jd b/docs/html-intl/intl/vi/guide/components/tasks-and-back-stack.jd new file mode 100644 index 0000000000000000000000000000000000000000..85afffff5ba54d001383442aaf4a1affbad663cc --- /dev/null +++ b/docs/html-intl/intl/vi/guide/components/tasks-and-back-stack.jd @@ -0,0 +1,578 @@ +page.title=Tác vụ và Ngăn xếp +parent.title=Hoạt động +parent.link=activities.html +@jd:body + + + + +

Một ứng dụng thường chứa nhiều hoạt động. Mỗi hoạt động +nên được thiết kế xung quanh một kiểu hành động cụ thể mà người dùng có thể thực hiện và bắt đầu các hoạt động +khác. Ví dụ, một ứng dụng e-mail có thể có một hoạt động để hiển thị một danh sách các thư mới. +Khi người dùng chọn một thư, một hoạt động mới sẽ mở ra để xem thư đó.

+ +

Hoạt động thậm chí có thể bắt đầu các hoạt động tồn tại trong các ứng dụng khác trên thiết bị. Ví +dụ, nếu ứng dụng của bạn muốn gửi một thư e-mail, bạn có thể định nghĩa một ý định để thực hiện một hành động +"gửi" và bao gồm một số dữ liệu, chẳng hạn như địa chỉ e-mail và nội dung. Một hoạt động từ một ứng dụng +khác tự khai báo là có khả năng xử lý kiểu ý định này sẽ mở ra. Trong trường hợp này, +ý định sẽ gửi một e-mail, sao cho hoạt động "soạn" của một ứng dụng e-mail sẽ bắt đầu (nếu nhiều hoạt động +hỗ trợ cùng ý định, khi đó hệ thống cho phép người dùng chọn hoạt động sẽ sử dụng). Khi e-mail được +gửi, hoạt động của bạn tiếp tục và dường như hoạt động e-mail là một phần ứng dụng của bạn. Mặc dù +các hoạt động có thể đến từ những ứng dụng khác nhau, Android duy trì trải nghiệm người dùng +mượt mà này bằng cách giữ cả hai hoạt động trong cùng tác vụ.

+ +

Tác vụ là một tập hợp gồm nhiều hoạt động mà người dùng tương tác với +khi thực hiện một công việc nhất định. Các hoạt động được sắp xếp trong một chồng (ngăn xếp), theo +thứ tự mở mỗi hoạt động.

+ + + +

Màn hình Trang chủ của thiết bị là nơi bắt đầu đối với hầu hết tác vụ. Khi người dùng chạm vào một biểu tượng trong trình khởi chạy +ứng dụng + (hoặc lối tắt trên màn hình Trang chủ), tác vụ của ứng dụng đó sẽ tiến ra tiền cảnh. Nếu không +có tác vụ nào cho ứng dụng (ứng dụng chưa được sử dụng gần đây), khi đó một tác vụ mới +sẽ được tạo và hoạt động "chính" cho ứng dụng đó sẽ mở ra thành hoạt động gốc trong chồng.

+ +

Khi hoạt động hiện tại bắt đầu một hoạt động khác, hoạt động mới sẽ bị đẩy lên trên cùng của chồng và +có tiêu điểm. Hoạt động trước vẫn nằm trong chồng, nhưng bị dừng lại. Khi một hoạt động +dừng, hệ thống giữ lại trạng thái hiện tại của giao diện người dùng của hoạt động đó. Khi người dùng nhấn nút +Quay lại +, hoạt động hiện tại được bật khỏi trên cùng của chồng (hoạt động bị hủy) và +hoạt động trước tiếp tục (trạng thái trước đó của UI của nó được khôi phục). Các hoạt động trong chồng +không bao giờ được sắp xếp lại, mà chỉ bị đẩy và bật khỏi chồng—bị đẩy lên trên chồng khi được bắt đầu bởi +hoạt động hiện tại và bị bật khỏi chồng khi người dùng rời nó bằng cách sử dụng nút Quay lại. Như vậy, +ngăn xếp +vận hành như một cấu trúc đối tượng "vào cuối, ra đầu". Hình 1 minh họa +hành vi này cùng một dòng thời gian thể hiện tiến độ giữa các hoạt động dọc theo ngăn xếp +hiện tại ở từng thời điểm.

+ + +

Hình 1. Biểu diễn cách mỗi hoạt động mới trong một +tác vụ thêm một mục vào ngăn xếp. Khi người dùng nhấn nút Quay lại, hoạt động +hiện tại +bị hủy và hoạt động trước đó tiếp tục.

+ + +

Nếu người dùng tiếp tục nhấn Quay lại, khi đó mỗi hoạt động trong chồng bị bật khỏi để +hiện ra +hoạt động trước, tới khi người dùng quay lại màn hình Trang chủ (hoặc trở về hoạt động đang chạy khi +tác vụ bắt đầu). Khi tất cả hoạt động bị loại bỏ khỏi chồng, tác vụ sẽ không còn tồn tại.

+ +
+

Hình 2. Hai tác vụ: Tác vụ B nhận tương tác người dùng +trong tiền cảnh, trong khi Tác vụ A nằm dưới nền, chờ được tiếp tục.

+
+
+

Hình 3. Một hoạt động đơn lẻ được khởi tạo nhiều lần.

+
+ +

Tác vụ là một đơn vị dính kết, có thể di chuyển tới "nền" khi người dùng bắt đầu một tác vụ mới hoặc đi đến +màn hình Trang chủ, thông qua nút Home. Khi ở trong nền, tất cả hoạt động trong +tác vụ bị +dừng, nhưng ngăn xếp cho tác vụ vẫn không bị ảnh hưởng—tác vụ chỉ đơn thuần mất tiêu điểm trong khi +một tác vụ khác thay thế, như minh họa trong hình 2. Khi đó, một tác vụ có thể quay lại "tiền cảnh" để người dùng +có thể chọn ở nơi họ đã rời đi. Ví dụ, giả sử rằng tác vụ hiện tại (Tác vụ A) có ba +hoạt động trong chồng của mình—hai trong số đó nằm dưới hoạt động hiện tại. Người dùng nhấn nút Trang chủ +, sau đó +bắt đầu một ứng dụng mới từ trình khởi chạy ứng dụng. Khi màn hình Trang chủ xuất hiện, Tác vụ A đi vào +nền. Khi ứng dụng mới bắt đầu, hệ thống sẽ bắt đầu một tác vụ cho ứng dụng đó +(Tác vụ B) bằng chồng các hoạt động của chính mình. Sau khi tương tác với +ứng dụng đó, người dùng quay lại Trang chủ lần nữa và chọn ứng dụng +đã bắt đầu Tác vụ A lúc đầu. Lúc này, Tác vụ A đi đến +tiền cảnh—cả ba hoạt động trong chồng của nó đều giữ nguyên và hoạt động trên cùng +của chồng được tiếp tục. Tại +điểm này, người dùng cũng có thể chuyển trở lại Tác vụ B bằng cách đến Trang chủ và chọn biểu tượng ứng dụng +đã bắt đầu tác vụ đó (hoặc bằng cách chọn tác vụ của ứng dụng từ +màn hình tổng quan). +Đây là một ví dụ về đa nhiệm trên Android.

+ +

Lưu ý: Nhiều tác vụ có thể được lưu giữ cùng lúc trong nền. +Tuy nhiên, nếu người dùng đang chạy nhiều tác vụ nền tại cùng thời điểm, hệ thống có thể bắt đầu +hủy các hoạt động nền để khôi phục bộ nhớ, khiến trạng thái của hoạt động bị mất. +Xem phần sau đây về Trạng thái của hoạt động.

+ +

Vì các hoạt động trong ngăn xếp không bao giờ được sắp xếp lại, nếu ứng dụng của bạn cho phép +người dùng bắt đầu một hoạt động cụ thể từ nhiều hơn một hoạt động, một thực thể mới của +hoạt động đó sẽ được tạo và đẩy lên chồng (thay vì mang bất kỳ thực thể nào trước đó của +hoạt động lên trên cùng). Như vậy, một hoạt động trong ứng dụng của bạn có thể được tạo phiên bản nhiều +lần (thậm chí từ các tác vụ khác nhau), như minh họa trong hình 3. Như vậy, nếu người dùng điều hướng ngược lại +bằng cách sử dụng nút Quay lại, mỗi thực thể của hoạt động được hiển thị theo thứ tự được +mở (mỗi hoạt động +có trạng thái UI của chính chúng). Tuy nhiên, bạn có thể sửa đổi hành vi này nếu không muốn một hoạt động được +khởi tạo nhiều hơn một lần. Cách làm như vậy được bàn trong phần sau về Quản lý Tác vụ.

+ + +

Để tóm tắt hành vi mặc định đối với các hoạt động và tác vụ:

+ +
    +
  • Khi Hoạt động A bắt đầu Hoạt động B, Hoạt động A bị dừng, nhưng hệ thống giữ lại trạng thái của nó +(chẳng hạn như vị trí cuộn và văn bản được nhập vào các mẫu). +Nếu người dùng nhấn nút Quay lại khi đang trong Hoạt động B, Hoạt động A sẽ tiếp tục với trạng thái +được khôi phục.
  • +
  • Khi người dùng rời khỏi một tác vụ bằng cách nhấn nút Trang chủ, hoạt động hiện tại bị +dừng và +tác vụ của nó sẽ đưa xuống dưới nền. Hệ thống sẽ giữ lại trạng thái của mọi hoạt động trong tác vụ. Nếu +sau đó người dùng tiếp tục tác vụ bằng cách chọn biểu tượng trình khởi chạy đã bắt đầu tác vụ, tác vụ sẽ vào +tiền cảnh và tiếp tục hoạt động ở trên cùng của chồng.
  • +
  • Nếu người dùng nhấn nút Quay lại, hoạt động hiện tại bị bật khỏi chồng +và +bị hủy. Hoạt động trước đó ở trong chồng sẽ được tiếp tục. Khi một hoạt động bị hủy, hệ thống +không giữ lại trạng thái của hoạt động đó.
  • +
  • Hoạt động có thể được khởi tạo nhiều lần, thậm chí từ các tác vụ khác.
  • +
+ + +
+

Thiết kế Điều hướng

+

Để biết thêm về cách điều hướng ứng dụng hoạt động trên Android, hãy đọc hướng dẫn Điều hướng của Thiết kế Android.

+
+ + +

Lưu Trạng thái của Hoạt động

+ +

Như được đề cập ở trên, hành vi mặc định của hệ thống giữ nguyên trạng thái của một hoạt động khi nó bị +dừng. Bằng cách này, khi người dùng điều hướng trở lại một hoạt động trước đó, giao diện người dùng của nó sẽ xuất hiện + như lúc bị rời đi. Tuy nhiên, bạn có thể—và nên—chủ động giữ lại +trạng thái của các hoạt động của mình bằng cách sử dụng các phương pháp gọi lại, trong trường hợp hoạt động bị hủy và phải +được tạo lại.

+ +

Khi hệ thống dừng một trong các hoạt động của bạn (chẳng hạn như khi một hoạt động mới bắt đầu hoặc tác vụ +di chuyển về nền), hệ thống có thể hoàn toàn hủy hoạt động đó nếu nó cần khôi phục +bộ nhớ hệ thống. Khi điều này xảy ra, thông tin về trạng thái của hoạt động sẽ bị mất. Nếu điều này xảy ra, +hệ thống vẫn +biết rằng hoạt động có một vị trí trong ngăn xếp, nhưng khi hoạt động được đưa tới vị trí trên cùng +của chồng, hệ thống phải tạo lại nó (thay vì tiếp tục). Để tránh +làm mất công việc của người dùng, bạn nên chủ động giữ lại nó bằng cách triển khai phương pháp gọi lại +{@link android.app.Activity#onSaveInstanceState onSaveInstanceState()} trong +hoạt động của mình.

+ +

Để biết thêm thông tin về cách lưu trạng thái hoạt động của mình, hãy xem tài liệu Hoạt động +.

+ + + +

Quản lý Tác vụ

+ +

Cách Android quản lý tác vụ và ngăn xếp, như được mô tả bên trên—bằng cách đặt tất cả +hoạt động được bắt đầu nối tiếp nhau vào cùng tác vụ và trong một chồng "vào cuối, ra đầu"—rất +hiệu quả đối với hầu hết ứng dụng và bạn không phải lo lắng về cách thức các hoạt động của mình được liên kết với +các tác vụ hay cách chúng tồn tại trong ngăn xếp. Tuy nhiên, bạn có thể quyết định rằng mình muốn gián đoạn +hành vi thông thường. Có thể bạn muốn một hoạt động trong ứng dụng của mình bắt đầu một tác vụ mới khi nó được +bắt đầu (thay vì được đặt trong tác vụ hiện tại); hoặc, khi bạn bắt đầu một hoạt động, bạn muốn +mang lên trước một thực thể hiện tại của nó (thay vì tạo một +thực thể mới trên cùng của ngăn xếp); hoặc, bạn muốn ngăn xếp của mình được xóa sạch tất cả các +hoạt động, ngoại trừ hoạt động gốc khi người dùng rời khỏi tác vụ.

+ +

Bạn có thể làm những điều này và nhiều điều khác với các thuộc tính trong phần tử bản kê khai +{@code <activity>} +và cờ trong ý định mà bạn chuyển cho +{@link android.app.Activity#startActivity startActivity()}.

+ +

Về mặt này, các thuộc tính +{@code <activity>} chính mà bạn có thể sử dụng là:

+ + + +

Và các cờ ý định chính mà bạn có thể sử dụng là:

+ +
    +
  • {@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK}
  • +
  • {@link android.content.Intent#FLAG_ACTIVITY_CLEAR_TOP}
  • +
  • {@link android.content.Intent#FLAG_ACTIVITY_SINGLE_TOP}
  • +
+ +

Trong những phần sau, bạn sẽ thấy cách bạn có thể sử dụng những thuộc tính của bản kê khai +và cờ ý định này để định nghĩa cách các hoạt động được liên kết với tác vụ và hành vi của chúng như thế nào trong ngăn xếp.

+ +

Bên cạnh đó, có phần bàn riêng về việc cân nhắc cách các tác vụ và hoạt động có thể +được biểu diễn và quản lý trong màn hình tổng quan. Xem phần Màn hình Tổng quan +để biết thêm thông tin. Thông thường, bạn nên cho phép hệ thống định nghĩa tác vụ và các hoạt động +của bạn được biểu diễn như thế nào trong màn hình tổng quan, và bạn không cần sửa đổi hành vi này.

+ +

Chú ý: Hầu hết các ứng dụng không nên gián đoạn hành vi +mặc định cho các hoạt động và tác vụ. Nếu bạn xác định rằng hoạt động của bạn cần phải sửa đổi +hành vi mặc định, hãy thận trọng và đảm bảo kiểm tra khả năng sử dụng được của hoạt động đó trong khi +khởi chạy và khi điều hướng trở lại nó từ các hoạt động và tác vụ khác bằng nút Quay lại. +Đảm bảo kiểm tra hành vi điều hướng mà có thể xung đột với hành vi được kỳ vọng của người dùng.

+ + +

Định nghĩa các chế độ khởi chạy

+ +

Các chế độ khởi chạy cho phép bạn định nghĩa một thực thể mới của một hoạt động được liên kết với tác vụ hiện tại +như thế nào. Bạn có thể định nghĩa các chế độ khởi chạy khác nhau theo hai cách:

+
    +
  • Sử dụng tệp bản kê khai +

    Khi bạn khai báo một hoạt động trong tệp bản kê khai của mình, bạn có thể quy định hoạt động +sẽ liên kết với các tác vụ như thế nào khi nó bắt đầu.

  • +
  • Sử dụng cờ Ý định +

    Khi bạn gọi {@link android.app.Activity#startActivity startActivity()}, +bạn có thể thêm một cờ vào {@link android.content.Intent} mà khai báo cách (hoặc +liệu có hay không) hoạt động mới sẽ liên kết với tác vụ hiện tại.

  • +
+ +

Như vậy, nếu Hoạt động A bắt đầu Hoạt động B, Hoạt động B có thể định nghĩa trong bản kê khai của mình cách nó +sẽ liên kết với tác vụ hiện tại (nếu có) và Hoạt động A cũng có thể yêu cầu cách mà Hoạt động +B sẽ liên kết với tác vụ hiện tại. Nếu cả hai hoạt động đều định nghĩa cách Hoạt động B +nên liên kết với một tác vụ thì yêu cầu của Hoạt động A (như định nghĩa trong ý định) được ưu tiên +so với yêu cầu của Hoạt động B (như được định nghĩa trong bản kê khai của nó).

+ +

Lưu ý: Một số chế độ khởi chạy sẵn có cho tệp bản kê khai +không sẵn có dưới dạng cờ cho một ý định và, tương tự, một số chế độ khởi chạy sẵn có dưới dạng cờ +cho một ý định không thể được định nghĩa trong bản kê khai.

+ + +

Sử dụng tệp bản kê khai

+ +

Khi khai báo một hoạt động trong tệp bản kê khai của bạn, bạn có thể quy định cách mà hoạt động +sẽ liên kết với một tác vụ bằng cách sử dụng thuộc tính của phần tử {@code <activity>} +, {@code +launchMode}.

+ +

Thuộc tính {@code +launchMode} quy định một chỉ lệnh về cách hoạt động sẽ được khởi chạy vào một +tác vụ. Có bốn chế độ khởi chạy khác nhau mà bạn có thể gán cho thuộc tính +launchMode +:

+ +
+
{@code "standard"} (chế độ mặc định)
+
Mặc định. Hệ thống tạo một thực thể mới của hoạt động trong tác vụ là nơi +mà nó được bắt đầu và định tuyến ý định tới đó. Hoạt động có thể được khởi tạo nhiều lần, +mỗi thực thể có thể thuộc về các tác vụ khác nhau, và một tác vụ có thể có nhiều thực thể.
+
{@code "singleTop"}
+
Nếu một thực thể của hoạt động đã tồn tại ở trên cùng của tác vụ hiện tại, hệ thống +sẽ định tuyến ý định tới thực thể đó thông qua một lệnh gọi tới phương pháp {@link +android.app.Activity#onNewIntent onNewIntent()} của nó, thay vì tạo một thực thể mới của +hoạt động. Hoạt động có thể được tạo phiên bản nhiều lần, mỗi thực thể có thể +thuộc về các tác vụ khác nhau, và một tác vụ có thể có nhiều thực thể (nhưng chỉ nếu +hoạt động nằm trên cùng của ngăn xếp không phải là một thực thể của hoạt động hiện có). +

Ví dụ, giả sử ngăn xếp của một tác vụ bao gồm hoạt động gốc A với các hoạt động B, C, +và D ở trên cùng (chồng là A-B-C-D; D ở trên cùng). Một ý định đến cho loại hoạt động D. +Nếu D có chế độ khởi chạy {@code "standard"} mặc định, một thực thể mới của lớp sẽ được khởi chạy và +chồng trở thành A-B-C-D-D. Tuy nhiên, nếu chế độ khởi chạy của D là {@code "singleTop"}, thực thể hiện tại +của D sẽ nhận ý định thông qua {@link +android.app.Activity#onNewIntent onNewIntent()}, bởi nó nằm ở vị trí trên cùng của chồng—chồng +vẫn là A-B-C-D. Tuy nhiên, nếu một ý định đến cho hoạt động loại B, khi đó một thực thể +mới của B sẽ được thêm vào chồng ngay cả khi chế độ khởi chạy của nó là {@code "singleTop"}.

+

Lưu ý: Khi một thực thể mới của hoạt động được tạo, +người dùng có thể nhấn nút Quay lại để quay về hoạt động trước đó. Nhưng khi một thực thể +hiện tại của +hoạt động xử lý một ý định mới, người dùng không thể nhấn nút Quay lại để quay về trạng thái +của +hoạt động trước khi ý định mới đến trong {@link android.app.Activity#onNewIntent +onNewIntent()}.

+
+ +
{@code "singleTask"}
+
Hệ thống sẽ tạo ra một tác vụ mới và khởi tạo hoạt động ở gốc của tác vụ mới. +Tuy nhiên, nếu một thực thể của hoạt động đã tồn tại trong một tác vụ riêng, hệ thống sẽ định tuyến +ý định tới thực thể hiện tại thông qua một lệnh gọi tới phương pháp {@link +android.app.Activity#onNewIntent onNewIntent()} của nó thay vì tạo một thực thể mới. Chỉ +một thực thể của hoạt động có thể tồn tại ở một thời điểm. +

Lưu ý: Mặc dù hoạt động bắt đầu một tác vụ mới, nút +Quay lại sẽ vẫn đưa người dùng quay về hoạt động trước đó.

+
{@code "singleInstance"}.
+
Giống như {@code "singleTask"}, trừ khi hệ thống không khởi chạy bất kỳ hoạt động nào khác vào +tác vụ đang nắm giữ thực thể. Hoạt động luôn là thành viên đơn lẻ và duy nhất của tác vụ; +bất kỳ hoạt động nào được bắt đầu bởi hoạt động này sẽ mở ra trong một tác vụ riêng.
+
+ + +

Lấy một ví dụ khác, ứng dụng Trình duyệt của Android khai báo rằng hoạt động trình duyệt web sẽ +luôn mở tác vụ của chính mình—bằng cách quy định chế độ khởi chạy {@code singleTask} trong phần tử {@code <activity>}. +Điều này có nghĩa rằng nếu ứng dụng của bạn phát hành một +ý định để mở Trình duyệt Android, hoạt động của nó không được đặt trong cùng +tác vụ như ứng dụng của bạn. Thay vào đó, hoặc là một tác vụ mới bắt đầu cho Trình duyệt hoặc, nếu Trình duyệt +đã có một tác vụ đang chạy trong nền thì tác vụ đó sẽ được đưa lên trước để xử lý +ý định mới.

+ +

Không phụ thuộc vào việc một hoạt động bắt đầu trong một tác vụ mới hay trong cùng tác vụ mà hoạt động +đã bắt đầu, nút Quay lại sẽ luôn đưa người dùng về hoạt động trước đó. Tuy nhiên, nếu bạn +bắt đầu một hoạt động mà quy định chế độ khởi chạy {@code singleTask}, khi đó nếu một thực thể của +hoạt động đó tồn tại trong một tác vụ nền, toàn bộ tác vụ đó sẽ được đưa ra tiền cảnh. Tại thời điểm này +, lúc này ngăn xếp bao gồm tất cả hoạt động từ tác vụ được mang ra, ở vị trí trên cùng của +chồng. Hình 4 minh họa loại kịch bản này.

+ + +

Hình 4. Biểu diễn cách thức một hoạt động với +chế độ khởi chạy "singleTask" được thêm vào ngăn xếp. Nếu hoạt động đã là một phần của một +tác vụ nền với ngăn xếp của chính nó, khi đó toàn bộ ngăn xếp cũng tiến +về phía trước, ở trên cùng tác vụ hiện tại.

+ +

Để biết thêm thông tin về việc sử dụng các chế độ khởi chạy trong tệp bản kê khai, hãy xem tài liệu phần tử +<activity> +, trong đó có trình bày thêm về thuộc tính {@code launchMode} và các giá trị +được chấp nhận.

+ +

Lưu ý: Hành vi mà bạn quy định cho hoạt động của mình bằng thuộc tính {@code launchMode} có thể +bị khống chế bởi cờ có ý định bắt đầu hoạt động của bạn, như được trình bày trong +phần tiếp theo.

+ + + +

Sử dụng cờ Ý định

+ +

Khi bắt đầu một hoạt động, bạn có thể sửa đổi liên kết mặc định giữa một hoạt động với tác vụ của nó +bằng cách thêm cờ vào trong ý định mà bạn chuyển tới {@link +android.app.Activity#startActivity startActivity()}. Những cờ mà bạn có thể sử dụng để sửa đổi +hành vi mặc định là:

+ +

+

{@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK}
+
Bắt đầu một hoạt động trong một tác vụ mới. Nếu đã có một tác vụ đang chạy cho hoạt động mà bạn đang +bắt đầu, tác vụ đó sẽ được đưa ra tiền cảnh với trạng thái cuối cùng được khôi phục và hoạt động +nhận ý định mới trong {@link android.app.Activity#onNewIntent onNewIntent()}. +

Điều này sẽ tạo ra cùng hành vi như giá trị {@code "singleTask"} {@code launchMode}, +đã được trình bày ở phần trước.

+
{@link android.content.Intent#FLAG_ACTIVITY_SINGLE_TOP}
+
Nếu hoạt động đang được bắt đầu là hoạt động hiện tại (ở trên cùng của ngăn xếp), khi đó +thực thể hiện có sẽ nhận một lệnh gọi đến {@link android.app.Activity#onNewIntent onNewIntent()}, +thay vì tạo một thực thể của hoạt động mới. +

Điều này sẽ tạo ra cùng hành vi như giá trị {@code "singleTop"} {@code launchMode}, +đã được trình bày ở phần trước.

+
{@link android.content.Intent#FLAG_ACTIVITY_CLEAR_TOP}
+
Nếu hoạt động đang được bắt đầu đang chạy trong tác vụ hiện tại, khi đó +thay vì khởi chạy một thực thể mới của hoạt động đó, tất cả các hoạt động khác bên trên nó đều +bị hủy và ý định này được chuyển tới thực thể được tiếp tục của hoạt động (lúc này đang ở trên cùng), +thông qua {@link android.app.Activity#onNewIntent onNewIntent()}). +

Không có giá trị cho thuộc tính {@code launchMode} +mà sinh ra hành vi này.

+

{@code FLAG_ACTIVITY_CLEAR_TOP} được sử dụng thường xuyên nhất cùng với + {@code FLAG_ACTIVITY_NEW_TASK}. +Khi được sử dụng cùng nhau, những cờ này là một cách để định vị hoạt động hiện tại +trong một tác vụ khác và đặt nó vào vị trí nơi nó có thể hồi đáp ý định.

+

Lưu ý: Nếu chế độ khởi chạy của hoạt động được chỉ định là + {@code "standard"}, +nó cũng bị loại bỏ khỏi chồng và một thực thể mới sẽ được khởi chạy thay chỗ nó để xử lý +ý định đến. Đó là bởi một thực thể mới luôn được tạo cho ý định mới khi chế độ khởi chạy +là {@code "standard"}.

+
+ + + + + + +

Xử lý quan hệ

+ +

Quan hệ sẽ cho biết một hoạt động ưu tiên thuộc về tác vụ nào hơn. Theo mặc định, tất cả +các hoạt động từ cùng ứng dụng có quan hệ với nhau. Vì thế, theo mặc định, tất cả +hoạt động trong cùng ứng dụng ưu tiên ở trong cùng tác vụ hơn. Tuy nhiên, bạn có thể sửa đổi +quan hệ mặc định cho một hoạt động. Các hoạt động được định nghĩa trong +các ứng dụng khác nhau có thể chia sẻ một quan hệ, hoặc các hoạt động được định nghĩa trong cùng ứng dụng có thể +được gán các quan hệ tác vụ khác nhau.

+ +

Bạn có thể sửa đổi quan hệ cho bất kỳ hoạt động đã cho nào bằng thuộc tính {@code taskAffinity} của +phần tử {@code <activity>} +.

+ +

Thuộc tính {@code taskAffinity} +lấy một giá trị xâu, đó phải là giá trị duy nhất từ tên gói mặc định +được khai báo trong phần tử +{@code <manifest>} +, do hệ thống sử dụng tên đó để nhận biết quan hệ +tác vụ mặc định cho ứng dụng.

+ +

Vấn đề quan hệ được xét trong hai trường hợp:

+
    +
  • Khi ý định khởi chạy hoạt động chứa cờ + {@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK} +. + +

    Theo mặc định, một hoạt động mới được khởi chạy vào tác vụ của hoạt động +đã gọi {@link android.app.Activity#startActivity startActivity()}. Nó được đẩy lên cùng +ngăn xếp như trình gọi. Tuy nhiên, nếu ý định được chuyển tới +{@link android.app.Activity#startActivity startActivity()} +chứa cờ {@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK} +, hệ thống sẽ tìm một tác vụ khác để chứa hoạt động mới. Thường thì đó là một tác vụ mới. +Tuy nhiên, không nhất thiết phải như vậy. Nếu đã có một tác vụ hiện tại với cùng quan hệ như +hoạt động mới, hoạt động sẽ được khởi chạy vào tác vụ đó. Nếu không, nó sẽ bắt đầu một tác vụ mới.

    + +

    Nếu cờ này khiến một hoạt động bắt đầu một tác vụ mới và người dùng nhấn nút Home +để rời +nó thì phải có một cách nào đó để người dùng điều hướng quay lại tác vụ. Một số đối tượng (chẳng hạn như +trình quản lý thông báo) luôn bắt đầu các hoạt động trong một tác vụ bên ngoài, không bao giờ bắt đầu trong chính tác vụ của mình, vì thế +chúng luôn đặt {@code FLAG_ACTIVITY_NEW_TASK} vào ý định mà chúng chuyển tới +{@link android.app.Activity#startActivity startActivity()}. +Nếu bạn có một hoạt động có thể được gọi ra bởi +một đối tượng bên ngoài mà có thể sử dụng cờ này, hãy lưu ý là người dùng có một cách độc lập để quay lại +tác vụ đã được bắt đầu, chẳng hạn như bằng một biểu tượng trình khởi chạy (hoạt động gốc của tác vụ +có một bộ lọc ý định {@link android.content.Intent#CATEGORY_LAUNCHER}; xem phần Bắt đầu một tác vụ ở bên dưới).

    +
  • + +
  • Khi một hoạt động có thuộc tính +{@code allowTaskReparenting} của nó được đặt thành {@code "true"}. +

    Trong trường hợp này, hoạt động có thể di chuyển từ tác vụ mà nó bắt đầu tới tác vụ mà nó có quan hệ + khi tác vụ đó đi ra tiền cảnh.

    +

    Ví dụ, giả sử rằng một hoạt động với chức năng báo cáo tình hình thời tiết ở các thành phố được chọn +được định nghĩa là một phần trong một ứng dụng du lịch. Nó có cùng quan hệ như các hoạt động khác trong cùng +ứng dụng (quan hệ ứng dụng mặc định) và nó cho phép tạo lại tập mẹ với thuộc tính này. +Khi một trong các hoạt động của bạn bắt đầu hoạt động trình báo cáo thời tiết, ban đầu nó thuộc về cùng +tác vụ như hoạt động của bạn. Tuy nhiên, khi tác vụ của ứng dụng du lịch đi ra tiền cảnh, hoạt động +của trình báo cáo thời tiết được gán lại cho tác vụ đó và được hiển thị bên trong nó.

    +
  • +
+ +

Mẹo: Nếu một tệp {@code .apk} chứa nhiều hơn một "ứng dụng" +từ quan điểm của người dùng, bạn có thể muốn sử dụng thuộc tính {@code taskAffinity} +để gán các quan hệ khác nhau cho hoạt động được liên kết với từng "ứng dụng".

+ + + +

Xóa ngăn xếp

+ +

Nếu người dùng rời một tác vụ trong khoảng thời gian dài, hệ thống sẽ xóa tác vụ của tất cả hoạt động ngoại trừ +hoạt động gốc. Khi người dùng quay trở lại tác vụ, chỉ hoạt động gốc được khôi phục. +Hệ thống sẽ hoạt động theo cách này, vì, sau một khoảng thời gian dài, người dùng có thể đã từ bỏ +việc mà họ đang làm trước đó và quay lại tác vụ để bắt đầu một việc mới.

+ +

Có một số thuộc tính hoạt động mà bạn có thể sử dụng để sửa đổi hành vi này:

+ +
+
alwaysRetainTaskState +
+
Nếu thuộc tính này được đặt thành {@code "true"} trong hoạt động gốc của một tác vụ, +hành vi mặc định được mô tả sẽ không xảy ra. +Tác vụ giữ lại tất cả hoạt động trong chồng của mình kể cả sau một khoảng thời gian dài.
+ +
clearTaskOnLaunch
+
Nếu thuộc tính này được đặt thành {@code "true"} trong hoạt động gốc của một tác vụ, +chồng sẽ bị xóa tới hoạt động gốc bất cứ khi nào người dùng rời khỏi tác vụ +và quay lại. Nói cách khác, nó ngược với + +{@code alwaysRetainTaskState}. Người dùng luôn quay lại tác vụ ở +trạng thái ban đầu của nó, ngay cả sau khi rời khỏi tác vụ trong chỉ một lúc.
+ +
finishOnTaskLaunch +
+
Thuộc tính này giống như {@code clearTaskOnLaunch}, +nhưng nó hoạt động trên một tác vụ +đơn lẻ chứ không phải một tác vụ toàn bộ. Nó cũng có thể khiến bất kỳ hoạt động nào +thoát mất, bao gồm cả hoạt động gốc. Khi nó được đặt thành {@code "true"}, hoạt động +vẫn là một bộ phận của tác vụ chỉ cho phiên làm việc hiện tại. Nếu người dùng rời đi +rồi quay lại tác vụ, nó không còn xuất hiện nữa.
+
+ + + + +

Bắt đầu một tác vụ

+ +

Bạn có thể thiết lập một hoạt động làm điểm bắt đầu cho một tác vụ bằng cách đưa cho nó một bộ lọc ý định với +{@code "android.intent.action.MAIN"} là hành động được quy định và +{@code "android.intent.category.LAUNCHER"} +là thể loại được quy định. Ví dụ:

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

Một bộ lọc ý định loại này khiến một biểu tượng và nhãn cho +hoạt động được hiển thị trong trình khởi chạy ứng dụng, cho phép người dùng khởi chạy hoạt động và +quay lại tác vụ mà nó tạo ra vào bất cứ lúc nào sau khi nó được khởi chạy. +

+ +

Khả năng thứ hai này là quan trọng: Người dùng chắc chắn có thể rời một tác vụ rồi quay lại +sau bằng cách sử dụng trình khởi chạy hoạt động này. Vì lý do này, hai chế độ +khởi chạy mà đánh dấu các hoạt động là luôn khởi tạo một tác vụ, {@code "singleTask"} và +{@code "singleInstance"}, sẽ chỉ được sử dụng khi hoạt động có một +{@link android.content.Intent#ACTION_MAIN} +và một bộ lọc {@link android.content.Intent#CATEGORY_LAUNCHER}. Ví dụ, hãy tưởng tượng chuyện gì +sẽ xảy ra nếu thiếu bộ lọc: Một ý định khởi chạy một hoạt động {@code "singleTask"}, khởi đầu một +tác vụ mới và người dùng dành một khoảng thời gian làm việc trong tác vụ đó. Khi đó, người dùng nhấn nút Home +. Lúc này, tác vụ được gửi tới nền và không hiển thị. Bây giờ, người dùng không có cách nào để quay lại +tác vụ bởi nó không được biểu diễn trong trình khởi chạy ứng dụng.

+ +

Đối với những trường hợp mà bạn không muốn người dùng có thể quay lại một hoạt động, hãy đặt giá trị của phần tử +<activity> +, +{@code finishOnTaskLaunch} +thành {@code "true"} (xem Xóa chồng).

+ +

Bạn có thể tham khảo thêm thông tin về cách các tác vụ và hoạt động được trình bày và quản lý trong +màn hình tổng quan sẵn có tại phần +Màn hình Tổng quan.

+ + diff --git a/docs/html-intl/intl/vi/guide/index.jd b/docs/html-intl/intl/vi/guide/index.jd new file mode 100644 index 0000000000000000000000000000000000000000..7be2d89a2d1784dfd88cea0e979847244d8b827a --- /dev/null +++ b/docs/html-intl/intl/vi/guide/index.jd @@ -0,0 +1,74 @@ +page.title=Giới thiệu về Android + +@jd:body + + + + +

Android cung cấp một khuôn khổ ứng dụng phong phú cho phép bạn xây dựng các ứng dụng và trò chơi mới +cho các thiết bị di động trong môi trường ngôn ngữ Java. Tài liệu được liệt kê trong vùng điều hướng +bên trái sẽ cung cấp chi tiết về cách xây dựng ứng dụng bằng cách sử dụng các API khác nhau của Android.

+ +

Nếu bạn mới làm quen với việc phát triển Android, quan trọng là bạn phải hiểu +những khái niệm cơ bản sau về khuôn khổ ứng dụng Android:

+ + +
+ +
+ +

Các ứng dụng cung cấp nhiều điểm nhập

+ +

Các ứng dụng Android được tích hợp như một sự kết hợp giữa các thành phần khác nhau có thể được gọi ra +riêng. Ví dụ, một hoạt động riêng lẻ cung cấp một màn hình +duy nhất cho một giao diện người dùng, và một dịch vụ chạy ngầm thực hiện độc lập +công việc.

+ +

Từ một thành phần, bạn có thể khởi động một thành phần khác bằng cách sử dụng một ý định. Thậm chí bạn có thể bắt đầu +một thành phần trong một ứng dụng khác, chẳng hạn như một hoạt động trong một ứng dụng bản đồ để hiển thị một địa chỉ. Mô hình này +cung cấp nhiều điểm nhập cho một ứng dụng duy nhất và cho phép bất kỳ ứng dụng nào xử lý như "mặc định" +của một người dùng đối với một hành động mà các ứng dụng khác có thể gọi ra.

+ + +

Tìm hiểu thêm:

+ + +
+ + +
+ +

Các ứng dụng sẽ thích ứng theo các thiết bị khác nhau

+ +

Android cung cấp một khuôn khổ ứng dụng thích ứng cho phép bạn cung cấp các tài nguyên duy nhất +cho các cấu hình thiết bị khác nhau. Ví dụ, bạn có thể tạo các tệp bố trí +XML khác nhau cho các kích cỡ màn hình khác nhau và hệ thống +sẽ xác định bố trí nào sẽ áp dụng dựa trên kích cỡ màn hình hiện tại của thiết bị.

+ +

Bạn có thể truy vấn về sự sẵn có của các tính năng trên thiết bị vào thời gian chạy nếu bất kỳ tính năng nào của ứng dụng +yêu cầu phần cứng cụ thể, chẳng hạn như máy ảnh. Nếu cần, bạn cũng có thể khai báo các tính năng mà ứng dụng của mình yêu cầu +vì vậy, những chợ ứng dụng như Google Play Store không cho phép cài đặt trên những thiết bị không hỗ trợ +tính năng đó.

+ + +

Tìm hiểu thêm:

+ + +
+ +
+ + + diff --git a/docs/html-intl/intl/vi/guide/topics/manifest/manifest-intro.jd b/docs/html-intl/intl/vi/guide/topics/manifest/manifest-intro.jd new file mode 100644 index 0000000000000000000000000000000000000000..ca2ed26270f2b52d566fefde53d5b66cb6cade30 --- /dev/null +++ b/docs/html-intl/intl/vi/guide/topics/manifest/manifest-intro.jd @@ -0,0 +1,517 @@ +page.title=Bản kê khai Ứng dụng +@jd:body + + + +

+ Mọi ứng dụng đều phải có một tệp AndroidManifest.xml (chính xác là + tên gọi này) trong thư mục gốc của mình. Tệp bản kê khai + trình bày những thông tin thiết yếu về ứng dụng của bạn với hệ thống Android, + thông tin mà hệ thống phải có trước khi có thể chạy bất kỳ mã nào + của ứng dụng. Ngoài một số mục đích khác, bản kê khai thực hiện những điều sau: +

+ +
    +
  • Nó đặt tên gói Java cho ứng dụng. +Tên gói đóng vai trò như một mã nhận diện duy nhất cho ứng dụng.
  • + +
  • Nó mô tả các thành phần của ứng dụng — hoạt động, +dịch vụ, hàm nhận quảng bá, và trình cung cấp nội dung mà ứng dụng +được soạn bởi. Nó đặt tên các lớp triển khai từng thành phần và +công bố các khả năng của chúng (ví dụ, những tin nhắn {@link android.content.Intent +Intent} mà chúng có thể xử lý). Những khai báo này cho phép hệ thống Android +biết các thành phần là gì và chúng có thể được khởi chạy trong những điều kiện nào.
  • + +
  • Nó xác định những tiến trình nào sẽ lưu trữ các thành phần ứng dụng.
  • + +
  • Nó khai báo các quyền mà ứng dụng phải có để +truy cập các phần được bảo vệ của API và tương tác với các ứng dụng khác.
  • + +
  • Nó cũng khai báo các quyền mà ứng dụng khác phải có để +tương tác với các thành phần của ứng dụng.
  • + +
  • Nó liệt kê các lớp {@link android.app.Instrumentation} cung cấp +tính năng tạo hồ sơ và các thông tin khác khi ứng dụng đang chạy. Những khai báo này +chỉ xuất hiện trong bản kê khai khi ứng dụng đang được phát triển và +thử nghiệm; chúng bị loại bỏ trước khi ứng dụng được công bố.
  • + +
  • Nó khai báo mức tối thiểu của API Android mà ứng dụng +yêu cầu.
  • + +
  • Nó liệt kê các thư viện mà ứng dụng phải được liên kết với.
  • +
+ + +

Cấu trúc của Tệp Bản kê khai

+ +

+Sơ đồ bên dưới minh họa cấu trúc chung của tệp bản kê khai và mọi +phần tử mà nó có thể chứa. Từng phần tử, cùng với tất cả thuộc tính +của mình, sẽ được lập tài liệu theo dõi đầy đủ vào một tệp riêng. Để xem thông tin +chi tiết về mọi phần tử, hãy nhấp vào tên phần tử trong sơ đồ, +trong danh sách các phần tử theo thứ tự chữ cái mà tuân theo sơ đồ, hoặc trên bất kỳ +nội dung nào khác đề cập tới tên phần tử. +

+ +
+<?xml version="1.0" encoding="utf-8"?>
+
+<manifest>
+
+    <uses-permission />
+    <permission />
+    <permission-tree />
+    <permission-group />
+    <instrumentation />
+    <uses-sdk />
+    <uses-configuration />  
+    <uses-feature />  
+    <supports-screens />  
+    <compatible-screens />  
+    <supports-gl-texture />  
+
+    <application>
+
+        <activity>
+            <intent-filter>
+                <action />
+                <category />
+                <data />
+            </intent-filter>
+            <meta-data />
+        </activity>
+
+        <activity-alias>
+            <intent-filter> . . . </intent-filter>
+            <meta-data />
+        </activity-alias>
+
+        <service>
+            <intent-filter> . . . </intent-filter>
+            <meta-data/>
+        </service>
+
+        <receiver>
+            <intent-filter> . . . </intent-filter>
+            <meta-data />
+        </receiver>
+
+        <provider>
+            <grant-uri-permission />
+            <meta-data />
+            <path-permission />
+        </provider>
+
+        <uses-library />
+
+    </application>
+
+</manifest>
+
+ +

+Tất cả phần tử có thể xuất hiện trong tệp bản kê khai được liệt kê ở bên dưới +theo thứ tự chữ cái. Đây là những phần tử hợp pháp duy nhất; bạn không thể +thêm các phần tử hay thuộc tính của chính mình. +

+ +

+<action> +
<activity> +
<activity-alias> +
<application> +
<category> +
<data> +
<grant-uri-permission> +
<instrumentation> +
<intent-filter> +
<manifest> +
<meta-data> +
<permission> +
<permission-group> +
<permission-tree> +
<provider> +
<receiver> +
<service> +
<supports-screens> +
<uses-configuration> +
<uses-feature> +
<uses-library> +
<uses-permission> +
<uses-sdk> +

+ + + + +

Các Quy ước Tệp

+ +

+Một số quy ước và quy tắc áp dụng chung cho tất cả các phần tử và thuộc tính +trong bản kê khai: +

+ +
+
Phần tử
+
Chỉ các phần tử +<manifest> và +<application> +là bắt buộc phải có, chúng đều phải có mặt và chỉ có thể xảy ra một lần. +Hầu hết các phần tử khác có thể xảy ra nhiều lần hoặc không xảy ra — mặc dù ít +nhất một vài trong số chúng phải có mặt để bản kê khai thực sự có +ý nghĩa nào đó. + +

+Nếu một phần tử chứa bất kỳ nội dung nào, nó có thể chứa các phần tử khác. +Tất cả giá trị sẽ được đặt thông qua thuộc tính, chứ không phải là dữ liệu ký tự trong một phần tử. +

+ +

+Các phần tử cùng cấp thường không theo thứ tự. Ví dụ, các phần tử +<activity>, +<provider>, và +<service> +có thể được trộn lẫn với nhau theo bất kỳ trình tự nào. (Phần tử +<activity-alias> +là trường hợp ngoại lệ đối với quy tắc này: Nó phải tuân theo +<activity> +, đối tượng mà nó là bí danh cho.) +

+ +
Thuộc tính
+
Theo cách hiểu thông thường, tất cả thuộc tính đều mang tính tùy chọn. Tuy nhiên, có một số thuộc tính +phải được quy định cho một phần tử để hoàn thành mục đích của nó. Sử dụng +tài liệu làm hướng dẫn. Đối với những thuộc tính thực sự tùy chọn, nó đề cập tới một giá trị +mặc định hoặc thông báo điều gì sẽ xảy ra nếu không có một đặc tả. + +

Ngoài một số thuộc tính của phần tử +<manifest> +gốc, tất cả tên thuộc tính đều bắt đầu bằng một tiền tố {@code android:}— +ví dụ, {@code android:alwaysRetainTaskState}. Do tiền tố này +phổ dụng, tài liệu thường bỏ sót nó khi tham chiếu tới các thuộc tính +theo tên.

+ +
Khai báo tên lớp
+
Nhiều thuộc tính tương ứng với các đối tượng Java, bao gồm các phần tử cho +chính ứng dụng (phần tử +<application> +) và các thành phần chính của nó — hoạt động +(<activity>), +dịch vụ +(<service>), +hàm nhận quảng bá +(<receiver>), +và trình cung cấp nội dung +(<provider>). + +

+Nếu bạn định nghĩa một lớp con như vẫn luôn làm đối với lớp thành phần +({@link android.app.Activity}, {@link android.app.Service}, +{@link android.content.BroadcastReceiver}, và {@link android.content.ContentProvider}), +lớp con sẽ được khai báo thông qua một thuộc tính {@code name}. Tên phải bao gồm +chỉ định gói đầy đủ. +Ví dụ, một lớp con {@link android.app.Service} có thể được khai báo như sau: +

+ +
<manifest . . . >
+    <application . . . >
+        <service android:name="com.example.project.SecretService" . . . >
+            . . .
+        </service>
+        . . .
+    </application>
+</manifest>
+ +

+Tuy nhiên, do cách viết tốc ký, nếu ký tự đầu tiên của xâu là một dấu chấm, +xâu sẽ được nối với tên gói của ứng dụng (như được quy định bởi +thuộc tính của phần tử <manifest> + +, package +). Cách gán sau cũng giống như trên: +

+ +
<manifest package="com.example.project" . . . >
+    <application . . . >
+        <service android:name=".SecretService" . . . >
+            . . .
+        </service>
+        . . .
+    </application>
+</manifest>
+ +

+Khi khởi động một thành phần, Android sẽ tạo một thực thể của lớp con được nêu tên. +Nếu lớp con không được quy định, nó sẽ tạo một thực thể của lớp cơ sở. +

+ +
Nhiều giá trị
+
Nếu có thể quy định nhiều hơn một giá trị, phần tử gần như luôn +được lặp lại, thay vì liệt kê nhiều giá trị trong một phần tử duy nhất. +Ví dụ, một bộ lọc ý định có thể liệt kê vài hành động: + +
<intent-filter . . . >
+    <action android:name="android.intent.action.EDIT" />
+    <action android:name="android.intent.action.INSERT" />
+    <action android:name="android.intent.action.DELETE" />
+    . . .
+</intent-filter>
+ +
Giá trị tài nguyên
+
Một số thuộc tính có các giá trị có thể được hiển thị với người dùng — ví +dụ, một nhãn và một biểu tượng cho một hoạt động. Giá trị của những thuộc tính này +cần được cục bộ hóa và vì thế phải được thiết đặt từ một tài nguyên hoặc chủ đề. Giá trị +tài nguyên được biểu diễn theo định dạng sau,

+ +

{@code @[gói:]kiểu:tên}

+ +

+trong đó gói có thể được bỏ qua nếu tài nguyên nằm trong cùng gói +với ứng dụng, kiểu là kiểu của tài nguyên — chẳng hạn như "xâu" hoặc +— "vẽ được" và tên là tên nhận biết tài nguyên cụ thể. +Ví dụ: +

+ +
<activity android:icon="@drawable/smallPic" . . . >
+ +

+Các giá trị từ một chủ đề được biểu diễn theo cách tương tự, nhưng với một '{@code ?}' +thay vì '{@code @}' ở đầu: +

+ +

{@code ?[gói:]kiểu:tên} +

+ +
Giá trị xâu
+
Trường hợp giá trị của một thuộc tính là một xâu, phải sử dụng hai dấu xuyệc ngược ('{@code \\}') +để thoát các ký tự — ví dụ, '{@code \\n}' đối với +một dòng tin tức hoặc '{@code \\uxxxx}' đối với một ký tự Unicode.
+
+ + +

Các Tính năng Tệp

+ +

+Phần sau đây mô tả cách phản ánh một số tính năng của Android +trong tệp bản kê khai. +

+ + +

Bộ lọc Ý định

+ +

+Các thành phần cốt lõi của một ứng dụng (hoạt động, dịch vụ và hàm nhận +quảng bá) được kích hoạt bởi ý định. Ý định là một +gói thông tin (một đối tượng {@link android.content.Intent}) mô tả một +hành động mong muốn — bao gồm dữ liệu sẽ được dựa trên, thể loại của +thành phần mà sẽ thực hiện hành động, và các chỉ dẫn thích hợp khác. +Android định vị một thành phần phù hợp để hồi đáp ý định, khởi chạy +một thực thể mới của thành phần nếu cần, và chuyển cho nó đối tượng đó +Ý định. +

+ +

+Các thành phần sẽ quảng cáo khả năng của mình — các kiểu ý định mà chúng có thể +hồi đáp — thông qua các bộ lọc ý định. Do hệ thống Android phải +tìm hiểu một thành phần có thể xử lý những ý định nào trước khi khởi chạy thành phần đó, +bộ lọc ý định được quy định trong bản kê khai như là các phần tử +<intent-filter> +. Một thành phần có thể có nhiều bộ lọc, mỗi bộ lọc lại mô tả +một khả năng khác nhau. +

+ +

+Một ý định mà công khai nêu tên một thành phần mục tiêu sẽ kích hoạt thành phần đó; +bộ lọc không có vai trò gì ở đây. Nhưng một ý định mà không quy định một mục tiêu +theo tên sẽ chỉ có thể kích hoạt thành phần nếu nó có thể chuyển qua một trong các bộ lọc của +thành phần. +

+ +

+Để biết thông tin về cách các đối tượng Ý định được kiểm tra thông qua bộ lọc ý định, +hãy xem tài liệu riêng có tiêu đề +Ý định +và Bộ lọc Ý định. +

+ + +

Biểu tượng và Nhãn

+ +

+Nhiều phần tử có thuộc tính {@code icon} và {@code label} cho một +biểu tượng nhỏ và nhãn văn bản mà có thể được hiển thị với người dùng. Một số cũng có thuộc tính +{@code description} cho văn bản giải trình dài hơn mà cũng có thể +được hiển thị trên màn hình. Ví dụ, phần tử +<permission> +có cả ba thuộc tính này, vì thế khi người dùng được hỏi xem có +cấp quyền cho một ứng dụng yêu cầu hay không, biểu tượng thể hiện +quyền, tên của quyền, và mô tả nội dung +của quyền đó đều có thể được trình bày cho người dùng xem. +

+ +

+Trong mọi trường hợp, biểu tượng và nhãn được đặt trong một phần tử chứa sẽ trở thành các thiết đặt +{@code icon} và {@code label} mặc định cho tất cả phần tử con của bộ chứa đó. +Vì thế, biểu tượng và nhãn được đặt trong phần tử +<application> +là biểu tượng và nhãn mặc định cho từng thành phần của ứng dụng. +Tương tự, biểu tượng và nhãn được đặt cho một thành phần — ví dụ, một phần tử +<activity> +— sẽ là các cài đặt mặc định cho từng phần tử +<intent-filter> +của thành phần đó. Nếu một phần tử +<application> +thiết đặt một nhãn, nhưng hoạt động và bộ lọc ý định của nó thì không, +nhãn ứng dụng sẽ được coi là nhãn của cả hoạt động và +bộ lọc ý định. +

+ +

+Biểu tượng và nhãn được đặt cho một bộ lọc ý định sẽ được sử dụng để biểu diễn một thành phần +bất cứ khi nào thành phần đó được trình bày với người dùng để thực hiện chức năng +mà bộ lọc đã quảng cáo. Ví dụ, một bộ lọc với các thiết đặt +"{@code android.intent.action.MAIN}" và +"{@code android.intent.category.LAUNCHER}" quảng cáo một hoạt động +là hoạt động khởi đầu một ứng dụng — cụ thể, là +hoạt động sẽ được hiển thị trong trình khởi chạy ứng dụng. Vì thế, biểu tượng và nhãn +được đặt trong bộ lọc là những nội dung được hiển thị trong trình khởi chạy. +

+ + +

Quyền

+ +

+Một quyền là sự hạn chế giới hạn truy cập vào một phần của mã +hoặc vào dữ liệu trên thiết bị. Giới hạn này được áp đặt nhằm bảo vệ dữ liệu +và mã trọng yếu, có thể bị lạm dụng để bóp méo hoặc làm hỏng trải nghiệm người dùng. +

+ +

+Mỗi quyền được nhận biết bằng một nhãn duy nhất. Thông thường, nhãn cho biết +hành động bị hạn chế. Ví dụ, sau đây là một số quyền được định nghĩa +bởi Android: +

+ +

{@code android.permission.CALL_EMERGENCY_NUMBERS} +
{@code android.permission.READ_OWNER_DATA} +
{@code android.permission.SET_WALLPAPER} +
{@code android.permission.DEVICE_POWER}

+ +

+Một tính năng có thể được bảo vệ bởi nhiều nhất một quyền. +

+ +

+Nếu một ứng dụng cần truy cập vào một tính năng được bảo vệ bởi một quyền, +nó phải khai báo rằng nó yêu cầu quyền đó cùng với một phần tử +<uses-permission> +trong bản kê khai. Lúc đó, khi ứng dụng được cài đặt trên +thiết bị, trình cài đặt sẽ xác định xem có cấp quyền +được yêu cầu hay không bằng cách kiểm tra các thẩm quyền đã ký chứng chỉ +của ứng dụng và trong một số trường hợp, bằng cách hỏi người dùng. +Nếu quyền được cấp, ứng dụng có thể sử dụng các tính năng +được bảo vệ. Nếu không, việc thử truy cập những tính năng đó sẽ thất bại +mà không có bất kỳ thông báo nào cho người dùng. +

+ +

+Một ứng dụng cũng có thể bảo vệ các thành phần của chính nó (hoạt động, dịch vụ, +hàm nhận quảng bá và trình cung cấp nội dung) bằng các quyền. Nó có thể sử dụng +bất kỳ quyền nào được định nghĩa bởi Android (được liệt kê trong +{@link android.Manifest.permission android.Manifest.permission}) hoặc được khai báo +bởi các ứng dụng khác. Hoặc nó có thể tự định nghĩa quyền của mình. Một quyền mới được khai báo +bằng phần tử +<permission> +. Ví dụ, một hoạt động có thể được bảo vệ như sau: +

+ +
+<manifest . . . >
+    <permission android:name="com.example.project.DEBIT_ACCT" . . . />
+    <uses-permission android:name="com.example.project.DEBIT_ACCT" />
+    . . .
+    <application . . .>
+        <activity android:name="com.example.project.FreneticActivity"
+                  android:permission="com.example.project.DEBIT_ACCT"
+                  . . . >
+            . . .
+        </activity>
+    </application>
+</manifest>
+
+ +

+Lưu ý rằng trong ví dụ này, quyền {@code DEBIT_ACCT} không chỉ +được khai báo bằng phần tử +<permission> +, việc sử dụng quyền cũng được yêu cầu bằng phần tử +<uses-permission> +. Phải yêu cầu sử dụng quyền để các thành phần khác của +ứng dụng nhằm khởi chạy hoạt động được bảo vệ, mặc dù việc bảo vệ +do chính ứng dụng áp đặt. +

+ +

+Trong cùng ví dụ này, nếu thuộc tính {@code permission} được đặt thành một quyền +được khai báo ở nơi khác +(chẳng hạn như {@code android.permission.CALL_EMERGENCY_NUMBERS}, sẽ không +cần phải khai báo lại nó bằng một phần tử +<permission> +. Tuy nhiên, sẽ vẫn cần phải yêu cầu sử dụng nó bằng +<uses-permission>. +

+ +

+Phần tử +<permission-tree> +sẽ khai báo một vùng tên cho nhóm quyền mà sẽ được định nghĩa trong +mã. Và +<permission-group> +sẽ định nghĩa một nhãn cho một tập hợp quyền (cả được khai báo trong bản kê khai bằng phần tử +<permission> +và được khai báo ở chỗ khác). Nó chỉ ảnh hưởng tới cách các quyền được +nhóm lại khi được trình bày với người dùng. Phần tử +<permission-group> +không quy định những quyền nào thuộc về nhóm; +nó chỉ đặt cho nhóm một cái tên. Một quyền được đặt vào nhóm +bằng cách gán tên nhóm với thuộc tính của phần tử +<permission> +, +permissionGroup +. +

+ + +

Thư viện

+ +

+Mọi ứng dụng đều được liên kết với thư viện Android mặc định, nó +bao gồm các gói cơ bản để xây dựng ứng dụng (bằng các lớp thông dụng +chẳng hạn như Hoạt động, Dịch vụ, Ý định, Dạng xem, Nút, Ứng dụng, Trình cung cấp Nội dung, +v.v.). +

+ +

+Tuy nhiên, một số gói nằm trong thư viện của chính mình. Nếu ứng dụng của bạn +sử dụng mã từ bất kỳ gói nào trong những gói này, nó phải công khai yêu cầu được liên kết +với chúng. Bản kê khai phải chứa một phần tử +<uses-library> +riêng để đặt tên cho từng thư viện. (Tên thư viện có thể được tìm thấy trong tài liệu +của gói.) +

diff --git a/docs/html-intl/intl/vi/guide/topics/providers/calendar-provider.jd b/docs/html-intl/intl/vi/guide/topics/providers/calendar-provider.jd new file mode 100644 index 0000000000000000000000000000000000000000..e2ecdb32b2e8116b906e2f0538b3d54d5059137c --- /dev/null +++ b/docs/html-intl/intl/vi/guide/topics/providers/calendar-provider.jd @@ -0,0 +1,1184 @@ +page.title=Trình cung cấp Lịch +@jd:body + +
+
+

Trong tài liệu này

+
    +
  1. Nội dung Cơ bản
  2. +
  3. Quyền của Người dùng
  4. +
  5. Bảng Lịch +
      +
    1. Truy vấn một lịch
    2. +
    3. Sửa đổi một lịch
    4. +
    5. Chèn một lịch
    6. +
    +
  6. +
  7. Bảng Sự kiện +
      +
    1. Thêm Sự kiện
    2. +
    3. Cập nhật Sự kiện
    4. +
    5. Xóa Sự kiện
    6. +
    +
  8. +
  9. Bảng Người dự +
      +
    1. Thêm Người dự
    2. +
    +
  10. +
  11. Bảng Nhắc nhở +
      +
    1. Thêm Nhắc nhở
    2. +
    +
  12. +
  13. Bảng Thực thể +
      +
    1. Truy vấn bảng Thực thể
    2. +
  14. +
  15. Ý định Lịch +
      +
    1. Sử dụng ý định để chèn một sự kiện
    2. +
    3. Sử dụng ý định để chỉnh sửa một sự kiện
    4. +
    5. Sử dụng ý định để xem dữ liệu lịch
    6. +
    +
  16. + +
  17. Trình điều hợp Đồng bộ
  18. +
+ +

Lớp khóa

+
    +
  1. {@link android.provider.CalendarContract.Calendars}
  2. +
  3. {@link android.provider.CalendarContract.Events}
  4. +
  5. {@link android.provider.CalendarContract.Attendees}
  6. +
  7. {@link android.provider.CalendarContract.Reminders}
  8. +
+
+
+ +

Trình cung cấp Lịch là một kho lưu trữ các sự kiện lịch của người dùng. API +Trình cung cấp Lịch cho phép bạn thực hiện truy vấn, chèn, cập nhật và xóa +các thao tác trên lịch, sự kiện, người dự, nhắc nhở, v.v.

+ + +

API Trình cung cấp Lịch có thể được sử dụng bởi các ứng dụng và trình điều hợp đồng bộ. Các quy tắc +thay đổi tùy vào loại chương trình đang thực hiện lệnh gọi. Tài liệu này +tập trung chủ yếu vào việc sử dụng API Trình cung cấp Lịch như một ứng dụng. Để bàn +về việc các trình điều hợp đồng bộ khác nhau như thế nào, hãy xem phần +Trình điều hợp Đồng bộ.

+ + +

Thông thường, để đọc hoặc ghi dữ liệu lịch, bản kê khai của ứng dụng phải +bao gồm các quyền thích hợp của người dùng, được nêu trong phần Quyền +của Người dùng. Để thực hiện các thao tác chung dễ hơn, Trình cung cấp +Lịch đưa ra một tập hợp ý định, như được mô tả trong phần Ý định +Lịch. Những ý định này đưa người dùng tới ứng dụng Lịch để chèn, xem, +và chỉnh sửa sự kiện. Người dùng tương tác với ứng dụng Lịch rồi +quay lại ứng dụng ban đầu. Vì thế, ứng dụng của bạn không cần yêu cầu quyền, +và cũng không cần cung cấp một giao diện người dùng để xem hoặc tạo sự kiện.

+ +

Nội dung Cơ bản

+ +

Các trình cung cấp nội dung sẽ lưu trữ dữ liệu và cho phép truy cập +ứng dụng. Trình cung cấp nội dung được nền tảng Android giới thiệu (bao gồm Trình cung cấp Lịch) thường trình bày dữ liệu như một tập hợp gồm nhiều bảng dựa trên một +mô hình cơ sở dữ liệu quan hệ, trong đó mỗi hàng là một bản ghi và mỗi cột là dữ liệu thuộc +một loại và có ý nghĩa cụ thể. Thông qua API Trình cung cấp Lịch, các ứng dụng +và trình điều hợp đồng bộ có thể nhận được quyền truy cập đọc/ghi vào các bảng trong cơ sở dữ liệu là nơi chứa +dữ liệu lịch của người dùng.

+ +

Mọi trình cung cấp nội dung đều đưa ra một URI công khai (được bẻ dòng như một đối tượng +{@link android.net.Uri} +) để xác định tập dữ liệu của nó một cách duy nhất. Trình cung cấp nội dung mà kiểm soát nhiều + tập dữ liệu (nhiều bảng) sẽ đưa ra một URI riêng cho từng bảng. Tất cả +URI cho trình cung cấp đều bắt đầu bằng xâu "content://". Điều này +sẽ xác định dữ liệu là đang được kiểm soát bởi một trình cung cấp nội dung. Trình cung cấp +Lịch định nghĩa các hằng số cho URI đối với từng lớp (bảng) của nó. Những URI +này có định dạng <class>.CONTENT_URI. Ví +dụ, {@link android.provider.CalendarContract.Events#CONTENT_URI Events.CONTENT_URI}.

+ +

Hình 1 cho biết biểu diễn đồ họa của mô hình dữ liệu Trình cung cấp Lịch. Nó cho biết +các bảng chính và các trường liên kết chúng với nhau.

+ +Calendar Provider Data Model +

Hình 1. Mô hình dữ liệu Trình cung cấp Lịch.

+ +

Một người dùng có thể có nhiều lịch, và các lịch khác nhau có thể được liên kết với các loại tài khoản khác nhau (Google Calendar, Exchange, v.v.).

+ +

{@link android.provider.CalendarContract} sẽ định nghĩa mô hình dữ liệu của thông tin liên quan tới lịch và sự kiện. Dữ liệu này được lưu trữ trong nhiều bảng như liệt kê bên dưới.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Bảng (Lớp)Mô tả

{@link android.provider.CalendarContract.Calendars}

Bảng này chứa +thông tin riêng của lịch. Mỗi hàng trong bảng này chứa chi tiết của +một lịch duy nhất, chẳng hạn như tên, màu, thông tin đồng bộ, v.v.
{@link android.provider.CalendarContract.Events}Bảng này chứa +thông tin riêng theo sự kiện. Mỗi hàng trong bảng có thông tin cho một +sự kiện duy nhất—ví dụ: tiêu đề sự kiện, địa điểm, thời gian bắt đầu +, thời gian kết thúc, v.v. Sự kiện có thể xảy ra một lần hoặc lặp lại nhiều lần. Người dự, +nhắc nhở, và các tính chất mở rộng được lưu trữ trong các bảng riêng. +Mỗi mục đều có một {@link android.provider.CalendarContract.AttendeesColumns#EVENT_ID} +tham chiếu tới {@link android.provider.BaseColumns#_ID} trong bảng Sự kiện.
{@link android.provider.CalendarContract.Instances}Bảng này chứa +thời gian bắt đầu và thời gian kết thúc của mỗi lần xảy ra một sự kiện. Mỗi hàng trong bảng này +đại diện cho một lần xảy ra sự kiện. Với các sự kiện xảy ra một lần thì có một ánh xạ 1:1 +của thực thể tới sự kiện. Đối với các sự kiện định kỳ, nhiều hàng sẽ tự động + được khởi tạo tương ứng với nhiều lần xảy ra sự kiện đó.
{@link android.provider.CalendarContract.Attendees}Bảng này chứa +thông tin về người dự (khách) của sự kiện. Mỗi hàng đại diện một khách duy nhất của +một sự kiện. Nó quy định loại khách và phản hồi tham dự của khách +cho một sự kiện.
{@link android.provider.CalendarContract.Reminders}Bảng này chứa +dữ liệu về cảnh báo/thông báo. Mỗi hàng đại diện một cảnh báo duy nhất cho một sự kiện. Một +sự kiện có thể có nhiều nhắc nhở. Số nhắc nhở tối đa của một sự kiện +được quy định trong +{@link android.provider.CalendarContract.CalendarColumns#MAX_REMINDERS}, +được đặt bởi trình điều hợp đồng bộ đang +sở hữu lịch đã cho. Nhắc nhở được quy định bằng số phút trước khi diễn ra sự kiện +và có một phương pháp để xác định cách người dùng sẽ được cảnh báo.
+ +

API Trình cung cấp Lịch được thiết kế để linh hoạt và mạnh mẽ. Đồng +thời, điều quan trọng là phải cung cấp một trải nghiệm người dùng cuối tốt và +bảo vệ sự toàn vẹn của lịch và dữ liệu lịch. Để làm điều này, sau đây là một số +điều cần ghi nhớ khi sử dụng API này:

+ +
    + +
  • Chèn, cập nhật, và xem sự kiện lịch. Để trực tiếp chèn, sửa đổi, và đọc sự kiện từ Trình cung cấp Lịch, bạn cần các quyền phù hợp. Tuy nhiên, nếu bạn không đang xây dựng một ứng dụng lịch hoặc trình điều hợp đồng bộ chính thức, việc yêu cầu những quyền này là không cần thiết. Thay vào đó, bạn có thể sử dụng những ý định được cung cấp bởi ứng dụng Lịch của Android để chuyển giao các thao tác đọc và ghi cho ứng dụng đó. Khi bạn sử dụng ý định, ứng dụng của bạn sẽ gửi người dùng tới ứng dụng Lịch để thực hiện thao tác mong muốn +trong một mẫu được điền trước. Sau khi xong, họ sẽ được trả về ứng dụng của bạn. +Bằng việc thiết kế ứng dụng của bạn để thực hiện các thao tác thường gặp thông qua Lịch, +bạn cung cấp cho người dùng một giao diện người dùng nhất quán, thiết thực. Đây là phương pháp +được khuyến cáo nên dùng. Để biết thêm thông tin, hãy xem phần Ý định +Lịch.

    + + +
  • Trình điều hợp đồng bộ. Trình điều hợp đồng bộ có chức năng đồng bộ dữ liệu lịch +lên thiết bị của một người dùng bằng một máy chủ hoặc nguồn dữ liệu khác. Trong bảng +{@link android.provider.CalendarContract.Calendars} và +{@link android.provider.CalendarContract.Events}, +có các cột để cho trình điều hợp đồng bộ sử dụng. +Trình cung cấp và ứng dụng không nên sửa đổi chúng. Trên thực tế, chúng không +hiển thị trừ khi được truy cập như một trình điều hợp đồng bộ. Để biết thêm thông tin về +trình điều hợp đồng bộ, hãy xem phần Trình điều hợp Đồng bộ.
  • + +
+ + +

Quyền của Người dùng

+ +

Để đọc dữ liệu lịch, một ứng dụng phải bao gồm quyền {@link +android.Manifest.permission#READ_CALENDAR} trong tệp bản kê khai của mình. Nó +phải bao gồm quyền {@link android.Manifest.permission#WRITE_CALENDAR} +để xóa, chèn hoặc cập nhật dữ liệu lịch:

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"...>
+    <uses-sdk android:minSdkVersion="14" />
+    <uses-permission android:name="android.permission.READ_CALENDAR" />
+    <uses-permission android:name="android.permission.WRITE_CALENDAR" />
+    ...
+</manifest>
+
+ + +

Bảng Lịch

+ +

Bảng {@link android.provider.CalendarContract.Calendars} chứa thông tin chi tiết +cho từng lịch. Các cột +Lịch sau có thể ghi được bởi cả ứng dụng và trình điều hợp đồng bộ. +Để xem danh sách đầy đủ về các trường được hỗ trợ, hãy xem tài liệu tham khảo +{@link android.provider.CalendarContract.Calendars}.

+ + + + + + + + + + + + + + + + + + + + + + + + + +
Hằng sốMô tả
{@link android.provider.CalendarContract.Calendars#NAME}Tên của lịch.
{@link android.provider.CalendarContract.Calendars#CALENDAR_DISPLAY_NAME}Tên của lịch này mà được hiển thị cho người dùng.
{@link android.provider.CalendarContract.Calendars#VISIBLE}Một boolean cho biết lịch có được chọn để hiển thị hay không. Giá trị +bằng 0 cho biết các sự kiện liên kết với lịch này sẽ không được +hiển thị. Giá trị bằng 1 cho biết các sự kiện liên kết với lịch này sẽ được +hiển thị. Giá trị này ảnh hưởng tới việc khởi tạo hàng trong bảng {@link +android.provider.CalendarContract.Instances}.
{@link android.provider.CalendarContract.CalendarColumns#SYNC_EVENTS}Một boolean cho biết lịch sẽ được đồng bộ và có các sự kiện +của mình được lưu trữ trên thiết bị hay không. Giá trị bằng 0 tức là không đồng bộ lịch này hay +lưu giữ các sự kiện của nó lên thiết bị. Giá trị bằng 1 tức là đồng bộ các sự kiện cho lịch này +và lưu trữ các sự kiện của nó lên thiết bị.
+ +

Truy vấn một lịch

+ +

Sau đây là một ví dụ về cách nhận được lịch do một người dùng +cụ thể sở hữu. Để đơn giản, trong ví dụ này, thao tác truy vấn được thể hiện trong + luồng giao diện người dùng ("luồng chính"). Trong thực hành, nên làm điều này trong một luồng +không đồng bộ thay vì trên luồng chính. Để bàn thêm, hãy xem phần +Trình tải. Nếu bạn đang không chỉ +đọc dữ liệu mà còn sửa đổi nó, hãy xem {@link android.content.AsyncQueryHandler}. +

+ + +
+// Projection array. Creating indices for this array instead of doing
+// dynamic lookups improves performance.
+public static final String[] EVENT_PROJECTION = new String[] {
+    Calendars._ID,                           // 0
+    Calendars.ACCOUNT_NAME,                  // 1
+    Calendars.CALENDAR_DISPLAY_NAME,         // 2
+    Calendars.OWNER_ACCOUNT                  // 3
+};
+  
+// The indices for the projection array above.
+private static final int PROJECTION_ID_INDEX = 0;
+private static final int PROJECTION_ACCOUNT_NAME_INDEX = 1;
+private static final int PROJECTION_DISPLAY_NAME_INDEX = 2;
+private static final int PROJECTION_OWNER_ACCOUNT_INDEX = 3;
+ + + + + +

Trong phần tiếp theo của ví dụ, bạn sẽ xây dựng truy vấn của mình. Lựa chọn +này sẽ quy định các tiêu chí cho truy vấn. Trong ví dụ này, truy vấn đang tìm +các lịch có ACCOUNT_NAME +"sampleuser@google.com", ACCOUNT_TYPE +"com.google", và OWNER_ACCOUNT +"sampleuser@google.com". Nếu bạn muốn xem tất cả lịch mà một người dùng +đã xem, không chỉ các lịch mà người dùng sở hữu, hãy bỏ qua OWNER_ACCOUNT. +Truy vấn sẽ trả về đối tượng {@link android.database.Cursor} +mà bạn có thể sử dụng để xem xét tập kết quả được trả về bởi truy vấn +cơ sở dữ liệu. Để bàn thêm về việc sử dụng các truy vấn trong trình cung cấp nội dung, +hãy xem phần Trình cung cấp Nội dung.

+ + +
// Run query
+Cursor cur = null;
+ContentResolver cr = getContentResolver();
+Uri uri = Calendars.CONTENT_URI;   
+String selection = "((" + Calendars.ACCOUNT_NAME + " = ?) AND (" 
+                        + Calendars.ACCOUNT_TYPE + " = ?) AND ("
+                        + Calendars.OWNER_ACCOUNT + " = ?))";
+String[] selectionArgs = new String[] {"sampleuser@gmail.com", "com.google",
+        "sampleuser@gmail.com"}; 
+// Submit the query and get a Cursor object back. 
+cur = cr.query(uri, EVENT_PROJECTION, selection, selectionArgs, null);
+ +

Phần tiếp theo sử dụng con chạy để duyệt qua tập kết quả. Nó sử dụng +các hằng số được thiết lập ngay từ đầu ví dụ để trả về các giá trị +cho mỗi trường.

+ +
// Use the cursor to step through the returned records
+while (cur.moveToNext()) {
+    long calID = 0;
+    String displayName = null;
+    String accountName = null;
+    String ownerName = null;
+      
+    // Get the field values
+    calID = cur.getLong(PROJECTION_ID_INDEX);
+    displayName = cur.getString(PROJECTION_DISPLAY_NAME_INDEX);
+    accountName = cur.getString(PROJECTION_ACCOUNT_NAME_INDEX);
+    ownerName = cur.getString(PROJECTION_OWNER_ACCOUNT_INDEX);
+              
+    // Do something with the values...
+
+   ...
+}
+
+ +

Sửa đổi một lịch

+ +

Để thực hiện cập nhật một lịch, bạn có thể cung cấp {@link +android.provider.BaseColumns#_ID} của lịch hoặc dưới dạng ID được nối vào cho +Uri + +({@link android.content.ContentUris#withAppendedId(android.net.Uri,long) withAppendedId()}) +hoặc dưới dạng mục chọn đầu tiên. Lựa chọn +nên bắt đầu bằng "_id=?", và +selectionArg đầu tiên sẽ là {@link +android.provider.BaseColumns#_ID} của lịch. +Bạn cũng có thể thực hiện cập nhật bằng cách mã hóa ID trong URI. Ví dụ này thay đổi tên hiển thị +của một lịch bằng cách sử dụng phương pháp +({@link android.content.ContentUris#withAppendedId(android.net.Uri,long) withAppendedId()}) +:

+ +
private static final String DEBUG_TAG = "MyActivity";
+...
+long calID = 2;
+ContentValues values = new ContentValues();
+// The new display name for the calendar
+values.put(Calendars.CALENDAR_DISPLAY_NAME, "Trevor's Calendar");
+Uri updateUri = ContentUris.withAppendedId(Calendars.CONTENT_URI, calID);
+int rows = getContentResolver().update(updateUri, values, null, null);
+Log.i(DEBUG_TAG, "Rows updated: " + rows);
+ +

Chèn một lịch

+ +

Lịch được thiết kế để được quản lý chủ yếu bởi một trình điều hợp đồng bộ, vì vậy bạn chỉ nên +chèn các lịch mới như một trình điều hợp đồng bộ. Phần lớn thì các ứng dụng +chỉ có thể thực hiện những thay đổi bề mặt về lịch chẳng hạn như thay đổi tên hiển thị. Nếu +một ứng dụng cần tạo một lịch cục bộ, nó có thể làm điều này bằng cách thực hiện +chèn lịch dưới dạng một trình điều hợp đồng bộ, sử dụng {@link +android.provider.CalendarContract.SyncColumns#ACCOUNT_TYPE} của {@link +android.provider.CalendarContract#ACCOUNT_TYPE_LOCAL}. +{@link android.provider.CalendarContract#ACCOUNT_TYPE_LOCAL} +là một loại tài khoản đặc biệt dành cho các lịch không +liên kết với một tài khoản thiết bị. Các lịch loại này không được đồng bộ với một máy chủ. Để +bàn về trình điều hợp đồng bộ, hãy xem phần Trình điều hợp Đồng bộ.

+ +

Bảng Sự kiện

+ +

Bảng {@link android.provider.CalendarContract.Events} chứa thông tin chi tiết +cho các sự kiện riêng lẻ. Để thêm, cập nhật hoặc xóa sự kiện, ứng dụng phải +bao gồm quyền {@link android.Manifest.permission#WRITE_CALENDAR} trong +tệp bản kê khai của mình.

+ +

Các cột Sự kiện sau có thể ghi được bởi cả ứng dụng và trình điều hợp +đồng bộ. Để xem danh sách đầy đủ về các trường được hỗ trợ, hãy xem tài liệu tham khảo {@link +android.provider.CalendarContract.Events}.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Hằng sốMô tả
{@link android.provider.CalendarContract.EventsColumns#CALENDAR_ID}{@link android.provider.BaseColumns#_ID} của lịch mà chứa sự kiện.
{@link android.provider.CalendarContract.EventsColumns#ORGANIZER}E-mail của người tổ chức (người chủ) của sự kiện.
{@link android.provider.CalendarContract.EventsColumns#TITLE}Tiêu đề của sự kiện.
{@link android.provider.CalendarContract.EventsColumns#EVENT_LOCATION}Nơi sự kiện diễn ra.
{@link android.provider.CalendarContract.EventsColumns#DESCRIPTION}Mô tả sự kiện.
{@link android.provider.CalendarContract.EventsColumns#DTSTART}Thời gian sự kiện bắt đầu tính bằng mili giây UTC trôi qua kể từ giờ epoch.
{@link android.provider.CalendarContract.EventsColumns#DTEND}Thời gian sự kiện kết thúc tính bằng mili giây UTC trôi qua kể từ giờ epoch.
{@link android.provider.CalendarContract.EventsColumns#EVENT_TIMEZONE}Múi giờ của sự kiện.
{@link android.provider.CalendarContract.EventsColumns#EVENT_END_TIMEZONE}Múi giờ của thời điểm kết thúc sự kiện.
{@link android.provider.CalendarContract.EventsColumns#DURATION}Thời lượng của sự kiện theo định dạng RFC5545. +Ví dụ, giá trị bằng "PT1H" cho biết sự kiện sẽ kéo dài +một giờ và giá trị bằng "P2W" cho biết +thời lượng là 2 tuần.
{@link android.provider.CalendarContract.EventsColumns#ALL_DAY}Giá trị bằng 1 cho biết sự kiện này chiếm cả ngày, được xác định bởi +múi giờ tại địa phương. Giá trị bằng 0 cho biết đó là một sự kiện thường xuyên mà có thể bắt đầu +và kết thúc vào bất cứ lúc nào trong một ngày.
{@link android.provider.CalendarContract.EventsColumns#RRULE}Quy tắc lặp lại đối với định dạng sự kiện. Ví +dụ, "FREQ=WEEKLY;COUNT=10;WKST=SU". Bạn có thể tìm thêm +nhiều ví dụ hơn ở đây.
{@link android.provider.CalendarContract.EventsColumns#RDATE}Ngày lặp lại đối với sự kiện. + Bạn thường sử dụng {@link android.provider.CalendarContract.EventsColumns#RDATE} + cùng với {@link android.provider.CalendarContract.EventsColumns#RRULE} + để định nghĩa một tập tổng hợp +các trường hợp xảy ra lặp lại. Để bàn thêm, hãy xem phần RFC5545 spec.
{@link android.provider.CalendarContract.EventsColumns#AVAILABILITY}Xem sự kiện này được tính là thời gian bận hay là thời gian rảnh có thể được +xếp lại lịch.
{@link android.provider.CalendarContract.EventsColumns#GUESTS_CAN_MODIFY}Khách có thể sửa đổi sự kiện được hay không.
{@link android.provider.CalendarContract.EventsColumns#GUESTS_CAN_INVITE_OTHERS}Khách có thể mời khách khác được hay không.
{@link android.provider.CalendarContract.EventsColumns#GUESTS_CAN_SEE_GUESTS}Khách có thể xem danh sách người dự được hay không.
+ +

Thêm Sự kiện

+ +

Khi ứng dụng của bạn chèn một sự kiện mới, chúng tôi khuyến cáo bạn nên sử dụng một Ý định +{@link android.content.Intent#ACTION_INSERT INSERT}, như được mô tả trong Sử dụng ý định để chèn một sự kiện. Tuy nhiên, nếu +cần, bạn có thể chèn sự kiện trực tiếp. Phần này mô tả cách làm điều +này.

+ + +

Sau đây là các quy tắc để chèn một sự kiện mới:

+
    + +
  • Bạn phải đưa vào {@link +android.provider.CalendarContract.EventsColumns#CALENDAR_ID} và {@link +android.provider.CalendarContract.EventsColumns#DTSTART}.
  • + +
  • Bạn phải đưa vào một {@link +android.provider.CalendarContract.EventsColumns#EVENT_TIMEZONE}. Để nhận một danh sách +các ID múi giờ được cài đặt của hệ thống, hãy sử dụng {@link +java.util.TimeZone#getAvailableIDs()}. Lưu ý rằng quy tắc này không áp dụng nếu +bạn đang chèn một sự kiện thông qua Ý định {@link +android.content.Intent#ACTION_INSERT INSERT} như được mô tả trong Sử dụng ý định để chèn một sự kiện—trong kịch bản +đó, một múi giờ mặc định sẽ được cung cấp.
  • + +
  • Đối với các sự kiện không định kỳ, bạn phải đưa vào {@link +android.provider.CalendarContract.EventsColumns#DTEND}.
  • + + +
  • Đối với các sự kiện định kỳ, bạn phải đưa vào một {@link +android.provider.CalendarContract.EventsColumns#DURATION} bên cạnh {@link +android.provider.CalendarContract.EventsColumns#RRULE} hay {@link +android.provider.CalendarContract.EventsColumns#RDATE}. Lưu ý rằng quy tắc này không áp dụng nếu +bạn đang chèn một sự kiện thông qua Ý định {@link +android.content.Intent#ACTION_INSERT INSERT} như được mô tả trong Sử dụng ý định để chèn một sự kiện—trong kịch bản +đó, bạn có thể sử dụng một {@link +android.provider.CalendarContract.EventsColumns#RRULE} cùng với {@link android.provider.CalendarContract.EventsColumns#DTSTART} và {@link android.provider.CalendarContract.EventsColumns#DTEND}, và ứng dụng Lịch +sẽ tự động chuyển nó thành một thời lượng.
  • + +
+ +

Sau đây là một ví dụ về cách chèn một sự kiện. Ví dụ này đang được thực hiện trong luồng +UI để cho đơn giản. Trong thực hành, chèn và cập nhật nên được thực hiện trong một +luồng không đồng bộ để di chuyển hành động vào một luồng chạy ngầm. Để biết thêm +thông tin, hãy xem phần {@link android.content.AsyncQueryHandler}.

+ + +
+long calID = 3;
+long startMillis = 0; 
+long endMillis = 0;     
+Calendar beginTime = Calendar.getInstance();
+beginTime.set(2012, 9, 14, 7, 30);
+startMillis = beginTime.getTimeInMillis();
+Calendar endTime = Calendar.getInstance();
+endTime.set(2012, 9, 14, 8, 45);
+endMillis = endTime.getTimeInMillis();
+...
+
+ContentResolver cr = getContentResolver();
+ContentValues values = new ContentValues();
+values.put(Events.DTSTART, startMillis);
+values.put(Events.DTEND, endMillis);
+values.put(Events.TITLE, "Jazzercise");
+values.put(Events.DESCRIPTION, "Group workout");
+values.put(Events.CALENDAR_ID, calID);
+values.put(Events.EVENT_TIMEZONE, "America/Los_Angeles");
+Uri uri = cr.insert(Events.CONTENT_URI, values);
+
+// get the event ID that is the last element in the Uri
+long eventID = Long.parseLong(uri.getLastPathSegment());
+// 
+// ... do something with event ID
+//
+//
+ +

Lưu ý: Xem cách mà ví dụ này bắt được ID +sự kiện sau khi sự kiện được tạo. Đây là cách dễ nhất để nhận được một ID sự kiện. Bạn thường +cần ID sự kiện để thực hiện các thao tác lịch khác—ví dụ: để thêm +người dự hoặc nhắc nhở vào một sự kiện.

+ + +

Cập nhật Sự kiện

+ +

Khi ứng dụng của bạn muốn cho phép người dùng chỉnh sửa một sự kiện, chúng tôi khuyến cáo +bạn nên sử dụng một Ý định {@link android.content.Intent#ACTION_EDIT EDIT} như được mô tả +trong Sử dụng ý định để chỉnh sửa một sự kiện. +Tuy nhiên, nếu cần, bạn có thể chỉnh sửa sự kiện trực tiếp. Để thực hiện cập nhật +một Sự kiện, bạn có thể cung cấp _ID của sự kiện +hoặc dưới dạng ID được nối vào cho Uri ({@link +android.content.ContentUris#withAppendedId(android.net.Uri,long) withAppendedId()}) +hoặc dưới dạng mục chọn đầu tiên. +Lựa chọn nên bắt đầu bằng "_id=?", và +selectionArg đầu tiên nên là _ID của sự kiện. Bạn cũng có thể +thực hiện cập nhật bằng cách sử dụng một lựa chọn không có ID. Sau đây là một ví dụ về cách cập nhật một +sự kiện. Nó thay đổi tiêu đề của sự kiện bằng cách sử dụng phương pháp +{@link android.content.ContentUris#withAppendedId(android.net.Uri,long) withAppendedId()} +:

+ + +
private static final String DEBUG_TAG = "MyActivity";
+...
+long eventID = 188;
+...
+ContentResolver cr = getContentResolver();
+ContentValues values = new ContentValues();
+Uri updateUri = null;
+// The new title for the event
+values.put(Events.TITLE, "Kickboxing"); 
+updateUri = ContentUris.withAppendedId(Events.CONTENT_URI, eventID);
+int rows = getContentResolver().update(updateUri, values, null, null);
+Log.i(DEBUG_TAG, "Rows updated: " + rows);  
+ +

Xóa Sự kiện

+ +

Bạn có thể xóa một sự kiện hoặc theo {@link +android.provider.BaseColumns#_ID} của nó như một ID được nối trên URI, hoặc bằng cách sử dụng +lựa chọn tiêu chuẩn. Nếu sử dụng một ID được nối, bạn không thể lựa chọn đồng thời. +Có hai kiểu xóa: như một ứng dụng và như một trình điều hợp đồng bộ. Xóa +như một ứng dụng sẽ đặt cột đã xóa thành 1. Cờ này sẽ báo cho +trình điều hợp đồng bộ rằng hàng đó đã được xóa và rằng việc xóa này nên +được truyền tới máy chủ. Xóa trình điều hợp đồng bộ sẽ gỡ bỏ sự kiện khỏi +cơ sở dữ liệu cùng với tất cả dữ liệu được liên kết của nó. Sau đây là một ví dụ về ứng dụng +xóa một sự kiện thông qua {@link android.provider.BaseColumns#_ID} của nó:

+ + +
private static final String DEBUG_TAG = "MyActivity";
+...
+long eventID = 201;
+...
+ContentResolver cr = getContentResolver();
+ContentValues values = new ContentValues();
+Uri deleteUri = null;
+deleteUri = ContentUris.withAppendedId(Events.CONTENT_URI, eventID);
+int rows = getContentResolver().delete(deleteUri, null, null);
+Log.i(DEBUG_TAG, "Rows deleted: " + rows);  
+
+ +

Bảng Người dự

+ +

Mỗi hàng của bảng {@link android.provider.CalendarContract.Attendees} đại diện +cho một người dự hoặc khách duy nhất của một sự kiện. Gọi +{@link android.provider.CalendarContract.Reminders#query(android.content.ContentResolver, long, java.lang.String[]) query()} +sẽ trả về một danh sách người dự cho sự kiện +với {@link android.provider.CalendarContract.AttendeesColumns#EVENT_ID} đã cho. +{@link android.provider.CalendarContract.AttendeesColumns#EVENT_ID} này +phải khớp với {@link +android.provider.BaseColumns#_ID} của một sự kiện cụ thể.

+ +

Bảng sau liệt kê các trường +có thể ghi được. Khi chèn một người dự mới, bạn phải điền tất cả +ngoại trừ ATTENDEE_NAME. +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Hằng sốMô tả
{@link android.provider.CalendarContract.AttendeesColumns#EVENT_ID}ID của sự kiện.
{@link android.provider.CalendarContract.AttendeesColumns#ATTENDEE_NAME}Tên của người dự.
{@link android.provider.CalendarContract.AttendeesColumns#ATTENDEE_EMAIL}Địa chỉ e-mail của người dự.
{@link android.provider.CalendarContract.AttendeesColumns#ATTENDEE_RELATIONSHIP}

Mối quan hệ của người dự với sự kiện. Một trong:

+
    +
  • {@link android.provider.CalendarContract.AttendeesColumns#RELATIONSHIP_ATTENDEE}
  • +
  • {@link android.provider.CalendarContract.AttendeesColumns#RELATIONSHIP_NONE}
  • +
  • {@link android.provider.CalendarContract.AttendeesColumns#RELATIONSHIP_ORGANIZER}
  • +
  • {@link android.provider.CalendarContract.AttendeesColumns#RELATIONSHIP_PERFORMER}
  • +
  • {@link android.provider.CalendarContract.AttendeesColumns#RELATIONSHIP_SPEAKER}
  • +
+
{@link android.provider.CalendarContract.AttendeesColumns#ATTENDEE_TYPE}

Loại người dự. Một trong:

+
    +
  • {@link android.provider.CalendarContract.AttendeesColumns#TYPE_REQUIRED}
  • +
  • {@link android.provider.CalendarContract.AttendeesColumns#TYPE_OPTIONAL}
  • +
{@link android.provider.CalendarContract.AttendeesColumns#ATTENDEE_STATUS}

Trạng thái tham dự của người dự. Một trong:

+
    +
  • {@link android.provider.CalendarContract.AttendeesColumns#ATTENDEE_STATUS_ACCEPTED}
  • +
  • {@link android.provider.CalendarContract.AttendeesColumns#ATTENDEE_STATUS_DECLINED}
  • +
  • {@link android.provider.CalendarContract.AttendeesColumns#ATTENDEE_STATUS_INVITED}
  • +
  • {@link android.provider.CalendarContract.AttendeesColumns#ATTENDEE_STATUS_NONE}
  • +
  • {@link android.provider.CalendarContract.AttendeesColumns#ATTENDEE_STATUS_TENTATIVE}
  • +
+ +

Thêm Người dự

+ +

Sau đây là một ví dụ về cách thêm một người dự vào một sự kiện. Lưu ý rằng +{@link android.provider.CalendarContract.AttendeesColumns#EVENT_ID} +là bắt buộc:

+ +
+long eventID = 202;
+...
+ContentResolver cr = getContentResolver();
+ContentValues values = new ContentValues();
+values.put(Attendees.ATTENDEE_NAME, "Trevor");
+values.put(Attendees.ATTENDEE_EMAIL, "trevor@example.com");
+values.put(Attendees.ATTENDEE_RELATIONSHIP, Attendees.RELATIONSHIP_ATTENDEE);
+values.put(Attendees.ATTENDEE_TYPE, Attendees.TYPE_OPTIONAL);
+values.put(Attendees.ATTENDEE_STATUS, Attendees.ATTENDEE_STATUS_INVITED);
+values.put(Attendees.EVENT_ID, eventID);
+Uri uri = cr.insert(Attendees.CONTENT_URI, values);
+
+ +

Bảng Nhắc nhở

+ +

Mỗi hàng của bảng {@link android.provider.CalendarContract.Reminders} đại diện +cho một nhắc nhở của một sự kiện. Gọi +{@link android.provider.CalendarContract.Reminders#query(android.content.ContentResolver, long, java.lang.String[]) query()} sẽ trả về một danh sách nhắc nhở cho +sự kiện với +{@link android.provider.CalendarContract.AttendeesColumns#EVENT_ID} đã cho.

+ + +

Bảng sau liệt kê các trường ghi được đối với nhắc nhở. Tất cả đều phải được +đưa vào khi chèn một nhắc nhở mới. Lưu ý rằng các trình điều hợp đồng bộ quy định +các loại nhắc nhở chúng hỗ trợ trong bảng {@link +android.provider.CalendarContract.Calendars}. Xem +{@link android.provider.CalendarContract.CalendarColumns#ALLOWED_REMINDERS} +để biết chi tiết.

+ + + + + + + + + + + + + + + + + + + +
Hằng sốMô tả
{@link android.provider.CalendarContract.RemindersColumns#EVENT_ID}ID của sự kiện.
{@link android.provider.CalendarContract.RemindersColumns#MINUTES}Số phút trước khi diễn ra sự kiện mà nhắc nhở cần báo.
{@link android.provider.CalendarContract.RemindersColumns#METHOD}

Phương pháp báo thức, như được đặt trên máy chủ. Một trong:

+
    +
  • {@link android.provider.CalendarContract.RemindersColumns#METHOD_ALERT}
  • +
  • {@link android.provider.CalendarContract.RemindersColumns#METHOD_DEFAULT}
  • +
  • {@link android.provider.CalendarContract.RemindersColumns#METHOD_EMAIL}
  • +
  • {@link android.provider.CalendarContract.RemindersColumns#METHOD_SMS}
  • +
+ +

Thêm Nhắc nhở

+ +

Ví dụ này thêm nhắc nhở vào một sự kiện. Nhắc nhở sẽ báo 15 +phút trước khi xảy ra sự kiện.

+
+long eventID = 221;
+...
+ContentResolver cr = getContentResolver();
+ContentValues values = new ContentValues();
+values.put(Reminders.MINUTES, 15);
+values.put(Reminders.EVENT_ID, eventID);
+values.put(Reminders.METHOD, Reminders.METHOD_ALERT);
+Uri uri = cr.insert(Reminders.CONTENT_URI, values);
+ +

Bảng Thực thể

+ +

Bảng +{@link android.provider.CalendarContract.Instances} chứa +thời gian bắt đầu và thời gian kết thúc của các lần xảy ra một sự kiện. Mỗi hàng trong bảng này +đại diện cho một lần xảy ra sự kiện. Bảng thực thể không ghi được và chỉ +đưa ra một cách để truy vấn các lần xảy ra sự kiện.

+ +

Bảng sau liệt kê một số trường mà bạn có thể truy vấn đối với một thực thể. Lưu ý +rằng múi giờ được định nghĩa bởi +{@link android.provider.CalendarContract.CalendarCache#KEY_TIMEZONE_TYPE} +và +{@link android.provider.CalendarContract.CalendarCache#KEY_TIMEZONE_INSTANCES}.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Hằng sốMô tả
{@link android.provider.CalendarContract.Instances#BEGIN}Thời gian bắt đầu của thực thể, tính bằng mili giây UTC.
{@link android.provider.CalendarContract.Instances#END}Thời gian kết thúc của thực thể, tính bằng mili giây UTC.
{@link android.provider.CalendarContract.Instances#END_DAY}Ngày kết thúc theo lịch Julian của thực thể theo múi giờ +của Lịch. + +
{@link android.provider.CalendarContract.Instances#END_MINUTE}Phút kết thúc của thực thể được xác định từ nửa đêm theo múi giờ +của Lịch.
{@link android.provider.CalendarContract.Instances#EVENT_ID}_ID của sự kiện đối với thực thể này.
{@link android.provider.CalendarContract.Instances#START_DAY}Ngày bắt đầu theo lịch Julian của thực thể theo múi giờ của Lịch. +
{@link android.provider.CalendarContract.Instances#START_MINUTE}Phút bắt đầu của thực thể được xác định từ nửa đêm theo múi giờ +của Lịch. +
+ +

Truy vấn bảng Thực thể

+ +

Để truy vấn bảng Thực thể, bạn cần chỉ định một khoảng thời gian cho truy vấn +trong URI. Trong ví dụ này, {@link android.provider.CalendarContract.Instances} +có quyền truy cập trường {@link +android.provider.CalendarContract.EventsColumns#TITLE} thông qua việc +triển khai giao diện {@link android.provider.CalendarContract.EventsColumns} của nó. +Nói cách khác, {@link +android.provider.CalendarContract.EventsColumns#TITLE} được trả về qua một +chế độ xem cơ sở dữ liệu, chứ không qua việc truy vấn bảng {@link +android.provider.CalendarContract.Instances} thô.

+ +
+private static final String DEBUG_TAG = "MyActivity";
+public static final String[] INSTANCE_PROJECTION = new String[] {
+    Instances.EVENT_ID,      // 0
+    Instances.BEGIN,         // 1
+    Instances.TITLE          // 2
+  };
+  
+// The indices for the projection array above.
+private static final int PROJECTION_ID_INDEX = 0;
+private static final int PROJECTION_BEGIN_INDEX = 1;
+private static final int PROJECTION_TITLE_INDEX = 2;
+...
+
+// Specify the date range you want to search for recurring
+// event instances
+Calendar beginTime = Calendar.getInstance();
+beginTime.set(2011, 9, 23, 8, 0);
+long startMillis = beginTime.getTimeInMillis();
+Calendar endTime = Calendar.getInstance();
+endTime.set(2011, 10, 24, 8, 0);
+long endMillis = endTime.getTimeInMillis();
+  
+Cursor cur = null;
+ContentResolver cr = getContentResolver();
+
+// The ID of the recurring event whose instances you are searching
+// for in the Instances table
+String selection = Instances.EVENT_ID + " = ?";
+String[] selectionArgs = new String[] {"207"};
+
+// Construct the query with the desired date range.
+Uri.Builder builder = Instances.CONTENT_URI.buildUpon();
+ContentUris.appendId(builder, startMillis);
+ContentUris.appendId(builder, endMillis);
+
+// Submit the query
+cur =  cr.query(builder.build(), 
+    INSTANCE_PROJECTION, 
+    selection, 
+    selectionArgs, 
+    null);
+   
+while (cur.moveToNext()) {
+    String title = null;
+    long eventID = 0;
+    long beginVal = 0;    
+    
+    // Get the field values
+    eventID = cur.getLong(PROJECTION_ID_INDEX);
+    beginVal = cur.getLong(PROJECTION_BEGIN_INDEX);
+    title = cur.getString(PROJECTION_TITLE_INDEX);
+              
+    // Do something with the values. 
+    Log.i(DEBUG_TAG, "Event:  " + title); 
+    Calendar calendar = Calendar.getInstance();
+    calendar.setTimeInMillis(beginVal);  
+    DateFormat formatter = new SimpleDateFormat("MM/dd/yyyy");
+    Log.i(DEBUG_TAG, "Date: " + formatter.format(calendar.getTime()));    
+    }
+ }
+ +

Ý định Lịch

+

Ứng dụng của bạn không cần quyền để ghi và đọc dữ liệu lịch. Thay vào đó, nó có thể sử dụng những ý định được cung cấp bởi ứng dụng Lịch của Android để chuyển giao các thao tác đọc và ghi cho ứng dụng đó. Bảng sau đây liệt kê các ý định được Trình cung cấp Lịch hỗ trợ:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Hành độngURIMô tảPhụ thêm

+ {@link android.content.Intent#ACTION_VIEW VIEW}

content://com.android.calendar/time/<ms_since_epoch>

+ Bạn cũng có thể tham khảo tới URI bằng +{@link android.provider.CalendarContract#CONTENT_URI CalendarContract.CONTENT_URI}. +Để xem một ví dụ về cách sử dụng ý định này, hãy xem Sử dụng ý định để xem dữ liệu lịch. + +
Mở lịch đến thời gian được chỉ định bởi <ms_since_epoch>.Không có.

{@link android.content.Intent#ACTION_VIEW VIEW}

+ +

content://com.android.calendar/events/<event_id>

+ + Bạn cũng có thể tham khảo tới URI bằng +{@link android.provider.CalendarContract.Events#CONTENT_URI Events.CONTENT_URI}. +Để xem một ví dụ về cách sử dụng ý định này, hãy xem Sử dụng ý định để xem dữ liệu lịch. + +
Xem sự kiện được chỉ định bởi <event_id>.{@link android.provider.CalendarContract#EXTRA_EVENT_BEGIN_TIME CalendarContract.EXTRA_EVENT_BEGIN_TIME}
+
+
+ {@link android.provider.CalendarContract#EXTRA_EVENT_END_TIME CalendarContract.EXTRA_EVENT_END_TIME}
{@link android.content.Intent#ACTION_EDIT EDIT}

content://com.android.calendar/events/<event_id>

+ + Bạn cũng có thể tham khảo tới URI bằng +{@link android.provider.CalendarContract.Events#CONTENT_URI Events.CONTENT_URI}. +Để xem một ví dụ về cách sử dụng ý định này, hãy xem Sử dụng ý định để chỉnh sửa một sự kiện. + + +
Chỉnh sửa sự kiện được chỉ định bởi <event_id>.{@link android.provider.CalendarContract#EXTRA_EVENT_BEGIN_TIME CalendarContract.EXTRA_EVENT_BEGIN_TIME}
+
+
+ {@link android.provider.CalendarContract#EXTRA_EVENT_END_TIME CalendarContract.EXTRA_EVENT_END_TIME}
{@link android.content.Intent#ACTION_EDIT EDIT}
+
+ {@link android.content.Intent#ACTION_INSERT INSERT}

content://com.android.calendar/events

+ + Bạn cũng có thể tham khảo tới URI bằng +{@link android.provider.CalendarContract.Events#CONTENT_URI Events.CONTENT_URI}. +Để xem một ví dụ về cách sử dụng ý định này, hãy xem Sử dụng ý định để chèn một sự kiện. + +
Tạo một sự kiện.Bất kỳ phụ thêm nào được liệt kê trong bảng bên dưới.
+ +

Bảng sau đây liệt kê các phụ thêm ý định được Trình cung cấp Lịch hỗ trợ: +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Phụ thêm Ý địnhMô tả
{@link android.provider.CalendarContract.EventsColumns#TITLE Events.TITLE}Tên cho sự kiện.
{@link android.provider.CalendarContract#EXTRA_EVENT_BEGIN_TIME +CalendarContract.EXTRA_EVENT_BEGIN_TIME}Thời gian bắt đầu sự kiện tính bằng mili giây trôi qua kể từ giờ epoch.
{@link android.provider.CalendarContract#EXTRA_EVENT_END_TIME +CalendarContract.EXTRA_EVENT_END_TIME}Thời gian kết thúc sự kiện tính bằng mili giây trôi qua kể từ giờ epoch.
{@link android.provider.CalendarContract#EXTRA_EVENT_ALL_DAY +CalendarContract.EXTRA_EVENT_ALL_DAY}Một boolean cho biết đó là một sự kiện cả ngày. Giá trị có thể bằng +true hoặc false.
{@link android.provider.CalendarContract.EventsColumns#EVENT_LOCATION +Events.EVENT_LOCATION}Địa điểm của sự kiện.
{@link android.provider.CalendarContract.EventsColumns#DESCRIPTION +Events.DESCRIPTION}Mô tả sự kiện.
+ {@link android.content.Intent#EXTRA_EMAIL Intent.EXTRA_EMAIL}Địa chỉ e-mail của những người cần mời dưới dạng một danh sách phân cách bởi dấu phẩy.
+ {@link android.provider.CalendarContract.EventsColumns#RRULE Events.RRULE}Quy tắc lặp lại đối với sự kiện.
+ {@link android.provider.CalendarContract.EventsColumns#ACCESS_LEVEL +Events.ACCESS_LEVEL}Sự kiện là riêng tư hay công khai.
{@link android.provider.CalendarContract.EventsColumns#AVAILABILITY +Events.AVAILABILITY}Xem sự kiện này tính là thời gian bận hay là thời gian rảnh có thể được xếp lại lịch.
+

Các phần sau mô tả cách sử dụng những ý định này.

+ + +

Sử dụng ý định để chèn một sự kiện

+ +

Sử dụng Ý định {@link android.content.Intent#ACTION_INSERT INSERT} cho phép +ứng dụng của bạn chuyển giao tác vụ chèn sự kiện cho bản thân Lịch. +Bằng cách này, ứng dụng của bạn thậm chí không cần phải có quyền {@link +android.Manifest.permission#WRITE_CALENDAR} được bao gồm trong tệp bản kê khai của mình.

+ + +

Khi người dùng chạy một ứng dụng mà sử dụng cách này, ứng dụng sẽ gửi +chúng tới Lịch để hoàn thành việc thêm một sự kiện. Ý định {@link +android.content.Intent#ACTION_INSERT INSERT} sử dụng các trường phụ thêm để +điền trước vào một mẫu bằng các chi tiết của sự kiện trong Lịch. Khi đó, người dùng có thể +hủy bỏ sự kiện, chỉnh sửa mẫu nếu cần, hoặc lưu sự kiện vào lịch +của mình.

+ + + +

Sau đây là một đoạn mã HTML lập biểu một sự kiện vào ngày 19/1/2012, diễn ra +từ 7:30 sáng đến 8:30 sáng. Lưu ý điều sau đây về đoạn mã HTML này:

+ +
    +
  • Nó quy định {@link android.provider.CalendarContract.Events#CONTENT_URI Events.CONTENT_URI} + là Uri.
  • + +
  • Nó sử dụng các trường phụ {@link +android.provider.CalendarContract#EXTRA_EVENT_BEGIN_TIME +CalendarContract.EXTRA_EVENT_BEGIN_TIME} và {@link +android.provider.CalendarContract#EXTRA_EVENT_END_TIME +CalendarContract.EXTRA_EVENT_END_TIME} để điền trước thời gian của sự kiện +vào mẫu. Các giá trị đối với những thời gian này phải tính bằng mili giây UTC +trôi qua kể từ giờ epoch.
  • + +
  • Nó sử dụng trường phụ {@link android.content.Intent#EXTRA_EMAIL Intent.EXTRA_EMAIL} +để cung cấp một danh sách người được mời phân cách bằng dấu phẩy, được chỉ định theo địa chỉ e-mail.
  • + +
+
+Calendar beginTime = Calendar.getInstance();
+beginTime.set(2012, 0, 19, 7, 30);
+Calendar endTime = Calendar.getInstance();
+endTime.set(2012, 0, 19, 8, 30);
+Intent intent = new Intent(Intent.ACTION_INSERT)
+        .setData(Events.CONTENT_URI)
+        .putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, beginTime.getTimeInMillis())
+        .putExtra(CalendarContract.EXTRA_EVENT_END_TIME, endTime.getTimeInMillis())
+        .putExtra(Events.TITLE, "Yoga")
+        .putExtra(Events.DESCRIPTION, "Group class")
+        .putExtra(Events.EVENT_LOCATION, "The gym")
+        .putExtra(Events.AVAILABILITY, Events.AVAILABILITY_BUSY)
+        .putExtra(Intent.EXTRA_EMAIL, "rowan@example.com,trevor@example.com");
+startActivity(intent);
+
+ +

Sử dụng ý định để chỉnh sửa một sự kiện

+ +

Bạn có thể cập nhật một sự kiện trực tiếp như được mô tả trong phần Cập nhật sự kiện. Nhưng việc sử dụng Ý định {@link +android.content.Intent#ACTION_EDIT EDIT} cho phép một ứng dụng +không có quyền được chuyển giao chỉnh sửa sự kiện cho ứng dụng Lịch. +Khi người dùng hoàn thành chỉnh sửa sự kiện của mình trong Lịch, họ được trả về ứng dụng +ban đầu.

Sau đây là một ví dụ về ý định đặt một tiêu đề mới +cho một sự kiện được quy định và cho phép người dùng chỉnh sửa sự kiện trong Lịch.

+ + +
long eventID = 208;
+Uri uri = ContentUris.withAppendedId(Events.CONTENT_URI, eventID);
+Intent intent = new Intent(Intent.ACTION_EDIT)
+    .setData(uri)
+    .putExtra(Events.TITLE, "My New Title");
+startActivity(intent);
+ +

Sử dụng ý định để xem dữ liệu lịch

+

Trình cung cấp Lịch giới thiệu hai cách khác nhau để sử dụng Ý định {@link android.content.Intent#ACTION_VIEW VIEW}:

+
    +
  • Để mở Lịch tới một ngày cụ thể.
  • +
  • Để xem một sự kiện.
  • + +
+

Sau đây là một ví dụ cho biết cách mở Lịch tới một ngày cụ thể:

+
// A date-time specified in milliseconds since the epoch.
+long startMillis;
+...
+Uri.Builder builder = CalendarContract.CONTENT_URI.buildUpon();
+builder.appendPath("time");
+ContentUris.appendId(builder, startMillis);
+Intent intent = new Intent(Intent.ACTION_VIEW)
+    .setData(builder.build());
+startActivity(intent);
+ +

Sau đây là một ví dụ cho biết cách mở một sự kiện để xem:

+
long eventID = 208;
+...
+Uri uri = ContentUris.withAppendedId(Events.CONTENT_URI, eventID);
+Intent intent = new Intent(Intent.ACTION_VIEW)
+   .setData(uri);
+startActivity(intent);
+
+ + +

Trình điều hợp Đồng bộ

+ + +

Chỉ có vài điểm khác nhau nhỏ giữa cách một ứng dụng và cách một trình điều hợp đồng bộ +truy cập Trình cung cấp Lịch:

+ +
    +
  • Trình điều hợp đồng bộ cần chỉ định rằng nó là một trình điều hợp đồng bộ bằng cách đặt {@link android.provider.CalendarContract#CALLER_IS_SYNCADAPTER} thành true.
  • + + +
  • Trình điều hợp đồng bộ cần cung cấp một {@link +android.provider.CalendarContract.SyncColumns#ACCOUNT_NAME} và một {@link +android.provider.CalendarContract.SyncColumns#ACCOUNT_TYPE} làm tham số truy vấn trong URI.
  • + +
  • Trình điều hợp đồng bộ có quyền truy nhập ghi vào nhiều cột hơn ứng dụng hay widget. + Ví dụ, một ứng dụng chỉ có thể sửa đổi một vài đặc điểm của một lịch, + chẳng hạn như tên lịch, tên hiển thị, thiết đặt hiển thị, và lịch có được + đồng bộ hay không. Nếu so sánh, một trình điều hợp đồng bộ có thể truy cập không chỉ những cột đó, mà còn nhiều cột khác, + chẳng hạn như màu lịch, múi giờ, mức truy nhập, địa điểm, v.v. +Tuy nhiên, trình điều hợp đồng bộ bị hạn chế đối với ACCOUNT_NAME và +ACCOUNT_TYPE mà nó quy định.
+ +

Sau đây là một phương pháp hữu ích hơn mà bạn có thể sử dụng để trả về một URI để dùng với một trình điều hợp đồng bộ:

+
 static Uri asSyncAdapter(Uri uri, String account, String accountType) {
+    return uri.buildUpon()
+        .appendQueryParameter(android.provider.CalendarContract.CALLER_IS_SYNCADAPTER,"true")
+        .appendQueryParameter(Calendars.ACCOUNT_NAME, account)
+        .appendQueryParameter(Calendars.ACCOUNT_TYPE, accountType).build();
+ }
+
+

Để biết việc triển khai mẫu trình điều hợp đồng bộ (không liên quan cụ thể tới Lịch), hãy xem phần +SampleSyncAdapter. diff --git a/docs/html-intl/intl/vi/guide/topics/providers/contacts-provider.jd b/docs/html-intl/intl/vi/guide/topics/providers/contacts-provider.jd new file mode 100644 index 0000000000000000000000000000000000000000..2fa2ed3c9bdf9ecc1e500828d93d15b032d7b45b --- /dev/null +++ b/docs/html-intl/intl/vi/guide/topics/providers/contacts-provider.jd @@ -0,0 +1,2356 @@ +page.title=Trình cung cấp Danh bạ +@jd:body +

+
+

Xem nhanh

+
    +
  • Kho lưu giữ thông tin về con người của Android.
  • +
  • + Đồng bộ với web. +
  • +
  • + Tích hợp dữ liệu theo luồng xã hội. +
  • +
+

Trong tài liệu này

+
    +
  1. + Tổ chức Trình cung cấp Danh bạ +
  2. +
  3. + Liên lạc thô +
  4. +
  5. + Dữ liệu +
  6. +
  7. + Danh bạ +
  8. +
  9. + Dữ liệu từ Trình điều hợp Đồng bộ +
  10. +
  11. + Quyền được Yêu cầu +
  12. +
  13. + Hồ sơ Người dùng +
  14. +
  15. + Siêu dữ liệu Trình cung cấp Danh bạ +
  16. +
  17. + Truy cập Trình cung cấp Danh bạ +
  18. +
  19. +
  20. + Trình điều hợp Đồng bộ Trình cung cấp Danh bạ +
  21. +
  22. + Dữ liệu từ Luồng Xã hội +
  23. +
  24. + Các Tính năng Bổ sung của Trình cung cấp Danh bạ +
  25. +
+

Lớp khóa

+
    +
  1. {@link android.provider.ContactsContract.Contacts}
  2. +
  3. {@link android.provider.ContactsContract.RawContacts}
  4. +
  5. {@link android.provider.ContactsContract.Data}
  6. +
  7. {@code android.provider.ContactsContract.StreamItems}
  8. +
+

Các Mẫu Liên quan

+
    +
  1. + + Trình quản lý Danh bạ + +
  2. +
  3. + + Trình điều hợp Đồng bộ Mẫu +
  4. +
+

Xem thêm

+
    +
  1. + + Nội dung Cơ bản về Trình cung cấp Nội dung + +
  2. +
+
+
+

+ Trình cung cấp Danh bạ là một thành phần Android mạnh mẽ và linh hoạt có chức năng quản lý + kho dữ liệu trung tâm về con người của thiết bị. Trình cung cấp Danh bạ là nguồn của những dữ liệu + mà bạn thấy trong ứng dụng danh bạ của thiết bị, và bạn cũng có thể truy cập dữ liệu của nó trong + ứng dụng của chính mình và chuyển dữ liệu giữa thiết bị và các dịch vụ trực tuyến. Trình cung cấp chứa + đủ loại nguồn dữ liệu và cố gắng quản lý nhiều dữ liệu nhất có thể cho mỗi người, kết quả + là tổ chức của nó trở nên phức tạp. Vì điều này, API của trình cung cấp bao gồm một + tập mở rộng gồm các lớp hợp đồng và giao diện tạo điều kiện cho việc truy xuất và sửa đổi + dữ liệu. +

+

+ Hướng dẫn này trình bày những nội dung sau: +

+
    +
  • + Cấu trúc cơ bản của trình cung cấp. +
  • +
  • + Cách truy xuất dữ liệu từ trình cung cấp. +
  • +
  • + Cách sửa đổi dữ liệu trong trình cung cấp. +
  • +
  • + Cách ghi một trình điều hợp đồng bộ để đồng bộ hóa dữ liệu từ máy chủ của bạn với + Trình cung cấp Danh bạ. +
  • +
+

+ Hướng dẫn này giả sử rằng bạn biết những nội dung cơ bản về trình cung cấp nội dung Android. Để tìm hiểu thêm + về trình cung cấp nội dung Android, hãy đọc hướng dẫn + + Nội dung Cơ bản về Trình cung cấp Nội dung. Ứng dụng mẫu + Trình điều hợp Đồng bộ Mẫu + là một ví dụ về cách sử dụng một trình điều hợp đồng bộ để chuyển dữ liệu giữa Trình cung cấp + Danh bạ và ứng dụng mẫu được lưu trữ bởi Dịch vụ Web Google. +

+

Tổ chức Trình cung cấp Danh bạ

+

+ Trình cung cấp Danh bạ là một thành phần của trình cung cấp nội dung Android. Nó chứa ba loại + dữ liệu về một người, từng loại tương ứng với một bảng do trình cung cấp đưa ra, như + được minh họa trong hình 1: +

+ +

+ Hình 1. Cấu trúc bảng của Trình cung cấp Danh bạ. +

+

+ Ba bảng này thường được đề cập theo tên các lớp hợp đồng của chúng. Các lớp này + sẽ định nghĩa các hằng số cho URI nội dung, tên cột và giá trị cột được sử dụng bởi các bảng: +

+
+
+ Bảng {@link android.provider.ContactsContract.Contacts} +
+
+ Hàng thể hiện những người khác nhau dựa trên tổng hợp của các hàng liên lạc thô. +
+
+ Bảng {@link android.provider.ContactsContract.RawContacts} +
+
+ Hàng chứa một bản tổng hợp dữ liệu về một người, liên quan tới tài khoản và loại người dùng. +
+
+ Bảng {@link android.provider.ContactsContract.Data} +
+
+ Hàng chứa các thông tin chi tiết về liên lạc thô, chẳng hạn như địa chỉ e-mail hoặc số điện thoại. +
+
+

+ Các bảng khác được đại diện bởi các lớp hợp đồng trong {@link android.provider.ContactsContract} + là bảng phụ mà Trình cung cấp Danh bạ sử dụng để quản lý thao tác của nó hoặc hỗ trợ + các chức năng cụ thể trong ứng dụng danh bạ hoặc điện thoại của thiết bị. +

+

Liên lạc thô

+

+ Một liên lạc thô thể hiện dữ liệu của một người xuất phát từ một loại tài khoản và tên tài khoản + riêng. Vì Trình cung cấp Danh bạ cho phép nhiều hơn một dịch vụ trực tuyến làm nguồn + dữ liệu cho một người, Trình cung cấp Danh bạ cho phép nhiều liên lạc thô cho cùng một người. + Nhiều liên lạc thô cũng cho phép người dùng kết hợp dữ liệu của một người từ nhiều hơn một tài khoản + từ cùng loại tài khoản. +

+

+ Hầu hết dữ liệu của một liên lạc thô không được lưu giữ trong bảng + {@link android.provider.ContactsContract.RawContacts}. Thay vào đó, nó được lưu giữ trong một hoặc nhiều + hàng trong bảng {@link android.provider.ContactsContract.Data}. Mỗi hàng dữ liệu có một cột + {@link android.provider.ContactsContract.DataColumns#RAW_CONTACT_ID Data.RAW_CONTACT_ID} chứa + giá trị {@code android.provider.BaseColumns#_ID RawContacts._ID} của + hàng {@link android.provider.ContactsContract.RawContacts} mẹ của nó. +

+

Các cột liên lạc thô quan trọng

+

+ Các cột quan trọng trong bảng {@link android.provider.ContactsContract.RawContacts} được + liệt kê trong bảng 1. Hãy đọc các lưu ý theo sau bảng dưới đây: +

+

+ Bảng 1. Các cột liên lạc thô quan trọng. +

+ + + + + + + + + + + + + + + + + + + + +
Tên cộtSử dụngLưu ý
+ {@link android.provider.ContactsContract.SyncColumns#ACCOUNT_NAME} + + Tên tài khoản cho loại tài khoản là nguồn của liên lạc thô này. + Ví dụ, tên tài khoản của một tài khoản Google là một trong các địa chỉ Gmail + của chủ sở hữu thiết bị. Xem mục nhập tiếp theo cho + {@link android.provider.ContactsContract.SyncColumns#ACCOUNT_TYPE} để biết thêm + thông tin. + + Định dạng của tên này áp dụng theo loại tài khoản của nó. Đó không nhất thiết + là một địa chỉ e-mail. +
+ {@link android.provider.ContactsContract.SyncColumns#ACCOUNT_TYPE} + + Loại tài khoản là nguồn của liên lạc thô này. Ví dụ, loại tài khoản + của một tài khoản Google là com.google. Luôn xác định loại tài khoản của bạn + bằng một mã định danh miền cho một miền mà bạn sở hữu hoặc kiểm soát. Điều này đảm bảo rằng loại tài khoản + của bạn là duy nhất. + + Một loại tài khoản cung cấp dữ liệu danh bạ thường có một trình điều hợp đồng bộ liên kết để + đồng bộ hoá với Trình điều hợp Đồng bộ. +
+ {@link android.provider.ContactsContract.RawContactsColumns#DELETED} + + Cờ "đã xóa" cho một liên lạc thô. + + Cờ này cho phép Trình cung cấp Danh bạ duy trì hàng bên trong tới khi trình điều hợp đồng bộ + có thể xóa hàng đó khỏi máy chủ của chúng rồi cuối cùng là xóa hàng + khỏi kho lưu giữ. +
+

Lưu ý

+

+ Sau đây là các ghi chú quan trọng về bảng + {@link android.provider.ContactsContract.RawContacts}: +

+
    +
  • + Tên của liên lạc thô không được lưu giữ trong hàng của nó trong + {@link android.provider.ContactsContract.RawContacts}. Thay vào đó, nó được lưu giữ trong + bảng {@link android.provider.ContactsContract.Data}, trong một hàng + {@link android.provider.ContactsContract.CommonDataKinds.StructuredName}. Liên lạc thô + chỉ có một hàng thuộc loại này trong bảng {@link android.provider.ContactsContract.Data}. +
  • +
  • + Chú ý: Để sử dụng dữ liệu tài khoản của chính bạn trong một hàng liên lạc thô, trước tiên dữ liệu + phải được đăng ký với {@link android.accounts.AccountManager}. Để làm điều này, hãy nhắc + người dùng thêm loại tài khoản và tên tài khoản của chúng vào danh sách tài khoản. Nếu bạn không + làm vậy, Trình cung cấp Danh bạ sẽ tự động xóa hàng liên lạc thô của bạn. +

    + Ví dụ, nếu bạn muốn ứng dụng của mình duy trì dữ liệu danh bạ cho dịch vụ dựa trên nền web của mình + với miền {@code com.example.dataservice}, và tài khoản của người dùng cho dịch vụ của bạn + là {@code becky.sharp@dataservice.example.com}, trước tiên, người dùng phải thêm + "loại" tài khoản ({@code com.example.dataservice}) và "tên" tài khoản + ({@code becky.smart@dataservice.example.com}) trước khi ứng dụng của bạn có thể thêm hàng liên lạc thô. + Bạn có thể giải thích yêu cầu này với người dùng bằng tài liệu, hoặc bạn có thể nhắc + người dùng thêm loại và tên này, hoặc cả hai. Loại tài khoản và tên tài khoản + được trình bày chi tiết hơn trong phần sau. +

  • +
+

Các nguồn dữ liệu liên lạc thô

+

+ Để hiểu cách hoạt động của liên lạc thô, hãy xét người dùng "Emily Dickinson", cô ta có ba tài khoản + người dùng sau được xác định trên thiết bị của mình: +

+
    +
  • emily.dickinson@gmail.com
  • +
  • emilyd@gmail.com
  • +
  • Tài khoản Twitter "belle_of_amherst"
  • +
+

+ Người dùng này đã kích hoạt Đồng bộ Danh bạ cho cả ba tài khoản này trong cài đặt + Tài khoản. +

+

+ Giả sử Emily Dickinson mở một cửa sổ trình duyệt, đăng nhập vào Gmail bằng tài khoản + emily.dickinson@gmail.com, mở + Danh bạ, và thêm "Thomas Higginson". Sau đó, cô đăng nhập vào Gmail bằng tài khoản + emilyd@gmail.com và gửi một e-mail tới "Thomas Higginson", làm vậy sẽ tự động + thêm người này làm một liên lạc. Cô ấy cũng theo dõi "colonel_tom" (ID Twitter của Thomas Higginson) trên + Twitter. +

+

+ Trình cung cấp Danh bạ sẽ tạo ba liên lạc thô do kết quả của việc làm này: +

+
    +
  1. + Một liên lạc thô cho "Thomas Higginson" liên kết với emily.dickinson@gmail.com. + Loại tài khoản người dùng là Google. +
  2. +
  3. + Một liên lạc thô thứ hai cho "Thomas Higginson" liên kết với emilyd@gmail.com. + Loại tài khoản người dùng cũng là Google. Có một liên lạc thô thứ hai ngay cả khi + tên giống với một tên trước đó, vì người này đã được thêm cho một + tài khoản người dùng khác. +
  4. +
  5. + Một liên lạc thô thứ ba cho "Thomas Higginson" liên kết với "belle_of_amherst". Loại tài khoản người dùng + là Twitter. +
  6. +
+

Dữ liệu

+

+ Như đề cập trước đó, dữ liệu của một liên lạc thô được lưu giữ trong một hàng + {@link android.provider.ContactsContract.Data} được liên kết với giá trị + _ID của liên lạc thô. Điều này cho phép một liên lạc thô có nhiều thực thể cùng loại + dữ liệu chẳng hạn như địa chỉ e-mail hay số điện thoại. Ví dụ, nếu + "Thomas Higginson" của {@code emilyd@gmail.com} (hàng liên lạc thô cho Thomas Higginson + liên kết với tài khoản Google emilyd@gmail.com) có một địa chỉ e-mail nhà là + thigg@gmail.com và một địa chỉ e-mail cơ quan là + thomas.higginson@gmail.com, Trình cung cấp Danh bạ sẽ lưu trữ hai hàng địa chỉ e-mail đó + và liên kết cả hai với liên lạc thô. +

+

+ Để ý rằng các loại dữ liệu khác nhau được lưu giữ trong một bảng này. Các hàng tên hiển thị, + số điện thoại, e-mail, địa chỉ gửi thư, ảnh và chi tiết trang web đều được tìm thấy trong bảng + {@link android.provider.ContactsContract.Data}. Để giúp quản lý điều này, bảng + {@link android.provider.ContactsContract.Data} có một số cột có tên mô tả, + và các cột còn lại có tên chung. Các nội dung của cột tên mô tả có cùng ý nghĩa + không phụ thuộc vào loại dữ liệu trong hàng, trong khi nội dung của cột tên chung có + ý nghĩa khác nhau tùy vào loại dữ liệu. +

+

Tên cột mô tả

+

+ Một số ví dụ về tên cột mô tả là: +

+
+
+ {@link android.provider.ContactsContract.Data#RAW_CONTACT_ID} +
+
+ Giá trị của cột _ID của liên lạc thô đối với dữ liệu này. +
+
+ {@link android.provider.ContactsContract.Data#MIMETYPE} +
+
+ Loại dữ liệu được lưu giữ trong hàng này, được thể hiện dưới dạng một kiểu MIME tùy chỉnh. Trình cung cấp Danh bạ + sử dụng các kiểu MIME được định nghĩa trong lớp con của + {@link android.provider.ContactsContract.CommonDataKinds}. Các kiểu MIME là nguồn mở, + và có thể được sử dụng bởi bất kỳ ứng dụng hay trình điều hợp đồng bộ nào hoạt động với Trình cung cấp Danh bạ. +
+
+ {@link android.provider.ContactsContract.DataColumns#IS_PRIMARY} +
+
+ Nếu kiểu hàng dữ liệu này có thể xảy ra nhiều hơn một lần đối với một liên lạc thô, cột + {@link android.provider.ContactsContract.DataColumns#IS_PRIMARY} sẽ gắn cờ + hàng dữ liệu chứa dữ liệu sơ cấp cho kiểu đó. Ví dụ, nếu + người dùng nhấn giữ một số điện thoại cho một liên lạc và chọn Đặt mặc định, + khi đó hàng {@link android.provider.ContactsContract.Data} chứa số đó + có cột tương ứng {@link android.provider.ContactsContract.DataColumns#IS_PRIMARY} được đặt thành một + giá trị khác 0. +
+
+

Tên cột chung

+

+ Có 15 cột chung được đặt tên DATA1 thông qua + DATA15 thường có sẵn và thêm bốn cột + chung SYNC1 thông qua SYNC4 mà chỉ được sử dụng bởi trình điều hợp + đồng bộ. Các hằng số tên cột chung luôn có tác dụng, không phụ thuộc vào loại + dữ liệu mà hàng đó chứa. +

+

+ Cột DATA1 được đánh chỉ mục. Trình cung cấp Danh bạ luôn sử dụng cột này cho + dữ liệu mà trình cung cấp kỳ vọng sẽ là đối tượng truy vấn thường xuyên nhất. Ví dụ, + trong một hàng e-mail, cột này chứa địa chỉ e-mail thực sự. +

+

+ Theo quy ước, cột DATA15 được dành để lưu giữ dữ liệu Binary Large Object + (BLOB) chẳng hạn như hình thu nhỏ của ảnh. +

+

Tên cột theo kiểu

+

+ Để tạo điều kiện làm việc với các cột đối với một kiểu hàng cụ thể, Trình cung cấp Danh bạ + cũng cung cấp các hằng số tên cột theo kiểu, được định nghĩa trong các lớp con của + {@link android.provider.ContactsContract.CommonDataKinds}. Các hằng số chỉ cấp một + tên hằng số khác cho cùng tên cột, điều này giúp bạn truy cập dữ liệu trong một hàng thuộc + một kiểu cụ thể. +

+

+ Ví dụ, lớp {@link android.provider.ContactsContract.CommonDataKinds.Email} định nghĩa + các hằng số tên cột theo kiểu cho một hàng {@link android.provider.ContactsContract.Data} mà + có kiểu MIME + {@link android.provider.ContactsContract.CommonDataKinds.Email#CONTENT_ITEM_TYPE + Email.CONTENT_ITEM_TYPE}. Lớp chứa hằng số + {@link android.provider.ContactsContract.CommonDataKinds.Email#ADDRESS} cho cột + địa chỉ e-mail. Giá trị thực sự của + {@link android.provider.ContactsContract.CommonDataKinds.Email#ADDRESS} là "data1", giá trị này + giống hệt như tên chung của cột. +

+

+ Chú ý: Không được thêm dữ liệu tùy chỉnh của chính bạn vào bảng + {@link android.provider.ContactsContract.Data} bằng cách sử dụng một hàng có một trong các kiểu MIME được xác định trước + của trình cung cấp. Nếu làm vậy, bạn có thể làm mất dữ liệu hoặc khiến trình cung cấp + gặp trục trặc. Ví dụ, bạn không nên thêm một hàng có kiểu MIME + {@link android.provider.ContactsContract.CommonDataKinds.Email#CONTENT_ITEM_TYPE + Email.CONTENT_ITEM_TYPE} mà chứa tên người dùng thay vì địa chỉ e-mail trong cột + DATA1. Nếu sử dụng kiểu MIME tùy chỉnh của mình cho hàng, khi đó bạn được tự do + định nghĩa tên cột theo kiểu của chính mình và sử dụng các cột theo cách bạn muốn. +

+

+ Hình 2 minh họa cách các cột mô tả và cột dữ liệu xuất hiện trong hàng + {@link android.provider.ContactsContract.Data}, và cách mà tên cột theo kiểu "phủ lên" + tên cột chung +

+How type-specific column names map to generic column names +

+ Hình 2. Tên cột theo kiểu và tên cột chung. +

+

Lớp tên cột theo kiểu

+

+ Bảng 2 liệt kê các lớp tên cột theo kiểu thường được sử dụng nhất: +

+

+ Bảng 2. Lớp tên cột theo kiểu

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Lớp ánh xạKiểu dữ liệuLưu ý
{@link android.provider.ContactsContract.CommonDataKinds.StructuredName}Dữ liệu tên của liên lạc thô liên kết với hàng dữ liệu này.Một liên lạc thô chỉ có một trong những hàng này.
{@link android.provider.ContactsContract.CommonDataKinds.Photo}Ảnh chính của liên lạc thô được liên kết với hàng dữ liệu này.Một liên lạc thô chỉ có một trong những hàng này.
{@link android.provider.ContactsContract.CommonDataKinds.Email}Địa chỉ e-mail của liên lạc thô được liên kết với hàng dữ liệu này.Một liên lạc thô có thể có nhiều địa chỉ e-mail.
{@link android.provider.ContactsContract.CommonDataKinds.StructuredPostal}Địa chỉ cổng của liên lạc thô được liên kết với hàng dữ liệu này.Một liên lạc thô có thể có nhiều địa chỉ cổng.
{@link android.provider.ContactsContract.CommonDataKinds.GroupMembership}Mã định danh liên kết liên lạc thô với một trong các nhóm trong Trình cung cấp Danh bạ. + Nhóm là một tính năng tùy chọn của loại tài khoản và tên tài khoản. Chúng được mô tả + chi tiết hơn trong phần Nhóm liên lạc. +
+

Danh bạ

+

+ Trình cung cấp Danh bạ kết hợp các hàng liên lạc thô giữa tất cả các loại tài khoản và tên tài khoản + để tạo thành một liên lạc. Điều này tạo điều kiện để hiển thị và sửa đổi tất cả dữ liệu mà một + người dùng đã thu thập cho một người. Trình cung cấp Danh bạ quản lý việc tạo các hàng liên lạc mới + và tổng hợp các liên lạc thô với hàng liên lạc hiện có. Ứng dụng lẫn + trình điều hợp đồng bộ đều không được cho phép thêm liên lạc và một số cột trong một hàng liên lạc là cột chỉ đọc. +

+

+ Lưu ý: Nếu bạn cố gắng thêm một liên lạc vào Trình cung cấp Danh bạ có một + {@link android.content.ContentResolver#insert(Uri,ContentValues) insert()}, bạn sẽ gặp + lỗi ngoại lệ {@link java.lang.UnsupportedOperationException}. Nếu bạn cố gắng cập nhật một cột + mà được liệt kê là "chỉ đọc," cập nhật sẽ bị bỏ qua. +

+

+ Trình cung cấp Danh bạ tạo một liên lạc mới để hồi đáp lại việc thêm một liên lạc thô mới + không khớp với bất kỳ liên lạc nào hiện có. Trình cung cấp cũng làm vậy nếu dữ liệu + của một liên lạc thô hiện có thay đổi sao cho nó không còn khớp với liên lạc mà trước đó + nó được gắn với. Nếu một ứng dụng hoặc trình điều hợp đồng bộ tạo một liên lạc thô mới mà + khớp với một liên lạc hiện tại, liên lạc thô mới sẽ được tổng hợp vào liên lạc + hiện có. +

+

+ Trình cung cấp Danh bạ sẽ liên kết một hàng liên lạc với các hàng liên lạc thô của nó bằng cột + _ID của hàng liên lạc đó trong bảng {@link android.provider.ContactsContract.Contacts Contacts} +. Cột CONTACT_ID của bảng liên lạc thô + {@link android.provider.ContactsContract.RawContacts} chứa các giá trị _ID cho + các hàng liên lạc liên kết với từng hàng liên lạc thô. +

+

+ Bảng {@link android.provider.ContactsContract.Contacts} cũng có cột + {@code android.provider.ContactsContract.ContactsColumns#LOOKUP_KEY} mà là một liên kết + "cố định" với hàng liên lạc đó. Vì Trình cung cấp Danh bạ tự động duy trì + các liên lạc, nó có thể thay đổi giá trị {@code android.provider.BaseColumns#_ID} của một hàng liên lạc + hồi đáp lại một sự tổng hợp hoặc đồng bộ. Ngay cả khi điều này xảy ra, URI nội dung + {@link android.provider.ContactsContract.Contacts#CONTENT_LOOKUP_URI} kết hợp với + {@code android.provider.ContactsContract.ContactsColumns#LOOKUP_KEY} của liên lạc sẽ vẫn + chỉ về hàng liên lạc đó, vì thế bạn có thể sử dụng + {@code android.provider.ContactsContract.ContactsColumns#LOOKUP_KEY} + để duy trì các liên kết đến liên lạc "yêu thích", v.v. Cột này có định dạng riêng không + liên quan tới định dạng của cột {@code android.provider.BaseColumns#_ID}. +

+

+ Hình 3 minh họa mối liên quan giữa ba bảng chính này với nhau. +

+Contacts provider main tables +

+ Hình 3. Mối quan hệ giữa các bảng Danh bạ, Liên lạc Thô, và Chi tiết. +

+

Dữ liệu từ Trình điều hợp Đồng bộ

+

+ Người dùng nhập dữ liệu danh bạ trực tiếp vào thiết bị, nhưng dữ liệu cũng đi đến Trình cung cấp + Danh bạ từ các dịch vụ web thông qua trình điều hợp đồng bộ, giúp tự động + chuyển dữ liệu giữa thiết bị và các dịch vụ. Trình điều hợp đồng bộ chạy ngầm + dưới sự kiểm soát của hệ thống, và chúng gọi các phương pháp {@link android.content.ContentResolver} để + quản lý dữ liệu. +

+

+ Trong Android, dịch vụ web mà một trình điều hợp đồng bộ làm việc cùng sẽ được xác định bằng một loại tài khoản. + Mỗi trình điều hợp đồng bộ làm việc với một loại tài khoản, nhưng nó có thể hỗ trợ nhiều tên tài khoản cho + loại đó. Các loại tài khoản và tên tài khoản được mô tả sơ qua trong phần + Các nguồn dữ liệu liên lạc thô. Các định nghĩa sau trình bày + chi tiết hơn và mô tả mối liên quan giữa loại và tên tài khoản với các trình điều hợp đồng bộ và dịch vụ. +

+
+
+ Loại tài khoản +
+
+ Xác định một dịch vụ mà người dùng đã lưu giữ dữ liệu trong đó. Trong phần lớn thời gian, người dùng phải + xác thực dịch vụ. Ví dụ, Google Contacts là một loại tài khoản được xác định + bởi mã google.com. Giá trị này tương ứng với loại tài khoản được sử dụng bởi + {@link android.accounts.AccountManager}. +
+
+ Tên tài khoản +
+
+ Xác định một tài khoản hoặc đăng nhập cụ thể cho một loại tài khoản. Tài khoản Google Contacts + giống như tài khoản Google, chúng có một địa chỉ e-mail làm tên tài khoản. + Các dịch vụ khác có thể sử dụng tên người dùng là một từ hoặc id chữ số. +
+
+

+ Loại tài khoản không nhất thiết phải duy nhất. Một người dùng có thể cấu hình nhiều tài khoản Google Contacts + và tải xuống dữ liệu của chúng vào Trình cung cấp Danh bạ; điều này có thể xảy ra nếu người dùng có một tập hợp + các liên lạc cá nhân cho một tên tài khoản cá nhân, và một tập hợp khác cho cơ quan. Tên tài khoản thường + là duy nhất. Cùng nhau, chúng xác định một dòng dữ liệu cụ thể giữa Trình cung cấp Danh bạ và + một dịch vụ bên ngoài. +

+

+ Nếu muốn chuyển dữ liệu từ dịch vụ của bạn sang Trình cung cấp Danh bạ, bạn cần ghi + vào trình điều hợp đồng bộ của chính mình. Điều này được mô tả chi tiết hơn trong phần + Trình điều hợp Đồng bộ Trình cung cấp Danh bạ. +

+

+ Hình 4 minh họa cách mà Trình cung cấp Danh bạ phù hợp với dòng dữ liệu + về con người. Trong hộp được đánh dấu "trình điều hợp đồng bộ," mỗi trình điều hợp được ghi nhãn theo loại tài khoản của nó. +

+Flow of data about people +

+ Hình 4. Luồng dữ liệu của Trình cung cấp Danh bạ. +

+

Quyền được Yêu cầu

+

+ Những ứng dụng muốn truy cập Trình cung cấp Danh bạ phải yêu cầu các quyền + sau: +

+
+
Quyền truy cập đọc vào một hoặc nhiều bảng
+
+ {@link android.Manifest.permission#READ_CONTACTS}, được quy định trong + AndroidManifest.xml với phần tử + + <uses-permission> là + <uses-permission android:name="android.permission.READ_CONTACTS">. +
+
Quyền truy cập ghi vào một hoặc nhiều bảng
+
+ {@link android.Manifest.permission#WRITE_CONTACTS}, được quy định trong + AndroidManifest.xml với phần tử + + <uses-permission> là + <uses-permission android:name="android.permission.WRITE_CONTACTS">. +
+
+

+ Những quyền này không mở rộng sang dữ liệu hồ sơ người dùng. Hồ sơ người dùng và các quyền + được yêu cầu được đề cập trong phần sau, + Hồ sơ Người dùng. +

+

+ Nhớ rằng dữ liệu danh bạ của người dùng là dữ liệu cá nhân và nhạy cảm. Người dùng quan tâm về + quyền riêng tư của họ, vì thế họ không muốn các ứng dụng thu thập dữ liệu về mình hoặc danh bạ của mình. + Nếu không rõ ràng về lý do bạn cần quyền truy cập dữ liệu danh bạ của họ, họ có thể cho + ứng dụng của bạn đánh giá thấp hoặc từ chối cài đặt ứng dụng. +

+

Hồ sơ Người dùng

+

+ Bảng {@link android.provider.ContactsContract.Contacts} có một hàng đơn chứa + dữ liệu hồ sơ cho người dùng của thiết bị. Dữ liệu này mô tả user của thiết bị chứ không phải + của một trong các liên lạc của người dùng. Hàng liên lạc hồ sơ được liên kết với hàng + liên lạc thô đối với từng hệ thống sử dụng hồ sơ. + Mỗi hàng liên lạc thô của hồ sơ có thể có nhiều hàng dữ liệu. Các hằng số để truy cập hồ sơ + người dùng có sẵn trong lớp {@link android.provider.ContactsContract.Profile}. +

+

+ Truy cập hồ sơ người dùng đòi hỏi phải có các quyền đặc biệt. Ngoài các quyền + {@link android.Manifest.permission#READ_CONTACTS} và + {@link android.Manifest.permission#WRITE_CONTACTS} cần để đọc và ghi, truy cập + hồ sơ người dùng còn yêu cầu quyền {@code android.Manifest.permission#READ_PROFILE} và + {@code android.Manifest.permission#WRITE_PROFILE} tương ứng cho quyền truy cập đọc và + ghi. +

+

+ Nhớ rằng bạn nên coi hồ sơ của một người dùng là nội dung nhạy cảm. Quyền + {@code android.Manifest.permission#READ_PROFILE} cho phép bạn truy cập dữ liệu xác định cá nhân + của người dùng thiết bị. Chắc chắn phải nói cho người dùng biết lý do tại sao + bạn cần các quyền truy cập hồ sơ người dùng trong phần mô tả ứng dụng của mình. +

+

+ Để truy xuất hàng liên lạc chứa hồ sơ của người dùng, + hãy gọi {@link android.content.ContentResolver#query(Uri,String[], String, String[], String) + ContentResolver.query()}. Đặt URI nội dung thành + {@link android.provider.ContactsContract.Profile#CONTENT_URI} và không cung cấp bất kỳ + tiêu chí lựa chọn nào. Bạn cũng có thể sử dụng URI nội dung này làm URI cơ sở để truy xuất các liên lạc thô + hoặc dữ liệu cho hồ sơ. Ví dụ, đoạn mã HTML này truy xuất dữ liệu cho hồ sơ: +

+
+// Sets the columns to retrieve for the user profile
+mProjection = new String[]
+    {
+        Profile._ID,
+        Profile.DISPLAY_NAME_PRIMARY,
+        Profile.LOOKUP_KEY,
+        Profile.PHOTO_THUMBNAIL_URI
+    };
+
+// Retrieves the profile from the Contacts Provider
+mProfileCursor =
+        getContentResolver().query(
+                Profile.CONTENT_URI,
+                mProjection ,
+                null,
+                null,
+                null);
+
+

+ Lưu ý: Nếu bạn truy xuất nhiều hàng liên lạc và muốn xác định xem một trong số chúng có phải + là hồ sơ người dùng không, hãy kiểm tra cột + {@link android.provider.ContactsContract.ContactsColumns#IS_USER_PROFILE} của hàng. Cột này + được đặt thành "1" nếu liên lạc là hồ sơ người dùng. +

+

Siêu dữ liệu Trình cung cấp Danh bạ

+

+ Trình cung cấp Danh bạ quản lý dữ liệu theo dõi trạng thái của dữ liệu danh bạ trong + kho lưu giữ. Siêu dữ liệu về kho lưu giữ này được lưu giữ ở nhiều nơi khác nhau, bao gồm + các hàng bảng Liên lạc Thô, Dữ liệu, và Danh bạ, bảng + {@link android.provider.ContactsContract.Settings}, và bảng + {@link android.provider.ContactsContract.SyncState}. Bảng sau đây cho biết + ảnh hưởng của từng mục trong siêu dữ liệu này: +

+

+ Bảng 3. Siêu dữ liệu trong Trình cung cấp Danh bạ

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
BảngCộtGiá trịÝ nghĩa
{@link android.provider.ContactsContract.RawContacts}{@link android.provider.ContactsContract.SyncColumns#DIRTY}"0" - không thay đổi kể từ lần đồng bộ cuối cùng. + Đánh dấu các liên lạc thô đã được thay đổi trên thiết bị và phải được đồng bộ trở lại + máy chủ. Giá trị được đặt tự động bởi Trình cung cấp Danh bạ khi các ứng dụng + Android cập nhật một hàng. +

+ Các trình điều hợp đồng bộ sửa đổi bảng liên lạc thô hoặc dữ liệu nên luôn nối + xâu {@link android.provider.ContactsContract#CALLER_IS_SYNCADAPTER} với + URI nội dung mà chúng sử dụng. Làm vậy sẽ ngăn không cho trình cung cấp đánh dấu hàng là không tốt. + Nếu không, các sửa đổi trình điều hợp đồng bộ xem như sửa đổi cục bộ và được + gửi tới máy chủ, ngay cả khi máy chủ là nguồn sửa đổi. +

+
"1" - đã thay đổi kể từ lần đồng bộ cuối cùng, cần được đồng bộ lại máy chủ.
{@link android.provider.ContactsContract.RawContacts}{@link android.provider.ContactsContract.SyncColumns#VERSION}Số phiên bản của hàng này. + Trình cung cấp Danh bạ tự động tăng dần giá trị này bất cứ khi nào hàng hoặc + dữ liệu có liên quan của hàng thay đổi. +
{@link android.provider.ContactsContract.Data}{@link android.provider.ContactsContract.DataColumns#DATA_VERSION}Số phiên bản của hàng này. + Trình cung cấp Danh bạ tự động tăng dần giá trị này bất cứ khi nào hàng dữ liệu + bị thay đổi. +
{@link android.provider.ContactsContract.RawContacts}{@link android.provider.ContactsContract.SyncColumns#SOURCE_ID} + Một xâu giá trị xác định duy nhất liên lạc thô này cho tài khoản mà + nó được tạo trong đó. + + Khi một trình điều hợp đồng bộ tạo một liên lạc thô mới, cột này nên được đặt thành + ID duy nhất của máy chủ dành cho liên lạc thô đó. Khi một ứng dụng Android tạo một liên lạc thô + mới, ứng dụng đó sẽ để trống cột này. Điều này báo hiệu với trình điều hợp + đồng bộ rằng nó nên tạo một liên lạc thô mới trên máy chủ, và lấy một + giá trị cho {@link android.provider.ContactsContract.SyncColumns#SOURCE_ID}. +

+ Cụ thể, id nguồn phải là duy nhất đối với từng loại tài khoản + và nên ổn định giữa các lần đồng bộ: +

+
    +
  • + Duy nhất: Mỗi liên lạc thô đối với một tài khoản phải có id nguồn riêng của mình. Nếu không + thi hành điều này, bạn sẽ gây ra sự cố trong ứng dụng danh bạ. + Để ý rằng hai liên lạc thô đối với cùng loại tài khoản có thể có + cùng id nguồn. Ví dụ, liên lạc thô "Thomas Higginson" đối với + tài khoản {@code emily.dickinson@gmail.com} được cho phép có cùng id nguồn + như liên lạc thô "Thomas Higginson" đối với tài khoản + {@code emilyd@gmail.com}. +
  • +
  • + Ổn định: Id nguồn là một bộ phận cố định của dữ liệu từ dịch vụ trực tuyến đối với + liên lạc thô. Ví dụ, nếu người dùng xóa Lưu trữ Danh bạ khỏi + cài đặt Ứng dụng và đồng bộ lại, các liên lạc thô được khôi phục sẽ có cùng + id nguồn như trước. Nếu bạn không thi hành điều này, các lối tắt sẽ dừng + hoạt động. +
  • +
+
{@link android.provider.ContactsContract.Groups}{@link android.provider.ContactsContract.GroupsColumns#GROUP_VISIBLE}"0" - Các liên lạc trong nhóm này không nên được hiển thị trong UI ứng dụng Android. + Cột này dành cho tính tương thích với các máy chủ mà cho phép người dùng ẩn các liên lạc trong + một số nhóm. +
"1" - Các liên lạc trong nhóm này được cho phép hiển thị trong UI ứng dụng.
{@link android.provider.ContactsContract.Settings} + {@link android.provider.ContactsContract.SettingsColumns#UNGROUPED_VISIBLE} + "0" - Đối với tài khoản và loại tài khoản này, những liên lạc không thuộc về nhóm + được ẩn đối với UI ứng dụng Android. + + Theo mặc định, các liên lạc được hiển thị nếu không có liên lạc thô nào của chúng thuộc về một nhóm + (Tư cách thành viên nhóm đối với một liên lạc thô được thể hiện bằng một hoặc nhiều hàng + {@link android.provider.ContactsContract.CommonDataKinds.GroupMembership} trong + bảng {@link android.provider.ContactsContract.Data}). + Bằng cách đặt cờ này trong hàng bảng {@link android.provider.ContactsContract.Settings} đối với + một loại tài khoản và tài khoản, bạn có thể buộc những liên lạc không có nhóm phải hiển thị. + Một công dụng của cờ này đó là để hiển thị liên lạc từ các máy chủ không sử dụng nhóm. +
+ "1" - Đối với tài khoản và loại tài khoản này, những liên lạc không thuộc về nhóm + sẽ được hiển thị đối với UI ứng dụng. +
{@link android.provider.ContactsContract.SyncState}(tất cả) + Sử dụng bảng này để lưu giữ siêu dữ liệu cho trình điều hợp đồng bộ của bạn. + + Với bảng này, bạn có thể lưu giữ trạng thái đồng bộ và các dữ liệu khác liên quan tới đồng bộ một cách lâu dài + trên thiết bị. +
+

Truy cập Trình cung cấp Danh bạ

+

+ Phần này mô tả các hướng dẫn về truy cập dữ liệu từ Trình cung cấp Danh bạ, tập trung vào những + nội dung sau: +

+
    +
  • + Truy vấn thực thể. +
  • +
  • + Sửa đổi hàng loạt. +
  • +
  • + Truy xuất và sửa đổi bằng ý định. +
  • +
  • + Toàn vẹn dữ liệu. +
  • +
+

+ Thực hiện sửa đổi từ một trình điều hợp đồng bộ cũng được đề cập chi tiết hơn trong phần + Trình điều hợp Đồng bộ Trình cung cấp Danh bạ. +

+

Truy vấn thực thể

+

+ Vì các bảng của Trình cung cấp Danh bạ được tổ chức theo một phân cấp, thường sẽ hữu ích nếu + truy xuất một hàng và tất cả hàng "con" được liên kết với nó. Ví dụ, để hiển thị + tất cả thông tin cho một người, bạn có thể muốn truy xuất tất cả hàng + {@link android.provider.ContactsContract.RawContacts} đối với một hàng + {@link android.provider.ContactsContract.Contacts} đơn, hoặc tất cả hàng + {@link android.provider.ContactsContract.CommonDataKinds.Email} đối với một hàng + {@link android.provider.ContactsContract.RawContacts} đơn. Để tạo điều kiện cho điều này, Trình cung cấp + Danh bạ sẽ cung cấp các cấu trúc thực thể đóng vai trò như liên kết cơ sở dữ liệu + giữa các bảng. +

+

+ Thực thể giống như một bảng bao gồm các cột được chọn từ một bảng mẹ và bảng con của nó. + Khi bạn truy vấn một thực thể, bạn cung cấp một dự thảo và các tiêu chí dựa trên các cột + có sẵn từ thực thể. Kết quả là một {@link android.database.Cursor} trong đó chứa + một hàng cho từng hàng bảng con được truy xuất. Ví dụ, nếu bạn truy vấn + {@link android.provider.ContactsContract.Contacts.Entity} cho một tên liên lạc + và tất cả hàng {@link android.provider.ContactsContract.CommonDataKinds.Email} đối với tất cả + liên lạc thô cho tên đó, bạn sẽ nhận lại một {@link android.database.Cursor} chứa một hàng + cho mỗi hàng {@link android.provider.ContactsContract.CommonDataKinds.Email}. +

+

+ Các thực thể sẽ đơn giản hóa việc truy vấn. Bằng cách sử dụng một thực thể, bạn có thể truy xuất ngay lập tức tất cả dữ liệu danh bạ cho một + liên lạc hoặc liên lạc thô, thay vì phải truy vấn bảng mẹ trước để nhận một + ID, và rồi phải truy xuất bảng con bằng ID đó. Đồng thời, Trình cung cấp Danh bạ xử lý + một truy vấn đối với một thực thể trong một giao tác đơn, điều này đảm bảo rằng dữ liệu được truy xuất sẽ được + nhất quán trong nội bộ. +

+

+ Lưu ý: Một thực thể thường không chứa tất cả cột của bảng mẹ và + bảng con. Nếu bạn cố gắng làm việc với một tên cột không có trong danh sách các hằng số tên cột + đối với thực thể đó, bạn sẽ nhận được một {@link java.lang.Exception}. +

+

+ Đoạn mã HTML sau cho biết cách truy xuất tất cả hàng liên lạc thô cho một liên lạc. Đoạn mã HTML + là bộ phận của một ứng dụng lớn hơn có hai hoạt động, "chính" và "chi tiết". Hoạt động chính + hiển thị một danh sách các hàng liên lạc; khi người dùng chọn một hàng, hoạt động sẽ gửi ID của hàng tới hoạt động + chi tiết. Hoạt động chi tiết sử dụng {@link android.provider.ContactsContract.Contacts.Entity} + để hiển thị tất cả hàng dữ liệu từ tất cả liên lạc thô được liên kết với liên lạc + đã chọn. +

+

+ Đoạn mã HTML này được lấy từ hoạt động "chi tiết": +

+
+...
+    /*
+     * Appends the entity path to the URI. In the case of the Contacts Provider, the
+     * expected URI is content://com.google.contacts/#/entity (# is the ID value).
+     */
+    mContactUri = Uri.withAppendedPath(
+            mContactUri,
+            ContactsContract.Contacts.Entity.CONTENT_DIRECTORY);
+
+    // Initializes the loader identified by LOADER_ID.
+    getLoaderManager().initLoader(
+            LOADER_ID,  // The identifier of the loader to initialize
+            null,       // Arguments for the loader (in this case, none)
+            this);      // The context of the activity
+
+    // Creates a new cursor adapter to attach to the list view
+    mCursorAdapter = new SimpleCursorAdapter(
+            this,                        // the context of the activity
+            R.layout.detail_list_item,   // the view item containing the detail widgets
+            mCursor,                     // the backing cursor
+            mFromColumns,                // the columns in the cursor that provide the data
+            mToViews,                    // the views in the view item that display the data
+            0);                          // flags
+
+    // Sets the ListView's backing adapter.
+    mRawContactList.setAdapter(mCursorAdapter);
+...
+@Override
+public Loader<Cursor> onCreateLoader(int id, Bundle args) {
+
+    /*
+     * Sets the columns to retrieve.
+     * RAW_CONTACT_ID is included to identify the raw contact associated with the data row.
+     * DATA1 contains the first column in the data row (usually the most important one).
+     * MIMETYPE indicates the type of data in the data row.
+     */
+    String[] projection =
+        {
+            ContactsContract.Contacts.Entity.RAW_CONTACT_ID,
+            ContactsContract.Contacts.Entity.DATA1,
+            ContactsContract.Contacts.Entity.MIMETYPE
+        };
+
+    /*
+     * Sorts the retrieved cursor by raw contact id, to keep all data rows for a single raw
+     * contact collated together.
+     */
+    String sortOrder =
+            ContactsContract.Contacts.Entity.RAW_CONTACT_ID +
+            " ASC";
+
+    /*
+     * Returns a new CursorLoader. The arguments are similar to
+     * ContentResolver.query(), except for the Context argument, which supplies the location of
+     * the ContentResolver to use.
+     */
+    return new CursorLoader(
+            getApplicationContext(),  // The activity's context
+            mContactUri,              // The entity content URI for a single contact
+            projection,               // The columns to retrieve
+            null,                     // Retrieve all the raw contacts and their data rows.
+            null,                     //
+            sortOrder);               // Sort by the raw contact ID.
+}
+
+

+ Khi hoàn thành việc tải, {@link android.app.LoaderManager} gọi ra một lệnh gọi lại đến + {@link android.app.LoaderManager.LoaderCallbacks#onLoadFinished(Loader, D) + onLoadFinished()}. Một trong các tham đối đến với phương pháp này là một + {@link android.database.Cursor} với các kết quả của truy vấn. Trong ứng dụng của chính mình, bạn có thể nhận dữ liệu + từ {@link android.database.Cursor} này để hiển thị nó hoặc thao tác thêm với nó. +

+

Sửa đổi hàng loạt

+

+ Bất cứ khi nào có thể, bạn nên chèn, cập nhật và xóa dữ liệu trong Trình cung cấp Danh bạ trong + "chế độ hàng loạt", bằng cách tạo một {@link java.util.ArrayList} của các đối tượng + {@link android.content.ContentProviderOperation} và gọi + {@link android.content.ContentResolver#applyBatch(String, ArrayList) applyBatch()}. Vì + Trình cung cấp Danh bạ thực hiện tất cả thao tác trong một + {@link android.content.ContentResolver#applyBatch(String, ArrayList) applyBatch()} trong một giao tác + đơn, các sửa đổi của bạn sẽ không bao giờ ra khỏi kho lưu giữ danh bạ một cách + không nhất quán. Sửa đổi hàng loạt cũng tạo điều kiện cho việc chèn một liên lạc thô và dữ liệu chi tiết của liên lạc + tại cùng thời điểm. +

+

+ Lưu ý: Để sửa đổi một liên lạc thô đơn, hãy xét gửi một ý định tới + ứng dụng danh bạ của thiết bị thay vì xử lý sửa đổi trong ứng dụng của bạn. + Việc làm này được mô tả chi tiết hơn trong phần + Truy xuất và sửa đổi bằng ý định. +

+

Điểm kết quả

+

+ Sửa đổi hàng loạt chứa nhiều thao tác có thể chặn các tiến trình khác, + dẫn đến trải nghiệm người dùng tổng thể không tốt. Để sắp xếp tổ chức tất cả sửa đổi mà bạn muốn + thực hiện trong ít danh sách riêng nhất có thể, và đồng thời ngăn chúng + chặn hệ thống, bạn nên đặt các điểm kết quả cho một hoặc nhiều thao tác. + Điểm kết quả là một đối tượng {@link android.content.ContentProviderOperation} có giá trị + {@link android.content.ContentProviderOperation#isYieldAllowed()} được đặt thành + true. Khi các Trình cung cấp Danh bạ gặp phải một điểm kết quả, nó tạm dừng công việc để + cho phép các tiến trình khác chạy và đóng giao tác hiện tại. Khi trình cung cấp bắt đầu lại, nó + tiếp tục với thao tác tiếp theo trong {@link java.util.ArrayList} và bắt đầu một giao tác + mới. +

+

+ Điểm kết quả dẫn đến có nhiều hơn một giao tác trên mỗi lệnh gọi tới + {@link android.content.ContentResolver#applyBatch(String, ArrayList) applyBatch()}. Vì + điều này, bạn nên đặt một điểm kết quả cho thao tác cuối cùng đối với một tập hợp các hàng có liên quan. + Ví dụ, bạn nên đặt một điểm kết quả cho thao tác cuối cùng trong một tập hợp mà thêm + các hàng liên lạc thô và hàng dữ liệu liên kết của chúng, hoặc thao tác cuối cùng đối với một tập hợp các hàng liên quan tới + một liên lạc riêng lẻ. +

+

+ Điểm kết quả cũng là một đơn vị thao tác nguyên tử. Tất cả truy cập giữa hai điểm kết quả sẽ + hoặc thành công hoặc thất bại như một đơn vị riêng lẻ. Nếu bạn không đặt bất kỳ điểm kết quả nào, thao tác + nguyên tử nhỏ nhất chính là toàn bộ loạt thao tác. Nếu sử dụng điểm kết quả, bạn ngăn cản + các thao tác làm giảm hiệu suất của hệ thống, đồng thời đảm bảo rằng một tập con của + thao tác là tập nguyên tử. +

+

Tham chiếu lại sửa đổi

+

+ Khi bạn đang chèn một hàng liên lạc thô mới và các hàng dữ liệu liên kết của nó như một tập hợp các đối tượng + {@link android.content.ContentProviderOperation}, bạn phải liên kết các hàng dữ liệu với + hàng liên lạc thô bằng cách chèn giá trị + {@code android.provider.BaseColumns#_ID} của liên lạc thô làm giá trị + {@link android.provider.ContactsContract.DataColumns#RAW_CONTACT_ID}. Tuy nhiên, giá trị + này không có sẵn khi bạn đang tạo {@link android.content.ContentProviderOperation} + cho hàng dữ liệu, vì bạn chưa áp dụng + {@link android.content.ContentProviderOperation} cho hàng liên lạc thô. Để khắc phục điều này, + lớp {@link android.content.ContentProviderOperation.Builder} có phương pháp + {@link android.content.ContentProviderOperation.Builder#withValueBackReference(String, int) withValueBackReference()}. + Phương pháp này cho phép bạn chèn hoặc sửa đổi một cột bằng + kết quả của một thao tác trước đó. +

+

+ Phương pháp {@link android.content.ContentProviderOperation.Builder#withValueBackReference(String, int) withValueBackReference()} + có hai tham đối: +

+
+
+ key +
+
+ Khóa của một cặp khóa-giá trị. Giá trị của tham đối này nên là tên của một cột + trong bảng mà bạn đang sửa đổi. +
+
+ previousResult +
+
+ Chỉ mục dựa trên 0 của một giá trị trong mảng đối tượng + {@link android.content.ContentProviderResult} từ + {@link android.content.ContentResolver#applyBatch(String, ArrayList) applyBatch()}. Khi + thao tác hàng loạt được áp dụng, kết quả của mỗi thao tác được lưu giữ trong một mảng kết quả + trung gian. Giá trị previousResult là chỉ mục + của một trong những kết quả này, nó được truy xuất và lưu giữ với giá trị key +. Điều này cho phép bạn chèn một bản ghi liên lạc thô mới và nhận lại giá trị + {@code android.provider.BaseColumns#_ID} của nó, rồi thực hiện một "tham chiếu ngược" về + giá trị đó khi bạn thêm một hàng {@link android.provider.ContactsContract.Data}. +

+ Toàn bộ mảng kết quả được tạo khi bạn lần đầu gọi + {@link android.content.ContentResolver#applyBatch(String, ArrayList) applyBatch()}, + với kích cỡ bằng với kích cỡ của {@link java.util.ArrayList} của các đối tượng + {@link android.content.ContentProviderOperation} mà bạn cung cấp. Tuy nhiên, tất cả + các phần tử trong mảng kết quả được đặt thành null, và nếu bạn cố gắng + thực hiện tham chiếu ngược tới một kết quả cho một thao tác chưa được áp dụng, +{@link android.content.ContentProviderOperation.Builder#withValueBackReference(String, int) withValueBackReference()} + sẽ đưa ra một lỗi {@link java.lang.Exception}. + +

+
+
+

+ Các đoạn mã HTML sau minh họa cách chèn một liên lạc thô mới và dữ liệu hàng loạt. Chúng + bao gồm mã thiết lập một điểm kết quả và sử dụng một tham chiếu lại. Đoạn mã HTML là một + phiên bản mở rộng của phương pháp createContacEntry(), nó là một phần của lớp + ContactAdder trong ứng dụng mẫu + + Contact Manager. +

+

+ Đoạn mã HTML đầu tiên truy xuất dữ liệu liên lạc từ UI. Tại điểm này, người dùng đã + chọn tài khoản mà liên lạc thô mới nên được thêm cho tài khoản đó. +

+
+// Creates a contact entry from the current UI values, using the currently-selected account.
+protected void createContactEntry() {
+    /*
+     * Gets values from the UI
+     */
+    String name = mContactNameEditText.getText().toString();
+    String phone = mContactPhoneEditText.getText().toString();
+    String email = mContactEmailEditText.getText().toString();
+
+    int phoneType = mContactPhoneTypes.get(
+            mContactPhoneTypeSpinner.getSelectedItemPosition());
+
+    int emailType = mContactEmailTypes.get(
+            mContactEmailTypeSpinner.getSelectedItemPosition());
+
+

+ Đoạn mã HTML tiếp theo tạo một thao tác để chèn hàng liên lạc thô vào bảng + {@link android.provider.ContactsContract.RawContacts}: +

+
+    /*
+     * Prepares the batch operation for inserting a new raw contact and its data. Even if
+     * the Contacts Provider does not have any data for this person, you can't add a Contact,
+     * only a raw contact. The Contacts Provider will then add a Contact automatically.
+     */
+
+     // Creates a new array of ContentProviderOperation objects.
+    ArrayList<ContentProviderOperation> ops =
+            new ArrayList<ContentProviderOperation>();
+
+    /*
+     * Creates a new raw contact with its account type (server type) and account name
+     * (user's account). Remember that the display name is not stored in this row, but in a
+     * StructuredName data row. No other data is required.
+     */
+    ContentProviderOperation.Builder op =
+            ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI)
+            .withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, mSelectedAccount.getType())
+            .withValue(ContactsContract.RawContacts.ACCOUNT_NAME, mSelectedAccount.getName());
+
+    // Builds the operation and adds it to the array of operations
+    ops.add(op.build());
+
+

+ Tiếp theo, mã tạo các hàng dữ liệu cho hàng tên hiển thị, điện thoại và e-mail. +

+

+ Từng đối tượng bộ dựng thao tác sẽ sử dụng + {@link android.content.ContentProviderOperation.Builder#withValueBackReference(String, int) withValueBackReference()} + để nhận + {@link android.provider.ContactsContract.DataColumns#RAW_CONTACT_ID}. Tham chiếu đó sẽ trỏ + ngược về đối tượng {@link android.content.ContentProviderResult} từ thao tác đầu tiên, + là thao tác thêm hàng liên lạc thô và trả về giá trị {@code android.provider.BaseColumns#_ID} + mới của nó. Kết quả là, mỗi hàng dữ liệu được tự động liên kết bởi + {@link android.provider.ContactsContract.DataColumns#RAW_CONTACT_ID} + của nó với hàng {@link android.provider.ContactsContract.RawContacts} mới mà nó thuộc về. +

+

+ Đối tượng {@link android.content.ContentProviderOperation.Builder} thêm hàng e-mail sẽ được + gắn cờ bằng {@link android.content.ContentProviderOperation.Builder#withYieldAllowed(boolean) + withYieldAllowed()}, mà điều này đặt một điểm kết quả: +

+
+    // Creates the display name for the new raw contact, as a StructuredName data row.
+    op =
+            ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
+            /*
+             * withValueBackReference sets the value of the first argument to the value of
+             * the ContentProviderResult indexed by the second argument. In this particular
+             * call, the raw contact ID column of the StructuredName data row is set to the
+             * value of the result returned by the first operation, which is the one that
+             * actually adds the raw contact row.
+             */
+            .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
+
+            // Sets the data row's MIME type to StructuredName
+            .withValue(ContactsContract.Data.MIMETYPE,
+                    ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE)
+
+            // Sets the data row's display name to the name in the UI.
+            .withValue(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, name);
+
+    // Builds the operation and adds it to the array of operations
+    ops.add(op.build());
+
+    // Inserts the specified phone number and type as a Phone data row
+    op =
+            ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
+            /*
+             * Sets the value of the raw contact id column to the new raw contact ID returned
+             * by the first operation in the batch.
+             */
+            .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
+
+            // Sets the data row's MIME type to Phone
+            .withValue(ContactsContract.Data.MIMETYPE,
+                    ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE)
+
+            // Sets the phone number and type
+            .withValue(ContactsContract.CommonDataKinds.Phone.NUMBER, phone)
+            .withValue(ContactsContract.CommonDataKinds.Phone.TYPE, phoneType);
+
+    // Builds the operation and adds it to the array of operations
+    ops.add(op.build());
+
+    // Inserts the specified email and type as a Phone data row
+    op =
+            ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
+            /*
+             * Sets the value of the raw contact id column to the new raw contact ID returned
+             * by the first operation in the batch.
+             */
+            .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
+
+            // Sets the data row's MIME type to Email
+            .withValue(ContactsContract.Data.MIMETYPE,
+                    ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE)
+
+            // Sets the email address and type
+            .withValue(ContactsContract.CommonDataKinds.Email.ADDRESS, email)
+            .withValue(ContactsContract.CommonDataKinds.Email.TYPE, emailType);
+
+    /*
+     * Demonstrates a yield point. At the end of this insert, the batch operation's thread
+     * will yield priority to other threads. Use after every set of operations that affect a
+     * single contact, to avoid degrading performance.
+     */
+    op.withYieldAllowed(true);
+
+    // Builds the operation and adds it to the array of operations
+    ops.add(op.build());
+
+

+ Đoạn mã HTML cuối cùng hiển thị lệnh gọi tới + {@link android.content.ContentResolver#applyBatch(String, ArrayList) applyBatch()} mà + chèn liên lạc thô mới và các hàng dữ liệu. +

+
+    // Ask the Contacts Provider to create a new contact
+    Log.d(TAG,"Selected account: " + mSelectedAccount.getName() + " (" +
+            mSelectedAccount.getType() + ")");
+    Log.d(TAG,"Creating contact: " + name);
+
+    /*
+     * Applies the array of ContentProviderOperation objects in batch. The results are
+     * discarded.
+     */
+    try {
+
+            getContentResolver().applyBatch(ContactsContract.AUTHORITY, ops);
+    } catch (Exception e) {
+
+            // Display a warning
+            Context ctx = getApplicationContext();
+
+            CharSequence txt = getString(R.string.contactCreationFailure);
+            int duration = Toast.LENGTH_SHORT;
+            Toast toast = Toast.makeText(ctx, txt, duration);
+            toast.show();
+
+            // Log exception
+            Log.e(TAG, "Exception encountered while inserting contact: " + e);
+    }
+}
+
+

+ Thao tác hàng loạt cũng cho phép bạn triển khai kiểm soát đồng thời lạc quan, + một phương pháp áp dụng các giao tác sửa đổi mà không phải khóa kho lưu giữ liên quan. + Để sử dụng phương pháp này, bạn áp dụng giao tác đó rồi kiểm tra các sửa đổi khác mà + có thể đã được thực hiện tại cùng thời điểm. Nếu bạn thấy đã diễn ra một sửa đổi không nhất quán, + hãy quay lui giao tác của bạn và thử lại. +

+

+ Kiểm soát đồng thời lạc quan rất hữu ích đối với thiết bị di động, khi đó mỗi lúc chỉ có một người dùng + và việc truy cập đồng thời vào một kho lưu giữ dữ liệu hiếm khi xảy ra. Vì không sử dụng khóa nên + không bị lãng phí thời gian cho việc thiết đặt khóa hay chờ các giao tác khác nhả khóa của mình. +

+

+ Để sử dụng kiểm soát đồng thời lạc quan trong khi đang cập nhật một hàng + {@link android.provider.ContactsContract.RawContacts} đơn, hãy làm theo các bước sau: +

+
    +
  1. + Truy xuất cột {@link android.provider.ContactsContract.SyncColumns#VERSION} + của liên lạc thô cùng với dữ liệu khác mà bạn truy xuất. +
  2. +
  3. + Tạo một đối tượng {@link android.content.ContentProviderOperation.Builder} phù hợp để + thi hành một ràng buộc, bằng cách sử dụng phương pháp + {@link android.content.ContentProviderOperation#newAssertQuery(Uri)}. Đối với URI nội dung, + sử dụng {@link android.provider.ContactsContract.RawContacts#CONTENT_URI + RawContacts.CONTENT_URI} + với {@code android.provider.BaseColumns#_ID} của liên lạc thô được nối với nó. +
  4. +
  5. + Đối với đối tượng {@link android.content.ContentProviderOperation.Builder}, hãy gọi + {@link android.content.ContentProviderOperation.Builder#withValue(String, Object) + withValue()} để so sánh cột {@link android.provider.ContactsContract.SyncColumns#VERSION} + với số phiên bản bạn vừa truy xuất. +
  6. +
  7. + Đối với cùng {@link android.content.ContentProviderOperation.Builder}, hãy gọi + {@link android.content.ContentProviderOperation.Builder#withExpectedCount(int) + withExpectedCount()} để đảm bảo rằng chỉ một hàng được kiểm tra bằng xác nhận này. +
  8. +
  9. + Gọi {@link android.content.ContentProviderOperation.Builder#build()} để tạo đối tượng + {@link android.content.ContentProviderOperation}, rồi thêm đối tượng này làm + đối tượng đầu tiên trong {@link java.util.ArrayList} mà bạn chuyển cho + {@link android.content.ContentResolver#applyBatch(String, ArrayList) applyBatch()}. +
  10. +
  11. + Áp dụng giao tác hàng loạt. +
  12. +
+

+ Nếu hàng liên lạc thô được cập nhật bởi một thao tác khác giữa thời điểm bạn đọc hàng và + thời điểm bạn cố gắng sửa đổi nó, "xác nhận" {@link android.content.ContentProviderOperation} + sẽ thất bại, và toàn bộ loạt thao tác sẽ được rút khỏi. Sau đó, bạn có thể chọn thử lại + loạt hoặc thực hiện một hành động khác. +

+

+ Đoạn mã HTML sau minh họa cách tạo một "xác nhận" + {@link android.content.ContentProviderOperation} sau khi truy vấn một liên lạc thô đơn bằng cách sử dụng + một {@link android.content.CursorLoader}: +

+
+/*
+ * The application uses CursorLoader to query the raw contacts table. The system calls this method
+ * when the load is finished.
+ */
+public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
+
+    // Gets the raw contact's _ID and VERSION values
+    mRawContactID = cursor.getLong(cursor.getColumnIndex(BaseColumns._ID));
+    mVersion = cursor.getInt(cursor.getColumnIndex(SyncColumns.VERSION));
+}
+
+...
+
+// Sets up a Uri for the assert operation
+Uri rawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, mRawContactID);
+
+// Creates a builder for the assert operation
+ContentProviderOperation.Builder assertOp = ContentProviderOperation.netAssertQuery(rawContactUri);
+
+// Adds the assertions to the assert operation: checks the version and count of rows tested
+assertOp.withValue(SyncColumns.VERSION, mVersion);
+assertOp.withExpectedCount(1);
+
+// Creates an ArrayList to hold the ContentProviderOperation objects
+ArrayList ops = new ArrayList<ContentProviderOperationg>;
+
+ops.add(assertOp.build());
+
+// You would add the rest of your batch operations to "ops" here
+
+...
+
+// Applies the batch. If the assert fails, an Exception is thrown
+try
+    {
+        ContentProviderResult[] results =
+                getContentResolver().applyBatch(AUTHORITY, ops);
+
+    } catch (OperationApplicationException e) {
+
+        // Actions you want to take if the assert operation fails go here
+    }
+
+

Truy xuất và sửa đổi bằng ý định

+

+ Việc gửi một ý định tới ứng dụng danh bạ của thiết bị cho phép bạn truy cập Trình cung cấp Danh bạ + một cách gián tiếp. Ý định sẽ khởi động UI ứng dụng danh bạ của thiết bị, trong đó người dùng có thể + thực hiện công việc liên quan tới danh bạ. Với kiểu truy cập này, người dùng có thể: +

    +
  • Chọn một liên lạc từ danh sách và trả nó về ứng dụng của bạn để làm việc tiếp.
  • +
  • Chỉnh sửa dữ liệu của một liên lạc hiện có.
  • +
  • Chèn một liên lạc thô mới cho bất kỳ tài khoản nào của họ.
  • +
  • Xóa một liên lạc hoặc dữ liệu danh bạ.
  • +
+

+ Nếu người dùng đang chèn hoặc cập nhật dữ liệu, bạn có thể thu thập dữ liệu trước và gửi nó như + một phần của ý định. +

+

+ Khi bạn sử dụng ý định để truy cập Trình cung cấp Danh bạ thông qua ứng dụng danh bạ của thiết bị, bạn + không phải ghi UI hay mã của chính mình để truy nhập trình cung cấp. Bạn cũng không phải + yêu cầu quyền đọc hoặc ghi đến trình cung cấp. Ứng dụng danh bạ của thiết bị có thể + cấp quyền đọc đối với một liên lạc cho bạn, và vì bạn đang thực hiện sửa đổi đối với + trình cung cấp thông qua một ứng dụng khác, bạn không cần phải có quyền ghi. +

+

+ Tiến trình chung để gửi một ý định nhằm truy cập một trình cung cấp được mô tả chi tiết trong hướng dẫn + + Nội dung Cơ bản về Trình cung cấp Nội dung trong phần "Truy cập dữ liệu thông qua ý định." Hành động, + kiểu MIME, và các giá trị dữ liệu bạn sử dụng cho các tác vụ có sẵn được tóm tắt trong Bảng 4, trong khi các giá trị + phụ thêm mà bạn có thể sử dụng với + {@link android.content.Intent#putExtra(String, String) putExtra()} được liệt kê trong + tài liệu tham khảo cho {@link android.provider.ContactsContract.Intents.Insert}: +

+

+ Bảng 4. Ý định của Trình cung cấp Danh bạ. +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tác vụHành độngDữ liệuKiểu MIMELưu ý
Chọn một liên lạc từ danh sách{@link android.content.Intent#ACTION_PICK} + Một trong: +
    +
  • +{@link android.provider.ContactsContract.Contacts#CONTENT_URI Contacts.CONTENT_URI}, + mà hiển thị một danh sách các liên lạc. +
  • +
  • +{@link android.provider.ContactsContract.CommonDataKinds.Phone#CONTENT_URI Phone.CONTENT_URI}, + mà hiển thị một danh sách các số điện thoại cho một liên lạc thô. +
  • +
  • +{@link android.provider.ContactsContract.CommonDataKinds.StructuredPostal#CONTENT_URI +StructuredPostal.CONTENT_URI}, + mà hiển thị một danh sách các địa chỉ bưu điện cho một liên lạc thô. +
  • +
  • +{@link android.provider.ContactsContract.CommonDataKinds.Email#CONTENT_URI Email.CONTENT_URI}, + mà hiển thị một danh sách các địa chỉ e-mail cho một liên lạc thô. +
  • +
+
+ Không sử dụng + + Hiển thị một danh sách các liên lạc thô hoặc danh sách dữ liệu từ một liên lạc thô, tùy vào kiểu + URI nội dung mà bạn cung cấp. +

+ Gọi + {@link android.app.Activity#startActivityForResult(Intent, int) startActivityForResult()}, + nó trả về URI nội dung của hàng được chọn. Hình thức của URI là URI nội dung + của bảng với LOOKUP_ID của hàng được nối với nó. + Ứng dụng danh bạ của thiết bị cấp quyền đọc và ghi cho URI nội dung này + trong suốt thời gian hoạt động của bạn. Xem hướng dẫn + + Nội dung Cơ bản về Trình cung cấp Nội dung để biết thêm chi tiết. +

+
Chèn một liên lạc thô mới{@link android.provider.ContactsContract.Intents.Insert#ACTION Insert.ACTION}Không áp dụng + {@link android.provider.ContactsContract.RawContacts#CONTENT_TYPE + RawContacts.CONTENT_TYPE}, kiểu MIME cho một tập hợp liên các lạc thô. + + Hiển thị màn hình Thêm Liên lạc của ứng dụng danh bạ của thiết bị. Các + giá trị phụ thêm mà bạn thêm vào ý định sẽ được hiển thị. Nếu được gửi bằng + {@link android.app.Activity#startActivityForResult(Intent, int) startActivityForResult()}, + URI nội dung của liên lạc thô mới thêm sẽ được chuyển lại cho phương pháp gọi lại + {@link android.app.Activity#onActivityResult(int, int, Intent) onActivityResult()} + của hoạt động của bạn trong tham đối {@link android.content.Intent}, trong + trường "dữ liệu". Để nhận giá trị, hãy gọi {@link android.content.Intent#getData()}. +
Chỉnh sửa một liên lạc{@link android.content.Intent#ACTION_EDIT} + {@link android.provider.ContactsContract.Contacts#CONTENT_LOOKUP_URI} đối với + liên lạc. Hoạt động của trình chỉnh sửa sẽ cho phép người dùng chỉnh sửa bất kỳ dữ liệu nào được liên kết + với liên lạc này. + + {@link android.provider.ContactsContract.Contacts#CONTENT_ITEM_TYPE + Contacts.CONTENT_ITEM_TYPE}, một liên lạc đơn. + Hiển thị màn hình Chỉnh sửa Liên lạc trong ứng dụng danh bạ. Các giá trị phụ thêm mà bạn thêm + vào ý định sẽ được hiển thị. Khi người dùng nhấp vào Xong để lưu các + chỉnh sửa, hoạt động của bạn quay lại tiền cảnh. +
Hiển thị một trình chọn mà cũng có thể thêm dữ liệu.{@link android.content.Intent#ACTION_INSERT_OR_EDIT} + Không áp dụng + + {@link android.provider.ContactsContract.Contacts#CONTENT_ITEM_TYPE} + + Ý định này luôn hiển thị màn hình bộ chọn của ứng dụng danh bạ. Người dùng có thể hoặc + chọn một liên lạc để chỉnh sửa, hoặc thêm một liên lạc mới. Hoặc màn hình chỉnh sửa hoặc màn hình thêm + sẽ xuất hiện, tùy vào lựa chọn của người dùng, và dữ liệu phụ thêm mà bạn chuyển trong ý định + sẽ được hiển thị. Nếu ứng dụng của bạn hiển thị dữ liệu chẳng hạn như e-mail hoặc số điện thoại, hãy sử dụng + ý định này để cho phép người dùng thêm dữ liệu vào một liên lạc hiện tại. + liên lạc, +

+ Lưu ý: Không cần gửi một giá trị tên trong phần phụ thêm của ý định, + vì người dùng luôn chọn một tên hiện có hoặc thêm một tên mới. Thêm nữa, + nếu bạn gửi một tên, và người dùng chọn thực hiện chỉnh sửa, ứng dụng danh bạ sẽ + hiển thị tên mà bạn gửi, ghi đè giá trị trước. Nếu người dùng không + để ý thấy điều này và lưu chỉnh sửa, giá trị cũ sẽ bị mất. +

+
+

+ Ứng dụng danh bạ của thiết bị không cho phép bạn xóa một liên lạc thô hay bất kỳ dữ liệu nào bằng một + ý định. Thay vào đó, để xóa một liên lạc thô, hãy sử dụng + {@link android.content.ContentResolver#delete(Uri, String, String[]) ContentResolver.delete()} + hoặc {@link android.content.ContentProviderOperation#newDelete(Uri) + ContentProviderOperation.newDelete()}. +

+

+ Đoạn mã HTML sau minh họa cách xây dựng và gửi một ý định để chèn một liên lạc thô mới và + dữ liệu: +

+
+// Gets values from the UI
+String name = mContactNameEditText.getText().toString();
+String phone = mContactPhoneEditText.getText().toString();
+String email = mContactEmailEditText.getText().toString();
+
+String company = mCompanyName.getText().toString();
+String jobtitle = mJobTitle.getText().toString();
+
+// Creates a new intent for sending to the device's contacts application
+Intent insertIntent = new Intent(ContactsContract.Intents.Insert.ACTION);
+
+// Sets the MIME type to the one expected by the insertion activity
+insertIntent.setType(ContactsContract.RawContacts.CONTENT_TYPE);
+
+// Sets the new contact name
+insertIntent.putExtra(ContactsContract.Intents.Insert.NAME, name);
+
+// Sets the new company and job title
+insertIntent.putExtra(ContactsContract.Intents.Insert.COMPANY, company);
+insertIntent.putExtra(ContactsContract.Intents.Insert.JOB_TITLE, jobtitle);
+
+/*
+ * Demonstrates adding data rows as an array list associated with the DATA key
+ */
+
+// Defines an array list to contain the ContentValues objects for each row
+ArrayList<ContentValues> contactData = new ArrayList<ContentValues>();
+
+
+/*
+ * Defines the raw contact row
+ */
+
+// Sets up the row as a ContentValues object
+ContentValues rawContactRow = new ContentValues();
+
+// Adds the account type and name to the row
+rawContactRow.put(ContactsContract.RawContacts.ACCOUNT_TYPE, mSelectedAccount.getType());
+rawContactRow.put(ContactsContract.RawContacts.ACCOUNT_NAME, mSelectedAccount.getName());
+
+// Adds the row to the array
+contactData.add(rawContactRow);
+
+/*
+ * Sets up the phone number data row
+ */
+
+// Sets up the row as a ContentValues object
+ContentValues phoneRow = new ContentValues();
+
+// Specifies the MIME type for this data row (all data rows must be marked by their type)
+phoneRow.put(
+        ContactsContract.Data.MIMETYPE,
+        ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE
+);
+
+// Adds the phone number and its type to the row
+phoneRow.put(ContactsContract.CommonDataKinds.Phone.NUMBER, phone);
+
+// Adds the row to the array
+contactData.add(phoneRow);
+
+/*
+ * Sets up the email data row
+ */
+
+// Sets up the row as a ContentValues object
+ContentValues emailRow = new ContentValues();
+
+// Specifies the MIME type for this data row (all data rows must be marked by their type)
+emailRow.put(
+        ContactsContract.Data.MIMETYPE,
+        ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE
+);
+
+// Adds the email address and its type to the row
+emailRow.put(ContactsContract.CommonDataKinds.Email.ADDRESS, email);
+
+// Adds the row to the array
+contactData.add(emailRow);
+
+/*
+ * Adds the array to the intent's extras. It must be a parcelable object in order to
+ * travel between processes. The device's contacts app expects its key to be
+ * Intents.Insert.DATA
+ */
+insertIntent.putParcelableArrayListExtra(ContactsContract.Intents.Insert.DATA, contactData);
+
+// Send out the intent to start the device's contacts app in its add contact activity.
+startActivity(insertIntent);
+
+

Toàn vẹn dữ liệu

+

+ Vì kho lưu giữ danh bạ chứa dữ liệu quan trọng và nhạy cảm mà người dùng cho là + đúng và cập nhật, Trình cung cấp Danh bạ có các quy tắc về toàn vẹn dữ liệu được định nghĩa rõ ràng. Bạn có + trách nhiệm tuân theo những quy tắc này khi sửa đổi dữ liệu danh bạ. Các quy tắc + quan trọng được liệt kê ở đây: +

+
+
+ Luôn thêm một hàng {@link android.provider.ContactsContract.CommonDataKinds.StructuredName} cho + mỗi hàng {@link android.provider.ContactsContract.RawContacts} mà bạn thêm. +
+
+ Hàng {@link android.provider.ContactsContract.RawContacts} không có một hàng + {@link android.provider.ContactsContract.CommonDataKinds.StructuredName} trong bảng + {@link android.provider.ContactsContract.Data} có thể gây ra sự cố trong khi + tổng hợp. +
+
+ Luôn liên kết các hàng {@link android.provider.ContactsContract.Data} mới với hàng + {@link android.provider.ContactsContract.RawContacts} mẹ của chúng. +
+
+ Mỗi hàng {@link android.provider.ContactsContract.Data} mà không được liên kết với một + {@link android.provider.ContactsContract.RawContacts} sẽ không hiển thị trong ứng dụng danh bạ + của thiết bị, và nó có thể gây ra sự cố với trình điều hợp đồng bộ. +
+
+ Chỉ thay đổi dữ liệu đối với những liên lạc thô mà bạn sở hữu. +
+
+ Nhớ rằng Trình cung cấp Danh bạ luôn quản lý dữ liệu từ vài + loại tài khoản/dịch vụ trực tuyến khác nhau. Bạn cần đảm bảo rằng ứng dụng của bạn chỉ sửa đổi + hoặc xóa dữ liệu đối với các hàng thuộc về bạn, và rằng nó chỉ chèn dữ liệu có + loại và tên tài khoản mà bạn kiểm soát. +
+
+ Luôn sử dụng các hằng số được định nghĩa trong {@link android.provider.ContactsContract} và các lớp con của nó + đối với thẩm quyền, URI nội dung, đường dẫn URI, tên cột, kiểu MIME, và các giá trị + {@link android.provider.ContactsContract.CommonDataKinds.CommonColumns#TYPE}. +
+
+ Sử dụng những hằng số này sẽ giúp bạn tránh gặp lỗi. Bạn cũng sẽ được thông báo bằng cảnh báo + từ trình biên dịch nếu bất kỳ hằng số nào không được chấp nhận. +
+
+

Hàng dữ liệu tùy chỉnh

+

+ Bằng cách tạo và sử dụng các kiểu MIME tùy chỉnh của chính mình, bạn có thể chèn, chỉnh sửa, xóa và truy xuất + các hàng dữ liệu của chính mình trong bảng {@link android.provider.ContactsContract.Data}. Các hàng của bạn + bị giới hạn bằng cách sử dụng cột được định nghĩa trong + {@link android.provider.ContactsContract.DataColumns}, mặc dù bạn có thể ánh xạ + tên cột theo kiểu của chính mình với tên cột mặc định. Trong ứng dụng danh bạ của thiết bị, + dữ liệu cho các hàng của bạn được hiển thị nhưng không thể chỉnh sửa hay xóa được, và người dùng không thể thêm + dữ liệu bổ sung. Để cho phép người dùng sửa đổi các hàng dữ liệu tùy chỉnh của mình, bạn phải cung cấp một hoạt động + trình chỉnh sửa trong ứng dụng của chính mình. +

+

+ Để hiển thị dữ liệu tùy chỉnh của mình, hãy cung cấp một tệp contacts.xml chứa một phần tử + <ContactsAccountType> và một hoặc nhiều phần tử con + <ContactsDataKind> của nó. Điều này được mô tả chi tiết hơn trong + phần <ContactsDataKind> element. +

+

+ Để tìm hiểu thêm về các kiểu MIME tùy chỉnh, hãy đọc hướng dẫn + + Tạo một Trình cung cấp Nội dung. +

+

Trình điều hợp Đồng bộ Trình cung cấp Danh bạ

+

+ Trình cung cấp Danh bạ được thiết kế riêng để xử lý đồng bộ hoá + dữ liệu danh bạ giữa một thiết bị và một dịch vụ trực tuyến. Điều này cho phép người dùng tải + dữ liệu hiện có xuống một thiết bị mới và tải dữ liệu hiện có lên một tài khoản mới. + Đồng bộ hoá cũng đảm bảo rằng người dùng có sẵn dữ liệu mới nhất, không phụ thuộc vào + nguồn của các bổ sung và thay đổi. Một ưu điểm khác của đồng bộ hoá đó là nó khiến + dữ liệu danh bạ có sẵn ngay cả khi thiết bị không được kết nối với mạng. +

+

+ Mặc dù bạn có thể triển khai đồng bộ hoá theo nhiều cách, hệ thống Android cung cấp + một khuôn khổ đồng bộ hóa bổ trợ có khả năng tự động hóa những tác vụ sau: +

    + +
  • + Kiểm tra sự sẵn sàng của mạng. +
  • +
  • + Lập lịch biểu và thực hiện đồng bộ hoá dựa trên tùy chọn của người dùng. +
  • +
  • + Khởi động lại những đồng bộ hoá đã dừng. +
  • +
+

+ Để sử dụng khuôn khổ này, bạn phải cung cấp một phần bổ trợ trình điều hợp đồng bộ. Mỗi trình điều hợp đồng bộ là duy nhất đối với + một dịch vụ và trình cung cấp nội dung, nhưng có thể xử lý nhiều tên tài khoản cho cùng dịch vụ. Khuôn khổ + cũng cho phép nhiều trình điều hợp đồng bộ cho cùng dịch vụ và trình cung cấp. +

+

Các lớp và tệp trình điều hợp đồng bộ

+

+ Bạn triển khai một trình điều hợp đồng bộ làm lớp con của + {@link android.content.AbstractThreadedSyncAdapter} và cài đặt nó như một phần của một ứng dụng + Android. Hệ thống biết về trình điều hợp đồng bộ từ các phần tử trong bản kê khai ứng dụng + của nó, và từ một tệp XML đặc biệt được chỉ đến trong bản kê khai. Tệp XML sẽ định nghĩa + loại tài khoản cho dịch vụ trực tuyến và thẩm quyền cho trình cung cấp nội dung, cùng nhau chúng + xác định duy nhất một trình điều hợp. Trình điều hợp đồng bộ không được kích hoạt cho tới khi người dùng thêm một + tài khoản cho loại tài khoản của trình điều hợp đồng bộ và kích hoạt đồng bộ hoá cho trình cung cấp + nội dung mà trình điều hợp đồng bộ sẽ đồng bộ cùng. Tại thời điểm đó, hệ thống bắt đầu quản lý trình điều hợp, + gọi nó nếu cần thiết để đồng bộ hoá giữa trình cung cấp nội dung và máy chủ. +

+

+ Lưu ý: Việc sử dụng một loại tài khoản để tham gia nhận biết trình điều hợp đồng bộ sẽ cho phép + hệ thống phát hiện và nhóm cùng nhau những trình điều hợp đồng bộ truy cập các dịch vụ khác nhau từ + cùng tổ chức. Ví dụ, các trình điều hợp đồng bộ cho dịch vụ trực tuyến của Google đều có cùng + loại tài khoản com.google. Khi người dùng thêm một tài khoản Google vào thiết bị của mình, tất cả + trình điều hợp đồng bộ được cài đặt cho dịch vụ Google được liệt kê cùng nhau; mỗi trình điều hợp đồng bộ + được liệt kê sẽ đồng bộ với một trình cung cấp nội dung khác nhau trên thiết bị. +

+

+ Vì hầu hết dịch vụ đều yêu cầu người dùng xác minh danh tính của họ trước khi truy cập + dữ liệu, hệ thống Android cung cấp một khuôn khổ xác thực tương tự như và thường + được sử dụng cùng với khuôn khổ của trình điều hợp đồng bộ. Khuôn khổ xác thực sử dụng + các trình xác thực bổ trợ là lớp con của + {@link android.accounts.AbstractAccountAuthenticator}. Một trình xác thực sẽ xác minh + danh tính của người dùng theo các bước sau: +

    +
  1. + Thu thập tên, mật khẩu hoặc thông tin tương tự của người dùng ( +thông tin xác thực của người dùng). +
  2. +
  3. + Gửi thông tin xác thực tới dịch vụ +
  4. +
  5. + Kiểm tra trả lời của dịch vụ. +
  6. +
+

+ Nếu dịch vụ chấp nhận thông tin xác thực, trình xác thực có thể + lưu giữ thông tin xác thực đó để sử dụng sau. Vì khuôn khổ trình xác thực bổ trợ, + {@link android.accounts.AccountManager} có thể cung cấp quyền truy cập bất kỳ token xác thực nào mà một trình xác thực + hỗ trợ và chọn hiện ra, chẳng hạn như token xác thực OAuth2. +

+

+ Mặc dù không yêu cầu xác thực, phần lớn dịch vụ danh bạ đều sử dụng nó. + Tuy nhiên, bạn không phải sử dụng khuôn khổ xác thực của Android để thực hiện xác thực. +

+

Triển khai trình điều hợp đồng bộ

+

+ Để triển khai một trình điều hợp đồng bộ cho Trình cung cấp Danh bạ, bạn bắt đầu bằng cách tạo một + ứng dụng Android chứa: +

+
+
+ Một thành phần {@link android.app.Service} để hồi đáp lại các yêu cầu từ hệ thống nhằm + gắn kết với trình điều hợp đồng bộ. +
+
+ Khi hệ thống muốn chạy đồng bộ hoá, nó gọi phương pháp + {@link android.app.Service#onBind(Intent) onBind()} của dịch vụ và nhận một + {@link android.os.IBinder} cho trình điều hợp đồng bộ. Điều này cho phép hệ thống thực hiện + lệnh gọi liên tiến trình tới các phương pháp của trình điều hợp. +

+ Trong ứng dụng mẫu + Trình điều hợp Đồng bộ Mẫu, tên lớp của dịch vụ này là + com.example.android.samplesync.syncadapter.SyncService. +

+
+
+ Trình điều hợp đồng bộ thực tế, được triển khai như một lớp con cụ thể của + {@link android.content.AbstractThreadedSyncAdapter}. +
+
+ Lớp này thực hiện công việc tải xuống dữ liệu từ máy chủ, tải lên dữ liệu từ + thiết bị, và xử lý xung đột. Công việc chính của trình điều hợp được + thực hiện trong phương pháp {@link android.content.AbstractThreadedSyncAdapter#onPerformSync( + Account, Bundle, String, ContentProviderClient, SyncResult) + onPerformSync()}. Lớp này phải được khởi tạo như một đối tượng duy nhất (singleton). +

+ Trong ứng dụng mẫu + Trình điều hợp Đồng bộ Mẫu, trình điều hợp đồng bộ được định nghĩa trong lớp + com.example.android.samplesync.syncadapter.SyncAdapter. +

+
+
+ Một lớp con của {@link android.app.Application}. +
+
+ Lớp này đóng vai trò như một nhà máy cho đối tượng duy nhất của trình điều hợp đồng bộ. Sử dụng phương pháp + {@link android.app.Application#onCreate()} để khởi tạo trình điều hợp đồng bộ, và + cung cấp một phương pháp "bộ nhận" tĩnh để trả đối tượng duy nhất về phương pháp + {@link android.app.Service#onBind(Intent) onBind()} của dịch vụ + của trình điều hợp đồng bộ. +
+
+ Tùy chọn: Một thành phần {@link android.app.Service} để hồi đáp lại + các yêu cầu từ hệ thống về xác thực người dùng. +
+
+ {@link android.accounts.AccountManager} khởi động dịch vụ này để bắt đầu tiến trình + xác thực. Phương pháp {@link android.app.Service#onCreate()} của dịch vụ này sẽ khởi tạo một + đối tượng trình xác thực. Khi hệ thống muốn xác thực một tài khoản người dùng cho trình điều hợp đồng bộ + của ứng dụng, nó sẽ gọi phương pháp + {@link android.app.Service#onBind(Intent) onBind()} của dịch vụ để nhận một + {@link android.os.IBinder} cho trình xác thực. Điều này cho phép hệ thống thực hiện + lệnh gọi liên tiến trình tới các phương pháp của trình xác thực. +

+ Trong ứng dụng mẫu + Trình điều hợp Đồng bộ Mẫu, tên lớp của dịch vụ này là + com.example.android.samplesync.authenticator.AuthenticationService. +

+
+
+ Tùy chọn: Một lớp con cụ thể của + {@link android.accounts.AbstractAccountAuthenticator} để xử lý các yêu cầu về + xác thực. +
+
+ Lớp này cung cấp các phương pháp mà {@link android.accounts.AccountManager} gọi ra + để xác thực các thông tin xác thực của người dùng với máy chủ. Các chi tiết của + tiến trình xác thực rất khác nhau dựa trên công nghệ máy chủ đang sử dụng. Bạn nên + tham khảo tài liệu cho phần mềm máy chủ của mình để tìm hiểu thêm về xác thực. +

+ Trong ứng dụng mẫu + Trình điều hợp Đồng bộ Mẫu, trình xác thực được định nghĩa trong lớp + com.example.android.samplesync.authenticator.Authenticator. +

+
+
+ Các tệp XML để định nghĩa trình điều hợp đồng bộ và trình xác thực cho hệ thống. +
+
+ Các thành phần dịch vụ trình điều hợp đồng bộ và trình xác thực đã nêu được + định nghĩa trong các phần tử +<service> + ở bản kê khai của ứng dụng. Những phần tử này + chứa các phần tử con +<meta-data> +mà cung cấp dữ liệu cụ thể cho + hệ thống: +
    +
  • + Phần tử +<meta-data> + cho dịch vụ trình điều hợp đồng bộ sẽ trỏ về + tệp XML res/xml/syncadapter.xml. Đến lượt mình, tệp này quy định + một URI cho dịch vụ web mà sẽ được đồng bộ hóa với Trình cung cấp Danh bạ, + và một loại tài khoản cho dịch vụ web. +
  • +
  • + Tùy chọn: Phần tử +<meta-data> + cho trình xác thực sẽ trỏ về tệp XML + res/xml/authenticator.xml. Đến lượt mình, tệp này quy định + loại tài khoản mà trình xác thực này hỗ trợ, cũng như các tài nguyên UI mà + xuất hiện trong tiến trình xác thực. Loại tài khoản được quy định trong phần tử + này phải giống như loại tài khoản được quy định cho trình điều hợp + đồng bộ. +
  • +
+
+
+

Dữ liệu từ Luồng Xã hội

+

+ Các bảng {@code android.provider.ContactsContract.StreamItems} và + {@code android.provider.ContactsContract.StreamItemPhotos} quản lý + dữ liệu đến từ các mạng xã hội. Bạn có thể ghi một trình điều hợp đồng bộ mà thêm dữ liệu luồng từ + mạng của chính mình vào những bảng này, hoặc bạn có thể đọc dữ liệu luồng từ những bảng này và + hiển thị nó trong ứng dụng của chính mình, hoặc cả hai. Với những tính năng này, các dịch vụ và ứng dụng + mạng xã hội của bạn có thể được tích hợp vào trải nghiệm mạng xã hội của Android. +

+

Văn bản từ luồng xã hội

+

+ Các mục dòng dữ liệu luôn được liên kết với một liên lạc thô. + {@code android.provider.ContactsContract.StreamItemsColumns#RAW_CONTACT_ID} liên kết với giá trị + _ID của liên lạc thô mới. Loại tài khoản và tên tài khoản của liên lạc thô + cũng được lưu giữ trong hàng mục dòng. +

+

+ Lưu giữ dữ liệu từ luồng của bạn vào những cột sau: +

+
+
+ {@code android.provider.ContactsContract.StreamItemsColumns#ACCOUNT_TYPE} +
+
+ Bắt buộc. Loại tài khoản của người dùng đối với liên lạc thô được liên kết với mục dòng + này. Nhớ đặt giá trị này khi bạn chèn một mục dòng. +
+
+ {@code android.provider.ContactsContract.StreamItemsColumns#ACCOUNT_NAME} +
+
+ Bắt buộc. Tên tài khoản của người dùng đối với liên lạc thô được liên kết với mục dòng + này. Nhớ đặt giá trị này khi bạn chèn một mục dòng. +
+
+ Cột mã định danh +
+
+ Bắt buộc. Bạn phải chèn các cột mã định danh sau khi chèn + một mục dòng: +
    +
  • + {@code android.provider.ContactsContract.StreamItemsColumns#CONTACT_ID}: Giá trị + {@code android.provider.BaseColumns#_ID} của liên lạc mà mục dòng + này được liên kết với. +
  • +
  • + {@code android.provider.ContactsContract.StreamItemsColumns#CONTACT_LOOKUP_KEY}: Giá trị + {@code android.provider.ContactsContract.ContactsColumns#LOOKUP_KEY} của liên lạc + mà mục dòng này được liên kết với. +
  • +
  • + {@code android.provider.ContactsContract.StreamItemsColumns#RAW_CONTACT_ID}: Giá trị + {@code android.provider.BaseColumns#_ID} của liên lạc thô mà mục dòng này + được liên kết với. +
  • +
+
+
+ {@code android.provider.ContactsContract.StreamItemsColumns#COMMENTS} +
+
+ Tùy chọn. Lưu giữ thông tin tóm tắt mà bạn có thể hiển thị ở phần đầu của một mục dòng. +
+
+ {@code android.provider.ContactsContract.StreamItemsColumns#TEXT} +
+
+ Văn bản của mục dòng, hoặc là nội dung đã được đăng bởi nguồn của mục đó, + hoặc là mô tả về một số hành động đã khởi tạo mục dòng. Cột này có thể chứa + bất kỳ hình ảnh tài nguyên định dạng và được nhúng nào mà có thể được kết xuất bởi + {@link android.text.Html#fromHtml(String) fromHtml()}. Trình cung cấp có thể cắt bớt hoặc + cắt ngắn bằng dấu ba chấm các nội dung dài, nhưng sẽ cố gắng tránh làm hỏng các tag. +
+
+ {@code android.provider.ContactsContract.StreamItemsColumns#TIMESTAMP} +
+
+ Xâu văn bản chứa thời gian mà mục dòng được chèn hoặc cập nhật, có + dạng mili giây trôi qua kể từ giờ epoch. Những ứng dụng chèn hoặc cập nhật mục dòng sẽ chịu + trách nhiệm duy trì cột này; nó không được tự động duy trì bởi + Trình cung cấp Danh bạ. +
+
+

+ Để hiển thị thông tin nhận dạng cho các mục dòng của bạn, hãy sử dụng + {@code android.provider.ContactsContract.StreamItemsColumns#RES_ICON}, + {@code android.provider.ContactsContract.StreamItemsColumns#RES_LABEL}, và + {@code android.provider.ContactsContract.StreamItemsColumns#RES_PACKAGE} để liên kết với các tài nguyên + trong ứng dụng của mình. +

+

+ Bảng {@code android.provider.ContactsContract.StreamItems} chứa các cột + {@code android.provider.ContactsContract.StreamItemsColumns#SYNC1} thông qua + {@code android.provider.ContactsContract.StreamItemsColumns#SYNC4} dành riêng để sử dụng + trình điều hợp đồng bộ. +

+

Ảnh từ luồng xã hội

+

+ Bảng {@code android.provider.ContactsContract.StreamItemPhotos} lưu giữ ảnh được liên kết + với một mục dòng. Cột + {@code android.provider.ContactsContract.StreamItemPhotosColumns#STREAM_ITEM_ID} của bảng + liên kết với các giá trị trong {@code android.provider.BaseColumns#_ID} của bảng + {@code android.provider.ContactsContract.StreamItems}. Các tham chiếu ảnh được lưu giữ trong + bảng ở những cột này: +

+
+
+ Cột {@code android.provider.ContactsContract.StreamItemPhotos#PHOTO} (một BLOB). +
+
+ Biểu diễn dạng nhị phân của ảnh, được trình cung cấp đổi kích cỡ để lưu giữ và hiển thị. + Cột này có sẵn để tương thích ngược với các phiên bản trước của Trình cung cấp + Danh bạ mà đã sử dụng nó để lưu giữ ảnh. Tuy nhiên, trong phiên bản hiện tại + bạn không nên sử dụng cột này để lưu giữ ảnh. Thay vào đó, hãy sử dụng + hoặc {@code android.provider.ContactsContract.StreamItemPhotosColumns#PHOTO_FILE_ID} hoặc + {@code android.provider.ContactsContract.StreamItemPhotosColumns#PHOTO_URI} (cả hai + đều được mô tả trong các điểm sau) để lưu giữ ảnh trong một tệp. Lúc này, cột này + chứa một hình thu nhỏ của ảnh sẵn sàng để đọc. +
+
+ {@code android.provider.ContactsContract.StreamItemPhotosColumns#PHOTO_FILE_ID} +
+
+ Một mã định danh dạng số của ảnh cho một liên lạc thô. Nối giá trị này với hằng số + {@link android.provider.ContactsContract.DisplayPhoto#CONTENT_URI DisplayPhoto.CONTENT_URI} + để nhận một URI nội dung trỏ về một tệp ảnh đơn, rồi gọi + {@link android.content.ContentResolver#openAssetFileDescriptor(Uri, String) + openAssetFileDescriptor()} để nhận một điều khiển (handle) cho tệp ảnh. +
+
+ {@code android.provider.ContactsContract.StreamItemPhotosColumns#PHOTO_URI} +
+
+ Một URI nội dung trỏ trực tiếp tới tệp ảnh cho ảnh được đại diện bởi hàng này. + Gọi {@link android.content.ContentResolver#openAssetFileDescriptor(Uri, String) + openAssetFileDescriptor()} bằng URI này để nhận một điều khiển (handle) cho tệp ảnh. +
+
+

Sử dụng các bảng luồng xã hội

+

+ Những bảng này hoạt động giống như các bảng chính khác trong Trình cung cấp Danh bạ, ngoại trừ: +

+
    +
  • + Những bảng này yêu cầu quyền truy cập bổ sung. Để đọc từ chúng, ứng dụng của bạn + phải có quyền {@code android.Manifest.permission#READ_SOCIAL_STREAM}. Để + sửa đổi chúng, ứng dụng của bạn phải có quyền + {@code android.Manifest.permission#WRITE_SOCIAL_STREAM}. +
  • +
  • + Đối với bảng {@code android.provider.ContactsContract.StreamItems}, số hàng + được lưu giữ cho mỗi liên lạc thô sẽ bị giới hạn. Sau khi đạt đến giới hạn này, + Trình cung cấp Danh bạ sẽ tạo khoảng trống cho các hàng mục dòng mới bằng cách tự động xóa + những hàng có + {@code android.provider.ContactsContract.StreamItemsColumns#TIMESTAMP} lâu nhất. Để nhận + giới hạn, hãy phát hành một truy vấn tới URI nội dung + {@code android.provider.ContactsContract.StreamItems#CONTENT_LIMIT_URI}. Bạn có thể để + tất cả các tham đối khác ngoài URI nội dung được đặt về null. Truy vấn + trả về một Con chạy chứa một hàng đơn, với cột đơn + {@code android.provider.ContactsContract.StreamItems#MAX_ITEMS}. +
  • +
+ +

+ Lớp {@code android.provider.ContactsContract.StreamItems.StreamItemPhotos} định nghĩa một + bảng con {@code android.provider.ContactsContract.StreamItemPhotos} chứa các hàng ảnh + cho một mục dòng đơn. +

+

Tương tác từ luồng xã hội

+

+ Dữ liệu từ luồng xã hội được quản lý bởi Trình cung cấp Danh bạ, kết hợp với + ứng dụng danh bạ của thiết bị, cung cấp một cách hiệu quả để kết nối hệ thống mạng xã hội của bạn + với các liên lạc hiện tại. Có sẵn những tính năng sau: +

+
    +
  • + Bằng cách đồng bộ dịch vụ mạng xã hội của bạn với Trình cung cấp Danh bạ bằng một trình điều hợp + đồng bộ, bạn có thể truy xuất hoạt động gần đây đối với danh bạ của một người dùng và lưu giữ nó trong + các bảng {@code android.provider.ContactsContract.StreamItems} và + {@code android.provider.ContactsContract.StreamItemPhotos} để sử dụng sau. +
  • +
  • + Bên cạnh việc đồng bộ hoá thường xuyên, bạn có thể kích khởi trình điều hợp đồng bộ của mình để truy xuất + dữ liệu bổ sung khi người dùng chọn một liên lạc để xem. Điều này cho phép trình điều hợp đồng bộ của bạn + truy xuất ảnh độ phân giải cao và các mục dòng gần đây nhất cho liên lạc đó. +
  • +
  • + Bằng cách đăng ký một thông báo với ứng dụng danh bạ của thiết bị và Trình cung cấp + Danh bạ, bạn có thể nhận một ý định khi một liên lạc được xem, và tại thời điểm đó, + cập nhật trạng thái của liên lạc đó từ dịch vụ của bạn. Phương pháp này có thể nhanh hơn và sử dụng ít + băng thông hơn việc thực hiện đồng bộ đầy đủ với một trình điều hợp đồng bộ. +
  • +
  • + Người dùng có thể thêm một liên lạc vào dịch vụ mạng xã hội của mình trong khi đang xem liên lạc đó + trong ứng dụng danh bạ của thiết bị. Bạn kích hoạt điều này bằng tính năng "mời liên lạc", + theo đó cho phép kết hợp một hoạt động để thêm một liên lạc hiện có vào mạng + của bạn, và một tệp XML để cung cấp cho ứng dụng danh bạ của thiết bị và + Trình cung cấp Danh bạ thông tin chi tiết về ứng dụng của bạn. +
  • +
+

+ Đồng bộ hóa thường xuyên các mục dòng với Trình cung cấp Danh bạ giống như + các trường hợp đồng bộ hoá khác. Để tìm hiểu thêm về đồng bộ hoá, hãy xem phần + Trình điều hợp Đồng bộ Trình cung cấp Danh bạ. Việc đăng ký thông tin và + mời liên lạc được đề cập trong hai phần tiếp theo. +

+

Đăng ký để xử lý các lượt xem mạng xã hội

+

+ Để đăng ký để trình điều hợp đồng bộ của bạn nhận thông báo khi người dùng xem một liên lạc do + trình điều hợp đồng bộ của bạn quản lý: +

+
    +
  1. + Tạo một tệp có tên contacts.xml trong thư mục res/xml/ + của dự án của bạn. Nếu đã có tệp này, bạn có thể bỏ qua bước này. +
  2. +
  3. + Trong tệp này, hãy thêm phần tử +<ContactsAccountType xmlns:android="http://schemas.android.com/apk/res/android">. + Nếu phần tử này đã tồn tại, bạn có thể bỏ qua bước này. +
  4. +
  5. + Để đăng ký một dịch vụ được thông báo khi người dùng mở trang chi tiết của một liên lạc trong + ứng dụng danh bạ của thiết bị, hãy thêm thuộc tính + viewContactNotifyService="serviceclass" vào phần tử, trong đó + serviceclass là tên lớp được đáp ứng đầy đủ của dịch vụ + mà sẽ nhận ý định từ ứng dụng danh bạ của thiết bị. Đối với dịch vụ + trình thông báo, hãy sử dụng một lớp mở rộng {@link android.app.IntentService}, để cho phép dịch vụ + nhận các ý định. Dữ liệu trong ý định đến chứa URI nội dung của liên lạc + thô mà người dùng đã nhấp vào. Từ dịch vụ trình thông báo, bạn có thể gắn kết với rồi gọi trình điều hợp đồng bộ + của bạn để cập nhật dữ liệu cho liên lạc thô. +
  6. +
+

+ Để đăng ký một hoạt động sẽ được gọi khi người dùng nhấp vào một mục dòng hay ảnh hoặc cả hai: +

+
    +
  1. + Tạo một tệp có tên contacts.xml trong thư mục res/xml/ + của dự án của bạn. Nếu đã có tệp này, bạn có thể bỏ qua bước này. +
  2. +
  3. + Trong tệp này, hãy thêm phần tử +<ContactsAccountType xmlns:android="http://schemas.android.com/apk/res/android">. + Nếu phần tử này đã tồn tại, bạn có thể bỏ qua bước này. +
  4. +
  5. + Để đăng ký một trong các hoạt động của bạn sẽ xử lý khi người dùng nhấp vào một mục dòng trong + ứng dụng danh bạ của thiết bị, hãy thêm thuộc tính + viewStreamItemActivity="activityclass" vào phần tử đó, trong đó + activityclass là tên lớp được xác định đầy đủ của hoạt động + mà sẽ nhận ý định từ ứng dụng danh bạ của thiết bị. +
  6. +
  7. + Để đăng ký một trong các hoạt động của bạn sẽ xử lý khi người dùng nhấp vào một ảnh luồng trong + ứng dụng danh bạ của thiết bị, hãy thêm thuộc tính + viewStreamItemPhotoActivity="activityclass" vào phần tử đó, trong đó + activityclass là tên lớp được xác định đầy đủ của hoạt động + mà sẽ nhận ý định từ ứng dụng danh bạ của thiết bị. +
  8. +
+

+ Phần tử <ContactsAccountType> được mô tả chi tiết hơn trong mục + phần tử <ContactsAccountType>. +

+

+ Ý định đến chứa URI nội dung của mục hoặc ảnh mà người dùng đã nhấp vào. + Để có các hoạt động riêng cho các mục văn bản và ảnh, hãy sử dụng cả hai thuộc tính trong cùng tệp. +

+

Tương tác với dịch vụ mạng xã hội của bạn

+

+ Người dùng không phải rời ứng dụng danh bạ của thiết bị để mời một liên lạc tới trang mạng xã hội + của bạn. Thay vào đó, bạn có thể thiết đặt để ứng dụng danh bạ của thiết bị gửi một ý định để mời + liên lạc đó tới một trong các hoạt động của mình. Để thiết đặt điều này: +

+
    +
  1. + Tạo một tệp có tên contacts.xml trong thư mục res/xml/ + của dự án của bạn. Nếu đã có tệp này, bạn có thể bỏ qua bước này. +
  2. +
  3. + Trong tệp này, hãy thêm phần tử +<ContactsAccountType xmlns:android="http://schemas.android.com/apk/res/android">. + Nếu phần tử này đã tồn tại, bạn có thể bỏ qua bước này. +
  4. +
  5. + Thêm các thuộc tính sau: +
      +
    • inviteContactActivity="activityclass"
    • +
    • + inviteContactActionLabel="@string/invite_action_label" +
    • +
    + Giá trị activityclass là tên lớp được xác định đầy đủ của hoạt động + mà sẽ nhận được ý định. Giá trị invite_action_label + là một xâu văn bản được hiển thị trong menu Thêm Kết nối trong ứng dụng danh bạ + của thiết bị. +
  6. +
+

+ Lưu ý: ContactsSource là một tên tag không được chấp nhận đối với + ContactsAccountType. +

+

Tham chiếu contacts.xml

+

+ Tệp contacts.xml chứa các phần tử XML có chức năng kiểm soát tương tác giữa + trình điều hợp đồng bộ và ứng dụng của bạn với ứng dụng danh bạ và Trình cung cấp Danh bạ. Những + phần tử này được mô tả trong các mục sau. +

+

Thành phần <ContactsAccountType>

+

+ Phần tử <ContactsAccountType> kiểm soát tương tác giữa + ứng dụng của bạn với ứng dụng danh bạ. Nó có những cú pháp sau: +

+
+<ContactsAccountType
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        inviteContactActivity="activity_name"
+        inviteContactActionLabel="invite_command_text"
+        viewContactNotifyService="view_notify_service"
+        viewGroupActivity="group_view_activity"
+        viewGroupActionLabel="group_action_text"
+        viewStreamItemActivity="viewstream_activity_name"
+        viewStreamItemPhotoActivity="viewphotostream_activity_name">
+
+

+ chứa trong: +

+

+ res/xml/contacts.xml +

+

+ có thể chứa: +

+

+ <ContactsDataKind> +

+

+ Mô tả: +

+

+ Khai báo các thành phần Android và nhãn UI mà cho phép người dùng mời một trong các liên lạc của mình đến + một mạng xã hội, thông báo người dùng khi một trong các luồng mạng xã hội của họ được cập nhật, + v.v. +

+

+ Để ý rằng tiền tố thuộc tính android: không nhất thiết áp dụng cho các thuộc tính + của <ContactsAccountType>. +

+

+ Thuộc tính: +

+
+
{@code inviteContactActivity}
+
+ Tên lớp được xác định đầy đủ của hoạt động trong ứng dụng của bạn mà bạn muốn + kích hoạt khi người dùng chọn Thêm kết nối từ ứng dụng danh bạ + của thiết bị. +
+
{@code inviteContactActionLabel}
+
+ Một xâu văn bản được hiển thị cho hoạt động được quy định trong + {@code inviteContactActivity}, trong menu Thêm kết nối. + Ví dụ, bạn có thể sử dụng xâu "Follow in my network". Bạn có thể sử dụng mã định danh + tài nguyên xâu cho nhãn này. +
+
{@code viewContactNotifyService}
+
+ Tên lớp được xác định đầy đủ của một dịch vụ trong ứng dụng của bạn mà sẽ nhận được + thông báo khi người dùng xem một liên lạc. Thông báo này được gửi từ ứng dụng danh bạ + của thiết bị; nó cho phép ứng dụng của bạn tạm hoãn các thao tác dùng nhiều dữ liệu tới + khi cần. Ví dụ, ứng dụng của bạn có thể hồi đáp lại thông báo này + bằng cách đọc và hiển thị ảnh độ phân giải cao của danh bạ và các mục dòng mạng xã hội + gần đây nhất. Tính năng này được mô tả chi tiết hơn trong phần + Tương tác với luồng xã hội. Bạn có thể thấy một + ví dụ về dịch vụ thông báo trong tệp NotifierService.java trong ứng dụng mẫu + SampleSyncAdapter +. +
+
{@code viewGroupActivity}
+
+ Tên lớp được xác định đầy đủ của một hoạt động trong ứng dụng của bạn mà có thể + hiển thị thông tin nhóm. Khi người dùng nhấp vào nhãn nhóm trong ứng dụng danh bạ + của thiết bị, UI cho hoạt động này sẽ được hiển thị. +
+
{@code viewGroupActionLabel}
+
+ Nhãn mà ứng dụng danh bạ hiển thị cho một điều khiển UI có cho phép + người dùng xem các nhóm trong ứng dụng của bạn. +

+ Ví dụ, nếu bạn cài đặt ứng dụng Google+ trên thiết bị của mình và bạn đồng bộ + Google+ với ứng dụng danh bạ, bạn sẽ thấy các vòng tròn Google+ được liệt kê thành các nhóm + trong tab Nhóm của ứng dụng danh bạ của bạn. Nếu bạn nhấp vào một vòng tròn + Google+, bạn sẽ thấy những người trong vòng tròn đó được liệt kê thành một "nhóm". Phía bên trên của + hiển thị, bạn sẽ thấy một biểu tượng Google+; nếu bạn nhấp vào đó, điều khiển sẽ chuyển sang ứng dụng + Google+. Ứng dụng danh bạ làm điều này bằng + {@code viewGroupActivity}, bằng cách sử dụng biểu tượng Google+ làm giá trị của + {@code viewGroupActionLabel}. +

+

+ Một mã định danh tài nguyên xâu được cho phép cho thuộc tính này. +

+
+
{@code viewStreamItemActivity}
+
+ Tên lớp được xác định đầy đủ của một hoạt động trong ứng dụng của bạn mà + ứng dụng danh bạ của thiết bị khởi chạy khi người dùng nhấp vào một mục dòng đối với một liên lạc thô. +
+
{@code viewStreamItemPhotoActivity}
+
+ Tên lớp được xác định đầy đủ của một hoạt động trong ứng dụng của bạn mà + ứng dụng danh bạ của thiết bị khởi chạy khi người dùng nhấp vào một ảnh trong mục dòng + đối với một liên lạc thô. +
+
+

Phần tử <ContactsDataKind>

+

+ Phần tử <ContactsDataKind> kiểm soát việc hiển thị các hàng + dữ liệu tùy chỉnh của ứng dụng của bạn trong UI của ứng dụng danh bạ. Nó có những cú pháp sau: +

+
+<ContactsDataKind
+        android:mimeType="MIMEtype"
+        android:icon="icon_resources"
+        android:summaryColumn="column_name"
+        android:detailColumn="column_name">
+
+

+ chứa trong: +

+<ContactsAccountType> +

+ Mô tả: +

+

+ Sử dụng phần tử này để ứng dụng danh bạ hiển thị các nội dung trong một hàng dữ liệu tùy chỉnh như + một phần chi tiết của một liên lạc thô. Mỗi phần tử con <ContactsDataKind> + của <ContactsAccountType> đại diện cho một kiểu hàng dữ liệu tùy chỉnh mà trình điều hợp + đồng bộ của bạn thêm vào bảng {@link android.provider.ContactsContract.Data}. Thêm một phần tử + <ContactsDataKind> cho mỗi kiểu MIME tùy chỉnh mà bạn sử dụng. Bạn không phải + thêm phần tử nếu có một hàng dữ liệu tùy chỉnh mà bạn không muốn hiển thị dữ liệu. +

+

+ Thuộc tính: +

+
+
{@code android:mimeType}
+
+ Kiểu MIME tùy chỉnh mà bạn đã định nghĩa cho một trong các kiểu hàng dữ liệu tùy chỉnh của bạn trong bảng + {@link android.provider.ContactsContract.Data}. Ví dụ, giá trị + vnd.android.cursor.item/vnd.example.locationstatus có thể là một kiểu + MIME tùy chỉnh cho một hàng dữ liệu có chức năng ghi lại vị trí được biết đến cuối cùng của một liên lạc. +
+
{@code android:icon}
+
+ Một tài nguyên + có thể vẽ của Android + mà ứng dụng danh bạ hiển thị bên cạnh dữ liệu của bạn. Sử dụng nó để thể hiện với + người dùng rằng dữ liệu xuất phát từ dịch vụ của bạn. +
+
{@code android:summaryColumn}
+
+ Tên cột của giá trị thứ nhất trong hai giá trị được truy xuất từ hàng dữ liệu. Giá trị + được hiển thị là dòng thứ nhất của mục nhập cho hàng dữ liệu này. Dòng thứ nhất có + mục đích sử dụng làm bản tóm tắt dữ liệu, nhưng điều đó là tùy chọn. Xem thêm + android:detailColumn. +
+
{@code android:detailColumn}
+
+ Tên cột của giá trị thứ hai trong hai giá trị được truy xuất từ hàng dữ liệu. Giá trị + được hiển thị là dòng thứ hai của mục nhập cho hàng dữ liệu này. Xem thêm + {@code android:summaryColumn}. +
+
+

Các Tính năng Bổ sung của Trình cung cấp Danh bạ

+

+ Bên cạnh các tính năng chính được mô tả trong các phần trước, Trình cung cấp Danh bạ còn cung cấp + những tính năng hữu ích sau khi làm việc với dữ liệu danh bạ: +

+
    +
  • Nhóm liên lạc
  • +
  • Tính năng ảnh
  • +
+

Nhóm liên lạc

+

+ Trình cung cấp Danh bạ có thể tùy chọn đánh nhãn các bộ sưu tập liên lạc có liên quan bằng dữ liệu + nhóm. Nếu máy chủ liên kết với một tài khoản người dùng + muốn duy trì nhóm, trình điều hợp đồng bộ cho loại tài khoản của tài khoản đó sẽ chuyển + dữ liệu nhóm giữa Trình cung cấp Danh bạ và máy chủ. Khi người dùng thêm một liên lạc mới vào + máy chủ, trình điều hợp đồng bộ phải thêm nhóm mới + vào bảng {@link android.provider.ContactsContract.Groups}. Nhóm hoặc các nhóm mà một liên lạc + thô thuộc về được lưu giữ trong bảng {@link android.provider.ContactsContract.Data}, bằng cách sử dụng + kiểu MIME {@link android.provider.ContactsContract.CommonDataKinds.GroupMembership}. +

+

+ Nếu bạn đang thiết kế một trình điều hợp đồng bộ mà sẽ thêm dữ liệu liên lạc thô từ + máy chủ tới Trình cung cấp Danh bạ, và bạn không sử dụng các nhóm, khi đó bạn cần báo cho + Trình cung cấp làm các dữ liệu của bạn thấy được. Trong đoạn mã được thực hiện khi một người dùng thêm một tài khoản + vào thiết bị, hãy cập nhật hàng {@link android.provider.ContactsContract.Settings} + mà Trình cung cấp Danh bạ thêm cho tài khoản. Trong hàng này, đặt giá trị của cột + {@link android.provider.ContactsContract.SettingsColumns#UNGROUPED_VISIBLE + Settings.UNGROUPED_VISIBLE} thành 1. Khi bạn làm vậy, Trình cung cấp Danh bạ sẽ luôn + làm cho dữ liệu danh bạ của bạn thấy được, ngay cả khi bạn không sử dụng nhóm. +

+

Ảnh liên lạc

+

+ Bảng {@link android.provider.ContactsContract.Data} lưu giữ ảnh thành hàng với kiểu MIME + {@link android.provider.ContactsContract.CommonDataKinds.Photo#CONTENT_ITEM_TYPE + Photo.CONTENT_ITEM_TYPE}. Cột + {@link android.provider.ContactsContract.RawContactsColumns#CONTACT_ID} của hàng được liên kết với cột + {@code android.provider.BaseColumns#_ID} của liên lạc thô mà nó thuộc về. + Lớp {@link android.provider.ContactsContract.Contacts.Photo} định nghĩa một bảng con của + {@link android.provider.ContactsContract.Contacts} chứa thông tin ảnh về ảnh chính + của một liên lạc, đây là ảnh chính của liên lạc thô chính của liên lạc. Tương tự, + lớp {@link android.provider.ContactsContract.RawContacts.DisplayPhoto} định nghĩa một bảng con + của {@link android.provider.ContactsContract.RawContacts} chứa thông tin ảnh đối với ảnh chính + của một liên lạc thô. +

+

+ Tài liệu tham khảo cho {@link android.provider.ContactsContract.Contacts.Photo} và + {@link android.provider.ContactsContract.RawContacts.DisplayPhoto} có các ví dụ về + việc truy xuất thông tin ảnh. Không có lớp thuận tiện cho việc truy xuất hình thu nhỏ + chính đối với một liên lạc thô, nhưng bạn có thể gửi một truy vấn tới bảng + {@link android.provider.ContactsContract.Data}, chọn + {@code android.provider.BaseColumns#_ID} của liên lạc thô, + {@link android.provider.ContactsContract.CommonDataKinds.Photo#CONTENT_ITEM_TYPE + Photo.CONTENT_ITEM_TYPE}, và cột {@link android.provider.ContactsContract.Data#IS_PRIMARY} + để tìm hàng ảnh chính của liên lạc thô. +

+

+ Dữ liệu từ luồng xã hội đối với một người cũng có thể bao gồm ảnh. Những ảnh này được lưu giữ trong bảng + {@code android.provider.ContactsContract.StreamItemPhotos}, được mô tả chi tiết hơn + trong phần Ảnh từ luồng xã hội. +

diff --git a/docs/html-intl/intl/vi/guide/topics/providers/content-provider-basics.jd b/docs/html-intl/intl/vi/guide/topics/providers/content-provider-basics.jd new file mode 100644 index 0000000000000000000000000000000000000000..5f868cacf5aea33b0b795e2e405cfa7de0c01a12 --- /dev/null +++ b/docs/html-intl/intl/vi/guide/topics/providers/content-provider-basics.jd @@ -0,0 +1,1196 @@ +page.title=Nội dung Cơ bản về Trình cung cấp Nội dung +@jd:body + + + +

+ Trình cung cấp nội dung quản lý truy cập vào một kho dữ liệu tập trung. Trình cung cấp + là bộ phận của một ứng dụng Android, nó thường cung cấp UI của chính mình để làm việc cùng + dữ liệu. Tuy nhiên, trình cung cấp nội dung được thiết kế chủ yếu cho các ứng dụng khác + sử dụng, giúp truy cập trình cung cấp bằng cách sử dụng một đối tượng máy khách cung cấp. Cùng nhau, trình cung cấp + và máy khách cung cấp sẽ mang đến một giao diện nhất quán, tiêu chuẩn cho dữ liệu, giao diện này + cũng đồng thời xử lý truyền thông liên tiến trình và bảo mật truy cập dữ liệu. +

+

+ Chủ đề này đề cập đến những nội dung cơ bản sau đây: +

+
    +
  • Cách trình cung cấp nội dung hoạt động.
  • +
  • API bạn sử dụng để truy xuất dữ liệu từ một trình cung cấp nội dung.
  • +
  • API bạn sử dụng để chèn, cập nhật, hoặc xóa dữ liệu trong một trình cung cấp nội dung.
  • +
  • Các tính năng API khác tạo điều kiện làm việc cùng các trình cung cấp.
  • +
+ + +

Tổng quan

+

+ Trình cung cấp nội dung trình bày dữ liệu cho các ứng dụng bên ngoài dưới dạng một hoặc nhiều bảng tương tự + như các bảng được tìm thấy trong một cơ sở dữ liệu quan hệ. Mỗi hàng thể hiện một thực thể của một số kiểu dữ liệu + mà trình cung cấp thu thập, và mỗi cột trong hàng thể hiện một phần riêng biệt của dữ liệu được thu thập + đối với một thực thể. +

+

+ Ví dụ, một trong các trình cung cấp tích hợp trong nền tảng Android đó là từ điển người dùng, nó + lưu giữ chính tả của những từ phi tiêu chuẩn mà người dùng muốn giữ lại. Bảng 1 minh họa + cách mà dữ liệu có thể được trình bày trong bảng của trình cung cấp này: +

+

+ Bảng 1: Bảng từ điển người dùng mẫu. +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
từid ứng dụngtần suấtbản địa_ID
mapreduceuser1100en_US1
precompileruser14200fr_FR2
appletuser2225fr_CA3
constuser1255pt_BR4
intuser5100en_UK5
+

+ Trong bảng 1, mỗi hàng thể hiện một thực thể của một từ mà có thể không thấy có + trong từ điển chuẩn. Mỗi cột thể hiện một số dữ liệu cho từ đó, chẳng hạn như + từ bản địa được dùng lần đầu cho từ đó. Tiêu đề cột là các tên cột được lưu giữ trong + trình cung cấp. Để tham khảo tới bản địa của một hàng, bạn tham khảo tới cột locale của hàng đó. Đối với + trình cung cấp này, cột _ID đóng vai trò là cột "khóa chính" mà + trình cung cấp tự động duy trì. +

+

+ Lưu ý: Trình cung cấp không bắt buộc phải có một khóa chính, và không bắt buộc phải + sử dụng _ID làm tên cột của một khóa chính nếu có khóa. Tuy nhiên, + nếu bạn muốn gắn kết dữ liệu từ một trình cung cấp với một {@link android.widget.ListView}, một trong các + tên cột sẽ phải là _ID. Yêu cầu này được giải thích chi tiết hơn trong + phần Hiển thị các kết quả truy vấn. +

+

Truy cập một trình cung cấp

+

+ Một ứng dụng truy cập dữ liệu từ một trình cung cấp nội dung bằng + một đối tượng máy khách {@link android.content.ContentResolver}. Đối tượng này có các phương pháp để gọi + những phương pháp có tên giống nhau trong đối tượng trình cung cấp, một thực thể của một trong những lớp con + cụ thể của {@link android.content.ContentProvider}. Các phương pháp + {@link android.content.ContentResolver} cung cấp các chức năng + "CRUD" (tạo, truy xuất, cập nhật, và xóa) cơ bản của thiết bị lưu trữ liên tục. +

+

+ Đối tượng {@link android.content.ContentResolver} trong tiến trình + của ứng dụng máy khách và đối tượng {@link android.content.ContentProvider} trong ứng dụng mà sở hữu + trình cung cấp sẽ tự động xử lý truyền thông liên tiến trình. + {@link android.content.ContentProvider} cũng đóng vai trò như một lớp rút gọn giữa kho dữ liệu + của nó và biểu diễn bên ngoài của dữ liệu dưới dạng bảng. +

+

+ Lưu ý: Để truy cập một trình cung cấp, ứng dụng của bạn thường phải yêu cầu các quyền + cụ thể trong tệp bản kê khai của mình. Điều này được mô tả chi tiết hơn trong phần + Quyền của Trình cung cấp Nội dung +

+

+ Ví dụ, để có một danh sách các từ và nội dung bản địa của chúng từ Trình cung cấp Từ điển Người dùng, + bạn hãy gọi {@link android.content.ContentResolver#query ContentResolver.query()}. + Phương pháp {@link android.content.ContentResolver#query query()} sẽ gọi phương pháp + {@link android.content.ContentProvider#query ContentProvider.query()} được định nghĩa bởi + Trình cung cấp Từ điển Người dùng. Các dòng mã sau thể hiện một lệnh gọi + {@link android.content.ContentResolver#query ContentResolver.query()}: +

+

+// Queries the user dictionary and returns results
+mCursor = getContentResolver().query(
+    UserDictionary.Words.CONTENT_URI,   // The content URI of the words table
+    mProjection,                        // The columns to return for each row
+    mSelectionClause                    // Selection criteria
+    mSelectionArgs,                     // Selection criteria
+    mSortOrder);                        // The sort order for the returned rows
+
+

+ Bảng 2 cho biết các tham đối tới + {@link android.content.ContentResolver#query + query(Uri,projection,selection,selectionArgs,sortOrder)} khớp với một câu lệnh SQL SELECT như thế nào: +

+

+ Bảng 2: Query() so với truy vấn SQL. +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
tham đối query()Từ khóa/tham số SELECTLưu ý
UriFROM table_nameUri ánh xạ tới bảng trong trình cung cấp có tên table_name.
projectioncol,col,col,... + projection là một mảng gồm các cột nên được đưa vào đối với mỗi hàng + được truy xuất. +
selectionWHERE col = valueselection quy định các tiêu chí để lựa chọn hàng.
selectionArgs + (Không có sự tương đương chính xác. Các tham đối lựa chọn sẽ thay thế các chỗ dành sẵn ? trong + mệnh đề lựa chọn.) +
sortOrderORDER BY col,col,... + sortOrder quy định thứ tự các hàng xuất hiện trong + {@link android.database.Cursor} được trả về. +
+

URI Nội dung

+

+ URI nội dung là một URI xác định dữ liệu trong một trình cung cấp. URI nội dung + bao gồm tên biểu tượng của toàn bộ trình cung cấp (quyền của nó) và một + tên trỏ đến một bảng (đường dẫn). Khi bạn gọi + một phương pháp máy khách để truy cập một bảng trong một trình cung cấp, URI nội dung cho bảng là một trong các + tham đối. +

+

+ Trong các dòng mã trước, hằng số + {@link android.provider.UserDictionary.Words#CONTENT_URI} chứa URI nội dung của + bảng "từ" của từ điển người dùng. Đối tượng {@link android.content.ContentResolver} + sẽ phân tích quyền của URI, và sử dụng nó để "giải quyết" trình cung cấp bằng cách + so sánh quyền với một bảng hệ thống của các trình cung cấp đã biết. Khi đó, + {@link android.content.ContentResolver} có thể phân phối các tham đối truy vấn tới đúng + trình cung cấp. +

+

+ {@link android.content.ContentProvider} sử dụng phần đường dẫn của URI nội dung nhằm chọn + bảng để truy cập. Trình cung cấp thường có một đường dẫn cho mỗi bảng mà nó hiện ra. +

+

+ Trong các dòng mã trước, URI đầy đủ cho bảng "từ" là: +

+
+content://user_dictionary/words
+
+

+ trong đó xâu user_dictionary là quyền của trình cung cấp, và +xâu words là đường dẫn của bảng. Xâu + content:// (lược đồ) sẽ luôn có mặt, + và xác định đây là một URI nội dung. +

+

+ Nhiều trình cung cấp cho phép bạn truy cập một hàng đơn lẻ trong một bảng bằng cách nối một giá trị ID + với đuôi của URI. Ví dụ, để truy xuất một hàng có _ID là + 4 từ một từ điển người dùng, bạn có thể sử dụng URI nội dung này: +

+
+Uri singleUri = ContentUris.withAppendedId(UserDictionary.Words.CONTENT_URI,4);
+
+

+ Bạn thường sử dụng các giá trị id khi bạn đã truy xuất một tập hợp các hàng, sau đó muốn cập nhật hoặc xóa + một trong số chúng. +

+

+ Lưu ý: Các lớp {@link android.net.Uri} và {@link android.net.Uri.Builder} + chứa các phương pháp thuận tiện để xây dựng đối tượng URI định dạng tốt từ các xâu. + {@link android.content.ContentUris} chứa các phương pháp thuận tiện để nối các giá trị id với + một URI. Đoạn mã HTML trước sử dụng {@link android.content.ContentUris#withAppendedId +withAppendedId()} để nối một id với URI nội dung Từ điển Người dùng. +

+ + + +

Truy xuất Dữ liệu từ Trình cung cấp

+

+ Phần này mô tả cách truy xuất dữ liệu từ một trình cung cấp bằng cách sử dụng Trình cung cấp Từ điển Người dùng + làm ví dụ. +

+

+ Để giải thích rõ, đoạn mã HTML trong phần này gọi + {@link android.content.ContentResolver#query ContentResolver.query()} trên "luồng UI"". Tuy nhiên, trong + mã thực sự, bạn nên thực hiện các truy vấn không đồng bộ trên một luồng riêng. Một cách để làm + điều này đó là sử dụng lớp {@link android.content.CursorLoader}, nó được mô tả chi tiết hơn + trong hướng dẫn + Trình tải. Bênh cạnh đó, các dòng mã chỉ là đoạn mã HTML; chúng không thể hiện một ứng dụng + hoàn chỉnh. +

+

+ Để truy xuất dữ liệu từ một trình cung cấp, hãy làm theo các bước cơ bản sau: +

+
    +
  1. + Yêu cầu quyền truy cập đọc cho trình cung cấp. +
  2. +
  3. + Định nghĩa mã để gửi một truy vấn tới trình cung cấp. +
  4. +
+

Yêu cầu quyền truy cập đọc

+

+ Để truy xuất dữ liệu từ một trình cung cấp, ứng dụng của bạn cần "quyền truy cập đọc" cho + trình cung cấp. Bạn không thể yêu cầu quyền này trong thời gian chạy; thay vào đó, bạn phải chỉ định rằng + bạn cần quyền này trong bản kê khai của mình bằng cách sử dụng phần tử +<uses-permission> + và tên quyền chính xác được định nghĩa bởi + trình cung cấp. Khi bạn chỉ định phần tử này trong bản kê khai của mình, bạn đang thực tế hóa "yêu cầu" quyền + này cho ứng dụng của mình. Khi người dùng cài đặt ứng dụng của bạn, họ ngầm hiểu cấp + yêu cầu này. +

+

+ Để tìm tên chính xác của quyền truy cập đọc cho trình cung cấp bạn đang sử dụng, cũng như + tên cho các quyền truy cập khác được sử dụng bởi trình truy cập, hãy xem trong tài liệu + của trình cung cấp. +

+

+ Vai trò của quyền trong việc truy cập các trình cung cấp được mô tả chi tiết hơn trong phần + Quyền của Trình cung cấp Nội dung. +

+

+ Trình cung cấp Từ điển Người dùng sẽ định nghĩa quyền + android.permission.READ_USER_DICTIONARY trong tệp bản kê khai của nó, vì vậy một + ứng dụng muốn đọc từ trình cung cấp sẽ phải yêu cầu quyền này. +

+ +

Xây dựng truy vấn

+

+ Bước tiếp theo trong việc truy xuất dữ liệu từ một trình cung cấp đó là xây dựng một truy vấn. Đoạn mã HTML đầu tiên + này định nghĩa một số biến cho việc truy cập Trình cung cấp Từ điển Người dùng: +

+
+
+// A "projection" defines the columns that will be returned for each row
+String[] mProjection =
+{
+    UserDictionary.Words._ID,    // Contract class constant for the _ID column name
+    UserDictionary.Words.WORD,   // Contract class constant for the word column name
+    UserDictionary.Words.LOCALE  // Contract class constant for the locale column name
+};
+
+// Defines a string to contain the selection clause
+String mSelectionClause = null;
+
+// Initializes an array to contain selection arguments
+String[] mSelectionArgs = {""};
+
+
+

+ Đoạn mã HTML tiếp theo cho biết cách sử dụng + {@link android.content.ContentResolver#query ContentResolver.query()}, bằng cách sử dụng Trình cung cấp Từ điển + Người dùng như một ví dụ. Truy vấn máy khách trình cung cấp tương tự như một truy vấn SQL, và nó chứa một + tập hợp các cột để trả về, một tập hợp các tiêu chí lựa chọn, và một thứ tự sắp xếp. +

+

+ Tập hợp các cột mà truy vấn cần trả về được gọi là dự thảo + (biến mProjection). +

+

+ Biểu thức để chỉ định các hàng cần truy xuất sẽ được chia thành một mệnh đề lựa chọn và + tham đối lựa chọn. Mệnh đề lựa chọn là sự kết hợp giữa các biểu thức lô-gic và biểu thức Boolean, + tên cột, và giá trị (biến mSelectionClause). Nếu bạn chỉ định + tham số thay thế được ? thay vì một giá trị, phương pháp truy vấn sẽ truy xuất giá trị + từ mảng tham đối lựa chọn (biến mSelectionArgs). +

+

+ Trong đoạn mã HTML tiếp theo, nếu người dùng không điền từ thì mệnh đề lựa chọn được đặt thành + null, và truy vấn trả về tất cả các từ trong trình cung cấp. Nếu người dùng nhập + một từ, mệnh đề lựa chọn được đặt thành UserDictionary.Words.WORD + " = ?" và + phần tử đầu tiên của mảng tham đối lựa chọn được đặt thành từ mà người dùng đã nhập. +

+
+/*
+ * This defines a one-element String array to contain the selection argument.
+ */
+String[] mSelectionArgs = {""};
+
+// Gets a word from the UI
+mSearchString = mSearchWord.getText().toString();
+
+// Remember to insert code here to check for invalid or malicious input.
+
+// If the word is the empty string, gets everything
+if (TextUtils.isEmpty(mSearchString)) {
+    // Setting the selection clause to null will return all words
+    mSelectionClause = null;
+    mSelectionArgs[0] = "";
+
+} else {
+    // Constructs a selection clause that matches the word that the user entered.
+    mSelectionClause = UserDictionary.Words.WORD + " = ?";
+
+    // Moves the user's input string to the selection arguments.
+    mSelectionArgs[0] = mSearchString;
+
+}
+
+// Does a query against the table and returns a Cursor object
+mCursor = getContentResolver().query(
+    UserDictionary.Words.CONTENT_URI,  // The content URI of the words table
+    mProjection,                       // The columns to return for each row
+    mSelectionClause                   // Either null, or the word the user entered
+    mSelectionArgs,                    // Either empty, or the string the user entered
+    mSortOrder);                       // The sort order for the returned rows
+
+// Some providers return null if an error occurs, others throw an exception
+if (null == mCursor) {
+    /*
+     * Insert code here to handle the error. Be sure not to use the cursor! You may want to
+     * call android.util.Log.e() to log this error.
+     *
+     */
+// If the Cursor is empty, the provider found no matches
+} else if (mCursor.getCount() < 1) {
+
+    /*
+     * Insert code here to notify the user that the search was unsuccessful. This isn't necessarily
+     * an error. You may want to offer the user the option to insert a new row, or re-type the
+     * search term.
+     */
+
+} else {
+    // Insert code here to do something with the results
+
+}
+
+

+ Truy vấn này tương tự như câu lệnh SQL: +

+
+SELECT _ID, word, locale FROM words WHERE word = <userinput> ORDER BY word ASC;
+
+

+ Trong câu lệnh SQL này, tên cột thực tế được sử dụng thay vì các hằng số lớp hợp đồng. +

+

Bảo vệ trước mục nhập độc hại

+

+ Nếu dữ liệu được quản lý bởi trình cung cấp nội dung nằm trong một cơ sở dữ liệu SQL, việc điền dữ liệu không được tin cậy từ bên ngoài + vào các câu lệnh SQL thô có thể dẫn đến tiêm lỗi SQL. +

+

+ Hãy xét mệnh đề lựa chọn sau: +

+
+// Constructs a selection clause by concatenating the user's input to the column name
+String mSelectionClause =  "var = " + mUserInput;
+
+

+ Nếu bạn làm vậy, bạn đang cho phép người dùng ghép nối SQL độc hại lên câu lệnh SQL của mình. + Ví dụ, người dùng có thể điền "nothing; DROP TABLE *;" cho mUserInput, làm vậy + sẽ dẫn đến mệnh đề lựa chọn var = nothing; DROP TABLE *;. Do + mệnh đề lựa chọn được coi như một câu lệnh SQL, điều này có thể khiến trình cung cấp xóa tất cả + bảng trong cơ sở dữ liệu SQLite cơ bản (trừ khi trình cung cấp được thiết lập để bắt những lần thử + tiêm lỗi SQL). +

+

+ Để tránh vấn đề này, hãy sử dụng một mệnh đề lựa chọn mà sử dụng ? làm tham số + thay thế được và một mảng các tham đối lựa chọn riêng. Khi bạn làm như vậy, mục nhập của người dùng + được gắn kết trực tiếp với truy vấn thay vì được giải nghĩa như một phần của câu lệnh SQL. + Vì nó không được coi như SQL, mục nhập của người dùng không thể tiêm lỗi SQL độc hại. Thay vì sử dụng + ghép nối để điền mục nhập của người dùng, hãy sử dụng mệnh đề lựa chọn này: +

+
+// Constructs a selection clause with a replaceable parameter
+String mSelectionClause =  "var = ?";
+
+

+ Thiết lập mảng các tham đối lựa chọn như sau: +

+
+// Defines an array to contain the selection arguments
+String[] selectionArgs = {""};
+
+

+ Đặt một giá trị trong mảng các tham đối lựa chọn như sau: +

+
+// Sets the selection argument to the user's input
+selectionArgs[0] = mUserInput;
+
+

+ Mệnh đề lựa chọn mà sử dụng ? như một tham số thay thế được và một mảng + các tham đối lựa chọn là cách được ưu tiên để chỉ định một lựa chọn, ngay cả khi trình cung cấp không + được dựa trên cơ sở dữ liệu SQL. +

+ +

Hiển thị các kết quả truy vấn

+

+ Phương pháp máy khách {@link android.content.ContentResolver#query ContentResolver.query()} luôn trả về + một {@link android.database.Cursor} chứa các cột được chỉ định bởi dự thảo của + truy vấn cho các hàng khớp với các tiêu chí lựa chọn của truy vấn. Một đối tượng + {@link android.database.Cursor} cung cấp truy cập đọc ngẫu nhiên vào các hàng và cột mà nó + chứa. Bằng cách sử dụng phương pháp {@link android.database.Cursor}, bạn có thể lặp lại các hàng trong + kết quả, xác định kiểu dữ liệu của từng cột, lấy dữ liệu ra khỏi cột, và kiểm tra các tính chất khác + của kết quả. Một số triển khai {@link android.database.Cursor} sẽ tự động + cập nhật đối tượng khi dữ liệu của trình cung cấp thay đổi, hoặc kích khởi các phương pháp trong một đối tượng quan sát + khi {@link android.database.Cursor} thay đổi, hoặc cả hai. +

+

+ Lưu ý: Một trình cung cấp có thể hạn chế truy cập vào các cột dựa trên tính chất của + đối tượng thực hiện truy vấn. Ví dụ, Trình cung cấp Danh bạ hạn chế truy cập đối với một số cột cho + các trình điều hợp đồng bộ, vì thế nó sẽ không trả chúng về một hoạt động hay dịch vụ. +

+

+ Nếu không hàng nào khớp với các tiêu chí lựa chọn, trình cung cấp + sẽ trả về một đối tượng {@link android.database.Cursor} mà trong đó + {@link android.database.Cursor#getCount Cursor.getCount()} bằng 0 (con chạy trống). +

+

+ Nếu xảy ra một lỗi nội bộ, các kết quả của truy vấn sẽ phụ thuộc vào trình cung cấp cụ thể. Nó có thể + chọn trả về null, hoặc nó có thể đưa ra một lỗi {@link java.lang.Exception}. +

+

+ Do {@link android.database.Cursor} là một "danh sách" hàng, một cách hay để hiển thị + nội dung của một {@link android.database.Cursor} đó là liên kết nó với một {@link android.widget.ListView} + thông qua một {@link android.widget.SimpleCursorAdapter}. +

+

+ Đoạn mã HTML sau tiếp tục từ đoạn mã HTML trước. Nó tạo một đối tượng + {@link android.widget.SimpleCursorAdapter} chứa {@link android.database.Cursor} + được truy xuất bởi truy vấn, và đặt đối tượng này thành trình điều hợp cho một + {@link android.widget.ListView}: +

+
+// Defines a list of columns to retrieve from the Cursor and load into an output row
+String[] mWordListColumns =
+{
+    UserDictionary.Words.WORD,   // Contract class constant containing the word column name
+    UserDictionary.Words.LOCALE  // Contract class constant containing the locale column name
+};
+
+// Defines a list of View IDs that will receive the Cursor columns for each row
+int[] mWordListItems = { R.id.dictWord, R.id.locale};
+
+// Creates a new SimpleCursorAdapter
+mCursorAdapter = new SimpleCursorAdapter(
+    getApplicationContext(),               // The application's Context object
+    R.layout.wordlistrow,                  // A layout in XML for one row in the ListView
+    mCursor,                               // The result from the query
+    mWordListColumns,                      // A string array of column names in the cursor
+    mWordListItems,                        // An integer array of view IDs in the row layout
+    0);                                    // Flags (usually none are needed)
+
+// Sets the adapter for the ListView
+mWordList.setAdapter(mCursorAdapter);
+
+

+ Lưu ý: Để lùi {@link android.widget.ListView} bằng một + {@link android.database.Cursor}, con chạy phải chứa một cột có tên _ID. + Vì điều này, truy vấn được hiện lúc trước truy xuất cột _ID cho + bảng "từ" mặc dù {@link android.widget.ListView} không hiển thị nó. + Hạn chế này cũng giải thích lý do tại sao phần lớn trình cung cấp đều có một cột _ID cho mỗi + bảng của nó. +

+ + +

Lấy dữ liệu từ các kết quả truy vấn

+

+ Thay vì chỉ hiển thị các kết quả truy vấn, bạn có thể sử dụng chúng cho các tác vụ khác. Ví + dụ, bạn có thể truy xuất chính tả từ một từ điển người dùng, rồi sau đó tìm kiếm từ đó trong + các trình cung cấp khác. Để làm điều này, bạn lặp lại các hàng trong {@link android.database.Cursor}: +

+
+
+// Determine the column index of the column named "word"
+int index = mCursor.getColumnIndex(UserDictionary.Words.WORD);
+
+/*
+ * Only executes if the cursor is valid. The User Dictionary Provider returns null if
+ * an internal error occurs. Other providers may throw an Exception instead of returning null.
+ */
+
+if (mCursor != null) {
+    /*
+     * Moves to the next row in the cursor. Before the first movement in the cursor, the
+     * "row pointer" is -1, and if you try to retrieve data at that position you will get an
+     * exception.
+     */
+    while (mCursor.moveToNext()) {
+
+        // Gets the value from the column.
+        newWord = mCursor.getString(index);
+
+        // Insert code here to process the retrieved word.
+
+        ...
+
+        // end of while loop
+    }
+} else {
+
+    // Insert code here to report an error if the cursor is null or the provider threw an exception.
+}
+
+

+ Triển khai {@link android.database.Cursor} sẽ chứa một vài phương pháp “get" để + truy xuất các kiểu dữ liệu khác nhau từ đối tượng. Ví dụ, đoạn mã HTML trước + sử dụng {@link android.database.Cursor#getString getString()}. Chúng cũng có một phương pháp + {@link android.database.Cursor#getType getType()} để trả về một giá trị cho biết + kiểu dữ liệu của cột. +

+ + + +

Quyền của Trình cung cấp Nội dung

+

+ Ứng dụng của một trình cung cấp có thể chỉ định các quyền mà ứng dụng khác có thể có để + truy cập dữ liệu của trình cung cấp đó. Những quyền này đảm bảo rằng người dùng biết một ứng dụng + sẽ cố gắng truy cập dữ liệu nào. Dựa trên các yêu cầu của trình cung cấp, các ứng dụng khác + yêu cầu quyền mà chúng cần để truy cập trình dữ liệu. Người dùng cuối thấy các quyền + được yêu cầu khi họ cài đặt ứng dụng. +

+

+ Nếu ứng dụng của một trình cung cấp không chỉ định bất kỳ quyền nào, khi đó các ứng dụng khác không có + quyền truy cập dữ liệu của trình cung cấp. Tuy nhiên, các thành phần trong ứng dụng của trình cung cấp luôn có + đầy đủ quyền truy nhập đọc và ghi, không phụ thuộc vào các quyền được chỉ định. +

+

+ Như đã lưu ý, Trình cung cấp Từ điển Người dùng sẽ yêu cầu + quyền android.permission.READ_USER_DICTIONARY để truy xuất dữ liệu từ nó. + Trình cung cấp có quyền android.permission.WRITE_USER_DICTIONARY + riêng để chèn, cập nhật, hoặc xóa dữ liệu. +

+

+ Để nhận các quyền cần để truy cập một trình cung cấp, ứng dụng yêu cầu chúng bằng một phần tử +<uses-permission> + trong tệp bản kê khai của nó. Khi Trình quản lý Gói Android cài đặt các ứng dụng, người dùng + phải phê chuẩn tất cả quyền mà ứng dụng yêu cầu. Nếu người dùng phê chuẩn tất cả quyền, khi đó + Trình quản lý Gói sẽ tiếp tục cài đặt; nếu người dùng không phê chuẩn chúng, Trình quản lý Gói sẽ + hủy bỏ việc cài đặt. +

+

+ Phần tử +<uses-permission> + sau yêu cầu quyền truy cập đọc vào Trình cung cấp Từ điển Người dùng: +

+
+    <uses-permission android:name="android.permission.READ_USER_DICTIONARY">
+
+

+ Tác động của các quyền tới việc truy cập trình cung cấp được giải thích chi tiết hơn trong + hướng dẫn Bảo mật và Quyền. +

+ + + +

Chèn, Cập nhật, và Xóa Dữ liệu

+

+ Giống như cách bạn truy xuất dữ liệu từ một trình cung cấp, bạn cũng có thể sử dụng tương tác giữa + một máy khách cung cấp và {@link android.content.ContentProvider} của trình cung cấp để sửa đổi dữ liệu. + Bạn gọi một phương pháp {@link android.content.ContentResolver} với các tham đối được chuyển sang + phương pháp {@link android.content.ContentProvider} tương ứng. Trình cung cấp và + máy khách cung cấp sẽ tự động xử lý bảo mật và truyền thông liên tiến trình. +

+

Chèn dữ liệu

+

+ Để chèn dữ liệu vào một trình cung cấp, bạn gọi phương pháp + {@link android.content.ContentResolver#insert ContentResolver.insert()} +. Phương pháp này chèn một hàng mới vào trình cung cấp và trả về một URI nội dung cho hàng đó. + Đoạn mã HTML này cho biết cách chèn một từ mới vào Trình cung cấp Từ điển Người dùng: +

+
+// Defines a new Uri object that receives the result of the insertion
+Uri mNewUri;
+
+...
+
+// Defines an object to contain the new values to insert
+ContentValues mNewValues = new ContentValues();
+
+/*
+ * Sets the values of each column and inserts the word. The arguments to the "put"
+ * method are "column name" and "value"
+ */
+mNewValues.put(UserDictionary.Words.APP_ID, "example.user");
+mNewValues.put(UserDictionary.Words.LOCALE, "en_US");
+mNewValues.put(UserDictionary.Words.WORD, "insert");
+mNewValues.put(UserDictionary.Words.FREQUENCY, "100");
+
+mNewUri = getContentResolver().insert(
+    UserDictionary.Word.CONTENT_URI,   // the user dictionary content URI
+    mNewValues                          // the values to insert
+);
+
+

+ Dữ liệu cho hàng mới đi vào trong một đối tượng {@link android.content.ContentValues} đơn lẻ, đối tượng này + có dạng tương tự như con chạy một hàng. Các cột trong đối tượng này không cần có + cùng kiểu dữ liệu, và nếu hoàn toàn không muốn chỉ định một giá trị, bạn có thể đặt một cột + thành null bằng cách sử dụng {@link android.content.ContentValues#putNull ContentValues.putNull()}. +

+

+ Đoạn mã HTML không thêm cột _ID vì cột này được tự động + duy trì. Trình cung cấp sẽ gán một giá trị duy nhất _ID cho mỗi hàng được + thêm. Các trình cung cấp thường sử dụng giá trị này làm khóa chính của bảng. +

+

+ URI nội dung được trả về trong newUri sẽ xác định hàng mới thêm, có + định dạng như sau: +

+
+content://user_dictionary/words/<id_value>
+
+

+ <id_value> là nội dung của _ID cho hàng mới. + Hầu hết các trình cung cấp đều có thể tự động phát hiện dạng URI nội dung này rồi thực hiện thao tác được yêu cầu + trên hàng cụ thể đó. +

+

+ Để nhận giá trị _ID từ {@link android.net.Uri} được trả về, hãy gọi + {@link android.content.ContentUris#parseId ContentUris.parseId()}. +

+

Cập nhật dữ liệu

+

+ Để cập nhật một hàng, bạn sử dụng một đối tượng {@link android.content.ContentValues} với các giá trị + được cập nhật giống như cách bạn làm với việc chèn, và các tiêu chí lựa chọn giống như cách bạn làm với truy vấn. + Phương pháp máy khách mà bạn sử dụng là + {@link android.content.ContentResolver#update ContentResolver.update()}. Bạn chỉ cần thêm + các giá trị vào đối tượng {@link android.content.ContentValues} cho các cột mà bạn đang cập nhật. Nếu bạn + muốn xóa các nội dung của một cột, hãy đặt giá trị thành null. +

+

+ Đoạn mã HTML sau thay đổi tất cả hàng với cột bản địa có ngôn ngữ "en" thành cột + có bản địa là null. Giá trị trả về là số hàng đã được cập nhật: +

+
+// Defines an object to contain the updated values
+ContentValues mUpdateValues = new ContentValues();
+
+// Defines selection criteria for the rows you want to update
+String mSelectionClause = UserDictionary.Words.LOCALE +  "LIKE ?";
+String[] mSelectionArgs = {"en_%"};
+
+// Defines a variable to contain the number of updated rows
+int mRowsUpdated = 0;
+
+...
+
+/*
+ * Sets the updated value and updates the selected words.
+ */
+mUpdateValues.putNull(UserDictionary.Words.LOCALE);
+
+mRowsUpdated = getContentResolver().update(
+    UserDictionary.Words.CONTENT_URI,   // the user dictionary content URI
+    mUpdateValues                       // the columns to update
+    mSelectionClause                    // the column to select on
+    mSelectionArgs                      // the value to compare to
+);
+
+

+ Bạn cũng nên thanh lọc thông tin đầu vào của người dùng khi gọi + {@link android.content.ContentResolver#update ContentResolver.update()}. Để tìm hiểu thêm về + điều này, hãy đọc phần Bảo vệ trước mục nhập độc hại. +

+

Xóa dữ liệu

+

+ Xóa hàng tương tự như truy xuất dữ liệu hàng: bạn chỉ định các tiêu chí lựa chọn cho hàng + mà bạn muốn xóa và phương pháp máy khách trả về số hàng được xóa. + Đoạn mã HTML sau xóa các hàng có appid khớp với "user". Phương pháp sẽ trả về + số hàng được xóa. +

+
+
+// Defines selection criteria for the rows you want to delete
+String mSelectionClause = UserDictionary.Words.APP_ID + " LIKE ?";
+String[] mSelectionArgs = {"user"};
+
+// Defines a variable to contain the number of rows deleted
+int mRowsDeleted = 0;
+
+...
+
+// Deletes the words that match the selection criteria
+mRowsDeleted = getContentResolver().delete(
+    UserDictionary.Words.CONTENT_URI,   // the user dictionary content URI
+    mSelectionClause                    // the column to select on
+    mSelectionArgs                      // the value to compare to
+);
+
+

+ Bạn cũng nên thanh lọc thông tin đầu vào của người dùng khi gọi + {@link android.content.ContentResolver#delete ContentResolver.delete()}. Để tìm hiểu thêm về + điều này, hãy đọc phần Bảo vệ trước mục nhập độc hại. +

+ +

Các Kiểu Dữ liệu của Trình cung cấp

+

+ Trình cung cấp nội dung có thể cung cấp nhiều kiểu dữ liệu khác nhau. Trình cung cấp Từ điển Người dùng chỉ cung cấp + văn bản, nhưng trình cung cấp cũng có thể cung cấp các định dạng sau: +

+
    +
  • + integer +
  • +
  • + long integer (long) +
  • +
  • + floating point +
  • +
  • + long floating point (double) +
  • +
+

+ Một kiểu dữ liệu khác mà các trình cung cấp thường sử dụng đó là Binary Large OBject (BLOB) được triển khai như một + mảng 64KB byte. Bạn có thể xem các kiểu dữ liệu có sẵn bằng cách xem các phương pháp "get" lớp + {@link android.database.Cursor}. +

+

+ Kiểu dữ liệu đối với mỗi cột trong một trình cung cấp thường được liệt kê trong tài liệu của trình cung cấp đó. + Các kiểu dữ liệu dành cho Trình cung cấp Từ điển Người dùng được liệt kê trong tài liệu tham khảo + cho lớp hợp đồng {@link android.provider.UserDictionary.Words} của nó (lớp hợp đồng được + mô tả trong phần Các Lớp Hợp đồng). + Bạn cũng có thể xác định kiểu dữ liệu bằng cách gọi {@link android.database.Cursor#getType + Cursor.getType()}. +

+

+ Trình cung cấp cũng duy trì thông tin về kiểu dữ liệu MIME cho mỗi URI nội dung mà chúng định nghĩa. Bạn có thể + sử dụng thông tin về kiểu MIME để tìm hiểu xem ứng dụng của mình có thể xử lý dữ liệu mà + trình cung cấp đưa ra hay không, hoặc để chọn một kiểu xử lý dựa trên kiểu MIME. Bạn thường cần kiểu + MIME khi đang làm việc với một trình cung cấp chứa các cấu trúc hoặc tệp + dữ liệu phức tạp. Ví dụ, bảng {@link android.provider.ContactsContract.Data} + trong Trình cung cấp Danh bạ sử dụng các kiểu MIME để dán nhãn kiểu dữ liệu liên lạc được lưu trữ trong từng + hàng. Để nhận được kiểu MIME tương ứng với một URI nội dung, hãy gọi + {@link android.content.ContentResolver#getType ContentResolver.getType()}. +

+

+ Phần Tham khảo Kiểu MIME mô tả + cú pháp của cả kiểu MIME tiêu chuẩn lẫn tùy chỉnh. +

+ + + +

Các Hình thức Truy cập Trình cung cấp Thay thế

+

+ Có ba hình thức truy cập trình cung cấp thay thế quan trọng trong phát triển ứng dụng: +

+
    +
  • + Truy cập hàng loạt: Bạn có thể tạo một loạt lệnh gọi truy cập bằng các phương pháp trong + lớp {@link android.content.ContentProviderOperation}, rồi sau đó áp dụng chúng với + {@link android.content.ContentResolver#applyBatch ContentResolver.applyBatch()}. +
  • +
  • + Truy vấn không đồng bộ: Bạn nên thực hiện các truy vấn trong một luồng riêng. Một cách để làm điều này đó là + sử dụng một đối tượng {@link android.content.CursorLoader}. Các ví dụ trong + hướng dẫn Trình tải sẽ minh họa + cách làm điều này. +
  • +
  • + Truy cập dữ liệu thông qua ý định: Mặc dù không thể gửi một ý định + trực tiếp tới một trình cung cấp, bạn có thể gửi một ý định tới ứng dụng của trình cung cấp đó, + đây thường là cách tốt nhất để sửa đổi dữ liệu của trình cung cấp. +
  • +
+

+ Truy cập hàng loạt và sửa đổi thông qua ý định được mô tả trong các phần sau. +

+

Truy cập hàng loạt

+

+ Truy cập hàng loạt vào một trình cung cấp là cách hữu ích để chèn nhiều hàng, hoặc để chèn + các hàng vào nhiều bảng trong cùng lệnh gọi phương pháp, hoặc nhìn chung để thực hiện một tập hợp + thao tác qua các ranh giới tiến trình như một giao tác (thao tác nguyên tử). +

+

+ Để truy cập một trình cung cấp trong "chế độ hàng loạt", + bạn tạo một mảng đối tượng {@link android.content.ContentProviderOperation} rồi + phân phối chúng tới một trình cung cấp nội dung bằng + {@link android.content.ContentResolver#applyBatch ContentResolver.applyBatch()}. Bạn chuyển + quyền của trình cung cấp nội dung cho phương pháp này thay vì một URI nội dung cụ thể. +Điều này cho phép đối tượng {@link android.content.ContentProviderOperation} trong mảng có tác dụng + đối với một bảng khác. Một lệnh gọi tới {@link android.content.ContentResolver#applyBatch + ContentResolver.applyBatch()} trả về một mảng kết quả. +

+

+ Mô tả lớp hợp đồng {@link android.provider.ContactsContract.RawContacts} + bao gồm một đoạn mã HTML thể hiện việc chèn hàng loạt. Ứng dụng mẫu + Trình quản lý Danh bạ + có một ví dụ về truy cập hàng loạt trong tệp nguồn ContactAdder.java + của nó. +

+ +

Truy cập dữ liệu thông qua ý định

+

+ Ý định có thể cho phép truy cập gián tiếp vào một trình cung cấp nội dung. Bạn cho phép người dùng truy cập + dữ liệu trong một trình cung cấp ngay cả khi ứng dụng của bạn không có quyền truy cập, hoặc bằng cách + nhận lại một ý định kết quả từ một ứng dụng có quyền, hoặc bằng cách kích hoạt một + ứng dụng có phép và cho phép người dùng được làm việc trong nó. +

+

Được truy cập với các quyền tạm thời

+

+ Bạn có thể truy cập dữ liệu trong một trình cung cấp nội dung, ngay cả khi bạn không có quyền + truy nhập phù hợp, bằng cách gửi một ý định tới một ứng dụng có quyền và + nhận lại một ý định kết quả chứa quyền "URI". + Đây là những quyền cho một URI nội dung cụ thể kéo dài tới khi hoạt động nhận chúng + được hoàn thành. Ứng dụng có quyền lâu dài sẽ cấp quyền tạm thời + bằng cách đặt một cờ trong ý định kết quả: +

+
    +
  • + Quyền đọc: + {@link android.content.Intent#FLAG_GRANT_READ_URI_PERMISSION} +
  • +
  • + Quyền ghi: + {@link android.content.Intent#FLAG_GRANT_WRITE_URI_PERMISSION} +
  • +
+

+ Lưu ý: Những cờ này không cấp quyền truy cập đọc và ghi nói chung cho trình cung cấp + mà có quyền được chứa trong URI nội dung. Quyền truy cập này chỉ áp dụng cho chính URI đó. +

+

+ Trình cung cấp sẽ định nghĩa quyền URI cho các URI nội dung trong bản kê khai của nó, bằng cách sử dụng thuộc tính +android:grantUriPermission + của phần tử +<provider> + cũng như phần tử con +<grant-uri-permission> + của phần tử +<provider> + . Cơ chế cấp quyền URI này được giải thích chi tiết hơn trong + hướng dẫn Bảo mật và Quyền, + trong phần "Quyền URI". +

+

+ Ví dụ, bạn có thể truy xuất dữ liệu cho một liên lạc trong Trình cung cấp Danh bạ, ngay cả khi bạn không + có quyền {@link android.Manifest.permission#READ_CONTACTS}. Bạn có thể muốn thực hiện điều này + trong một ứng dụng gửi thiệp mừng điện tử tới một liên lạc vào ngày sinh nhật của người đó. Thay vì + yêu cầu {@link android.Manifest.permission#READ_CONTACTS}, là nơi cấp cho bạn quyền truy cập tất cả liên lạc + của người dùng và tất cả thông tin của họ, bạn nên cho phép người dùng kiểm soát những liên lạc + nào được sử dụng bởi ứng dụng của bạn. Để làm điều này, bạn sử dụng tiến trình sau: +

+
    +
  1. + Ứng dụng của bạn gửi một ý định chứa hành động + {@link android.content.Intent#ACTION_PICK} và kiểu MIME "danh bạ" +{@link android.provider.ContactsContract.RawContacts#CONTENT_ITEM_TYPE}, bằng cách sử dụng + phương pháp {@link android.app.Activity#startActivityForResult + startActivityForResult()}. +
  2. +
  3. + Vì ý định này khớp với bộ lọc ý định cho hoạt động + "lựa chọn" của ứng dụng Danh bạ, hoạt động sẽ đi đến tiền cảnh. +
  4. +
  5. + Trong hoạt động lựa chọn, người dùng chọn một + liên lạc để cập nhật. Khi điều này xảy ra, hoạt động lựa chọn sẽ gọi + {@link android.app.Activity#setResult setResult(resultcode, intent)} + để thiết lập một ý định nhằm gửi lại ứng dụng của bạn. Ý định chứa URI nội dung + của liên lạc mà người dùng đã chọn, và các cờ "phụ thêm" + {@link android.content.Intent#FLAG_GRANT_READ_URI_PERMISSION}. Những cờ này cấp quyền URI + cho ứng dụng của bạn để đọc dữ liệu cho liên lạc được trỏ đến bởi + URI nội dung. Sau đó, hoạt động lựa chọn gọi {@link android.app.Activity#finish()} để + trả kiểm soát về ứng dụng của bạn. +
  6. +
  7. + Hoạt động của bạn trả về tiền cảnh, và hệ thống sẽ gọi phương pháp + {@link android.app.Activity#onActivityResult onActivityResult()} + của hoạt động của bạn. Phương pháp này nhận được ý định kết quả do hoạt động lựa chọn tạo trong + ứng dụng Danh bạ. +
  8. +
  9. + Với URI nội dung từ ý định kết quả, bạn có thể đọc dữ liệu của liên lạc + từ Trình cung cấp Danh bạ, ngay cả khi bạn không yêu cầu quyền truy cập đọc lâu dài + vào trình cung cấp trong bản kê khai của mình. Sau đó, bạn có thể nhận thông tin ngày sinh của liên lạc + hoặc địa chỉ e-mail của người đó rồi gửi thiệp mừng điện tử. +
  10. +
+

Sử dụng một ứng dụng khác

+

+ Một cách đơn giản để cho phép người dùng sửa đổi dữ liệu mà bạn không có quyền truy cập đó là + kích hoạt một ứng dụng có quyền và cho phép người dùng làm việc ở đó. +

+

+ Ví dụ, ứng dụng Lịch chấp nhận một + ý định {@link android.content.Intent#ACTION_INSERT}, nó cho phép bạn kích hoạt UI chèn + của ứng dụng. Bạn có thể chuyển dữ liệu "phụ thêm" trong ý định này mà được ứng dụng sử dụng + để điền trước vào UI. Vì các sự kiện định kỳ có cú pháp phức tạp, cách + ưu tiên để chèn sự kiện vào Trình cung cấp Lịch đó là kích hoạt ứng dụng Lịch với một + {@link android.content.Intent#ACTION_INSERT} rồi để người dùng chèn sự kiện tại đó. +

+ +

Các Lớp Hợp đồng

+

+ Lớp hợp đồng định nghĩa các hằng số sẽ giúp ứng dụng hoạt động với các URI nội dung, tên + cột, hành động ý định, và các tính năng khác của một trình cung cấp nội dung. Các lớp hợp đồng không + được tự động đưa vào cùng một trình cung cấp; nhà phát triển của trình cung cấp phải định nghĩa chúng rồi + cung cấp chúng cho các nhà phát triển khác. Nhiều trình cung cấp được bao gồm cùng với nền tảng + Android có các lớp hợp đồng tương ứng trong gói {@link android.provider}. +

+

+ Ví dụ, Trình cung cấp Từ điển Người dùng có một lớp hợp đồng + {@link android.provider.UserDictionary} chứa các hằng số URI nội dung và tên cột. URI nội dung + đối với bảng "từ" được định nghĩa trong hằng số + {@link android.provider.UserDictionary.Words#CONTENT_URI UserDictionary.Words.CONTENT_URI}. + Lớp {@link android.provider.UserDictionary.Words} cũng chứa các hằng số tên cột, + chúng được sử dụng trong đoạn mã HTML mẫu trong hướng dẫn này. Ví dụ, một dự thảo truy vấn có thể được + định nghĩa là: +

+
+String[] mProjection =
+{
+    UserDictionary.Words._ID,
+    UserDictionary.Words.WORD,
+    UserDictionary.Words.LOCALE
+};
+
+

+ Một lớp hợp đồng khác là {@link android.provider.ContactsContract} dành cho Trình cung cấp Danh bạ. + Tài liệu tham khảo cho lớp này bao gồm các đoạn mã HTML mẫu. Một trong số các + lớp con của nó, {@link android.provider.ContactsContract.Intents.Insert}, là một lớp hợp đồng + chứa các hằng số cho ý định và dữ liệu ý định. +

+ + + +

Tham khảo Kiểu MIME

+

+ Trình cung cấp nội dung có thể trả về các kiểu phương tiện MIME tiêu chuẩn, hoặc xâu kiểu MIME tùy chỉnh, hoặc cả hai. +

+

+ Các kiểu MIME có định dạng +

+
+type/subtype
+
+

+ Ví dụ, kiểu MIME thông dụng text/html có kiểu text và + kiểu con html. Nếu trình cung cấp trả về loại này cho một URI, điều đó có nghĩa rằng một + truy vấn đang sử dụng URI đó sẽ trả về văn bản chứa thẻ HTML. +

+

+ Các xâu kiểu MIME tùy chỉnh, còn gọi là kiểu MIME "theo nhà cung cấp", có các giá trị + kiểukiểu con phức tạp hơn. Giá trị kiểu luôn luôn +

+
+vnd.android.cursor.dir
+
+

+ áp dụng cho nhiều hàng, hoặc +

+
+vnd.android.cursor.item
+
+

+ áp dụng cho một hàng. +

+

+ Giá trị kiểu con áp dụng theo trình cung cấp. Các trình cung cấp được tích hợp trong Android thường có một kiểu con + đơn giản. Ví dụ, khi ứng dụng Danh bạ tạo một hàng cho một số điện thoại, + nó đặt kiểu MIME sau trong hàng: +

+
+vnd.android.cursor.item/phone_v2
+
+

+ Để ý rằng giá trị kiểu con đơn giản là phone_v2. +

+

+ Các nhà phát triển trình cung cấp khác có thể tạo mẫu hình kiểu con của riêng mình dựa trên quyền + và tên bảng của trình cung cấp. Ví dụ, xét một trình cung cấp chứa các biểu thời gian lịch tàu. + Quyền của trình cung cấp là com.example.trains, và nó chứa các bảng + Line1, Line2, và Line3. Để phản hồi lại URI nội dung +

+

+

+content://com.example.trains/Line1
+
+

+ đối với bảng Line1, trình cung cấp trả về kiểu MIME +

+
+vnd.android.cursor.dir/vnd.example.line1
+
+

+ Để phản hồi lại URI nội dung +

+
+content://com.example.trains/Line2/5
+
+

+ đối với hàng 5 trong bảng Line2, trình cung cấp trả về kiểu MIME +

+
+vnd.android.cursor.item/vnd.example.line2
+
+

+ Hầu hết các trình cung cấp nội dung đều định nghĩa hằng số lớp hợp đồng cho các kiểu MIME mà chúng sử dụng. Ví dụ như lớp hợp đồng + của Trình cung cấp Danh bạ {@link android.provider.ContactsContract.RawContacts}, + sẽ định nghĩa hằng số + {@link android.provider.ContactsContract.RawContacts#CONTENT_ITEM_TYPE} cho kiểu MIME của + một hàng liên lạc thô duy nhất. +

+

+ Các URI nội dung đối với hàng duy nhất được mô tả trong phần + URI nội dung. +

diff --git a/docs/html-intl/intl/vi/guide/topics/providers/content-provider-creating.jd b/docs/html-intl/intl/vi/guide/topics/providers/content-provider-creating.jd new file mode 100644 index 0000000000000000000000000000000000000000..2e8579a1e92eff57d75cc3cc8e37f3457f6de54f --- /dev/null +++ b/docs/html-intl/intl/vi/guide/topics/providers/content-provider-creating.jd @@ -0,0 +1,1214 @@ +page.title=Tạo một Trình cung cấp Nội dung +@jd:body + + + +

+ Trình cung cấp nội dung quản lý truy cập vào một kho dữ liệu tập trung. Bạn triển khai một + trình cung cấp thành một hoặc nhiều lớp trong một ứng dụng Android, bên cạnh các phần tử trong + tệp bản kê khai. Một trong các lớp của bạn triển khai một lớp con + {@link android.content.ContentProvider}, đây là giao diện giữa trình cung cấp của bạn và + các ứng dụng khác. Mặc dù mục đích của các trình cung cấp nội dung khác là cung cấp dữ liệu có sẵn cho các + ứng dụng khác, dĩ nhiên bạn có thể ra lệnh cho các hoạt động trong ứng dụng của mình + truy vấn và sửa đổi dữ liệu được quản lý bởi trình cung cấp của bạn. +

+

+ Phần còn lại của chủ đề này là một danh sách cơ bản về các bước để xây dựng một trình cung cấp nội dung và một danh sách + các API để sử dụng. +

+ + + +

Trước khi Bạn Bắt đầu Xây dựng

+

+ Trước khi bạn bắt đầu xây dựng một trình cung cấp, hãy làm việc sau: +

+
    +
  1. + Quyết định xem bạn có cần một trình cung cấp nội dung không. Bạn cần xây dựng một trình cung cấp + nội dung nếu muốn cung cấp một hoặc nhiều tính năng sau đây: +
      +
    • Bạn muốn cung cấp dữ liệu hoặc tệp phức tạp cho các ứng dụng khác.
    • +
    • Bạn muốn cho phép người dùng sao chép dữ liệu phức tạp từ ứng dụng của bạn vào các ứng dụng khác.
    • +
    • Bạn muốn cung cấp các gợi ý tìm kiếm tùy chỉnh bằng cách sử dụng khuôn khổ tìm kiếm.
    • +
    +

    + Bạn không cần trình cung cấp phải sử dụng một cơ sở dữ liệu SQLite nếu việc sử dụng hoàn toàn + diễn ra trong ứng dụng của bạn. +

    +
  2. +
  3. + Nếu bạn chưa làm như vậy, hãy đọc chủ đề + + Nội dung Cơ bản về Trình cung cấp Nội dung để tìm hiểu thêm về trình cung cấp. +
  4. +
+

+ Tiếp theo, hãy làm theo những bước sau để xây dựng trình cung cấp của bạn: +

+
    +
  1. + Thiết kế kho lưu trữ thô cho dữ liệu của bạn. Một trình cung cấp nội dung sẽ cung cấp dữ liệu theo hai cách: +
    +
    + Dữ liệu tệp +
    +
    + Dữ liệu mà thường đến các tệp chẳng hạn như + ảnh, âm thanh, hoặc video. Lưu trữ các tệp ở không gian + riêng tư trong ứng dụng của bạn. Để hồi đáp lại một yêu cầu tệp từ một ứng dụng khác, trình cung cấp + của bạn có thể cung cấp một núm điều tác cho tệp. +
    +
    + Dữ liệu "cấu trúc" +
    +
    + Dữ liệu mà thường đến một cơ sở dữ liệu, mảng, hoặc cấu trúc tương tự. + Lưu trữ dữ liệu dưới dạng tương thích với các bảng hàng cột. Hàng + biểu diễn một đối tượng, chẳng hạn như một người hoặc khoản mục trong kiểm kê. Cột biểu diễn + một số dữ liệu cho đối tượng, chẳng hạn như tên của một người hoặc giá của một khoản mục. Một cách thường dùng để + lưu trữ loại dữ liệu này đó là trong cơ sở dữ liệu SQLite, nhưng bạn có thể sử dụng bất kỳ loại + kho lưu trữ lâu dài nào. Để tìm hiểu thêm về các loại kho lưu trữ có sẵn trong + hệ thống Android, hãy xem phần + Thiết kế Kho lưu trữ Dữ liệu. +
    +
    +
  2. +
  3. + Định nghĩa một triển khai cụ thể của lớp {@link android.content.ContentProvider} và + các phương pháp được yêu cầu của nó. Lớp này là giao diện giữa dữ liệu của bạn và phần còn lại của + hệ thống Android. Để biết thêm thông tin về lớp này, hãy xem phần + Triển khai Lớp ContentProvider. +
  4. +
  5. + Định nghĩa xâu thẩm quyền của trình cung cấp, URI nội dung của nó, và các tên cột. Nếu bạn muốn + ứng dụng của trình cung cấp xử lý các ý định, hãy định nghĩa các hành động ý định, dữ liệu phụ thêm, + và cờ. Đồng thời, hãy định nghĩa các quyền mà bạn sẽ yêu cầu cho những ứng dụng muốn + truy cập dữ liệu của bạn. Bạn nên cân nhắc định nghĩa tất cả những giá trị này là hằng số trong một + lớp riêng; sau đó, bạn có thể cho hiện lớp này ra với các nhà phát triển khác. Để biết thêm + thông tin về URI nội dung, hãy xem + phần Thiết kế URI Nội dung. + Để biết thêm thông tin về ý định, hãy xem + phần Ý định và Truy cập Dữ liệu. +
  6. +
  7. + Thêm các nội dung tùy chọn khác, chẳng hạn như dữ liệu mẫu hoặc triển + khai {@link android.content.AbstractThreadedSyncAdapter} mà có thể đồng bộ hoá dữ liệu giữa + trình cung cấp và dữ liệu nền đám mây. +
  8. +
+ + + +

Thiết kế Kho lưu trữ Dữ liệu

+

+ Trình cung cấp nội dung là giao diện đối với dữ liệu được lưu theo một định dạng cấu trúc. Trước khi tạo + giao diện, bạn phải quyết định cách lưu trữ dữ liệu. Bạn có thể lưu trữ dữ liệu theo bất kỳ dạng nào + mà bạn muốn rồi thiết kế giao diện để đọc và ghi dữ liệu nếu cần thiết. +

+

+ Có một số công nghệ lưu trữ dữ liệu có sẵn trong Android: +

+
    +
  • + Hệ thống Android bao gồm một API cơ sở dữ liệu SQLite mà các trình cung cấp của chính Androi sử dụng + để lưu trữ dữ liệu theo định hướng bảng. Lớp + {@link android.database.sqlite.SQLiteOpenHelper} giúp bạn tạo cơ sở dữ liệu, và lớp + {@link android.database.sqlite.SQLiteDatabase} là lớp cơ bản để đánh giá + các cơ sở dữ liệu. +

    + Nhớ rằng bạn không phải sử dụng một cơ sở dữ liệu để triển khai kho lưu giữ của mình. Bề ngoài, một trình cung cấp + có dạng như là một tập hợp bảng, tương tự như một cơ sở dữ liệu quan hệ, nhưng đây + không phải là một yêu cầu đối với việc triển khai nội bộ của trình cung cấp. +

    +
  • +
  • + Để lưu trữ dữ liệu tệp, Android có nhiều API định hướng tệp khác nhau. + Để tìm hiểu thêm về lưu trữ tệp, hãy đọc chủ đề + Kho lưu trữ Dữ liệu. Nếu bạn + đang thiết kế một trình cung cấp dữ liệu liên quan tới phương tiện chẳng hạn như nhạc hay video, bạn có thể + có một trình cung cấp cho phép kết hợp dữ liệu bảng và các tệp. +
  • +
  • + Để làm việc với dữ liệu trên nền mạng, hãy sử dụng các lớp trong {@link java.net} và + {@link android.net}. Bạn cũng có thể đồng bộ hoá dữ liệu trên nền mạng với một kho lưu trữ dữ liệu cục bộ + chẳng hạn như một cơ sở dữ liệu, rồi cung cấp dữ liệu dưới dạng bảng hoặc tệp. + Ứng dụng mẫu + Trình điều hợp Đồng bộ Mẫu minh họa loại đồng bộ hoá này. +
  • +
+

+ Những nội dung cần xem xét khi thiết kế dữ liệu +

+

+ Sau đây là một số mẹo để thiết kế cấu trúc dữ liệu cho trình cung cấp của bạn: +

+
    +
  • + Dữ liệu bảng nên luôn có một cột "khóa chính" mà trình cung cấp duy trì + như một giá trị số duy nhất cho mỗi hàng. Bạn có thể sử dụng giá trị này để liên kết hàng với các hàng + có liên quan trong các bảng khác (sử dụng nó làm "khóa ngoại"). Mặc dù bạn có thể sử dụng bất kỳ tên gọi nào + cho cột này, sử dụng {@link android.provider.BaseColumns#_ID BaseColumns._ID} là lựa chọn tốt nhất + vì việc liên kết các kết quả của một truy vấn trình cung cấp với + {@link android.widget.ListView} đòi hỏi một trong các cột được truy xuất phải có tên + _ID. +
  • +
  • + Nếu bạn muốn cung cấp các hình ảnh bitmap hoặc nội dung dữ liệu định hướng tệp rất lớn khác, hãy lưu trữ + dữ liệu vào một tệp rồi cung cấp nó gián tiếp thay vì lưu trữ nó trực tiếp trong một + bảng. Nếu làm vậy, bạn cần báo cho người dùng trình cung cấp của bạn rằng họ cần sử dụng một phương pháp tệp + {@link android.content.ContentResolver} để truy cập dữ liệu. +
  • +
  • + Sử dụng kiểu dữ liệu Binary Large OBject (BLOB) để lưu trữ dữ liệu có kích cỡ khác nhau hoặc có một + cấu trúc thay đổi. Ví dụ, bạn có thể sử dụng cột BLOB để lưu trữ một + bộ đệm giao thức hay + cấu trúc JSON. +

    + Bạn cũng có thể sử dụng một BLOB để triển khai một bảng độc lập với sơ đồ. Trong + kiểu bảng này, bạn định nghĩa một cột khóa chính, một cột kiểu MIME, và một hoặc + nhiều cột chung là BLOB. Ý nghĩa của dữ liệu trong cột BLOB được thể hiện + bởi giá trị trong cột kiểu MIME. Điều này cho phép bạn lưu trữ các kiểu hàng khác nhau trong + cùng bảng. Bảng "dữ liệu" + {@link android.provider.ContactsContract.Data} của Trình cung cấp Danh bạ là một ví dụ về bảng + độc lập với sơ đồ. +

    +
  • +
+ +

Thiết kế URI Nội dung

+

+ URI nội dung là một URI xác định dữ liệu trong một trình cung cấp. URI nội dung bao gồm + tên mang tính biểu tượng của toàn bộ trình cung cấp (quyền của nó) và một + tên trỏ đến một bảng hoặc tệp (đường dẫn). Phần id tùy chọn chỉ đến một + hàng riêng lẻ trong một bảng. Mọi phương thức truy cập dữ liệu + {@link android.content.ContentProvider} đều có một URI nội dung là một tham đối; điều này cho phép bạn + xác định bảng, hàng, hoặc tệp để truy cập. +

+

+ Nội dung cơ bản của URI nội dung được mô tả trong chủ đề + + Nội dung Cơ bản về Trình cung cấp Nội dung. +

+

Thiết kế một thẩm quyền

+

+ Một trình cung cấp thường có một thẩm quyền duy nhất, đóng vai trò là tên nội bộ Android của nó. Để + tránh xung đột với các trình cung cấp khác, bạn nên sử dụng quyền sở hữu miền Internet (đảo ngược) + làm cơ sở cho thẩm quyền của trình cung cấp của mình. Vì đề xuất này cũng đúng đối với tên gói + Android, bạn có thể định nghĩa thẩm quyền trình cung cấp của mình là phần mở rộng của tên + gói chứa trình cung cấp. Ví dụ, nếu tên gói Android là + com.example.<appname>, bạn nên cấp cho trình cung cấp của mình + thẩm quyền com.example.<appname>.provider. +

+

Thiết kế một cấu trúc đường dẫn

+

+ Nhà phát triển thường tạo URI nội dung từ thẩm quyền bằng cách nối các đường dẫn trỏ đến + các bảng riêng lẻ. Ví dụ, nếu bạn có hai bảng table1 và + table2, bạn kết hợp thẩm quyền từ ví dụ trước để tạo ra + các URI nội dung + com.example.<appname>.provider/table1 và + com.example.<appname>.provider/table2. Các đường dẫn + không bị giới hạn ở một phân đoạn duy nhất, và không cần phải có một bảng cho từng cấp của đường dẫn. +

+

Xử lý ID URI nội dung

+

+ Theo quy ước, các trình cung cấp cho phép truy cập một hàng đơn trong một bảng bằng cách chấp nhận một URI nội dung + có một giá trị ID cho hàng đó ở cuối URI. Cũng theo quy ước, các trình cung cấp sẽ so khớp + giá trị ID với cột _ID của bảng, và thực hiện truy cập yêu cầu đối với + hàng trùng khớp. +

+

+ Quy ước này tạo điều kiện cho một kiểu mẫu thiết kế chung cho các ứng dụng truy cập một trình cung cấp. Ứng dụng + tiến hành truy vấn đối với trình cung cấp và hiển thị kết quả {@link android.database.Cursor} + trong một {@link android.widget.ListView} bằng cách sử dụng {@link android.widget.CursorAdapter}. + Định nghĩa {@link android.widget.CursorAdapter} yêu cầu một trong các cột trong + {@link android.database.Cursor} phải là _ID +

+

+ Sau đó, người dùng chọn một trong các hàng được hiển thị từ UI để xem hoặc sửa đổi + dữ liệu. Ứng dụng sẽ nhận được hàng tương ứng từ {@link android.database.Cursor} làm nền cho + {@link android.widget.ListView}, nhận giá trị _ID cho hàng này, nối nó với + URI nội dung, và gửi yêu cầu truy cập tới trình cung cấp. Sau đó, trình cung cấp có thể thực hiện + truy vấn hoặc sửa đổi đối với chính xác hàng mà người dùng đã chọn. +

+

Kiểu mẫu URI nội dung

+

+ Để giúp bạn chọn hành động nào sẽ thực hiện cho URI nội dung đến, API của trình cung cấp sẽ bao gồm + lớp thuận tiện {@link android.content.UriMatcher}, nó ánh xạ "kiểu mẫu" URI nội dung với + các giá trị số nguyên. Bạn có thể sử dụng các giá trị số nguyên trong một câu lệnh switch mà chọn + hành động mong muốn cho URI nội dung hoặc URI mà khớp với một kiểu mẫu cụ thể. +

+

+ Kiểu mẫu URI nội dung sẽ so khớp các URI nội dung bằng cách sử dụng ký tự đại diện: +

+
    +
  • + *: Khớp một xâu ký tự hợp lệ bất kỳ với chiều dài bất kỳ. +
  • +
  • + #: Khớp một xâu ký tự số có chiều dài bất kỳ. +
  • +
+

+ Lấy một ví dụ về thiết kế và tạo mã xử lý URI nội dung, hãy xét một trình cung cấp có + thẩm quyền com.example.app.provider mà nhận ra các URI nội dung + trỏ đến các bảng sau: +

+
    +
  • + content://com.example.app.provider/table1: Một bảng gọi là table1. +
  • +
  • + content://com.example.app.provider/table2/dataset1: Một bảng gọi là + dataset1. +
  • +
  • + content://com.example.app.provider/table2/dataset2: Một bảng gọi là + dataset2. +
  • +
  • + content://com.example.app.provider/table3: Một bảng gọi là table3. +
  • +
+

+ Trình cung cấp cũng nhận ra những URI nội dung này nếu chúng có một ID hàng được nối kèm, như + ví dụ content://com.example.app.provider/table3/1 đối với hàng được nhận biết bởi + 1 trong table3. +

+

+ Sẽ có thể có các kiểu mẫu URI nội dung sau: +

+
+
+ content://com.example.app.provider/* +
+
+ Khớp với bất kỳ URI nội dung nào trong trình cung cấp. +
+
+ content://com.example.app.provider/table2/*: +
+
+ Khớp với một URI nội dung cho các bảng dataset1 + và dataset2, nhưng không khớp với URI nội dung cho table1 hoặc + table3. +
+
+ content://com.example.app.provider/table3/#: Khớp với một URI nội dung + cho các hàng đơn trong table3, chẳng hạn như + content://com.example.app.provider/table3/6 đối với hàng được xác định bởi + 6. +
+
+

+ Đoạn mã HTML sau cho biết cách hoạt động của các phương pháp trong {@link android.content.UriMatcher}. + Đoạn mã này xử lý các URI cho toàn bộ một bảng khác với URI cho một + hàng đơn, bằng cách sử dụng mẫu hình URI nội dung + content://<authority>/<path> cho các bảng, và + content://<authority>/<path>/<id> cho các hàng đơn. +

+

+ Phương pháp {@link android.content.UriMatcher#addURI(String, String, int) addURI()} ánh xạ một + thẩm quyền và đường dẫn tới một giá trị số nguyên. Phương pháp {@link android.content.UriMatcher#match(Uri) + match()} trả về giá trị số nguyên cho một URI. Câu lệnh switch sẽ chọn + giữa truy vấn toàn bộ bảng và truy vấn cho một bản ghi đơn: +

+
+public class ExampleProvider extends ContentProvider {
+...
+    // Creates a UriMatcher object.
+    private static final UriMatcher sUriMatcher;
+...
+    /*
+     * The calls to addURI() go here, for all of the content URI patterns that the provider
+     * should recognize. 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
+     */
+    sUriMatcher.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.
+     */
+    sUriMatcher.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 (sUriMatcher.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 is not recognized, you should do some error handling here.
+        }
+        // call the code to actually do the query
+    }
+
+

+ Một lớp khác, {@link android.content.ContentUris}, sẽ cung cấp các phương pháp thuận tiện để làm việc + với phần id của URI nội dung. Các lớp {@link android.net.Uri} và + {@link android.net.Uri.Builder} bao gồm các phương pháp thuận tiện cho việc phân tích các đối tượng + {@link android.net.Uri} hiện có và xây dựng các đối tượng mới. +

+ + +

Triển khai Lớp Trình cung cấp Nội dung

+

+ Thực thể {@link android.content.ContentProvider} quản lý truy cập vào + một tập dữ liệu cấu trúc bằng cách xử lý yêu cầu từ các ứng dụng khác. Tất cả các dạng + truy cập cuối cùng đều gọi {@link android.content.ContentResolver}, sau đó nó gọi ra một phương pháp + cụ thể của {@link android.content.ContentProvider} để lấy quyền truy cập. +

+

Phương pháp được yêu cầu

+

+ Lớp tóm tắt {@link android.content.ContentProvider} sẽ định nghĩa sáu phương pháp tóm tắt + mà bạn phải triển khai như một phần lớp con cụ thể của mình. Tất cả những phương pháp này ngoại trừ + {@link android.content.ContentProvider#onCreate() onCreate()} đều được gọi ra bởi một ứng dụng máy khách + đang cố truy cập trình cung cấp nội dung của bạn: +

+
+
+ {@link android.content.ContentProvider#query(Uri, String[], String, String[], String) + query()} +
+
+ Truy xuất dữ liệu từ trình cung cấp của bạn. Sử dụng các tham đối để chọn bảng để + truy vấn, các hàng và cột để trả về, và thứ tự sắp xếp của kết quả. + Trả về dữ liệu như một đối tượng {@link android.database.Cursor}. +
+
+ {@link android.content.ContentProvider#insert(Uri, ContentValues) insert()} +
+
+ Chèn một hàng mới vào trình cung cấp của bạn. Sử dụng các tham đối để lựa chọn + bảng đích và nhận các giá trị cột để sử dụng. Trả về một URI nội dung cho + hàng mới chèn. +
+
+ {@link android.content.ContentProvider#update(Uri, ContentValues, String, String[]) + update()} +
+
+ Cập nhật các hàng hiện tại trong trình cung cấp của bạn. Sử dụng các tham đối để lựa chọn bảng và hàng + để cập nhật và nhận các giá trị cột được cập nhật. Trả về số hàng được cập nhật. +
+
+ {@link android.content.ContentProvider#delete(Uri, String, String[]) delete()} +
+
+ Xóa hàng khỏi trình cung cấp của bạn. Sử dụng các tham đối để lựa chọn bảng và các hàng + cần xóa. Trả về số hàng được xóa. +
+
+ {@link android.content.ContentProvider#getType(Uri) getType()} +
+
+ Trả về kiểu MIME tương ứng với một URI nội dung. Phương pháp này được mô tả chi tiết hơn + trong phần Triển khai Kiểu MIME của Trình cung cấp Nội dung. +
+
+ {@link android.content.ContentProvider#onCreate() onCreate()} +
+
+ Khởi tạo trình cung cấp của bạn. Hệ thống Android sẽ gọi ra phương pháp này ngay lập tức sau khi nó + tạo trình cung cấp của bạn. Để ý rằng trình cung cấp của bạn không được tạo cho đến khi đối tượng + {@link android.content.ContentResolver} cố truy cập nó. +
+
+

+ Để ý rằng những phương pháp này có cùng chữ ký như các phương pháp + {@link android.content.ContentResolver} được đặt tên như nhau. +

+

+ Việc bạn triển khai những phương pháp này nên xét tới các nội dung sau: +

+
    +
  • + Tất cả phương pháp này ngoại trừ {@link android.content.ContentProvider#onCreate() onCreate()} + đều có thể được gọi đồng thời bằng nhiều luồng, vì thế chúng phải an toàn đối với luồng. Để tìm hiểu + thêm về nhiều luồng, hãy xem chủ đề + + Tiến trình và Luồng. +
  • +
  • + Tránh thực hiện những thao tác dài trong {@link android.content.ContentProvider#onCreate() + onCreate()}. Hoãn các tác vụ khởi tạo tới khi chúng thực sự cần thiết. + Phần Triển khai phương pháp onCreate() + sẽ bàn kỹ hơn về vấn đề này. +
  • +
  • + Mặc dù bạn phải triển khai những phương pháp này, mã của bạn không nhất thiết phải làm gì ngoại trừ việc + trả về kiểu dữ liệu kỳ vọng. Ví dụ, bạn có thể muốn ngăn những ứng dụng khác + chèn dữ liệu vào một số bảng. Để làm điều này, bạn có thể bỏ qua lệnh gọi tới + {@link android.content.ContentProvider#insert(Uri, ContentValues) insert()} và trả về + 0. +
  • +
+

Triển khai phương pháp query()

+

+ Phương pháp + {@link android.content.ContentProvider#query(Uri, String[], String, String[], String) + ContentProvider.query()} phải trả về một đối tượng {@link android.database.Cursor}, nếu không nó sẽ thất bại +, đưa ra một lỗi {@link java.lang.Exception}. Nếu bạn đang sử dụng một cơ sở dữ liệu SQLite làm kho lưu trữ dữ liệu của mình +, bạn có thể chỉ cần trả về {@link android.database.Cursor} được trả về bởi một trong các phương pháp + query() của lớp {@link android.database.sqlite.SQLiteDatabase}. + Nếu truy vấn không khớp với bất kỳ hàng nào, bạn nên trả về một thực thể {@link android.database.Cursor} + có phương pháp {@link android.database.Cursor#getCount()} trả về 0. + Bạn chỉ nên trả về null nếu đã xảy ra một lỗi nội bộ trong tiến trình truy vấn. +

+

+ Nếu bạn không đang sử dụng một cơ sở dữ liệu SQLite làm kho lưu trữ dữ liệu của mình, hãy sử dụng một trong các lớp con cụ thể + của {@link android.database.Cursor}. Ví dụ, lớp {@link android.database.MatrixCursor} sẽ triển khai + một con chạy trong đó mỗi hàng là một mảng của {@link java.lang.Object}. Với lớp này, + hãy sử dụng {@link android.database.MatrixCursor#addRow(Object[]) addRow()} để thêm một hàng mới. +

+

+ Nhớ rằng hệ thống Android phải có thể giao tiếp với {@link java.lang.Exception} + qua các ranh giới tiến trình. Android có thể làm vậy cho những trường hợp ngoại lệ sau, điều này có thể hữu ích + trong xử lý lỗi truy vấn: +

+
    +
  • + {@link java.lang.IllegalArgumentException} (Bạn có thể chọn đưa ra lỗi này nếu trình cung cấp của bạn + nhận một URI nội dung không hợp lệ) +
  • +
  • + {@link java.lang.NullPointerException} +
  • +
+

Triển khai phương pháp insert()

+

+ Phương pháp {@link android.content.ContentProvider#insert(Uri, ContentValues) insert()} sẽ thêm một + hàng mới vào bảng phù hợp bằng cách sử dụng các giá trị trong tham đối {@link android.content.ContentValues} +. Nếu tên cột không nằm trong tham đối {@link android.content.ContentValues}, bạn có thể + muốn cung cấp một giá trị mặc định cho nó hoặc trong mã trình cung cấp của bạn hoặc trong sơ đồ + cơ sở dữ liệu của bạn. +

+

+ Phương pháp này sẽ trả về URI nội dung cho hàng mới. Để xây dựng điều này, hãy nối + giá trị _ID của hàng mới (hay khóa chính khác) với URI nội dung của bảng bằng cách sử dụng + {@link android.content.ContentUris#withAppendedId(Uri, long) withAppendedId()}. +

+

Triển khai phương pháp delete()

+

+ Phương pháp {@link android.content.ContentProvider#delete(Uri, String, String[]) delete()} + không cần phải xóa hàng thực chất khỏi kho lưu trữ dữ liệu của bạn. Nếu bạn đang sử dụng một trình điều hợp đồng bộ + với trình cung cấp của mình, bạn nên cân nhắc đánh dấu một hàng đã xóa + bằng cờ "xóa" thay vì gỡ bỏ hàng một cách hoàn toàn. Trình điều hợp đồng bộ có thể + kiểm tra các hàng đã xóa và gỡ bỏ chúng khỏi máy chủ trước khi xóa chúng khỏi trình cung cấp. +

+

Triển khai phương pháp update()

+

+ Phương pháp {@link android.content.ContentProvider#update(Uri, ContentValues, String, String[]) + update()} lấy cùng tham đối {@link android.content.ContentValues} được sử dụng bởi + {@link android.content.ContentProvider#insert(Uri, ContentValues) insert()}, và + cùng tham đối selectionselectionArgs được sử dụng bởi + {@link android.content.ContentProvider#delete(Uri, String, String[]) delete()} và + {@link android.content.ContentProvider#query(Uri, String[], String, String[], String) + ContentProvider.query()}. Điều này có thể cho phép bạn sử dụng lại mã giữa những phương pháp này. +

+

Triển khai phương pháp onCreate()

+

+ Hệ thống Android sẽ gọi {@link android.content.ContentProvider#onCreate() + onCreate()} khi nó khởi động trình cung cấp. Bạn chỉ nên thực hiện các tác vụ khởi tạo chạy nhanh + trong phương pháp này, và hoãn việc tạo cơ sở dữ liệu và nạp dữ liệu tới khi trình cung cấp thực sự + nhận được yêu cầu cho dữ liệu. Nếu bạn thực hiện các tác vụ dài trong + {@link android.content.ContentProvider#onCreate() onCreate()}, bạn sẽ làm chậm lại + quá trình khởi động của trình cung cấp. Đến lượt mình, điều này sẽ làm chậm hồi đáp từ trình cung cấp đối với các + ứng dụng khác. +

+

+ Ví dụ, nếu bạn đang sử dụng một cơ sở dữ liệu SQLite, bạn có thể tạo + một đối tượng {@link android.database.sqlite.SQLiteOpenHelper} mới trong + {@link android.content.ContentProvider#onCreate() ContentProvider.onCreate()}, + rồi tạo các bảng SQL lần đầu tiên khi bạn mở cơ sở dữ liệu. Để tạo điều kiện cho điều này, + lần đầu tiên bạn gọi {@link android.database.sqlite.SQLiteOpenHelper#getWritableDatabase + getWritableDatabase()}, nó sẽ tự động gọi ra phương pháp + {@link android.database.sqlite.SQLiteOpenHelper#onCreate(SQLiteDatabase) + SQLiteOpenHelper.onCreate()}. +

+

+ Hai đoạn mã HTML sau minh họa tương tác giữa + {@link android.content.ContentProvider#onCreate() ContentProvider.onCreate()} và + {@link android.database.sqlite.SQLiteOpenHelper#onCreate(SQLiteDatabase) + SQLiteOpenHelper.onCreate()}. Đoạn mã HTML đầu tiên là triển khai + {@link android.content.ContentProvider#onCreate() ContentProvider.onCreate()}: +

+
+public class ExampleProvider extends ContentProvider
+
+    /*
+     * Defines a handle to the database helper object. The MainDatabaseHelper class is defined
+     * in a following snippet.
+     */
+    private MainDatabaseHelper mOpenHelper;
+
+    // Defines the database name
+    private static final String DBNAME = "mydb";
+
+    // Holds the database object
+    private SQLiteDatabase db;
+
+    public boolean onCreate() {
+
+        /*
+         * Creates a new helper object. This method always returns quickly.
+         * Notice that the database itself isn't created or opened
+         * until SQLiteOpenHelper.getWritableDatabase is called
+         */
+        mOpenHelper = new MainDatabaseHelper(
+            getContext(),        // the application context
+            DBNAME,              // the name of the database)
+            null,                // uses the default SQLite cursor
+            1                    // the version number
+        );
+
+        return true;
+    }
+
+    ...
+
+    // Implements the provider's insert method
+    public Cursor insert(Uri uri, ContentValues values) {
+        // Insert code here to determine which table to open, handle error-checking, and so forth
+
+        ...
+
+        /*
+         * Gets a writeable database. This will trigger its creation if it doesn't already exist.
+         *
+         */
+        db = mOpenHelper.getWritableDatabase();
+    }
+}
+
+

+ Đoạn mã HTML tiếp theo là triển khai + {@link android.database.sqlite.SQLiteOpenHelper#onCreate(SQLiteDatabase) + SQLiteOpenHelper.onCreate()}, bao gồm một lớp trình trợ giúp: +

+
+...
+// A string that defines the SQL statement for creating a table
+private static final String SQL_CREATE_MAIN = "CREATE TABLE " +
+    "main " +                       // Table's name
+    "(" +                           // The columns in the table
+    " _ID INTEGER PRIMARY KEY, " +
+    " WORD TEXT"
+    " FREQUENCY INTEGER " +
+    " LOCALE TEXT )";
+...
+/**
+ * Helper class that actually creates and manages the provider's underlying data repository.
+ */
+protected static final class MainDatabaseHelper extends SQLiteOpenHelper {
+
+    /*
+     * Instantiates an open helper for the provider's SQLite data repository
+     * Do not do database creation and upgrade here.
+     */
+    MainDatabaseHelper(Context context) {
+        super(context, DBNAME, null, 1);
+    }
+
+    /*
+     * Creates the data repository. This is called when the provider attempts to open the
+     * repository and SQLite reports that it doesn't exist.
+     */
+    public void onCreate(SQLiteDatabase db) {
+
+        // Creates the main table
+        db.execSQL(SQL_CREATE_MAIN);
+    }
+}
+
+ + + +

Triển khai Kiểu MIME của Trình cung cấp Nội dung

+

+ Lớp {@link android.content.ContentProvider} có hai phương pháp để trả về các kiểu MIME: +

+
+
+ {@link android.content.ContentProvider#getType(Uri) getType()} +
+
+ Một trong các phương pháp được yêu cầu mà bạn phải triển khai cho bất kỳ trình cung cấp nào. +
+
+ {@link android.content.ContentProvider#getStreamTypes(Uri, String) getStreamTypes()} +
+
+ Một phương pháp mà bạn được dự tính sẽ triển khai nếu trình cung cấp của bạn cung cấp tệp. +
+
+

Kiểu MIME cho bảng

+

+ Phương pháp {@link android.content.ContentProvider#getType(Uri) getType()} trả về một + {@link java.lang.String} theo định dạng MIME mà mô tả kiểu dữ liệu được trả về bởi tham đối + URI nội dung. Tham đối {@link android.net.Uri} có thể là một mẫu hình thay vì một URI cụ thể; + trong trường hợp này, bạn nên trả về kiểu dữ liệu được liên kết với các URI nội dung mà khớp với + mẫu hình đó. +

+

+ Đối với các kiểu dữ liệu phổ biến như văn bản, HTML, hay JPEG, + {@link android.content.ContentProvider#getType(Uri) getType()} sẽ trả về + kiểu MIME tiêu chuẩn cho dữ liệu đó. Một danh sách đầy đủ về những kiểu tiêu chuẩn này có sẵn trên trang web + IANA MIME Media Types +. +

+

+ Đối với các URI nội dung mà trỏ tới một hàng hoặc các hàng của bảng dữ liệu, + {@link android.content.ContentProvider#getType(Uri) getType()} sẽ trả về + một kiểu MIME theo định dạng MIME riêng cho nhà cung cấp của Android: +

+
    +
  • + Bộ phận kiểu: vnd +
  • +
  • + Bộ phận kiểu con: +
      +
    • + Nếu mẫu hình URI áp dụng cho một hàng đơn: android.cursor.item/ +
    • +
    • + Nếu mẫu hình URI áp dụng cho nhiều hơn một hàng: android.cursor.dir/ +
    • +
    +
  • +
  • + Bộ phận riêng theo trình cung cấp: vnd.<name>.<type> +

    + Bạn cung cấp <name><type>. + Giá trị <name> nên là giá trị duy nhất toàn cục, + và giá trị <type> nên là giá trị duy nhất đối với mẫu hình + URI tương ứng. Một lựa chọn hay cho <name> đó là tên công ty của bạn hoặc + một thành phần nào đó trong tên gói Android cho ứng dụng của bạn. Một lựa chọn hay cho + <type> đó là một xâu xác định bảng được liên kết với + URI. +

    + +
  • +
+

+ Ví dụ, nếu thẩm quyền của một trình cung cấp là + com.example.app.provider, và nó làm hiện ra một bảng có tên + table1 thì kiểu MIME cho nhiều hàng trong table1 là: +

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

+ Đối với một hàng đơn của table1, kiểu MIME là: +

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

Kiểu MIME cho tệp

+

+ Nếu trình cung cấp của bạn cung cấp tệp, hãy triển khai + {@link android.content.ContentProvider#getStreamTypes(Uri, String) getStreamTypes()}. + Phương pháp này sẽ trả về một mảng {@link java.lang.String} của kiểu MIME đối với các tệp mà trình cung cấp của bạn + có thể trả về cho một URI nội dung cho trước. Bạn nên lọc các kiểu MIME mà mình cung cấp bằng tham đối bộ lọc + kiểu MIME, sao cho bạn chỉ trả về những kiểu MIME mà máy khách muốn xử lý. +

+

+ Ví dụ, xét một trình cung cấp hình ảnh dưới dạng tệp có định dạng .jpg, + .png.gif. + Nếu một ứng dụng gọi {@link android.content.ContentResolver#getStreamTypes(Uri, String) + ContentResolver.getStreamTypes()} bằng xâu bộ lọc image/* ( + mà là một "hình ảnh"), + khi đó phương pháp {@link android.content.ContentProvider#getStreamTypes(Uri, String) + ContentProvider.getStreamTypes()} sẽ trả về mảng: +

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

+ Nếu ứng dụng chỉ quan tâm đến các tệp .jpg, vậy nó có thể gọi + {@link android.content.ContentResolver#getStreamTypes(Uri, String) + ContentResolver.getStreamTypes()} bằng xâu bộ lọc *\/jpeg, và + {@link android.content.ContentProvider#getStreamTypes(Uri, String) + ContentProvider.getStreamTypes()} sẽ trả về: +

+{"image/jpeg"}
+
+

+ Nếu trình cung cấp của bạn không cung cấp bất kỳ kiểu MIME nào được yêu cầu trong xâu bộ lọc, + {@link android.content.ContentProvider#getStreamTypes(Uri, String) getStreamTypes()} + sẽ trả về null. +

+ + + +

Triển khai một Lớp Hợp đồng

+

+ Lớp hợp đồng là một lớp public final chứa các định nghĩa hằng số cho + URI, tên cột, kiểu MIME, và siêu dữ liệu khác liên quan tới trình cung cấp. Lớp này + sẽ thiết lập một hợp đồng giữa trình cung cấp và các ứng dụng khác bằng cách đảm bảo rằng trình cung cấp + có thể được truy cập đúng ngay cả khi có thay đổi về giá trị thực sự của URI, tên cột, + v.v. +

+

+ Lớp hợp đồng cũng giúp các nhà phát triển vì chúng thường có tên dễ nhớ cho các hằng số của mình, + vì vậy các nhà phát triển ít có khả năng sử dụng các giá trị không đúng cho tên cột hay URI hơn. Do đó là một + lớp, nó có thể chứa tài liệu Javadoc. Các môi trường phát triển tích hợp như + Eclipse có thể tự động điền các tên hằng số từ lớp hợp đồng và hiển thị Javadoc cho các + hằng số đó. +

+

+ Các nhà phát triển không thể truy cập tệp lớp của lớp hợp đồng từ ứng dụng của mình, nhưng họ có thể + lặng lẽ biên dịch nó vào ứng dụng của họ từ một tệp .jar mà bạn cung cấp. +

+

+ Lớp {@link android.provider.ContactsContract} và các lớp lồng nhau của nó là các ví dụ về + lớp hợp đồng. +

+

Triển khai Quyền của Trình cung cấp Nội dung

+

+ Quyền và truy cập đối với tất cả khía cạnh trong hệ thống Android được mô tả chi tiết trong + chủ đề Bảo mật và Quyền. + Chủ đề Kho lưu trữ Dữ liệu cũng + mô tả bảo mật và các quyền có hiệu lực cho nhiều loại kho lưu trữ khác nhau. + Nói tóm lại, các điểm quan trọng là: +

+
    +
  • + Theo mặc định, các tệp dữ liệu được lưu trữ trên bộ nhớ trong của thiết bị là dữ liệu riêng tư + đối với ứng dụng và trình cung cấp của bạn. +
  • +
  • + Các cơ sở dữ liệu {@link android.database.sqlite.SQLiteDatabase} mà bạn tạo là dữ liệu riêng tư + đối với ứng dụng và trình cung cấp của bạn. +
  • +
  • + Theo mặc định, các tệp dữ liệu mà bạn lưu vào bộ nhớ ngoài là dữ liệu công khai và + đọc được công khai. Bạn không thể sử dụng một trình cung cấp nội dung để hạn chế truy cập vào các tệp trong + bộ nhớ ngoài vì các ứng dụng khác có thể sử dụng lệnh gọi API khác để đọc và ghi chúng. +
  • +
  • + Các lệnh gọi phương pháp để mở hoặc tạo tệp hoặc cơ sở dữ liệu SQLite trên bộ nhớ trong + của thiết bị của bạn có thể cấp quyền truy cập đọc và ghi cho tất cả ứng dụng khác. Nếu bạn + sử dụng một tệp hoặc cơ sở dữ liệu nội bộ làm kho lưu giữ của trình cung cấp của mình, và bạn cấp quyền truy cập + "đọc được công khai" hoặc "ghi được công khai", quyền mà bạn đặt cho trình cung cấp của mình trong + bản kê khai của nó sẽ không bảo vệ dữ liệu của bạn. Quyền truy cập mặc định cho các tệp và cơ sở dữ liệu trong + bộ nhớ trong là "riêng tư", và đối với kho lưu giữ của trình cung cấp của mình, bạn không nên thay đổi điều này. +
  • +
+

+ Nếu bạn muốn sử dụng các quyền của trình cung cấp nội dung để kiểm soát truy cập vào dữ liệu của mình, khi đó bạn nên + lưu trữ dữ liệu của mình trong các tệp nội bộ, cơ sở dữ liệu SQLite, hoặc "đám mây" (ví dụ, + trên một máy chủ từ xa), và bạn nên giữ các tệp và cơ sở dữ liệu riêng tư cho ứng dụng của mình. +

+

Triển khai quyền

+

+ Tất cả ứng dụng đều có thể đọc từ hoặc ghi vào trình cung cấp của bạn, ngay cả khi dữ liệu liên quan + là dữ liệu riêng tư, vì theo mặc định, trình cung cấp của bạn không được đặt quyền. Để thay đổi điều này, + hãy đặt quyền cho trình cung cấp của bạn trong tệp bản kê khai của bạn bằng cách sử dụng các thuộc tính hoặc phần tử + con của phần tử + <provider>. Bạn có thể đặt quyền áp dụng cho toàn bộ trình cung cấp, + hoặc cho một số bảng, hoặc thậm chí cho một số bản ghi, hoặc cả ba. +

+

+ Bạn định nghĩa các quyền cho trình cung cấp của bạn bằng một hoặc nhiều phần tử + + <permission> trong tệp bản kê khai của bạn. Để + quyền là duy nhất cho trình cung cấp của bạn, hãy sử dụng phạm vi kiểu Java cho thuộc tính + + android:name. Ví dụ, đặt tên quyền đọc + com.example.app.provider.permission.READ_PROVIDER. + +

+

+ Danh sách sau liệt kê phạm vi các quyền của trình cung cấp, bắt đầu với các quyền + áp dụng cho toàn bộ trình cung cấp rồi mới đến các quyền chi tiết hơn. + Các quyền chi tiết hơn được ưu tiên so với các quyền có phạm vi rộng hơn: +

+
+
+ Quyền đọc-ghi đơn lẻ ở cấp trình cung cấp +
+
+ Một quyền kiểm soát cả quyền truy cập đọc và ghi cho toàn bộ trình cung cấp, được quy định + bằng thuộc tính + android:permission của phần tử + + <provider>. +
+
+ Quyền đọc ghi tách riêng ở cấp độ trình cung cấp +
+
+ Một quyền đọc và một quyền ghi cho toàn bộ trình cung cấp. Bạn chỉ định chúng + bằng các thuộc tính + android:readPermission và + + android:writePermission của phần tử + + <provider>. Chúng được ưu tiên so với quyền được yêu cầu bởi + + android:permission. +
+
+ Quyền ở cấp đường dẫn +
+
+ Quyền đọc, ghi, hoặc đọc/ghi cho một URI nội dung trong trình cung cấp của bạn. Bạn chỉ định + từng URI mà bạn muốn kiểm soát bằng một phần tử con + + <path-permission> của phần tử + + <provider>. Với mỗi một URI nội dung mà bạn chỉ định, bạn có thể chỉ định một + quyền đọc/ghi, quyền đọc, hoặc quyền ghi, hoặc cả ba. Quyền đọc và + quyền ghi được ưu tiên so với quyền đọc/ghi. Đồng thời, quyền ở cấp độ đường dẫn + sẽ được ưu tiên so với quyền ở cấp độ trình cung cấp. +
+
+ Quyền tạm thời +
+
+ Là cấp độ quyền cho phép truy cập tạm thời vào một ứng dụng, ngay cả khi ứng dụng + không có các quyền thường được yêu cầu. Tính năng truy cập + tạm thời làm giảm số quyền mà một ứng dụng phải yêu cầu trong + bản kê khai của mình. Khi bạn dùng đến các quyền tạm thời, những ứng dụng duy nhất mà cần + quyền "lâu dài" cho trình cung cấp của bạn là những ứng dụng liên tục truy cập tất cả + dữ liệu của bạn. +

+ Xét các quyền bạn cần để triển khai một trình cung cấp và ứng dụng e-mail khi bạn + muốn cho phép một ứng dụng trình xem ảnh bên ngoài hiển thị các tài liệu đính kèm dạng ảnh từ trình cung cấp + của bạn. Để cấp cho trình xem ảnh quyền truy cập cần thiết mà không cần yêu cầu quyền, + hãy thiết lập các quyền tạm thời cho URI nội dung đối với ảnh. Thiết kế ứng dụng e-mail của bạn sao cho + khi người dùng muốn hiển thị một ảnh, ứng dụng sẽ gửi một ý định chứa URI nội dung + của ảnh và cờ cho phép tới trình xem ảnh. Trình xem ảnh khi đó có thể + truy vấn trình cung cấp e-mail của bạn để truy xuất ảnh, ngay cả khi trình xem không + có quyền đọc bình thường cho trình cung cấp của bạn. +

+

+ Để sử dụng các quyền tạm thời, hoặc đặt thuộc tính + + android:grantUriPermissions của phần tử + + <provider> hoặc thêm một hoặc nhiều phần tử con + + <grant-uri-permission> vào phần tử + + <provider> của bạn. Nếu bạn sử dụng các quyền tạm thời, bạn phải gọi + {@link android.content.Context#revokeUriPermission(Uri, int) + Context.revokeUriPermission()} bất cứ khi nào bạn gỡ bỏ hỗ trợ cho một URI nội dung khỏi + trình cung cấp của mình, và URI nội dung đó sẽ được liên kết với một quyền tạm thời. +

+

+ Giá trị của thuộc tính sẽ xác định trình cung cấp của bạn được cho phép truy cập bao nhiêu. + Nếu thuộc tính được đặt thành true, khi đó hệ thống sẽ cấp quyền tạm thời + cho toàn bộ trình cung cấp của bạn, khống chế mọi quyền khác mà được yêu cầu bởi + quyền ở cấp độ trình cung cấp hoặc cấp độ đường dẫn của bạn. +

+

+ Nếu cờ này được đặt thành false, khi đó bạn phải thêm các phần tử con + + <grant-uri-permission> vào phần tử + + <provider> của mình. Mỗi phần tử con lại quy định URI nội dung hoặc + các URI mà truy cập tạm thời được cấp cho. +

+

+ Để ủy quyền truy cập tạm thời cho một ứng dụng, ý định phải chứa + cờ {@link android.content.Intent#FLAG_GRANT_READ_URI_PERMISSION} hoặc cờ + {@link android.content.Intent#FLAG_GRANT_WRITE_URI_PERMISSION}, hoặc cả hai. Những quyền + này được đặt bằng phương pháp {@link android.content.Intent#setFlags(int) setFlags()}. +

+

+ Nếu thuộc tính + android:grantUriPermissions không có mặt, giả sử rằng nó là + false. +

+
+
+ + + + +

Phần tử <provider>

+

+ Như các thành phần {@link android.app.Activity} và {@link android.app.Service}, + một lớp con của {@link android.content.ContentProvider} + phải được định nghĩa trong tệp bản kê khai cho ứng dụng của nó bằng cách sử dụng phần tử + + <provider>. Hệ thống Android nhận thông tin sau từ + phần tử: +

+
+ Thẩm quyền + ({@code + android:authorities}) +
+
+ Các tên biểu tượng nhận biết toàn bộ trình cung cấp trong hệ thống. Thuộc tính + này được mô tả chi tiết hơn trong phần + Thiết kế URI Nội dung. +
+
+ Tên lớp của trình cung cấp + ( +android:name + ) +
+
+ Lớp triển khai {@link android.content.ContentProvider}. Lớp này + được mô tả chi tiết hơn trong phần + Triển khai Lớp Trình cung cấp Nội dung. +
+
+ Quyền +
+
+ Những thuộc tính quy định quyền mà các ứng dụng khác phải có để truy cập + dữ liệu của trình cung cấp: + +

+ Các quyền và thuộc tính tương ứng của chúng được mô tả chi tiết hơn trong + phần + Triển khai Quyền của Trình cung cấp Nội dung. +

+
+
+ Thuộc tính khởi động và kiểm soát +
+
+ Những thuộc tính này xác định cách và thời điểm hệ thống Android khởi động trình cung cấp, các + đặc tính tiến trình của trình cung cấp, và các thiết đặt về thời gian chạy: +
    +
  • + + android:enabled: Cờ cho phép hệ thống khởi động trình cung cấp. +
  • +
  • + + android:exported: Cờ cho phép các ứng dụng sử dụng trình cung cấp này. +
  • +
  • + + android:initOrder: Thứ tự mà trình cung cấp nên được khởi động, + so với các trình cung cấp khác trong cùng tiến trình. +
  • +
  • + + android:multiProcess: Cờ cho phép hệ thống khởi động trình cung cấp + trong cùng tiến trình như máy khách gọi. +
  • +
  • + + android:process: Tên của tiến trình mà trình cung cấp + nên chạy trong đó. +
  • +
  • + + android:syncable: Cờ cho biết rằng dữ liệu của trình cung cấp sẽ được + đồng bộ với dữ liệu trên một máy chủ. +
  • +
+

+ Các thuộc tính được lập tài liệu theo dõi đầy đủ trong chủ đề hướng dẫn nhà phát triển đối với phần tử + + <provider> +. +

+
+
+ Các thuộc tính thông tin +
+
+ Một biểu tượng tùy chọn và nhãn cho trình cung cấp: +
    +
  • + + android:icon: Một tài nguyên có thể vẽ chứa một biểu tượng cho trình cung cấp. + Biểu tượng xuất hiện bên cạnh nhãn của trình cung cấp trong danh sách ứng dụng trong + Settings > Apps > All. +
  • +
  • + + android:label: Một nhãn thông tin mô tả trình cung cấp hoặc dữ liệu + của nó, hoặc cả hai. Nhãn xuất hiện trong danh sách ứng dụng trong + Settings > Apps > All. +
  • +
+

+ Các thuộc tính được lập tài liệu theo dõi đầy đủ trong chủ đề hướng dẫn nhà phát triển đối với phần tử + + <provider>. +

+
+
+ + +

Ý định và Truy cập Dữ liệu

+

+ Các ứng dụng có thể gián tiếp truy cập một trình cung cấp nội dung bằng một {@link android.content.Intent}. + Ứng dụng không gọi bất kỳ phương pháp nào của {@link android.content.ContentResolver} hoặc + {@link android.content.ContentProvider}. Thay vào đó, nó sẽ gửi một ý định để bắt đầu một hoạt động, + đây thường là một bộ phận trong ứng dụng của chính trình cung cấp. Hoạt động đích phụ trách + truy xuất và hiển thị dữ liệu trong UI của nó. Tùy vào hành động trong ý định, hoạt động + đích cũng có thể nhắc người dùng thực hiện sửa đổi dữ liệu của trình cung cấp. + Một ý định cũng có thể chứa dữ liệu "phụ thêm" mà hoạt động đích hiển thị + trong UI; khi đó người dùng có tùy chọn thay đổi dữ liệu này trước khi sử dụng nó để sửa đổi + dữ liệu trong trình cung cấp. +

+

+ +

+

+ Bạn có thể muốn sử dụng truy cập ý định để giúp đảm bảo toàn vẹn dữ liệu. Trình cung cấp của bạn có thể phụ thuộc vào + việc chèn, cập nhật và xóa dữ liệu theo lô-gic nghiệp vụ được quy định chặt chẽ. Trong + trường hợp như vậy, việc cho phép các ứng dụng khác trực tiếp sửa đổi dữ liệu của bạn có thể dẫn đến dữ liệu + không hợp lệ. Nếu bạn muốn các nhà phát triển sử dụng truy cập ý định, hãy đảm bảo lập tài liệu theo dõi nó thật kỹ. + Giải thích với họ tại sao truy cập ý định sử dụng UI ứng dụng của chính bạn lại tốt hơn là cố gắng sửa đổi + dữ liệu bằng mã của họ. +

+

+ Việc xử lý một ý định đến nhằm sửa đổi dữ liệu của trình cung cấp của bạn không khác với + việc xử lý các ý định khác. Bạn có thể tìm hiểu về việc sử dụng ý định bằng cách đọc chủ đề + Ý định và Bộ lọc Ý định. +

diff --git a/docs/html-intl/intl/vi/guide/topics/providers/content-providers.jd b/docs/html-intl/intl/vi/guide/topics/providers/content-providers.jd new file mode 100644 index 0000000000000000000000000000000000000000..0b0233705d879bc9aa9a9deee542240bee124499 --- /dev/null +++ b/docs/html-intl/intl/vi/guide/topics/providers/content-providers.jd @@ -0,0 +1,108 @@ +page.title=Trình cung cấp Nội dung +@jd:body + +

+ Trình cung cấp nội dung quản lý truy cập vào một tập dữ liệu cấu trúc. Chúng gói gọn + dữ liệu và cung cấp các cơ chế để định nghĩa bảo mật dữ liệu. Trình cung cấp nội dung là + giao diện tiêu chuẩn kết nối dữ liệu trong một tiến trình với mã đang chạy trong một tiến trình khác. +

+

+ Khi bạn truy cập dữ liệu trong một trình cung cấp nội dung, bạn sử dụng đối tượng + {@link android.content.ContentResolver} trong + {@link android.content.Context} của ứng dụng của bạn để giao tiếp với trình cung cấp như một máy khách. + Đối tượng {@link android.content.ContentResolver} giao tiếp với đối tượng trình cung cấp, một + thực thể của lớp triển khai {@link android.content.ContentProvider}. Đối tượng + trình cung cấp nhận các yêu cầu dữ liệu từ máy khách, thực hiện hành động được yêu cầu, và + trả về kết quả. +

+

+ Bạn không cần phát triển trình cung cấp của chính mình nếu không có ý định chia sẻ dữ liệu của bạn với + các ứng dụng khác. Tuy nhiên, bạn cần phải có trình cung cấp của chính mình để cung cấp các gợi ý tìm kiếm tùy chỉnh + trong ứng dụng của chính bạn. Bạn cũng cần phải có trình cung cấp của chính mình nếu muốn sao chép và + dán dữ liệu hoặc tệp phức tạp từ ứng dụng của bạn sang các ứng dụng khác. +

+

+ Bản thân Android bao gồm các trình cung cấp nội dung chuyên quản lý dữ liệu như âm thanh, video, hình ảnh và + thông tin liên lạc cá nhân. Bạn có thể thấy một số được liệt kê trong tài liệu + tham khảo cho gói + android.provider + . Với một số hạn chế, những trình cung cấp này có thể truy cập vào bất kỳ ứng dụng + Android nào. +

+ Các chủ đề sau mô tả chi tiết hơn về các trình cung cấp nội dung: +

+
+
+ + Nội dung Cơ bản về Trình cung cấp Nội dung +
+
+ Cách truy cập dữ liệu trong một trình cung cấp nội dung khi dữ liệu được tổ chức dưới dạng bảng. +
+
+ + Tạo một Trình cung cấp Nội dung +
+
+ Cách tạo trình cung cấp nội dung của chính bạn. +
+
+ + Trình cung cấp Lịch +
+
+ Cách truy cập Trình cung cấp Lịch mà là một bộ phận của nền tảng Android. +
+
+ + Trình cung cấp Danh bạ +
+
+ Cách truy cập Trình cung cấp Danh bạ mà là một bộ phận của nền tảng Android. +
+
diff --git a/docs/html-intl/intl/vi/guide/topics/providers/document-provider.jd b/docs/html-intl/intl/vi/guide/topics/providers/document-provider.jd new file mode 100644 index 0000000000000000000000000000000000000000..30844d7c277e2639b6107ff5b2f3af0ebbd2e2f5 --- /dev/null +++ b/docs/html-intl/intl/vi/guide/topics/providers/document-provider.jd @@ -0,0 +1,916 @@ +page.title=Khuôn khổ Truy cập Kho lưu trữ +@jd:body + + + +

Android 4.4 (API mức 19) giới thiệu Khuôn khổ Truy cập Kho lưu trữ (SAF). SAF + giúp người dùng đơn giản hóa việc duyệt và mở tài liệu, hình ảnh và các tệp khác +giữa tất cả trình cung cấp lưu trữ tài liệu mà họ thích. UI tiêu chuẩn, dễ sử dụng +cho phép người dùng duyệt tệp và truy cập hoạt động gần đây một cách nhất quán giữa các ứng dụng và trình cung cấp.

+ +

Dịch vụ lưu trữ đám mây hoặc cục bộ có thể tham gia vào hệ sinh thái này bằng cách triển khai một +{@link android.provider.DocumentsProvider} để gói gọn các dịch vụ của mình. Những ứng dụng +máy khách cần truy cập vào tài liệu của một trình cung cấp có thể tích hợp với SAF chỉ bằng một vài +dòng mã.

+ +

SAF bao gồm:

+ +
    +
  • Trình cung cấp tài liệu—Một trình cung cấp nội dung cho phép một +dịch vụ lưu trữ (chẳng hạn như Google Drive) phát hiện các tệp mà nó quản lý. Trình cung cấp tài liệu được +triển khai thành một lớp con của lớp {@link android.provider.DocumentsProvider}. +Sơ đồ tài liệu-trình cung cấp sẽ được dựa trên một phân cấp tệp truyền thống, +cho dù cách thức trình cung cấp tài liệu của bạn trực tiếp lưu trữ dữ liệu là hoàn toàn do bạn. +Nền tảng Android bao gồm một vài trình cung cấp tài liệu tích hợp, chẳng hạn như +Downloads, Images, và Videos.
  • + +
  • Ứng dụng máy khách—Một ứng dụng tùy chỉnh có chức năng gọi ra ý định +{@link android.content.Intent#ACTION_OPEN_DOCUMENT} và/hoặc +{@link android.content.Intent#ACTION_CREATE_DOCUMENT} và nhận các tệp +được trả về bởi trình cung cấp tài liệu.
  • + +
  • Bộ chọn—Một UI hệ thống cho phép người dùng truy cập tài liệu từ tất cả +trình cung cấp tài liệu mà thỏa mãn các tiêu chí tìm kiếm của ứng dụng máy khách.
  • +
+ +

Một số tính năng được SAF cung cấp bao gồm:

+
    +
  • Cho phép người dùng duyệt nội dung từ tất cả trình cung cấp tài liệu, không chỉ một ứng dụng duy nhất.
  • +
  • Giúp ứng dụng của bạn có thể có quyền truy cập lâu dài, cố định vào + các tài liệu được sở hữu bởi một trình cung cấp tài liệu. Thông qua truy cập này, người dùng có thể thêm, chỉnh sửa, + lưu và xóa tệp trên trình cung cấp.
  • +
  • Hỗ trợ nhiều tài khoản người dùng và các phần gốc tạm thời chẳng hạn như trình cung cấp +bộ nhớ USB, nó chỉ xuất hiện nếu ổ đĩa được cắm vào.
  • +
+ +

Tổng quan

+ +

SAF tập trung xoay quanh một trình cung cấp nội dung là một lớp con +của lớp {@link android.provider.DocumentsProvider}. Trong một trình cung cấp tài liệu, dữ liệu được +cấu trúc thành một phân cấp tệp truyền thống:

+

data model

+

Hình 1. Mô hình dữ liệu của trình cung cấp tài liệu. Một Phần gốc chỉ đến một Tài liệu duy nhất, +sau đó nó bắt đầu xòe ra toàn bộ cây.

+ +

Lưu ý điều sau đây:

+
    + +
  • Mỗi một trình cung cấp tài liệu sẽ báo cáo một hoặc nhiều +"phần gốc" là điểm bắt đầu khám phá cây tài liệu. +Mỗi phần gốc có một {@link android.provider.DocumentsContract.Root#COLUMN_ROOT_ID} duy nhất, +và nó trỏ đến một tài liệu (thư mục) +biểu diễn nội dung bên dưới phần gốc đó. +Phần gốc có thể linh hoạt theo thiết kế để hỗ trợ các trường hợp sử dụng như nhiều tài khoản, +thiết bị lưu trữ USB tạm thời, hoặc đăng nhập/đăng xuất người dùng.
  • + +
  • Dưới mỗi phần gốc là một tài liệu đơn lẻ. Tài liệu đó sẽ trỏ tới 1 đến N tài liệu, +mỗi tài liệu lại có thể trỏ tới 1 đến N tài liệu khác.
  • + +
  • Mỗi bộ nhớ phụ trợ phủ bề mặt +các tệp và thư mục riêng lẻ bằng cách tham chiếu chúng bằng một +{@link android.provider.DocumentsContract.Document#COLUMN_DOCUMENT_ID} duy nhất. +ID của tài liệu phải là duy nhất và không thay đổi sau khi được phát hành, do chúng được sử dụng để cấp URI +không thay đổi giữa các lần khởi động lại thiết bị.
  • + + +
  • Tài liệu có thể là một tệp mở được (có một kiểu MIME cụ thể), hoặc một +thư mục chứa các tài liệu bổ sung (có kiểu MIME +{@link android.provider.DocumentsContract.Document#MIME_TYPE_DIR}).
  • + +
  • Mỗi tài liệu có thể có các khả năng khác nhau như được mô tả bởi +{@link android.provider.DocumentsContract.Document#COLUMN_FLAGS COLUMN_FLAGS}. +Ví dụ, {@link android.provider.DocumentsContract.Document#FLAG_SUPPORTS_WRITE}, +{@link android.provider.DocumentsContract.Document#FLAG_SUPPORTS_DELETE}, và +{@link android.provider.DocumentsContract.Document#FLAG_SUPPORTS_THUMBNAIL}. +{@link android.provider.DocumentsContract.Document#COLUMN_DOCUMENT_ID} cũng có thể +có trong nhiều thư mục.
  • +
+ +

Dòng Điều khiển

+

Như nêu trên, mô hình dữ liệu của trình cung cấp tài liệu được dựa trên một phân cấp +tệp truyền thống. Tuy nhiên, bạn có thể thực tế lưu trữ dữ liệu của mình bằng bất kỳ cách nào mà mình thích, miễn +là nó có thể được truy cập thông qua API {@link android.provider.DocumentsProvider}. Ví dụ, bạn +có thể sử dụng kho lưu trữ đám mây dựa trên tag cho dữ liệu của mình.

+ +

Hình 2 minh họa một ví dụ về cách mà một ứng dụng ảnh có thể sử dụng SAF +để truy cập dữ liệu được lưu trữ:

+

app

+ +

Hình 2. Dòng Khuôn khổ Truy cập Kho lưu trữ

+ +

Lưu ý điều sau đây:

+
    + +
  • Trong SAF, trình cung cấp và máy khách không tương tác +trực tiếp với nhau. Một máy khách yêu cầu quyền để tương tác +với tệp (cụ thể là quyền đọc, chỉnh sửa, tạo hoặc xóa tệp).
  • + +
  • Tương tác bắt đầu khi một ứng dụng (trong ví dụ này này một ứng dụng ảnh) thể hiện ý định +{@link android.content.Intent#ACTION_OPEN_DOCUMENT} hoặc {@link android.content.Intent#ACTION_CREATE_DOCUMENT}. Ý định có thể bao gồm các bộ lọc +để cụ thể hơn các tiêu chí—ví dụ, "cấp cho tôi tất cả tệp mở được +có kiểu MIME là 'image'."
  • + +
  • Sau khi ý định thể hiện, bộ chọn của hệ thống sẽ đi đến từng trình cung cấp được đăng ký +và hiển thị cho người dùng xem các phần gốc nội dung khớp với tiêu chí.
  • + +
  • Bộ chọn cấp cho người dùng một giao diện tiêu chuẩn để truy cập tài liệu, mặc +dù các trình cung cấp tài liệu liên quan có thể rất khác nhau. Ví dụ, hình 2 +minh họa một trình cung cấp Google Drive, một trình cung cấp USB, và một trình cung cấp đám mây.
  • +
+ +

Hình 3 minh họa một bộ chọn mà trong đó một người dùng đang tìm kiếm hình ảnh đã chọn một +tài khoản Google Drive:

+ +

picker

+ +

Hình 3. Bộ chọn

+ +

Khi người dùng chọn Google Drive, hình ảnh được hiển thị như minh họa trong +hình 4. Từ điểm đó trở đi, người dùng có thể tương tác với chúng theo bất kỳ cách nào +được hỗ trợ bởi trình cung cấp và ứng dụng máy khách. + +

picker

+ +

Hình 4. Hình ảnh

+ +

Ghi một Ứng dụng Máy khách

+ +

Trên phiên bản Android 4.3 và thấp hơn, nếu bạn muốn ứng dụng của mình truy xuất một tệp từ một ứng dụng +khác, nó phải gọi ra một ý định chẳng hạn như {@link android.content.Intent#ACTION_PICK} +hay {@link android.content.Intent#ACTION_GET_CONTENT}. Khi đó, người dùng phải chọn +một ứng dụng duy nhất mà từ đó họ chọn một tệp và ứng dụng được chọn phải cung cấp một +giao diện người dùng để người dùng duyệt và chọn từ các tệp có sẵn.

+ +

Trên phiên bản Android 4.4 trở lên, bạn có thêm một tùy chọn là sử dụng ý định +{@link android.content.Intent#ACTION_OPEN_DOCUMENT}, +nó hiển thị một UI bộ chọn được điều khiển bởi hệ thống, cho phép người dùng +duyệt tất cả tệp mà các ứng dụng khác đã cung cấp. Từ UI duy nhất này, người dùng +có thể chọn một tệp từ bất kỳ ứng dụng nào được hỗ trợ.

+ +

{@link android.content.Intent#ACTION_OPEN_DOCUMENT} không +nhằm mục đích thay thế cho {@link android.content.Intent#ACTION_GET_CONTENT}. +Bạn nên sử dụng cái nào sẽ phụ thuộc vào nhu cầu của ứng dụng của bạn:

+ +
    +
  • Sử dụng {@link android.content.Intent#ACTION_GET_CONTENT} nếu bạn muốn ứng dụng của mình +chỉ đơn thuần đọc/nhập dữ liệu. Bằng cách này, ứng dụng nhập một bản sao dữ liệu, +chẳng hạn như một tệp hình ảnh.
  • + +
  • Sử dụng {@link android.content.Intent#ACTION_OPEN_DOCUMENT} nếu bạn muốn ứng dụng +của mình có quyền truy cập lâu dài, cố định vào các tài liệu được sở hữu bởi một +trình cung cấp tài liệu. Ví dụ như trường hợp một ứng dụng chỉnh sửa ảnh cho phép người dùng chỉnh sửa +các hình ảnh được lưu trữ trong một trình cung cấp tài liệu.
  • + +
+ + +

Phần này mô tả cách ghi các ứng dụng máy khách dựa trên +{@link android.content.Intent#ACTION_OPEN_DOCUMENT} và +các ý định {@link android.content.Intent#ACTION_CREATE_DOCUMENT}.

+ + + + +

+Đoạn mã HTML sau sử dụng {@link android.content.Intent#ACTION_OPEN_DOCUMENT} +để tìm kiếm các trình cung cấp tài liệu mà +chứa tệp hình ảnh:

+ +
private static final int READ_REQUEST_CODE = 42;
+...
+/**
+ * Fires an intent to spin up the "file chooser" UI and select an image.
+ */
+public void performFileSearch() {
+
+    // ACTION_OPEN_DOCUMENT is the intent to choose a file via the system's file
+    // browser.
+    Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
+
+    // Filter to only show results that can be "opened", such as a
+    // file (as opposed to a list of contacts or timezones)
+    intent.addCategory(Intent.CATEGORY_OPENABLE);
+
+    // Filter to show only images, using the image MIME data type.
+    // If one wanted to search for ogg vorbis files, the type would be "audio/ogg".
+    // To search for all documents available via installed storage providers,
+    // it would be "*/*".
+    intent.setType("image/*");
+
+    startActivityForResult(intent, READ_REQUEST_CODE);
+}
+ +

Lưu ý điều sau đây:

+
    +
  • Khi ứng dụng thể hiện ý định {@link android.content.Intent#ACTION_OPEN_DOCUMENT} +, nó sẽ khởi chạy một bộ chọn để hiển thị tất cả trình cung cấp tài liệu khớp với tiêu chí.
  • + +
  • Thêm thể loại {@link android.content.Intent#CATEGORY_OPENABLE} vào +ý định sẽ lọc kết quả để chỉ hiển thị những tài liệu có thể mở được, chẳng hạn như tệp hình ảnh.
  • + +
  • Câu lệnh {@code intent.setType("image/*")} sẽ lọc thêm để +chỉ hiển thị những tài liệu có kiểu dữ liệu MIME hình ảnh.
  • +
+ +

Kết quả Tiến trình

+ +

Sau khi người dùng chọn một tài liệu trong bộ chọn, +{@link android.app.Activity#onActivityResult onActivityResult()} sẽ được gọi. +URI tro tới tài liệu được chọn sẽ nằm trong tham số {@code resultData} +. Trích xuất UI bằng cách sử dụng {@link android.content.Intent#getData getData()}. +Sau khi có nó, bạn có thể sử dụng nó để truy xuất tài liệu mà người dùng muốn. Ví +dụ:

+ +
@Override
+public void onActivityResult(int requestCode, int resultCode,
+        Intent resultData) {
+
+    // The ACTION_OPEN_DOCUMENT intent was sent with the request code
+    // READ_REQUEST_CODE. If the request code seen here doesn't match, it's the
+    // response to some other intent, and the code below shouldn't run at all.
+
+    if (requestCode == READ_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
+        // The document selected by the user won't be returned in the intent.
+        // Instead, a URI to that document will be contained in the return intent
+        // provided to this method as a parameter.
+        // Pull that URI using resultData.getData().
+        Uri uri = null;
+        if (resultData != null) {
+            uri = resultData.getData();
+            Log.i(TAG, "Uri: " + uri.toString());
+            showImage(uri);
+        }
+    }
+}
+
+ +

Kiểm tra siêu dữ liệu tài liệu

+ +

Sau khi có URI cho một tài liệu, bạn có quyền truy cập siêu dữ liệu của nó. Đoạn mã HTML +này bắt siêu dữ liệu cho một tài liệu được quy định bởi URI, và ghi lại nó:

+ +
public void dumpImageMetaData(Uri uri) {
+
+    // The query, since it only applies to a single document, will only return
+    // one row. There's no need to filter, sort, or select fields, since we want
+    // all fields for one document.
+    Cursor cursor = getActivity().getContentResolver()
+            .query(uri, null, null, null, null, null);
+
+    try {
+    // moveToFirst() returns false if the cursor has 0 rows.  Very handy for
+    // "if there's anything to look at, look at it" conditionals.
+        if (cursor != null && cursor.moveToFirst()) {
+
+            // Note it's called "Display Name".  This is
+            // provider-specific, and might not necessarily be the file name.
+            String displayName = cursor.getString(
+                    cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME));
+            Log.i(TAG, "Display Name: " + displayName);
+
+            int sizeIndex = cursor.getColumnIndex(OpenableColumns.SIZE);
+            // If the size is unknown, the value stored is null.  But since an
+            // int can't be null in Java, the behavior is implementation-specific,
+            // which is just a fancy term for "unpredictable".  So as
+            // a rule, check if it's null before assigning to an int.  This will
+            // happen often:  The storage API allows for remote files, whose
+            // size might not be locally known.
+            String size = null;
+            if (!cursor.isNull(sizeIndex)) {
+                // Technically the column stores an int, but cursor.getString()
+                // will do the conversion automatically.
+                size = cursor.getString(sizeIndex);
+            } else {
+                size = "Unknown";
+            }
+            Log.i(TAG, "Size: " + size);
+        }
+    } finally {
+        cursor.close();
+    }
+}
+
+ +

Mở một tài liệu

+ +

Sau khi có URI cho một tài liệu, bạn có thể mở nó hoặc làm bất kỳ điều gì +mà bạn muốn.

+ +

Bitmap

+ +

Sau đây là một ví dụ về cách bạn có thể mở một {@link android.graphics.Bitmap}:

+ +
private Bitmap getBitmapFromUri(Uri uri) throws IOException {
+    ParcelFileDescriptor parcelFileDescriptor =
+            getContentResolver().openFileDescriptor(uri, "r");
+    FileDescriptor fileDescriptor = parcelFileDescriptor.getFileDescriptor();
+    Bitmap image = BitmapFactory.decodeFileDescriptor(fileDescriptor);
+    parcelFileDescriptor.close();
+    return image;
+}
+
+ +

Lưu ý rằng bạn không nên thực hiện thao tác này trên luồng UI. Thực hiện điều này dưới +nền bằng cách sử dụng {@link android.os.AsyncTask}. Sau khi mở bitmap, bạn có thể +hiển thị nó trong một {@link android.widget.ImageView}. +

+ +

Nhận một InputStream

+ +

Sau đây là một ví dụ về cách mà bạn có thể nhận một {@link java.io.InputStream} từ URI. Trong đoạn mã HTML +này, các dòng tệp đang được đọc thành một xâu:

+ +
private String readTextFromUri(Uri uri) throws IOException {
+    InputStream inputStream = getContentResolver().openInputStream(uri);
+    BufferedReader reader = new BufferedReader(new InputStreamReader(
+            inputStream));
+    StringBuilder stringBuilder = new StringBuilder();
+    String line;
+    while ((line = reader.readLine()) != null) {
+        stringBuilder.append(line);
+    }
+    fileInputStream.close();
+    parcelFileDescriptor.close();
+    return stringBuilder.toString();
+}
+
+ +

Tạo một tài liệu mới

+ +

Ứng dụng của bạn có thể tạo một tài liệu mới trong một trình cung cấp tài liệu bằng cách sử dụng ý định +{@link android.content.Intent#ACTION_CREATE_DOCUMENT} +. Để tạo một tệp, bạn cấp cho ý định của mình một kiểu MIME và tên tệp, và +khởi chạy nó bằng một mã yêu cầu duy nhất. Phần còn lại sẽ được làm hộ bạn:

+ + +
+// Here are some examples of how you might call this method.
+// The first parameter is the MIME type, and the second parameter is the name
+// of the file you are creating:
+//
+// createFile("text/plain", "foobar.txt");
+// createFile("image/png", "mypicture.png");
+
+// Unique request code.
+private static final int WRITE_REQUEST_CODE = 43;
+...
+private void createFile(String mimeType, String fileName) {
+    Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
+
+    // Filter to only show results that can be "opened", such as
+    // a file (as opposed to a list of contacts or timezones).
+    intent.addCategory(Intent.CATEGORY_OPENABLE);
+
+    // Create a file with the requested MIME type.
+    intent.setType(mimeType);
+    intent.putExtra(Intent.EXTRA_TITLE, fileName);
+    startActivityForResult(intent, WRITE_REQUEST_CODE);
+}
+
+ +

Sau khi tạo một tài liệu mới, bạn có thể nhận URI của tài liệu trong +{@link android.app.Activity#onActivityResult onActivityResult()}, sao cho bạn +có thể tiếp tục ghi nó.

+ +

Xóa một tài liệu

+ +

Nếu bạn có URI cho một tài liệu và +{@link android.provider.DocumentsContract.Document#COLUMN_FLAGS Document.COLUMN_FLAGS} +của tài liệu chứa +{@link android.provider.DocumentsContract.Document#FLAG_SUPPORTS_DELETE SUPPORTS_DELETE}, +bạn có thể xóa tài liệu đó. Ví dụ:

+ +
+DocumentsContract.deleteDocument(getContentResolver(), uri);
+
+ +

Chỉnh sửa một tài liệu

+ +

Bạn có thể sử dụng SAF để chỉnh sửa một tài liệu văn bản ngay tại chỗ. +Đoạn mã HTML này thể hiện +ý định {@link android.content.Intent#ACTION_OPEN_DOCUMENT} và sử dụng +thể loại {@link android.content.Intent#CATEGORY_OPENABLE} để chỉ hiển thị +những tài liệu có thể mở được. Nó lọc thêm để chỉ hiển thị những tệp văn bản:

+ +
+private static final int EDIT_REQUEST_CODE = 44;
+/**
+ * Open a file for writing and append some text to it.
+ */
+ private void editDocument() {
+    // ACTION_OPEN_DOCUMENT is the intent to choose a file via the system's
+    // file browser.
+    Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
+
+    // Filter to only show results that can be "opened", such as a
+    // file (as opposed to a list of contacts or timezones).
+    intent.addCategory(Intent.CATEGORY_OPENABLE);
+
+    // Filter to show only text files.
+    intent.setType("text/plain");
+
+    startActivityForResult(intent, EDIT_REQUEST_CODE);
+}
+
+ +

Tiếp theo, từ {@link android.app.Activity#onActivityResult onActivityResult()} +(xem Kết quả tiến trình) bạn có thể gọi mã để thực hiện chỉnh sửa. +Đoạn mã HTML sau nhận được một {@link java.io.FileOutputStream} +từ {@link android.content.ContentResolver}. Theo mặc định, nó sử dụng chế độ “ghi”. +Cách tốt nhất là yêu cầu lượng quyền truy cập bạn cần ở mức ít nhất, vì thế đừng yêu cầu +quyền đọc/ghi nếu bạn chỉ cần quyền ghi:

+ +
private void alterDocument(Uri uri) {
+    try {
+        ParcelFileDescriptor pfd = getActivity().getContentResolver().
+                openFileDescriptor(uri, "w");
+        FileOutputStream fileOutputStream =
+                new FileOutputStream(pfd.getFileDescriptor());
+        fileOutputStream.write(("Overwritten by MyCloud at " +
+                System.currentTimeMillis() + "\n").getBytes());
+        // Let the document provider know you're done by closing the stream.
+        fileOutputStream.close();
+        pfd.close();
+    } catch (FileNotFoundException e) {
+        e.printStackTrace();
+    } catch (IOException e) {
+        e.printStackTrace();
+    }
+}
+ +

Cố định các quyền

+ +

Khi ứng dụng của bạn mở một tệp để đọc hoặc ghi, hệ thống sẽ cấp cho +ứng dụng của bạn một quyền URI được cấp cho tệp đó. Quyền này sẽ kéo dài tới khi thiết bị của bạn khởi động lại. +Nhưng giả sử ứng dụng của bạn là một ứng dụng chỉnh sửa hình ảnh, và bạn muốn người dùng có thể +truy cập 5 hình ảnh cuối cùng mà họ đã chỉnh sửa, trực tiếp từ ứng dụng của bạn. Nếu thiết bị của người dùng +đã khởi động lại, bạn sẽ phải gửi người dùng trở lại bộ chọn hệ thống để tìm +các tệp đó, đây rõ ràng không phải là cách lý tưởng.

+ +

Để tránh điều này xảy ra, bạn có thể cố định các quyền mà hệ thống +cấp cho ứng dụng của bạn. Ứng dụng của bạn sẽ "nhận" cấp quyền URI có thể cố định +mà hệ thống cung cấp một cách hiệu quả. Điều này cho phép người dùng có quyền liên tục truy cập các tệp đó +thông qua ứng dụng của bạn, ngay cả khi thiết bị đã bị khởi động lại:

+ + +
final int takeFlags = intent.getFlags()
+            & (Intent.FLAG_GRANT_READ_URI_PERMISSION
+            | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+// Check for the freshest data.
+getContentResolver().takePersistableUriPermission(uri, takeFlags);
+ +

Còn một bước cuối cùng. Bạn có thể đã lưu các +URI gần đây nhất mà ứng dụng của bạn đã truy cập, nhưng chúng còn thể không còn hợp lệ—một ứng dụng khác +có thể đã xóa hoặc sửa đổi tài liệu. Vì thế, bạn luôn nên gọi +{@code getContentResolver().takePersistableUriPermission()} để kiểm tra +dữ liệu mới nhất.

+ +

Ghi một Trình cung cấp Tài liệu Tùy chỉnh

+ +

+Nếu bạn đang phát triển một ứng dụng cung cấp dịch vụ lưu trữ cho tệp (chẳng hạn như +một dịch vụ lưu trữ đám mây), bạn có thể cung cấp các tệp của mình thông qua +SAF bằng cách ghi một trình cung cấp tài liệu tùy chỉnh. Phần này mô tả cách làm điều +này.

+ + +

Bản kê khai

+ +

Để triển khai một trình cung cấp tài liệu tùy chỉnh, hãy thêm nội dung sau vào bản kê khai +của ứng dụng của bạn:

+
    + +
  • Một mục tiêu API mức 19 hoặc cao hơn.
  • + +
  • Một phần tử <provider> khai báo trình cung cấp lưu trữ +tùy chỉnh của bạn.
  • + +
  • Tên của trình cung cấp của bạn, là tên lớp của nó, bao gồm tên gói. +Ví dụ: com.example.android.storageprovider.MyCloudProvider.
  • + +
  • Tên thẩm quyền của bạn, tức là tên gói của bạn (trong ví dụ này là +com.example.android.storageprovider) cộng với kiểu của trình cung cấp nội dung +(documents). Ví dụ, {@code com.example.android.storageprovider.documents}.
  • + +
  • Thuộc tính android:exported được đặt thành "true". +Bạn phải xuất trình cung cấp của mình để các ứng dụng khác có thể thấy nó.
  • + +
  • Thuộc tính android:grantUriPermissions được đặt thành +"true". Thiết đặt này cho phép hệ thống cấp cho các ứng dụng khác quyền truy cập +vào nội dung trong trình cung cấp của bạn. Để thảo luận về cách cố định quyền được cấp cho +một tài liệu cụ thể, hãy xem phầnCố định các quyền.
  • + +
  • Quyền {@code MANAGE_DOCUMENTS}. Theo mặc định, một trình cung cấp sẽ có sẵn +đối với mọi người. Việc thêm quyền này sẽ hạn chế trình cung cấp của bạn vào hệ thống. +Hạn chế này có ý nghĩa quan trọng đối với vấn đề bảo mật.
  • + +
  • Thuộc tính {@code android:enabled} được đặt thành một giá trị boolean được định nghĩa trong một tệp +tài nguyên. Mục đích của thuộc tính này là để vô hiệu hóa trình cung cấp trên các thiết bị chạy phiên bản Android 4.3 hoặc thấp hơn. +Ví dụ, {@code android:enabled="@bool/atLeastKitKat"}. Bên +cạnh việc nêu thuộc tính này trong bản kê khai, bạn cần làm như sau: +
      +
    • Trong tệp tài nguyên {@code bool.xml} của bạn bên dưới {@code res/values/}, hãy thêm +dòng sau:
      <bool name="atLeastKitKat">false</bool>
    • + +
    • Trong tệp tài nguyên {@code bool.xml} của bạn bên dưới {@code res/values-v19/}, hãy thêm +dòng sau:
      <bool name="atLeastKitKat">true</bool>
    • +
  • + +
  • Một bộ lọc ý định chứa hành động +{@code android.content.action.DOCUMENTS_PROVIDER}, sao cho trình cung cấp của bạn +xuất hiện trong bộ chọn khi hệ thống tìm kiếm trình cung cấp.
  • + +
+

Sau đây là các đoạn trích từ một bản kê khai mẫu chứa một trình cung cấp:

+ +
<manifest... >
+    ...
+    <uses-sdk
+        android:minSdkVersion="19"
+        android:targetSdkVersion="19" />
+        ....
+        <provider
+            android:name="com.example.android.storageprovider.MyCloudProvider"
+            android:authorities="com.example.android.storageprovider.documents"
+            android:grantUriPermissions="true"
+            android:exported="true"
+            android:permission="android.permission.MANAGE_DOCUMENTS"
+            android:enabled="@bool/atLeastKitKat">
+            <intent-filter>
+                <action android:name="android.content.action.DOCUMENTS_PROVIDER" />
+            </intent-filter>
+        </provider>
+    </application>
+
+</manifest>
+ +

Hỗ trợ các thiết bị chạy phiên bản Android 4.3 và thấp hơn

+ +

Ý định +{@link android.content.Intent#ACTION_OPEN_DOCUMENT} chỉ có sẵn +trên các thiết bị chạy phiên bản Android 4.4 trở lên. +Nếu bạn muốn ứng dụng của mình hỗ trợ {@link android.content.Intent#ACTION_GET_CONTENT} +để tạo điều kiện cho các thiết bị đang chạy phiên bản Android 4.3 và thấp hơn, bạn nên +vô hiệu hóa bộ lọc ý định {@link android.content.Intent#ACTION_GET_CONTENT} trong +bản kê khai của bạn cho các thiết bị chạy phiên bản Android 4.4 trở lên. Một +trình cung cấp tài liệu và {@link android.content.Intent#ACTION_GET_CONTENT} nên được xem xét + loại trừ lẫn nhau. Nếu bạn hỗ trợ cả hai đồng thời, ứng dụng của bạn sẽ +xuất hiện hai lần trong UI của bộ chọn hệ thống, đưa ra hai cách khác nhau để truy cập +dữ liệu đã lưu của bạn. Điều này có thể khiến người dùng bị nhầm lẫn.

+ +

Sau đây là cách được khuyến cáo để vô hiệu hóa bộ lọc ý định +{@link android.content.Intent#ACTION_GET_CONTENT} đối với các thiết bị +chạy phiên bản Android 4.4 hoặc cao hơn:

+ +
    +
  1. Trong tệp tài nguyên {@code bool.xml} của bạn bên dưới {@code res/values/}, hãy thêm +dòng sau:
    <bool name="atMostJellyBeanMR2">true</bool>
  2. + +
  3. Trong tệp tài nguyên {@code bool.xml} của bạn bên dưới {@code res/values-v19/}, hãy thêm +dòng sau:
    <bool name="atMostJellyBeanMR2">false</bool>
  4. + +
  5. Thêm một +bí danh +hoạt động để vô hiệu hóa bộ lọc ý định {@link android.content.Intent#ACTION_GET_CONTENT} +đối với các phiên bản 4.4 (API mức 19) trở lên. Ví dụ: + +
    +<!-- This activity alias is added so that GET_CONTENT intent-filter
    +     can be disabled for builds on API level 19 and higher. -->
    +<activity-alias android:name="com.android.example.app.MyPicker"
    +        android:targetActivity="com.android.example.app.MyActivity"
    +        ...
    +        android:enabled="@bool/atMostJellyBeanMR2">
    +    <intent-filter>
    +        <action android:name="android.intent.action.GET_CONTENT" />
    +        <category android:name="android.intent.category.OPENABLE" />
    +        <category android:name="android.intent.category.DEFAULT" />
    +        <data android:mimeType="image/*" />
    +        <data android:mimeType="video/*" />
    +    </intent-filter>
    +</activity-alias>
    +
    +
  6. +
+

Hợp đồng

+ +

Thường khi bạn ghi một trình cung cấp nội dung tùy chỉnh, một trong những tác vụ đó là +triển khai các lớp hợp đồng như được mô tả trong hướng dẫn cho nhà phát triển + +Trình cung cấp Nội dung. Lớp hợp đồng là một lớp {@code public final} mà +chứa các định nghĩa hằng số cho URI, tên cột, kiểu MIME và +siêu dữ liệu khác liên quan tới trình cung cấp. SAF +cung cấp những lớp hợp đồng này cho bạn, vì thế bạn không cần tự +ghi:

+ +
    +
  • {@link android.provider.DocumentsContract.Document}
  • +
  • {@link android.provider.DocumentsContract.Root}
  • +
+ +

Ví dụ, sau đây là các cột bạn có thể trả về trong một con chạy khi +trình cung cấp tài liệu của bạn được truy vấn về tài liệu hoặc phần gốc:

+ +
private static final String[] DEFAULT_ROOT_PROJECTION =
+        new String[]{Root.COLUMN_ROOT_ID, Root.COLUMN_MIME_TYPES,
+        Root.COLUMN_FLAGS, Root.COLUMN_ICON, Root.COLUMN_TITLE,
+        Root.COLUMN_SUMMARY, Root.COLUMN_DOCUMENT_ID,
+        Root.COLUMN_AVAILABLE_BYTES,};
+private static final String[] DEFAULT_DOCUMENT_PROJECTION = new
+        String[]{Document.COLUMN_DOCUMENT_ID, Document.COLUMN_MIME_TYPE,
+        Document.COLUMN_DISPLAY_NAME, Document.COLUMN_LAST_MODIFIED,
+        Document.COLUMN_FLAGS, Document.COLUMN_SIZE,};
+
+ +

Phân lớp con DocumentsProvider

+ +

Bước tiếp theo trong khi ghi một trình cung cấp tài liệu tùy chỉnh đó là phân lớp con +cho lớp tóm tắt {@link android.provider.DocumentsProvider}. Tối thiểu, bạn cần triển khai +các phương pháp sau:

+ +
    +
  • {@link android.provider.DocumentsProvider#queryRoots queryRoots()}
  • + +
  • {@link android.provider.DocumentsProvider#queryChildDocuments queryChildDocuments()}
  • + +
  • {@link android.provider.DocumentsProvider#queryDocument queryDocument()}
  • + +
  • {@link android.provider.DocumentsProvider#openDocument openDocument()}
  • +
+ +

Đây là những phương pháp duy nhất mà bạn được yêu cầu phải triển khai, nhưng còn +nhiều phương pháp nữa mà bạn có thể muốn triển khai. Xem {@link android.provider.DocumentsProvider} +để biết chi tiết.

+ +

Triển khai queryRoots

+ +

Việc bạn triển khai {@link android.provider.DocumentsProvider#queryRoots +queryRoots()} phải trả về một {@link android.database.Cursor} trỏ về tất cả +thư mục gốc trong trình cung cấp tài liệu của bạn, bằng cách sử dụng các cột được định nghĩa trong +{@link android.provider.DocumentsContract.Root}.

+ +

Trong đoạn mã HTML sau, tham số {@code projection} biểu diễn các trường cụ thể +mà hàm gọi muốn nhận về. Đoạn mã HTML tạo một con chạy mới +và thêm một hàng vào nó—một thư mục gốc, mức cao nhất, như +Downloads hoặc Images. Hầu hết các trình cung cấp chỉ có một phần gốc. Bạn có thể có nhiều hơn một, +ví dụ, trong trường hợp nhiều tài khoản người dùng. Trong trường hợp đó, chỉ cần thêm một +hàng thứ hai vào con chạy.

+ +
+@Override
+public Cursor queryRoots(String[] projection) throws FileNotFoundException {
+
+    // Create a cursor with either the requested fields, or the default
+    // projection if "projection" is null.
+    final MatrixCursor result =
+            new MatrixCursor(resolveRootProjection(projection));
+
+    // If user is not logged in, return an empty root cursor.  This removes our
+    // provider from the list entirely.
+    if (!isUserLoggedIn()) {
+        return result;
+    }
+
+    // It's possible to have multiple roots (e.g. for multiple accounts in the
+    // same app) -- just add multiple cursor rows.
+    // Construct one row for a root called "MyCloud".
+    final MatrixCursor.RowBuilder row = result.newRow();
+    row.add(Root.COLUMN_ROOT_ID, ROOT);
+    row.add(Root.COLUMN_SUMMARY, getContext().getString(R.string.root_summary));
+
+    // FLAG_SUPPORTS_CREATE means at least one directory under the root supports
+    // creating documents. FLAG_SUPPORTS_RECENTS means your application's most
+    // recently used documents will show up in the "Recents" category.
+    // FLAG_SUPPORTS_SEARCH allows users to search all documents the application
+    // shares.
+    row.add(Root.COLUMN_FLAGS, Root.FLAG_SUPPORTS_CREATE |
+            Root.FLAG_SUPPORTS_RECENTS |
+            Root.FLAG_SUPPORTS_SEARCH);
+
+    // COLUMN_TITLE is the root title (e.g. Gallery, Drive).
+    row.add(Root.COLUMN_TITLE, getContext().getString(R.string.title));
+
+    // This document id cannot change once it's shared.
+    row.add(Root.COLUMN_DOCUMENT_ID, getDocIdForFile(mBaseDir));
+
+    // The child MIME types are used to filter the roots and only present to the
+    //  user roots that contain the desired type somewhere in their file hierarchy.
+    row.add(Root.COLUMN_MIME_TYPES, getChildMimeTypes(mBaseDir));
+    row.add(Root.COLUMN_AVAILABLE_BYTES, mBaseDir.getFreeSpace());
+    row.add(Root.COLUMN_ICON, R.drawable.ic_launcher);
+
+    return result;
+}
+ +

Triển khai queryChildDocuments

+ +

Việc bạn triển khai +{@link android.provider.DocumentsProvider#queryChildDocuments queryChildDocuments()} +phải trả về một {@link android.database.Cursor} mà chỉ đến tất cả tệp trong +thư mục được chỉ định, bằng cách sử dụng các cột được định nghĩa trong +{@link android.provider.DocumentsContract.Document}.

+ +

Phương pháp này được gọi khi bạn chọn một thư mục gốc ứng dụng trong UI bộ chọn. +Nó nhận được tài liệu con của một thư mục nằm dưới phần gốc. Nó có thể được gọi ở bất kỳ mức nào trong phân cấp tệp +, không chỉ phần gốc. Đoạn mã HTML +này tạo một con chạy mới bằng các cột được yêu cầu, sau đó thêm thông tin về +mọi tệp con trực tiếp trong thư mục mẹ vào con chạy. +Tệp con có thể là một hình ảnh, một thư mục khác—bất kỳ tệp nào:

+ +
@Override
+public Cursor queryChildDocuments(String parentDocumentId, String[] projection,
+                              String sortOrder) throws FileNotFoundException {
+
+    final MatrixCursor result = new
+            MatrixCursor(resolveDocumentProjection(projection));
+    final File parent = getFileForDocId(parentDocumentId);
+    for (File file : parent.listFiles()) {
+        // Adds the file's display name, MIME type, size, and so on.
+        includeFile(result, null, file);
+    }
+    return result;
+}
+
+ +

Triển khai queryDocument

+ +

Việc bạn triển khai +{@link android.provider.DocumentsProvider#queryDocument queryDocument()} +phải trả về một {@link android.database.Cursor} mà chỉ đến tệp được chỉ định, +bằng cách sử dụng các cột được định nghĩa trong {@link android.provider.DocumentsContract.Document}. +

+ +

Phương pháp {@link android.provider.DocumentsProvider#queryDocument queryDocument()} +trả về cùng thông tin đã được chuyển trong +{@link android.provider.DocumentsProvider#queryChildDocuments queryChildDocuments()}, +nhưng là đối với một tệp cụ thể:

+ + +
@Override
+public Cursor queryDocument(String documentId, String[] projection) throws
+        FileNotFoundException {
+
+    // Create a cursor with the requested projection, or the default projection.
+    final MatrixCursor result = new
+            MatrixCursor(resolveDocumentProjection(projection));
+    includeFile(result, documentId, null);
+    return result;
+}
+
+ +

Triển khai openDocument

+ +

Bạn phải triển khai {@link android.provider.DocumentsProvider#openDocument +openDocument()} để trả về một {@link android.os.ParcelFileDescriptor} biểu diễn +tệp được chỉ định. Các ứng dụng khác có thể sử dụng {@link android.os.ParcelFileDescriptor} +được trả về để truyền phát dữ liệu. Hệ thống gọi phương pháp này sau khi người dùng chọn một tệp +và ứng dụng máy khách yêu cầu truy cập nó bằng cách gọi +{@link android.content.ContentResolver#openFileDescriptor openFileDescriptor()}. +Ví dụ:

+ +
@Override
+public ParcelFileDescriptor openDocument(final String documentId,
+                                         final String mode,
+                                         CancellationSignal signal) throws
+        FileNotFoundException {
+    Log.v(TAG, "openDocument, mode: " + mode);
+    // It's OK to do network operations in this method to download the document,
+    // as long as you periodically check the CancellationSignal. If you have an
+    // extremely large file to transfer from the network, a better solution may
+    // be pipes or sockets (see ParcelFileDescriptor for helper methods).
+
+    final File file = getFileForDocId(documentId);
+
+    final boolean isWrite = (mode.indexOf('w') != -1);
+    if(isWrite) {
+        // Attach a close listener if the document is opened in write mode.
+        try {
+            Handler handler = new Handler(getContext().getMainLooper());
+            return ParcelFileDescriptor.open(file, accessMode, handler,
+                        new ParcelFileDescriptor.OnCloseListener() {
+                @Override
+                public void onClose(IOException e) {
+
+                    // Update the file with the cloud server. The client is done
+                    // writing.
+                    Log.i(TAG, "A file with id " +
+                    documentId + " has been closed!
+                    Time to " +
+                    "update the server.");
+                }
+
+            });
+        } catch (IOException e) {
+            throw new FileNotFoundException("Failed to open document with id "
+            + documentId + " and mode " + mode);
+        }
+    } else {
+        return ParcelFileDescriptor.open(file, accessMode);
+    }
+}
+
+ +

Bảo mật

+ +

Giả sử trình cung cấp tài liệu của bạn là một dịch vụ lưu trữ đám mây được bảo vệ bằng mật khẩu +và bạn muốn đảm bảo rằng người dùng được đăng nhập trước khi bạn bắt đầu chia sẻ tệp của họ. +Ứng dụng của bạn nên làm gì nếu người dùng không đăng nhập? Giải pháp là trả về +phần gốc 0 trong triển khai {@link android.provider.DocumentsProvider#queryRoots +queryRoots()} của bạn. Cụ thể là một con chạy gốc trống:

+ +
+public Cursor queryRoots(String[] projection) throws FileNotFoundException {
+...
+    // If user is not logged in, return an empty root cursor.  This removes our
+    // provider from the list entirely.
+    if (!isUserLoggedIn()) {
+        return result;
+}
+
+ +

Bước còn lại là gọi {@code getContentResolver().notifyChange()}. +Bạn còn nhớ {@link android.provider.DocumentsContract} chứ? Chúng ta đang sử dụng nó để tạo +URI này. Đoạn mã HTML sau báo cho hệ thống truy vấn các phần gốc trong +trình cung cấp tài liệu của bạn bất cứ khi nào trạng thái đăng nhập của người dùng thay đổi. Nếu người dùng không được +đăng nhập, lệnh gọi tới {@link android.provider.DocumentsProvider#queryRoots queryRoots()} sẽ trả về một +con chạy trống như minh họa bên trên. Điều này đảm bảo rằng tài liệu của một trình cung cấp chỉ +có sẵn nếu người dùng đăng nhập vào trình cung cấp đó.

+ +
private void onLoginButtonClick() {
+    loginOrLogout();
+    getContentResolver().notifyChange(DocumentsContract
+            .buildRootsUri(AUTHORITY), null);
+}
+
\ No newline at end of file diff --git a/docs/html-intl/intl/vi/guide/topics/resources/accessing-resources.jd b/docs/html-intl/intl/vi/guide/topics/resources/accessing-resources.jd new file mode 100644 index 0000000000000000000000000000000000000000..b5491dcdee2c8d6fca3e1c96f0c7d3dc5d6bd81f --- /dev/null +++ b/docs/html-intl/intl/vi/guide/topics/resources/accessing-resources.jd @@ -0,0 +1,337 @@ +page.title=Truy cập Tài nguyên +parent.title=Tài nguyên Ứng dụng +parent.link=index.html +@jd:body + +
+
+

Xem nhanh

+
    +
  • Tài nguyên có thể được tham chiếu từ mã bằng các số nguyên từ {@code R.java}, chẳng hạn như +{@code R.drawable.myimage}
  • +
  • Tài nguyên có thể được tham chiếu từ các tài nguyên bằng cách sử dụng một cú pháp XML đặc biệt, ví dụ như {@code +@drawable/myimage}
  • +
  • Bạn cũng có thể truy cập tài nguyên ứng dụng của mình bằng các phương pháp trong +{@link android.content.res.Resources}
  • +
+ +

Lớp khóa

+
    +
  1. {@link android.content.res.Resources}
  2. +
+ +

Trong tài liệu này

+
    +
  1. Truy cập Tài nguyên từ Mã
  2. +
  3. Truy cập Tài nguyên từ XML +
      +
    1. Tham chiếu các thuộc tính kiểu
    2. +
    +
  4. +
  5. Truy cập Tài nguyên Nền tảng
  6. +
+ +

Xem thêm

+
    +
  1. Cung cấp Tài nguyên
  2. +
  3. Loại Tài nguyên
  4. +
+
+
+ + + + +

Sau khi cung cấp một tài nguyên trong ứng dụng của mình (đề cập trong Cung cấp Tài nguyên), bạn có thể áp dụng nó bằng cách +tham chiếu ID tài nguyên đó. Tất cả ID tài nguyên được định nghĩa trong lớp {@code R} dự án của bạn, do +công cụ {@code aapt} tự động khởi tạo.

+ +

Khi ứng dụng của bạn được biên dịch, {@code aapt} khởi tạo lớp {@code R}, trong đó chứa +ID tài nguyên cho tất cả tài nguyên trong thư mục {@code +res/} của bạn. Với mỗi loại tài nguyên, có một lớp con {@code R} (ví dụ, +{@code R.drawable} cho tất cả tài nguyên có thể vẽ), và với mỗi tài nguyên loại đó, có một số nguyên +tĩnh (ví dụ, {@code R.drawable.icon}). Số nguyên này là ID tài nguyên mà bạn có thể sử dụng +để truy xuất tài nguyên của mình.

+ +

Mặc dù lớp {@code R} là nơi các ID tài nguyên được quy định, bạn sẽ không cần +tìm ở đó để khám phá một ID tài nguyên. Một ID tài nguyên luôn bao gồm:

+
    +
  • Loại tài nguyên: Mỗi tài nguyên được nhóm vào một "loại," chẳng hạn như {@code +string}, {@code drawable}, và {@code layout}. Để biết thêm về các loại khác nhau, hãy xem phần Loại Tài nguyên. +
  • +
  • Tên tài nguyên, là, hoặc: tên tệp, +không bao gồm phần mở rộng; hoặc giá trị trong thuộc tính XML {@code android:name}, nếu tài nguyên +đó là một giá trị đơn giản (chẳng hạn như một xâu).
  • +
+ +

Có hai cách để bạn có thể truy cập một tài nguyên:

+
    +
  • Trong mã: Sử dụng một số nguyên tĩnh từ một lớp con của lớp {@code R} +của bạn, chẳng hạn như: +
    R.string.hello
    +

    {@code string} là loại tài nguyên và {@code hello} là tên tài nguyên. Có nhiều +API Android mà có thể truy cập các tài nguyên của bạn khi bạn cung cấp một ID tài nguyên theo định dạng này. Xem +Truy cập Tài nguyên trong Mã.

    +
  • +
  • Trong XML: Sử dụng một cú pháp XML đặc biệt mà cũng tương ứng với +ID tài nguyên được định nghĩa trong lớp {@code R} của bạn, chẳng hạn như: +
    @string/hello
    +

    {@code string} là loại tài nguyên và {@code hello} là tên tài nguyên. Bạn có thể sử dụng cú pháp +này trong một tài nguyên XML ở bất kỳ nơi nào có kỳ vọng một giá trị mà bạn cung cấp trong một tài nguyên. Xem phần Truy cập Tài nguyên từ XML.

    +
  • +
+ + + +

Truy cập Tài nguyên trong Mã

+ +

Bạn có thể sử dụng một tài nguyên trong mã bằng cách chuyển ID tài nguyên như một tham số phương pháp. Ví +dụ, bạn có thể đặt một {@link android.widget.ImageView} để sử dụng tài nguyên {@code res/drawable/myimage.png} +bằng cách sử dụng {@link android.widget.ImageView#setImageResource(int) setImageResource()}:

+
+ImageView imageView = (ImageView) findViewById(R.id.myimageview);
+imageView.setImageResource(R.drawable.myimage);
+
+ +

Bạn cũng có thể truy xuất các tài nguyên riêng lẻ bằng các phương pháp trong {@link +android.content.res.Resources}, theo đó bạn có thể nhận được một thực thể +bằng {@link android.content.Context#getResources()}.

+ + + + +

Cú pháp

+ +

Sau đây là cú pháp để tham chiếu một tài nguyên trong mã:

+ +
+[<package_name>.]R.<resource_type>.<resource_name>
+
+ +
    +
  • {@code <package_name>} là tên của gói mà tài nguyên nằm trong đó (không +bắt buộc khi tham chiếu các tài nguyên từ gói của chính bạn).
  • +
  • {@code <resource_type>} là lớp con {@code R} cho loại tài nguyên.
  • +
  • {@code <resource_name>} hoặc là tên tệp tài nguyên +không có phần mở rộng hoặc là giá trị thuộc tính {@code android:name} trong phần tử XML (đối với các giá trị +đơn giản).
  • +
+

Xem phần Loại Tài nguyên để +biết thêm thông tin về mỗi loại tài nguyên và cách tham chiếu chúng.

+ + +

Trường hợp sử dụng

+ +

Có nhiều phương pháp chấp nhận một tham số ID tài nguyên và bạn có thể truy xuất tài nguyên bằng cách sử dụng +các phương pháp trong {@link android.content.res.Resources}. Bạn có thể lấy một thực thể {@link +android.content.res.Resources} bằng {@link android.content.Context#getResources +Context.getResources()}.

+ + +

Sau đây là một số ví dụ về truy cập tài nguyên trong mã:

+ +
+// Load a background for the current screen from a drawable resource
+{@link android.app.Activity#getWindow()}.{@link
+android.view.Window#setBackgroundDrawableResource(int)
+setBackgroundDrawableResource}(R.drawable.my_background_image) ;
+
+// Set the Activity title by getting a string from the Resources object, because
+//  this method requires a CharSequence rather than a resource ID
+{@link android.app.Activity#getWindow()}.{@link android.view.Window#setTitle(CharSequence)
+setTitle}(getResources().{@link android.content.res.Resources#getText(int)
+getText}(R.string.main_title));
+
+// Load a custom layout for the current screen
+{@link android.app.Activity#setContentView(int)
+setContentView}(R.layout.main_screen);
+
+// Set a slide in animation by getting an Animation from the Resources object
+mFlipper.{@link android.widget.ViewAnimator#setInAnimation(Animation)
+setInAnimation}(AnimationUtils.loadAnimation(this,
+        R.anim.hyperspace_in));
+
+// Set the text on a TextView object using a resource ID
+TextView msgTextView = (TextView) findViewById(R.id.msg);
+msgTextView.{@link android.widget.TextView#setText(int)
+setText}(R.string.hello_message);
+
+ + +

Chú ý: Bạn không nên sửa đổi tệp {@code +R.java} bằng cách thủ công—nó được khởi tạo bởi công cụ {@code aapt} khi dự án của bạn được +biên dịch. Mọi thay đổi đều bị ghi đè vào lần biên dịch tới của bạn.

+ + + +

Truy cập Tài nguyên từ XML

+ +

Bạn có thể định nghĩa các giá trị cho một số thuộc tính và phần tử XML bằng cách sử dụng một +tham chiếu tới một tài nguyên hiện có. Bạn sẽ thường làm điều này khi tạo các tệp bố trí, để +cung cấp các xâu và hình ảnh cho widget của mình.

+ +

Ví dụ, nếu thêm một {@link android.widget.Button} vào bố trí của mình, bạn nên sử dụng +một tài nguyên xâu cho văn bản nút:

+ +
+<Button
+    android:layout_width="fill_parent"
+    android:layout_height="wrap_content"
+    android:text="@string/submit" />
+
+ + +

Cú pháp

+ +

Sau đây là cú pháp để tham chiếu một tài nguyên trong một tài nguyên XML:

+ +
+@[<package_name>:]<resource_type>/<resource_name>
+
+ +
    +
  • {@code <package_name>} là tên của gói mà tài nguyên nằm trong đó (không +bắt buộc khi tham chiếu các tài nguyên từ cùng gói đó)
  • +
  • {@code <resource_type>} là lớp con +{@code R} cho loại tài nguyên.
  • +
  • {@code <resource_name>} hoặc là tên tệp tài nguyên +không có phần mở rộng hoặc là giá trị thuộc tính {@code android:name} trong phần tử XML (đối với các giá trị +đơn giản).
  • +
+ +

Xem phần Loại Tài nguyên để +biết thêm thông tin về mỗi loại tài nguyên và cách tham chiếu chúng.

+ + +

Trường hợp sử dụng

+ +

Trong một số trường hợp bạn phải sử dụng một tài nguyên cho một giá trị trong XML (ví dụ, để áp dụng một hình ảnh có thể vẽ +cho một widget), nhưng bạn cũng có thể sử dụng một tài nguyên trong XML ở bất kỳ nơi nào chấp nhận một giá trị đơn giản. Ví +dụ, nếu bạn có tệp tài nguyên sau bao gồm một tài nguyên màu và một tài nguyên xâu:

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+   <color name="opaque_red">#f00</color>
+   <string name="hello">Hello!</string>
+</resources>
+
+ +

Bạn có thể sử dụng những tài nguyên này trong tệp bố trí sau để đặt màu văn bản và +xâu văn bản:

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<EditText xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    android:textColor="@color/opaque_red"
+    android:text="@string/hello" />
+
+ +

Trong trường hợp này, bạn không cần quy định tên gói trong tham chiếu tài nguyên đó vì tài nguyên +xuất phát từ gói của chính bạn. Để +tham chiếu một tài nguyên hệ thống, bạn sẽ cần đưa vào tên gói. Ví dụ:

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<EditText xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    android:textColor="@android:color/secondary_text_dark"
+    android:text="@string/hello" />
+
+ +

Lưu ý: Bạn nên sử dụng các tài nguyên xâu +vào mọi lúc, để ứng dụng của bạn có thể được bản địa hóa cho các ngôn ngữ khác. +Để biết thông tin về việc tạo các tài nguyên +thay thế (chẳng hạn như xâu được bản địa hóa), hãy xem phần Cung cấp Tài nguyên +Thay thế. Để được hướng dẫn đầy đủ về việc bản địa hóa ứng dụng của bạn cho các ngôn ngữ khác, +hãy xem phần Bản địa hóa.

+ +

Bạn thậm chí có thể sử dụng tài nguyên trong XML để tạo các bí danh. Ví dụ, bạn có thể tạo một tài nguyên có thể vẽ +là một bí danh cho một tài nguyên có thể vẽ khác:

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
+    android:src="@drawable/other_drawable" />
+
+ +

Nghe có vẻ thừa, nhưng có thể rất hữu ích khi sử dụng tài nguyên thay thế. Đọc thêm về +Tạo tài nguyên bí danh.

+ + + +

Tham chiếu các thuộc tính kiểu

+ +

Một tài nguyên thuộc tính kiểu sẽ cho phép bạn tham chiếu giá trị +của một thuộc tính trong chủ đề đang áp dụng. Tham chiếu một thuộc tính kiểu sẽ cho phép bạn +tùy chỉnh diện mạo của các phần tử UI bằng cách tạo kiểu cho chúng để phù hợp với các biến đổi tiêu chuẩn được cung cấp bởi +chủ đề hiện tại, thay vì cung cấp một giá trị được mã hóa cố định. Tham chiếu một thuộc tính kiểu +về cơ bản mà nói, là "sử dụng kiểu được định nghĩa bởi thuộc tính này, trong chủ đề hiện tại."

+ +

Để tham chiếu một thuộc tính kiểu, cú pháp tên gần như tương tự với định dạng tài nguyên thường +, nhưng thay vì biểu tượng @ ({@code @}), hãy sử dụng một dấu hỏi ({@code ?}), và +phần loại tài nguyên là tùy chọn. Ví dụ:

+ +
+?[<package_name>:][<resource_type>/]<resource_name>
+
+ +

Ví dụ, sau đây là cách bạn có thể tham chiếu một thuộc tính để đặt màu văn bản cho phù hợp với màu văn bản +"chính" của chủ đề hệ thống:

+ +
+<EditText id="text"
+    android:layout_width="fill_parent"
+    android:layout_height="wrap_content"
+    android:textColor="?android:textColorSecondary"
+    android:text="@string/hello_world" />
+
+ +

Ở đây, thuộc tính {@code android:textColor} quy định tên của một thuộc tính kiểu +trong chủ đề hiện tại. Hiện nay, Android sử dụng giá trị được áp dụng cho thuộc tính kiểu {@code android:textColorSecondary} +làm giá trị cho {@code android:textColor} trong widget này. Vì công cụ tài nguyên +hệ thống biết rằng một tài nguyên thuộc tính sẽ được yêu cầu trong ngữ cảnh này, +bạn không cần nêu rõ loại (mà sẽ là +?android:attr/textColorSecondary)—bạn có thể không nêu loại {@code attr}.

+ + + + +

Truy cập Tài nguyên Nền tảng

+ +

Android bao gồm nhiều tài nguyên tiêu chuẩn, chẳng hạn như kiểu, chủ đề và bố trí. Để +truy cập các tài nguyên này, hãy xác định tham chiếu tài nguyên của bạn bằng tên gói +android. Ví dụ, Android cung cấp một tài nguyên bố trí bạn có thể sử dụng cho +các mục danh sách trong một {@link android.widget.ListAdapter}:

+ +
+{@link android.app.ListActivity#setListAdapter(ListAdapter)
+setListAdapter}(new {@link
+android.widget.ArrayAdapter}<String>(this, android.R.layout.simple_list_item_1, myarray));
+
+ +

Trong ví dụ này, {@link android.R.layout#simple_list_item_1} là một tài nguyên bố trí được định nghĩa bởi +nền tảng cho các mục trong một {@link android.widget.ListView}. Bạn có thể sử dụng điều này thay vì tạo +bố trí riêng của mình cho các mục danh sách. Để biết thêm thông tin, hãy xem phần +Dạng xem Danh sách trong hướng dẫn cho nhà phát triển.

+ diff --git a/docs/html-intl/intl/vi/guide/topics/resources/overview.jd b/docs/html-intl/intl/vi/guide/topics/resources/overview.jd new file mode 100644 index 0000000000000000000000000000000000000000..7bbd72af96a77d8427d0202d76ee74b621cf773c --- /dev/null +++ b/docs/html-intl/intl/vi/guide/topics/resources/overview.jd @@ -0,0 +1,103 @@ +page.title=Tổng quan về Tài nguyên +@jd:body + + + + +

Bạn nên luôn ngoại hiện hóa các tài nguyên chẳng hạn như hình ảnh và xâu từ mã +ứng dụng của mình, sao cho bạn có thể duy trì chúng một cách độc lập. Việc ngoại hiện hóa +tài nguyên cũng cho phép bạn cung cấp các tài nguyên thay thế hỗ trợ những cấu hình +thiết bị cụ thể chẳng hạn như ngôn ngữ hoặc kích cỡ màn hình khác nhau, điều này đang ngày càng trở nên +quan trọng bởi các thiết bị dựa trên nền tảng Android ngày càng sẵn có với các cấu hình khác nhau. Để +đảm bảo tính tương thích với các cấu hình khác nhau, bạn phải tổ chức tài nguyên trong +thư mục {@code res/} dự án của bạn bằng cách sử dụng các thư mục con khác nhau có chức năng nhóm tài nguyên lại theo loại và +cấu hình.

+ +
+ +

+Hình 1. Hai thiết bị khác nhau, mỗi thiết bị sử dụng bố trí mặc định +(ứng dụng không cung cấp bố trí thay thế).

+
+ +
+ +

+Hình 2. Hai thiết bị khác nhau, mỗi thiết bị sử dụng một bố trí khác nhau được cung cấp +cho các kích cỡ màn hình khác nhau.

+
+ +

Đối với mọi loại tài nguyên, bạn có thể quy định tài nguyên mặc định và nhiều tài nguyên +thay thế cho ứng dụng của mình:

+
    +
  • Tài nguyên mặc định là những tài nguyên nên được sử dụng không phụ thuộc vào +cấu hình thiết bị hoặc khi không có tài nguyên thay thế khớp với cấu hình +hiện tại.
  • +
  • Tài nguyên thay thế là những tài nguyên mà bạn đã thiết kế để sử dụng với một cấu hình +cụ thể. Để quy định rằng một nhóm tài nguyên áp dụng cho một cấu hình cụ thể, +hãy nối hình dạng cấu hình phù hợp với tên thư mục.
  • +
+ +

Ví dụ, trong khi bố trí UI mặc định của bạn +được lưu trong thư mục {@code res/layout/}, bạn có thể quy định một bố trí khác sẽ +được sử dụng khi màn hình ở hướng khổ ngang, bằng cách lưu nó trong thư mục {@code res/layout-land/} +. Android tự động áp dụng các tài nguyên phù hợp bằng cách khớp cấu hình hiện tại +của thiết bị với tên thư mục tài nguyên của bạn.

+ +

Hình 1 minh họa cách hệ thống áp dụng cùng bố trí cho +hai thiết bị khác nhau khi không có sẵn tài nguyên thay thế. Hình 2 minh họa +cùng ứng dụng khi nó thêm một tài nguyên bố trí thay thế cho các màn hình lớn hơn.

+ +

Các tài liệu sau trình bày hướng dẫn hoàn chỉnh về cách bạn có thể tổ chức các tài nguyên ứng dụng của mình, +quy định tài nguyên thay thế, truy cập chúng trong ứng dụng của bạn, và nhiều điều khác:

+ +
+
Cung cấp Tài nguyên
+
Những kiểu tài nguyên mà bạn có thể cung cấp trong ứng dụng của mình, nơi lưu chúng, và cách tạo +tài nguyên thay thế cho những cấu hình thiết bị cụ thể.
+
Truy cập Tài nguyên
+
Cách sử dụng tài nguyên mà bạn đã cung cấp hoặc bằng cách tham chiếu chúng từ mã ứng dụng của mình +hoặc từ các tài nguyên XML khác.
+
Xử lý Thay đổi Thời gian chạy
+
Cách quản lý những thay đổi cấu hình mà diễn ra trong khi Hoạt động của bạn đang chạy.
+
Bản địa hóa
+
Một hướng dẫn từ dưới lên về việc bản địa hóa ứng dụng của bạn bằng cách sử dụng các tài nguyên thay thế. Trong khi đây +chỉ là một công dụng cụ thể của tài nguyên thay thế, nó rất quan trọng để tiếp cận với nhiều +người dùng hơn.
+
Loại Tài nguyên
+
Một tham chiếu về các loại tài nguyên khác nhau mà bạn có thể cung cấp, mô tả các phần tử XML, +thuộc tính và cú pháp của chúng. Ví dụ, tham chiếu này cho bạn thấy cách tạo một tài nguyên cho +menu ứng dụng, đối tượng vẽ được, hoạt ảnh, và hơn thế nữa.
+
+ + diff --git a/docs/html-intl/intl/vi/guide/topics/resources/providing-resources.jd b/docs/html-intl/intl/vi/guide/topics/resources/providing-resources.jd new file mode 100644 index 0000000000000000000000000000000000000000..b733643e75cd21fedb069846678809b8eb85e26f --- /dev/null +++ b/docs/html-intl/intl/vi/guide/topics/resources/providing-resources.jd @@ -0,0 +1,1094 @@ +page.title=Cung cấp Tài nguyên +parent.title=Tài nguyên Ứng dụng +parent.link=index.html +@jd:body + +
+
+

Xem nhanh

+
    +
  • Các loại tài nguyên khác nhau thuộc về các thư mục con khác nhau của {@code res/}
  • +
  • Tài nguyên thay thế cung cấp các tệp tài nguyên theo cấu hình cụ thể
  • +
  • Luôn bao gồm tài nguyên mặc định để ứng dụng của bạn không phụ thuộc vào các +cấu hình thiết bị cụ thể
  • +
+

Trong tài liệu này

+
    +
  1. Nhóm các Loại Tài nguyên lại
  2. +
  3. Cung cấp Tài nguyên Thay thế +
      +
    1. Quy tắc về tên hạn định
    2. +
    3. Tạo tài nguyên bí danh
    4. +
    +
  4. +
  5. Cung cấp Tính tương thích giữa Thiết bị với Tài nguyên Tốt nhất
  6. +
  7. Cách Android tìm Tài nguyên Khớp Tốt nhất
  8. +
+ +

Xem thêm

+
    +
  1. Truy cập Tài nguyên
  2. +
  3. Loại Tài nguyên
  4. +
  5. Hỗ trợ Nhiều +Màn hình
  6. +
+
+
+ +

Bạn nên luôn ngoại hiện hóa các tài nguyên ứng dụng chẳng hạn như hình ảnh và xâu từ mã +của mình, sao cho bạn có thể duy trì chúng một cách độc lập. Bạn cũng nên cung cấp tài nguyên thay thế cho +cấu hình thiết bị cụ thể bằng cách nhóm chúng lại trong những thư mục tài nguyên đích danh. Trong +thời gian chạy, Android sẽ sử dụng tài nguyên phù hợp dựa trên cấu hình hiện tại. Ví +dụ, bạn có thể muốn cung cấp một bố trí UI khác phụ thuộc vào kích cỡ màn hình hoặc các xâu +khác nhau phụ thuộc vào thiết đặt ngôn ngữ.

+ +

Sau khi ngoại hiện hóa các tài nguyên ứng dụng của mình, bạn có thể truy cập chúng +bằng cách sử dụng các ID tài nguyên được khởi tạo trong lớp {@code R} của dự án của bạn. Cách sử dụng +tài nguyên trong ứng dụng của bạn được trình bày trong phần Truy cập +Tài nguyên. Tài liệu này trình bày với bạn cách nhóm các tài nguyên lại trong dự án Android của bạn và +cung cấp tài nguyên thay thế cho những cấu hình thiết bị cụ thể.

+ + +

Nhóm các Loại Tài nguyên lại

+ +

Bạn nên đặt từng loại tài nguyên vào một thư mục con cụ thể trong thư mục +{@code res/} dự án của mình. Ví dụ, sau đây là phân cấp tệp của một dự án đơn giản:

+ +
+MyProject/
+    src/  
+        MyActivity.java  
+    res/
+        drawable/  
+            graphic.png  
+        layout/  
+            main.xml
+            info.xml
+        mipmap/  
+            icon.png 
+        values/  
+            strings.xml  
+
+ +

Như bạn có thể thấy trong ví dụ này, thư mục {@code res/} chứa tất cả tài nguyên (trong +các thư mục con): một tài nguyên hình ảnh, hai tài nguyên bố trí, các thư mục{@code mipmap/} cho biểu tượng của trình khởi chạy +, và một tệp tài nguyên xâu. Tên thư mục +tài nguyên có vai trò quan trọng và được mô tả trong bảng 1.

+ +

Lưu ý: Để biết thêm thông tin về cách sử dụng thư mục mipmap, hãy xem phần +Tổng quan về Quản lý Dự án.

+ +

Bảng 1. Các thư mục tài nguyên +được hỗ trợ bên trong thư mục {@code res/} của dự án.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Thư mụcLoại Tài nguyên
animator/Tệp XML định nghĩa các hoạt hình +tính chất.
anim/Tệp XML định nghĩa các hoạt hình +tween. (Các hoạt hình tính chất cũng có thể được lưu trong thư mục này, nhưng +thư mục {@code animator/} được ưu tiên cho hoạt hình tính chất để phân biệt giữa hai +loại này.)
color/Tệp XML định nghĩa một danh sách trạng thái các màu. Xem phần Tài nguyên +Danh sách Trạng thái Màu
drawable/

Tệp bitmap ({@code .png}, {@code .9.png}, {@code .jpg}, {@code .gif}) hoặc tệp XML +được biên dịch thành các loại tài nguyên con vẽ được sau:

+
    +
  • Tệp bitmap
  • +
  • Nine-Patche (tệp bitmap có thể thay đổi kích cỡ)
  • +
  • Danh sách trạng thái
  • +
  • Hình
  • +
  • Nội dung vẽ được hoạt hình
  • +
  • Nội dung vẽ được khác
  • +
+

Xem phần Tài nguyên Vẽ được.

+
mipmap/Tệp vẽ được cho các mật độ biểu tượng trình khởi chạy khác nhau. Để biết thêm thông tin về việc quản lý + các biểu tượng trình khởi chạy bằng thư mục {@code mipmap/}, xem phần + Tổng quan về Quản lý Dự án.
layout/Tệp XML định nghĩa một bố trí giao diện người dùng. + Xem phần Tài nguyên Bố trí.
menu/Tệp XML định nghĩa các menu ứng dụng, chẳng hạn như Menu Tùy chọn, Menu Ngữ cảnh, hoặc Menu +Con. Xem phần Tài nguyên Menu.
raw/

Tệp tùy ý để lưu trong dạng thô của chúng. Để mở những tài nguyên có một +{@link java.io.InputStream} thô này, hãy gọi {@link android.content.res.Resources#openRawResource(int) +Resources.openRawResource()} bằng ID tài nguyên, chính là {@code R.raw.filename}.

+

Tuy nhiên, nếu cần truy cập tên tệp gốc và phân cấp tệp, bạn có thể xem xét +lưu một số tài nguyên trong thư mục {@code +assets/} (thay vì {@code res/raw/}). Các tệp trong {@code assets/} không được cấp +ID tài nguyên, vì thế bạn chỉ có thể đọc chúng bằng cách sử dụng {@link android.content.res.AssetManager}.

values/

Tệp XML chứa các giá trị đơn giản, chẳng hạn như xâu, số nguyên, và màu sắc.

+

Trong đó, tệp tài nguyên XML trong các thư mục con {@code res/} khác định nghĩa một tài nguyên đơn lẻ +dựa trên tên tệp XML, tệp trong thư mục {@code values/} sẽ mô tả nhiều nguồn. +Đối với tệp trong thư mục này, mỗi phần tử con của phần tử {@code <resources>} lại định nghĩa một tài nguyên +duy nhất. Ví dụ, phần tử {@code <string>} tạo tài nguyên +{@code R.string} và phần tử {@code <color>} tạo tài nguyên {@code R.color} +.

+

Vì mỗi tài nguyên được định nghĩa bằng phần tử XML của chính nó, bạn có thể đặt tên tệp +theo cách mình muốn và đặt các loại tài nguyên khác nhau vào một tệp. Tuy nhiên, để giải thích rõ, bạn có thể +muốn đặt các loại tài nguyên duy nhất vào những tệp khác nhau. Ví dụ, sau đây là một số quy ước +tên tệp cho các tài nguyên mà bạn có thể tạo trong thư mục này:

+ +

Xem các phần Tài nguyên Xâu, + Tài nguyên Kiểu, và + các Loại Tài nguyên khác.

+
xml/Tệp XML tùy ý mà có thể được đọc vào thời gian chạy bằng cách gọi {@link +android.content.res.Resources#getXml(int) Resources.getXML()}. Các tệp cấu hình XML khác nhau +phải được lưu ở đây, chẳng hạn như một cấu hình có thể tìm kiếm. +
+ +

Chú ý: Không được lưu tệp tài nguyên trực tiếp vào trong thư mục +{@code res/}—nó sẽ gây ra lỗi với trình biên dịch.

+ +

Để biết thêm thông tin về các loại tài nguyên, hãy xem tài liệu Các Loại Tài nguyên.

+ +

Tài nguyên mà bạn lưu trong thư mục con được định nghĩa trong bảng 1 là những tài nguyên "mặc định" +của bạn. Cụ thể, những tài nguyên này định nghĩa thiết kế và nội dung mặc định cho ứng dụng của bạn. +Tuy nhiên, các loại thiết bị dựa trên nền tảng Android khác nhau có thể gọi các loại tài nguyên khác nhau. +Ví dụ, nếu một thiết bị có một màn hình lớn hơn bình thường, khi đó bạn nên cung cấp +các tài nguyên bố trí khác nhau để tận dụng diện tích màn hình tăng thêm. Hoặc, nếu một thiết bị có +thiết đặt ngôn ngữ khác, khi đó bạn nên cung cấp các tài nguyên xâu khác để biên dịch +văn bản trong giao diện người dùng của mình. Để cung cấp những tài nguyên khác nhau này cho các cấu hình +thiết bị khác nhau, bạn cần cung cấp tài nguyên thay thế bên cạnh những tài nguyên +mặc định của mình.

+ + +

Cung cấp Tài nguyên Thay thế

+ + +
+ +

+Hình 1. Hai thiết bị khác nhau, mỗi thiết bị sử dụng các tài nguyên bố trí khác nhau.

+
+ +

Hầu như mọi ứng dụng đều nên cung cấp các tài nguyên thay thế để hỗ trợ những cấu hình +thiết bị cụ thể. Ví dụ, bạn nên bao gồm các tài nguyên vẽ được thay thế cho các mật độ +màn hình khác nhau và tài nguyên xâu thay thế cho các ngôn ngữ khác nhau. Vào thời gian chạy, Android +sẽ phát hiện cấu hình thiết bị hiện tại và tải các tài nguyên +tương ứng cho ứng dụng của bạn.

+ +

Để quy định các phương án thay thế theo cấu hình cụ thể cho một tập hợp tài nguyên:

+
    +
  1. Tạo một thư mục mới trong {@code res/} có tên theo dạng {@code +<resources_name>-<config_qualifier>}. +
      +
    • {@code <resources_name>} là tên thư mục của các tài nguyên mặc định tương ứng +(được định nghĩa trong bảng 1).
    • +
    • {@code <qualifier>} là tên quy định một cấu hình riêng +mà những tài nguyên này sẽ được sử dụng cho nó (được định nghĩa trong bảng 2).
    • +
    +

    Bạn có thể nối nhiều hơn một {@code <qualifier>}. Tách riêng từng cái +bằng một nét gạch.

    +

    Chú ý: Khi nối nhiều hạn định, bạn phải +đặt chúng theo cùng thứ tự liệt kê trong bảng 2. Nếu các hạn định được xếp thứ tự +sai, tài nguyên sẽ bị bỏ qua.

    +
  2. +
  3. Lưu các tài nguyên thay thế tương ứng vào thư mục mới này. Tệp tài nguyên phải được +đặt tên đúng như các tệp tài nguyên mặc định.
  4. +
+ +

Ví dụ, sau đây là một số tài nguyên mặc định và thay thế:

+ +
+res/
+    drawable/   
+        icon.png
+        background.png    
+    drawable-hdpi/  
+        icon.png
+        background.png  
+
+ +

Hạn định {@code hdpi} cho biết rằng các tài nguyên trong thư mục đó áp dụng cho những thiết bị có +màn hình mật độ cao. Hình ảnh trong từng thư mục vẽ được này được định cỡ cho một mật độ +màn hình cụ thể, nhưng tên tệp thì +giống hệt. Bằng cách này, ID tài nguyên mà bạn sử dụng để tham chiếu {@code icon.png} hoặc hình ảnh {@code +background.png} luôn như nhau, nhưng Android sẽ chọn +phiên bản của từng tài nguyên cho khớp tốt nhất với thiết bị hiện tại, bằng cách so sánh thông tin cấu hình thiết bị +với các hạn định về tên thư mục tài nguyên.

+ +

Android hỗ trợ một vài hạn định cấu hình và bạn có thể +thêm nhiều hạn định vào một tên thư mục, bằng cách tách riêng từng hạn định bằng một nét gạch. Bảng 2 +liệt kê các hạn định cấu hình hợp lệ, theo thứ tự ưu tiên—nếu bạn sử dụng nhiều +hạn định cho một thư mục tài nguyên, bạn phải thêm chúng vào tên thư mục theo thứ tự được liệt kê trong +bảng.

+ + +

Bảng 2. Tên của hạn định +cấu hình.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Cấu hìnhGiá trị Hạn địnhMô tả
MCC và MNCVí dụ:
+ mcc310
+ mcc310-mnc004
+ mcc208-mnc00
+ v.v. +
+

Mã quốc gia di động (MCC), đằng sau có thể là mã mạng di động (MNC) + từ thẻ SIM trong thiết bị. Ví dụ, mcc310 ở Hoa Kỳ đối với mọi nhà mạng, + mcc310-mnc004 ở Hoa Kỳ đối với Verizon, và mcc208-mnc00 ở Pháp đối với + Orange.

+

Nếu thiết bị sử dụng một kết nối vô tuyến (điện thoại GSM), các giá trị MCC và MNC sẽ lấy + từ thẻ SIM.

+

Bạn cũng có thể sử dụng chỉ MCC (ví dụ, để đưa các tài nguyên +pháp lý theo quốc gia cụ thể vào ứng dụng của bạn). Nếu bạn cần quy định chỉ dựa trên ngôn ngữ, hãy sử dụng hạn định +ngôn ngữ và khu vực để thay thế (được trình bày ở phần tiếp theo). Nếu bạn quyết định sử dụng hạn định MCC và +MNC, bạn nên cẩn thận và kiểm tra xem nó có hoạt động như kỳ vọng không.

+

Ngoài ra, cũng xem các trường cấu hình {@link +android.content.res.Configuration#mcc}, và {@link +android.content.res.Configuration#mnc}, tương ứng cho biết mã quốc gia di động và +mã mạng di động hiện tại.

+
Ngôn ngữ và khu vựcVí dụ:
+ en
+ fr
+ en-rUS
+ fr-rFR
+ fr-rCA
+ v.v. +

Ngôn ngữ được định nghĩa bằng một mã ngôn ngữ ISO + 639-1 gồm hai chữ cái, có thể theo sau là một mã khu vực + ISO + 3166-1-alpha-2 dài hai chữ cái (đằng trước là "{@code r}" chữ thường). +

+ Các mã không phân biệt chữ hoa/thường; tiền tố {@code r} được sử dụng để + phân biệt phần khu vực. + Bạn không thể chỉ quy định một khu vực.

+

Điều này có thể thay đổi trong suốt vòng đời +ứng dụng của bạn nếu người dùng thay đổi ngôn ngữ của mình trong cài đặt hệ thống. Xem phần Xử lý Thay đổi Thời gian chạy để biết thông tin về +ảnh hưởng có thể có của thay đổi này tới ứng dụng của bạn trong thời gian chạy.

+

Xem phần Bản địa hóa để biết hướng dẫn đầy đủ về việc bản địa hóa +ứng dụng của bạn cho các ngôn ngữ khác.

+

Xem thêm trường cấu hình {@link android.content.res.Configuration#locale}, trong đó +cho biết địa phương hiện tại.

+
Chỉ hướng Bố tríldrtl
+ ldltr
+

Chỉ hướng bố trí của ứng dụng của bạn. {@code ldrtl} có nghĩa là "chỉ-hướng-bố-trí-phải-qua-trái". + {@code ldltr} có nghĩa là "chỉ-hướng-bố-trí-trái-qua-phải" và là giá trị không biểu thị mặc định. +

+

Điều này có thể áp dụng cho bất kỳ tài nguyên nào, chẳng hạn như bố trí, nội dung vẽ được hoặc giá trị. +

+

Ví dụ, nếu bạn muốn cung cấp một bố trí cụ thể cho ngôn ngữ Ả-rập và một + bố trí chung nào đó cho bất kỳ ngôn ngữ “phải-qua-trái" nào khác (như chữ Ba Tư hoặc Do Thái), vậy bạn sẽ phải: +

+
+res/
+    layout/   
+        main.xml  (Default layout)
+    layout-ar/  
+        main.xml  (Specific layout for Arabic)
+    layout-ldrtl/  
+        main.xml  (Any "right-to-left" language, except
+                  for Arabic, because the "ar" language qualifier
+                  has a higher precedence.)
+
+

Lưu ý: Để kích hoạt các tính năng bố trí phải-qua-trái + cho ứng dụng của mình, bạn phải đặt {@code + supportsRtl} thành {@code "true"} và đặt {@code targetSdkVersion} thành 17 trở lên.

+

Được thêm trong API mức 17.

+
smallestWidthsw<N>dp

+ Ví dụ:
+ sw320dp
+ sw600dp
+ sw720dp
+ v.v. +
+

Kích cỡ cơ bản của một màn hình, thể hiện bằng kích thước ngắn nhất của khu vực màn hình +khả dụng. Cụ thể, smallestWidth của thiết bị bằng khoảng ngắn nhất giữa chiều cao và chiều rộng +khả dụng của màn hình (bạn cũng có thể gọi là "chiều rộng nhỏ nhất có thể" cho màn hình). Bạn có thể +sử dụng hạn định này để đảm bảo rằng, không phụ thuộc vào hướng hiện tại của màn hình, ứng dụng +của bạn có ít nhất {@code <N>} dp chiều rộng khả dụng cho UI của mình.

+

Ví dụ, nếu bố trí của bạn yêu cầu rằng kích thước nhỏ nhất của khu vực màn hình tối thiểu +phải luôn bằng 600 dp, vậy bạn có thể sử dụng hạn định này để tạo các tài nguyên bố trí, {@code +res/layout-sw600dp/}. Hệ thống sẽ chỉ sử dụng những tài nguyên này khi kích thước nhỏ nhất của +màn hình khả dụng tối thiểu bằng 600dp, không phụ thuộc vào cạnh 600dp là chiều cao hay chiều rộng +theo nhận thức của người dùng. SmallestWidth là đặc trưng kích cỡ màn hình cố định của thiết bị; smallestWidth của +thiết bị không thay đổi khi hướng của màn hình thay đổi.

+

SmallestWidth của một thiết bị sẽ xem xét cả trang trí màn hình và UI hệ thống. Ví +dụ, nếu thiết bị có một số phần tử UI cố định trên màn hình mà chiếm mất khoảng trống dọc +theo trục smallestWidth, hệ thống sẽ khai báo smallestWidth nhỏ hơn kích cỡ màn hình +thực tế, bởi chúng là những điểm ảnh màn hình không khả dụng cho UI của bạn. Vì thế, giá trị mà bạn sử dụng +nên là kích thước nhỏ nhất thực tế mà bố trí của bạn yêu cầu (thông thường, giá trị này bằng +"chiều rộng nhỏ nhất" mà bố trí của bạn hỗ trợ, không phụ thuộc vào hướng hiện tại của màn hình).

+

Một số giá trị mà bạn có thể sử dụng ở đây đối với các kích cỡ màn hình phổ biến:

+
    +
  • 320, cho các thiết bị có cấu hình màn hình như: +
      +
    • 240x320 ldpi (thiết bị cầm tay QVGA)
    • +
    • 320x480 mdpi (thiết bị cầm tay)
    • +
    • 480x800 hdpi (thiết bị cầm tay mật độ cao)
    • +
    +
  • +
  • 480, đối với những màn hình như 480x800 mdpi (máy tính bảng/thiết bị cầm tay).
  • +
  • 600, đối với những màn hình như 600x1024 mdpi (máy tính bảng 7").
  • +
  • 720, đối với những màn hình như 720x1280 mdpi (máy tính bảng 10").
  • +
+

Khi ứng dụng của bạn cung cấp nhiều thư mục tài nguyên với những giá trị khác nhau cho + hạn định smallestWidth, hệ thống sẽ sử dụng hạn định gần nhất với (không vượt quá) +smallestWidth của thiết bị.

+

Được thêm trong API mức 13.

+

Xem thêm thuộc tính {@code +android:requiresSmallestWidthDp}, trong đó khai báo smallestWidth tối thiểu mà ứng dụng của bạn +tương thích với, và trường cấu hình {@link +android.content.res.Configuration#smallestScreenWidthDp}, trong đó lưu trữ giá trị +smallestWidth của thiết bị.

+

Để biết thêm thông tin về việc thiết kế cho các màn hình khác nhau và sử dụng hạn định +này, hãy xem hướng dẫn dành cho nhà phát triển Hỗ trợ +Nhiều Màn hình.

+
Chiều rộng khả dụngw<N>dp

+ Ví dụ:
+ w720dp
+ w1024dp
+ v.v. +
+

Quy định một chiều rộng màn hình khả dụng tối thiểu theo đơn vị {@code dp} mà tại đó, tài nguyên + nên được sử dụng—được định nghĩa bởi giá trị <N>. Giá trị + cấu hình này sẽ thay đổi khi hướng + thay đổi giữa khổ ngang và dọc để khớp với chiều rộng thực tế hiện tại.

+

Khi ứng dụng của bạn cung cấp nhiều thư mục tài nguyên với những giá trị khác nhau + cho cấu hình này, hệ thống sẽ sử dụng giá trị gần nhất với (không vượt quá) + chiều rộng hiện tại của màn hình. Giá trị + ở đây xét cả trang trí trên màn hình, vì thế nếu thiết bị có một số + phần tử UI cố định ở cạnh trái hoặc phải của màn hình, nó + sẽ sử dụng một giá trị cho chiều rộng nhỏ hơn kích cỡ màn hình thực sự, dùng + cho những phần tử UI này và làm giảm khoảng trống khả dụng của ứng dụng.

+

Được thêm trong API mức 13.

+

Xem thêm trường cấu hình {@link android.content.res.Configuration#screenWidthDp} + mà chứa chiều rộng màn hình hiện tại.

+

Để biết thêm thông tin về việc thiết kế cho các màn hình khác nhau và sử dụng hạn định +này, hãy xem hướng dẫn dành cho nhà phát triển Hỗ trợ +Nhiều Màn hình.

+
Chiều cao khả dụngh<N>dp

+ Ví dụ:
+ h720dp
+ h1024dp
+ v.v. +
+

Quy định chiều cao màn hình khả dụng tối thiểu theo đơn vị "dp" mà tại đó tài nguyên + nên được sử dụng—được định nghĩa bởi giá trị <N>. Giá trị + cấu hình này sẽ thay đổi khi hướng + thay đổi giữa khổ ngang và dọc để khớp với chiều cao thực tế hiện tại.

+

Khi ứng dụng của bạn cung cấp nhiều thư mục tài nguyên với những giá trị khác nhau + cho cấu hình này, hệ thống sẽ sử dụng giá trị gần nhất với (không vượt quá) + chiều cao hiện tại của màn hình. Giá trị + ở đây xét cả trang trí trên màn hình, vì thế nếu thiết bị có một số + phần tử UI cố định trên cạnh trên hoặc dưới của màn hình, nó sẽ sử dụng + một giá trị cho chiều cao nhỏ hơn kích cỡ màn hình thực sự, dùng + cho những phần tử UI này và làm giảm khoảng trống khả dụng của ứng dụng. Trang trí + trên màn hình mà không cố định (chẳng hạn như thanh trạng thái của điện thoại mà có thể được + ẩn khi ở toàn màn hình) không được xét ở đây, cả + những trang trí trên cửa sổ như thanh tiêu đề hay thanh hành động cũng vậy, vì thế ứng dụng phải được chuẩn bị để + xử lý một khoảng trống nhỏ hơn mức mà chúng quy định. +

Được thêm trong API mức 13.

+

Xem thêm trường cấu hình {@link android.content.res.Configuration#screenHeightDp} + mà chứa chiều rộng màn hình hiện tại.

+

Để biết thêm thông tin về việc thiết kế cho các màn hình khác nhau và sử dụng hạn định +này, hãy xem hướng dẫn dành cho nhà phát triển Hỗ trợ +Nhiều Màn hình.

+
Kích cỡ màn hình + small
+ normal
+ large
+ xlarge +
+
    +
  • {@code small}: Các màn hình có kích cỡ tương tự như màn hình + QVGA mật độ thấp. Kích cỡ bố trí tối thiểu đối với một màn hình nhỏ + bằng xấp xỉ 320x426 đơn vị dp. Các ví dụ như QVGA mật độ thấp và VGA mật độ + cao.
  • +
  • {@code normal}: Các màn hình có kích cỡ tương tự như màn hình + HVGA mật độ trung bình. Kích cỡ bố trí tối thiểu + đối với một màn hình bình thường bằng xấp xỉ 320x470 đơn vị dp. Ví dụ + về những màn hình như vậy là WQVGA mật độ thấp, HVGA mật độ trung bình, WVGA + mật độ cao.
  • +
  • {@code large}: Các màn hình có kích cỡ tương tự như màn hình + VGA mật độ trung bình. + Kích cỡ bố trí tối thiểu đối với một màn hình lớn bằng xấp xỉ 480x640 đơn vị dp. + Ví dụ như các màn hình mật độ trung bình VGA và WVGA.
  • +
  • {@code xlarge}: Các màn hình lớn hơn đáng kể so với màn hình + HVGA mật độ trung bình truyền thống. Kích cỡ bố trí tối thiểu đối với một màn hình siêu lớn + bằng xấp xỉ 720x960 đơn vị dp. Trong hầu hết trường hợp, những thiết bị có màn hình + siêu lớn sẽ quá lớn để mang trong túi và gần như là + thiết bị kiểu máy tính bảng. Được thêm trong API mức 9.
  • +
+

Lưu ý: Việc sử dụng một hạn định kích cỡ không hàm ý rằng các +tài nguyên chỉ áp dụng cho màn hình có kích cỡ đó. Nếu bạn không cung cấp cho các tài nguyên +thay thế với các hạn định khớp tốt hơn với cấu hình thiết bị hiện tại, hệ thống có thể sử dụng +bất kỳ tài nguyên nào phù hợp nhất.

+

Chú ý: Nếu tất cả tài nguyên của bạn sử dụng một hạn định kích cỡ +lớn hơn màn hình hiện tại, hệ thống sẽ không sử dụng chúng và +ứng dụng của bạn sẽ bị lỗi vào thời gian chạy (ví dụ, nếu tất cả tài nguyên bố trí được gắn thẻ hạn định {@code +xlarge} nhưng thiết bị lại có màn hình kích cỡ bình thường).

+

Được thêm trong API mức 4.

+ +

Xem Hỗ trợ Nhiều +Màn hình để biết thêm thông tin.

+

Xem thêm trường cấu hình {@link android.content.res.Configuration#screenLayout}, +ở đó cho biết màn hình là màn hình nhỏ, bình thường, +hay lớn.

+
Tỷ lệ màn hình + long
+ notlong +
+
    +
  • {@code long}: Màn hình dài, chẳng hạn như WQVGA, WVGA, FWVGA
  • +
  • {@code notlong}: Màn hình không dài, chẳng hạn như QVGA, HVGA và VGA
  • +
+

Được thêm trong API mức 4.

+

Giá trị này thuần túy được dựa trên tỷ lệ khung ảnh của màn hình (màn hình "dài" sẽ rộng hơn). Nó +không liên quan tới hướng của màn hình.

+

Xem thêm trường cấu hình {@link android.content.res.Configuration#screenLayout}, +ở đó cho biết màn hình có dài không.

+
Hướng của màn hình + port
+ land +
+
    +
  • {@code port}: Thiết bị ở hướng đứng (thẳng đứng)
  • +
  • {@code land}: Thiết bị ở khổ ngang (nằm ngang)
  • + +
+

Giá trị này có thể thay đổi trong suốt vòng đời ứng dụng của bạn nếu người dùng xoay +màn hình. Xem phần Xử lý Thay đổi Thời gian chạy để biết thông tin về +ảnh hưởng của điều này tới ứng dụng của bạn trong thời gian chạy.

+

Xem thêm trường cấu hình {@link android.content.res.Configuration#orientation}, trong đó +cho biết hướng thiết bị hiện tại.

+
Chế độ UI + car
+ desk
+ television
+ appliance + watch +
+
    +
  • {@code car}: Thiết bị đang hiển thị trong đế gắn trên ô-tô
  • +
  • {@code desk}: Thiết bị đang hiển thị trong đế gắn trên bàn
  • +
  • {@code television}: Thiết bị đang hiển thị trên một TV, mang đến một + trải nghiệm "10 foot" (3 mét) trong đó UI của nó nằm trên một màn hình lớn + cách xa người dùng, được định hướng chủ yếu quanh DPAD hoặc cách + tương tác không sử dụng con trỏ khác
  • +
  • {@code appliance}: Thiết bị đang đóng vai trò như một dụng cụ không + có màn hình hiển thị
  • +
  • {@code watch}: Thiết bị có một màn hình hiển thị và được đeo trên cổ tay
  • +
+

Được thêm trong API mức 8, TV được thêm trong API 13, đồng hồ được thêm trong API 20.

+

Để biết thông tin về cách ứng dụng của bạn hồi đáp khi thiết bị được cắm vào hoặc + rút khỏi đế, hãy đọc Xác định +và Theo dõi Trạng thái và Loại Đế.

+

Giá trị này có thể thay đổi trong suốt vòng đời ứng dụng của bạn nếu người dùng đặt +thiết bị vào đế. Bạn có thể kích hoạt hoặc vô hiệu hóa một số chế độ này bằng cách sử dụng {@link +android.app.UiModeManager}. Xem phần Xử lý Thay đổi Thời gian chạy để +biết thông tin về ảnh hưởng của điều này tới ứng dụng của bạn trong thời gian chạy.

+
Chế độ ban đêm + night
+ notnight +
+
    +
  • {@code night}: Thời gian ban đêm
  • +
  • {@code notnight}: Thời gian ban ngày
  • +
+

Được thêm trong API mức 8.

+

Giá trị này có thể thay đổi trong suốt vòng đời ứng dụng của bạn nếu chế độ ban đêm được để ở +chế độ tự động (mặc định), trong trường hợp đó chế độ sẽ thay đổi dựa vào thời gian trong ngày. Bạn có thể kích hoạt +hoặc vô hiệu hóa chế độ này bằng cách sử dụng {@link android.app.UiModeManager}. Xem phần Xử lý Thay đổi Thời gian chạy để biết thông tin về ảnh hưởng của điều này tới +ứng dụng của bạn trong thời gian chạy.

+
Mật độ điểm ảnh màn hình (dpi) + ldpi
+ mdpi
+ hdpi
+ xhdpi
+ xxhdpi
+ xxxhdpi
+ nodpi
+ tvdpi +
+
    +
  • {@code ldpi}: Màn hình mật độ thấp; xấp xỉ 120dpi.
  • +
  • {@code mdpi}: Màn hình mật độ trung bình (trên HVGA truyền thống); xấp xỉ +160dpi.
  • +
  • {@code hdpi}: Màn hình mật độ cao; xấp xỉ 240dpi.
  • +
  • {@code xhdpi}: Màn hình mật độ siêu cao; xấp xỉ 320dpi. Được thêm trong API +Mức 8
  • +
  • {@code xxhdpi}: Màn hình mật độ siêu siêu cao; xấp xỉ 480dpi. Được thêm trong API +Mức 16
  • +
  • {@code xxxhdpi}: Mật độ siêu siêu siêu cao sử dụng (chỉ biểu tượng trình khởi chạy, xem + ghi chú + trong Hỗ trợ Nhiều Màn hình); xấp xỉ 640dpi. Được thêm trong API +Mức 18
  • +
  • {@code nodpi}: Loại này có thể được sử dụng cho tài nguyên bitmap mà bạn không muốn được định cỡ +cho khớp với mật độ của thiết bị.
  • +
  • {@code tvdpi}: Màn hình trong khoảng giữa mdpi và hdpi; xấp xỉ 213dpi. Đây +không được coi là nhóm mật độ "cơ bản". Nó được dành chủ yếu cho TV và hầu hết +các ứng dụng không cần nó—với điều kiện các tài nguyên mdpi và hpdi đủ cho hầu hết ứng dụng +và hệ thống sẽ định cỡ chúng cho phù hợp. Hạn định này đã được giới thiệu với API mức 13.
  • +
+

Có tỷ lệ định cỡ 3:4:6:8:12:16 giữa sáu mật độ cơ bản (bỏ qua mật độ +tvdpi). Vì thế, một tệp bimap 9x9 trong ldpi sẽ bằng 12x12 trong mdpi, 18x18 trong hdpi, 24x24 trong xhdpi, v.v. +

+

Nếu bạn quyết định rằng tài nguyên hình ảnh của mình không đủ đẹp trên TV hoặc +một số thiết bị khác và muốn thử tài nguyên tvdpi, hệ số định cỡ sẽ bằng 1,33*mdpi. Ví +dụ, một hình ảnh 100px x 100px đối với màn hình mdpi sẽ bằng 133px x 133px đối với tvdpi.

+

Lưu ý: Việc sử dụng một hạn định mật độ không hàm ý rằng các +tài nguyên chỉ áp dụng cho màn hình có mật độ đó. Nếu bạn không cung cấp cho các tài nguyên +thay thế với các hạn định khớp tốt hơn với cấu hình thiết bị hiện tại, hệ thống có thể sử dụng +bất kỳ tài nguyên nào phù hợp nhất.

+

Xem Hỗ trợ Nhiều +Màn hình để biết thêm thông tin về cách xử lý các mật độ màn hình khác nhau và cách Android +có thể định cỡ bitmap của mình cho vừa với mật độ hiện tại.

+
Loại màn hình cảm ứng + notouch
+ finger +
+
    +
  • {@code notouch}: Thiết bị không có màn hình cảm ứng.
  • +
  • {@code finger}: Thiết bị có màn hình cảm ứng để + được sử dụng thông qua tương tác hướng của ngón tay của người dùng.
  • +
+

Xem thêm trường cấu hình {@link android.content.res.Configuration#touchscreen}, +nó cho biết loại màn hình cảm ứng trên thiết bị.

+
Sự sẵn có của bàn phím + keysexposed
+ keyshidden
+ keyssoft +
+
    +
  • {@code keysexposed}: Thiết bị có sẵn một bàn phím. Nếu thiết bị có một +bàn phím mềm được kích hoạt (có khả năng), giá trị này có thể được sử dụng khi bàn phím cứng +không hiển thị trước người dùng, ngay cả khi thiết bị không có bàn phím cứng. Nếu không có +bàn phím mềm hoặc bàn phím mềm bị vô hiệu hóa, khi đó giá trị này chỉ được sử dụng khi một bàn phím cứng được +hiển thị.
  • +
  • {@code keyshidden}: Thiết bị có sẵn một bàn phím cứng nhưng nó bị +ẩn đi thiết bị không có bàn phím mềm được kích hoạt.
  • +
  • {@code keyssoft}: Thiết bị có một bàn phím mềm được kích hoạt dù nó có +hiển thị hay không.
  • +
+

Nếu bạn cung cấp các tài nguyên keysexposed, nhưng không cung cấp tài nguyên keyssoft +, hệ thống sẽ sử dụng tài nguyên keysexposed mà không phụ thuộc vào việc có hiển thị +bàn phím hay không, miễn là hệ thống có kích hoạt một bàn phím mềm.

+

Giá trị này có thể thay đổi trong vòng đời ứng dụng của bạn nếu người dùng mở một bàn phím +cứng. Xem phần Xử lý Thay đổi Thời gian chạy để biết thông tin về +ảnh hưởng của điều này tới ứng dụng của bạn trong thời gian chạy.

+

Xem thêm các trường cấu hình {@link +android.content.res.Configuration#hardKeyboardHidden} và {@link +android.content.res.Configuration#keyboardHidden}, theo đó tương ứng cho biết mức độ hiển thị của bàn phím +cứng và mức độ hiển thị của bất kỳ loại bàn phím nào (bao gồm bàn phím mềm).

+
Phương pháp nhập liệu văn bản chính + nokeys
+ qwerty
+ 12key +
+
    +
  • {@code nokeys}: Thiết bị không có phím cứng cho việc nhập liệu văn bản.
  • +
  • {@code qwerty}: Thiết bị có một bàn phím qwerty cứng, dù nó có hiển thị với +người dùng +hay không.
  • +
  • {@code 12key}: Thiết bị có một bàn phím 12-phím cứng, dù nó có hiển thị với +người dùng hay không.
  • +
+

Xem thêm trường cấu hình {@link android.content.res.Configuration#keyboard}, trong đó +cho biết phương pháp nhập liệu văn bản chính sẵn có.

+
Phiên bản Nền tảng (Mức API)Ví dụ:
+ v3
+ v4
+ v7
+ v.v.
+

Mức API được hỗ trợ bởi thiết bị. Ví dụ, v1 đối với API mức +1 (thiết bị ở phiên bản Android 1.0 hoặc cao hơn) và v4 đối với API mức 4 (thiết bị ở phiên bản Android +1.6 hoặc cao hơn). Xem tài liệu Mức API của Android để biết thêm thông tin +về những giá trị này.

+
+ + +

Lưu ý: Một số hạn định cấu hình đã được thêm kể từ phiên bản Android +1.0, vì thế không phải tất cả phiên bản Android đều hỗ trợ tất cả hạn định. Việc sử dụng một hạn định mới sẽ hàm ý +thêm hạn định phiên bản nền tảng sao cho các thiết bị cũ hơn chắc chắn sẽ bỏ qua nó. Ví dụ, sử dụng +một hạn định w600dp sẽ tự động bao gồm hạn định v13, vì +hạn định chiều rộng khả dụng mới có trong API mức 13. Để tránh bất kỳ sự cố nào, hãy luôn đưa vào một tập hợp +các tài nguyên mặc định (tập hợp các tài nguyên không có hạn định). Để biết thêm thông tin, hãy xem phần +nói về Cung cấp Tính tương thích giữa Thiết bị với Tài nguyên +Tốt nhất.

+ + + +

Quy tắc về tên hạn định

+ +

Sau đây là một số quy tắc về việc sử dụng tên của hạn định cấu hình:

+ +
    +
  • Bạn có thể quy định nhiều hạn định cho một tập hợp đơn lẻ các tài nguyên, được tách riêng bởi dấu gạch ngang. Ví +dụ, drawable-en-rUS-land sẽ áp dụng cho các thiết bị US-English ở hướng +khổ ngang.
  • +
  • Các hạn định phải theo thứ tự liệt kê trong bảng 2. Ví +dụ: +
      +
    • Sai: drawable-hdpi-port/
    • +
    • Đúng: drawable-port-hdpi/
    • +
    +
  • +
  • Các thư mục tài nguyên thay thế không được lồng nhau. Ví dụ, bạn không được có +res/drawable/drawable-en/.
  • +
  • Các giá trị không phân biệt chữ hoa/thường. Trình biên dịch tài nguyên sẽ chuyển tên thư mục + thành chữ thường trước khi xử lý để tránh các vấn đề xảy ra trên hệ thống tệp + không phân biệt chữ hoa/thường. Bất kỳ việc đổi sang chữ hoa nào trong tên chỉ nhằm mục đích dễ đọc hơn.
  • +
  • Chỉ hỗ trợ một giá trị cho mỗi loại hạn định. Ví dụ, nếu bạn muốn sử dụng +cùng các tệp vẽ được cho tiếng Tây Ban Nha và tiếng Pháp, bạn không thể đặt tên thư mục là +drawable-rES-rFR/. Thay vào đó, bạn cần hai thư mục tài nguyên chẳng hạn như +drawable-rES/drawable-rFR/, trong đó chứa các tệp phù hợp. +Tuy nhiên, bạn không bắt buộc thực sự phải tạo bản sao các tệp như nhau ở cả hai vị trí. Thay vào đó, bạn có thể +tạo một bí danh tới một tài nguyên. Xem phần Tạo +tài nguyên bí danh ở bên dưới.
  • +
+ +

Sau khi bạn lưu tài nguyên thay thế vào các thư mục được đặt tên bằng +những hạn định này, Android sẽ tự động áp dụng các tài nguyên trong ứng dụng của bạn dựa trên +cấu hình thiết bị hiện tại. Cứ mỗi lần yêu cầu một tài nguyên, Android lại kiểm tra các thư mục tài nguyên +thay thế chứa tệp tài nguyên được yêu cầu, rồi tìm tài nguyên +so khớp phù hợp nhất (được trình bày ở bên dưới). Nếu không có tài nguyên thay thế khớp +với một cấu hình thiết bị cụ thể, khi đó Android sẽ sử dụng các tài nguyên mặc định tương ứng ( +tập hợp các tài nguyên cho một loại tài nguyên cụ thể không bao gồm hạn định +cấu hình).

+ + + +

Tạo tài nguyên bí danh

+ +

Khi bạn có một tài nguyên muốn sử dụng cho nhiều hơn một cấu hình +thiết bị (nhưng không muốn cung cấp làm tài nguyên mặc định), bạn không cần đặt +cùng tài nguyên đó vào nhiều hơn một thư mục tài nguyên thay thế. Thay vào đó, bạn có thể (trong một số trường hợp) tạo một tài nguyên +thay thế +đóng vai trò như một bí danh cho tài nguyên được lưu trong thư mục tài nguyên mặc định của bạn.

+ +

Lưu ý: Không phải tất cả tài nguyên đều đưa ra cơ chế mà theo đó bạn có thể +tạo một bí danh tới một tài nguyên khác. Cụ thể, hoạt hình, menu, tài nguyên thô và các tài nguyên +không được quy định khác trong thư mục {@code xml/} không cung cấp tính năng này.

+ +

Ví dụ, hãy tưởng tượng bạn có một biểu tượng ứng dụng, {@code icon.png}, và cần phiên bản duy nhất của +nó cho các bản địa khác nhau. Tuy nhiên, hai bản địa English-Canadian và French-Canadian, cần +sử dụng cùng phiên bản. Bạn có thể giả sử rằng mình cần sao chép cùng hình ảnh +vào thư mục tài nguyên cho cả English-Canadian và French-Canadian, nhưng điều đó +không đúng. Thay vào đó, bạn có thể lưu hình ảnh được sử dụng cho cả hai thành {@code icon_ca.png} (bất kỳ +tên nào khác ngoài {@code icon.png}) và đặt +nó vào thư mục {@code res/drawable/} mặc định. Sau đó, tạo một tệp {@code icon.xml} trong {@code +res/drawable-en-rCA/} và {@code res/drawable-fr-rCA/} tham chiếu tới tài nguyên {@code icon_ca.png} +bằng cách sử dụng phần tử {@code <bitmap>}. Điều này cho phép bạn lưu trữ chỉ một phiên bản của tệp +PNG và hai tệp XML nhỏ trỏ tới nó. (Ví dụ về tệp XML được trình bày ở bên dưới.)

+ + +

Nội dung vẽ được

+ +

Để tạo một bí danh cho một nội dung vẽ được đang tồn tại, hãy sử dụng phần tử {@code <bitmap>}. +Ví dụ:

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
+    android:src="@drawable/icon_ca" />
+
+ +

Nếu bạn lưu tệp này thành {@code icon.xml} (trong một thư mục tài nguyên thay thế chẳng hạn như +{@code res/drawable-en-rCA/}), nó sẽ được biên dịch vào một tài nguyên mà bạn +có thể tham chiếu như là {@code R.drawable.icon}, nhưng thực tế lại là bí danh cho tài nguyên {@code +R.drawable.icon_ca} (được lưu trong {@code res/drawable/}).

+ + +

Bố trí

+ +

Để tạo một bí danh cho một bố trí hiện tại, hãy sử dụng phần tử {@code <include>} +, được bọc trong một {@code <merge>}. Ví dụ:

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<merge>
+    <include layout="@layout/main_ltr"/>
+</merge>
+
+ +

Nếu bạn lưu tệp này thành {@code main.xml}, nó sẽ được biên dịch thành một tài nguyên mà bạn có thể tham chiếu +như là {@code R.layout.main}, nhưng thực tế lại là một bí danh cho tài nguyên {@code R.layout.main_ltr} +.

+ + +

Xâu và các giá trị đơn giản khác

+ +

Để tạo một bí danh cho một xâu hiện có, chỉ cần sử dụng ID tài nguyên của xâu +mong muốn làm giá trị cho xâu mới. Ví dụ:

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <string name="hello">Hello</string>
+    <string name="hi">@string/hello</string>
+</resources>
+
+ +

Tài nguyên {@code R.string.hi} lúc này là một bí danh cho {@code R.string.hello}.

+ +

Các giá trị đơn giản khác cũng +hoạt động tương tự. Ví dụ, màu sắc:

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <color name="yellow">#f00</color>
+    <color name="highlight">@color/red</color>
+</resources>
+
+ + + + +

Cung cấp Tính tương thích giữa Thiết bị với Tài nguyên Tốt nhất

+ +

Để ứng dụng của bạn hỗ trợ nhiều cấu hình thiết bị, một điều rất quan trọng đó là +bạn luôn cung cấp các tài nguyên mặc định cho từng loại tài nguyên mà ứng dụng của bạn sử dụng.

+ +

Ví dụ, nếu ứng dụng của bạn hỗ trợ vài ngôn ngữ, hãy luôn bao gồm một thư mục {@code +values/} (trong đó, xâu của bạn được lưu) mà không cần một hạn định ngôn ngữ và khu vực. Nếu thay vào đó bạn đặt tất cả tệp xâu của mình +vào các thư mục có một hạn định ngôn ngữ và khu vực, khi đó ứng dụng của bạn sẽ bị lỗi khi chạy +trên một thiết bị được đặt ở một ngôn ngữ mà các xâu của bạn không hỗ trợ. Nhưng miễn là bạn cung cấp các tài nguyên +{@code values/} mặc định, khi đó ứng dụng của bạn sẽ chạy bình thường (ngay cả khi người dùng không +hiểu ngôn ngữ đó—vậy còn tốt hơn là bị lỗi).

+ +

Tương tự, nếu bạn cung cấp các tài nguyên bố trí khác nhau dựa trên hướng của màn hình, bạn nên +chọn một hướng làm mặc định của mình. Ví dụ, thay vì cung cấp tài nguyên bố trí trong {@code +layout-land/} cho khổ ngang và {@code layout-port/} cho khổ dọc, hãy để một cái làm mặc định, chẳng hạn như +{@code layout/} đối với khổ ngang và {@code layout-port/} đối với khổ dọc.

+ +

Việc cung cấp tài nguyên mặc định quan trọng không chỉ bởi ứng dụng của bạn có thể chạy trên một +cấu hình mà bạn chưa nghĩ đến, mà còn bởi các phiên bản Android mới đôi khi thêm +hạn định cấu hình mà những phiên bản cũ hơn không hỗ trợ. Nếu bạn sử dụng một hạn định tài nguyên mới, +nhưng vẫn duy trì tính tương thích về mã với các phiên bản cũ hơn của Android thì khi một phiên bản cũ hơn của +Android chạy trên ứng dụng của bạn, nó sẽ bị lỗi nếu bạn không cung cấp tài nguyên mặc định, do nó +không thể sử dụng tài nguyên được đặt tên bằng hạn định mới. Ví dụ, nếu {@code +minSdkVersion} của bạn được đặt bằng 4, và bạn xác định tất cả tài nguyên vẽ được của mình bằng cách sử dụng chế độ ban đêm ({@code night} hoặc {@code notnight}, đã được thêm trong API +Mức 8), khi đó một thiết bị API mức 4 sẽ không thể truy cập tài nguyên vẽ được của bạn và sẽ bị lỗi. Trong trường hợp +này, bạn có thể muốn {@code notnight} làm tài nguyên mặc định của mình, vì thế bạn nên loại trừ hạn định +đó sao cho tài nguyên vẽ được của bạn ở trong {@code drawable/} hoặc {@code drawable-night/}.

+ +

Vì vậy, để mang lại khả năng tương thích với thiết bị tốt nhất, hãy luôn cung cấp tài nguyên +mặc định cho những tài nguyên mà ứng dụng của bạn cần thực hiện đúng cách. Sau đó, hãy tạo tài nguyên +thay thế cho các cấu hình thiết bị cụ thể bằng cách sử dụng hạn định cấu hình.

+ +

Có một ngoại lệ đối với quy tắc này: Nếu {@code minSdkVersion} của ứng dụng của bạn bằng 4 hoặc +lớn hơn, bạn không cần đến tài nguyên vẽ được mặc định khi cung cấp tài nguyên +vẽ được thay thế bằng hạn định mật độ màn hình. Kể cả khi không có +tài nguyên vẽ được mặc định, Android cũng có thể tìm thấy kết quả khớp tốt nhất trong số các mật độ màn hình thay thế và sẽ định cỡ +bitmap nếu cần. Tuy nhiên, để có trải nghiệm tốt nhất trên tất cả thiết bị, bạn nên +cung cấp nội dung vẽ được thay thế cho cả ba loại mật độ.

+ + + +

Cách Android tìm Tài nguyên Khớp Tốt nhất

+ +

Khi bạn yêu cầu một tài nguyên mà bạn cung cấp nội dung thay thế cho nó, Android sẽ lựa chọn +tài nguyên thay thế để sử dụng vào thời gian chạy, tùy vào cấu hình thiết bị hiện tại. Để +diễn tả cách Android lựa chọn một tài nguyên thay thế, giả sử có các thư mục vẽ được sau, +mỗi thư mục lại chứa các phiên bản khác nhau của cùng hình ảnh:

+ +
+drawable/
+drawable-en/
+drawable-fr-rCA/
+drawable-en-port/
+drawable-en-notouch-12key/
+drawable-port-ldpi/
+drawable-port-notouch-12key/
+
+ +

Và giả sử cấu hình thiết bị như sau:

+ +

+Bản địa = en-GB
+Hướng màn hình = port
+Mật độ điểm ảnh màn hình = hdpi
+Loại màn hình cảm ứng = notouch
+Phương pháp nhập liệu văn bản chính = 12key +

+ +

Bằng cách so sánh cấu hình thiết bị với các tài nguyên thay thế sẵn có, Android sẽ lựa chọn +nội dung vẽ được từ {@code drawable-en-port}.

+ +

Hệ thống ra quyết định của mình về các tài nguyên nào sẽ sử dụng bằng lô-gic +sau:

+ + +
+ +

Hình 2. Lưu đồ về cách Android tìm tài nguyên +khớp tốt nhất.

+
+ + +
    +
  1. Loại bỏ các tệp tài nguyên mà trái với cấu hình thiết bị. +

    Thư mục drawable-fr-rCA/ bị loại bỏ vì nó +trái với bản địa en-GB.

    +
    +drawable/
    +drawable-en/
    +drawable-fr-rCA/
    +drawable-en-port/
    +drawable-en-notouch-12key/
    +drawable-port-ldpi/
    +drawable-port-notouch-12key/
    +
    +

    Ngoại lệ: Mật độ điểm ảnh màn hình là một hạn định không +bị loại bỏ do trái ngược. Mặc dù mật độ màn hình của thiết bị là hdpi, +drawable-port-ldpi/ không bị loại bỏ vì mọi mật độ màn hình đều +được coi là một kết quả khớp tại thời điểm này. Bạn có thể tham khảo thêm thông tin trong tài liệu Hỗ trợ Nhiều +Màn hình.

  2. + +
  3. Chọn hạn định có mức ưu tiên cao nhất (tiếp theo) trong danh sách (bảng 2). +(Bắt đầu bằng MCC, sau đó di chuyển xuống.)
  4. +
  5. Có thư mục tài nguyên nào bao gồm hạn định này không?
  6. +
      +
    • Nếu Không, hãy quay lại bước 2 và tìm với hạn định tiếp theo. (Trong ví dụ, + câu trả lời là "không" tới khi đi đến hạn định ngôn ngữ.)
    • +
    • Nếu Có, tiếp tục sang bước 4.
    • +
    + + +
  7. Loại bỏ các thư mục tài nguyên không bao gồm hạn định này. Trong ví dụ, hệ thống +sẽ loại bỏ tất cả thư mục không bao gồm hạn định ngôn ngữ:
  8. +
    +drawable/
    +drawable-en/
    +drawable-en-port/
    +drawable-en-notouch-12key/
    +drawable-port-ldpi/
    +drawable-port-notouch-12key/
    +
    +

    Ngoại lệ: Nếu hạn định đang xét là mật độ điểm ảnh màn hình, +Android sẽ chọn tùy chọn khớp gần nhất với mật độ màn hình của thiết bị. +Nhìn chung, Android ưu tiên giảm kích cỡ một hình ảnh ban đầu lớn hơn thay vì tăng kích cỡ một hình ảnh ban đầu +nhỏ hơn. Xem phần Hỗ trợ Nhiều +Màn hình.

    + + +
  9. Quay lại và lặp lại các bước 2, 3 và 4 tới khi chỉ còn lại một thư mục. Trong ví dụ, hướng +màn hình là hạn định tiếp theo nếu có kết quả khớp. +Vì thế, các tài nguyên không quy định hướng màn hình sẽ bị loại bỏ: +
    +drawable-en/
    +drawable-en-port/
    +drawable-en-notouch-12key/
    +
    +

    Thư mục còn lại là {@code drawable-en-port}.

    +
  10. +
+ +

Mặc dù quy trình này được thực thi cho từng tài nguyên được yêu cầu, hệ thống sẽ tối ưu hóa hơn nữa +một số khía cạnh. Một cách tối ưu hóa như vậy đó là sau khi biết cấu hình thiết bị, nó có thể +loại bỏ các tài nguyên thay thế mà không thể khớp được. Ví dụ, nếu ngôn ngữ cấu hình +là English ("en"), khi đó bất kỳ thư mục tài nguyên nào có hạn định ngôn ngữ được đặt thành +ngôn ngữ khác English đều sẽ không được bao gồm trong tập hợp các tài nguyên được kiểm tra (mặc dù +thư mục tài nguyên không có hạn định ngôn ngữ vẫn được bao gồm).

+ +

Khi lựa chọn tài nguyên dựa trên hạn định kích cỡ màn hình, hệ thống sẽ sử dụng các tài nguyên +được thiết kế cho màn hình nhỏ hơn màn hình hiện tại nếu không có tài nguyên nào khớp tốt hơn +(ví dụ, một màn hình kích cỡ lớn sẽ sử dụng các tài nguyên màn hình kích cỡ bình thường nếu cần). Tuy nhiên, nếu +những tài nguyên duy nhất sẵn có lại lớn hơn màn hình hiện tại, hệ thống sẽ +không sử dụng chúng và ứng dụng của bạn sẽ bị lỗi nếu không có tài nguyên nào khác khớp với cấu hình +thiết bị (ví dụ, nếu tất cả tài nguyên bố trí đều được gắn thẻ bằng hạn định {@code xlarge}, +nhưng thiết bị lại có một màn hình kích cỡ bình thường).

+ +

Lưu ý: Mức ưu tiên của hạn định (trong bảng 2) quan trọng +hơn số lượng hạn định khớp chính xác với thiết bị. Ví dụ, trong bước 4 bên trên +lựa chọn trên danh sách bao gồm ba hạn định khớp chính xác với thiết bị (hướng, loại +màn hình cảm ứng, và phương pháp nhập liệu), trong khi drawable-en chỉ có một tham số khớp +(ngôn ngữ). Tuy nhiên, ngôn ngữ có mức ưu tiên cao hơn cả ba hạn định khác này, vì thế +drawable-port-notouch-12key bị loại.

+ +

Để tìm hiểu thêm về cách sử dụng tài nguyên trong ứng dụng của bạn, hãy tiếp tục sang phần Truy cập Tài nguyên.

diff --git a/docs/html-intl/intl/vi/guide/topics/resources/runtime-changes.jd b/docs/html-intl/intl/vi/guide/topics/resources/runtime-changes.jd new file mode 100644 index 0000000000000000000000000000000000000000..4a9c38ccfe6052614af5136e423ff3fced543153 --- /dev/null +++ b/docs/html-intl/intl/vi/guide/topics/resources/runtime-changes.jd @@ -0,0 +1,281 @@ +page.title=Xử lý Thay đổi Thời gian chạy +page.tags=hoạt động,vòng đời +@jd:body + + + +

Một số cấu hình thiết bị có thể thay đổi trong thời gian chạy +(chẳng hạn như hướng màn hình, sự sẵn có của bàn phím, và ngôn ngữ). Khi sự thay đổi như vậy diễn ra, +Android sẽ khởi động lại việc chạy +{@link android.app.Activity} ({@link android.app.Activity#onDestroy()} sẽ được gọi, sau đó là {@link +android.app.Activity#onCreate(Bundle) onCreate()}). Hành vi khởi động lại được thiết kế để giúp +ứng dụng điều chỉnh phù hợp với cấu hình mới bằng cách tự động tải lại ứng dụng của bạn bằng +các tài nguyên thay thế khớp với cấu hình thiết bị mới.

+ +

Để xử lý khởi động lại cho đúng, điều quan trọng là hoạt động của bạn khôi phục lại trạng thái trước đó +của nó thông qua vòng đời Hoạt động +thông thường, trong đó Android sẽ gọi +{@link android.app.Activity#onSaveInstanceState(Bundle) onSaveInstanceState()} trước khi nó hủy +hoạt động của bạn sao cho bạn có thể lưu dữ liệu về trạng thái của ứng dụng. Khi đó, bạn có thể khôi phục trạng thái +trong khi {@link android.app.Activity#onCreate(Bundle) onCreate()} hoặc {@link +android.app.Activity#onRestoreInstanceState(Bundle) onRestoreInstanceState()}.

+ +

Để kiểm tra xem ứng dụng của bạn có tự khởi động lại mà giữ nguyên trạng thái ứng dụng hay không, +bạn cần gọi ra các thay đổi cấu hình (chẳng hạn như thay đổi hướng màn hình) trong khi thực hiện các +tác vụ khác nhau trong ứng dụng của bạn. Ứng dụng của bạn sẽ có thể khởi động lại vào bất cứ lúc nào mà không bị mất +dữ liệu của người dùng hay trạng thái để xử lý các sự kiện như thay đổi cấu hình hoặc khi người dùng nhận được +một cuộc gọi đến rồi quay lại ứng dụng của bạn muộn hơn nhiều sau khi tiến trình +ứng dụng của bạn có thể đã bị hủy. Để tìm hiểu về cách bạn có thể khôi phục trạng thái hoạt động của mình, hãy đọc về Vòng đời của hoạt động.

+ +

Tuy nhiên, bạn có thể gặp phải một tình huống trong đó việc khởi động lại ứng dụng của bạn và +khôi phục phần lớn dữ liệu có thể tốn kém và tạo nên trải nghiệm người dùng kém. Trong +tình huống như vậy, bạn có hai tùy chọn:

+ +
    +
  1. Giữ lại một đối tượng trong khi thay đổi cấu hình +

    Cho phép hoạt động của bạn khởi động lại khi cấu hình thay đổi, nhưng mang theo một +đối tượng có trạng thái tới thực thể mới của hoạt động của bạn.

    + +
  2. +
  3. Tự mình xử lý thay đổi cấu hình +

    Ngăn không cho hệ thống khởi động lại hoạt động của bạn trong những thay đổi +cấu hình nhất định, nhưng nhận một lệnh gọi lại khi cấu hình thay đổi, sao cho bạn có thể cập nhật thủ công +hoạt động của mình nếu cần.

    +
  4. +
+ + +

Giữ lại một Đối tượng trong khi Thay đổi Cấu hình

+ +

Nếu việc khởi động lại hoạt động của bạn yêu cầu bạn phải khôi phục nhiều tập hợp dữ liệu lớn, hãy thiết lập lại kết nối +mạng, hoặc thực hiện các thao tác tăng cường khác, khi đó khởi động lại hoàn toàn do thay đổi cấu hình +có thể gây ra trải nghiệm người dùng chậm chạp. Đồng thời, có thể bạn sẽ không thể hoàn toàn khôi phục được +trạng thái hoạt động của mình với {@link android.os.Bundle} mà hệ thống lưu cho bạn bằng phương pháp gọi lại {@link +android.app.Activity#onSaveInstanceState(Bundle) onSaveInstanceState()}—nó không +được thiết kế để mang các đối tượng lớn (chẳng hạn như bitmap) và dữ liệu trong nó phải được nối tiếp hóa rồi +bỏ nối tiếp hóa, điều này có thể tiêu tốn nhiều bộ nhớ và khiến việc thay đổi cấu hình diễn ra chậm. Trong một +tình huống như vậy, bạn có thể gỡ bỏ gánh nặng khởi tạo lại hoạt động của mình bằng cách giữ lại {@link +android.app.Fragment} khi hoạt động của bạn được khởi động lại do thay đổi cấu hình. Phân đoạn này +có thể chứa các tham chiếu tới đối tượng có trạng thái mà bạn muốn giữ lại.

+ +

Khi hệ thống Android tắt hoạt động của bạn do một thay đổi cấu hình, các phân đoạn +của hoạt động mà bạn đã đánh dấu để giữ lại sẽ không bị hủy. Bạn có thể thêm các phân đoạn này vào +hoạt động của mình để giữ lại các đối tượng có trạng thái.

+ +

Để giữ lại các đối tượng có trạng thái trong một phân đoạn trong khi thay đổi cấu hình thời gian chạy:

+ +
    +
  1. Mở rộng lớp {@link android.app.Fragment} và khai báo các tham chiếu tới đối tượng + có trạng thái của bạn.
  2. +
  3. Gọi {@link android.app.Fragment#setRetainInstance(boolean)} khi phân đoạn được tạo. +
  4. +
  5. Thêm phân đoạn vào hoạt động của bạn.
  6. +
  7. Sử dụng {@link android.app.FragmentManager} để truy xuất phân đoạn khi hoạt động + được khởi động lại.
  8. +
+ +

Ví dụ, định nghĩa phân đoạn của bạn như sau:

+ +
+public class RetainedFragment extends Fragment {
+
+    // data object we want to retain
+    private MyDataObject data;
+
+    // this method is only called once for this fragment
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        // retain this fragment
+        setRetainInstance(true);
+    }
+
+    public void setData(MyDataObject data) {
+        this.data = data;
+    }
+
+    public MyDataObject getData() {
+        return data;
+    }
+}
+
+ +

Chú ý: Trong khi bạn có thể lưu trữ bất kỳ đối tượng nào, bạn +không nên chuyển một đối tượng được gắn với {@link android.app.Activity}, chẳng hạn như {@link +android.graphics.drawable.Drawable}, {@link android.widget.Adapter}, {@link android.view.View} +hay bất kỳ đối tượng nào khác đi kèm với một {@link android.content.Context}. Nếu bạn làm vậy, nó sẽ +rò rỉ tất cả dạng xem và tài nguyên của thực thể hoạt động gốc. (Rò rỉ tài nguyên +có nghĩa là ứng dụng của bạn duy trì việc lưu giữ tài nguyên và chúng không thể được thu dọn bộ nhớ rác, vì thế +rất nhiều bộ nhớ có thể bị mất.)

+ +

Khi đó, hãy sử dụng {@link android.app.FragmentManager} để thêm phân đoạn vào hoạt động. +Bạn có thể thu được đối tượng dữ liệu từ phân đoạn khi hoạt động bắt đầu lại trong khi +thay đổi cấu hình thời gian chạy. Ví dụ, định nghĩa hoạt động của bạn như sau:

+ +
+public class MyActivity extends Activity {
+
+    private RetainedFragment dataFragment;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.main);
+
+        // find the retained fragment on activity restarts
+        FragmentManager fm = getFragmentManager();
+        dataFragment = (DataFragment) fm.findFragmentByTag(“data”);
+
+        // create the fragment and data the first time
+        if (dataFragment == null) {
+            // add the fragment
+            dataFragment = new DataFragment();
+            fm.beginTransaction().add(dataFragment, “data”).commit();
+            // load the data from the web
+            dataFragment.setData(loadMyData());
+        }
+
+        // the data is available in dataFragment.getData()
+        ...
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+        // store the data in the fragment
+        dataFragment.setData(collectMyLoadedData());
+    }
+}
+
+ +

Trong ví dụ này, {@link android.app.Activity#onCreate(Bundle) onCreate()} thêm một phân đoạn +hoặc khôi phục một tham chiếu đến nó. {@link android.app.Activity#onCreate(Bundle) onCreate()} cũng +lưu trữ đối tượng có trạng thái bên trong thực thể phân đoạn đó. +{@link android.app.Activity#onDestroy() onDestroy()} cập nhật đối tượng có trạng thái bên trong +thực thể phân đoạn được giữ lại.

+ + + + + +

Tự mình Xử lý Thay đổi Cấu hình

+ +

Nếu ứng dụng của bạn không cần cập nhật các tài nguyên trong một thay đổi +cấu hình cụ thể bạn có giới hạn về hiệu năng yêu cầu bạn phải +tránh khởi động lại hoạt động, khi đó bạn có thể khai báo rằng hoạt động của bạn tự mình xử lý thay đổi cấu hình +, làm vậy sẽ tránh cho hệ thống khởi động lại hoạt động của bạn.

+ +

Lưu ý: Việc tự mình xử lý thay đổi cấu hình có thể khiến việc +sử dụng các tài nguyên thay thế khó khăn hơn nhiều, vì hệ thống không tự động áp dụng chúng +cho bạn. Kỹ thuật này nên được coi là giải pháp cuối cùng khi bạn phải tránh khởi động lại do một +thay đổi cấu hình và không được khuyến cáo đối với hầu hết ứng dụng.

+ +

Để khai báo rằng hoạt động của bạn xử lý một thay đổi cấu hình, hãy chỉnh sửa phần tử {@code <activity>} phù hợp trong +tệp bản kê khai của bạn để bao gồm thuộc tính {@code +android:configChanges} với một giá trị có chức năng biểu diễn cấu hình mà bạn muốn +xử lý. Các giá trị có thể được liệt kê trong tài liệu dành cho thuộc tính {@code +android:configChanges} (các giá trị thường được sử dụng nhất là {@code "orientation"} để +ngăn khởi động lại khi hướng màn hình thay đổi và {@code "keyboardHidden"} để ngăn +khởi động lại khi tính sẵn có của bàn phím thay đổi). Bạn có thể khai báo nhiều giá trị cấu hình trong +thuộc tính bằng cách tách chúng bằng một ký tự {@code |} đường dẫn nối.

+ +

Ví dụ, đoạn mã bản kê khai sau khai báo một hoạt động có chức năng xử lý cả +thay đổi về hướng màn hình và thay đổi về tính sẵn có của bàn phím:

+ +
+<activity android:name=".MyActivity"
+          android:configChanges="orientation|keyboardHidden"
+          android:label="@string/app_name">
+
+ +

Lúc này, khi một trong những cấu hình này thay đổi, {@code MyActivity} không khởi động lại. +Thay vào đó, {@code MyActivity} nhận được một lệnh gọi tới {@link +android.app.Activity#onConfigurationChanged(Configuration) onConfigurationChanged()}. Phương pháp này +được chuyển bởi một đối tượng {@link android.content.res.Configuration} mà quy định +cấu hình thiết bị mới. Bằng cách đọc các trường trong {@link android.content.res.Configuration}, +bạn có thể xác định cấu hình mới và thực hiện những thay đổi phù hợp bằng cách cập nhật +tài nguyên được sử dụng trong giao diện của bạn. Tại +thời điểm phương pháp này được gọi, đối tượng {@link android.content.res.Resources} của hoạt động của bạn được cập nhật +để trả về các tài nguyên dựa trên cấu hình mới, sao cho bạn có thể dễ dàng +đặt lại các phần tử trong UI của mình mà không để hệ thống khởi động lại hoạt động của bạn.

+ +

Chú ý: Bắt đầu với Android 3.2 (API mức 13), +"kích cỡ màn hình" cũng thay đổi khi thiết bị chuyển giữa hướng dọc và khổ ngang +. Vì thế, nếu bạn muốn ngăn cản việc khởi động lại vào thời gian chạy do thay đổi hướng khi phát triển +cho API mức 13 hoặc cao hơn (như được khai báo bởi các thuộc tính {@code minSdkVersion}{@code targetSdkVersion} +), bạn phải bao gồm giá trị {@code "screenSize"} bên cạnh giá trị {@code +"orientation"}. Cụ thể, bạn phải khai báo {@code +android:configChanges="orientation|screenSize"}. Tuy nhiên, nếu ứng dụng của bạn nhắm tới API mức +12 hoặc thấp hơn, khi đó hoạt động của bạn luôn tự mình xử lý thay đổi cấu hình này (thay đổi +cấu hình này không khởi động lại hoạt động của bạn, ngay cả khi đang chạy trên một thiết bị phiên bản Android 3.2 hoặc cao hơn).

+ +

Ví dụ, việc triển khai {@link +android.app.Activity#onConfigurationChanged(Configuration) onConfigurationChanged()} sau +sẽ kiểm tra hướng thiết bị hiện tại:

+ +
+@Override
+public void onConfigurationChanged(Configuration newConfig) {
+    super.onConfigurationChanged(newConfig);
+
+    // Checks the orientation of the screen
+    if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
+        Toast.makeText(this, "landscape", Toast.LENGTH_SHORT).show();
+    } else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT){
+        Toast.makeText(this, "portrait", Toast.LENGTH_SHORT).show();
+    }
+}
+
+ +

Đối tượng {@link android.content.res.Configuration} biểu diễn tất cả cấu hình +hiện tại, không chỉ những cấu hình đã thay đổi. Trong phần lớn thời gian, bạn sẽ không quan tâm chính xác xem +cấu hình đã thay đổi như thế nào và có thể đơn giản gán lại tất cả tài nguyên của mình với chức năng cung cấp nội dung thay thế +cho cấu hình mà bạn đang xử lý. Ví dụ, do đối tượng {@link +android.content.res.Resources} nay đã được cập nhật, bạn có thể đặt lại +bất kỳ{@link android.widget.ImageView} nào với {@link android.widget.ImageView#setImageResource(int) +setImageResource()} +và tài nguyên phù hợp cho cấu hình mới được sử dụng (như được mô tả trong phần Cung cấp Tài nguyên).

+ +

Lưu ý rằng giá trị từ các trường {@link +android.content.res.Configuration} là những số nguyên khớp với hằng số cụ thể +từ lớp {@link android.content.res.Configuration}. Đối với tài liệu về những hằng số +cần sử dụng với mỗi trường, hãy tham khảo trường phù hợp trong tham chiếu {@link +android.content.res.Configuration}.

+ +

Hãy ghi nhớ: Khi bạn khai báo hoạt động của mình để xử lý một +thay đổi cấu hình, bạn có trách nhiệm đặt lại bất kỳ phần tử nào mà bạn cung cấp nội dung thay thế cho. Nếu bạn +khai báo hoạt động của mình để xử lý thay đổi hướng và có những hình ảnh nên thay đổi +giữa khổ ngang và hướng dọc, bạn phải gán lại từng tài nguyên cho từng phần tử trong khi {@link +android.app.Activity#onConfigurationChanged(Configuration) onConfigurationChanged()}.

+ +

Nếu bạn không cần cập nhật ứng dụng của mình dựa trên những thay đổi +cấu hình này, thay vào đó bạn có thể không triển khai {@link +android.app.Activity#onConfigurationChanged(Configuration) onConfigurationChanged()}. Trong +trường hợp đó, tất cả tài nguyên được sử dụng trước khi thay đổi cấu hình sẽ vẫn được sử dụng +và bạn chỉ mới tránh được việc khởi động lại hoạt động của mình. Tuy nhiên, ứng dụng của bạn cần luôn có khả năng +tắt và khởi động lại với trạng thái trước đó của nó được giữ nguyên, vì thế bạn không nên coi +kỹ thuật này như một cách để thoát khỏi việc giữ lại trạng thái của mình trong vòng đời của hoạt động bình thường. Không chỉ bởi +có những thay đổi cấu hình khác mà bạn không thể ngăn không cho khởi động lại ứng dụng của mình, mà +cả bởi vì bạn nên xử lý những sự kiện như là khi người dùng rời khỏi ứng dụng của bạn và nó bị +hủy trước khi người dùng quay lại.

+ +

Để biết thêm về những thay đổi cấu hình nào mà bạn có thể xử lý trong hoạt động của mình, hãy xem tài liệu {@code +android:configChanges} và lớp {@link android.content.res.Configuration} +.

diff --git a/docs/html-intl/intl/vi/guide/topics/ui/controls.jd b/docs/html-intl/intl/vi/guide/topics/ui/controls.jd new file mode 100644 index 0000000000000000000000000000000000000000..37fe81c8c73a89e06789a0fcae950f4b085f1283 --- /dev/null +++ b/docs/html-intl/intl/vi/guide/topics/ui/controls.jd @@ -0,0 +1,90 @@ +page.title=Điều khiển Nhập liệu +parent.title=Giao diện Người dùng +parent.link=index.html +@jd:body + +
+ +
+ +

Điều khiển nhập liệu là những thành phần tương tác trong giao diện người dùng của ứng dụng của bạn. Android cung cấp +nhiều kiểu điều khiển bạn có thể sử dụng trong UI của mình, chẳng hạn như nút, trường văn bản, thanh tìm kiếm, +hộp kiểm, nút thu phóng, nút bật tắt, và nhiều kiểu khác.

+ +

Thêm một điều khiển nhập liệu vào UI của bạn cũng đơn giản như thêm một phần tử XML vào bố trí XML của bạn. Ví dụ, đây là một bố trí +với một trường văn bản và nút:

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    android:orientation="horizontal">
+    <EditText android:id="@+id/edit_message"
+        android:layout_weight="1"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:hint="@string/edit_message" />
+    <Button android:id="@+id/button_send"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/button_send"
+        android:onClick="sendMessage" />
+</LinearLayout>
+
+ +

Mỗi điều khiển nhập liệu hỗ trợ một tập hợp sự kiện nhập liệu cụ thể để bạn có thể xử lý các sự kiện chẳng hạn như khi +người dùng nhập văn bản hoặc chạm vào một nút.

+ + +

Điều khiển Thông dụng

+

Sau đây là danh sách những điều khiển thông dụng mà bạn có thể sử dụng trong ứng dụng của mình. Theo dõi các liên kết để tìm +hiểu thêm về việc sử dụng từng điều khiển.

+ +

Lưu ý: Android cung cấp nhiều điều khiển hơn một chút so với liệt kê ở +đây. Duyệt gói {@link android.widget} để khám phá thêm. Nếu ứng dụng của bạn yêu cầu một +kiểu điều khiển nhập liệu cụ thể, bạn có thể xây dựng các thành phần tùy chỉnh của chính mình.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Kiểu Điều khiểnMô tảLớp Liên quan
NútNút nhấn có thể được nhấn, hoặc nhấp vào, bởi người dùng để thực hiện một hành động.{@link android.widget.Button Button}
Trường văn bảnTrường văn bản có thể chỉnh sửa. Bạn có thể sử dụng widget AutoCompleteTextView để tạo một widget mục nhập văn bản nhằm cung cấp các gợi ý tự động hoàn thành{@link android.widget.EditText EditText}, {@link android.widget.AutoCompleteTextView}
Hộp kiểmMột công tắc bật/tắt mà có thể được chuyển đổi bởi người dùng. Bạn nên sử dụng các hộp kiểm khi trình bày cho người dùng một nhóm các tùy chọn có thể chọn mà không loại trừ lẫn nhau.{@link android.widget.CheckBox CheckBox}
Nút chọn mộtTương tự như hộp kiểm, chỉ khác ở chỗ chỉ có thể chọn một tùy chọn trong nhóm.{@link android.widget.RadioGroup RadioGroup} +
{@link android.widget.RadioButton RadioButton}
Nút bật tắtMột nút bật/tắt có đèn chỉ báo.{@link android.widget.ToggleButton ToggleButton}
Quay trònMột danh sách thả xuống cho phép người dùng chọn một giá trị từ một tập hợp.{@link android.widget.Spinner Spinner}
Bộ chọnMột hộp thoại cho người dùng chọn một giá trị đơn lẻ cho một tập hợp bằng cách sử dụng các nút lên/xuống hoặc thông qua cử chỉ trượt nhanh. Sử dụng một widget DatePicker để nhập giá trị cho ngày (tháng, ngày, năm) hoặc một widget TimePicker để nhập giá trị cho thời gian (giờ, phút, Sáng/Chiều tối) mà sẽ được định dạng tự động theo bản địa của người dùng.{@link android.widget.DatePicker}, {@link android.widget.TimePicker}
diff --git a/docs/html-intl/intl/vi/guide/topics/ui/declaring-layout.jd b/docs/html-intl/intl/vi/guide/topics/ui/declaring-layout.jd new file mode 100644 index 0000000000000000000000000000000000000000..6add812d89f7d5192356da81754c559c877d1e30 --- /dev/null +++ b/docs/html-intl/intl/vi/guide/topics/ui/declaring-layout.jd @@ -0,0 +1,492 @@ +page.title=Bố trí +page.tags=dạng xem, nhóm dạng xem +@jd:body + + + +

Bố trí định nghĩa cấu trúc hiển thị cho một giao diện người dùng, chẳng hạn như UI cho một hoạt động hoặc widget ứng dụng. +Bạn có thể khai báo một bố trí bằng hai cách:

+
    +
  • Khai báo phần tử UI trong XML. Android cung cấp một kho từ vựng XML +đơn giản, tương ứng với các lớp và lớp con Dạng xem, chẳng hạn như dành cho các widget và bố trí.
  • +
  • Khởi tạo các phần tử bố trí vào thời gian chạy. Ứng dụng +của bạn có thể tạo các đối tượng Dạng xem và Nhóm Dạng xem (và thao tác trên các tính chất của nó) theo lập trình.
  • +
+ +

Khuôn khổ Android cho bạn sự linh hoạt trong khi sử dụng một hoặc cả hai phương pháp này để khai báo và quản lý UI ứng dụng của mình. Ví dụ, bạn có thể khai báo các bố trí mặc định cho ứng dụng của mình trong XML, bao gồm các phần tử màn hình mà sẽ xuất hiện trong chúng hoặc tính chất của chúng. Sau đó, bạn có thể thêm mã trong ứng dụng của mình để sửa đổi trạng thái của các đối tượng trên màn hình, bao gồm những đối tượng được khai báo trong XML, vào thời gian chạy.

+ + + +

Ưu điểm của việc khai báo UI của bạn trong XML là cho phép bạn tách việc trình bày ứng dụng của mình với mã điều khiển hành vi của nó hiệu quả hơn. Mô tả UI của bạn nằm ngoài mã ứng dụng của bạn, điều này có nghĩa rằng bạn có thể sửa đổi hoặc điều hợp nó mà không phải sửa đổi mã nguồn của bạn và biên dịch lại. Ví dụ, bạn có thể tạo bố trí XML cho các hướng màn hình khác nhau, kích cỡ màn hình khác nhau, và ngôn ngữ khác nhau. Ngoài ra, việc khai báo bố trí trong XML giúp dễ dàng hơn trong việc hiển thị cấu trúc UI của bạn, vì vậy sẽ dễ gỡ lỗi sự cố hơn. Như vậy, tài liệu này tập trung vào việc hướng dẫn bạn cách khai báo bố trí của mình trong XML. Nếu bạn +quan tâm tới việc khởi tạo các đối tượng Dạng xem vào thời gian chạy, hãy tham khảo {@link android.view.ViewGroup} và +tài liệu tham khảo lớp {@link android.view.View}.

+ +

Nhìn chung, kho từ vựng của XML đối với việc khai báo các phần tử UI tuân thủ chặt chẽ cấu trúc và cách đặt tên các lớp và phương pháp, trong đó các tên phần tử tương ứng với tên lớp và tên thuộc tính tương ứng với phương pháp. Trên thực tế, sự tương ứng thường trực tiếp đến mức bạn có thể đoán thuộc tính XML nào tương ứng với một phương pháp lớp, hoặc đoán xem lớp nào tương ứng với một phần tử XML cho trước. Tuy nhiên, lưu ý rằng không phải tất cả từ vựng đều giống nhau. Trong một số trường hợp, có sự khác biệt nhỏ trong việc đặt tên. Ví +dụ, phần tử EditText có thuộc tính text tương ứng với +EditText.setText().

+ +

Mẹo: Tìm hiểu thêm về các kiểu bố trí trong Đối tượng Bố trí +Thường gặp. Có một tuyển tập các bài hướng dẫn về việc xây dựng các bố trí khác nhau trong hướng dẫn bài học +Dạng xem Hello.

+ +

Ghi XML

+ +

Khi sử dụng từ vựng XML của Android, bạn có thể nhanh chóng thiết kế các bố trí UI và phần tử màn hình mà chúng chứa, giống như cách bạn tạo trang web trong HTML — bằng một loạt các phần tử lồng nhau.

+ +

Mỗi tệp bố trí phải chứa chính xác một phần tử gốc, đó phải là một đối tượng Dạng xem hoặc Nhóm Dạng xem. Sau khi đã định nghĩa phần tử gốc, bạn có thể thêm các đối tượng hoặc widget bố trí bổ sung làm phần tử con để dần dần xây dựng một phân cấp Dạng xem định nghĩa bố trí của bạn. Ví dụ, sau đây là một bố trí XML sử dụng một {@link android.widget.LinearLayout} +thẳng đứng để giữ một {@link android.widget.TextView} và một {@link android.widget.Button}:

+
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:layout_width="match_parent"
+              android:layout_height="match_parent"
+              android:orientation="vertical" >
+    <TextView android:id="@+id/text"
+              android:layout_width="wrap_content"
+              android:layout_height="wrap_content"
+              android:text="Hello, I am a TextView" />
+    <Button android:id="@+id/button"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Hello, I am a Button" />
+</LinearLayout>
+
+ +

Sau khi bạn đã khai báo bố trí của mình trong XML, hãy lưu tệp với phần mở rộng .xml, +trong thư mục dự án res/layout/ Android của bạn để biên dịch cho phù hợp.

+ +

Thông tin về cú pháp đối với tệp XML bố trí có sẵn trong tài liệu Tài nguyên Bố trí.

+ +

Nạp Tài nguyên XML

+ +

Khi bạn biên dịch ứng dụng của mình, từng tệp bố trí XML được biên dịch thành một tài nguyên +{@link android.view.View}. Bạn nên nạp tài nguyên bố trí từ mã ứng dụng của mình, trong triển khai gọi lại +{@link android.app.Activity#onCreate(android.os.Bundle) Activity.onCreate()} của bạn. +Làm vậy bằng cách gọi {@link android.app.Activity#setContentView(int) setContentView()}, +chuyển cho nó tham chiếu tới tài nguyên bố trí của bạn dưới hình thức: +R.layout.layout_file_name. +Ví dụ, nếu bố trí XML của bạn được lưu thành main_layout.xml, bạn sẽ nạp nó +cho Hoạt động của mình như sau:

+
+public void onCreate(Bundle savedInstanceState) {
+    super.onCreate(savedInstanceState);
+    setContentView(R.layout.main_layout);
+}
+
+ +

Phương pháp gọi lại onCreate() trong Hoạt động của bạn được gọi bởi khuôn khổ Android khi +Hoạt động của bạn được khởi chạy (xem phần thảo luận về vòng đời, trong tài liệu +Hoạt động +).

+ + +

Thuộc tính

+ +

Mọi đối tượng Dạng xem và Nhóm Dạng xem đều hỗ trợ các phiên bản thuộc tính XML của chính mình. +Một số thuộc tính áp dụng riêng cho đối tượng Dạng xem (ví dụ, TextView hỗ trợ thuộc tính textSize +), nhưng những thuộc tính này cũng được kế thừa bởi bất kỳ đối tượng Dạng xem nào mà có thể mở rộng lớp này. +Một số được áp dụng chung cho tất cả đối tượng Dạng xem vì chúng được kế thừa từ lớp Dạng xem gốc (như +thuộc tính id). Và những thuộc tính còn lại được coi là "tham số bố trí," đó là những thuộc tính +mô tả một số hướng bố trí nhất định của đối tượng Dạng xem, như được định nghĩa bởi đối tượng Nhóm Dạng xem +mẹ của đối tượng đó.

+ +

ID

+ +

Bất kỳ đối tượng Dạng xem nào cũng có một ID số nguyên được liên kết với nó để nhận biết duy nhất Dạng xem trong cây. +Khi ứng dụng được biên dịch, ID này được tham chiếu như một số nguyên, nhưng ID thường +được gán trong tệp XML bố trí như một xâu, trong thuộc tính id. +Đây là thuộc tính XML chung cho tất cả đối tượng Dạng xem +(được định nghĩa theo lớp {@link android.view.View}) và bạn sẽ rất hay sử dụng nó. +Cú pháp đối với một ID bên trong một tag XML là:

+
android:id="@+id/my_button"
+ +

Biểu tượng "a móc" (@) ở đầu xâu thể hiện rằng trình phân tích XML nên phân tích và mở rộng phần còn lại +của xâu ID và nhận biết nó như một tài nguyên ID. Biểu tượng dấu cộng (+) có nghĩa rằng đây là một tên tài nguyên mới mà phải +được tạo và thêm vào tài nguyên của chúng ta (trong tệp R.java). Có nhiều tài nguyên ID khác +được cung cấp bởi khuôn khổ Android. Khi tham chiếu một ID tài nguyên Android, bạn không cần biểu tượng dấu cộng, +nhưng phải thêm vùng tên gói android, như sau:

+
android:id="@android:id/empty"
+

Khi đã có vùng tên gói android, giờ chúng ta đang tham chiếu một ID từ lớp tài nguyên android.R +, thay vì lớp tài nguyên cục bộ.

+ +

Để tạo các dạng xem và tham chiếu chúng từ ứng dụng, một mô thức thường thấy đó là:

+
    +
  1. Định nghĩa một dạng xem/widget trong tệp bố trí và gán cho nó một ID duy nhất: +
    +<Button android:id="@+id/my_button"
    +        android:layout_width="wrap_content"
    +        android:layout_height="wrap_content"
    +        android:text="@string/my_button_text"/>
    +
    +
  2. +
  3. Sau đó, tạo một thực thể của đối tượng dạng xem và chụp nó từ bố trí +(thường trong phương pháp {@link android.app.Activity#onCreate(Bundle) onCreate()}): +
    +Button myButton = (Button) findViewById(R.id.my_button);
    +
    +
  4. +
+

Định nghĩa các ID cho đối tượng dạng xem là việc quan trọng khi tạo một {@link android.widget.RelativeLayout}. +Trong một bố trí tương đối, các dạng xem đồng hạng có thể định nghĩa bố trí của nó so với dạng xem đồng hạng kia, +dạng xem mà được tham chiếu bởi ID duy nhất.

+

Một ID không cần phải là duy nhất trong toàn bộ cây, nhưng nên là +duy nhất trong bộ phận của cây mà bạn đang tìm kiếm (thường là toàn bộ cây, vì thế tốt nhất là +ID nên hoàn toàn duy nhất khi có thể).

+ + +

Tham số Bố trí

+ +

Các thuộc tính bố trí XML layout_something sẽ định nghĩa +các tham số bố trí cho Dạng xem phù hợp với Nhóm Dạng xem mà nó nằm trong đó.

+ +

Mọi lớp Nhóm Dạng xem đều triển khai một lớp lồng nhau có chức năng mở rộng {@link +android.view.ViewGroup.LayoutParams}. Lớp con này +chứa các kiểu tính chất mà định nghĩa kích cỡ và vị trí của từng dạng xem con cho +phù hợp với nhóm dạng xem. Như bạn có thể thấy trong hình 1, nhóm dạng xem +mẹ sẽ định nghĩa các tham số bố trí cho từng dạng xem con (bao gồm nhóm dạng xem con).

+ + +

Hình 1. Trực quan hóa một phân cấp dạng xem với các tham số +bố trí được liên kết với từng dạng xem.

+ +

Để ý rằng mọi lớp con LayoutParams đều có cú pháp riêng của mình cho các giá trị +thiết đặt. Mỗi phần tử con phải định nghĩa LayoutParams cho phù hợp với phần tử mẹ của nó, +mặc dù cũng có thể định nghĩa LayoutParams khác cho phần tử con của chính nó.

+ +

Tất cả nhóm dạng xem đều có chiều rộng và chiều cao (layout_width và +layout_height), và mỗi dạng xem đều phải định nghĩa chúng. Nhiều +LayoutParams cũng có lề và viền tùy chọn.

+ +

Bạn có thể chỉ định chiều rộng và chiều cao bằng các số đo chính xác, mặc dù có thể +bạn sẽ không muốn làm điều này thường xuyên. Bạn sẽ thường sử dụng một trong những hằng số này để +đặt chiều rộng hoặc chiều cao:

+ +
    +
  • wrap_content cho biết dạng xem của bạn tự định cỡ theo kích thước +mà nội dung của nó yêu cầu.
  • +
  • match_parent (được đặt tên fill_parent trước API Mức 8) +cho biết dạng xem của bạn có thể phóng lớn khi nhóm dạng xem mẹ của nó cho phép.
  • +
+ +

Nhìn chung, việc chỉ định một chiều rộng và chiều cao bố trí bằng cách sử dụng các đơn vị tuyệt đối như +điểm ảnh là điều không được khuyến cáo. Thay vào đó, sử dụng các số đo tương đối như +số đơn vị điểm ảnh độc lập với mật độ (dp), wrap_content, hoặc +match_parent, là một phương pháp tốt hơn, vì nó giúp đảm bảo rằng +ứng dụng của bạn sẽ hiển thị phù hợp giữa nhiều loại kích cỡ màn hình thiết bị khác nhau. +Các kiểu số đo được chấp nhận được định nghĩa trong tài liệu + +Tài nguyên Có sẵn.

+ + +

Vị trí Bố trí

+

+ Hình học của một dạng xem là hình chữ nhật. Dạng xem có một vị trí, + được biểu diễn thành một cặp tọa độ tráitrên và + hai kích thước, được biểu diễn thành chiều rộng và chiều cao. Đơn vị của vị trí + và kích thước là điểm ảnh. +

+ +

+ Có thể truy xuất vị trí của một dạng xem bằng cách gọi ra các phương pháp + {@link android.view.View#getLeft()} và {@link android.view.View#getTop()}. Phương pháp đầu trả về tọa độ trái, hay X, + của hình chữ nhật biểu diễn dạng xem. Phương pháp sau trả về tọa độ trên, hay Y, + của hình chữ nhật biểu diễn dạng xem. Những phương pháp này + đều trả về vị trí của dạng xem so với dạng xem mẹ của nó. Ví dụ, + khi getLeft() trả về 20, điều đó có nghĩa là dạng xem nằm ở 20 điểm ảnh về + bên phải của cạnh trái của dạng xem mẹ trực tiếp của nó. +

+ +

+ Ngoài ra, một vài phương pháp thuận tiện được đưa ra để tránh những tính toán + không cần thiết, cụ thể là {@link android.view.View#getRight()} và {@link android.view.View#getBottom()}. + Những phương pháp này trả về tọa độ của cạnh phải và cạnh đáy của hình chữ nhật + biểu diễn dạng xem. Ví dụ, việc gọi {@link android.view.View#getRight()} + tương tự như tính toán sau: getLeft() + getWidth(). +

+ + +

Kích cỡ, Phần đệm và Lề

+

+ Kích cỡ của một dạng xem được biểu diễn bằng chiều rộng và chiều cao. Thực ra một dạng xem + sẽ có hai cặp giá trị chiều rộng và chiều cao. +

+ +

+ Cặp thứ nhất được gọi là chiều rộng đo được và + chiều cao đo được. Những kích thước này xác định một dạng xem muốn phóng lớn bao nhiêu + trong dạng xem mẹ của nó. Các + kích thước đo được có thể thu được bằng cách gọi {@link android.view.View#getMeasuredWidth()} + và {@link android.view.View#getMeasuredHeight()}. +

+ +

+ Cặp thứ hai đơn thuần là chiều rộngchiều cao, hoặc + đôi khi gọi là chiều rộng vẽchiều cao vẽ. Những + kích thước này xác định kích cỡ thực sự của dạng xem trên màn hình, tại thời điểm vẽ và + sau khi bố trí. Những giá trị này có thể nhưng không nhất thiết phải khác với + chiều rộng và chiều cao đo được. Chiều rộng và chiều cao này có thể lấy được bằng cách gọi + {@link android.view.View#getWidth()} và {@link android.view.View#getHeight()}. +

+ +

+ Để đo các kích thước của nó, dạng xem cần xét tới phần đệm của nó. Phần đệm + được biểu diễn bằng số điểm ảnh của phần bên trái, bên trên, bên phải và bên dưới của dạng xem. + Phần đệm có thể được sử dụng để bù trừ nội dung của dạng xem bằng một số điểm ảnh + cụ thể. Ví dụ, phần đệm bên trái bằng 2 sẽ đẩy nội dung của dạng xem đi + 2 điểm ảnh về bên phải của cạnh bên trái. Phần đệm có thể được đặt bằng cách sử dụng phương pháp + {@link android.view.View#setPadding(int, int, int, int)} và được truy vấn bằng cách gọi + {@link android.view.View#getPaddingLeft()}, {@link android.view.View#getPaddingTop()}, + {@link android.view.View#getPaddingRight()} và {@link android.view.View#getPaddingBottom()}. +

+ +

+ Mặc dù dạng xem có thể xác định phần đệm, nó không có bất kỳ sự hỗ trợ nào cho + lề. Tuy nhiên, các nhóm dạng xem lại cung cấp sự hỗ trợ như vậy. Tham khảo + {@link android.view.ViewGroup} và + {@link android.view.ViewGroup.MarginLayoutParams} để biết thêm thông tin. +

+ +

Để biết thêm thông tin về kích thước, xem + Giá trị Kích thước. +

+ + + + + + + + + + + +

Các Bố trí Thường gặp

+ +

Mỗi lớp con của lớp {@link android.view.ViewGroup} cung cấp một cách duy nhất để hiển thị +các dạng xem mà bạn lồng trong nó. Dưới đây là một số kiểu bố trí phổ biến hơn mà được tích hợp +trong nền tảng Android.

+ +

Lưu ý: Mặc dù bạn có thể lồng một hoặc nhiều bố trí với một +bố trí khác để đạt được thiết kế UI của mình, bạn nên cố gắng duy trì phân cấp bố trí của mình ở mức nông nhất +có thể. Bố trí của bạn sẽ vẽ nhanh hơn nếu nó có ít bố trí lồng nhau hơn (phân cấp dạng xem rộng +sẽ tốt hơn phân cấp dạng xem sâu).

+ + + + +
+

Bố trí Tuyến tính

+ +

Một bố trí có chức năng sắp xếp tổ chức các bố trí con của nó thành một hàng ngang hoặc thẳng đứng. Nó + sẽ tạo một thanh cuộn nếu chiều dài của cửa sổ vượt quá chiều dài của màn hình.

+
+ +
+

Bố trí Tương đối

+ +

Cho phép bạn chỉ định vị trí của các đối tượng con so với nhau (đối tượng con A về phía +bên trái của đối tượng con B) hoặc so với đối tượng mẹ (được căn theo bên trên đối tượng mẹ).

+
+ +
+

Dạng xem Web

+ +

Hiển thị trang web.

+
+ + + + +

Xây dựng Bố trí bằng một Trình điều hợp

+ +

Khi nội dung cho bố trí của bạn động hoặc chưa được xác định trước, bạn có thể sử dụng một bố trí có chức năng +tạo lớp con {@link android.widget.AdapterView} để đưa vào bố trí có dạng xem vào thời gian chạy. Một +lớp con của lớp {@link android.widget.AdapterView} sẽ sử dụng một {@link android.widget.Adapter} để +gắn kết dữ liệu với bố trí của nó. {@link android.widget.Adapter} đóng vai trò trung gian giữa nguồn +dữ liệu và bố trí {@link android.widget.AdapterView} —{@link android.widget.Adapter} +sẽ truy xuất dữ liệu (từ một nguồn chẳng hạn như một mảng hoặc truy vấn cơ sở dữ liệu) và chuyển từng mục nhập +thành một dạng xem có thể thêm vào bố trí {@link android.widget.AdapterView}.

+ +

Các bố trí phổ biến được hỗ trợ bởi trình điều hợp bao gồm:

+ +
+

Dạng xem Danh sách

+ +

Hiển thị một danh sách cột cuộn đơn.

+
+ +
+

Dạng xem Lưới

+ +

Hiển thị một lưới cuộn gồm nhiều hàng và cột.

+
+ + + +

Điền dữ liệu vào một dạng xem trình điều hợp

+ +

Bạn có thể đưa vào một {@link android.widget.AdapterView} chẳng hạn như {@link android.widget.ListView} hoặc +{@link android.widget.GridView} bằng cách gắn kết thực thể {@link android.widget.AdapterView} với một +{@link android.widget.Adapter}, nó truy xuất dữ liệu từ một nguồn bên ngoài và tạo một {@link +android.view.View} để biểu diễn từng mục nhập dữ liệu.

+ +

Android cung cấp một vài lớp con của {@link android.widget.Adapter} rất hữu ích cho việc +truy xuất các kiểu dữ liệu khác nhau và xây dựng dạng xem cho một {@link android.widget.AdapterView}. Hai +trình điều hợp phổ biến nhất là:

+ +
+
{@link android.widget.ArrayAdapter}
+
Sử dụng trình điều hợp này khi nguồn dữ liệu của bạn là một mảng. Theo mặc định, {@link +android.widget.ArrayAdapter} tạo một dạng xem cho mỗi mục mảng bằng cách gọi {@link +java.lang.Object#toString()} trên từng mục và đặt nội dung trong một {@link +android.widget.TextView}. +

Ví dụ, nếu bạn có một mảng xâu mà bạn muốn hiển thị trong một {@link +android.widget.ListView}, hãy khởi tạo một {@link android.widget.ArrayAdapter} mới bằng cách sử dụng +hàm dựng để chỉ định bố trí cho từng xâu và mảng xâu:

+
+ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
+        android.R.layout.simple_list_item_1, myStringArray);
+
+

Các tham đối cho hàm dựng này là:

+
    +
  • Ứng dụng của bạn {@link android.content.Context}
  • +
  • Bố trí chứa một {@link android.widget.TextView} cho mỗi xâu trong mảng
  • +
  • Mảng xâu
  • +
+

Sau đó chỉ cần gọi +{@link android.widget.ListView#setAdapter setAdapter()} trên {@link android.widget.ListView} của bạn:

+
+ListView listView = (ListView) findViewById(R.id.listview);
+listView.setAdapter(adapter);
+
+ +

Để tùy chỉnh diện mạo của từng mục, bạn có thể khống chế phương pháp {@link +java.lang.Object#toString()} cho các đối tượng trong mảng của mình. Hoặc, để tạo một dạng xem cho từng +mục không phải là một {@link android.widget.TextView} (ví dụ, nếu bạn muốn một +{@link android.widget.ImageView} cho từng mục mảng), hãy mở rộng lớp {@link +android.widget.ArrayAdapter} và khống chế {@link android.widget.ArrayAdapter#getView +getView()} để trả về kiểu dạng xem mà bạn muốn cho từng mục.

+ +
+ +
{@link android.widget.SimpleCursorAdapter}
+
Sử dụng trình điều hợp này khi dữ liệu của bạn đến từ một {@link android.database.Cursor}. Khi +sử dụng {@link android.widget.SimpleCursorAdapter}, bạn phải chỉ định một bố trí để sử dụng cho từng +hàng trong {@link android.database.Cursor} và những cột nào trong {@link android.database.Cursor} +nên được chèn vào dạng xem nào của bố trí. Ví dụ, nếu bạn muốn tạo một danh sách +tên người và số điện thoại, bạn có thể thực hiện một truy vấn mà trả về một {@link +android.database.Cursor} chứa một hàng cho từng người và nhiều cột cho các tên và +số điện thoại. Sau đó, bạn tạo một mảng xâu chỉ định những cột nào từ {@link +android.database.Cursor} mà bạn muốn trong bố trí cho từng kết quả và một mảng số nguyên chỉ định các +dạng xem tương ứng mà từng cột sẽ được đặt vào:

+
+String[] fromColumns = {ContactsContract.Data.DISPLAY_NAME,
+                        ContactsContract.CommonDataKinds.Phone.NUMBER};
+int[] toViews = {R.id.display_name, R.id.phone_number};
+
+

Khi bạn khởi tạo {@link android.widget.SimpleCursorAdapter}, hãy chuyển bố trí cần sử dụng cho +từng kết quả, {@link android.database.Cursor} chứa các kết quả, và hai mảng sau:

+
+SimpleCursorAdapter adapter = new SimpleCursorAdapter(this,
+        R.layout.person_name_and_number, cursor, fromColumns, toViews, 0);
+ListView listView = getListView();
+listView.setAdapter(adapter);
+
+

Sau đó, {@link android.widget.SimpleCursorAdapter} tạo một dạng xem cho từng hàng trong +{@link android.database.Cursor} sử dụng bố trí được cung cấp bằng cách chèn từng mục {@code +fromColumns} vào dạng xem {@code toViews} tương ứng.

.
+
+ + +

Trong vòng đời ứng dụng của bạn, nếu bạn thay đổi dữ liệu liên quan được đọc bởi +trình điều hợp của mình, bạn nên gọi {@link android.widget.ArrayAdapter#notifyDataSetChanged()}. Nó sẽ +thông báo với dạng xem đính kèm rằng dữ liệu đã được thay đổi và dạng xem nên tự làm mới.

+ + + +

Xử lý sự kiện nhấp

+ +

Bạn có thể phản hồi các sự kiện nhấp trên từng mục trong một {@link android.widget.AdapterView} bằng cách +triển khai giao diện {@link android.widget.AdapterView.OnItemClickListener}. Ví dụ:

+ +
+// Create a message handling object as an anonymous class.
+private OnItemClickListener mMessageClickedHandler = new OnItemClickListener() {
+    public void onItemClick(AdapterView parent, View v, int position, long id) {
+        // Do something in response to the click
+    }
+};
+
+listView.setOnItemClickListener(mMessageClickedHandler);
+
+ + + diff --git a/docs/html-intl/intl/vi/guide/topics/ui/dialogs.jd b/docs/html-intl/intl/vi/guide/topics/ui/dialogs.jd new file mode 100644 index 0000000000000000000000000000000000000000..1fa45508ef0c384df4fec3e132a7029ec840992e --- /dev/null +++ b/docs/html-intl/intl/vi/guide/topics/ui/dialogs.jd @@ -0,0 +1,798 @@ +page.title=Hộp thoại +page.tags=alertdialog,dialogfragment + +@jd:body + + + + + +

Hộp thoại là một cửa sổ nhỏ có chức năng nhắc người dùng +đưa ra một quyết định hoặc nhập thông tin bổ sung. Hộp thoại không lấp kín màn hình và +thường được sử dụng cho các sự kiện mô thái yêu cầu người dùng phải thực hiện một hành động trước khi có thể đi tiếp.

+ +
+

Thiết kế Hộp thoại

+

Để biết thông tin về cách thiết kế hộp thoại của bạn, bao gồm các đề xuất + về ngôn ngữ, hãy đọc hướng dẫn thiết kế Hộp thoại.

+
+ + + +

Lớp {@link android.app.Dialog} là lớp cơ sở cho hộp thoại, nhưng bạn +nên tránh khởi tạo {@link android.app.Dialog} một cách trực tiếp. +Thay vào đó, hãy sử dụng một trong các lớp con sau:

+
+
{@link android.app.AlertDialog}
+
Hộp thoại có thể hiển thị một tiêu đề, tối đa ba nút, một danh sách + các mục có thể chọn, hoặc một bố trí tùy chỉnh.
+
{@link android.app.DatePickerDialog} hoặc {@link android.app.TimePickerDialog}
+
Hộp thoại với một UI được xác định trước, cho phép người dùng chọn ngày hoặc giờ.
+
+ + + +

Những lớp này định nghĩa kiểu và cấu trúc cho hộp thoại của bạn, nhưng bạn nên +sử dụng một {@link android.support.v4.app.DialogFragment} làm bộ chứa cho hộp thoại của mình. +Lớp {@link android.support.v4.app.DialogFragment} sẽ cung cấp tất cả điều khiển mà +bạn cần để tạo hộp thoại của mình và quản lý diện mạo của hộp thoại, thay vì gọi ra các phương pháp +trên đối tượng {@link android.app.Dialog}.

+ +

Việc sử dụng {@link android.support.v4.app.DialogFragment} để quản lý hộp thoại +sẽ đảm bảo rằng nó xử lý đúng các sự kiện vòng đời +chẳng hạn như khi người dùng nhấn nút Quay lại hoặc xoay màn hình. Lớp {@link +android.support.v4.app.DialogFragment} cũng cho phép bạn sử dụng lại UI của hộp thoại như một +thành phần có thể nhúng trong một UI rộng hơn, giống như một {@link +android.support.v4.app.Fragment} truyền thống (chẳng hạn như khi bạn muốn UI hộp thoại xuất hiện khác đi +trên các màn hình lớn và nhỏ).

+ +

Các phần sau trong hướng dẫn này mô tả cách sử dụng {@link +android.support.v4.app.DialogFragment} kết hợp với một đối tượng {@link android.app.AlertDialog} +. Nếu muốn tạo một bộ chọn ngày hoặc giờ, thay vào đó, bạn nên đọc hướng dẫn +Bộ chọn.

+ +

Lưu ý: +Vì lớp {@link android.app.DialogFragment} ban đầu được bổ sung cùng với +Android 3.0 (API mức 11), tài liệu này mô tả cách sử dụng lớp {@link +android.support.v4.app.DialogFragment} được cung cấp kèm Thư viện Hỗ trợ. Bằng cách thêm thư viện này +vào ứng dụng của mình, bạn có thể sử dụng {@link android.support.v4.app.DialogFragment} và nhiều loại +API khác trên các thiết bị chạy Android 1.6 hoặc cao hơn. Nếu phiên bản tối thiểu mà ứng dụng của bạn hỗ trợ +là API mức 11 hoặc cao hơn, khi đó bạn có thể sử dụng phiên bản khuôn khổ của {@link +android.app.DialogFragment}, nhưng hãy chú ý rằng các liên kết trong tài liệu này dành cho các API +thư viện hỗ trợ. Khi sử dụng thư viện hỗ trợ, +hãy nhớ rằng bạn nhập lớp android.support.v4.app.DialogFragment +chứ không phải android.app.DialogFragment.

+ + +

Tạo một Phân đoạn Hộp thoại

+ +

Bạn có thể hoàn thành nhiều loại thiết kế hộp thoại—bao gồm +bố trí tùy chỉnh và những bố trí được mô tả trong hướng dẫn thiết kế Hộp thoại +—bằng cách mở rộng +{@link android.support.v4.app.DialogFragment} và tạo một {@link android.app.AlertDialog} +trong phương pháp gọi lại {@link android.support.v4.app.DialogFragment#onCreateDialog +onCreateDialog()}.

+ +

Ví dụ, sau đây là một {@link android.app.AlertDialog} cơ bản được quản lý bên trong +một {@link android.support.v4.app.DialogFragment}:

+ +
+public class FireMissilesDialogFragment extends DialogFragment {
+    @Override
+    public Dialog onCreateDialog(Bundle savedInstanceState) {
+        // Use the Builder class for convenient dialog construction
+        AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+        builder.setMessage(R.string.dialog_fire_missiles)
+               .setPositiveButton(R.string.fire, new DialogInterface.OnClickListener() {
+                   public void onClick(DialogInterface dialog, int id) {
+                       // FIRE ZE MISSILES!
+                   }
+               })
+               .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
+                   public void onClick(DialogInterface dialog, int id) {
+                       // User cancelled the dialog
+                   }
+               });
+        // Create the AlertDialog object and return it
+        return builder.create();
+    }
+}
+
+ +
+ +

Hình 1. +Hộp thoại với một thông báo và hai nút hành động.

+
+ +

Lúc này, khi bạn tạo một thực thể thuộc lớp này và gọi {@link +android.support.v4.app.DialogFragment#show show()} trên đối tượng đó, hộp thoại sẽ xuất hiện +như minh họa trong hình 1.

+ +

Phần tiếp theo mô tả thêm về việc sử dụng các API {@link android.app.AlertDialog.Builder} +để tạo hộp thoại.

+ +

Tùy vào độ phức tạp của hộp thoại của bạn, bạn có thể triển khai nhiều loại phương pháp gọi lại khác +trong {@link android.support.v4.app.DialogFragment}, bao gồm tất cả +phương pháp vòng đời phân đoạn cơ bản. + + + + + +

Xây dựng một Hộp thoại Cảnh báo

+ + +

Lớp {@link android.app.AlertDialog} cho phép bạn xây dựng nhiều loại thiết kế hộp thoại và +thường là lớp hộp thoại duy nhất mà bạn sẽ cần. +Như được minh họa trong hình 2, có ba vùng trên một hộp thoại cảnh báo:

+ +
+ +

Hình 2. Bố trí của một hộp thoại.

+
+ +
    +
  1. Tiêu đề +

    Tiêu đề không bắt buộc và chỉ nên được sử dụng khi vùng nội dung + bị chiếm bởi một thông báo chi tiết, một danh sách, hay một bố trí tùy chỉnh. Nếu bạn cần nêu + một thông báo hoặc câu hỏi đơn giản (chẳng hạn như hộp thoại trong hình 1), bạn không cần tiêu đề.

  2. +
  3. Vùng nội dung +

    Vùng này có thể hiển thị một thông báo, danh sách, hay bố trí tùy chỉnh khác.

  4. +
  5. Nút hành động +

    Sẽ không có quá ba nút hành động trong một hộp thoại.

  6. +
+ +

Lớp {@link android.app.AlertDialog.Builder} +cung cấp các API cho phép bạn tạo một {@link android.app.AlertDialog} +với những kiểu nội dung này, bao gồm một bố trí tùy chỉnh.

+ +

Để xây dựng một {@link android.app.AlertDialog}:

+ +
+// 1. Instantiate an {@link android.app.AlertDialog.Builder} with its constructor
+AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+
+// 2. Chain together various setter methods to set the dialog characteristics
+builder.setMessage(R.string.dialog_message)
+       .setTitle(R.string.dialog_title);
+
+// 3. Get the {@link android.app.AlertDialog} from {@link android.app.AlertDialog.Builder#create()}
+AlertDialog dialog = builder.create();
+
+ +

Các chủ đề sau cho biết cách định nghĩa các thuộc tính hộp thoại khác nhau bằng cách +sử dụng lớp {@link android.app.AlertDialog.Builder}.

+ + + + +

Thêm nút

+ +

Để thêm các nút hành động như trong hình 2, +hãy gọi các phương pháp {@link android.app.AlertDialog.Builder#setPositiveButton setPositiveButton()} và +{@link android.app.AlertDialog.Builder#setNegativeButton setNegativeButton()}:

+ +
+AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+// Add the buttons
+builder.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
+           public void onClick(DialogInterface dialog, int id) {
+               // User clicked OK button
+           }
+       });
+builder.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
+           public void onClick(DialogInterface dialog, int id) {
+               // User cancelled the dialog
+           }
+       });
+// Set other dialog properties
+...
+
+// Create the AlertDialog
+AlertDialog dialog = builder.create();
+
+ +

Các phương pháp set...Button() yêu cầu một tiêu đề cho nút (được cung cấp +bởi một tài nguyên xâu) và một +{@link android.content.DialogInterface.OnClickListener} có chức năng định nghĩa hành động sẽ tiến hành +khi người dùng nhấn nút.

+ +

Có ba nút hành động khác nhau mà bạn có thể thêm:

+
+
Tích cực
+
Bạn nên sử dụng nút này để chấp nhận và tiếp tục với hành động (hành động "OK").
+
Tiêu cực
+
Bạn nên sử dụng nút này để hủy bỏ hành động.
+
Trung lập
+
Bạn nên sử dụng nút này khi người dùng có thể không muốn tiếp tục với hành động, + nhưng không hẳn muốn hủy bỏ. Nó nằm ở giữa nút + tích cực và tiêu cực. Ví dụ, hành động có thể là "Nhắc tôi sau."
+
+ +

Bạn chỉ có thể thêm một nút mỗi loại vào một {@link +android.app.AlertDialog}. Nghĩa là, bạn không thể có nhiều hơn một nút "tích cực".

+ + + +
+ +

Hình 3. +Hộp thoại có tiêu đề và danh sách.

+
+ +

Thêm một danh sách

+ +

Có ba loại danh sách có sẵn với các API {@link android.app.AlertDialog}:

+
    +
  • Danh sách một lựa chọn truyền thống
  • +
  • Danh sách một lựa chọn cố định (nút chọn một)
  • +
  • Danh sách nhiều lựa chọn cố định (hộp kiểm)
  • +
+ +

Để tạo danh sách một lựa chọn như danh sách trong hình 3, +hãy sử dụng phương pháp {@link android.app.AlertDialog.Builder#setItems setItems()}:

+ +
+@Override
+public Dialog onCreateDialog(Bundle savedInstanceState) {
+    AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+    builder.setTitle(R.string.pick_color)
+           .setItems(R.array.colors_array, new DialogInterface.OnClickListener() {
+               public void onClick(DialogInterface dialog, int which) {
+               // The 'which' argument contains the index position
+               // of the selected item
+           }
+    });
+    return builder.create();
+}
+
+ +

Vì danh sách xuất hiện trong vùng nội dung của hộp thoại, +hộp thoại không thể hiển thị cả thông báo và danh sách và bạn nên đặt một tiêu đề cho hộp thoại +bằng {@link android.app.AlertDialog.Builder#setTitle setTitle()}. +Để chỉ định các mục cho danh sách, hãy gọi {@link +android.app.AlertDialog.Builder#setItems setItems()}, chuyển một mảng. +Hoặc, bạn có thể chỉ định một danh sách bằng cách sử dụng {@link +android.app.AlertDialog.Builder#setAdapter setAdapter()}. Điều này cho phép bạn hỗ trợ danh sách +bằng dữ liệu động (chẳng hạn như từ một cơ sở dữ liệu) bằng cách sử dụng {@link android.widget.ListAdapter}.

+ +

Nếu bạn chọn hỗ trợ danh sách của mình bằng một {@link android.widget.ListAdapter}, +hãy luôn sử dụng {@link android.support.v4.content.Loader} sao cho nội dung tải +không đồng bộ. Điều này được mô tả thêm trong hướng dẫn +Xây dựng Bố trí +bằng một Trình điều hợpTrình tải +.

+ +

Lưu ý: Theo mặc định, chạm vào một mục danh sách sẽ bỏ hộp thoại, +trừ khi bạn đang sử dụng một trong các danh sách lựa chọn cố định sau.

+ +
+ +

Hình 4. +Danh sách nhiều mục lựa chọn.

+
+ + +

Thêm một danh sách nhiều lựa chọn hoặc một lựa chọn cố định

+ +

Để thêm một danh sách nhiều lựa chọn (hộp kiểm) hoặc +một lựa chọn (nút chọn một), hãy sử dụng các phương pháp +{@link android.app.AlertDialog.Builder#setMultiChoiceItems(Cursor,String,String, +DialogInterface.OnMultiChoiceClickListener) setMultiChoiceItems()} hoặc +{@link android.app.AlertDialog.Builder#setSingleChoiceItems(int,int,DialogInterface.OnClickListener) +setSingleChoiceItems()} tương ứng.

+ +

Ví dụ, sau đây là cách bạn có thể tạo một danh sách nhiều lựa chọn như +danh sách được minh họa trong hình 4 giúp lưu các mục +được chọn trong một {@link java.util.ArrayList}:

+ +
+@Override
+public Dialog onCreateDialog(Bundle savedInstanceState) {
+    mSelectedItems = new ArrayList();  // Where we track the selected items
+    AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+    // Set the dialog title
+    builder.setTitle(R.string.pick_toppings)
+    // Specify the list array, the items to be selected by default (null for none),
+    // and the listener through which to receive callbacks when items are selected
+           .setMultiChoiceItems(R.array.toppings, null,
+                      new DialogInterface.OnMultiChoiceClickListener() {
+               @Override
+               public void onClick(DialogInterface dialog, int which,
+                       boolean isChecked) {
+                   if (isChecked) {
+                       // If the user checked the item, add it to the selected items
+                       mSelectedItems.add(which);
+                   } else if (mSelectedItems.contains(which)) {
+                       // Else, if the item is already in the array, remove it 
+                       mSelectedItems.remove(Integer.valueOf(which));
+                   }
+               }
+           })
+    // Set the action buttons
+           .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
+               @Override
+               public void onClick(DialogInterface dialog, int id) {
+                   // User clicked OK, so save the mSelectedItems results somewhere
+                   // or return them to the component that opened the dialog
+                   ...
+               }
+           })
+           .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
+               @Override
+               public void onClick(DialogInterface dialog, int id) {
+                   ...
+               }
+           });
+
+    return builder.create();
+}
+
+ +

Mặc dù cả danh sách truyền thống và danh sách có nút chọn một +đều cung cấp hành động "một lựa chọn", bạn nên sử dụng {@link +android.app.AlertDialog.Builder#setSingleChoiceItems(int,int,DialogInterface.OnClickListener) +setSingleChoiceItems()} nếu bạn muốn cố định lựa chọn của người dùng. +Cụ thể, nếu việc mở hộp thoại lại sau này báo hiệu lựa chọn hiện tại của người dùng, khi đó +bạn hãy tạo một danh sách với các nút chọn một.

+ + + + + +

Tạo một Bố trí Tùy chỉnh

+ +
+ +

Hình 5. Một bố trí hộp thoại tùy chỉnh.

+
+ +

Nếu bạn muốn một bố trí tùy chỉnh trong một hộp thoại, hãy tạo một bố trí và thêm nó vào một +{@link android.app.AlertDialog} bằng cách gọi {@link +android.app.AlertDialog.Builder#setView setView()} trên đối tượng {@link +android.app.AlertDialog.Builder} của bạn.

+ +

Theo mặc định, bố trí tùy chỉnh sẽ lấp đầy cửa sổ hộp thoại, nhưng bạn vẫn có thể +sử dụng các phương pháp {@link android.app.AlertDialog.Builder} để thêm nút và tiêu đề.

+ +

Ví dụ, sau đây là tệp bố trí cho hộp thoại trong Hình 5:

+ +

res/layout/dialog_signin.xml

+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content">
+    <ImageView
+        android:src="@drawable/header_logo"
+        android:layout_width="match_parent"
+        android:layout_height="64dp"
+        android:scaleType="center"
+        android:background="#FFFFBB33"
+        android:contentDescription="@string/app_name" />
+    <EditText
+        android:id="@+id/username"
+        android:inputType="textEmailAddress"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="16dp"
+        android:layout_marginLeft="4dp"
+        android:layout_marginRight="4dp"
+        android:layout_marginBottom="4dp"
+        android:hint="@string/username" />
+    <EditText
+        android:id="@+id/password"
+        android:inputType="textPassword"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="4dp"
+        android:layout_marginLeft="4dp"
+        android:layout_marginRight="4dp"
+        android:layout_marginBottom="16dp"
+        android:fontFamily="sans-serif"
+        android:hint="@string/password"/>
+</LinearLayout>
+
+ +

Mẹo: Theo mặc định, khi bạn đặt một phần tử {@link android.widget.EditText} +để sử dụng kiểu đầu vào {@code "textPassword"}, họ phông được đặt thành đơn cách, vì thế +bạn nên đổi họ phông thành {@code "sans-serif"} sao cho cả hai trường văn bản đều sử dụng +một kiểu phông thống nhất.

+ +

Để bung bố trí ra trong {@link android.support.v4.app.DialogFragment} của bạn, +hãy lấy một {@link android.view.LayoutInflater} với +{@link android.app.Activity#getLayoutInflater()} và gọi +{@link android.view.LayoutInflater#inflate inflate()}, trong đó tham số đầu tiên +là ID tài nguyên bố trí và tham số thứ hai là một dạng xem mẹ cho bố trí. +Khi đó, bạn có thể gọi {@link android.app.AlertDialog#setView setView()} +để đặt bố trí vào một hộp thoại.

+ +
+@Override
+public Dialog onCreateDialog(Bundle savedInstanceState) {
+    AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+    // Get the layout inflater
+    LayoutInflater inflater = getActivity().getLayoutInflater();
+
+    // Inflate and set the layout for the dialog
+    // Pass null as the parent view because its going in the dialog layout
+    builder.setView(inflater.inflate(R.layout.dialog_signin, null))
+    // Add action buttons
+           .setPositiveButton(R.string.signin, new DialogInterface.OnClickListener() {
+               @Override
+               public void onClick(DialogInterface dialog, int id) {
+                   // sign in the user ...
+               }
+           })
+           .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
+               public void onClick(DialogInterface dialog, int id) {
+                   LoginDialogFragment.this.getDialog().cancel();
+               }
+           });      
+    return builder.create();
+}
+
+ +
+

Mẹo: Nếu bạn muốn một hộp thoại tùy chỉnh, +thay vào đó, bạn có thể hiển thị {@link android.app.Activity} như là một hộp thoại +thay vì sử dụng các API {@link android.app.Dialog}. Chỉ cần tạo một hoạt động và đặt chủ đề của nó thành +{@link android.R.style#Theme_Holo_Dialog Theme.Holo.Dialog} +trong phần tử bản kê khai {@code +<activity>}:

+ +
+<activity android:theme="@android:style/Theme.Holo.Dialog" >
+
+

Vậy là xong. Lúc này, hoạt động sẽ hiển thị một cửa sổ hộp thoại thay vì toàn màn hình.

+
+ + + +

Chuyển Sự kiện lại Máy chủ của Hộp thoại

+ +

Khi người dùng chạm vào một trong các nút hành động của hộp thoại hoặc chọn một mục từ danh sách của hộp thoại, +{@link android.support.v4.app.DialogFragment} của bạn có thể tự thực hiện hành động +cần thiết, nhưng thường thì bạn sẽ muốn chuyển sự kiện tới hoạt động hoặc phân đoạn +đã mở hộp thoại. Để làm điều này, hãy định nghĩa một giao diện bằng một phương pháp cho mỗi loại sự kiện nhấp. +Sau đó, triển khai giao diện đó trong thành phần chủ mà sẽ +nhận sự kiện hành động từ hộp thoại.

+ +

Ví dụ, sau đây là một {@link android.support.v4.app.DialogFragment} có chức năng định nghĩa một +giao diện mà thông qua đó, nó sẽ chuyển các sự kiện lại cho hoạt động chủ:

+ +
+public class NoticeDialogFragment extends DialogFragment {
+    
+    /* The activity that creates an instance of this dialog fragment must
+     * implement this interface in order to receive event callbacks.
+     * Each method passes the DialogFragment in case the host needs to query it. */
+    public interface NoticeDialogListener {
+        public void onDialogPositiveClick(DialogFragment dialog);
+        public void onDialogNegativeClick(DialogFragment dialog);
+    }
+    
+    // Use this instance of the interface to deliver action events
+    NoticeDialogListener mListener;
+    
+    // Override the Fragment.onAttach() method to instantiate the NoticeDialogListener
+    @Override
+    public void onAttach(Activity activity) {
+        super.onAttach(activity);
+        // Verify that the host activity implements the callback interface
+        try {
+            // Instantiate the NoticeDialogListener so we can send events to the host
+            mListener = (NoticeDialogListener) activity;
+        } catch (ClassCastException e) {
+            // The activity doesn't implement the interface, throw exception
+            throw new ClassCastException(activity.toString()
+                    + " must implement NoticeDialogListener");
+        }
+    }
+    ...
+}
+
+ +

Hoạt động lưu giữ hộp thoại sẽ tạo một thực thể của hộp thoại +bằng hàm dựng của phân đoạn hộp thoại và nhận sự kiện +của hộp thoại thông qua triển khai giao diện {@code NoticeDialogListener}:

+ +
+public class MainActivity extends FragmentActivity
+                          implements NoticeDialogFragment.NoticeDialogListener{
+    ...
+    
+    public void showNoticeDialog() {
+        // Create an instance of the dialog fragment and show it
+        DialogFragment dialog = new NoticeDialogFragment();
+        dialog.show(getSupportFragmentManager(), "NoticeDialogFragment");
+    }
+
+    // The dialog fragment receives a reference to this Activity through the
+    // Fragment.onAttach() callback, which it uses to call the following methods
+    // defined by the NoticeDialogFragment.NoticeDialogListener interface
+    @Override
+    public void onDialogPositiveClick(DialogFragment dialog) {
+        // User touched the dialog's positive button
+        ...
+    }
+
+    @Override
+    public void onDialogNegativeClick(DialogFragment dialog) {
+        // User touched the dialog's negative button
+        ...
+    }
+}
+
+ +

Vì hoạt động chủ sẽ triển khai {@code NoticeDialogListener}—, được +thực thi bởi phương pháp gọi lại {@link android.support.v4.app.Fragment#onAttach onAttach()} +minh họa bên trên,—phân đoạn hộp thoại có thể sử dụng các phương pháp gọi lại +giao diện để chuyển các sự kiện nhấp cho hoạt động:

+ +
+public class NoticeDialogFragment extends DialogFragment {
+    ...
+
+    @Override
+    public Dialog onCreateDialog(Bundle savedInstanceState) {
+        // Build the dialog and set up the button click handlers
+        AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+        builder.setMessage(R.string.dialog_fire_missiles)
+               .setPositiveButton(R.string.fire, new DialogInterface.OnClickListener() {
+                   public void onClick(DialogInterface dialog, int id) {
+                       // Send the positive button event back to the host activity
+                       mListener.onDialogPositiveClick(NoticeDialogFragment.this);
+                   }
+               })
+               .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
+                   public void onClick(DialogInterface dialog, int id) {
+                       // Send the negative button event back to the host activity
+                       mListener.onDialogNegativeClick(NoticeDialogFragment.this);
+                   }
+               });
+        return builder.create();
+    }
+}
+
+ + + +

Hiển thị một Hộp thoại

+ +

Khi bạn muốn hiển thị hộp thoại của mình, hãy tạo một thực thể {@link +android.support.v4.app.DialogFragment} của bạn và gọi {@link android.support.v4.app.DialogFragment#show +show()}, chuyển {@link android.support.v4.app.FragmentManager} và một tên tag +cho phân đoạn hộp thoại.

+ +

Bạn có thể nhận được {@link android.support.v4.app.FragmentManager} bằng cách gọi +{@link android.support.v4.app.FragmentActivity#getSupportFragmentManager()} từ +{@link android.support.v4.app.FragmentActivity} hoặc {@link +android.support.v4.app.Fragment#getFragmentManager()} từ một {@link +android.support.v4.app.Fragment}. Ví dụ:

+ +
+public void confirmFireMissiles() {
+    DialogFragment newFragment = new FireMissilesDialogFragment();
+    newFragment.show(getSupportFragmentManager(), "missiles");
+}
+
+ +

Tham đối thứ hai, {@code "missiles"}, là một tên tag duy nhất mà hệ thống sử dụng để lưu +và khôi phục trạng thái của phân đoạn khi cần thiết. Tag cũng cho phép bạn nhận một điều khiển (handle) cho +phân đoạn bằng cách gọi {@link android.support.v4.app.FragmentManager#findFragmentByTag +findFragmentByTag()}.

+ + + + +

Hiển thị một Hộp thoại Toàn màn hình hoặc dạng một Phân đoạn Nhúng

+ +

Bạn có thể có một thiết kế UI mà trong đó bạn muốn một phần UI xuất hiện như một hộp thoại trong một số +tình huống, nhưng ở dưới dạng toàn màn hình hoặc phân đoạn nhúng trong trường hợp khác (có thể phụ thuộc +vào thiết bị là màn hình lớn hay nhỏ). Lớp {@link android.support.v4.app.DialogFragment} +cung cấp cho bạn sự linh hoạt này vì nó vẫn có thể đóng vai trò như một {@link +android.support.v4.app.Fragment} nhúng được.

+ +

Tuy nhiên, bạn không thể sử dụng {@link android.app.AlertDialog.Builder AlertDialog.Builder} +hay các đối tượng {@link android.app.Dialog} khác để xây dựng hộp thoại trong trường hợp này. Nếu +bạn muốn {@link android.support.v4.app.DialogFragment} có thể +nhúng được, bạn phải định nghĩa UI của hộp thoại trong một bố trí, rồi tải bố trí đó trong lệnh gọi lại +{@link android.support.v4.app.DialogFragment#onCreateView +onCreateView()}.

+ +

Sau đây là một ví dụ {@link android.support.v4.app.DialogFragment} có thể xuất hiện như một +hộp thoại hoặc phân đoạn nhúng được (sử dụng một bố trí có tên gọi purchase_items.xml):

+ +
+public class CustomDialogFragment extends DialogFragment {
+    /** The system calls this to get the DialogFragment's layout, regardless
+        of whether it's being displayed as a dialog or an embedded fragment. */
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+            Bundle savedInstanceState) {
+        // Inflate the layout to use as dialog or embedded fragment
+        return inflater.inflate(R.layout.purchase_items, container, false);
+    }
+  
+    /** The system calls this only when creating the layout in a dialog. */
+    @Override
+    public Dialog onCreateDialog(Bundle savedInstanceState) {
+        // The only reason you might override this method when using onCreateView() is
+        // to modify any dialog characteristics. For example, the dialog includes a
+        // title by default, but your custom layout might not need it. So here you can
+        // remove the dialog title, but you must call the superclass to get the Dialog.
+        Dialog dialog = super.onCreateDialog(savedInstanceState);
+        dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
+        return dialog;
+    }
+}
+
+ +

Và sau đây là một số mã quyết định xem hiển thị phân đoạn như một hộp thoại +hay UI toàn màn hình, dựa vào kích cỡ màn hình:

+ +
+public void showDialog() {
+    FragmentManager fragmentManager = getSupportFragmentManager();
+    CustomDialogFragment newFragment = new CustomDialogFragment();
+    
+    if (mIsLargeLayout) {
+        // The device is using a large layout, so show the fragment as a dialog
+        newFragment.show(fragmentManager, "dialog");
+    } else {
+        // The device is smaller, so show the fragment fullscreen
+        FragmentTransaction transaction = fragmentManager.beginTransaction();
+        // For a little polish, specify a transition animation
+        transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
+        // To make it fullscreen, use the 'content' root view as the container
+        // for the fragment, which is always the root view for the activity
+        transaction.add(android.R.id.content, newFragment)
+                   .addToBackStack(null).commit();
+    }
+}
+
+ +

Để biết thêm thông tin về việc thực hiện các giao tác phân đoạn, hãy xem hướng dẫn +Phân đoạn.

+ +

Trong ví dụ này, boolean mIsLargeLayout chỉ định liệu thiết bị hiện tại +có nên sử dụng thiết kế bố trí lớn của ứng dụng (và vì thế, nó hiển thị phân đoạn này như một hộp thoại thay vì +toàn màn hình) hay không. Cách tốt nhất để đặt loại boolean này đó là khai báo một +giá trị tài nguyên bool +bằng một giá trị tài nguyên thay thế cho các kích cỡ màn hình khác nhau. Ví dụ, sau đây là hai +phiên bản của tài nguyên bool cho các kích cỡ màn hình khác nhau:

+ +

res/values/bools.xml

+
+<!-- Default boolean values -->
+<resources>
+    <bool name="large_layout">false</bool>
+</resources>
+
+ +

res/values-large/bools.xml

+
+<!-- Large screen boolean values -->
+<resources>
+    <bool name="large_layout">true</bool>
+</resources>
+
+ +

Khi đó, bạn có thể khởi tạo giá trị {@code mIsLargeLayout} trong phương pháp +{@link android.app.Activity#onCreate onCreate()} của hoạt động:

+ +
+boolean mIsLargeLayout;
+
+@Override
+public void onCreate(Bundle savedInstanceState) {
+    super.onCreate(savedInstanceState);
+    setContentView(R.layout.activity_main);
+
+    mIsLargeLayout = getResources().getBoolean(R.bool.large_layout);
+}
+
+ + + +

Hiển thị một hoạt động dưới dạng một hộp thoại trên màn hình lớn

+ +

Thay vì hiển thị một hộp thoại thành UI toàn màn hình trên các màn hình nhỏ, bạn có thể đạt được +kết quả tương tự bằng cách hiển thị một {@link android.app.Activity} thành một hộp thoại trên +màn hình lớn. Phương pháp mà bạn chọn phụ thuộc vào thiết kế ứng dụng của bạn, nhưng +việc hiển thị một hoạt động thành một hộp thoại thường có ích khi ứng dụng của bạn đã được thiết kế cho màn hình +nhỏ và bạn muốn cải thiện trải nghiệm trên máy tính bảng bằng cách hiển thị một hoạt động có vòng đời ngắn +thành một hộp thoại.

+ +

Để hiển thị một hoạt động thành một hộp thoại chỉ khi trên màn hình lớn, +hãy áp dụng chủ đề {@link android.R.style#Theme_Holo_DialogWhenLarge Theme.Holo.DialogWhenLarge} +cho phần tử bản kê khai {@code +<activity>}:

+ +
+<activity android:theme="@android:style/Theme.Holo.DialogWhenLarge" >
+
+ +

Để biết thêm thông tin về việc tạo kiểu cho các hoạt động của bạn bằng chủ đề, hãy xem hướng dẫn Kiểu và Chủ đề.

+ + + +

Bỏ một Hộp thoại

+ +

Khi người dùng chạm vào bất kỳ nút hành động nào được tạo bằng +{@link android.app.AlertDialog.Builder}, hệ thống sẽ bỏ hộp thoại cho bạn.

+ +

Hệ thống cũng bỏ hộp thoại khi người dùng chạm vào một mục trong một danh sách hộp thoại, trừ +khi danh sách sử dụng nút chọn một hoặc hộp kiểm. Nếu không, bạn có thể bỏ thủ công hộp thoại của mình +bằng cách gọi {@link android.support.v4.app.DialogFragment#dismiss()} trên {@link +android.support.v4.app.DialogFragment} của bạn.

+ +

Trong trường hợp bạn cần thực hiện các +hành động nhất định khi hộp thoại biến mất, bạn có thể triển khai phương pháp {@link +android.support.v4.app.DialogFragment#onDismiss onDismiss()} trong {@link +android.support.v4.app.DialogFragment} của mình.

+ +

Bạn cũng có thể hủy bỏ một hộp thoại. Đây là một sự kiện đặc biệt chỉ báo người dùng +chủ ý rời khỏi hộp thoại mà không hoàn thành tác vụ. Điều này xảy ra nếu người dùng nhấn nút +Quay lại, chạm vào màn hình ngoài vùng hộp thoại, +hoặc nếu bạn công khai gọi {@link android.app.Dialog#cancel()} trên {@link +android.app.Dialog} (chẳng hạn như khi hồi đáp lại một nút "Hủy bỏ" trong hộp thoại).

+ +

Như nêu trong ví dụ bên trên, bạn có thể hồi đáp lại sự kiện hủy bỏ này bằng cách triển khai +{@link android.support.v4.app.DialogFragment#onCancel onCancel()} trong lớp {@link +android.support.v4.app.DialogFragment} của mình.

+ +

Lưu ý: Hệ thống sẽ gọi +{@link android.support.v4.app.DialogFragment#onDismiss onDismiss()} trên mỗi sự kiện mà +gọi ra lệnh gọi lại {@link android.support.v4.app.DialogFragment#onCancel onCancel()}. Tuy nhiên, +nếu bạn gọi {@link android.app.Dialog#dismiss Dialog.dismiss()} hoặc {@link +android.support.v4.app.DialogFragment#dismiss DialogFragment.dismiss()}, +hệ thống sẽ gọi {@link android.support.v4.app.DialogFragment#onDismiss onDismiss()} chứ +không phải {@link android.support.v4.app.DialogFragment#onCancel onCancel()}. Vì thế, nhìn chung bạn nên +gọi {@link android.support.v4.app.DialogFragment#dismiss dismiss()} khi người dùng nhấn nút +tích cực trong hộp thoại của bạn để xóa hộp thoại khỏi dạng xem.

+ + diff --git a/docs/html-intl/intl/vi/guide/topics/ui/menus.jd b/docs/html-intl/intl/vi/guide/topics/ui/menus.jd new file mode 100644 index 0000000000000000000000000000000000000000..8e9e1c412c5a9061bd348abf807d06d0e3702216 --- /dev/null +++ b/docs/html-intl/intl/vi/guide/topics/ui/menus.jd @@ -0,0 +1,1031 @@ +page.title=Menu +parent.title=Giao diện Người dùng +parent.link=index.html +@jd:body + + + +

Menu là một thành phần giao diện người dùng phổ biến trong nhiều loại ứng dụng. Để cung cấp một +trải nghiệm người dùng quen thuộc và nhất quán, bạn nên sử dụng các API {@link android.view.Menu} để trình bày +hành động người dùng và các tùy chọn khác trong hoạt động của mình.

+ +

Bắt đầu với Android 3.0 (API mức 11), các thiết bị dựa trên nền tảng Android không còn phải +cung cấp một nút Menu chuyên dụng nữa. Với sự thay đổi này, các ứng dụng Android cần tránh khỏi +sự phụ thuộc vào bảng điều khiển menu 6 mục truyền thống này mà thay vào đó cung cấp một thanh hành động để trình bày +các hành động người dùng thông dụng.

+ +

Mặc dù thiết kế và trải nghiệm người dùng đối với một số mục menu đã thay đổi, ngữ nghĩa để định nghĩa +tập hợp hành động và tùy chọn thì vẫn dựa trên các API {@link android.view.Menu}. Hướng dẫn +này trình bày cách tạo ba loại menu hay trình bày hành động cơ bản trên tất cả +phiên bản Android:

+ +
+
Menu tùy chọn và thanh hành động
+
Menu tùy chọn là tập hợp các mục menu cơ bản cho một +hoạt động. Đó là nơi bạn nên đặt các hành động có tác động chung tới ứng dụng, chẳng hạn như +"Tìm kiếm," "Soạn e-mail" và "Cài đặt." +

Nếu bạn đang phát triển cho phiên bản Android 2.3 hoặc thấp hơn, người dùng có thể +hiện bảng điều khiển menu tùy chọn bằng cách nhấn nút Menu.

+

Trên phiên bản Android 3.0 trở lên, các mục từ menu tùy chọn được trình bày bởi thanh hành động, là sự kết hợp giữa các mục hành động +trên màn hình và các tùy chọn tràn. Bắt đầu với phiên bản Android 3.0, nút Menu bị bỏ đi (một số +thiết bị +không có), vì thế bạn nên chuyển sang sử dụng thanh hành động để cho phép truy cập vào hành động và +các tùy chọn khác.

+

Xem phần về Tạo một Menu Tùy chọn.

+
+ +
Menu ngữ cảnh và chế độ hành động theo ngữ cảnh
+ +
Menu ngữ cảnh là một menu nổi xuất hiện khi +người dùng thực hiện nhấp giữ trên một phần tử. Nó cung cấp các hành động ảnh hưởng tới nội dung hoặc +khung ngữ cảnh được chọn. +

Khi phát triển cho phiên bản Android 3.0 trở lên, thay vào đó, bạn nên sử dụng chế độ hành động theo ngữ cảnh để kích hoạt các hành động trên nội dung được chọn. Chế độ này hiển thị +các mục hành động ảnh hưởng tới nội dung được chọn trong một thanh ở trên cùng của màn hình và cho phép người dùng +chọn nhiều mục.

+

Xem phần nói về Tạo Menu Ngữ cảnh.

+
+ +
Menu bật lên
+
Menu bật lên sẽ hiển thị danh sách các mục trong một danh sách thẳng đứng được neo vào dạng xem +đã gọi ra menu. Nên cung cấp một phần tràn gồm các hành động liên quan tới nội dung cụ thể hoặc +nhằm cung cấp các tùy chọn cho phần thứ hai của một lệnh. Các hành động trong một menu bật lên +không nên trực tiếp ảnh hưởng tới nội dung tương ứng—đó là việc của hành động ngữ cảnh +. Thay vào đó, menu bật lên áp dụng cho các hành động mở rộng liên quan tới các vùng nội dung trong hoạt động +của bạn. +

Xem phần về Tạo một Menu Bật lên.

+
+
+ + + +

Định nghĩa một Menu trong XML

+ +

Đối với tất cả các loại menu, Android cung cấp một định dạng XML chuẩn để định nghĩa các mục menu. +Thay vì xây dựng một menu trong mã của hoạt động của bạn, bạn nên định nghĩa một menu và tất cả các mục của nó trong một +tài nguyên menu XML. Khi đó, bạn có thể +bung tài nguyên menu (tải nó như một đối tượng {@link android.view.Menu}) trong hoạt động hoặc +phân đoạn của mình.

+ +

Sử dụng một tài nguyên menu là một cách làm hay vì một vài lý do:

+
    +
  • Nó dễ trực quan hóa cấu trúc menu trong XML hơn.
  • +
  • Nó tách riêng nội dung cho menu với mã hành vi của ứng dụng của bạn.
  • +
  • Nó cho phép bạn tạo các cấu hình menu phái sinh cho các phiên bản nền tảng, +kích cỡ màn hình khác nhau và các cấu hình khác bằng cách tận dụng khuôn khổ tài nguyên ứng dụng.
  • +
+ +

Để định nghĩa menu, hãy tạo một tệp XML bên trong thư mục res/menu/ +dự án của bạn và xây dựng menu với các phần tử sau:

+
+
<menu>
+
Định nghĩa một {@link android.view.Menu}, đó là một bộ chứa các mục menu. Phần tử +<menu> phải là một nút gốc cho tệp và có thể giữ một hoặc nhiều phần tử +<item><group>.
+ +
<item>
+
Tạo một {@link android.view.MenuItem}, nó biểu diễn một mục đơn trong một menu. Phần tử +này có thể chứa một phần tử <menu> được lồng nhau để tạo một menu con.
+ +
<group>
+
Một bộ chứa tùy chọn, vô hình cho các phần tử {@code <item>}. Nó cho phép bạn +phân loại các mục menu sao cho chúng chia sẻ các tính chất như trạng thái hiện hoạt và khả năng hiển thị. Để biết thêm +thông tin, hãy xem phần nói về Tạo Nhóm Menu.
+
+ + +

Sau đây là một menu ví dụ có tên là game_menu.xml:

+
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:id="@+id/new_game"
+          android:icon="@drawable/ic_new_game"
+          android:title="@string/new_game"
+          android:showAsAction="ifRoom"/>
+    <item android:id="@+id/help"
+          android:icon="@drawable/ic_help"
+          android:title="@string/help" />
+</menu>
+
+ +

Phần tử <item> hỗ trợ một vài thuộc tính bạn có thể sử dụng để định nghĩa biểu hiện bên ngoài +và hành vi của một mục. Các mục trong menu trên bao gồm những thuộc tính sau:

+ +
+
{@code android:id}
+
Một ID tài nguyên duy nhất đối với mục, nó cho phép ứng dụng có thể nhận ra mục đó +khi người dùng chọn nó.
+
{@code android:icon}
+
Một tham chiếu tới một nội dung vẽ được để dùng làm biểu tượng của mục.
+
{@code android:title}
+
Một tham chiếu tới một xâu để dùng làm tiêu đề của mục.
+
{@code android:showAsAction}
+
Quy định thời điểm và cách thức mục này nên xuất hiện như một mục hành động trong thanh hành động.
+
+ +

Đây là những thuộc tính quan trọng nhất bạn nên sử dụng, nhưng còn nhiều thuộc tính sẵn có khác. +Để biết thông tin về tất cả thuộc tính được hỗ trợ, hãy xem tài liệu Tài nguyên Menu.

+ +

Bạn có thể thêm một menu con vào một mục trong bất kỳ menu nào (ngoại trừ menu con) bằng cách thêm một phần tử {@code <menu>} +làm con của {@code <item>}. Các menu con thường hữu ích khi ứng dụng của bạn có nhiều +chức năng mà có thể được tổ chức thành các chủ đề, như các mục trong thanh menu của một ứng dụng PC (Tệp, +Chỉnh sửa, Dạng xem, v.v.). Ví dụ:

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:id="@+id/file"
+          android:title="@string/file" >
+        <!-- "file" submenu -->
+        <menu>
+            <item android:id="@+id/create_new"
+                  android:title="@string/create_new" />
+            <item android:id="@+id/open"
+                  android:title="@string/open" />
+        </menu>
+    </item>
+</menu>
+
+ +

Để sử dụng menu trong hoạt động của mình, bạn cần bung tài nguyên menu (chuyển tài nguyên XML +thành một đối tượng có thể lập trình) bằng cách sử dụng {@link android.view.MenuInflater#inflate(int,Menu) +MenuInflater.inflate()}. Trong những phần sau, bạn sẽ biết cách bung một menu đối với mỗi +loại menu.

+ + + +

Tạo một Menu Tùy chọn

+ +
+ +

Hình 1. Các menu tùy chọn trong +Trình duyệt, trên Android 2.3.

+
+ +

Menu tùy chọn là nơi bạn nên đưa vào hành động và các tùy chọn khác liên quan tới +ngữ cảnh hoạt động hiện tại, chẳng hạn như "Tìm kiếm," "Soạn e-mail," và "Cài đặt."

+ +

Nơi mà các mục trong menu tùy chọn của bạn xuất hiện trên màn hình sẽ phụ thuộc vào phiên bản mà bạn +phát triển ứng dụng của mình cho:

+ +
    +
  • Nếu bạn phát triển ứng dụng của mình cho phiên bản Android 2.3.x (API mức 10) hoặc +thấp hơn, nội dung của menu tùy chọn sẽ xuất hiện ở dưới cùng màn hình khi người dùng +nhấn nút Menu như minh họa trong hình 1. Khi được mở, phần hiển thị đầu tiên là +menu biểu tượng +với tối đa sáu mục menu. Nếu menu của bạn bao gồm nhiều hơn sáu mục, Android sẽ đặt +mục thứ sáu và phần còn lại vào một menu tràn mà người dùng có thể mở bằng cách chọn +Thêm nữa.
  • + +
  • Nếu bạn phát triển ứng dụng của mình cho phiên bản Android 3.0 (API mức 11) và +cao hơn, các mục từ menu tùy chọn sẵn ở trong thanh hành động. Theo mặc định, hệ thống +đặt tất cả các mục trong phần tràn hành động mà người dùng có thể hiện bằng biểu tượng tràn hành động phía +bên phải của thanh hành động (hoặc bằng cách nhấn nút Menu của thiết bị nếu có). Để +kích hoạt +truy cập nhanh vào các hành động quan trọng, bạn có thể đưa lên một vài mục xuất hiện trong thanh hành động bằng cách thêm +{@code android:showAsAction="ifRoom"} vào phần tử {@code <item>} tương ứng (xem hình +2).

    Để biết thêm thông tin về các mục hành động và hành vi khác của thanh hành động, hãy xem hướng dẫn Thanh Hành động.

    +

    Lưu ý: Ngay cả khi bạn không đang phát triển cho phiên bản Android 3.0 hoặc +cao hơn, bạn có thể xây dựng bố trí thanh hành động của chính mình cho hiệu ứng tương tự. Để xem ví dụ về cách bạn có thể hỗ trợ các phiên bản cao hơn +của Android bằng một thanh hành động, hãy xem mẫu Tương thích với Thanh Hành động +.

    +
  • +
+ + +

Hình 2. Thanh hành động từ ứng dụng Honeycomb Gallery, hiển thị +các tab điều hướng và một mục hành động máy ảnh (cộng với nút tràn hành động).

+ +

Bạn có thể khai báo các mục cho menu tùy chọn từ lớp con {@link android.app.Activity} +của bạn hoặc một lớp con {@link android.app.Fragment}. Nếu cả hoạt động của bạn và (các) phân đoạn +đều khai báo các mục cho menu tùy chọn, chúng sẽ được kết hợp lại trong UI. Các mục của hoạt động xuất hiện +trước, sau đó là các mục của từng phân đoạn theo thứ tự phân đoạn được thêm vào +hoạt động. Nếu cần, bạn có thể sắp xếp lại các mục menu bằng thuộc tính {@code android:orderInCategory} +trong mỗi {@code <item>} mà bạn cần di chuyển.

+ +

Để quy định menu tùy chọn cho một hoạt động, hãy khống chế {@link +android.app.Activity#onCreateOptionsMenu(Menu) onCreateOptionsMenu()} (các phân đoạn cung cấp +phương pháp gọi lại {@link android.app.Fragment#onCreateOptionsMenu onCreateOptionsMenu()} của chính mình). Trong +phương pháp này, bạn có thể bung tài nguyên menu của mình (được định nghĩa trong XML) vào {@link +android.view.Menu} được cung cấp trong phương pháp gọi lại. Ví dụ:

+ +
+@Override
+public boolean onCreateOptionsMenu(Menu menu) {
+    MenuInflater inflater = {@link android.app.Activity#getMenuInflater()};
+    inflater.inflate(R.menu.game_menu, menu);
+    return true;
+}
+
+ +

Bạn cũng có thể thêm các mục menu bằng cách sử dụng {@link android.view.Menu#add(int,int,int,int) +add()} và truy xuất các mục bằng {@link android.view.Menu#findItem findItem()} để xem lại +tính chất của chúng bằng các API {@link android.view.MenuItem}.

+ +

Nếu bạn phát triển ứng dụng của mình cho phiên bản Android 2.3.x và thấp hơn, hệ thống gọi {@link +android.app.Activity#onCreateOptionsMenu(Menu) onCreateOptionsMenu()} để tạo menu tùy chọn +khi người dùng mở menu lần đầu tiên. Nếu bạn phát triển cho phiên bản Android 3.0 vào cao hơn, +hệ thống sẽ gọi {@link android.app.Activity#onCreateOptionsMenu(Menu) onCreateOptionsMenu()} khi +bắt đầu hoạt động để hiển thị các mục cho thanh hành động.

+ + + +

Xử lý sự kiện nhấp

+ +

Khi người dùng chọn một mục từ menu tùy chọn (bao gồm các mục hành động trong thanh hành động), +hệ thống sẽ gọi phương pháp {@link android.app.Activity#onOptionsItemSelected(MenuItem) +onOptionsItemSelected()} của hoạt động của bạn. Phương pháp này thông qua {@link android.view.MenuItem} được chọn. Bạn +có thể nhận biết mục bằng cách gọi {@link android.view.MenuItem#getItemId()}, nó trả về ID duy nhất +cho mục menu (được định nghĩa bởi thuộc tính {@code android:id} trong tài nguyên menu hoặc bằng một +số nguyên được cấp cho phương pháp {@link android.view.Menu#add(int,int,int,int) add()}). Bạn có thể khớp +ID này với các mục menu đã biết để thực hiện hành động phù hợp. Ví dụ:

+ +
+@Override
+public boolean onOptionsItemSelected(MenuItem item) {
+    // Handle item selection
+    switch (item.getItemId()) {
+        case R.id.new_game:
+            newGame();
+            return true;
+        case R.id.help:
+            showHelp();
+            return true;
+        default:
+            return super.onOptionsItemSelected(item);
+    }
+}
+
+ +

Khi bạn xử lý thành công một mục menu, trả về {@code true}. Nếu không xử lý được +mục menu, bạn nên gọi triển khai siêu lớp của {@link +android.app.Activity#onOptionsItemSelected(MenuItem) onOptionsItemSelected()} (triển khai +mặc định trả về sai).

+ +

Nếu hoạt động của bạn bao gồm các phân đoạn, trước tiên hệ thống sẽ gọi {@link +android.app.Activity#onOptionsItemSelected(MenuItem) onOptionsItemSelected()} cho hoạt động, rồi mới +cho từng phân đoạn (theo thứ tự thêm phân đoạn) tới khi trả về +{@code true} hoặc tất cả phân đoạn đều được gọi.

+ +

Mẹo: Android 3.0 thêm khả năng cho phép bạn định nghĩa hành vi +khi nhấp đối với một mục menu trong XML, bằng cách sử dụng thuộc tính {@code android:onClick}. Giá trị cho +thuộc tính phải là tên của một phương pháp được định nghĩa bởi hoạt động sử dụng menu. Phương pháp +phải công khai và chấp nhận một tham số {@link android.view.MenuItem} đơn—khi hệ thống +gọi phương pháp này, nó thông qua mục menu được chọn. Để biết thêm thông tin và ví dụ, hãy xem tài liệu Tài nguyên Menu.

+ +

Mẹo: Nếu ứng dụng của bạn chứa nhiều hoạt động và +một số chúng cung cấp menu tùy chọn tương tự, hãy xem xét tạo + một hoạt động chỉ triển khai các phương pháp {@link android.app.Activity#onCreateOptionsMenu(Menu) +onCreateOptionsMenu()} và {@link android.app.Activity#onOptionsItemSelected(MenuItem) +onOptionsItemSelected()}. Sau đó, mở rộng lớp này đối với mỗi hoạt động cần chia sẻ +menu tùy chọn tương tự. Bằng cách này, bạn có thể quản lý một bộ mã để xử lý các hành động +menu và từng lớp hậu duệ kế thừa các hành vi menu. +Nếu bạn muốn thêm các mục menu vào một trong các hoạt động hậu duệ, +hãy khống chế {@link android.app.Activity#onCreateOptionsMenu(Menu) +onCreateOptionsMenu()} trong hoạt động đó. Gọi {@code super.onCreateOptionsMenu(menu)} sao cho +các mục menu gốc được tạo, sau đó thêm các mục menu mới bằng {@link +android.view.Menu#add(int,int,int,int) menu.add()}. Bạn cũng có thể khống chế hành vi +của siêu lớp đối với các mục menu riêng lẻ.

+ + +

Thay đổi các mục menu vào thời gian chạy

+ +

Sau khi hệ thống gọi {@link android.app.Activity#onCreateOptionsMenu(Menu) +onCreateOptionsMenu()}, nó sẽ giữ lại một thực thể của {@link android.view.Menu} mà bạn đưa vào và +sẽ không gọi lại {@link android.app.Activity#onCreateOptionsMenu(Menu) onCreateOptionsMenu()} +trừ khi menu bị vô hiệu hóa vì lý do nào đó. Tuy nhiên, bạn chỉ nên sử dụng {@link +android.app.Activity#onCreateOptionsMenu(Menu) onCreateOptionsMenu()} để tạo trạng thái menu +ban đầu chứ không phải để thực hiện thay đổi trong vòng đời của hoạt động.

+ +

Nếu bạn muốn sửa đổi menu tùy chọn dựa trên +các sự kiện xảy ra trong vòng đời của hoạt động, bạn có thể làm vậy trong phương pháp + {@link android.app.Activity#onPrepareOptionsMenu(Menu) onPrepareOptionsMenu()}. Phương pháp +này chuyển cho bạn đối tượng {@link android.view.Menu} như hiện đang có để bạn có thể sửa đổi nó, +chẳng hạn như thêm, xóa bỏ, hoặc vô hiệu hóa các mục. (Phân đoạn cũng cung cấp lệnh gọi lại {@link +android.app.Fragment#onPrepareOptionsMenu onPrepareOptionsMenu()}.)

+ +

Trên phiên bản Android 2.3.x và thấp hơn, hệ thống gọi {@link +android.app.Activity#onPrepareOptionsMenu(Menu) +onPrepareOptionsMenu()} mỗi lần người dùng mở menu tùy chọn (nhấn nút Menu +).

+ +

Trên phiên bản Android 3.0 trở lên, menu tùy chọn được coi như luôn mở khi các mục menu được +trình bày trong thanh hành động. Khi một sự kiện xảy ra và bạn muốn thực hiện một cập nhật menu, bạn phải +gọi {@link android.app.Activity#invalidateOptionsMenu invalidateOptionsMenu()} để yêu cầu +hệ thống gọi {@link android.app.Activity#onPrepareOptionsMenu(Menu) onPrepareOptionsMenu()}.

+ +

Lưu ý: +Bạn không nên thay đổi các mục trong menu tùy chọn dựa trên {@link android.view.View} đang +trong tiêu điểm. Khi ở chế độ cảm ứng (khi người dùng không sử dụng bi xoay hay d-pad), các dạng xem +không thể lấy tiêu điểm, vì thế bạn không nên sử dụng tiêu điểm làm cơ sở để sửa đổi +các mục trong menu tùy chọn. Nếu bạn muốn cung cấp các mục menu nhạy cảm với ngữ cảnh cho một {@link +android.view.View}, hãy sử dụng một Menu Ngữ cảnh.

+ + + + +

Tạo một Menu Ngữ cảnh

+ +
+ +

Hình 3. Ảnh chụp màn hình một menu ngữ cảnh nổi (trái) +và thanh hành động ngữ cảnh (phải).

+
+ +

Menu ngữ cảnh sẽ đưa ra các hành động ảnh hưởng tới một mục hoặc khung ngữ cảnh cụ thể trong UI. Bạn +có thể cung cấp một menu ngữ cảnh cho bất kỳ dạng xem nào, nhưng chúng thường được sử dụng nhiều nhất cho các mục trong một {@link +android.widget.ListView}, {@link android.widget.GridView}, hoặc các bộ sưu tập dạng xem khác mà +người dùng có thể thực hiện hành động trực tiếp trên mỗi mục.

+ +

Có hai cách để cung cấp các hành động ngữ cảnh:

+
    +
  • Trong một menu ngữ cảnh nổi. Menu xuất hiện như một +danh sách nổi gồm nhiều mục menu (tương tự như một hộp thoại) khi người dùng thực hiện nhấp giữ (nhấn và +giữ) trên một dạng xem có khai báo hỗ trợ menu ngữ cảnh. Người dùng có thể thực hiện hành động +ngữ cảnh trên một mục vào một thời điểm.
  • + +
  • Trong chế độ hành động theo ngữ cảnh. Chế độ này là một hệ thống triển khai +{@link android.view.ActionMode} có chức năng hiển thị một thanh hành động ngữ cảnh ở bên trên +màn hình với các mục hành động ảnh hưởng tới (các) mục được chọn. Khi chế độ này hiện hoạt, người dùng +có thể thực hiện một hành động trên nhiều mục ngay lập tức (nếu ứng dụng của bạn cho phép).
  • +
+ +

Lưu ý: Chế độ hành động theo ngữ cảnh sẵn có trên phiên bản Android 3.0 (API +mức 11) và cao hơn và là kỹ thuật được ưu tiên cho việc hiển thị các hành động theo ngữ cảnh khi +sẵn có. Nếu ứng dụng của bạn hỗ trợ các phiên bản thấp hơn 3.0, vậy bạn nên quay lại menu ngữ cảnh +nổi trên những thiết bị đó.

+ + +

Tạo một menu ngữ cảnh nổi

+ +

Để cung cấp một menu ngữ cảnh nổi:

+
    +
  1. Đăng ký {@link android.view.View} mà menu ngữ cảnh nên được liên kết với bằng cách +gọi {@link android.app.Activity#registerForContextMenu(View) registerForContextMenu()} và chuyển +cho nó {@link android.view.View}. +

    Nếu hoạt động của bạn sử dụng một {@link android.widget.ListView} hoặc {@link android.widget.GridView} và +bạn muốn từng mục cung cấp cùng menu ngữ cảnh, hãy đăng ký tất cả mục cho một menu ngữ cảnh bằng cách +chuyển {@link android.widget.ListView} hoặc {@link android.widget.GridView} cho {@link +android.app.Activity#registerForContextMenu(View) registerForContextMenu()}.

    +
  2. + +
  3. Triển khai phương pháp {@link +android.view.View.OnCreateContextMenuListener#onCreateContextMenu onCreateContextMenu()} +trong {@link android.app.Activity} hoặc {@link android.app.Fragment} của bạn. +

    Khi dạng xem được đăng ký nhận được một sự kiện nhấp giữ, hệ thống sẽ gọi phương pháp {@link +android.view.View.OnCreateContextMenuListener#onCreateContextMenu onCreateContextMenu()} +của bạn. Đây là nơi bạn định nghĩa các mục menu, thường bằng cách bung một tài nguyên menu. Ví +dụ:

    +
    +@Override
    +public void onCreateContextMenu(ContextMenu menu, View v,
    +                                ContextMenuInfo menuInfo) {
    +    super.onCreateContextMenu(menu, v, menuInfo);
    +    MenuInflater inflater = getMenuInflater();
    +    inflater.inflate(R.menu.context_menu, menu);
    +}
    +
    + +

    {@link android.view.MenuInflater} cho phép bạn bung menu ngữ cảnh từ một tài nguyên menu. Các tham số của phương pháp gọi lại +bao gồm {@link android.view.View} +mà người dùng đã chọn và một đối tượng {@link android.view.ContextMenu.ContextMenuInfo} cung cấp +thông tin bổ sung về mục được chọn. Nếu hoạt động của bạn có một vài dạng xem mà mỗi dạng cung cấp +một menu ngữ cảnh khác nhau, bạn có thể sử dụng những tham số này để xác định menu ngữ cảnh nào cần +bung.

    +
  4. + +
  5. Triển khai {@link android.app.Activity#onContextItemSelected(MenuItem) +onContextItemSelected()}. +

    Khi người dùng chọn một mục menu, hệ thống sẽ gọi phương pháp này để bạn có thể thực hiện +hành động phù hợp. Ví dụ:

    + +
    +@Override
    +public boolean onContextItemSelected(MenuItem item) {
    +    AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo();
    +    switch (item.getItemId()) {
    +        case R.id.edit:
    +            editNote(info.id);
    +            return true;
    +        case R.id.delete:
    +            deleteNote(info.id);
    +            return true;
    +        default:
    +            return super.onContextItemSelected(item);
    +    }
    +}
    +
    + +

    Phương pháp {@link android.view.MenuItem#getItemId()} sẽ truy vấn ID cho +mục menu được chọn, bạn nên gán mục này cho từng mục menu trong XML bằng cách sử dụng thuộc tính {@code +android:id} như trình bày trong phần về Định nghĩa một Menu trong +XML.

    + +

    Khi bạn xử lý thành công một mục menu, trả về {@code true}. Nếu bạn không xử lý mục menu, +bạn nên chuyển mục menu đó tới triển khai siêu lớp. Nếu hoạt động của bạn bao gồm nhiều phân đoạn, +hoạt động sẽ nhận được lệnh gọi lại này trước. Bằng cách gọi siêu lớp khi chưa được xử lý, hệ thống +sẽ chuyển sự kiện tới phương pháp gọi lại tương ứng trong từng phân đoạn, lần lượt (theo thứ tự +thêm phân đoạn) tới khi {@code true} hoặc {@code false} được trả về. (Triển khai +mặc định cho {@link android.app.Activity} và {@code android.app.Fragment} sẽ trả về {@code +false}, vì thế bạn nên luôn gọi siêu lớp khi chưa được xử lý.)

    +
  6. +
+ + +

Sử dụng chế độ hành động theo ngữ cảnh

+ +

Chế độ hành động theo ngữ cảnh là một triển khai hệ thống {@link android.view.ActionMode} +tập trung vào tương tác người dùng hướng tới việc thực hiện các hành động theo ngữ cảnh. Khi một +người dùng kích hoạt chế độ này bằng cách chọn một mục, một thanh hành động ngữ cảnh sẽ xuất hiện bên trên +màn hình để trình bày các hành động mà người dùng có thể thực hiện trên (các) mục đang được chọn. Trong khi +chế độ này được kích hoạt, người dùng có thể chọn nhiều mục (nếu bạn cho phép), bỏ chọn mục, và tiếp tục +điều hướng trong hoạt động (miễn là bạn sẵn lòng cho phép). Chế độ hành động bị vô hiệu hóa +và thanh hành động ngữ cảnh biến mất khi người dùng bỏ chọn tất cả các mục, nhấn nút QUAY LẠI, +hoặc chọn hành động Xong ở phía bên trái của thanh.

+ +

Lưu ý: Thanh hành động ngữ cảnh không nhất thiết +phải được liên kết với thanh hành động. Chúng vận hành +độc lập, mặc dù thanh hành động ngữ cảnh đè lên vị trí của thanh hành động +về mặt hiển thị.

+ +

Nếu bạn đang phát triển cho phiên bản Android 3.0 (API mức 11) hoặc cao hơn, bạn +nên sử dụng chế độ hành động theo ngữ cảnh để trình bày các hành động ngữ cảnh, thay vì sử dụng menu ngữ cảnh nổi.

+ +

Đối với các dạng xem cung cấp hành động ngữ cảnh, bạn nên thường xuyên gọi ra chế độ hành động theo ngữ cảnh +khi xảy ra một trong hai sự kiện sau (hoặc cả hai):

+
    +
  • Người dùng thực hiện nhấp giữ trên dạng xem.
  • +
  • Người dùng chọn một hộp kiểm hoặc một thành phần UI tương tự trong dạng xem.
  • +
+ +

Cách ứng dụng của bạn gọi ra chế độ hành động theo ngữ cảnh và định nghĩa hành vi cho từng +hành động phụ thuộc vào thiết kế của bạn. Cơ bản có hai thiết kế:

+
    +
  • Đối với các hành động ngữ cảnh trên các dạng xem riêng lẻ, tùy ý.
  • +
  • Đối với các hành động ngữ cảnh hàng loạt trên các nhóm mục trong một {@link +android.widget.ListView} hoặc {@link android.widget.GridView} (cho phép người dùng chọn nhiều +mục và thực hiện một hành động trên tất cả).
  • +
+ +

Các phần sau mô tả phần thiết lập cần thiết đối với từng kịch bản.

+ + +

Kích hoạt chế độ hành động theo ngữ cảnh cho các dạng xem riêng lẻ

+ +

Nếu muốn gọi ra chế độ hành động theo ngữ cảnh chỉ khi người dùng chọn các dạng xem +cụ thể, bạn nên:

+
    +
  1. Triển khai giao diện {@link android.view.ActionMode.Callback}. Trong các phương pháp gọi lại của giao diện, bạn +có thể quy định các hành động cho thanh hành động ngữ cảnh, hồi đáp các sự kiện nhấp trên mục hành động, và +xử lý các sự kiện vòng đời khác đối với chế độ hành động.
  2. +
  3. Gọi {@link android.app.Activity#startActionMode startActionMode()} khi bạn muốn hiển thị +thanh (chẳng hạn như khi người dùng nhấp giữ dạng xem).
  4. +
+ +

Ví dụ:

+ +
    +
  1. Triển khai giao diện {@link android.view.ActionMode.Callback ActionMode.Callback}: +
    +private ActionMode.Callback mActionModeCallback = new ActionMode.Callback() {
    +
    +    // Called when the action mode is created; startActionMode() was called
    +    @Override
    +    public boolean onCreateActionMode(ActionMode mode, Menu menu) {
    +        // Inflate a menu resource providing context menu items
    +        MenuInflater inflater = mode.getMenuInflater();
    +        inflater.inflate(R.menu.context_menu, menu);
    +        return true;
    +    }
    +
    +    // Called each time the action mode is shown. Always called after onCreateActionMode, but
    +    // may be called multiple times if the mode is invalidated.
    +    @Override
    +    public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
    +        return false; // Return false if nothing is done
    +    }
    +
    +    // Called when the user selects a contextual menu item
    +    @Override
    +    public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
    +        switch (item.getItemId()) {
    +            case R.id.menu_share:
    +                shareCurrentItem();
    +                mode.finish(); // Action picked, so close the CAB
    +                return true;
    +            default:
    +                return false;
    +        }
    +    }
    +
    +    // Called when the user exits the action mode
    +    @Override
    +    public void onDestroyActionMode(ActionMode mode) {
    +        mActionMode = null;
    +    }
    +};
    +
    + +

    Lưu ý rằng những phương pháp gọi lại sự kiện này hầu như giống với các phương pháp gọi lại đối với menu tùy chọn, khác ở chỗ từng phương pháp cũng chuyển đối tượng {@link +android.view.ActionMode} được liên kết với sự kiện đó. Bạn có thể sử dụng các API {@link +android.view.ActionMode} để thực hiện những thay đổi khác nhau với CAB, chẳng hạn như sửa đổi tiêu đề và +phụ đề bằng {@link android.view.ActionMode#setTitle setTitle()} và {@link +android.view.ActionMode#setSubtitle setSubtitle()} (hữu ích khi muốn cho biết có bao nhiêu mục +được chọn).

    + +

    Cũng lưu ý rằng các bộ mẫu trên sẽ đặt biến {@code mActionMode} là rỗng khi +chế độ hành động bị hủy. Ở bước tiếp theo, bạn sẽ thấy cách nó được khởi tạo và việc lưu +biến thành viên trong hoạt động hoặc phân đoạn của bạn có thể hữu ích như thế nào.

    +
  2. + +
  3. Gọi {@link android.app.Activity#startActionMode startActionMode()} để kích hoạt chế độ hành động theo ngữ cảnh +khi phù hợp, chẳng hạn như để hồi đáp lại một sự kiện nhấp giữ trên một {@link +android.view.View}:

    + +
    +someView.setOnLongClickListener(new View.OnLongClickListener() {
    +    // Called when the user long-clicks on someView
    +    public boolean onLongClick(View view) {
    +        if (mActionMode != null) {
    +            return false;
    +        }
    +
    +        // Start the CAB using the ActionMode.Callback defined above
    +        mActionMode = getActivity().startActionMode(mActionModeCallback);
    +        view.setSelected(true);
    +        return true;
    +    }
    +});
    +
    + +

    Khi bạn gọi {@link android.app.Activity#startActionMode startActionMode()}, hệ thống sẽ trả về +{@link android.view.ActionMode} được tạo. Bằng cách lưu điều này trong một biến thành viên, bạn có thể +thực hiện thay đổi thanh hành động theo ngữ cảnh để hồi đáp những sự kiện khác. Trong mẫu trên, +{@link android.view.ActionMode} được sử dụng để đảm bảo rằng thực thể {@link android.view.ActionMode} không +được tạo lại nếu nó đã hiện hoạt, bằng cách kiểm tra xem thành viên có rỗng không trước khi khởi động +chế độ hành động.

    +
  4. +
+ + + +

Kích hoạt hành động theo ngữ cảnh hàng loạt trong ListView hoặc GridView

+ +

Nếu bạn có một bộ sưu tập các mục trong một {@link android.widget.ListView} hoặc {@link +android.widget.GridView} (hoặc một phần mở rộng khác của {@link android.widget.AbsListView}) và muốn +cho phép người dùng thực hiện các hành động hàng loạt, bạn nên:

+ +
    +
  • Triển khai giao diện {@link android.widget.AbsListView.MultiChoiceModeListener} và đặt nó +cho nhóm dạng xem bằng {@link android.widget.AbsListView#setMultiChoiceModeListener +setMultiChoiceModeListener()}. Trong các phương pháp gọi lại của trình nghe, bạn có thể quy định các hành động +cho thanh hành động theo ngữ cảnh, hồi đáp các sự kiện nhấp trên các mục hành động, và xử lý các phương pháp gọi lại khác +được kế thừa từ giao diện {@link android.view.ActionMode.Callback}.
  • + +
  • Gọi {@link android.widget.AbsListView#setChoiceMode setChoiceMode()} bằng tham đối {@link +android.widget.AbsListView#CHOICE_MODE_MULTIPLE_MODAL}.
  • +
+ +

Ví dụ:

+ +
+ListView listView = getListView();
+listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL);
+listView.setMultiChoiceModeListener(new MultiChoiceModeListener() {
+
+    @Override
+    public void onItemCheckedStateChanged(ActionMode mode, int position,
+                                          long id, boolean checked) {
+        // Here you can do something when items are selected/de-selected,
+        // such as update the title in the CAB
+    }
+
+    @Override
+    public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
+        // Respond to clicks on the actions in the CAB
+        switch (item.getItemId()) {
+            case R.id.menu_delete:
+                deleteSelectedItems();
+                mode.finish(); // Action picked, so close the CAB
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    @Override
+    public boolean onCreateActionMode(ActionMode mode, Menu menu) {
+        // Inflate the menu for the CAB
+        MenuInflater inflater = mode.getMenuInflater();
+        inflater.inflate(R.menu.context, menu);
+        return true;
+    }
+
+    @Override
+    public void onDestroyActionMode(ActionMode mode) {
+        // Here you can make any necessary updates to the activity when
+        // the CAB is removed. By default, selected items are deselected/unchecked.
+    }
+
+    @Override
+    public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
+        // Here you can perform updates to the CAB due to
+        // an {@link android.view.ActionMode#invalidate} request
+        return false;
+    }
+});
+
+ +

Vậy là xong. Lúc này, khi người dùng chọn một mục bằng nhấp giữ, hệ thống sẽ gọi phương pháp {@link +android.widget.AbsListView.MultiChoiceModeListener#onCreateActionMode onCreateActionMode()} +và hiển thị thanh hành động theo ngữ cảnh với các hành động được quy định. Trong khi thanh hành động theo ngữ cảnh +hiển thị, người dùng có thể chọn thêm mục.

+ +

Trong một số trường hợp mà các hành động ngữ cảnh cung cấp các mục hành động chung, bạn có thể muốn +thêm một hộp kiểm hoặc một phần tử UI tương tự để cho phép người dùng chọn các mục, vì +họ có thể không phát hiện được hành vi nhấp giữ. Khi một người dùng chọn hộp kiểm, bạn +có thể gọi ra chế độ hành động theo ngữ cảnh bằng cách thiết đặt mục danh sách tương ứng về trạng thái +đã chọn bằng {@link android.widget.AbsListView#setItemChecked setItemChecked()}.

+ + + + +

Tạo một Menu Bật lên

+ +
+ +

Hình 4. Menu bật lên trong ứng dụng Gmail, được neo vào nút tràn +ở trên cùng bên phải.

+
+ +

{@link android.widget.PopupMenu} là một menu mô thái được neo vào một {@link android.view.View}. +Nó xuất hiện bên dưới dạng xem dấu neo nếu có khoảng trống, hoặc bên trên dạng xem nếu không. Nó có ích cho việc:

+
    +
  • Cung cấp một menu kiểu tràn cho các hành động mà liên quan tới nội dung cụ thể (chẳng hạn như tiêu đề e-mail +của Gmail như minh họa trong hình 4). +

    Lưu ý: Nó không giống như một menu ngữ cảnh, vốn thường +áp dụng cho các hành động mà ảnh hưởng tới nội dung được chọn. Đối với những hành động ảnh hưởng tới nội dung +được chọn, hãy sử dụng chế độ hành động theo ngữ cảnh hoặc menu ngữ cảnh nổi.

  • +
  • Cung cấp một phần thứ hai của câu lệnh (chẳng hạn như một nút được đánh dấu "Thêm" +có chức năng tạo ra một menu bật lên với các tùy chọn "Thêm" khác nhau).
  • +
  • Cung cấp một danh sách thả xuống tương tự như {@link android.widget.Spinner}, nó không giữ lại một +lựa chọn liên tục.
  • +
+ + +

Lưu ý: {@link android.widget.PopupMenu} sẵn có với API +mức 11 trở lên.

+ +

Nếu bạn định nghĩa menu của mình trong XML, sau đây là cách bạn có thể hiển thị menu bật lên:

+
    +
  1. Khởi tạo một {@link android.widget.PopupMenu} bằng hàm dựng của nó, có chức năng đưa +ứng dụng hiện tại {@link android.content.Context} và {@link android.view.View} tới menu +mà sẽ được neo.
  2. +
  3. Sử dụng {@link android.view.MenuInflater} để bung tài nguyên menu của bạn vào đối tượng {@link +android.view.Menu} được trả về bởi {@link +android.widget.PopupMenu#getMenu() PopupMenu.getMenu()}. Trên API mức 14 trở lên, bạn có thể sử dụng +{@link android.widget.PopupMenu#inflate PopupMenu.inflate()} thay thế.
  4. +
  5. Gọi {@link android.widget.PopupMenu#show() PopupMenu.show()}.
  6. +
+ +

Ví dụ, sau đây là một nút với thuộc tính {@link android.R.attr#onClick android:onClick} có chức năng +hiển thị một menu bật lên:

+ +
+<ImageButton
+    android:layout_width="wrap_content" 
+    android:layout_height="wrap_content" 
+    android:src="@drawable/ic_overflow_holo_dark"
+    android:contentDescription="@string/descr_overflow_button"
+    android:onClick="showPopup" />
+
+ +

Khi đó, hoạt động có thể hiển thị menu bật lên như sau:

+ +
+public void showPopup(View v) {
+    PopupMenu popup = new PopupMenu(this, v);
+    MenuInflater inflater = popup.getMenuInflater();
+    inflater.inflate(R.menu.actions, popup.getMenu());
+    popup.show();
+}
+
+ +

Trong API mức 14 trở lên, bạn có thể kết hợp hai dòng có chức năng bung menu bằng {@link +android.widget.PopupMenu#inflate PopupMenu.inflate()}.

+ +

Menu bị bỏ qua khi người dùng chọn một mục hoặc chạm vào bên ngoài vùng +menu. Bạn có thể lắng nghe báo hiệu sự kiện bỏ bằng cách sử dụng {@link +android.widget.PopupMenu.OnDismissListener}.

+ +

Xử lý sự kiện nhấp

+ +

Để thực hiện một +hành động khi người dùng chọn một mục menu, bạn phải triển khai giao diện {@link +android.widget.PopupMenu.OnMenuItemClickListener} và đăng ký nó với {@link +android.widget.PopupMenu} của mình bằng cách gọi {@link android.widget.PopupMenu#setOnMenuItemClickListener +setOnMenuItemclickListener()}. Khi người dùng chọn một mục, hệ thống sẽ gọi lệnh gọi lại {@link +android.widget.PopupMenu.OnMenuItemClickListener#onMenuItemClick onMenuItemClick()} trong +giao diện của bạn.

+ +

Ví dụ:

+ +
+public void showMenu(View v) {
+    PopupMenu popup = new PopupMenu(this, v);
+
+    // This activity implements OnMenuItemClickListener
+    popup.setOnMenuItemClickListener(this);
+    popup.inflate(R.menu.actions);
+    popup.show();
+}
+
+@Override
+public boolean onMenuItemClick(MenuItem item) {
+    switch (item.getItemId()) {
+        case R.id.archive:
+            archive(item);
+            return true;
+        case R.id.delete:
+            delete(item);
+            return true;
+        default:
+            return false;
+    }
+}
+
+ + +

Tạo Nhóm Menu

+ +

Nhóm menu là một tập hợp các mục menu chia sẻ những đặc điểm nhất định. Với một nhóm, bạn có thể +:

+
    +
  • Hiển thị hoặc ẩn tất cả các mục bằng {@link android.view.Menu#setGroupVisible(int,boolean) +setGroupVisible()}
  • +
  • Kích hoạt hoặc vô hiệu hóa tất cả các mục bằng {@link android.view.Menu#setGroupEnabled(int,boolean) +setGroupEnabled()}
  • +
  • Quy định xem tất cả các mục có thể chọn hay không bằng {@link +android.view.Menu#setGroupCheckable(int,boolean,boolean) setGroupCheckable()}
  • +
+ +

Bạn có thể tạo một nhóm bằng cách lồng các phần tử {@code <item>} bên trong một phần tử {@code <group>} +vào tài nguyên menu của bạn hoặc bằng cách quy định một ID nhóm bằng phương pháp {@link +android.view.Menu#add(int,int,int,int) add()}.

+ +

Sau đây là một ví dụ về tài nguyên menu bao gồm một nhóm:

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:id="@+id/menu_save"
+          android:icon="@drawable/menu_save"
+          android:title="@string/menu_save" />
+    <!-- menu group -->
+    <group android:id="@+id/group_delete">
+        <item android:id="@+id/menu_archive"
+              android:title="@string/menu_archive" />
+        <item android:id="@+id/menu_delete"
+              android:title="@string/menu_delete" />
+    </group>
+</menu>
+
+ +

Các mục nằm trong nhóm xuất hiện ở cùng cấp như mục đầu tiên—tất cả ba mục +trong menu đều là các mục đồng cấp. Tuy nhiên, bạn có thể sửa đổi các đặc điểm của hai +mục trong nhóm bằng cách tham chiếu ID nhóm và sử dụng các phương pháp được liệt kê bên trên. Hệ thống cũng sẽ +không bao giờ tách riêng các mục đã ghép nhóm. Ví dụ, nếu bạn khai báo {@code +android:showAsAction="ifRoom"} cho từng mục, chúng sẽ hoặc đều xuất hiện trong thanh hành động +hoặc đều xuất hiện trong phần tràn hành động.

+ + +

Sử dụng mục menu có thể chọn

+ +
+ +

Hình 5. Ảnh chụp màn hình một menu con với các mục +có thể chọn.

+
+ +

Một mục có thể có ích như một giao diện để bật và tắt các tùy chọn, bằng cách sử dụng một hộp kiểm cho +các tùy chọn độc lập, hoặc nút chọn một cho các nhóm +tùy chọn loại trừ lẫn nhau. Hình 5 minh họa một menu con với các mục có thể chọn bằng các nút + chọn một.

+ +

Lưu ý: Các mục menu trong Menu Biểu tượng (từ menu tùy chọn) không thể +hiển thị một hộp kiểm hay nút chọn một. Nếu bạn chọn đặt các mục trong Menu Biểu tượng là có thể chọn, +bạn phải chỉ định trạng thái được chọn bằng cách tráo đổi biểu tượng và/hoặc văn bản +mỗi lần trạng thái thay đổi một cách thủ công.

+ +

Bạn có thể định nghĩa hành vi có thể chọn cho các mục menu riêng lẻ bằng cách sử dụng thuộc tính {@code +android:checkable} trong phần tử {@code <item>}, hoặc cho toàn bộ nhóm với +thuộc tính {@code android:checkableBehavior} trong phần tử {@code <group>}. Ví +dụ, tất cả các mục trong nhóm menu này có thể chọn bằng một nút chọn một:

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+    <group android:checkableBehavior="single">
+        <item android:id="@+id/red"
+              android:title="@string/red" />
+        <item android:id="@+id/blue"
+              android:title="@string/blue" />
+    </group>
+</menu>
+
+ +

Thuộc tính {@code android:checkableBehavior} chấp nhận hoặc: +

+
{@code single}
+
Chỉ chọn được một mục từ nhóm (nút chọn một)
+
{@code all}
+
Có thể chọn được tất cả các mục (hộp kiểm)
+
{@code none}
+
Không chọn được mục nào
+
+ +

Bạn có thể áp dụng một trạng thái được chọn mặc định cho một mục bằng cách sử dụng thuộc tính {@code android:checked} trong +phần tử {@code <item>} và thay đổi nó trong mã bằng phương pháp {@link +android.view.MenuItem#setChecked(boolean) setChecked()}.

+ +

Khi chọn một mục có thể chọn, hệ thống sẽ gọi phương pháp gọi lại mục được chọn tương ứng +(chẳng hạn như {@link android.app.Activity#onOptionsItemSelected(MenuItem) onOptionsItemSelected()}). Chính +ở đây bạn phải đặt trạng thái của hộp kiểm, vì hộp kiểm hay nút chọn một đều không +tự động thay đổi trạng thái của nó. Bạn có thể truy vấn trạng thái hiện tại của mục (như trước khi +người dùng chọn) bằng {@link android.view.MenuItem#isChecked()} và sau đó đặt trạng thái được chọn bằng +{@link android.view.MenuItem#setChecked(boolean) setChecked()}. Ví dụ:

+ +
+@Override
+public boolean onOptionsItemSelected(MenuItem item) {
+    switch (item.getItemId()) {
+        case R.id.vibrate:
+        case R.id.dont_vibrate:
+            if (item.isChecked()) item.setChecked(false);
+            else item.setChecked(true);
+            return true;
+        default:
+            return super.onOptionsItemSelected(item);
+    }
+}
+
+ +

Nếu bạn không đặt trạng thái được chọn bằng cách này, khi đó trạng thái hiển thị của mục (hộp kiểm hoặc +nút chọn một) sẽ không +thay đổi khi người dùng chọn nó. Khi bạn đặt trạng thái, hoạt động sẽ giữ nguyên trạng thái được chọn +của mục đó để khi người dùng mở menu sau, trạng thái được chọn mà bạn đặt +sẽ được hiển thị.

+ +

Lưu ý: +Các mục menu có thể chọn được chỉ định sử dụng trên mỗi phiên và không được lưu sau khi +ứng dụng bị hủy. Nếu bạn có các cài đặt ứng dụng mà bạn muốn lưu cho người dùng, +bạn nên lưu trữ dữ liệu bằng cách sử dụng Tùy chọn dùng chung.

+ + + +

Thêm Mục Menu dựa trên Ý định

+ +

Đôi khi bạn sẽ muốn một mục menu khởi chạy một hoạt động bằng cách sử dụng một {@link android.content.Intent} +(dù đó là một hoạt động trong ứng dụng của bạn hay một ứng dụng khác). Khi bạn biết ý định mà mình +muốn sử dụng và có một mục menu cụ thể sẽ khởi tạo ý định, bạn có thể thực thi ý định +bằng {@link android.app.Activity#startActivity(Intent) startActivity()} trong phương pháp gọi lại +phù hợp theo mục được chọn (chẳng hạn như lệnh gọi lại {@link +android.app.Activity#onOptionsItemSelected(MenuItem) onOptionsItemSelected()}).

+ +

Tuy nhiên, nếu bạn không chắc chắn rằng thiết bị của người dùng +chứa một ứng dụng xử lý ý định đó thì việc thêm một mục menu gọi nó ra có thể dẫn đến +mục menu không hoạt động, do ý định có thể không phân giải thành một +hoạt động. Để giải quyết điều này, Android cho phép bạn linh hoạt thêm các mục menu vào menu của mình +khi Android tìm các hoạt động trên thiết bị để xử lý ý định của bạn.

+ +

Để thêm các mục menu dựa trên các hoạt động sẵn có mà chấp nhận ý định:

+
    +
  1. Định nghĩa một +ý định bằng thể loại {@link android.content.Intent#CATEGORY_ALTERNATIVE} và/hoặc +{@link android.content.Intent#CATEGORY_SELECTED_ALTERNATIVE}, cộng với bất kỳ yêu cầu nào khác.
  2. +
  3. Gọi {@link +android.view.Menu#addIntentOptions(int,int,int,ComponentName,Intent[],Intent,int,MenuItem[]) +Menu.addIntentOptions()}. Sau đó, Android tìm kiếm bất kỳ ứng dụng nào có thể thực hiện ý định +và thêm chúng vào menu của bạn.
  4. +
+ +

Nếu không có ứng dụng được cài đặt +mà thỏa mãn ý định thì không có mục menu nào được thêm vào.

+ +

Lưu ý: +{@link android.content.Intent#CATEGORY_SELECTED_ALTERNATIVE} được sử dụng để xử lý +phần tử đang được chọn trên màn hình. Vì vậy, nó chỉ nên được sử dụng khi tạo một Menu trong {@link +android.app.Activity#onCreateContextMenu(ContextMenu,View,ContextMenuInfo) +onCreateContextMenu()}.

+ +

Ví dụ:

+ +
+@Override
+public boolean onCreateOptionsMenu(Menu menu){
+    super.onCreateOptionsMenu(menu);
+
+    // Create an Intent that describes the requirements to fulfill, to be included
+    // in our menu. The offering app must include a category value of Intent.CATEGORY_ALTERNATIVE.
+    Intent intent = new Intent(null, dataUri);
+    intent.addCategory(Intent.CATEGORY_ALTERNATIVE);
+
+    // Search and populate the menu with acceptable offering applications.
+    menu.addIntentOptions(
+         R.id.intent_group,  // Menu group to which new items will be added
+         0,      // Unique item ID (none)
+         0,      // Order for the items (none)
+         this.getComponentName(),   // The current activity name
+         null,   // Specific items to place first (none)
+         intent, // Intent created above that describes our requirements
+         0,      // Additional flags to control items (none)
+         null);  // Array of MenuItems that correlate to specific items (none)
+
+    return true;
+}
+ +

Đối với mỗi hoạt động được tìm thấy mà cung cấp một bộ lọc ý định khớp với ý định được định nghĩa, một mục menu +được thêm, bằng cách sử dụng giá trị trong android:label của bộ lọc ý định làm +tiêu đề của mục menu và biểu tượng của ứng dụng làm biểu tượng của mục menu. Phương pháp +{@link android.view.Menu#addIntentOptions(int,int,int,ComponentName,Intent[],Intent,int,MenuItem[]) +addIntentOptions()} trả về số mục menu được thêm.

+ +

Lưu ý: Khi bạn gọi {@link +android.view.Menu#addIntentOptions(int,int,int,ComponentName,Intent[],Intent,int,MenuItem[]) +addIntentOptions()}, nó sẽ khống chế bất kỳ và tất cả các mục menu theo nhóm menu được quy định trong tham đối +đầu tiên.

+ + +

Cho phép hoạt động của bạn được thêm vào các menu khác

+ +

Bạn cũng có thể cung cấp các dịch vụ của hoạt động của mình cho các ứng dụng khác, vì vậy ứng dụng của bạn +có thể nằm trong menu của các ứng dụng khác (đảo ngược vai trò nêu trên).

+ +

Để được nằm trong menu của ứng dụng khác, bạn cần định nghĩa một bộ lọc +ý định như bình thường, nhưng đảm bảo thêm các giá trị {@link android.content.Intent#CATEGORY_ALTERNATIVE} +và/hoặc {@link android.content.Intent#CATEGORY_SELECTED_ALTERNATIVE} cho thể loại +bộ lọc ý định. Ví dụ:

+
+<intent-filter label="@string/resize_image">
+    ...
+    <category android:name="android.intent.category.ALTERNATIVE" />
+    <category android:name="android.intent.category.SELECTED_ALTERNATIVE" />
+    ...
+</intent-filter>
+
+ +

Tìm hiểu thêm về việc ghi các bộ lọc ý định trong tài liệu +Ý định và Bộ lọc Ý định.

+ +

Để tham khảo một ứng dụng mẫu sử dụng kỹ thuật này, hãy xem mã mẫu +Note +Pad.

diff --git a/docs/html-intl/intl/vi/guide/topics/ui/notifiers/notifications.jd b/docs/html-intl/intl/vi/guide/topics/ui/notifiers/notifications.jd new file mode 100644 index 0000000000000000000000000000000000000000..5890cb331b965ddb22e97cb2337a58d98bc6897d --- /dev/null +++ b/docs/html-intl/intl/vi/guide/topics/ui/notifiers/notifications.jd @@ -0,0 +1,979 @@ +page.title=Thông báo +@jd:body + + +

+ Thông báo là một thông điệp bạn có thể hiển thị với người dùng bên ngoài + UI bình thường của ứng dụng của bạn. Khi bạn yêu cầu hệ thống phát hành một thông báo, trước tiên nó xuất hiện như một biểu tượng trong + khu vực thông báo. Để xem chi tiết thông báo, người dùng mở + ngăn kéo thông báo. Cả khu vực thông báo và ngăn kéo thông báo đều + là các khu vực do hệ thống kiểm soát mà người dùng có thể xem vào bất cứ lúc nào. +

+ +

+ Hình 1. Thông báo trong khu vực thông báo. +

+ +

+ Hình 2. Thông báo trong ngăn kéo thông báo. +

+ +

Lưu ý: Ngoại trừ phần được lưu ý, hướng dẫn này nhắc đến +lớp {@link android.support.v4.app.NotificationCompat.Builder NotificationCompat.Builder} +trong phiên bản 4 của Thư viện Hỗ trợ. +Lớp {@link android.app.Notification.Builder Notification.Builder} đã được thêm vào trong Android +3.0 (API mức 11).

+ +

Cân nhắc Thiết kế

+ +

Là một phần quan trọng của giao diện người dùng Android, thông báo có các hướng dẫn thiết kế của riêng mình. +Những thay đổi thiết kế cơ bản được giới thiệu trong Android 5.0 (API mức 21) đặc biệt +quan trọng và bạn nên xem phần đào tạo về Thiết kế Material +để biết thêm thông tin. Để tìm hiểu cách thiết kế thông báo và tương tác của chúng, hãy đọc hướng dẫn thiết kế +Thông báo.

+ +

Tạo một Thông báo

+ +

Bạn quy định thông tin UI và các hành động cho một thông báo trong một đối tượng +{@link android.support.v4.app.NotificationCompat.Builder NotificationCompat.Builder}. +Để tạo chính thông báo, bạn gọi +{@link android.support.v4.app.NotificationCompat.Builder#build NotificationCompat.Builder.build()}, +nó sẽ trả về một đối tượng {@link android.app.Notification} chứa những đặc tả của bạn. Để phát hành +thông báo, bạn chuyển đối tượng {@link android.app.Notification} tới hệ thống bằng cách gọi +{@link android.app.NotificationManager#notify NotificationManager.notify()}.

+ +

Nội dung thông báo được yêu cầu

+

+ Một đối tượng {@link android.app.Notification} phải chứa những điều sau: +

+
    +
  • + Một biểu tượng nhỏ được đặt bởi + {@link android.support.v4.app.NotificationCompat.Builder#setSmallIcon setSmallIcon()} +
  • +
  • + Một tiêu đề được đặt bởi + {@link android.support.v4.app.NotificationCompat.Builder#setContentTitle setContentTitle()} +
  • +
  • + Văn bản chi tiết được đặt bởi + {@link android.support.v4.app.NotificationCompat.Builder#setContentText setContentText()} +
  • +
+

Nội dung và cài đặt thông báo tùy chọn

+

+ Tất cả cài đặt và nội dung thông báo khác đều mang tính tùy chọn. Để tìm hiểu thêm về chúng, + hãy xem tài liệu tham khảo cho {@link android.support.v4.app.NotificationCompat.Builder}. +

+ +

Hành động thông báo

+

+ Mặc dù chúng mang tính tùy chọn, bạn nên thêm ít nhất một hành động vào thông báo của mình. + Một hành động cho phép người dùng đi trực tiếp từ thông báo tới một + {@link android.app.Activity} trong ứng dụng của bạn, nơi mà họ có thể xem thêm một hoặc nhiều sự kiện + hoặc làm việc thêm. +

+

+ Một thông báo có thể cung cấp nhiều hành động. Bạn nên luôn định nghĩa hành động mà được + kích khởi khi người dùng nhấp vào thông báo; thường thì hành động này mở ra một + {@link android.app.Activity} trong ứng dụng của bạn. Bạn cũng có thể thêm các nút vào thông báo + để thực hiện những hành động bổ sung chẳng hạn như báo lại báo thức hay hồi đáp ngay lập tức một tin nhắn + văn bản; tính năng này sẵn có từ phiên bản Android 4.1. Nếu sử dụng các nút hành động bổ sung, bạn + cũng phải cung cấp tính năng của chúng trong một {@link android.app.Activity} trong ứng dụng của bạn; xem + phần Xử lý tính tương thích để biết thêm chi tiết. +

+

+ Bên trong một {@link android.app.Notification}, bản thân hành động được định nghĩa bởi một + {@link android.app.PendingIntent} chứa một + {@link android.content.Intent} có chức năng bắt đầu + một {@link android.app.Activity} trong ứng dụng của bạn. Để liên kết + {@link android.app.PendingIntent} với một cử chỉ, hãy gọi phương pháp + {@link android.support.v4.app.NotificationCompat.Builder} phù hợp. Ví dụ, nếu bạn muốn bắt đầu + {@link android.app.Activity} khi người dùng nhấp vào văn bản thông báo trong + ngăn kéo thông báo, bạn hãy thêm {@link android.app.PendingIntent} bằng cách gọi + {@link android.support.v4.app.NotificationCompat.Builder#setContentIntent setContentIntent()}. +

+

+ Bắt đầu một {@link android.app.Activity} khi người dùng nhấp vào thông báo là kịch bản + hành động phổ biến nhất. Bạn cũng có thể bắt đầu một {@link android.app.Activity} khi người dùng + bỏ một thông báo. Trong phiên bản Android 4.1 và sau đó, bạn có thể bắt đầu một + {@link android.app.Activity} từ một nút hành động. Để tìm hiểu thêm, hãy đọc hướng dẫn tham khảo cho + {@link android.support.v4.app.NotificationCompat.Builder}. +

+ +

Mức ưu tiên của thông báo

+

+ Nếu muốn, bạn có thể đặt mức ưu tiên của một thông báo. Mức ưu tiên đóng vai trò + như một gợi ý cho UI của thiết bị về cách thông báo sẽ được hiển thị. + Để đặt mức ưu tiên của một thông báo, hãy gọi {@link + android.support.v4.app.NotificationCompat.Builder#setPriority(int) + NotificationCompat.Builder.setPriority()} và chuyển trong một trong các hằng số mức ưu tiên {@link + android.support.v4.app.NotificationCompat}. Có năm mức ưu tiên, + dao động từ {@link + android.support.v4.app.NotificationCompat#PRIORITY_MIN} (-2) đến {@link + android.support.v4.app.NotificationCompat#PRIORITY_MAX} (2); nếu không được đặt, + mức ưu tiên mặc định thành {@link + android.support.v4.app.NotificationCompat#PRIORITY_DEFAULT} (0). +

+

Để biết thông tin về việc đặt một mức ưu tiên phù hợp, hãy xem phần "Đặt + và quản lý mức ưu tiên của thông báo cho đúng" trong hướng dẫn Thiết kế Thông báo +. +

+ +

Tạo một thông báo đơn giản

+

+ Đoạn mã HTML sau minh họa một thông báo đơn giản, trong đó quy định một hoạt động sẽ mở khi + người dùng nhấp vào thông báo. Để ý rằng đoạn mã này sẽ tạo một đối tượng + {@link android.support.v4.app.TaskStackBuilder} và sử dụng nó để tạo + {@link android.app.PendingIntent} cho hành động. Kiểu mẫu này được giải thích chi tiết hơn + trong phần + Giữ lại Điều hướng khi Bắt đầu một Hoạt động: +

+
+NotificationCompat.Builder mBuilder =
+        new NotificationCompat.Builder(this)
+        .setSmallIcon(R.drawable.notification_icon)
+        .setContentTitle("My notification")
+        .setContentText("Hello World!");
+// Creates an explicit intent for an Activity in your app
+Intent resultIntent = new Intent(this, ResultActivity.class);
+
+// The stack builder object will contain an artificial back stack for the
+// started Activity.
+// This ensures that navigating backward from the Activity leads out of
+// your application to the Home screen.
+TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);
+// Adds the back stack for the Intent (but not the Intent itself)
+stackBuilder.addParentStack(ResultActivity.class);
+// Adds the Intent that starts the Activity to the top of the stack
+stackBuilder.addNextIntent(resultIntent);
+PendingIntent resultPendingIntent =
+        stackBuilder.getPendingIntent(
+            0,
+            PendingIntent.FLAG_UPDATE_CURRENT
+        );
+mBuilder.setContentIntent(resultPendingIntent);
+NotificationManager mNotificationManager =
+    (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+// mId allows you to update the notification later on.
+mNotificationManager.notify(mId, mBuilder.build());
+
+

Vậy là xong. Người dùng của bạn hiện đã được thông báo.

+ +

Áp dụng bố trí mở rộng cho một thông báo

+

+ Để có một thông báo xuất hiện trong một dạng xem mở rộng, trước tiên hãy tạo một đối tượng + {@link android.support.v4.app.NotificationCompat.Builder} với các tùy chọn dạng xem thông thường + mà bạn muốn. Tiếp theo, hãy gọi {@link android.support.v4.app.NotificationCompat.Builder#setStyle + Builder.setStyle()} với một đối tượng bố trí mở rộng làm tham đối. +

+

+ Ghi nhớ rằng các thông báo mở rộng không sẵn có trên các nền tảng trước Android 4.1. Để + tìm hiểu về cách xử lý thông báo đối với nền tảng phiên bản Android 4.1 và trước đó, hãy đọc + phần Xử lý tính tương thích. +

+

+ Ví dụ, đoạn mã HTML sau minh họa cách thay đổi thông báo được tạo + trong đoạn mã HTML trước để sử dụng bố trí mở rộng: +

+
+NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(this)
+    .setSmallIcon(R.drawable.notification_icon)
+    .setContentTitle("Event tracker")
+    .setContentText("Events received")
+NotificationCompat.InboxStyle inboxStyle =
+        new NotificationCompat.InboxStyle();
+String[] events = new String[6];
+// Sets a title for the Inbox in expanded layout
+inboxStyle.setBigContentTitle("Event tracker details:");
+...
+// Moves events into the expanded layout
+for (int i=0; i < events.length; i++) {
+
+    inboxStyle.addLine(events[i]);
+}
+// Moves the expanded layout object into the notification object.
+mBuilder.setStyle(inBoxStyle);
+...
+// Issue the notification here.
+
+ +

Xử lý tính tương thích

+ +

+ Không phải tất cả tính năng thông báo đều sẵn có đối với một phiên bản cụ thể, mặc dù + các phương pháp đặt chúng đều nằm trong lớp thư viện hỗ trợ + {@link android.support.v4.app.NotificationCompat.Builder NotificationCompat.Builder}. + Ví dụ, nút hành động phụ thuộc vào thông báo mở rộng chỉ xuất hiện trên phiên bản Android + 4.1 trở lên, bởi bản thân thông báo mở rộng chỉ sẵn có trên phiên bản + Android 4.1 trở lên. +

+

+ Để đảm bảo tính tương thích tốt nhất, hãy tạo thông báo bằng + {@link android.support.v4.app.NotificationCompat NotificationCompat} và các lớp con của nó, + đặc biệt là {@link android.support.v4.app.NotificationCompat.Builder + NotificationCompat.Builder}. Bên cạnh đó, hãy tuân theo tiến trình sau khi bạn triển khai một thông báo: +

+
    +
  1. + Cung cấp tất cả tính năng thông báo cho tất cả người dùng, không phụ thuộc vào phiên bản + mà họ đang sử dụng. Để làm vậy, hãy xác minh rằng tất cả tính năng đều sẵn có từ một + {@link android.app.Activity} trong ứng dụng của bạn. Bạn có thể muốn thêm một + {@link android.app.Activity} mới để làm điều này. +

    + Ví dụ, nếu bạn muốn sử dụng + {@link android.support.v4.app.NotificationCompat.Builder#addAction addAction()} để + cung cấp khả năng điều khiển dừng và bắt đầu phát lại phương tiện, trước tiên hãy triển khai khả năng + điều khiển này trong một {@link android.app.Activity} trong ứng dụng của bạn. +

    +
  2. +
  3. + Đảm bảo rằng tất cả người dùng đều có thể tiếp cận với tính năng trong {@link android.app.Activity}, + bằng cách cho nó khởi động khi người dùng nhấp vào thông báo. Để làm điều này, + hãy tạo một {@link android.app.PendingIntent} + cho {@link android.app.Activity}. Gọi + {@link android.support.v4.app.NotificationCompat.Builder#setContentIntent + setContentIntent()} để thêm {@link android.app.PendingIntent} vào thông báo. +
  4. +
  5. + Bây giờ, hãy thêm các tính năng thông báo mở rộng mà bạn muốn sử dụng vào thông báo. Ghi nhớ + rằng bất kỳ tính năng nào mà bạn thêm cũng phải sẵn có trong {@link android.app.Activity} + mà bắt đầu khi người dùng nhấp vào thông báo. +
  6. +
+ + + + +

Quản lý Thông báo

+

+ Khi cần phát hành một thông báo nhiều lần cho cùng loại sự kiện, bạn + nên tránh tạo một thông báo hoàn toàn mới. Thay vào đó, bạn nên cân nhắc cập nhật + một thông báo trước đó, hoặc bằng cách thay đổi một vài giá trị hoặc bằng cách thêm vào nó, hoặc cả hai. +

+

+ Ví dụ, Gmail thông báo với người dùng rằng e-mail mới đã đến bằng cách tăng số đếm + tin nhắn chưa đọc và bằng cách thêm một phần tóm tắt từng e-mail vào thông báo. Đây được gọi là + "xếp chồng" thông báo; nó được mô tả chi tiết hơn trong phần hướng dẫn Thiết kế + Thông báo. +

+

+ Lưu ý: Tính năng Gmail này yêu cầu bố trí mở rộng "hộp thư đến", đó là + một phần của tính năng thông báo mở rộng sẵn có bắt đầu từ Android 4.1. +

+

+ Phần sau mô tả cách cập nhật thông báo và cả cách loại bỏ chúng. +

+

Cập nhật thông báo

+

+ Để thiết lập một thông báo để nó có thể được cập nhật, hãy phát hành nó cùng một ID thông báo bằng cách gọi + {@link android.app.NotificationManager#notify(int, android.app.Notification) NotificationManager.notify()}. + Để cập nhật thông báo sau khi bạn đã phát hành + nó, hãy cập nhật hoặc tạo một đối tượng {@link android.support.v4.app.NotificationCompat.Builder}, + xây dựng một đối tượng {@link android.app.Notification} từ nó, và phát hành + {@link android.app.Notification} với cùng ID mà bạn đã sử dụng trước đó. Nếu + thông báo trước đó vẫn hiển thị, hệ thống sẽ cập nhật nó từ nội dung của + đối tượng {@link android.app.Notification}. Nếu thông báo trước đó đã bị bỏ đi, một + thông báo mới sẽ được tạo thay thế. +

+

+ Đoạn mã HTML sau minh họa một thông báo được cập nhật để phản ánh + số sự kiện đã xảy ra. Nó xếp chồng thông báo, hiển thị một tóm tắt: +

+
+mNotificationManager =
+        (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+// Sets an ID for the notification, so it can be updated
+int notifyID = 1;
+mNotifyBuilder = new NotificationCompat.Builder(this)
+    .setContentTitle("New Message")
+    .setContentText("You've received new messages.")
+    .setSmallIcon(R.drawable.ic_notify_status)
+numMessages = 0;
+// Start of a loop that processes data and then notifies the user
+...
+    mNotifyBuilder.setContentText(currentText)
+        .setNumber(++numMessages);
+    // Because the ID remains unchanged, the existing notification is
+    // updated.
+    mNotificationManager.notify(
+            notifyID,
+            mNotifyBuilder.build());
+...
+
+ + +

Loại bỏ thông báo

+

+ Thông báo vẫn hiển thị cho tới khi xảy ra một trong những điều sau: +

+
    +
  • + Người dùng bỏ từng thông báo một hoặc bỏ tất cả bằng cách sử dụng "Xóa Tất cả" (nếu + thông báo có thể xóa được). +
  • +
  • + Người dùng nhấp vào thông báo và bạn đã gọi + {@link android.support.v4.app.NotificationCompat.Builder#setAutoCancel setAutoCancel()} khi + tạo thông báo. +
  • +
  • + Bạn gọi {@link android.app.NotificationManager#cancel(int) cancel()} cho một ID thông báo + cụ thể. Phương pháp này cũng xóa các thông báo đang diễn ra. +
  • +
  • + Bạn gọi {@link android.app.NotificationManager#cancelAll() cancelAll()}, nó sẽ xóa + tất cả thông báo mà bạn đã phát hành trước đó. +
  • +
+ + +

Giữ lại Điều hướng khi Bắt đầu một Hoạt động

+

+ Khi bạn bắt đầu một {@link android.app.Activity} từ một thông báo, bạn phải giữ lại trải nghiệm điều hướng kỳ vọng + của người dùng. Nhấp vào Quay lại sẽ đưa người dùng quay lại thông qua + tiến trình làm việc bình thường của ứng dụng về màn hình Trang chủ, và nhấp vào Gần đây sẽ hiển thị + {@link android.app.Activity} như một tác vụ riêng. Để giữ lại trải nghiệm điều hướng, bạn + nên bắt đầu {@link android.app.Activity} trong một tác vụ mới. Cách bạn thiết lập + {@link android.app.PendingIntent} để cấp cho bạn một tác vụ mới sẽ phụ thuộc vào tính chất của + {@link android.app.Activity} mà bạn đang bắt đầu. Có hai tình huống thông thường: +

+
+
+ Hoạt động thường xuyên +
+
+ Bạn đang bắt đầu một {@link android.app.Activity} là một phần của tiến trình công việc + bình thường của ứng dụng. Trong tình huống này, hãy thiết lập {@link android.app.PendingIntent} để + bắt đầu một tác vụ mới, và cung cấp {@link android.app.PendingIntent} với một ngăn xếp + có chức năng tái tạo lại hành vi thông thường Quay lại của ứng dụng. +

+ Thông báo từ ứng dụng Gmail thể hiện điều này. Khi bạn nhấp vào một thông báo + cho một thư e-mail đơn lẻ, bạn sẽ thấy chính thư đó. Chạm vào Quay lại sẽ đưa bạn + ngược lại qua Gmail về màn hình Trang chủ, giống như thể bạn đã vào Gmail từ + màn hình Trang chủ chứ không phải vào từ một thông báo. +

+

+ Điều này xảy ra mà không phụ thuộc vào ứng dụng bạn đang ở trong khi chạm vào + thông báo. Ví dụ, nếu bạn đang vào Gmail để soạn thư, và bạn nhấp vào một + thông báo cho một e-mail đơn lẻ, bạn sẽ đến e-mail đó ngay lập tức. Chạm vào Quay lại + sẽ đưa bạn về hộp thư đến rồi tới màn hình Trang chủ, thay vì đưa bạn tới + thư mà bạn đang soạn. +

+
+
+ Hoạt động đặc biệt +
+
+ Người dùng chỉ thấy {@link android.app.Activity} này nếu nó được bắt đầu từ một thông báo. + Nghĩa là, {@link android.app.Activity} mở rộng thông báo bằng cách cung cấp + thông tin mà sẽ khó hiển thị trong chính thông báo đó. Đối với tình huống này, + hãy thiết lập {@link android.app.PendingIntent} để bắt đầu một tác vụ mới. Tuy nhiên, không cần tạo + một ngăn xếp vì {@link android.app.Activity} được bắt đầu không phải là một phần trong + tiến trình hoạt động của ứng dụng. Nhấp vào Quay lại sẽ vẫn đưa người dùng đến + màn hình Trang chủ. +
+
+ +

Thiết đặt một PendingIntent cho hoạt động thường xuyên

+

+ Để thiết lập {@link android.app.PendingIntent} để bắt đầu một mục nhập trực tiếp + {@link android.app.Activity}, hãy làm theo những bước sau: +

+
    +
  1. + Định nghĩa phân cấp {@link android.app.Activity} của ứng dụng của bạn trong bản kê khai. +
      +
    1. + Thêm hỗ trợ cho phiên bản Android 4.0.3 và trước đó. Để làm điều này, hãy quy định mẹ của + {@link android.app.Activity} mà bạn đang bắt đầu bằng cách thêm phần tử +<meta-data> + làm con của +<activity>. +

      + Đối với phần tử này, hãy đặt +android:name="android.support.PARENT_ACTIVITY". + Đặt +android:value="<parent_activity_name>" + trong đó <parent_activity_name> là giá trị của +android:name + đối với phần tử +<activity> + mẹ. Xem ví dụ trong XML sau. +

      +
    2. +
    3. + Cũng thêm hỗ trợ cho phiên bản Android 4.1 và sau đó. Để làm điều này, hãy thêm thuộc tính +android:parentActivityName + vào phần tử +<activity> + của {@link android.app.Activity} mà bạn đang bắt đầu. +
    4. +
    +

    + XML cuối cùng sẽ trông như sau: +

    +
    +<activity
    +    android:name=".MainActivity"
    +    android:label="@string/app_name" >
    +    <intent-filter>
    +        <action android:name="android.intent.action.MAIN" />
    +        <category android:name="android.intent.category.LAUNCHER" />
    +    </intent-filter>
    +</activity>
    +<activity
    +    android:name=".ResultActivity"
    +    android:parentActivityName=".MainActivity">
    +    <meta-data
    +        android:name="android.support.PARENT_ACTIVITY"
    +        android:value=".MainActivity"/>
    +</activity>
    +
    +
  2. +
  3. + Tạo một ngăn xếp dựa trên {@link android.content.Intent} mà bắt đầu + {@link android.app.Activity}: +
      +
    1. + Tạo {@link android.content.Intent} để bắt đầu {@link android.app.Activity}. +
    2. +
    3. + Tạo một bộ dựng chồng bằng cách gọi {@link android.app.TaskStackBuilder#create + TaskStackBuilder.create()}. +
    4. +
    5. + Thêm ngăn xếp vào bộ dựng ngăn xếp bằng cách gọi + {@link android.support.v4.app.TaskStackBuilder#addParentStack addParentStack()}. + Đối với mỗi {@link android.app.Activity} trong phân cấp mà bạn đã định nghĩa trong + bản kê khai, ngăn xếp chứa một đối tượng {@link android.content.Intent} mà + sẽ khởi động {@link android.app.Activity}. Phương pháp này cũng thêm cờ có chức năng bắt đầu + chồng trong một tác vụ mới. +

      + Lưu ý: Mặc dù tham đối đến + {@link android.support.v4.app.TaskStackBuilder#addParentStack addParentStack()} + là một tham chiếu tới {@link android.app.Activity} được bắt đầu, phương pháp gọi + không thêm {@link android.content.Intent} có chức năng bắt đầu + {@link android.app.Activity}. Thay vào đó, nó được xử lý ở bước tiếp theo. +

      +
    6. +
    7. + Thêm {@link android.content.Intent} có chức năng bắt đầu {@link android.app.Activity} + từ thông báo bằng cách gọi + {@link android.support.v4.app.TaskStackBuilder#addNextIntent addNextIntent()}. + Chuyển {@link android.content.Intent} mà bạn đã tạo ở bước đầu tiên làm + tham đối tới + {@link android.support.v4.app.TaskStackBuilder#addNextIntent addNextIntent()}. +
    8. +
    9. + Nếu bạn cần, hãy thêm các tham đối tới các đối tượng {@link android.content.Intent} trên chồng + bằng cách gọi {@link android.support.v4.app.TaskStackBuilder#editIntentAt + TaskStackBuilder.editIntentAt()}. Đôi khi cần phải đảm bảo rằng + {@link android.app.Activity} mục tiêu sẽ hiển thị dữ liệu có ý nghĩa khi người dùng điều hướng + tới nó bằng cách sử dụng Quay lại. +
    10. +
    11. + Nhận một {@link android.app.PendingIntent} cho ngăn xếp này bằng cách gọi + {@link android.support.v4.app.TaskStackBuilder#getPendingIntent getPendingIntent()}. + Sau đó, bạn có thể sử dụng {@link android.app.PendingIntent} này làm tham đối tới + {@link android.support.v4.app.NotificationCompat.Builder#setContentIntent + setContentIntent()}. +
    12. +
    +
  4. +
+

+ Đoạn mã HTML sau minh họa tiến trình: +

+
+...
+Intent resultIntent = new Intent(this, ResultActivity.class);
+TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);
+// Adds the back stack
+stackBuilder.addParentStack(ResultActivity.class);
+// Adds the Intent to the top of the stack
+stackBuilder.addNextIntent(resultIntent);
+// Gets a PendingIntent containing the entire back stack
+PendingIntent resultPendingIntent =
+        stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
+...
+NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
+builder.setContentIntent(resultPendingIntent);
+NotificationManager mNotificationManager =
+    (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+mNotificationManager.notify(id, builder.build());
+
+ +

Thiết đặt một PendingIntent cho hoạt động đặc biệt

+

+ Phần sau mô tả cách thiết lập một + {@link android.app.PendingIntent} cho hoạt động đặc biệt. +

+

+ Một {@link android.app.Activity} đặc biệt không cần ngăn xếp, vì thế bạn không phải + định nghĩa phân cấp {@link android.app.Activity} của nó trong bản kê khai, và bạn không phải + gọi + {@link android.support.v4.app.TaskStackBuilder#addParentStack addParentStack()} để xây dựng một + ngăn xếp. Thay vào đó, hãy sử dụng bản kê khai để thiết lập các tùy chọn tác vụ {@link android.app.Activity}, + và tạo {@link android.app.PendingIntent} bằng cách gọi + {@link android.app.PendingIntent#getActivity getActivity()}: +

+
    +
  1. + Trong bản kê khai của mình, hãy thêm các thuộc tính sau vào phần tử +<activity> + cho {@link android.app.Activity} +
    +
    +android:name="activityclass" +
    +
    + Tên lớp được xác định đầy đủ của hoạt động. +
    +
    +android:taskAffinity="" +
    +
    + Kết hợp với cờ + {@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK FLAG_ACTIVITY_NEW_TASK} + mà bạn đặt trong mã, điều này đảm bảo rằng {@link android.app.Activity} này không + đi đến tác vụ mặc định của ứng dụng. Bất kỳ tác vụ hiện tại nào mà có + bố trí mặc định của ứng dụng đều không bị ảnh hưởng. +
    +
    +android:excludeFromRecents="true" +
    +
    + Loại bỏ tác vụ mới khỏi Gần đây, sao cho người dùng không thể vô tình + điều hướng quay lại nó. +
    +
    +

    + Đoạn mã HTML này thể hiện phần tử: +

    +
    +<activity
    +    android:name=".ResultActivity"
    +...
    +    android:launchMode="singleTask"
    +    android:taskAffinity=""
    +    android:excludeFromRecents="true">
    +</activity>
    +...
    +
    +
  2. +
  3. + Xây dựng và phát hành thông báo: +
      +
    1. + Tạo một {@link android.content.Intent} có chức năng bắt đầu + {@link android.app.Activity}. +
    2. +
    3. + Đặt {@link android.app.Activity} để bắt đầu trong một tác vụ mới, trống bằng cách gọi + {@link android.content.Intent#setFlags setFlags()} với các cờ + {@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK FLAG_ACTIVITY_NEW_TASK} + và + {@link android.content.Intent#FLAG_ACTIVITY_CLEAR_TASK FLAG_ACTIVITY_CLEAR_TASK}. +
    4. +
    5. + Đặt bất kỳ tùy chọn nào khác mà bạn cần cho {@link android.content.Intent}. +
    6. +
    7. + Tạo một {@link android.app.PendingIntent} từ {@link android.content.Intent} + bằng cách gọi {@link android.app.PendingIntent#getActivity getActivity()}. + Sau đó, bạn có thể sử dụng {@link android.app.PendingIntent} này làm tham đối tới + {@link android.support.v4.app.NotificationCompat.Builder#setContentIntent + setContentIntent()}. +
    8. +
    +

    + Đoạn mã HTML sau minh họa tiến trình: +

    +
    +// Instantiate a Builder object.
    +NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
    +// Creates an Intent for the Activity
    +Intent notifyIntent =
    +        new Intent(this, ResultActivity.class);
    +// Sets the Activity to start in a new, empty task
    +notifyIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
    +                        | Intent.FLAG_ACTIVITY_CLEAR_TASK);
    +// Creates the PendingIntent
    +PendingIntent notifyPendingIntent =
    +        PendingIntent.getActivity(
    +        this,
    +        0,
    +        notifyIntent,
    +        PendingIntent.FLAG_UPDATE_CURRENT
    +);
    +
    +// Puts the PendingIntent into the notification builder
    +builder.setContentIntent(notifyPendingIntent);
    +// Notifications are issued by sending them to the
    +// NotificationManager system service.
    +NotificationManager mNotificationManager =
    +    (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
    +// Builds an anonymous Notification object from the builder, and
    +// passes it to the NotificationManager
    +mNotificationManager.notify(id, builder.build());
    +
    +
  4. +
+ + +

Hiển thị Tiến độ trong một Thông báo

+

+ Thông báo có thể bao gồm một chỉ báo tiến độ dạng hoạt ảnh để cho người dùng thấy trạng thái + của một thao tác đang diễn ra. Nếu bạn có thể ước lượng thao tác mất bao lâu và nó được + được hoàn thành bao nhiêu vào bất cứ lúc nào, hãy sử dụng hình thức "xác định" của chỉ báo + (thanh tiến độ). Nếu bạn không thể ước lượng thời lượng của thao tác, hãy sử dụng hình thức + "không xác định" của chỉ báo (chỉ báo hoạt động). +

+

+ Chỉ báo tiến độ được hiển thị bằng triển khai lớp + {@link android.widget.ProgressBar} của nền tảng. +

+

+ Để sử dụng chỉ báo tiến độ trên các nền tảng bắt đầu từ Android 4.0, hãy gọi + {@link android.support.v4.app.NotificationCompat.Builder#setProgress setProgress()}. Đối + với các phiên bản trước đây, bạn phải tạo bố trí thông báo tùy chỉnh của chính mình + trong đó chứa một dạng xem {@link android.widget.ProgressBar}. +

+

+ Phần sau đây mô tả cách hiển thị tiến độ trong một thông báo bằng cách sử dụng + {@link android.support.v4.app.NotificationCompat.Builder#setProgress setProgress()}. +

+ +

Hiển thị một chỉ báo tiến độ thời lượng cố định

+

+ Để hiển thị một thanh tiến độ xác định, hãy thêm thanh vào thông báo của bạn bằng cách gọi + {@link android.support.v4.app.NotificationCompat.Builder#setProgress + setProgress(max, progress, false)} rồi phát hành thông báo. Khi thông báo của bạn tiến hành, + tăng dầnprogress, và cập nhật thông báo. Khi kết thúc thao tác, + progress sẽ bằng max. Một cách thông thường để gọi + {@link android.support.v4.app.NotificationCompat.Builder#setProgress setProgress()} + đó là đặt max thành 100 rồi tăng dần progress dưới dạng giá trị + "phần trăm hoàn thành" cho thao tác. +

+

+ Bạn có thể hoặc để thanh tiến độ hiển thị khi nào thì thao tác hoàn thành, hoặc loại bỏ nó. Dù + trong trường hợp nào hãy nhớ cập nhật văn bản thông báo để hiển thị thao tác hoàn tất. + Để loại bỏ thanh tiến độ, hãy gọi + {@link android.support.v4.app.NotificationCompat.Builder#setProgress + setProgress(0, 0, false)}. Ví dụ: +

+
+...
+mNotifyManager =
+        (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+mBuilder = new NotificationCompat.Builder(this);
+mBuilder.setContentTitle("Picture Download")
+    .setContentText("Download in progress")
+    .setSmallIcon(R.drawable.ic_notification);
+// Start a lengthy operation in a background thread
+new Thread(
+    new Runnable() {
+        @Override
+        public void run() {
+            int incr;
+            // Do the "lengthy" operation 20 times
+            for (incr = 0; incr <= 100; incr+=5) {
+                    // Sets the progress indicator to a max value, the
+                    // current completion percentage, and "determinate"
+                    // state
+                    mBuilder.setProgress(100, incr, false);
+                    // Displays the progress bar for the first time.
+                    mNotifyManager.notify(0, mBuilder.build());
+                        // Sleeps the thread, simulating an operation
+                        // that takes time
+                        try {
+                            // Sleep for 5 seconds
+                            Thread.sleep(5*1000);
+                        } catch (InterruptedException e) {
+                            Log.d(TAG, "sleep failure");
+                        }
+            }
+            // When the loop is finished, updates the notification
+            mBuilder.setContentText("Download complete")
+            // Removes the progress bar
+                    .setProgress(0,0,false);
+            mNotifyManager.notify(ID, mBuilder.build());
+        }
+    }
+// Starts the thread by calling the run() method in its Runnable
+).start();
+
+ + +

Hiển thị một chỉ báo hoạt động liên tục

+

+ Để hiển thị một chỉ báo hoạt động không xác định, hãy thêm nó vào thông báo của bạn bằng + {@link android.support.v4.app.NotificationCompat.Builder#setProgress setProgress(0, 0, true)} + (hai tham đối đầu tiên bị bỏ qua), và phát hành thông báo. Kết quả là một chỉ báo + mà có cùng kiểu như một thanh tiến độ, khác ở chỗ hoạt ảnh của nó đang diễn ra. +

+

+ Phát hành thông báo khi bắt đầu thao tác. Hoạt ảnh sẽ chạy tới khi bạn + sửa đổi thông báo của mình. Khi thao tác hoàn thành, hãy gọi + {@link android.support.v4.app.NotificationCompat.Builder#setProgress setProgress(0, 0, false)} + và rồi cập nhật thông báo để loại bỏ chỉ báo hoạt động. + Luôn làm điều này; nếu không, hoạt ảnh sẽ chạy ngay cả khi thao tác đã hoàn thành. Đồng thời + hãy nhớ thay đổi văn bản thông báo để biểu thị thao tác hoàn tất. +

+

+ Để xem chỉ báo hoạt động vận hành như thế nào, hãy tham khảo đoạn mã HTML trước. Định vị các dòng sau: +

+
+// Sets the progress indicator to a max value, the current completion
+// percentage, and "determinate" state
+mBuilder.setProgress(100, incr, false);
+// Issues the notification
+mNotifyManager.notify(0, mBuilder.build());
+
+

+ Thay thế các dòng bạn đã tìm thấy bằng các dòng sau: +

+
+ // Sets an activity indicator for an operation of indeterminate length
+mBuilder.setProgress(0, 0, true);
+// Issues the notification
+mNotifyManager.notify(0, mBuilder.build());
+
+ +

Siêu dữ liệu Thông báo

+ +

Thông báo có thể được sắp xếp theo siêu dữ liệu mà bạn gán cho bằng các +phương pháp {@link android.support.v4.app.NotificationCompat.Builder} sau:

+ +
    +
  • {@link android.support.v4.app.NotificationCompat.Builder#setCategory(java.lang.String) setCategory()} + thông báo hệ thống cách xử lý thông báo ứng dụng của bạn khi thiết bị đang trong chế độ Ưu tiên + (ví dụ, nếu thông báo của bạn biểu diễn một cuộc gọi đến, tin nhắn tức thời hoặc báo thức).
  • +
  • {@link android.support.v4.app.NotificationCompat.Builder#setPriority(int) setPriority()} khiến + thông báo với trường mức ưu tiên được đặt thành {@code PRIORITY_MAX} hoặc {@code PRIORITY_HIGH} xuất hiện + trong một cửa sổ nổi nhỏ nếu thông báo cũng có âm thanh hoặc rung.
  • +
  • {@link android.support.v4.app.NotificationCompat.Builder#addPerson(java.lang.String) addPerson()} + cho phép bạn thêm một danh sách người vào thông báo. Ứng dụng của bạn có thể sử dụng tín hiệu này cho + hệ thống mà nó sẽ nhóm cùng với thông báo từ những người được quy định, hoặc xếp hạng thông báo + từ những người này là quan trọng hơn.
  • +
+ +
+ +

+ Hình 3. Hoạt động toàn màn hình thể hiện một thông báo cảnh báo +

+
+ +

Thông báo Cảnh báo

+ +

Với Android 5.0 (API mức 21), thông báo có thể xuất hiện trong một cửa sổ nổi nhỏ +(còn gọi là thông báo cảnh báo) khi thiết bị hiện hoạt +(tức là thiết bị được mở khóa và màn hình của nó đang bật). Những thông báo +này có vẻ tương tự như dạng rút gọn thông báo của bạn, chỉ khác là +thông báo cảnh báo cũng hiển thị các nút hành động. Người dùng có thể hành động trên đó, hoặc bỏ, +một thông báo cảnh báo mà không phải rời khỏi ứng dụng hiện tại.

+ +

Các ví dụ về điều kiện có thể kích khởi thông báo cảnh báo bao gồm:

+ +
    +
  • Hoạt động của người dùng đang trong chế độ toàn màn hình (ứng dụng sử dụng +{@link android.app.Notification#fullScreenIntent}), hoặc
  • +
  • Thông báo có mức ưu tiên cao và sử dụng nhạc chuông hoặc + rung
  • +
+ +

Thông báo Màn hình Khóa

+ +

Với việc phát hành Android 5.0 (API mức 21), giờ đây thông báo có thể xuất hiện trên màn hình +khóa. Ứng dụng của bạn có thể sử dụng tính năng này để cung cấp các chức năng điều khiển phát lại phương tiện và các hành động +thông dụng khác. Người dùng có thể chọn thông qua Cài đặt để xem có hiển thị thông báo trên màn hình khóa không, và +bạn có thể chỉ định xem thông báo từ ứng dụng của mình có hiển thị trên màn hình khóa không.

+ +

Thiết đặt Khả năng Hiển thị

+ +

Ứng dụng của bạn có thể điều khiển mức chi tiết có thể nhìn thấy được trong thông báo được hiển thị trên một +màn hình khóa bảo mật. Bạn gọi {@link android.support.v4.app.NotificationCompat.Builder#setVisibility(int) setVisibility()} +và quy định một trong những giá trị sau:

+ +
    +
  • {@link android.support.v4.app.NotificationCompat#VISIBILITY_PUBLIC} hiển thị đầy đủ nội dung của + thông báo.
  • +
  • {@link android.support.v4.app.NotificationCompat#VISIBILITY_SECRET} không hiển thị bất kỳ phần nào của + thông báo này trên màn hình khóa.
  • +
  • {@link android.support.v4.app.NotificationCompat#VISIBILITY_PRIVATE} hiển thị thông tin cơ bản, + chẳng hạn như biểu tượng và tiêu đề nội dung của thông báo, nhưng ẩn nội dung đầy đủ của thông báo.
  • +
+ +

Khi {@link android.support.v4.app.NotificationCompat#VISIBILITY_PRIVATE} được đặt, bạn cũng có thể +cung cấp một phiên bản thay thế cho nội dung thông báo, trong đó ẩn một số chi tiết nhất định. Ví dụ, +một ứng dụng SMS có thể hiển thị một thông báo hiển thị Bạn có 3 tin nhắn văn bản mới, nhưng ẩn +nội dung của tin nhắn và người gửi. Để cung cấp thông báo thay thế này, trước tiên hãy tạo thông báo +thay thế bằng cách sử dụng {@link android.support.v4.app.NotificationCompat.Builder}. Khi bạn tạo +đối tượng thông báo riêng tư, hãy đính kèm thông báo thay thế cho nó thông qua phương pháp +{@link android.support.v4.app.NotificationCompat.Builder#setPublicVersion(android.app.Notification) setPublicVersion()} +.

+ +

Điều khiển Phát lại Phương tiện trên Màn hình Khóa

+ +

Trong Android 5.0 (API mức 21) màn hình khóa không còn hiển thị điều khiển phương tiện +dựa trên {@link android.media.RemoteControlClient}, điều mà nay đã bị bỏ đi. Thay vào đó, hãy sử dụng mẫu +{@link android.app.Notification.MediaStyle} với phương pháp +{@link android.app.Notification.Builder#addAction(android.app.Notification.Action) addAction()} +, có chức năng chuyển hành động thành các biểu tượng có thể nhấp.

+ +

Lưu ý: Mẫu và phương pháp {@link android.app.Notification.Builder#addAction(android.app.Notification.Action) addAction()} +không nằm trong thư viện hỗ trợ, vì thế những tính năng này chỉ chạy trong phiên bản Android 5.0 trở lên +.

+ +

Để hiển thị điều khiển phát lại phương tiện trên màn hình khóa trong Android 5.0, hãy đặt mức độ nhìn thấy +thành {@link android.support.v4.app.NotificationCompat#VISIBILITY_PUBLIC}, như mô tả bên trên. Sau đó thêm +các hành động và đặt mẫu {@link android.app.Notification.MediaStyle}, như được mô tả trong +đoạn mã mẫu sau:

+ +
+Notification notification = new Notification.Builder(context)
+    // Show controls on lock screen even when user hides sensitive content.
+    .setVisibility(Notification.VISIBILITY_PUBLIC)
+    .setSmallIcon(R.drawable.ic_stat_player)
+    // Add media control buttons that invoke intents in your media service
+    .addAction(R.drawable.ic_prev, "Previous", prevPendingIntent) // #0
+    .addAction(R.drawable.ic_pause, "Pause", pausePendingIntent)  // #1
+    .addAction(R.drawable.ic_next, "Next", nextPendingIntent)     // #2
+    // Apply the media style template
+    .setStyle(new Notification.MediaStyle()
+    .setShowActionsInCompactView(1 /* #1: pause button */)
+    .setMediaSession(mMediaSession.getSessionToken())
+    .setContentTitle("Wonderful music")
+    .setContentText("My Awesome Band")
+    .setLargeIcon(albumArtBitmap)
+    .build();
+
+ +

Lưu ý: Việc rút bỏ {@link android.media.RemoteControlClient} +còn có nhiều ý nghĩa khác đối với việc điều khiển phương tiện. Xem phần +Điều khiển Phát lại Phương tiện +để biết thêm thông tin về các API mới đối với quản lý phiên phương tiện và điều khiển phát lại này.

+ + + +

Bố trí Thông báo Tùy chỉnh

+

+ Khuôn khổ thông báo cho phép bạn định nghĩa một bố trí thông báo tùy chỉnh, nó + định nghĩa hình thức của thông báo trong một đối tượng {@link android.widget.RemoteViews}. + Thông báo có bố trí tùy chỉnh tương tự như thông báo thường, nhưng chúng được dựa trên + {@link android.widget.RemoteViews} được định nghĩa trong một tệp bố trí XML. +

+

+ Chiều cao sẵn có cho bố trí thông báo tùy chỉnh phụ thuộc vào dạng xem thông báo. Bố trí dạng xem bình thường + được giới hạn ở 64 dp, và bố trí dạng xem mở rộng được giới hạn ở 256 dp. +

+

+ Để định nghĩa một bố trí thông báo tùy chỉnh, hãy bắt đầu bằng việc khởi tạo đối tượng + {@link android.widget.RemoteViews} để bung một tệp bố trí XML. Sau đó, + thay vì gọi các phương pháp như + {@link android.support.v4.app.NotificationCompat.Builder#setContentTitle setContentTitle()}, + hãy gọi {@link android.support.v4.app.NotificationCompat.Builder#setContent setContent()}. Để đặt + chi tiết nội dung trong thông báo tùy chỉnh, hãy sử dụng các phương pháp + {@link android.widget.RemoteViews} để đặt giá trị của các tập con của dạng xem: +

+
    +
  1. + Tạo một bố trí XML cho thông báo trong một tệp riêng. Bạn có thể sử dụng bất kỳ tên tệp nào + mà bạn muốn, nhưng phải sử dụng phần mở rộng .xml +
  2. +
  3. + Trong ứng dụng của bạn, hãy sử dụng các phương pháp {@link android.widget.RemoteViews} để định nghĩa các biểu tượng và văn bản + của thông báo của bạn. Đặt đối tượng {@link android.widget.RemoteViews} này vào + {@link android.support.v4.app.NotificationCompat.Builder} của bạn bằng cách gọi + {@link android.support.v4.app.NotificationCompat.Builder#setContent setContent()}. Tránh + đặt một {@link android.graphics.drawable.Drawable} nền trên đối tượng + {@link android.widget.RemoteViews} của bạn, vì màu văn bản của bạn có thể không đọc được. +
  4. +
+

+ Lớp {@link android.widget.RemoteViews} cũng bao gồm các phương pháp mà bạn có thể sử dụng để dễ dạng + thêm một {@link android.widget.Chronometer} hoặc {@link android.widget.ProgressBar} + vào bố trí thông báo của bạn. Để biết thêm thông tin về việc tạo bố trí tùy chỉnh cho thông báo + của mình, hãy tham khảo tài liệu tham khảo {@link android.widget.RemoteViews}. +

+

+ Chú ý: Khi bạn sử dụng một bố trí thông báo tùy chỉnh, hãy đặc biệt cẩn thận + để đảm bảo rằng bố trí tùy chỉnh của bạn có tác dụng với các hướng và độ phân giải thiết bị khác nhau. Trong khi + lời khuyên này áp dụng cho tất cả bố trí Dạng xem, nó đặc biệt quan trọng đối với thông báo + vì khoảng trống trong ngăn kéo thông báo rất hạn chế. Không được tạo bố trí tùy chỉnh của bạn quá + phức tạp và đảm bảo kiểm tra nó trong các cấu hình khác nhau. +

+ +

Sử dụng các tài nguyên kiểu cho văn bản thông báo tùy chỉnh

+

+ Luôn sử dụng tài nguyên kiểu cho văn bản của một thông báo tùy chỉnh. Màu nền của thông báo + có thể thay đổi giữa các thiết bị và phiên bản khác nhau, và việc sử dụng tài nguyên kiểu + sẽ giúp bạn khắc phục điều này. Bắt đầu từ Android 2.3, hệ thống đã định nghĩa kiểu cho + văn bản bố trí thông báo chuẩn. Nếu bạn sử dụng cùng kiểu trong các ứng dụng nhắm đến phiên bản Android + 2.3 hoặc cao hơn, bạn sẽ phải đảm bảo rằng văn bản của bạn nhìn thấy được trên nền hiển thị. +

diff --git a/docs/html-intl/intl/vi/guide/topics/ui/overview.jd b/docs/html-intl/intl/vi/guide/topics/ui/overview.jd new file mode 100644 index 0000000000000000000000000000000000000000..7bd45527c7b4b494d23294f69bf73ca7f7c08b6f --- /dev/null +++ b/docs/html-intl/intl/vi/guide/topics/ui/overview.jd @@ -0,0 +1,71 @@ +page.title=Tổng quan về UI +@jd:body + + +

Tất cả phần tử giao diện người dùng trong một ứng dụng Android đều được xây dựng bằng cách sử dụng các đối tượng {@link android.view.View} và +{@link android.view.ViewGroup}. {@link android.view.View} là một đối tượng có chức năng vẽ +thứ gì đó trên màn hình mà người dùng có thể tương tác với. {@link android.view.ViewGroup} là một đối tượng +có chức năng giữ các đối tượng {@link android.view.View} (và {@link android.view.ViewGroup}) khác +để định nghĩa bố trí của giao diện.

+ +

Android cung cấp một bộ sưu tập cả lớp con {@link android.view.View} và {@link +android.view.ViewGroup} cung cấp cho bạn các cách điều khiển nhập liệu thông dụng (chẳng hạn như nút và +trường văn bản) và các mô hình bố trí khác nhau (chẳng hạn như bố trí tuyến tính hoặc tương đối).

+ + +

Bố trí Giao diện Người dùng

+ +

Giao diện người dùng của từng thành phần trong ứng dụng của bạn được định nghĩa bằng cách sử dụng một phân cấp của các đối tượng {@link +android.view.View} và {@link android.view.ViewGroup} như minh họa trong hình 1. Mỗi nhóm dạng xem +là một bộ chứa vô hình có chức năng tổ chức các dạng xem con, trong khi dạng xem con có thể là +điều khiển nhập liệu hoặc các widget khác để +vẽ một phần nào đó của UI. Cây phân cấp này có thể đơn giản hoặc phức tạp như nhu cầu +của bạn (nhưng đơn giản sẽ tốt cho hiệu năng).

+ + +

Hình 1. Minh họa một phân cấp dạng xem có chức năng định nghĩa một +bố trí UI.

+ +

Để khai báo bố trí của mình, bạn có thể khởi tạo các đối tượng {@link android.view.View} trong mã và bắt đầu +xây dựng một cây, nhưng cách dễ nhất và hiệu quả nhất để định nghĩa bố trí của bạn đó là bằng một tệp XML. +XML cung cấp một cấu trúc mà người dùng có thể đọc được cho bố trí, tương tự như HTML.

+ +

Tên của phần tử XML đối với một dạng xem tương ứng với lớp Android mà nó biểu diễn. Vì thế một phần tử +<TextView> tạo ra một widget {@link android.widget.TextView} trong UI của bạn, +và một phần tử <LinearLayout> tạo ra một nhóm dạng xem {@link android.widget.LinearLayout} +.

+ +

Ví dụ, một bố trí thẳng đứng đơn giản với dạng xem văn bản và nút trông như sau:

+
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:layout_width="fill_parent" 
+              android:layout_height="fill_parent"
+              android:orientation="vertical" >
+    <TextView android:id="@+id/text"
+              android:layout_width="wrap_content"
+              android:layout_height="wrap_content"
+              android:text="I am a TextView" />
+    <Button android:id="@+id/button"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="I am a Button" />
+</LinearLayout>
+
+ +

Khi bạn tải một tài nguyên bố trí trong ứng dụng của mình, Android khởi tạo từng nút của bố trí vào một +đối tượng thời gian chạy mà bạn có thể sử dụng để định nghĩa các hành vi bổ sung, truy vấn trạng thái của đối tượng hoặc sửa đổi +bố trí.

+ +

Để xem hướng dẫn đầy đủ về tạo bố trí UI, hãy xem phần Bố trí +XML. + + +

Thành phần Giao diện Người dùng

+ +

Bạn không phải xây dựng tất cả UI của mình bằng cách sử dụng các đối tượng {@link android.view.View} và {@link +android.view.ViewGroup}. Android cung cấp một vài thành phần ứng dụng đưa ra một +bố trí UI chuẩn mà bạn chỉ cần định nghĩa nội dung cho nó. Mỗi thành phần UI như vậy +đều có một bộ API duy nhất được mô tả trong các tài liệu tương ứng, chẳng hạn như Thanh Hành động, Hộp thoạiThông báo Trạng thái.

+ + diff --git a/docs/html-intl/intl/vi/guide/topics/ui/settings.jd b/docs/html-intl/intl/vi/guide/topics/ui/settings.jd new file mode 100644 index 0000000000000000000000000000000000000000..8e19b979d0ac180451c42f100701391fc870cdc5 --- /dev/null +++ b/docs/html-intl/intl/vi/guide/topics/ui/settings.jd @@ -0,0 +1,1202 @@ +page.title=Thiết đặt +page.tags=preference,preferenceactivity,preferencefragment + +@jd:body + + + + + + + +

Ứng dụng thường bao gồm những thiết đặt cho phép người dùng sửa đổi các tính năng và hành vi của ứng dụng. Ví +dụ, một số ứng dụng cho phép người dùng quy định xem thông báo có được kích hoạt hay không hoặc quy định tần suất +ứng dụng sẽ đồng bộ dữ liệu với đám mây.

+ +

Nếu muốn cung cấp thiết đặt cho ứng dụng của mình, bạn nên sử dụng +các API {@link android.preference.Preference} của Android để xây dựng một giao diện phù hợp với +trải nghiệm người dùng trong các ứng dụng Android khác (bao gồm thiết đặt hệ thống). Tài liệu này mô tả +cách xây dựng thiết đặt ứng dụng của bạn bằng cách sử dụng các API {@link android.preference.Preference}.

+ +
+

Thiết kế Thiết đặt

+

Để biết thông tin về cách thiết kế thiết đặt của bạn, hãy đọc hướng dẫn thiết kế Thiết đặt.

+
+ + + +

Hình 1. Ảnh chụp màn hình từ thiết đặt của ứng dụng +Messaging trên Android. Chọn một mục được định nghĩa bởi một {@link android.preference.Preference} +sẽ mở ra một giao diện để thay đổi thiết đặt.

+ + + + +

Tổng quan

+ +

Thay vì sử dụng các đối tượng {@link android.view.View} để xây dựng giao diện người dùng, thiết đặt được +xây dựng bằng cách sử dụng các lớp con khác nhau của lớp {@link android.preference.Preference} mà bạn +khai báo trong một tệp XML.

+ +

Đối tượng {@link android.preference.Preference} là một khối dựng cho một thiết đặt +đơn lẻ. Mỗi {@link android.preference.Preference} xuất hiện như một mục trong một danh sách và cung cấp +UI phù hợp để người dùng sửa đổi thiết đặt. Ví dụ, một {@link +android.preference.CheckBoxPreference} tạo một mục danh sách hiển thị một hộp kiểm, và một {@link +android.preference.ListPreference} tạo một mục mở ra một hộp thoại với danh sách lựa chọn.

+ +

Mỗi {@link android.preference.Preference} mà bạn thêm có một cặp khóa-giá trị tương ứng mà +hệ thống sử dụng để lưu thiết đặt trong một tệp {@link android.content.SharedPreferences} +mặc định cho thiết đặt của ứng dụng của bạn. Khi người dùng thay đổi một thiết đặt, hệ thống sẽ cập nhật giá trị +tương ứng trong tệp {@link android.content.SharedPreferences} cho bạn. Lần duy nhất mà bạn nên +trực tiếp tương tác với tệp {@link android.content.SharedPreferences} được liên kết đó là khi bạn +cần đọc giá trị để xác định xem hành vi ứng dụng của mình có được dựa trên thiết đặt của người dùng không.

+ +

Giá trị được lưu trong {@link android.content.SharedPreferences} cho từng thiết đặt có thể là một trong các kiểu dữ liệu +sau:

+ +
    +
  • Boolean
  • +
  • Float
  • +
  • Int
  • +
  • Long
  • +
  • String
  • +
  • String {@link java.util.Set}
  • +
+ +

Vì thiết đặt của ứng dụng của bạn được xây dựng bằng cách sử dụng các đối tượng {@link android.preference.Preference} +thay vì đối tượng +{@link android.view.View}, bạn nên sử dụng một lớp con {@link android.app.Activity} hoặc +{@link android.app.Fragment} chuyên dụng để hiển thị thiết đặt danh sách:

+ +
    +
  • Nếu ứng dụng của bạn hỗ trợ các phiên bản Android cũ hơn 3.0 (API mức 10 và thấp hơn), bạn phải +xây dựng hoạt động như một phần mở rộng của lớp {@link android.preference.PreferenceActivity}.
  • +
  • Trên phiên bản Android 3.0 trở lên, thay vào đó, bạn nên sử dụng một {@link android.app.Activity} +truyền thống nơi lưu giữ {@link android.preference.PreferenceFragment} để hiển thị thiết đặt ứng dụng của bạn. +Tuy nhiên, bạn cũng có thể sử dụng {@link android.preference.PreferenceActivity} để tạo một bố trí hai bảng +cho màn hình lớn khi bạn có nhiều nhóm thiết đặt.
  • +
+ +

Cách thiết đặt {@link android.preference.PreferenceActivity} của bạn và các thực thể của {@link +android.preference.PreferenceFragment} được trình bày trong các phần về Tạo một Hoạt động Tùy chọnSử dụng +Phân đoạn Tùy chọn.

+ + +

Tùy chọn

+ +

Mọi thiết đặt cho ứng dụng của bạn đều được biểu diễn bởi một lớp con cụ thể của lớp {@link +android.preference.Preference}. Mỗi lớp con lại bao gồm một tập hợp các tính chất cốt lõi cho phép bạn +quy định những thứ như tiêu đề cho thiết đặt và giá trị mặc định. Mỗi lớp con cũng cung cấp +các tính chất và giao diện người dùng chuyên dụng của chính nó. Ví dụ, hình 1 mình họa một ảnh chụp màn hình từ thiết đặt của ứng dụng +Messaging. Mỗi mục danh sách trong màn hình thiết đặt được hỗ trợ bởi một đối tượng {@link +android.preference.Preference} khác nhau.

+ +

Sau đây là một số tùy chọn phổ biến nhất:

+ +
+
{@link android.preference.CheckBoxPreference}
+
Hiển thị một mục kèm một hộp kiểm cho thiết đặt hoặc được kích hoạt hoặc bị vô hiệu hóa. Giá trị +được lưu là một boolean (true nếu nó được chọn).
+ +
{@link android.preference.ListPreference}
+
Mở một hộp thoại kèm danh sách nút chọn một. Giá trị được lưu +có thể là bất kỳ loại giá trị được hỗ trợ nào (liệt kê bên trên).
+ +
{@link android.preference.EditTextPreference}
+
Mở một hộp thoại kèm một widget {@link android.widget.EditText}. Giá trị được lưu là một {@link +java.lang.String}.
+
+ +

Xem lớp {@link android.preference.Preference} để biết danh sách tất cả các lớp con khác và tính chất +tương ứng của chúng.

+ +

Dĩ nhiên, các lớp tích hợp không đáp ứng mọi nhu cầu và ứng dụng của bạn có thể yêu cầu +lớp con chuyên dụng hơn. Ví dụ, nền tảng này hiện chưa cung cấp một lớp {@link +android.preference.Preference} cho việc chọn một số hay ngày. Vì thế, bạn có thể cần phải định nghĩa +lớp con {@link android.preference.Preference} của chính mình. Để được trợ giúp khi làm vậy, hãy xem phần về Xây dựng Thiết đặt Tùy chỉnh.

+ + + +

Định nghĩa Tùy chọn trong XML

+ +

Mặc dù bạn có thể khởi tạo các đối tượng {@link android.preference.Preference} mới vào thời gian chạy, bạn +nên định nghĩa danh sách các thiết đặt của mình trong XML kèm một phân cấp của các đối tượng {@link android.preference.Preference} +. Việc sử dụng một tệp XML để định nghĩa bộ sưu tập thiết đặt của bạn sẽ được ưu tiên vì tệp +cung cấp một cấu trúc dễ đọc, cập nhật đơn giản. Bên cạnh đó, các thiết đặt ứng dụng của bạn thường được +xác định trước, mặc dù bạn vẫn có thể sửa đổi bộ sưu tập vào thời gian chạy.

+ +

Mỗi lớp con {@link android.preference.Preference} có thể được khai báo bằng một phần tử XML mà +khớp với tên lớp đó, chẳng hạn như {@code <CheckBoxPreference>}.

+ +

Bạn phải lưu tệp XML trong thư mục {@code res/xml/}. Mặc dù bạn có thể đặt tên tệp là +bất cứ thứ gì mình muốn, nó thường được đặt tên là{@code preferences.xml}. Bạn thường chỉ cần một tệp, +bởi các nhánh trong phân cấp (mà mở danh sách thiết đặt của riêng chúng) sẽ được khai báo bằng cách sử dụng các thực thể +lồng nhau của {@link android.preference.PreferenceScreen}.

+ +

Lưu ý: Nếu bạn muốn tạo một bố trí đa bảng cho thiết đặt +của mình, vậy bạn nên tách riêng các tệp XML cho từng phân đoạn.

+ +

Node gốc cho tệp XML phải là một phần tử {@link android.preference.PreferenceScreen +<PreferenceScreen>}. Trong phần tử này là nơi bạn thêm từng {@link +android.preference.Preference}. Từng phần tử con mà bạn thêm vào trong phần tử +{@link android.preference.PreferenceScreen <PreferenceScreen>} sẽ xuất hiện như một mục +đơn lẻ trong danh sách thiết đặt.

+ +

Ví dụ:

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <CheckBoxPreference
+        android:key="pref_sync"
+        android:title="@string/pref_sync"
+        android:summary="@string/pref_sync_summ"
+        android:defaultValue="true" />
+    <ListPreference
+        android:dependency="pref_sync"
+        android:key="pref_syncConnectionType"
+        android:title="@string/pref_syncConnectionType"
+        android:dialogTitle="@string/pref_syncConnectionType"
+        android:entries="@array/pref_syncConnectionTypes_entries"
+        android:entryValues="@array/pref_syncConnectionTypes_values"
+        android:defaultValue="@string/pref_syncConnectionTypes_default" />
+</PreferenceScreen>
+
+ +

Trong ví dụ này, có một {@link android.preference.CheckBoxPreference} và một {@link +android.preference.ListPreference}. Cả hai mục đều bao gồm ba thuộc tính sau:

+ +
+
{@code android:key}
+
Thuộc tính này được yêu cầu cho các tùy chọn duy trì một giá trị dữ liệu. Nó quy định khóa +(xâu) duy nhất mà hệ thống sử dụng khi lưu giá trị của thiết đặt này trong {@link +android.content.SharedPreferences}. +

Các thực thể duy nhất mà thuộc tính này không được yêu cầu là khi tùy chọn là một +{@link android.preference.PreferenceCategory} hoặc {@link android.preference.PreferenceScreen}, hoặc +tùy chọn quy định một {@link android.content.Intent} để gọi ra (bằng phần tử {@code <intent>}) hoặc {@link android.app.Fragment} để hiển thị (bằng thuộc tính {@code +android:fragment}).

+
+
{@code android:title}
+
Thuộc tính này cung cấp một tên hiển thị với người dùng cho thiết đặt.
+
{@code android:defaultValue}
+
Nó quy định giá trị ban đầu mà hệ thống nên đặt trong tệp {@link +android.content.SharedPreferences}. Bạn nên cung cấp một giá trị mặc định cho tất cả +thiết đặt.
+
+ +

Để biết thông tin về tất cả thuộc tính được hỗ trợ khác, hãy xem tài liệu {@link +android.preference.Preference} (và lớp con tương ứng).

+ + +
+ +

Hình 2. Thiết đặt thể loại + có tiêu đề.
1. Thể loại được quy định bởi phần tử {@link +android.preference.PreferenceCategory <PreferenceCategory>}.
2. Tiêu đề +được quy định bằng thuộc tính {@code android:title}.

+
+ + +

Khi danh sách thiết đặt của bạn vượt quá khoảng 10 mục, bạn có thể muốn thêm tiêu đề để +định nghĩa các nhóm thiết đặt hoặc hiển thị các nhóm đó trong một +màn hình riêng. Những tùy chọn này được mô tả trong các phần sau.

+ + +

Tạo nhóm thiết đặt

+ +

Nếu bạn trình bày một danh sách từ 10 thiết đặt trở lên, người dùng +có thể gặp khó khăn trong việc dò tìm, hiểu và xử lý chúng. Bạn có thể khắc phục điều này bằng cách +chia một số hoặc tất cả thiết đặt thành các nhóm, qua đó biến một danh sách dài thành nhiều +danh sách ngắn hơn. Một nhóm các thiết đặt có liên quan có thể được trình bày bằng một trong hai cách:

+ + + +

Bạn có thể sử dụng một hoặc cả hai kỹ thuật tạo nhóm này để sắp xếp các thiết đặt cho ứng dụng của mình. Khi +quyết định sử dụng cái nào và làm thế nào để chia các thiết đặt của mình, bạn nên tuân theo các hướng dẫn trong tài liệu hướng dẫn +Thiết đặt của Thiết kế Android.

+ + +

Sử dụng tiêu đề

+ +

Nếu bạn muốn cung cấp các thanh chia có tiêu đề giữa các nhóm thiết đặt (như minh họa trong hình 2), +hãy đặt từng nhóm đối tượng {@link android.preference.Preference} vào bên trong một {@link +android.preference.PreferenceCategory}.

+ +

Ví dụ:

+ +
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <PreferenceCategory 
+        android:title="@string/pref_sms_storage_title"
+        android:key="pref_key_storage_settings">
+        <CheckBoxPreference
+            android:key="pref_key_auto_delete"
+            android:summary="@string/pref_summary_auto_delete"
+            android:title="@string/pref_title_auto_delete"
+            android:defaultValue="false"... />
+        <Preference 
+            android:key="pref_key_sms_delete_limit"
+            android:dependency="pref_key_auto_delete"
+            android:summary="@string/pref_summary_delete_limit"
+            android:title="@string/pref_title_sms_delete"... />
+        <Preference 
+            android:key="pref_key_mms_delete_limit"
+            android:dependency="pref_key_auto_delete"
+            android:summary="@string/pref_summary_delete_limit"
+            android:title="@string/pref_title_mms_delete" ... />
+    </PreferenceCategory>
+    ...
+</PreferenceScreen>
+
+ + +

Sử dụng màn hình con

+ +

Nếu bạn muốn đặt các nhóm thiết đặt vào một màn hình con (như minh họa trong hình 3), hãy đặt nhóm +các đối tượng {@link android.preference.Preference} vào bên trong một {@link +android.preference.PreferenceScreen}.

+ + +

Hình 3. Màn hình con thiết đặt. Phần tử {@code +<PreferenceScreen>} sẽ tạo +một mục mà, khi được chọn, nó sẽ mở ra một danh sách riêng để hiển thị các thiết đặt lồng nhau.

+ +

Ví dụ:

+ +
+<PreferenceScreen  xmlns:android="http://schemas.android.com/apk/res/android">
+    <!-- opens a subscreen of settings -->
+    <PreferenceScreen
+        android:key="button_voicemail_category_key"
+        android:title="@string/voicemail"
+        android:persistent="false">
+        <ListPreference
+            android:key="button_voicemail_provider_key"
+            android:title="@string/voicemail_provider" ... />
+        <!-- opens another nested subscreen -->
+        <PreferenceScreen
+            android:key="button_voicemail_setting_key"
+            android:title="@string/voicemail_settings"
+            android:persistent="false">
+            ...
+        </PreferenceScreen>
+        <RingtonePreference
+            android:key="button_voicemail_ringtone_key"
+            android:title="@string/voicemail_ringtone_title"
+            android:ringtoneType="notification" ... />
+        ...
+    </PreferenceScreen>
+    ...
+</PreferenceScreen>
+
+ + +

Sử dụng ý định

+ +

Trong một số trường hợp, bạn có thể muốn một mục tùy chọn mở một hoạt động khác thay vì một +màn hình thiết đặt, chẳng hạn như một trình duyệt web để xem một trang web. Để gọi ra một {@link +android.content.Intent} khi người dùng chọn một mục tùy chọn, hãy thêm một phần tử {@code <intent>} +làm con của phần tử {@code <Preference>} tương ứng.

+ +

Ví dụ, sau đây là cách bạn có thể sử dụng một mục tùy chọn để mở một trang web:

+ +
+<Preference android:title="@string/prefs_web_page" >
+    <intent android:action="android.intent.action.VIEW"
+            android:data="http://www.example.com" />
+</Preference>
+
+ +

Bạn có thể tạo cả ý định biểu thị và không biểu thị bằng cách sử dụng các thuộc tính sau:

+ +
+
{@code android:action}
+
Hành động cần gán, theo mỗi phương pháp {@link android.content.Intent#setAction setAction()} +.
+
{@code android:data}
+
Dữ liệu cần gán, theo mỗi phương pháp {@link android.content.Intent#setData setData()}.
+
{@code android:mimeType}
+
Kiểu MIME cần gán, theo mỗi phương pháp {@link android.content.Intent#setType setType()} +.
+
{@code android:targetClass}
+
Phần lớp của tên thành phần, theo mỗi phương pháp {@link android.content.Intent#setComponent +setComponent()}.
+
{@code android:targetPackage}
+
Phần gói của tên thành phần, theo mỗi phương pháp {@link +android.content.Intent#setComponent setComponent()}.
+
+ + + +

Tạo một Hoạt động Tùy chọn

+ +

Để hiển thị thiết đặt của bạn trong một hoạt động, hãy mở rộng lớp {@link +android.preference.PreferenceActivity}. Đây là phần mở rộng của lớp {@link +android.app.Activity} truyền thống mà hiển thị một danh sách các thiết đặt dựa trên một phân cấp của các đối tượng {@link +android.preference.Preference}. {@link android.preference.PreferenceActivity} +sẽ tự động duy trì các thiết đặt liên kết với từng {@link +android.preference.Preference} khi người dùng thực hiện một thay đổi.

+ +

Lưu ý: Nếu bạn đang phát triển ứng dụng của mình cho phiên bản Android 3.0 và +cao hơn, thay vào đó bạn nên sử dụng {@link android.preference.PreferenceFragment}. Đi đến phần +tiếp theo về Sử dụng Phân đoạn Tùy chọn.

+ +

Điều quan trọng nhất cần nhớ đó là bạn không được tải một bố trí dạng xem trong khi gọi lại {@link +android.preference.PreferenceActivity#onCreate onCreate()}. Thay vào đó, bạn hãy gọi {@link +android.preference.PreferenceActivity#addPreferencesFromResource addPreferencesFromResource()} để +thêm tùy chọn mà bạn đã khai báo trong một tệp XML vào hoạt động. Ví dụ, sau đây là đoạn mã tối thiểu +cần thiết cho một {@link android.preference.PreferenceActivity} chức năng:

+ +
+public class SettingsActivity extends PreferenceActivity {
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        addPreferencesFromResource(R.xml.preferences);
+    }
+}
+
+ +

Đây là đoạn mã vừa đủ cho một số ứng dụng bởi ngay khi người dùng sửa đổi một tùy chọn, +hệ thống sẽ lưu thay đổi đối với tệp {@link android.content.SharedPreferences} mặc định mà các +thành phần ứng dụng khác của bạn có thể đọc khi bạn cần kiểm tra thiết đặt của người dùng. Tuy nhiên, +nhiều ứng dụng lại yêu cầu thêm mã để theo dõi những thay đổi xảy ra với các tùy chọn đó. +Để biết thông tin về việc theo dõi thay đổi trong tệp {@link android.content.SharedPreferences}, +hãy xem phần về Đọc Tùy chọn.

+ + + + +

Sử dụng Phân đoạn Tùy chọn

+ +

Nếu bạn đang phát triển cho phiên bản Android 3.0 (API mức 11) trở lên, bạn nên sử dụng một {@link +android.preference.PreferenceFragment} để hiển thị danh sách các đối tượng {@link android.preference.Preference} +của bạn. Bạn có thể thêm một {@link android.preference.PreferenceFragment} vào bất kỳ hoạt động nào—bạn không cần +sử dụng {@link android.preference.PreferenceActivity}.

+ +

Phân đoạn cung cấp một kiến trúc +linh hoạt hơn cho ứng dụng của bạn, so với việc sử dụng chỉ các hoạt động, dù loại hoạt động +mà bạn đang xây dựng là gì. Như vậy, chúng tôi gợi ý bạn sử dụng {@link +android.preference.PreferenceFragment} để kiểm soát hiển thị các thiết đặt của mình thay cho {@link +android.preference.PreferenceActivity} khi có thể.

+ +

Việc triển khai {@link android.preference.PreferenceFragment} có thể chỉ đơn giản như +định nghĩa phương pháp {@link android.preference.PreferenceFragment#onCreate onCreate()} để tải một +tệp tùy chọn bằng {@link android.preference.PreferenceFragment#addPreferencesFromResource +addPreferencesFromResource()}. Ví dụ:

+ +
+public static class SettingsFragment extends PreferenceFragment {
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        // Load the preferences from an XML resource
+        addPreferencesFromResource(R.xml.preferences);
+    }
+    ...
+}
+
+ +

Khi đó, bạn có thể thêm phân đoạn này vào một {@link android.app.Activity} giống như cách mà bạn sẽ làm với bất kỳ +{@link android.app.Fragment} nào khác. Ví dụ:

+ +
+public class SettingsActivity extends Activity {
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        // Display the fragment as the main content.
+        getFragmentManager().beginTransaction()
+                .replace(android.R.id.content, new SettingsFragment())
+                .commit();
+    }
+}
+
+ +

Lưu ý: {@link android.preference.PreferenceFragment} không có một +đối tượng {@link android.content.Context} của chính nó. Nếu bạn cần một đối tượng {@link android.content.Context} +, bạn có thể gọi {@link android.app.Fragment#getActivity()}. Tuy nhiên, hãy chắc chắn là chỉ gọi +{@link android.app.Fragment#getActivity()} khi phân đoạn đó được gắn kèm với một hoạt động. Khi +phân đoạn chưa được gắn kèm, hoặc bị bỏ gắn kèm trong khi kết thúc vòng đời của nó, {@link +android.app.Fragment#getActivity()} sẽ trả về rỗng.

+ + +

Thiết đặt Giá trị Mặc định

+ +

Tùy chọn mà bạn tạo có thể định nghĩa một số hành vi quan trọng cho ứng dụng của bạn, vì thế +bạn cần phải khởi tạo tệp {@link android.content.SharedPreferences} kèm theo với các +giá trị mặc định cho từng {@link android.preference.Preference} khi người dùng lần đầu mở +ứng dụng của bạn.

+ +

Điều đầu tiên bạn phải làm đó là quy định một giá trị mặc định cho từng đối tượng {@link +android.preference.Preference} +trong tệp XML của bạn bằng cách sử dụng thuộc tính {@code android:defaultValue}. Giá trị đó có thể là bất kỳ kiểu +dữ liệu nào mà phù hợp với đối tượng {@link android.preference.Preference} tương ứng. Ví +dụ:

+ +
+<!-- default value is a boolean -->
+<CheckBoxPreference
+    android:defaultValue="true"
+    ... />
+
+<!-- default value is a string -->
+<ListPreference
+    android:defaultValue="@string/pref_syncConnectionTypes_default"
+    ... />
+
+ +

Khi đó, từ phương pháp {@link android.app.Activity#onCreate onCreate()} trong hoạt động chính +—của ứng dụng của bạn và trong bất kỳ hoạt động nào khác mà thông qua đó người dùng có thể vào ứng dụng của bạn lần +đầu tiên—hãy gọi {@link android.preference.PreferenceManager#setDefaultValues +setDefaultValues()}:

+ +
+PreferenceManager.setDefaultValues(this, R.xml.advanced_preferences, false);
+
+ +

Việc gọi này trong khi {@link android.app.Activity#onCreate onCreate()} sẽ đảm bảo rằng +ứng dụng của bạn được khởi tạo phù hợp với các thiết đặt mặc định mà ứng dụng của bạn có thể cần +đọc để xác định một số hành vi (chẳng hạn như có tải xuống dữ liệu trong khi đang trên +mạng di động hay không).

+ +

Phương pháp này dùng ba tham đối:

+
    +
  • {@link android.content.Context} ứng dụng của bạn.
  • +
  • ID tài nguyên cho tệp XML tùy chọn mà bạn muốn đặt các giá trị mặc định cho.
  • +
  • Một boolean cho biết các giá trị mặc định có nên được đặt nhiều hơn một lần hay không. +

    Khi tham đối này là false, hệ thống sẽ đặt các giá trị mặc định chỉ khi phương pháp này chưa từng được +gọi trước đây (hoặc {@link android.preference.PreferenceManager#KEY_HAS_SET_DEFAULT_VALUES} +trong tệp tùy chọn được chia sẻ giá trị mặc định là sai).

  • +
+ +

Miễn là bạn đặt tham đối thứ ba này thành false, bạn có thể gọi phương pháp này một cách an toàn +mỗi khi hoạt động của bạn bắt đầu mà không khống chế các tùy chọn đã lưu của người dùng bằng cách đặt lại chúng thành +mặc định. Tuy nhiên, nếu bạn đặt nó thành true, bạn sẽ khống chế mọi giá trị +trước đó bằng các giá trị mặc định.

+ + + +

Sử dụng Tiêu đề Tùy chọn

+ +

Trong vài trường hợp hiếm gặp, bạn có thể muốn thiết kế các thiết đặt của mình sao cho màn hình thứ nhất +chỉ hiển thị một danh sách các màn hình con (chẳng hạn như trong ứng dụng Thiết đặt của hệ thống, +như minh họa trong các hình 4 và 5). Khi phát triển thiết kế như vậy cho phiên bản Android 3.0 trở lên, bạn +nên sử dụng tính năng "tiêu đề" mới trong Android 3.0, thay vì xây dựng màn hình con với các phần tử +{@link android.preference.PreferenceScreen} lồng nhau.

+ +

Để xây dựng thiết đặt có tiêu đề của mình, bạn cần:

+
    +
  1. Tách riêng từng nhóm thiết đặt thành các thực thể riêng của {@link +android.preference.PreferenceFragment}. Cụ thể, mỗi nhóm thiết đặt cần một tệp XML +riêng.
  2. +
  3. Tạo một tệp tiêu đề XML liệt kê từng nhóm thiết đặt và khai báo phân đoạn nào +chứa danh sách thiết đặt tương ứng.
  4. +
  5. Mở rộng lớp {@link android.preference.PreferenceActivity} để lưu trữ các thiết đặt của bạn.
  6. +
  7. Triển khai lệnh gọi lại {@link +android.preference.PreferenceActivity#onBuildHeaders onBuildHeaders()} để quy định +tệp tiêu đề.
  8. +
+ +

Một lợi ích tuyệt vời đối với việc sử dụng thiết kế này đó là {@link android.preference.PreferenceActivity} +tự động trình bày bố trí hai bảng như minh họa trong hình 4 khi chạy trên màn hình lớn.

+ +

Ngay cả khi ứng dụng của bạn hỗ trợ các phiên bản Android cũ hơn 3.0, bạn có thể xây dựng ứng dụng +của mình để sử dụng {@link android.preference.PreferenceFragment} cho một trình chiếu hai bảng trên +các thiết bị mới hơn, trong khi vẫn hỗ trợ phân cấp đa màn hình truyền thống trên các thiết bị +cũ hơn (xem phần nói về Hỗ trợ các phiên bản cũ hơn +với tiêu đề tùy chọn).

+ + +

Hình 4. Bố trí có hai bảng với tiêu đề.
1. +Tiêu đề được định nghĩa trong một tệp tiêu đề XML.
2. Mỗi nhóm thiết đặt được định nghĩa bởi một +{@link android.preference.PreferenceFragment}, được quy định bởi một phần tử {@code <header>} trong tệp tiêu đề +.

+ + +

Hình 5. Thiết bị cầm tay với các tiêu đề thiết đặt. Khi một +mục được chọn, {@link android.preference.PreferenceFragment} được liên kết sẽ thay thế +tiêu đề.

+ + +

Tạo tệp tiêu đề

+ +

Mỗi nhóm thiết đặt trong danh sách tiêu đề của bạn được quy định bởi một phần tử {@code <header>} +đơn lẻ bên trong một phần tử {@code <preference-headers>} gốc. Ví dụ:

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<preference-headers xmlns:android="http://schemas.android.com/apk/res/android">
+    <header 
+        android:fragment="com.example.prefs.SettingsActivity$SettingsFragmentOne"
+        android:title="@string/prefs_category_one"
+        android:summary="@string/prefs_summ_category_one" />
+    <header 
+        android:fragment="com.example.prefs.SettingsActivity$SettingsFragmentTwo"
+        android:title="@string/prefs_category_two"
+        android:summary="@string/prefs_summ_category_two" >
+        <!-- key/value pairs can be included as arguments for the fragment. -->
+        <extra android:name="someKey" android:value="someHeaderValue" />
+    </header>
+</preference-headers>
+
+ +

Với thuộc tính {@code android:fragment}, mỗi tiêu đề sẽ khai báo một thực thể của {@link +android.preference.PreferenceFragment} mà sẽ mở khi người dùng chọn tiêu đề đó.

+ +

Phần tử {@code <extras>} cho phép bạn chuyển các cặp khóa-giá trị sang phân đoạn trong một {@link +android.os.Bundle}. Phân đoạn có thể truy xuất các tham đối bằng cách gọi {@link +android.app.Fragment#getArguments()}. Bạn có thể chuyển các tham đối tới phân đoạn vì nhiều +lý do khác nhau, nhưng một lý do chính đáng đó là để sử dụng lại cùng lớp con của {@link +android.preference.PreferenceFragment} cho mỗi nhóm và sử dụng tham đối để quy định +tệp XML tùy chọn nào mà phân đoạn cần tải.

+ +

Ví dụ, sau đây là một phân đoạn mà có thể được tái sử dụng cho nhiều nhóm thiết đặt, khi từng +tiêu đề định nghĩa một tham đối {@code <extra>} với khóa {@code "settings"}:

+ +
+public static class SettingsFragment extends PreferenceFragment {
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        String settings = getArguments().getString("settings");
+        if ("notifications".equals(settings)) {
+            addPreferencesFromResource(R.xml.settings_wifi);
+        } else if ("sync".equals(settings)) {
+            addPreferencesFromResource(R.xml.settings_sync);
+        }
+    }
+}
+
+ + + +

Hiển thị tiêu đề

+ +

Để hiển thị tiêu đề tùy chọn, bạn phải triển khai phương pháp gọi lại {@link +android.preference.PreferenceActivity#onBuildHeaders onBuildHeaders()} và gọi +{@link android.preference.PreferenceActivity#loadHeadersFromResource +loadHeadersFromResource()}. Ví dụ:

+ +
+public class SettingsActivity extends PreferenceActivity {
+    @Override
+    public void onBuildHeaders(List<Header> target) {
+        loadHeadersFromResource(R.xml.preference_headers, target);
+    }
+}
+
+ +

Khi người dùng chọn một mục từ danh sách tiêu đề, hệ thống sẽ mở {@link +android.preference.PreferenceFragment} kèm theo.

+ +

Lưu ý: Khi sử dụng tiêu đề tùy chọn, lớp con {@link +android.preference.PreferenceActivity} của bạn không cần triển khai phương pháp {@link +android.preference.PreferenceActivity#onCreate onCreate()}, vì tác vụ cần thiết duy nhất +cho hoạt động đó là tải tiêu đề.

+ + +

Hỗ trợ các phiên bản cũ hơn với tiêu đề tùy chọn

+ +

Nếu ứng dụng của bạn hỗ trợ các phiên bản Android cũ hơn 3.0, bạn vẫn có thể sử dụng tiêu đề để +cung cấp một bố trí hai bảng khi chạy trên Android 3.0 trở lên. Tất cả những việc bạn cần làm đó là tạo một +tệp XML tùy chọn bổ sung có sử dụng phần tử cơ bản {@link android.preference.Preference +<Preference>} đóng vai trò như mục tiêu đề (để dùng cho các phiên bản Android +cũ hơn).

+ +

Tuy nhiên, thay vì mở một {@link android.preference.PreferenceScreen} mới, từng phần tử {@link +android.preference.Preference <Preference>} sẽ gửi một {@link android.content.Intent} tới +{@link android.preference.PreferenceActivity} mà quy định tệp XML tùy chọn cần +tải.

+ +

Ví dụ, sau đây là một tệp XML cho các tiêu đề tùy chọn được sử dụng trên Android 3.0 +trở lên ({@code res/xml/preference_headers.xml}):

+ +
+<preference-headers xmlns:android="http://schemas.android.com/apk/res/android">
+    <header 
+        android:fragment="com.example.prefs.SettingsFragmentOne"
+        android:title="@string/prefs_category_one"
+        android:summary="@string/prefs_summ_category_one" />
+    <header 
+        android:fragment="com.example.prefs.SettingsFragmentTwo"
+        android:title="@string/prefs_category_two"
+        android:summary="@string/prefs_summ_category_two" />
+</preference-headers>
+
+ +

Và sau đây là một tệp tùy chọn cung cấp cùng các tiêu đề cho các phiên bản cũ hơn +Android 3.0 ({@code res/xml/preference_headers_legacy.xml}):

+ +
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <Preference 
+        android:title="@string/prefs_category_one"
+        android:summary="@string/prefs_summ_category_one"  >
+        <intent 
+            android:targetPackage="com.example.prefs"
+            android:targetClass="com.example.prefs.SettingsActivity"
+            android:action="com.example.prefs.PREFS_ONE" />
+    </Preference>
+    <Preference 
+        android:title="@string/prefs_category_two"
+        android:summary="@string/prefs_summ_category_two" >
+        <intent 
+            android:targetPackage="com.example.prefs"
+            android:targetClass="com.example.prefs.SettingsActivity"
+            android:action="com.example.prefs.PREFS_TWO" />
+    </Preference>
+</PreferenceScreen>
+
+ +

Vì hỗ trợ dành cho {@code <preference-headers>} đã được thêm trong Android 3.0, hệ thống sẽ gọi +{@link android.preference.PreferenceActivity#onBuildHeaders onBuildHeaders()} trong {@link +android.preference.PreferenceActivity} của bạn chỉ khi đang chạy trên phiên bản Androd 3.0 hoặc cao hơn. Để tải +tệp tiêu đề "kế thừa" ({@code preference_headers_legacy.xml}), bạn phải kiểm tra phiên bản Android +và, nếu phiên bản cũ hơn Android 3.0 ({@link +android.os.Build.VERSION_CODES#HONEYCOMB}), hãy gọi {@link +android.preference.PreferenceActivity#addPreferencesFromResource addPreferencesFromResource()} để +tải tệp tiêu đề kế thừa. Ví dụ:

+ +
+@Override
+public void onCreate(Bundle savedInstanceState) {
+    super.onCreate(savedInstanceState);
+    ...
+
+    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
+        // Load the legacy preferences headers
+        addPreferencesFromResource(R.xml.preference_headers_legacy);
+    }
+}
+
+// Called only on Honeycomb and later
+@Override
+public void onBuildHeaders(List<Header> target) {
+   loadHeadersFromResource(R.xml.preference_headers, target);
+}
+
+ +

Việc duy nhất còn lại cần làm đó là xử lý {@link android.content.Intent} mà được chuyển vào +hoạt động để nhận biết tệp tùy chọn nào cần tải. Vì vậy, hãy truy xuất hành động của ý định và so sánh nó với +các xâu hành động đã biết mà bạn đã sử dụng trong tag {@code <intent>} của XML tùy chọn:

+ +
+final static String ACTION_PREFS_ONE = "com.example.prefs.PREFS_ONE";
+...
+
+@Override
+public void onCreate(Bundle savedInstanceState) {
+    super.onCreate(savedInstanceState);
+
+    String action = getIntent().getAction();
+    if (action != null && action.equals(ACTION_PREFS_ONE)) {
+        addPreferencesFromResource(R.xml.preferences);
+    }
+    ...
+
+    else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
+        // Load the legacy preferences headers
+        addPreferencesFromResource(R.xml.preference_headers_legacy);
+    }
+}
+
+ +

Lưu ý rằng các lệnh gọi liên tiếp đến {@link +android.preference.PreferenceActivity#addPreferencesFromResource addPreferencesFromResource()} sẽ +xếp chồng tất cả tùy chọn trong một danh sách duy nhất, vì thế hãy chắc chắn rằng nó chỉ được gọi một lần bằng cách liên kết các +điều kiện với mệnh đề else-if.

+ + + + + +

Đọc Tùy chọn

+ +

Theo mặc định, tất cả tùy chọn của ứng dụng của bạn đều được lưu vào một tệp có thể truy cập từ bất kỳ nơi nào +trong ứng dụng của bạn bằng cách gọi phương pháp tĩnh {@link +android.preference.PreferenceManager#getDefaultSharedPreferences +PreferenceManager.getDefaultSharedPreferences()}. Điều này sẽ trả về đối tượng {@link +android.content.SharedPreferences} chứa tất cả cặp khóa-giá trị liên kết +với các đối tượng {@link android.preference.Preference} được sử dụng trong {@link +android.preference.PreferenceActivity}.

+ +

Ví dụ, sau đây là cách bạn có thể đọc một trong các giá trị tùy chọn từ bất kỳ hoạt động nào khác trong ứng dụng +của mình:

+ +
+SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this);
+String syncConnPref = sharedPref.getString(SettingsActivity.KEY_PREF_SYNC_CONN, "");
+
+ + + +

Theo dõi thay đổi tùy chọn

+ +

Có một vài lý do khiến bạn có thể muốn được thông báo càng sớm càng tốt nếu người dùng thay đổi một trong các +tùy chọn. Để nhận một phương pháp gọi lại khi thay đổi xảy ra với bất kỳ tùy chọn nào, +hãy triển khai giao diện {@link android.content.SharedPreferences.OnSharedPreferenceChangeListener +SharedPreference.OnSharedPreferenceChangeListener} và đăng ký đối tượng theo dõi cho đối tượng +{@link android.content.SharedPreferences} bằng cách gọi {@link +android.content.SharedPreferences#registerOnSharedPreferenceChangeListener +registerOnSharedPreferenceChangeListener()}.

+ +

Giao diện này chỉ có một phương pháp gọi lại, {@link +android.content.SharedPreferences.OnSharedPreferenceChangeListener#onSharedPreferenceChanged +onSharedPreferenceChanged()}, và bạn có thể thấy đây là cách dễ nhất để triển khai giao diện như một phần +hoạt động của mình. Ví dụ:

+ +
+public class SettingsActivity extends PreferenceActivity
+                              implements OnSharedPreferenceChangeListener {
+    public static final String KEY_PREF_SYNC_CONN = "pref_syncConnectionType";
+    ...
+
+    public void onSharedPreferenceChanged(SharedPreferences sharedPreferences,
+        String key) {
+        if (key.equals(KEY_PREF_SYNC_CONN)) {
+            Preference connectionPref = findPreference(key);
+            // Set summary to be the user-description for the selected value
+            connectionPref.setSummary(sharedPreferences.getString(key, ""));
+        }
+    }
+}
+
+ +

Trong ví dụ này, phương pháp sẽ kiểm tra xem thiết đặt bị thay đổi có áp dụng cho một khóa tùy chọn đã biết không. Nó +sẽ gọi {@link android.preference.PreferenceActivity#findPreference findPreference()} để nhận đối tượng +{@link android.preference.Preference} đã bị thay đổi để nó có thể sửa đổi tóm tắt +của mục đó thành mô tả lựa chọn của người dùng. Cụ thể, khi thiết đặt là một {@link +android.preference.ListPreference} hoặc thiết đặt nhiều lựa chọn khác, bạn nên gọi {@link +android.preference.Preference#setSummary setSummary()} khi thiết đặt thay đổi để hiển thị +trạng thái hiện tại (chẳng hạn như thiết đặt Ngủ như minh họa trong hình 5).

+ +

Lưu ý: Như đã mô tả trong tài liệu Thiết kế Android về Thiết đặt, chúng tôi khuyên bạn nên cập nhật +tóm tắt cho {@link android.preference.ListPreference} mỗi khi người dùng thay đổi tùy chọn để +mô tả thiết đặt hiện tại.

+ +

Để quản lý vòng đời trong hoạt động cho phù hợp, chúng tôi khuyên rằng bạn nên đăng ký và bỏ đăng ký +{@link android.content.SharedPreferences.OnSharedPreferenceChangeListener} của mình tương ứng trong {@link +android.app.Activity#onResume} và các lệnh gọi lại {@link android.app.Activity#onPause}:

+ +
+@Override
+protected void onResume() {
+    super.onResume();
+    getPreferenceScreen().getSharedPreferences()
+            .registerOnSharedPreferenceChangeListener(this);
+}
+
+@Override
+protected void onPause() {
+    super.onPause();
+    getPreferenceScreen().getSharedPreferences()
+            .unregisterOnSharedPreferenceChangeListener(this);
+}
+
+ +

Chú ý: Khi bạn gọi {@link +android.content.SharedPreferences#registerOnSharedPreferenceChangeListener +registerOnSharedPreferenceChangeListener()}, trình quản lý tùy chọn hiện +không lưu trữ một tham chiếu mạnh tới đối tượng theo dõi. Bạn phải lưu trữ một tham chiếu +mạnh tới đối tượng theo dõi, nếu không nó sẽ dễ bị thu thập thông tin rác. Chúng tôi +khuyên bạn nên giữ một tham chiếu tới đối tượng theo dõi trong dữ liệu thực thể của một đối tượng +mà sẽ tồn tại miễn là bạn còn cần đối tượng theo dõi đó.

+ +

Ví dụ, trong đoạn mã sau, hàm gọi không giữ tham chiếu tới +đối tượng theo dõi. Kết quả là đối tượng theo dõi sẽ bị thu thập thông tin rác, +và nó sẽ bị lỗi tại một thời điểm không xác định trong tương lai:

+ +
+prefs.registerOnSharedPreferenceChangeListener(
+  // Bad! The listener is subject to garbage collection!
+  new SharedPreferences.OnSharedPreferenceChangeListener() {
+  public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
+    // listener implementation
+  }
+});
+
+ +

Thay vào đó, hãy lưu một tham chiếu tới đối tượng theo dõi trong một trường dữ liệu thực thể của một +đối tượng mà sẽ tồn tại miễn là còn cần đối tượng theo dõi đó:

+ +
+SharedPreferences.OnSharedPreferenceChangeListener listener =
+    new SharedPreferences.OnSharedPreferenceChangeListener() {
+  public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
+    // listener implementation
+  }
+};
+prefs.registerOnSharedPreferenceChangeListener(listener);
+
+ +

Quản lý Sử dụng Mạng

+ + +

Bắt đầu với Android 4.0, ứng dụng Thiết đặt của hệ thống sẽ cho phép người dùng xem +ứng dụng của họ đang sử dụng bao nhiêu dữ liệu mạng khi đang ở tiền cảnh và dưới nền. Khi đó, người dùng có thể +vô hiệu hóa việc sử dụng dữ liệu chạy ngầm cho từng ứng dụng. Để tránh việc người dùng vô hiệu hóa truy cập dữ liệu +của ứng dụng của bạn từ dưới nền, bạn nên sử dụng kết nối dữ liệu một cách hiệu quả và cho phép +người dùng tinh chỉnh mức sử dụng dữ liệu cho ứng dụng của bạn thông qua thiết đặt ứng dụng.

+ +

Ví dụ, bạn có thể cho phép người dùng kiểm soát tần suất ứng dụng của bạn đồng bộ dữ liệu, ứng dụng của bạn +chỉ được thực hiện tải lên/tải xuống khi trên Wi-Fi, ứng dụng của bạn sử dụng dữ liệu trong khi đang chuyển vùng dữ liệu, v.v... hay không. Với +những kiểm soát này, người dùng sẽ ít có khả năng vô hiệu hóa truy cập dữ liệu của ứng dụng của bạn +hơn nhiều khi họ đạt gần mức giới hạn đặt ra trong Thiết đặt hệ thống, vì thay vào đó, họ có thể kiểm soát chính xác +lượng dữ liệu mà ứng dụng của bạn sử dụng.

+ +

Sau khi bạn đã thêm các tùy chọn cần thiết trong {@link android.preference.PreferenceActivity} +của mình để kiểm soát các thói quen dữ liệu của ứng dụng của bạn, bạn nên thêm một bộ lọc ý định cho {@link +android.content.Intent#ACTION_MANAGE_NETWORK_USAGE} trong tệp bản kê khai của mình. Ví dụ:

+ +
+<activity android:name="SettingsActivity" ... >
+    <intent-filter>
+       <action android:name="android.intent.action.MANAGE_NETWORK_USAGE" />
+       <category android:name="android.intent.category.DEFAULT" />
+    </intent-filter>
+</activity>
+
+ +

Bộ lọc ý định này cho hệ thống biết rằng đây là hoạt động kiểm soát mức sử dụng dữ liệu +của ứng dụng của bạn. Vì thế, khi người dùng kiểm tra lượng dữ liệu mà ứng dụng của bạn đang dùng từ ứng dụng +Thiết đặt của hệ thống, sẽ có một nút Xem thiết đặt ứng dụng khởi chạy +{@link android.preference.PreferenceActivity} của bạn, vì thế người dùng có thể tinh chỉnh lượng dữ liệu mà ứng dụng của bạn +dùng.

+ + + + + + + +

Xây dựng một Thiết đặt Tùy chỉnh

+ +

Khuôn khổ Android bao gồm nhiều lớp con {@link android.preference.Preference} mà +cho phép bạn xây dựng một UI cho một vài loại thiết đặt khác nhau. +Tuy nhiên, bạn có thể khám phá thiết đặt mình cần mà chưa có giải pháp tích hợp sẵn, chẳng hạn như một +bộ chọn số hay bộ chọn ngày. Trong trường hợp như vậy, bạn sẽ cần tạo một tùy chọn tùy chỉnh bằng cách mở rộng +lớp {@link android.preference.Preference} hoặc một trong các lớp con khác.

+ +

Khi bạn mở rộng lớp {@link android.preference.Preference}, có một vài điều quan trọng +mà bạn cần làm:

+ +
    +
  • Quy định giao diện người dùng sẽ xuất hiện khi người dùng chọn thiết đặt.
  • +
  • Lưu giá trị của thiết đặt khi phù hợp.
  • +
  • Khởi tạo {@link android.preference.Preference} bằng giá trị hiện tại (hoặc mặc định) +khi xét tới dạng xem.
  • +
  • Cung cấp giá trị mặc định khi hệ thống yêu cầu.
  • +
  • Nếu {@link android.preference.Preference} cung cấp UI của chính mình (chẳng hạn như một hộp thoại), hãy lưu +và khôi phục trạng thái để xử lý các thay đổi trong vòng đời (chẳng hạn như khi người dùng xoay màn hình).
  • +
+ +

Các phần sau mô tả cách hoàn thành từng tác vụ này.

+ + + +

Quy định một giao diện người dùng

+ +

Nếu bạn trực tiếp mở rộng lớp {@link android.preference.Preference}, bạn cần triển khai +{@link android.preference.Preference#onClick()} để định nghĩa hành động xảy ra khi người dùng +chọn mục. Tuy nhiên, hầu hết các thiết đặt tùy chỉnh sẽ mở rộng {@link android.preference.DialogPreference} để +hiển thị một hộp thoại, điều này làm đơn giản hóa quy trình. Khi bạn mở rộng {@link +android.preference.DialogPreference}, bạn phải gọi {@link +android.preference.DialogPreference#setDialogLayoutResource setDialogLayoutResourcs()} trong khi đang ở trong +hàm dựng lớp để quy định bố trí cho hộp thoại.

+ +

Ví dụ, sau đây là hàm dựng cho một {@link +android.preference.DialogPreference} tùy chỉnh mà khai báo bố trí và quy định văn bản cho +các nút hộp thoại tích cực và tiêu cực mặc định:

+ +
+public class NumberPickerPreference extends DialogPreference {
+    public NumberPickerPreference(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        
+        setDialogLayoutResource(R.layout.numberpicker_dialog);
+        setPositiveButtonText(android.R.string.ok);
+        setNegativeButtonText(android.R.string.cancel);
+        
+        setDialogIcon(null);
+    }
+    ...
+}
+
+ + + +

Lưu giá trị của thiết đặt

+ +

Bạn có thể lưu một giá trị cho thiết đặt vào bất cứ lúc nào bằng cách gọi một trong các phương pháp của lớp {@link +android.preference.Preference}, {@code persist*()}, chẳng hạn như {@link +android.preference.Preference#persistInt persistInt()} nếu giá trị của thiết đặt là một số nguyên hoặc +{@link android.preference.Preference#persistBoolean persistBoolean()} để lưu một boolean.

+ +

Lưu ý: Mỗi {@link android.preference.Preference} chỉ có thể lưu một +kiểu dữ liệu, vì thế bạn phải sử dụng phương pháp {@code persist*()} phù hợp cho kiểu dữ liệu được sử dụng bởi +{@link android.preference.Preference} tùy chỉnh của mình.

+ +

Thời điểm bạn chọn duy trì thiết đặt có thể phụ thuộc vào lớp {@link +android.preference.Preference} nào mà bạn mở rộng. Nếu mở rộng {@link +android.preference.DialogPreference}, khi đó bạn nên duy trì giá trị đó chỉ khi hộp thoại +đóng lại do kết quả tích cực (người dùng chọn nút "OK").

+ +

Khi {@link android.preference.DialogPreference} đóng lại, hệ thống sẽ gọi phương pháp {@link +android.preference.DialogPreference#onDialogClosed onDialogClosed()}. Phương pháp bao gồm một +tham đối boolean quy định xem người dùng có trả về kết quả "tích cực" hay không—nếu kết quả là +true, khi đó, người dùng đã chọn nút tích cực và bạn nên lưu giá trị mới này. Ví +dụ:

+ +
+@Override
+protected void onDialogClosed(boolean positiveResult) {
+    // When the user selects "OK", persist the new value
+    if (positiveResult) {
+        persistInt(mNewValue);
+    }
+}
+
+ +

Trong ví dụ này, mNewValue là một thành viên lớp lưu giữ giá trị +hiện tại của thiết đặt. Việc gọi {@link android.preference.Preference#persistInt persistInt()} sẽ lưu giá trị vào +tệp {@link android.content.SharedPreferences} (tự động sử dụng khóa mà +được quy định trong tệp XML cho {@link android.preference.Preference} này).

+ + +

Khởi tạo giá trị hiện tại

+ +

Khi hệ thống thêm {@link android.preference.Preference} của bạn vào màn hình, nó +gọi {@link android.preference.Preference#onSetInitialValue onSetInitialValue()} để thông báo +với bạn xem thiết đặt có giá trị được duy trì hay không. Nếu không có giá trị được duy trì, lệnh gọi này sẽ cung cấp +cho bạn giá trị mặc định.

+ +

Phương pháp {@link android.preference.Preference#onSetInitialValue onSetInitialValue()} chuyển một +boolean, restorePersistedValue, để cho biết liệu giá trị đã được duy trì +cho thiết đặt hay không. Nếu nó là true, khi đó bạn nên truy xuất giá trị được duy trì bằng cách gọi +một trong các phương pháp của lớp {@link +android.preference.Preference}, {@code getPersisted*()}, chẳng hạn như {@link +android.preference.Preference#getPersistedInt getPersistedInt()} đối với một giá trị số nguyên. Bạn sẽ +thường muốn truy xuất giá trị được duy trì sao cho bạn có thể cập nhật UI cho phù hợp để phản ánh +giá trị đã lưu trước đó.

+ +

Nếu restorePersistedValuefalse, vậy bạn +nên sử dụng giá trị mặc định được chuyển trong tham đối thứ hai.

+ +
+@Override
+protected void onSetInitialValue(boolean restorePersistedValue, Object defaultValue) {
+    if (restorePersistedValue) {
+        // Restore existing state
+        mCurrentValue = this.getPersistedInt(DEFAULT_VALUE);
+    } else {
+        // Set default state from the XML attribute
+        mCurrentValue = (Integer) defaultValue;
+        persistInt(mCurrentValue);
+    }
+}
+
+ +

Mỗi phương pháp {@code getPersisted*()} sẽ lấy một tham đối quy định +giá trị mặc định sẽ sử dụng trong trường hợp thực sự không có giá trị được duy trì hoặc khóa không tồn tại. Trong +ví dụ trên, một hằng số cục bộ được sử dụng để quy định giá trị mặc định trong trường hợp {@link +android.preference.Preference#getPersistedInt getPersistedInt()} không thể trả về một giá trị được duy trì.

+ +

Chú ý: Bạn không thể sử dụng +defaultValue làm giá trị mặc định trong phương pháp {@code getPersisted*()}, bởi +giá trị của nó luôn rỗng khi restorePersistedValuetrue.

+ + +

Cung cấp một giá trị mặc định

+ +

Nếu trường hợp lớp {@link android.preference.Preference} của bạn quy định một giá trị mặc định +(với thuộc tính {@code android:defaultValue}), khi đó hệ thống +sẽ gọi {@link android.preference.Preference#onGetDefaultValue +onGetDefaultValue()} khi nó khởi tạo đối tượng để truy xuất giá trị. Bạn phải +triển khai phương pháp này để hệ thống lưu giá trị mặc định trong {@link +android.content.SharedPreferences}. Ví dụ:

+ +
+@Override
+protected Object onGetDefaultValue(TypedArray a, int index) {
+    return a.getInteger(index, DEFAULT_VALUE);
+}
+
+ +

Các tham đối của phương pháp cung cấp mọi thứ bạn cần: mảng thuộc tính và vị trí chỉ mục +của {@code android:defaultValue} mà bạn phải truy xuất. Lý do bạn phải triển khai +phương pháp này nhằm trích xuất giá trị mặc định từ thuộc tính đó là bởi bạn phải quy định +một giá trị mặc định cục bộ cho thuộc tính trong trường hợp giá trị không được định nghĩa.

+ + + +

Lưu và khôi phục trạng thái của Tùy chọn

+ +

Giống như {@link android.view.View} trong một bố trí, lớp con {@link android.preference.Preference} +của bạn chịu trách nhiệm lưu và khôi phục trạng thái của nó trong trường hợp hoạt động hoặc phân đoạn +được khởi động lại (chẳng hạn như khi người dùng xoay màn hình). Để lưu và khôi phục +trạng thái của lớp {@link android.preference.Preference} của bạn cho đúng, bạn phải triển khai các +phương pháp gọi lại vòng đời {@link android.preference.Preference#onSaveInstanceState +onSaveInstanceState()} và {@link +android.preference.Preference#onRestoreInstanceState onRestoreInstanceState()}.

+ +

Trạng thái của {@link android.preference.Preference} của bạn được định nghĩa bởi một đối tượng mà triển khai +giao diện {@link android.os.Parcelable}. Khuôn khổ Android sẽ cung cấp một đối tượng như vậy cho bạn +như một điểm bắt đầu để định nghĩa đối tượng trạng thái của bạn: lớp {@link +android.preference.Preference.BaseSavedState}.

+ +

Để định nghĩa cách thức lớp {@link android.preference.Preference} của bạn lưu trạng thái của nó +hãy mở rộng lớp {@link android.preference.Preference.BaseSavedState}. Bạn cần khống chế chỉ + một vài phương pháp và định nghĩa đối tượng {@link android.preference.Preference.BaseSavedState#CREATOR} +.

+ +

Đối với hầu hết ứng dụng, bạn có thể sao chép triển khai sau và chỉ cần thay đổi các dòng +xử lý {@code value} nếu lớp con {@link android.preference.Preference} của bạn lưu một kiểu +dữ liệu khác số nguyên.

+ +
+private static class SavedState extends BaseSavedState {
+    // Member that holds the setting's value
+    // Change this data type to match the type saved by your Preference
+    int value;
+
+    public SavedState(Parcelable superState) {
+        super(superState);
+    }
+
+    public SavedState(Parcel source) {
+        super(source);
+        // Get the current preference's value
+        value = source.readInt();  // Change this to read the appropriate data type
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        super.writeToParcel(dest, flags);
+        // Write the preference's value
+        dest.writeInt(value);  // Change this to write the appropriate data type
+    }
+
+    // Standard creator object using an instance of this class
+    public static final Parcelable.Creator<SavedState> CREATOR =
+            new Parcelable.Creator<SavedState>() {
+
+        public SavedState createFromParcel(Parcel in) {
+            return new SavedState(in);
+        }
+
+        public SavedState[] newArray(int size) {
+            return new SavedState[size];
+        }
+    };
+}
+
+ +

Với triển khai {@link android.preference.Preference.BaseSavedState} bên trên được thêm +vào ứng dụng của bạn (thường dưới dạng một lớp con của lớp con {@link android.preference.Preference} của bạn), khi đó +bạn cần triển khai các phương pháp {@link android.preference.Preference#onSaveInstanceState +onSaveInstanceState()} và {@link +android.preference.Preference#onRestoreInstanceState onRestoreInstanceState()} cho lớp con +{@link android.preference.Preference} của mình.

+ +

Ví dụ:

+ +
+@Override
+protected Parcelable onSaveInstanceState() {
+    final Parcelable superState = super.onSaveInstanceState();
+    // Check whether this Preference is persistent (continually saved)
+    if (isPersistent()) {
+        // No need to save instance state since it's persistent,
+        // use superclass state
+        return superState;
+    }
+
+    // Create instance of custom BaseSavedState
+    final SavedState myState = new SavedState(superState);
+    // Set the state's value with the class member that holds current
+    // setting value
+    myState.value = mNewValue;
+    return myState;
+}
+
+@Override
+protected void onRestoreInstanceState(Parcelable state) {
+    // Check whether we saved the state in onSaveInstanceState
+    if (state == null || !state.getClass().equals(SavedState.class)) {
+        // Didn't save the state, so call superclass
+        super.onRestoreInstanceState(state);
+        return;
+    }
+
+    // Cast state to custom BaseSavedState and pass to superclass
+    SavedState myState = (SavedState) state;
+    super.onRestoreInstanceState(myState.getSuperState());
+    
+    // Set this Preference's widget to reflect the restored state
+    mNumberPicker.setValue(myState.value);
+}
+
+ diff --git a/docs/html-intl/intl/vi/guide/topics/ui/ui-events.jd b/docs/html-intl/intl/vi/guide/topics/ui/ui-events.jd new file mode 100644 index 0000000000000000000000000000000000000000..b4d1635d1caa9e677b68dd31cf189781c60fa62c --- /dev/null +++ b/docs/html-intl/intl/vi/guide/topics/ui/ui-events.jd @@ -0,0 +1,291 @@ +page.title=Sự kiện Nhập liệu +parent.title=Giao diện Người dùng +parent.link=index.html +@jd:body + + + +

Trên Android, có nhiều cách để can thiệp vào các sự kiện từ tương tác của một người dùng với ứng dụng của bạn. +Khi xem xét các sự kiện trong giao diện người dùng của bạn, cách tiếp cận là chụp lại sự kiện từ +đối tượng Dạng xem cụ thể mà người dùng tương tác với. Lớp Dạng xem sẽ cung cấp phương thức để làm việc này.

+ +

Trong các lớp Dạng xem khác nhau mà bạn sẽ sử dụng để soạn bố trí của mình, bạn có thể thấy một vài phương pháp gọi lại +công khai dường như hữu ích đối với sự kiện UI. Những phương pháp này được khuôn khổ Android gọi khi +xảy ra hành động tương ứng trên đối tượng đó. Ví dụ, khi một Dạng xem (chẳng hạn như một Nút) được chạm vào, +phương pháp onTouchEvent() được gọi trên đối tượng đó. Tuy nhiên, để can thiệp vào điều này, bạn phải mở rộng +lớp và khống chế phương pháp đó. Tuy nhiên, việc mở rộng mọi đối tượng Dạng xem +để xử lý một sự kiện như vậy sẽ là không thực tế. Đây là lý do tại sao lớp Dạng xem cũng chứa +một tập hợp giao diện lồng nhau cùng các phương pháp gọi lại mà bạn có thể định nghĩa dễ dàng hơn nhiều. Những giao diện này, +được gọi là đối tượng theo dõi sự kiện, là tấm vé để bạn chụp lại tương tác giữa người dùng với UI của bạn.

+ +

Trong khi các đối tượng theo dõi sự kiện sẽ thường được sử dụng để theo dõi tương tác của người dùng, có thể +có lúc bạn muốn mở rộng một lớp Dạng xem để xây dựng một thành phần tùy chỉnh. +Có thể là bạn muốn mở rộng lớp {@link android.widget.Button} +để khiến cái gì đó trông ấn tượng hơn. Trong trường hợp này, bạn sẽ có thể định nghĩa các hành vi sự kiện mặc định cho lớp +của mình bằng cách sử dụng bộ xử lý sự kiện của lớp.

+ + +

Đối tượng theo dõi Sự kiện

+ +

Đối tượng theo dõi sự kiện là một giao diện trong lớp {@link android.view.View} chứa một phương pháp gọi lại +đơn lẻ. Những phương pháp này sẽ được khuôn khổ Android gọi khi Dạng xem mà đối tượng theo dõi đã +được đăng ký với bị kích khởi bởi tương tác giữa người dùng với mục trong UI.

+ +

Trong giao diện của đối tượng theo dõi sự kiện là những phương pháp gọi lại sau:

+ +
+
onClick()
+
Từ {@link android.view.View.OnClickListener}. + Phương pháp này được gọi khi người dùng chạm vào mục + (khi ở chế độ cảm ứng), hoặc lấy tiêu điểm vào một mục bằng phím điều hướng hoặc bi xoay và + nhấn phím "enter" phù hợp hoặc nhấn bi xoay.
+
onLongClick()
+
Từ {@link android.view.View.OnLongClickListener}. + Phương pháp này được gọi khi người gọi chạm và giữ mục (khi ở chế độ cảm ứng), hoặc + lấy tiêu điểm vào một mục bằng phím điều hướng hoặc bi xoay và + nhấn và giữ phím "enter" phù hợp hoặc nhấn và giữ bi xoay (trong một giây).
+
onFocusChange()
+
Từ {@link android.view.View.OnFocusChangeListener}. + Phương pháp này được gọi khi người dùng điều hướng lên hoặc ra khỏi một mục bằng cách sử dụng các phím điều hướng hoặc bi xoay.
+
onKey()
+
Từ {@link android.view.View.OnKeyListener}. + Phương pháp này được gọi khi người dùng được lấy tiêu điểm vào một mục và nhấn hoặc nhả phím cứng trên thiết bị.
+
onTouch()
+
Từ {@link android.view.View.OnTouchListener}. + Phương pháp này được gọi khi người dùng thực hiện một hành động được coi như một sự kiện chạm, bao gồm nhấn, nhả, + hoặc bất kỳ động tác chuyển động nào trên màn hình (trong đường biên của mục đó).
+
onCreateContextMenu()
+
Từ {@link android.view.View.OnCreateContextMenuListener}. + Phương pháp này được gọi khi một Menu Ngữ cảnh đang được xây dựng (kết quả của một sự kiện "nhấp giữ" kéo dài). Xem phần thảo luận về + menu ngữ cảnh trong hướng dẫn dành cho nhà phát triển Menu +.
+
+ +

Những phương pháp này là phương pháp duy nhất nằm trong giao diện tương ứng của chúng. Để định nghĩa một trong những phương pháp này +và xử lý sự kiện của bạn, hãy triển khai giao diện lồng nhau trong Hoạt động của bạn hoặc định nghĩa nó thành một lớp vô danh. +Sau đó, chuyển một thực thể triển khai của bạn +tới phương pháp View.set...Listener() tương ứng. (Ví dụ, gọi +{@link android.view.View#setOnClickListener(View.OnClickListener) setOnClickListener()} +và chuyển cho nó triển khai {@link android.view.View.OnClickListener OnClickListener} của bạn.)

+ +

Ví dụ bên dưới cho biết cách đăng ký một đối tượng theo dõi khi nhấp cho một Nút.

+ +
+// Create an anonymous implementation of OnClickListener
+private OnClickListener mCorkyListener = new OnClickListener() {
+    public void onClick(View v) {
+      // do something when the button is clicked
+    }
+};
+
+protected void onCreate(Bundle savedValues) {
+    ...
+    // Capture our button from layout
+    Button button = (Button)findViewById(R.id.corky);
+    // Register the onClick listener with the implementation above
+    button.setOnClickListener(mCorkyListener);
+    ...
+}
+
+ +

Bạn cũng có thể thấy tiện hơn khi triển khai OnClickListener như một phần trong Hoạt động của mình. +Làm vậy sẽ tránh phải tải lớp bổ sung và phân bổ đối tượng. Ví dụ:

+
+public class ExampleActivity extends Activity implements OnClickListener {
+    protected void onCreate(Bundle savedValues) {
+        ...
+        Button button = (Button)findViewById(R.id.corky);
+        button.setOnClickListener(this);
+    }
+
+    // Implement the OnClickListener callback
+    public void onClick(View v) {
+      // do something when the button is clicked
+    }
+    ...
+}
+
+ +

Lưu ý rằng phương pháp gọi lại onClick() trong ví dụ trên không có giá trị +trả về, nhưng một số phương pháp đối tượng theo dõi sự kiện khác phải trả về một boolean. Lý do +này phụ thuộc vào sự kiện. Với số ít sự kiện thực hiện như vậy, sau đây là lý do:

+
    +
  • {@link android.view.View.OnLongClickListener#onLongClick(View) onLongClick()} - + Trả về một boolean cho biết bạn đã xử lý sự kiện và sự kiện không nên được tiếp tục hay không. + Cụ thể, trả về true để cho biết rằng bạn đã xử lý sự kiện và nó nên dừng ở đây; + trả về false nếu bạn chưa xử lý nó và/hoặc sự kiện sẽ tiếp tục đối với bất kỳ + đối tượng theo dõi khi nhấp nào khác.
  • +
  • {@link android.view.View.OnKeyListener#onKey(View,int,KeyEvent) onKey()} - + Trả về một boolean cho biết bạn đã xử lý sự kiện và sự kiện không nên được tiếp tục hay không. + Cụ thể, trả về true sẽ cho biết rằng bạn đã xử lý sự kiện và nó nên dừng ở đây; + trả về false nếu bạn chưa xử lý nó và/hoặc sự kiện sẽ tiếp tục đối với bất kỳ + đối tượng theo dõi trên phím nào khác.
  • +
  • {@link android.view.View.OnTouchListener#onTouch(View,MotionEvent) onTouch()} - + Trả về một boolean cho biết đối tượng theo dõi của bạn có xử lý sự kiện này hay không. Điều quan trọng đó là + sự kiện này có thể có nhiều hành động nối tiếp nhau. Vì vậy, nếu bạn trả về false khi + nhận được sự kiện hành động hướng xuống, bạn sẽ cho biết rằng mình chưa xử lý sự kiện và cũng + không quan tâm tới các hành động sau đó từ sự kiện này. Vì thế, bạn sẽ không bị gọi vì bất kỳ hành động nào khác + trong sự kiện, chẳng hạn như một cử chỉ ngón tay, hay sự kiện hành động cho sự kiện hướng lên.
  • +
+ +

Ghi nhớ rằng các sự kiện phím cứng luôn được chuyển tới Dạng xem đang được lấy tiêu điểm. Chúng được chuyển bắt đầu từ trên cùng +của phân cấp Dạng xem, rồi xuống dưới, tới khi chúng đến đích phù hợp. Nếu Dạng xem của bạn (hoặc con của Dạng xem) +hiện có tiêu điểm, khi đó bạn có thể thấy hành trình của sự kiện qua phương pháp {@link android.view.View#dispatchKeyEvent(KeyEvent) +dispatchKeyEvent()}. Một cách khác để chụp lại các sự kiện phím bấm thông qua Dạng xem của mình, bạn cũng có thể nhận +tất cả sự kiện bên trong Hoạt động của mình bằng {@link android.app.Activity#onKeyDown(int,KeyEvent) onKeyDown()} +và {@link android.app.Activity#onKeyUp(int,KeyEvent) onKeyUp()}.

+ +

Đồng thời, khi nghĩ tới nhập liệu văn bản cho ứng dụng của bạn, hãy nhớ rằng nhiều thiết bị chỉ có các phương pháp +nhập liệu mềm. Những phương pháp như vậy không bắt buộc phải dựa trên phím bấm; một số có thể sử dụng nhập liệu bằng giọng nói, viết tay, v.v. Ngay cả khi +một phương pháp nhập liệu trình bày một giao diện như bàn phím, nó sẽ thường không kích khởi họ sự kiện +{@link android.app.Activity#onKeyDown(int,KeyEvent) onKeyDown()}. Bạn không nên +xây dựng UI yêu cầu kiểm soát các thao tác nhấn phím cụ thể trừ khi muốn giới hạn ứng dụng của bạn ở một số thiết bị +có bàn phím cứng. Cụ thể, không được dựa vào những phương pháp này để xác thực nhập liệu khi người dùng nhấn phím +quay lại; thay vào đó, hãy sử dụng các hành động như {@link android.view.inputmethod.EditorInfo#IME_ACTION_DONE} để báo hiệu với +phương pháp nhập liệu bạn kỳ vọng ứng dụng của mình sẽ phản ứng như thế nào để nó có thể thay đổi UI của mình cho có nghĩa. Tránh các giả định +về cách thức hoạt động của một phương pháp nhập liệu mềm và chỉ tin tưởng để nó cung cấp văn bản đã có định dạng cho ứng dụng của mình.

+ +

Lưu ý: Android sẽ gọi bộ xử lý sự kiện trước rồi mới tới bộ xử lý +mặc định phù hợp từ định nghĩa lớp. Như thế, việc trả về true từ những đối tượng theo dõi sự kiện này sẽ dừng +việc lan truyền sự kiện tới đối tượng theo dõi sự kiện khác và cũng sẽ chặn phương pháp gọi lại tới +bộ xử lý sự kiện mặc định trong Dạng xem. Vì thế, hãy chắc chắn rằng bạn muốn chấm dứt sự kiện khi trả về true.

+ + +

Bộ xử lý Sự kiện

+ +

Nếu bạn đang xây dựng một thành phần tùy chỉnh từ Dạng xem, khi đó bạn sẽ có thể định nghĩa một vài phương pháp gọi lại +được sử dụng như bộ xử lý sự kiện mặc định. +Trong tài liệu về Thành phần +Tùy chỉnh, bạn sẽ tìm hiểu về một số phương pháp gọi lại phổ biến được sử dụng để xử lý sự kiện, +bao gồm:

+
    +
  • {@link android.view.View#onKeyDown} - Được gọi khi xảy ra một sự kiện phím bấm mới.
  • +
  • {@link android.view.View#onKeyUp} - Được gọi khi xảy ra một sự kiện phím bấm hướng lên.
  • +
  • {@link android.view.View#onTrackballEvent} - Được gọi khi xảy ra một sự kiện chuyển động bi xoay.
  • +
  • {@link android.view.View#onTouchEvent} - Được gọi khi xảy ra một sự kiện chuyển động màn hình cảm ứng.
  • +
  • {@link android.view.View#onFocusChanged} - Được gọi khi dạng xem có hoặc mất tiêu điểm.
  • +
+

Có một số phương pháp khác mà bạn cần lưu ý, chúng không thuộc lớp Dạng xem, +nhưng có thể tác động trực tiếp tới cách bạn có thể xử lý sự kiện. Vì thế, khi quản lý các sự kiện phức tạp hơn bên trong +một bố trí, hãy xét những phương pháp khác sau:

+
    +
  • {@link android.app.Activity#dispatchTouchEvent(MotionEvent) + Activity.dispatchTouchEvent(MotionEvent)} - Phương pháp này cho phép {@link + android.app.Activity} của bạn can thiệp vào tất cả sự kiện chạm trước khi chúng được phân phối tới cửa sổ.
  • +
  • {@link android.view.ViewGroup#onInterceptTouchEvent(MotionEvent) + ViewGroup.onInterceptTouchEvent(MotionEvent)} - Phương pháp này cho phép một {@link + android.view.ViewGroup} xem sự kiện khi chúng được phân phối tới Dạng xem con.
  • +
  • {@link android.view.ViewParent#requestDisallowInterceptTouchEvent(boolean) + ViewParent.requestDisallowInterceptTouchEvent(boolean)} - Gọi phương pháp + này trên Dạng xem mẹ để cho biết rằng nó sẽ không can thiệp vào các sự kiện chạm bằng {@link + android.view.ViewGroup#onInterceptTouchEvent(MotionEvent)}.
  • +
+ +

Chế độ Cảm ứng

+

+Khi một người dùng đang điều hướng trong một giao diện người dùng bằng phím hướng hoặc bi xoay, cần +lấy tiêu điểm tới các mục có thể hành động (như nút) sao cho người dùng có thể thấy +mục nào sẽ chấp nhận nhập liệu. Tuy nhiên, nếu thiết bị có khả năng cảm ứng, và người dùng +bắt đầu tương tác với giao diện bằng cách chạm vào nó, khi đó không còn cần +tô sáng mục hay lấy tiêu điểm tới một Dạng xem cụ thể nữa. Do đó, có một chế độ cho +tương tác có tên là "chế độ cảm ứng." +

+

+Đối với thiết bị có khả năng cảm ứng, sau khi người dùng chạm vào màn hình, thiết bị +sẽ vào chế độ cảm ứng. Từ điểm này trở đi, chỉ những Dạng xem mà +{@link android.view.View#isFocusableInTouchMode} là đúng mới có thể lấy tiêu điểm, chẳng hạn như chế độ xem các widget chỉnh sửa văn bản. +Các Dạng xem chạm được, chẳng hạn như nút, sẽ không lấy được tiêu điểm khi chạm; chúng sẽ chỉ đơn giản +khởi chạy đối tượng theo dõi khi nhấp của mình khi được nhấn. +

+

+Bất cứ khi nào một người dùng nhấn phím hướng hoặc cuộn bằng bi xoay, thiết bị sẽ +thoát chế độ cảm ứng, và tìm một dạng xem để lấy tiêu điểm. Lúc này, người dùng có thể tiếp tục tương tác +với giao diện người dùng mà không chạm vào màn hình. +

+

+Trạng thái chế độ cảm ứng sẽ được duy trì trên toàn bộ hệ thống (tất cả cửa sổ và hoạt động). +Để truy vấn trạng thái hiện tại, bạn có thể gọi +{@link android.view.View#isInTouchMode} để xem liệu thiết bị có đang ở trong chế độ cảm ứng hay không. +

+ + +

Xử lý Tiêu điểm

+ +

Khuôn khổ sẽ xử lý chuyển động của tiêu điểm thường xuyên hồi đáp lại nhập liệu của người dùng. +Việc này bao gồm thay đổi tiêu điểm khi Dạng xem bị loại bỏ hoặc ẩn đi, hoặc khi Dạng xem +mới có sẵn. Dạng xem thể hiện sự sẵn sàng lấy tiêu điểm của chúng +thông qua phương pháp {@link android.view.View#isFocusable()}. Để thay đổi việc liệu một Dạng xem có thể lấy +tiêu điểm hay không, hãy gọi {@link android.view.View#setFocusable(boolean) setFocusable()}. Khi ở trong chế độ cảm ứng, +bạn có thể truy vấn xem Dạng xem có cho phép lấy tiêu điểm bằng {@link android.view.View#isFocusableInTouchMode()} hay không. +Bạn có thể thay đổi điều này bằng {@link android.view.View#setFocusableInTouchMode(boolean) setFocusableInTouchMode()}. +

+ +

Chuyển động tiêu điểm được dựa trên một giải thuật tìm kiếm đối tượng gần nhất theo +một hướng cho trước. Trong các trường hợp hiếm gặp, giải thuật mặc định có thể không khớp với +hành vi theo ý định của nhà phát triển. Trong những tình huống này, bạn có thể cung cấp +các khống chế rõ ràng với các thuộc tính XML sau trong tệp bố trí: +nextFocusDown, nextFocusLeft, nextFocusRight, và +nextFocusUp. Thêm một trong những thuộc tính này vào Dạng xem mà từ đó +tiêu điểm đang rời khỏi. Định nghĩa giá trị của thuộc tính là id của Dạng xem + cần được lấy tiêu điểm. Ví dụ:

+
+<LinearLayout
+    android:orientation="vertical"
+    ... >
+  <Button android:id="@+id/top"
+          android:nextFocusUp="@+id/bottom"
+          ... />
+  <Button android:id="@+id/bottom"
+          android:nextFocusDown="@+id/top"
+          ... />
+</LinearLayout>
+
+ +

Thông thưởng, trong bố trí thẳng đứng này, việc điều hướng lên từ Nút đầu tiên sẽ không +đi tới đâu hết và việc điều hướng xuống từ Nút thứ hai cũng vậy. Giờ thì khi Nút trên cùng +đã định nghĩa Nút dưới cùng là nextFocusUp (và ngược lại), tiêu điểm điều hướng sẽ +luân chuyển từ trên-xuống-dưới và dưới-lên-trên.

+ +

Nếu bạn muốn khai báo một Dạng xem là có thể lấy tiêu điểm trong UI của mình (thông thường thì không), +hãy thêm thuộc tính XML android:focusable vào Dạng xem, trong khai báo bố trí của bạn. +Đặt giá trị true. Bạn cũng có thể khai báo một Dạng xem +là có thể lấy tiêu điểm trong khi ở Chế độ Cảm ứng bằng android:focusableInTouchMode.

+

Để yêu cầu một Dạng xem cụ thể để lấy tiêu điểm, hãy gọi {@link android.view.View#requestFocus()}.

+

Để theo dõi các sự kiện tiêu điểm (được thông báo khi một Dạng xem nhận được hoặc mất tiêu điểm), hãy sử dụng +{@link android.view.View.OnFocusChangeListener#onFocusChange(View,boolean) onFocusChange()}, +như được đề cập trong phần Đối tượng theo dõi Sự kiện bên trên.

+ + + + diff --git a/docs/html-intl/intl/vi/sdk/index.jd b/docs/html-intl/intl/vi/sdk/index.jd new file mode 100644 index 0000000000000000000000000000000000000000..401476ea41efb6a456483189ac8c241fef41ba56 --- /dev/null +++ b/docs/html-intl/intl/vi/sdk/index.jd @@ -0,0 +1,487 @@ +page.title=Tải xuống Android Studio và SDK Tools +page.tags=sdk, android studio +page.template=sdk +header.hide=1 +page.metaDescription=Tải xuống Android IDE chính thức và bộ công cụ cho nhà phát triển để xây dựng ứng dụng cho điện thoại, máy tính bảng, thiết bị đeo được, TV chạy Android và nhiều thiết bị khác. + +studio.version=1.1.0 + +studio.linux_bundle_download=android-studio-ide-135.1740770-linux.zip +studio.linux_bundle_bytes=259336386 +studio.linux_bundle_checksum=e8d166559c50a484f83ebfec6731cc0e3f259208 + +studio.mac_bundle_download=android-studio-ide-135.1740770-mac.dmg +studio.mac_bundle_bytes=261303345 +studio.mac_bundle_checksum=f9745d0fec1eefd498f6160a2d6a1b5247d4cda3 + +studio.win_bundle_exe_download=android-studio-bundle-135.1740770-windows.exe +studio.win_bundle_exe_bytes=856233768 +studio.win_bundle_exe_checksum=7484b9989d2914e1de30995fbaa97a271a514b3f + +studio.win_notools_exe_download=android-studio-ide-135.1740770-windows.exe +studio.win_notools_exe_bytes=242135128 +studio.win_notools_exe_checksum=5ea77661cd2300cea09e8e34f4a2219a0813911f + +studio.win_bundle_download=android-studio-ide-135.1740770-windows.zip +studio.win_bundle_bytes=261667054 +studio.win_bundle_checksum=e903f17cc6a57c7e3d460c4555386e3e65c6b4eb + + + +sdk.linux_download=android-sdk_r24.1.2-linux.tgz +sdk.linux_bytes=168121693 +sdk.linux_checksum=68980e4a26cca0182abb1032abffbb72a1240c51 + +sdk.mac_download=android-sdk_r24.1.2-macosx.zip +sdk.mac_bytes=89151287 +sdk.mac_checksum=00e43ff1557e8cba7da53e4f64f3a34498048256 + +sdk.win_download=android-sdk_r24.1.2-windows.zip +sdk.win_bytes=159778618 +sdk.win_checksum=704f6c874373b98e061fe2e7eb34f9fcb907a341 + +sdk.win_installer=installer_r24.1.2-windows.exe +sdk.win_installer_bytes=111364285 +sdk.win_installer_checksum=e0ec864efa0e7449db2d7ed069c03b1f4d36f0cd + + + + + + +@jd:body + + + + + + + +
+ + + + + + + + + +
+ +
 
+ + + +
+ +

Android Studio

+ +

IDE Android chính thức

+ +
    +
  • IDE Android Studio
  • +
  • Bộ công cụ SDK Android
  • +
  • Nền tảng Android 5.0 (Lollipop)
  • +
  • Ảnh hệ thống trình mô phỏng Android 5.0 cùng các API của Google
  • +
+ + +Tải xuống Android Studio + + +

+Để tải Android Studio hoặc bộ công cụ SDK độc lập, hãy truy cập developer.android.com/sdk/ +

+ + + +
+ + + + +

Trình chỉnh sửa mã thông minh

+ +
+ +
+ +
+

Nằm ở cốt lõi của Android Studio là một trình biên tập mã thông minh có khả năng hoàn thành + mã, dựng lại và phân tích mã nâng cao.

+

Trình chỉnh sửa mã mạnh mẽ này sẽ giúp bạn trở thành một nhà phát triển ứng dụng Android năng suất hơn.

+
+ + + + + +

Các ví dụ mã và tích hợp GitHub

+ +
+ +
+ +
+

Các trình hướng dẫn dự án mới sẽ giúp bắt đầu một dự án mới dễ hơn bao giờ hết.

+ +

Bắt đầu các dự án bằng cách sử dụng mã mẫu cho các kiểu mẫu như ngăn kéo điều hướng và các trình tạo trang dạng xem, + và thậm chí còn nhập được các ví dụ mã của Google từ GitHub.

+
+ + + + +

Phát triển ứng dụng đa màn hình

+ +
+ +
+ +
+

Xây dựng ứng dụng cho điện thoại, máy tính bảng Android, Android Wear, + Android TV, Android Auto và Google Glass.

+

Với Dạng xem Dự án Android mới và hỗ trợ mô-đun trong Android Studio, việc + quản lý các dự án và tài nguyên ứng dụng trở nên dễ dàng hơn. +

+ + + + +

Các thiết bị ảo cho tất cả hình dạng và kích cỡ

+ +
+ +
+ +
+

Android Studio được cấu hình sẵn cùng một ảnh trình mô phỏng được tối ưu hóa.

+

Trình quản lý Thiết bị Ảo được cập nhật và hợp lý hoá sẽ cung cấp + các cấu hình thiết bị định nghĩa sẵn cho các thiết bị Android thông thường.

+
+ + + + +

+Phát triển xây dựng Android bằng Gradle

+ +
+ +
+ +
+

Tạo nhiều tệp APK cho ứng dụng Android của bạn với các tính năng khác nhau bằng cách sử dụng cùng dự án.

+

Quản lý phụ thuộc của ứng dụng bằng Maven.

+

Xây dựng APK từ Android Studio hoặc dòng lệnh.

+
+ + + + +

Tìm hiểu thêm về Android Studio

+
+ +Tải xuống Android Studio + +
    +
  • Xây dựng Phiên bản Cộng đồng IntelliJ IDEA, IDE Java phổ biến của JetBrains.
  • +
  • Hệ thống xây dựng linh hoạt dựa trên Gradle.
  • +
  • Xây dựng các biến thể và khởi tạo nhiều APK.
  • +
  • Hỗ trợ mẫu mở rộng cho các Dịch vụ của Google và các loại thiết bị khác nhau.
  • +
  • Trình chỉnh sửa bố trí phong phú hỗ trợ chỉnh sửa chủ đề.
  • +
  • Bộ công cụ Lint để giải quyết các vấn đề về hiệu năng, khả năng sử dụng, tính tương thích với phiên bản và các vấn đề khác.
  • +
  • ProGuard và các khả năng ký ứng dụng.
  • +
  • Hỗ trợ tích hợp cho Nền tảng Đám mây của Google, giúp dễ dàng tích hợp Google Cloud + Nhắn tin và Công cụ Ứng dụng.
  • +
+ +

+Để biết thêm chi tiết về các tính năng có sẵn trong Android Studio, +hãy đọc hướng dẫn Nội dung Cơ bản về Android Studio.

+
+ + +

Nếu bạn đang sử dụng Eclipse với ADT, hãy lưu ý rằng hiện Android Studio là IDE chính thức +cho Android, vì thế bạn nên di chuyển sang Android Studio để nhận tất cả cập nhật +IDE mới nhất. Để được trợ giúp về di chuyển các dự án, +hãy xem phần Di chuyển sang Android +Studio.

+ + + + + + + +

Yêu cầu Hệ thống

+ +

Windows

+ +
    +
  • Microsoft® Windows® 8/7/Vista/2003 (32 hoặc 64-bit)
  • +
  • Tối thiểu 2 GB RAM, khuyến nghị 4 GB RAM
  • +
  • Dung lượng trống đĩa cứng 400 MB
  • +
  • Ít nhất 1 GB cho SDK Android, các ảnh hệ thống trình mô phỏng và bộ đệm ẩn
  • +
  • Độ phân giải màn hình tối thiểu 1280 x 800
  • +
  • Java Development Kit (JDK) 7
  • +
  • Tùy chọn dành cho trình mô phỏng tăng tốc: Bộ xử lý Intel® có hỗ trợ Intel® VT-x, Intel® EM64T +(Intel® 64), và tính năng Execute Disable (XD) Bit
  • +
+ + +

Mac OS X

+ +
    +
  • Mac® OS X® 10.8.5 hoặc cao hơn, lên tới 10.9 (Mavericks)
  • +
  • Tối thiểu 2 GB RAM, khuyến nghị 4 GB RAM
  • +
  • Dung lượng trống đĩa cứng 400 MB
  • +
  • Ít nhất 1 GB cho SDK Android, các ảnh hệ thống trình mô phỏng và bộ đệm ẩn
  • +
  • Độ phân giải màn hình tối thiểu 1280 x 800
  • +
  • Java Runtime Environment (JRE) 6
  • +
  • Java Development Kit (JDK) 7
  • +
  • Tùy chọn dành cho trình mô phỏng tăng tốc: Bộ xử lý Intel® có hỗ trợ Intel® VT-x, Intel® EM64T +(Intel® 64), và tính năng Execute Disable (XD) Bit
  • +
+ +

Trên Mac OS, hãy chạy Android Studio bằng Java Runtime Environment (JRE) 6 để dựng +phông chữ tối ưu. Khi đó, bạn có thể cấu hình dự án của mình để sử dụng Java Development Kit (JDK) 6 hoặc JDK 7.

+ + + +

Linux

+ +
    +
  • Máy tính bàn GNOME hoặc KDE
  • +
  • GNU C Library (glibc) 2.15 hoặc mới hơn
  • +
  • Tối thiểu 2 GB RAM, khuyến nghị 4 GB RAM
  • +
  • Dung lượng trống đĩa cứng 400 MB
  • +
  • Ít nhất 1 GB cho SDK Android, các ảnh hệ thống trình mô phỏng và bộ đệm ẩn
  • +
  • Độ phân giải màn hình tối thiểu 1280 x 800
  • +
  • Oracle® Java Development Kit (JDK) 7
  • +
+

Được thử nghiệm trên Ubuntu® 14.04, Trusty Tahr (có khả năng phân phối 64-bit khi chạy các ứng dụng +32-bit).

+ + + + +

Tùy chọn Tải xuống Khác

+ + diff --git a/docs/html-intl/intl/vi/sdk/installing/adding-packages.jd b/docs/html-intl/intl/vi/sdk/installing/adding-packages.jd new file mode 100644 index 0000000000000000000000000000000000000000..728c92d760f009e9de686d4454713f267baabc6b --- /dev/null +++ b/docs/html-intl/intl/vi/sdk/installing/adding-packages.jd @@ -0,0 +1,227 @@ +page.title=Thêm Gói SDK + +page.tags=trình quản lý sdk +helpoutsWidget=true + +@jd:body + + + + +

+Theo mặc định, SDK Android không bao gồm mọi thứ bạn cần để bắt đầu phát triển. +SDK chia những công cụ, nền tảng và các thành phần khác vào các gói mà bạn có thể +tải xuống nếu cần bằng cách sử dụng +Trình quản lý SDK Android. +Vì vậy, trước khi có thể bắt đầu, có một vài gói bạn nên thêm vào SDK Android của mình.

+ +

Để bắt đầu thêm gói, hãy khởi chạy Trình quản lý SDK Android bằng một trong những cách sau:

+
    +
  • Trong Android Studio, nhấp vào Trình quản lý SDK + ở thanh công cụ.
  • +
  • Nếu bạn không đang sử dụng Android Studio: +
      +
    • Windows: Bấm đúp tệp SDK Manager.exe ở gốc của thư mục SDK + Android.
    • +
    • Mac/Linux: Mở một terminal và điều hướng đến thư mục tools/ ở vị trí + nơi cài đặt SDK Android, rồi chạy android sdk.
    • +
    +
  • +
+ +

Khi bạn mở Trình quản lý SDK lần đầu, một vài gói sẽ được chọn +theo mặc định. Để nguyên những gói được chọn này, nhưng hãy chắc chắn bạn có mọi thứ mình cần +để bắt đầu bằng cách làm theo những bước sau:

+ + +
    +
  1. +

    Tải công cụ SDK mới nhất

    + + + +

    Tối thiểu khi thiết đặt SDK Android, + bạn nên tải xuống những công cụ và nền tảng Android mới nhất:

    +
      +
    1. Mở thư mục Tools và chọn: +
        +
      • Công cụ SDK Android
      • +
      • Công cụ Nền tảng SDK Android
      • +
      • Công cụ Xây dựng SDK Android (phiên bản mới nhất)
      • +
      +
    2. +
    3. Mở thư mục Android X.X đầu tiên (phiên bản mới nhất) và chọn: +
        +
      • Nền tảng SDK
      • +
      • Một ảnh hệ thống cho trình mô phỏng, chẳng hạn như
        + Ảnh Hệ thống ARM EABI v7a
      • +
      +
    4. +
    +
  2. + +
  3. +

    Tải thư viện hỗ trợ cho các API bổ sung

    + + + +

    Thư viện Hỗ trợ Android + cung cấp một tập API mở rộng tương thích với hầu hết các phiên bản của Android.

    + +

    Mở thư mục Extras và chọn:

    +
      +
    • Kho Hỗ trợ Android
    • +
    • Thư viện Hỗ trợ Android
    • +
    + +

     

    +

     

    + +
  4. + + +
  5. +

    Tải dịch vụ Google Play để có nhiều API hơn nữa

    + + + +

    Để phát triển bằng các API của Google, bạn cần gói dịch vụ Google Play.

    +

    Mở thư mục Extras và chọn:

    +
      +
    • Kho Google
    • +
    • Dịch vụ Google Play
    • +
    + +

    Lưu ý: Các API dịch vụ Google Play không có sẵn trên tất cả + thiết bị dựa trên nền tảng Android, nhưng có sẵn trên tất cả thiết bị có Google Play Store. Để sử dụng những + API này trong trình mô phỏng Android, bạn cũng phải cài đặt ảnh hệ thống Google API + từ thư mục Android X.X mới nhất trong Trình quản lý SDK.

    +
  6. + + +
  7. +

    Cài đặt gói

    +

    Sau khi bạn đã chọn tất cả gói mong muốn, hãy tiếp tục cài đặt:

    +
      +
    1. Bấm Cài đặt X gói.
    2. +
    3. Trong cửa sổ tiếp theo, bấm đúp vào tên của từng gói ở bên trái + để chấp nhận thỏa thuận cấp phép cho từng gói.
    4. +
    5. Bấm Cài đặt.
    6. +
    +

    Tiến trình tải xuống được hiện ở dưới cùng của cửa sổ Trình quản lý SDK. + Không được thoát Trình quản lý SDK nếu không nó sẽ hủy bỏ việc tải xuống.

    +
  8. + +
  9. +

    Xây dựng một thứ gì đó!

    + +

    Với những gói trên có trong SDK Android của bạn, giờ bạn đã sẵn sàng để xây dựng ứng dụng +cho Android. Khi có sẵn các công cụ mới và API khác, chỉ cần khởi chạy Trình quản lý SDK + để tải xuống các gói mới cho SDK của bạn.

    + +

    Sau đây là một vài tùy chọn về cách bạn nên tiến hành:

    + +
    +
    +

    Bắt đầu

    +

    Nếu bạn mới làm quen với phát triển Android, hãy tìm hiểu những nội dung cơ bản của ứng dụng Androi bằng cách làm theo +hướng dẫn Xây dựng Ứng dụng Đầu tiên của bạn.

    + +
    +
    +

    Xây dựng cho thiết bị đeo được

    +

    Nếu bạn sẵn sàng bắt đầu xây dựng ứng dụng cho thiết bị đeo được Android, hãy xem hướng dẫn +Xây dựng Ứng dụng cho Android Wear.

    + +
    +
    +

    Sử dụng API của Google

    +

    Để bắt đầu sử dụng các API của Google, chẳng hạn như các dịch vụ Bản đồ hoặc +Chơi Trò chơi, hãy xem hướng dẫn +Thiết đặt Dịch vụ Google Play +.

    + +
    +
    + + +
  10. + +
+ + diff --git a/docs/html-intl/intl/zh-cn/guide/components/activities.jd b/docs/html-intl/intl/zh-cn/guide/components/activities.jd new file mode 100644 index 0000000000000000000000000000000000000000..efc1fb1ba71129161d5ff9465a48cd7c06c860d5 --- /dev/null +++ b/docs/html-intl/intl/zh-cn/guide/components/activities.jd @@ -0,0 +1,756 @@ +page.title=Activity +page.tags=Activity, Intent +@jd:body + + + + + +

{@link android.app.Activity} +是一个应用组件,用户可与其提供的屏幕进行交互,以执行拨打电话、拍摄照片、发送电子邮件或查看地图等操作。 +每个 Activity 都会获得一个用于绘制其用户界面的窗口。窗口通常会充满屏幕,但也可小于屏幕并浮动在其他窗口之上。 + +

+ +

一个应用通常由多个彼此松散联系的 Activity 组成。 +一般会指定应用中的某个 Activity 为“主” Activity,即首次启动应用时呈现给用户的那个 Activity。 +而且每个 Activity 均可启动另一个 Activity,以便执行不同的操作。 +每次新 Activity 启动时,前一 Activity 便会停止,但系统会在堆栈(“返回栈”)中保留该 Activity。 + +当新 Activity 启动时,系统会将其推送到返回栈上,并取得用户焦点。 +返回栈遵循“后进先出”堆栈机制,因此,当用户完成当前 Activity 并按“返回” + +按钮时,系统会从堆栈中将其弹出(并销毁),然后恢复前一 Activity。(任务和返回栈文档中对返回栈有更详细的阐述。) + +

+ +

当一个 Activity 因某个新 Activity 启动而停止时,系统会通过该 Activity 的生命周期回调方法通知其这一状态变化。Activity 因状态变化—系统是创建 Activity、停止 Activity、恢复 Activity 还是销毁 Activity— 而收到的回调方法可能有若干种,每一种回调方法都会为您提供执行与该状态变化相应的特定操作的机会。 + + + + +例如,停止时,您的 Activity 应释放任何大型对象,例如网络或数据库连接。 +当 Activity 恢复时,您可以重新获取所需资源,并恢复执行中断的操作。 +这些状态转变都是 Activity 生命周期的一部分。 +

+ +

本文的其余部分阐述有关如何创建和使用 Activity 的基础知识(包括对 Activity 生命周期工作方式的全面阐述),以便您正确管理各种 Activity 状态之间的转变。 + +

+ + + +

创建 Activity

+ +

要创建 Activity,您必须创建 {@link android.app.Activity} +的子类(或使用其现有子类)。您需要在子类中实现 Activity 在其生命周期的各种状态之间转变时(例如创建 Activity、停止 Activity、恢复 Activity 或销毁 Activity 时)系统调用的回调方法。 + +两个最重要的回调方法是: +

+ +
+
{@link android.app.Activity#onCreate onCreate()}
+
您必须实现此方法。系统会在创建您的 +Activity 时调用此方法。您应该在实现内初始化 Activity 的必需组件。 + + 最重要的是,您必须在此方法内调用 {@link android.app.Activity#setContentView + setContentView()},以定义 Activity 用户界面的布局。
+
{@link android.app.Activity#onPause onPause()}
+
系统将此方法作为用户离开 Activity 的第一个信号(但并不总是意味着 Activity 会被销毁)进行调用。 +您通常应该在此方法内确认在当前用户会话结束后仍然有效的任何更改(因为用户可能不会返回)。 + +
+
+ +

您还应使用几种其他生命周期回调方法,以便提供流畅的 Activity 间用户体验,以及处理导致您的 Activity 停止甚至被销毁的意外中断。 + +后文的管理 Activity 生命周期部分对所有生命周期回调方法进行了阐述。 +

+ + + +

实现用户界面

+ +

Activity 的用户界面是由层级式视图—衍生自 {@link android.view.View} +类的对象—提供的。每个视图都控制 Activity 窗口内的特定矩形空间,可对用户交互作出响应。 +例如,视图可以是在用户触摸时启动某项操作的按钮。 +

+ +

您可以利用 Android +提供的许多现成视图设计和组织您的布局。“小工具”是提供按钮、文本字段、复选框或仅仅是一幅图像等屏幕视觉(交互式)元素的视图。 +“布局”是衍生自 {@link +android.view.ViewGroup} +的视图,为其子视图提供唯一布局模型,例如线性布局、网格布局或相对布局。您还可以为 {@link android.view.View} 类和 +{@link android.view.ViewGroup} +类创建子类(或使用其现有子类)来自行创建小工具和布局,然后将它们应用于您的 Activity 布局。

+ +

利用视图定义布局的最常见方法是借助保存在您的应用资源内的 XML +布局文件。这样一来,您就可以将用户界面的设计与定义 Activity 行为的源代码分开维护。 +您可以通过 {@link android.app.Activity#setContentView(int) setContentView()} +将布局设置为 Activity 的 UI,从而传递布局的资源 +ID。不过,您也可以在 Activity 代码中创建新 +{@link android.view.View},并通过将新 {@link +android.view.View} 插入 {@link android.view.ViewGroup} 来创建视图层次,然后通过将根 +{@link android.view.ViewGroup} 传递到 {@link android.app.Activity#setContentView(View) +setContentView()} 来使用该布局。

+ +

如需了解有关创建用户界面的信息,请参阅用户界面文档。

+ + + +

在清单文件中声明 Activity

+ +

您必须在清单文件中声明您的 Activity,这样系统才能访问它。 +要声明您的 Activity,请打开您的清单文件,并将 {@code <activity>} 元素添加为 +{@code <application>} +元素的子项。例如:

+ +
+<manifest ... >
+  <application ... >
+      <activity android:name=".ExampleActivity" />
+      ...
+  </application ... >
+  ...
+</manifest >
+
+ +

您还可以在此元素中加入几个其他特性,以定义 Activity 标签、Activity 图标或风格主题等用于设置 Activity +UI +风格的属性。{@code android:name} +特性是唯一的必需特性—它指定 Activity 的类名。应用一旦发布,即不应更改此类名,否则,可能会破坏诸如应用快捷方式等一些功能(请阅读博客文章 +Things +That Cannot Change +[不能更改的内容])。

+ +

请参阅 {@code <activity>} +元素参考文档,了解有关在清单文件中声明 Activity 的详细信息。

+ + +

使用 Intent 过滤器

+ +

{@code +<activity>} 元素还可指定各种 Intent 过滤器—使用 {@code +<Intent-filter>} +元素—以声明其他应用组件激活它的方法。

+ +

当您使用 Android SDK 工具创建新应用时,系统自动为您创建的存根 Activity 包含一个 Intent 过滤器,其中声明了该 Activity 响应“主”操作且应置于“launcher”类别内。 + + Intent 过滤器的内容与以下所示类似: +

+ +
+<activity android:name=".ExampleActivity" android:icon="@drawable/app_icon">
+    <intent-filter>
+        <action android:name="android.intent.action.MAIN" />
+        <category android:name="android.intent.category.LAUNCHER" />
+    </intent-filter>
+</activity>
+
+ +

{@code +<action>} 元素指定这是应用的“主”入口点。{@code +<category>} +元素指定此 Activity 应列入系统的应用启动器内(以便用户启动该 Activity)。

+ +

如果您打算让应用成为独立应用,不允许其他应用激活其 Activity,则您不需要任何其他 Intent 过滤器。 +正如前例所示,只应有一个 Activity 具有“主”操作和“launcher”类别。 +您不想提供给其他应用的 Activity 不应有任何 Intent 过滤器,您可以利用显式 Intent 自行启动它们(下文对此做了阐述)。 + +

+ +

不过,如果您想让 Activity 对衍生自其他应用(以及您的自有应用)的隐式 Intent 作出响应,则必须为 Activity 定义其他 Intent 过滤器。 + +对于您想要作出响应的每一个 Intent 类型,您都必须加入相应的 {@code +<Intent-filter>},其中包括一个 +{@code +<action>} 元素,还可选择性地包括一个 {@code +<category>} 元素和/或一个 {@code +<data>} 元素。这些元素指定您的 Activity 可以响应的 Intent 类型。 +

+ +

如需了解有关您的 Activity 如何响应 Intent 的详细信息,请参阅 Intent 和 Intent 过滤器文档。 +

+ + + +

启动 Activity

+ +

您可以通过调用 {@link android.app.Activity#startActivity + startActivity()},并将其传递给描述您想启动的 Activity 的 {@link android.content.Intent} +来启动另一个 Activity。Intent 对象会指定您想启动的具体 Activity 或描述您想执行的操作类型(系统会为您选择合适的 Activity,甚至是来自其他应用的 Activity)。 + + +Intent 对象还可能携带少量供所启动 Activity 使用的数据。 +

+ +

在您的自有应用内工作时,您经常只需要启动某个已知 Activity。 + 您可以通过使用类名创建一个显式定义您想启动的 Activity 的 Intent 对象来实现此目的。 +例如,可以通过以下代码让一个 Activity 启动另一个名为 {@code +SignInActivity} 的 Activity:

+ +
+Intent intent = new Intent(this, SignInActivity.class);
+startActivity(intent);
+
+ +

不过,您的应用可能还需要利用您的 Activity 数据执行某项操作,例如发送电子邮件、短信或状态更新。 +在这种情况下,您的应用自身可能不具有执行此类操作所需的 Activity,因此您可以改为利用设备上其他应用提供的 Activity 为您执行这些操作。 + +这便是 Intent 对象的真正价值所在—您可以创建一个 Intent 对象,对您想执行的操作进行描述,系统会从其他应用启动相应的 Activity。 + + +如果有多个 Activity 可以处理 Intent,则用户可以选择要使用哪一个。 +例如,如果您想允许用户发送电子邮件,可以创建以下 Intent 对象: + +

+ +
+Intent intent = new Intent(Intent.ACTION_SEND);
+intent.putExtra(Intent.EXTRA_EMAIL, recipientArray);
+startActivity(intent);
+
+ +

添加到 Intent 中的 {@link android.content.Intent#EXTRA_EMAIL} extra 是一个字符串数组,其中包含应将电子邮件发送到的电子邮件地址。 +当电子邮件应用响应此 Intent 时,它会读取 extra 中提供的字符串数组,并将它们放入电子邮件撰写窗体的“收件人”字段。 + +在这种情况下,电子邮件应用的 Activity 启动,并且当用户完成操作时,您的 Activity 会恢复执行。 +

+ + + + +

启动 Activity 以获得结果

+ +

有时,您可能需要从启动的 Activity 获得结果。在这种情况下,请通过调用 {@link android.app.Activity#startActivityForResult + startActivityForResult()}(而非 {@link android.app.Activity#startActivity + startActivity()})来启动 Activity。 +要想在随后收到后续 Activity 的结果,请实现 +{@link android.app.Activity#onActivityResult onActivityResult()} 回调方法。 +当后续 Activity 完成时,它会使用 {@link +android.content.Intent} 向您的 {@link android.app.Activity#onActivityResult onActivityResult()} +方法返回结果。

+ +

例如,您可能希望用户选取其中一位联系人,以便您的 Activity 对该联系人中的信息执行某项操作。 +您可以通过以下代码创建此类 Intent 并处理结果: +

+ +
+private void pickContact() {
+    // Create an intent to "pick" a contact, as defined by the content provider URI
+    Intent intent = new Intent(Intent.ACTION_PICK, Contacts.CONTENT_URI);
+    startActivityForResult(intent, PICK_CONTACT_REQUEST);
+}
+
+@Override
+protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+    // If the request went well (OK) and the request was PICK_CONTACT_REQUEST
+    if (resultCode == Activity.RESULT_OK && requestCode == PICK_CONTACT_REQUEST) {
+        // Perform a query to the contact's content provider for the contact's name
+        Cursor cursor = getContentResolver().query(data.getData(),
+        new String[] {Contacts.DISPLAY_NAME}, null, null, null);
+        if (cursor.moveToFirst()) { // True if the cursor is not empty
+            int columnIndex = cursor.getColumnIndex(Contacts.DISPLAY_NAME);
+            String name = cursor.getString(columnIndex);
+            // Do something with the selected contact's name...
+        }
+    }
+}
+
+ +

上例显示的是,您在处理 Activity 结果时应该在 {@link +android.app.Activity#onActivityResult onActivityResult()} +方法中使用的基本逻辑。第一个条件检查请求是否成功(如果成功,则{@code resultCode} 将为 {@link android.app.Activity#RESULT_OK})以及此结果响应的请求是否已知 — 在此情况下,{@code requestCode}与随 {@link android.app.Activity#startActivityForResult +startActivityForResult()} 发送的第二个参数匹配。 + + +代码通过查询 {@link android.content.Intent} 中返回的数据({@code data} 参数)从该处开始处理 Activity 结果。 +

+ +

实际情况是,{@link +android.content.ContentResolver} 对一个内容提供程序执行查询,后者返回一个 +{@link android.database.Cursor},让查询的数据能够被读取。如需了解详细信息,请参阅内容提供程序文档。 +

+ +

如需了解有关 Intent 用法的详细信息,请参阅 Intent 和 Intent 过滤器文档。 +

+ + +

结束 Activity

+ +

您可以通过调用 Activity 的 {@link android.app.Activity#finish +finish()} 方法来结束该 Activity。您还可以通过调用 +{@link android.app.Activity#finishActivity finishActivity()} 结束您之前启动的另一个 Activity。

+ +

注:在大多数情况下,您不应使用这些方法显式结束 Activity。 +正如下文有关 Activity 生命周期的部分所述,Android 系统会为您管理 Activity 的生命周期,因此您无需完成自己的 Activity。 + +调用这些方法可能对预期的用户体验产生不良影响,因此只应在您确实不想让用户返回此 Activity 实例时使用。 + +

+ + +

管理 Activity 生命周期

+ +

通过实现回调方法管理 Activity 的生命周期对开发强大而又灵活的应用至关重要。 + +Activity 的生命周期会直接受到 Activity 与其他 Activity、其任务及返回栈的关联性的影响。 +

+ +

Activity 基本上以三种状态存在:

+ +
+
已继续
+
此 Activity 位于屏幕前台并具有用户焦点。(有时也将此状态称作“运行中”。) +
+ +
已暂停
+
另一个 Activity 位于屏幕前台并具有用户焦点,但此 Activity 仍可见。也就是说,另一个 Activity 显示在此 Activity 上方,并且该 Activity 部分透明或未覆盖整个屏幕。 + +已暂停的 Activity 处于完全 Activity 状态({@link android.app.Activity} +对象保留在内存中,它保留了所有状态和成员信息,并与窗口管理器保持连接),但在内存极度不足的情况下,可能会被系统终止。 +
+ +
已停止
+
该 Activity 被另一个 Activity 完全遮盖(该 Activity 目前位于“后台”)。 +已停止的 Activity 同样仍处于 Activity 状态({@link android.app.Activity} +对象保留在内存中,它保留了所有状态和成员信息,但与窗口管理器连接)。 +不过,它对用户不再可见,在他处需要内存时可能会被系统终止。 +
+
+ +

如果 Activity 处于暂停或停止状态,系统可通过要求其结束(调用其 +{@link android.app.Activity#finish finish()} +方法)或直接终止其进程,将其从内存中删除。(将其结束或终止后)再次打开 Activity 时,必须重建。 +

+ + + +

实现生命周期回调

+ +

当一个 Activity 转入和转出上述不同状态时,系统会通过各种回调方法向其发出通知。 +所有回调方法都是挂钩,您可以在 Activity 状态发生变化时替代这些挂钩来执行相应操作。 +以下框架 Activity 包括每一个基本生命周期方法: +

+ + +
+public class ExampleActivity extends Activity {
+    @Override
+    public void {@link android.app.Activity#onCreate onCreate}(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        // The activity is being created.
+    }
+    @Override
+    protected void {@link android.app.Activity#onStart onStart()} {
+        super.onStart();
+        // The activity is about to become visible.
+    }
+    @Override
+    protected void {@link android.app.Activity#onResume onResume()} {
+        super.onResume();
+        // The activity has become visible (it is now "resumed").
+    }
+    @Override
+    protected void {@link android.app.Activity#onPause onPause()} {
+        super.onPause();
+        // Another activity is taking focus (this activity is about to be "paused").
+    }
+    @Override
+    protected void {@link android.app.Activity#onStop onStop()} {
+        super.onStop();
+        // The activity is no longer visible (it is now "stopped")
+    }
+    @Override
+    protected void {@link android.app.Activity#onDestroy onDestroy()} {
+        super.onDestroy();
+        // The activity is about to be destroyed.
+    }
+}
+
+ +

注:正如以上示例所示,您在实现这些生命周期方法时必须始终先调用超类实现,然后再执行任何操作。 +

+ +

这些方法共同定义 Activity 的整个生命周期。您可以通过实现这些方法监控 Activity 生命周期中的三个嵌套循环: +

+ +
    +
  • Activity 的整个生命周期发生在 {@link +android.app.Activity#onCreate onCreate()} 调用与 {@link +android.app.Activity#onDestroy} 调用之间。您的 Activity 应在 {@link android.app.Activity#onCreate onCreate()} +中执行“全局”状态设置(例如定义布局),并释放 {@link android.app.Activity#onDestroy} +中的所有其余资源。例如,如果您的 Activity 有一个在后台运行的线程,用于从网络上下载数据,它可能会在 +{@link android.app.Activity#onCreate onCreate()} 中创建该线程,然后在 +{@link +android.app.Activity#onDestroy} 中停止该线程。
  • + +
  • Activity 的可见生命周期发生在 {@link +android.app.Activity#onStart onStart()} 调用与 {@link +android.app.Activity#onStop onStop()} 调用之间。在这段时间,用户可以在屏幕上看到 Activity 并与其交互。 +例如,当一个新 Activity 启动,并且此 Activity 不再可见时,系统会调用 +{@link android.app.Activity#onStop onStop()}。您可以在调用这两个方法之间保留向用户显示 Activity 所需的资源。 +例如,您可以在 +{@link +android.app.Activity#onStart onStart()} 中注册一个 {@link android.content.BroadcastReceiver} 以监控影响 UI +的变化,并在用户无法再看到您显示的内容时在 {@link android.app.Activity#onStop onStop()} +中将其取消注册。在 Activity 的整个生命周期,当 Activity 在对用户可见和隐藏两种状态中交替变化时,系统可能会多次调用 {@link android.app.Activity#onStart onStart()} 和 {@link +android.app.Activity#onStop onStop()}。 +

  • + +
  • Activity 的前台生命周期发生在 {@link +android.app.Activity#onResume onResume()} 调用与 {@link android.app.Activity#onPause +onPause()} 调用之间。在这段时间,Activity 位于屏幕上的所有其他 Activity 之前,并具有用户输入焦点。 +Activity 可频繁转入和转出前台—例如,当设备转入休眠状态或出现对话框时,系统会调用 {@link android.app.Activity#onPause onPause()}。 + +由于此状态可能经常发生转变,因此这两个方法中应采用适度轻量级的代码,以避免因转变速度慢而让用户等待。 +

  • +
+ +

图 1 说明了这些循环以及 Activity 在状态转变期间可能经过的路径。矩形表示回调方法,当 Activity 在不同状态之间转变时,您可以实现这些方法来执行操作。 + +

+ + +

图 1. Activity 生命周期。

+ +

表 1 列出了相同的生命周期回调方法,其中对每一种回调方法做了更详细的描述,并说明了每一种方法在 Activity 整个生命周期内的位置,包括在回调方法完成后系统能否终止 Activity。 + + +

+ +

表 1. Activity 生命周期回调方法汇总表。 +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
方法 描述 是否能事后终止? 后接
{@link android.app.Activity#onCreate onCreate()}首次创建 Activity 时调用。 + 您应该在此方法中执行所有正常的静态设置— +创建视图、将数据绑定到列表等等。系统向此方法传递一个 Bundle 对象,其中包含 Activity 的上一状态,不过前提是捕获了该状态(请参阅后文的保存 Activity 状态)。 + + + +

始终后接 {@code onStart()}。

{@code onStart()}
    {@link android.app.Activity#onRestart +onRestart()}在 Activity 已停止并即将再次启动前调用。 + +

始终后接 {@code onStart()}

{@code onStart()}
{@link android.app.Activity#onStart onStart()}在 Activity 即将对用户可见之前调用。 +

如果 Activity 转入前台,则后接 {@code onResume()},如果 Activity 转入隐藏状态,则后接 {@code onStop()}。 +

{@code onResume()}

{@code onStop()}
    {@link android.app.Activity#onResume onResume()}在 Activity 即将开始与用户进行交互之前调用。 +此时,Activity 处于 Activity 堆栈的顶层,并具有用户输入焦点。 + +

始终后接 {@code onPause()}。

{@code onPause()}
{@link android.app.Activity#onPause onPause()}当系统即将开始继续另一个 Activity 时调用。 +此方法通常用于确认对持久性数据的未保存更改、停止动画以及其他可能消耗 CPU 的内容,诸如此类。 + +它应该非常迅速地执行所需操作,因为它返回后,下一个 Activity 才能继续执行。 + +

如果 Activity 返回前台,则后接 {@code onResume()},如果 Activity 转入对用户不可见状态,则后接 {@code onStop()}。 + +

{@code onResume()}

{@code onStop()}
{@link android.app.Activity#onStop onStop()}Activity 对用户不再可见时调用。如果 Activity 被销毁,或另一个 Activity(一个现有 Activity 或新 Activity)继续执行并将其覆盖,就可能发生这种情况。 + + +

如果 Activity 恢复与用户的交互,则后接 {@code onRestart()},如果 Activity 被销毁,则后接 +{@code onDestroy()}。 +

{@code onRestart()}

{@code onDestroy()}
{@link android.app.Activity#onDestroy +onDestroy()}在 Activity 被销毁前调用。这是 Activity 将收到的最后调用。 +当 Activity 结束(有人调用 Activity 上的 {@link android.app.Activity#finish + finish()}),或系统为节省空间而暂时销毁该 Activity 实例时,可能会调用它。 + +您可以通过 + {@link + android.app.Activity#isFinishing isFinishing()} 方法区分这两种情形。
+ +

名为“是否能事后终止?”的列表示系统是否能在不执行另一行 Activity 代码的情况下,在方法返回后随时终止承载 Activity 的进程。 + +有三个方法带有“是”标记:({@link +android.app.Activity#onPause +onPause()}、{@link android.app.Activity#onStop onStop()} 和 {@link android.app.Activity#onDestroy +onDestroy()})。由于 {@link android.app.Activity#onPause onPause()} +是这三个方法中的第一个,因此 Activity 创建后,{@link android.app.Activity#onPause onPause()} +必定成为最后调用的方法,然后才能终止进程—如果系统在紧急情况下必须恢复内存,则可能不会调用 +{@link +android.app.Activity#onStop onStop()} 和 +{@link android.app.Activity#onDestroy onDestroy()}。因此,您应该使用 {@link android.app.Activity#onPause onPause()} +向存储设备写入至关重要的持久性数据(例如用户编辑)。不过,您应该对 +{@link android.app.Activity#onPause onPause()} +调用期间必须保留的信息有所选择,因为该方法中的任何阻止过程都会妨碍向下一个 Activity 的转变并拖慢用户体验。 +

+ +

是否能在事后终止?列中标记为“否”的方法可从系统调用它们的一刻起防止承载 Activity 的进程被终止。 +因此,在从 {@link android.app.Activity#onPause onPause()} +返回的时间到 {@link android.app.Activity#onResume onResume()} +被调用的时间,系统可以终止 Activity。在 {@link android.app.Activity#onPause onPause()} +被再次调用并返回前,将无法再次终止 Activity。

+ +

注:根据表 +1 +中的定义属于技术上无法“终止”的 Activity 仍可能被系统终止—但这种情况只有在无任何其他资源的极端情况下才会发生。进程和线程处理文档对可能会终止 Activity 的情况做了更详尽的阐述。 + +

+ + +

保存 Activity 状态

+ +

管理 Activity 生命周期的引言部分简要提及,当 Activity 暂停或停止时,Activity 的状态会得到保留。 + +确实如此,因为当 Activity 暂停或停止时,{@link android.app.Activity} +对象仍保留在内存中 — 有关其成员和当前状态的所有信息仍处于 Activity 状态。 +因此,用户在 Activity 内所做的任何更改都会得到保留,这样一来,当 Activity 返回前台(当它“继续”)时,这些更改仍然存在。 + +

+ +

不过,当系统为了恢复内存而销毁某项 Activity 时,{@link +android.app.Activity} +对象也会被销毁,因此系统在继续 Activity 时根本无法让其状态保持完好,而是必须在用户返回Activity时重建 {@link android.app.Activity} +对象。但用户并不知道系统销毁 Activity 后又对其进行了重建,因此他们很可能认为 Activity 状态毫无变化。 + +在这种情况下,您可以实现另一个回调方法对有关 Activity 状态的信息进行保存,以确保有关 Activity 状态的重要信息得到保留:{@link +android.app.Activity#onSaveInstanceState onSaveInstanceState()}。 + +

+ +

系统会先调用 +{@link android.app.Activity#onSaveInstanceState onSaveInstanceState()},然后再使 Activity 变得易于销毁。系统会向该方法传递一个 +{@link android.os.Bundle},您可以在其中使用 +{@link +android.os.Bundle#putString putString()} 和 {@link +android.os.Bundle#putInt putInt()} 等方法以名称-值对形式保存有关 Activity 状态的信息。然后,如果系统终止您的应用进程,并且用户返回您的 Activity,则系统会重建该 Activity,并将 +{@link android.os.Bundle} 同时传递给 +{@link android.app.Activity#onCreate onCreate()} 和 {@link +android.app.Activity#onRestoreInstanceState onRestoreInstanceState()}。您可以使用上述任一方法从 +{@link android.os.Bundle} +提取您保存的状态并恢复该 Activity 状态。如果没有状态信息需要恢复,则传递给您的 {@link +android.os.Bundle} +是空值(如果是首次创建该 Activity,就会出现这种情况)。

+ + +

图 2. 在两种情况下,Activity 重获用户焦点时可保持状态完好:系统在销毁 Activity 后重建 Activity,Activity 必须恢复之前保存的状态;系统停止 Activity 后继续执行 Activity,并且 Activity 状态保持完好。 + + +

+ +

注:无法保证系统会在销毁您的 Activity 前调用 {@link +android.app.Activity#onSaveInstanceState onSaveInstanceState()},因为存在不需要保存状态的情况(例如用户使用“返回” + +按钮离开您的 Activity 时,因为用户的行为是在显式关闭 Activity)。 + +如果系统调用 {@link android.app.Activity#onSaveInstanceState +onSaveInstanceState()},它会在调用 {@link +android.app.Activity#onStop onStop()} 之前,并且可能会在调用 {@link android.app.Activity#onPause +onPause()} 之前进行调用。

+ +

不过,即使您什么都不做,也不实现 {@link +android.app.Activity#onSaveInstanceState onSaveInstanceState()},{@link android.app.Activity} +类的 {@link +android.app.Activity#onSaveInstanceState onSaveInstanceState()} 默认实现也会恢复部分 Activity 状态。具体地讲,默认实现会为布局中的每个 +{@link +android.view.View} 调用相应的 {@link +android.view.View#onSaveInstanceState onSaveInstanceState()} +方法,让每个视图都能提供有关自身的应保存信息。Android +框架中几乎每个小工具都会根据需要实现此方法,以便在重建 Activity 时自动保存和恢复对 UI +所做的任何可见更改。例如,{@link android.widget.EditText} +小工具保存用户输入的任何文本,{@link android.widget.CheckBox} +小工具保存复选框的选中或未选中状态。您只需为想要保存其状态的每个小工具提供一个唯一的 ID(通过 {@code android:id} +属性)。如果小工具没有 ID,则系统无法保存其状态。 +

+ + + +

尽管 {@link +android.app.Activity#onSaveInstanceState onSaveInstanceState()} +的默认实现会保存有关您的Activity UI +的有用信息,您可能仍需替代它以保存更多信息。例如,您可能需要保存在 Activity 生命周期内发生了变化的成员值(它们可能与 +UI 中恢复的值有关联,但默认情况下系统不会恢复储存这些 UI +值的成员)。

+ +

由于 {@link +android.app.Activity#onSaveInstanceState onSaveInstanceState()} 的默认实现有助于保存 UI 的状态, +因此如果您为了保存更多状态信息而重写该方法,应始终先调用 +{@link android.app.Activity#onSaveInstanceState onSaveInstanceState()} +的超类实现,然后再执行任何操作。同样,如果您替代 {@link +android.app.Activity#onRestoreInstanceState onRestoreInstanceState()} +方法,也应调用它的超类实现,以便默认实现能够恢复视图状态。

+ +

注:由于无法保证系统会调用 +{@link android.app.Activity#onSaveInstanceState +onSaveInstanceState()},因此您只应利用它来记录 Activity 的瞬态(UI +的状态)—切勿使用它来存储持久性数据,而应使用 {@link +android.app.Activity#onPause onPause()} +在用户离开 Activity 后存储持久性数据(例如应保存到数据库的数据)。

+ +

您只需旋转设备,让屏幕方向发生变化,就能有效地测试您的应用的状态恢复能力。 +当屏幕方向变化时,系统会销毁并重建 Activity,以便应用可供新屏幕配置使用的备用资源。 + +单凭这一理由,您的 Activity 在重建时能否完全恢复其状态就显得非常重要,因为用户在使用应用时经常需要旋转屏幕。 + +

+ + +

处理配置变更

+ +

有些设备配置可能会在运行时发生变化(例如屏幕方向、键盘可用性及语言)。 +发生此类变化时,Android 会重建运行中的 Activity(系统调用 +{@link android.app.Activity#onDestroy},然后立即调用 {@link +android.app.Activity#onCreate onCreate()})。此行为旨在通过利用您提供的备用资源(例如适用于不同屏幕方向和屏幕尺寸的不同布局)自动重新加载您的应用来帮助它适应新配置。 + + +

+ +

如果您对 Activity 进行了适当设计,让它能够按以上所述处理屏幕方向变化带来的重启并恢复 Activity 状态,那么在遭遇 Activity 生命周期中的其他意外事件时,您的应用将具有更强的适应性。 + +

+ +

正如上文所述,处理此类重启的最佳方法 +是利用 {@link + android.app.Activity#onSaveInstanceState onSaveInstanceState()} 和 {@link +android.app.Activity#onRestoreInstanceState onRestoreInstanceState()}(或 {@link +android.app.Activity#onCreate onCreate()})保存并恢复 Activity 的状态。

+ +

如需了解有关运行时发生的配置变更以及应对方法的详细信息,请阅读处理运行时变更指南。 + +

+ + + +

协调 Activity

+ +

当一个 Activity 启动另一个 Activity 时,它们都会体验到生命周期转变。第一个 Activity 暂停并停止(但如果它在后台仍然可见,则不会停止)时,系统会创建另一个 Activity。 + +如果这些 Activity 共用保存到磁盘或其他地方的数据,必须了解的是,在创建第二个 Activity 前,第一个 Activity 不会完全停止。更确切地说,启动第二个 Activity 的过程与停止第一个 Activity 的过程存在重叠。 + + +

+ +

生命周期回调的顺序经过明确定义,当两个 Activity 位于同一进程,并且由一个 Activity 启动另一个 Activity 时,其定义尤其明确。 +以下是当 Activity A +启动 Activity B 时一系列操作的发生顺序:

+ +
    +
  1. Activity A 的 {@link android.app.Activity#onPause onPause()} 方法执行。
  2. + +
  3. Activity B 的 {@link android.app.Activity#onCreate onCreate()}、{@link +android.app.Activity#onStart onStart()} 和 {@link android.app.Activity#onResume onResume()} +方法依次执行。(Activity B 现在具有用户焦点。)
  4. + +
  5. 然后,如果 Activity A 在屏幕上不再可见,则其 {@link +android.app.Activity#onStop onStop()} 方法执行。
  6. +
+ +

您可以利用这种可预测的生命周期回调顺序管理从一个 Activity 到另一个 Activity 的信息转变。 +例如,如果您必须在第一个 Activity 停止时向数据库写入数据,以便下一个 Activity 能够读取该数据,则应在 +{@link android.app.Activity#onPause onPause()} +而不是 {@link +android.app.Activity#onStop onStop()} 执行期间向数据库写入数据。

+ + diff --git a/docs/html-intl/intl/zh-cn/guide/components/bound-services.jd b/docs/html-intl/intl/zh-cn/guide/components/bound-services.jd new file mode 100644 index 0000000000000000000000000000000000000000..ed6aaf6ecbf85ec3689785f2e1c87588122ed6a4 --- /dev/null +++ b/docs/html-intl/intl/zh-cn/guide/components/bound-services.jd @@ -0,0 +1,658 @@ +page.title=绑定服务 +parent.title=服务 +parent.link=services.html +@jd:body + + +
+
    +

    本文内容

    +
      +
    1. 基础知识
    2. +
    3. 创建绑定服务 +
        +
      1. 扩展 Binder 类
      2. +
      3. 使用 Messenger
      4. +
      +
    4. +
    5. 绑定到服务
    6. +
    7. 管理绑定服务的生命周期
    8. +
    + +

    关键类

    +
      +
    1. {@link android.app.Service}
    2. +
    3. {@link android.content.ServiceConnection}
    4. +
    5. {@link android.os.IBinder}
    6. +
    + +

    示例

    +
      +
    1. {@code + RemoteService}
    2. +
    3. {@code + LocalService}
    4. +
    + +

    另请参阅

    +
      +
    1. 服务
    2. +
    +
+ + +

绑定服务是客户端-服务器接口中的服务器。绑定服务可让组件(例如 Activity)绑定到服务、发送请求、接收响应,甚至执行进程间通信 (IPC)。 + +绑定服务通常只在为其他应用组件服务时处于活动状态,不会无限期在后台运行。 +

+ +

本文向您介绍如何创建绑定服务,包括如何绑定到来自其他应用组件的服务。 +不过,您还应参阅服务文档,了解有关一般服务的更多信息,例如:如何利用服务传送通知、如何将服务设置为在前台运行等等。 + +

+ + +

基础知识

+ +

绑定服务是 {@link android.app.Service} +类的实现,可让其他应用与其绑定和交互。要提供服务绑定,您必须实现 {@link android.app.Service#onBind onBind()} +回调方法。该方法返回的 +{@link android.os.IBinder} +对象定义了客户端用来与服务进行交互的编程接口。

+ + + +

客户端可通过调用 {@link android.content.Context#bindService +bindService()} 绑定到服务。调用时,它必须提供 {@link +android.content.ServiceConnection} 的实现,后者会监控与服务的连接。{@link +android.content.Context#bindService bindService()} 方法会立即无值返回,但当 +Android +系统创建客户端与服务之间的连接时,会调用 {@link +android.content.ServiceConnection} 上的 {@link +android.content.ServiceConnection#onServiceConnected onServiceConnected()},向客户端传递用来与服务通信的 +{@link android.os.IBinder}。

+ +

多个客户端可同时连接到一个服务。不过,只有在第一个客户端绑定时,系统才会调用服务的 +{@link android.app.Service#onBind onBind()} +方法来检索 {@link android.os.IBinder}。系统随后无需再次调用 +{@link android.app.Service#onBind onBind()},便可将同一 {@link android.os.IBinder} 传递至任何其他绑定的客户端。

+ +

当最后一个客户端取消与服务的绑定时,系统会将服务销毁(除非 +{@link android.content.Context#startService startService()} 也启动了该服务)。

+ +

当您实现绑定服务时,最重要的环节是定义您的 +{@link android.app.Service#onBind onBind()} 回调方法返回的接口。您可以通过几种不同的方法定义服务的 +{@link android.os.IBinder} +接口,下文对这些方法逐一做了阐述。

+ + + +

创建绑定服务

+ +

创建提供绑定的服务时,您必须提供 {@link android.os.IBinder},用以提供客户端用来与服务进行交互的编程接口。 +您可以通过三种方法定义接口: +

+ +
+
扩展 Binder 类
+
如果服务是供您的自有应用专用,并且在与客户端相同的进程中运行(常见情况),则应通过扩展 +{@link android.os.Binder} +类并从 {@link android.app.Service#onBind onBind()} +返回它的一个实例来创建接口。客户端收到 {@link android.os.Binder} +后,可利用它直接访问 {@link android.os.Binder} 实现中乃至 {@link android.app.Service} +中可用的公共方法。 +

如果服务只是您的自有应用的后台工作线程,则优先采用这种方法。 +不以这种方式创建接口的唯一原因是,您的服务被其他应用或不同的进程占用。 +

+ +
使用 Messenger
+
如需让接口跨不同的进程工作,则可使用 +{@link android.os.Messenger} 为服务创建接口。服务可以这种方式定义对应于不同类型 +{@link +android.os.Message} 对象的 {@link android.os.Handler}。此 {@link android.os.Handler} +是 {@link android.os.Messenger} 的基础,后者随后可与客户端分享一个 {@link android.os.IBinder},从而让客户端能利用 {@link +android.os.Message} +对象向服务发送命令。此外,客户端还可定义自有 +{@link android.os.Messenger},以便服务回传消息。 +

这是执行进程间通信 (IPC) 的最简单方法,因为 {@link +android.os.Messenger} +会在单一线程中创建包含所有请求的队列,这样您就不必对服务进行线程安全设计。

+
+ +
使用 AIDL
+
AIDL(Android +接口定义语言)执行所有将对象分解成原语的工作,操作系统可以识别这些原语并将它们编组到各进程中,以执行 +IPC。之前采用 {@link android.os.Messenger} 的方法实际上是以 AIDL +作为其底层结构。如上所述,{@link android.os.Messenger} +会在单一线程中创建包含所有客户端请求的队列,以便服务一次接收一个请求。不过,如果您想让服务同时处理多个请求,则可直接使用 +AIDL。 +在此情况下,您的服务必须具备多线程处理能力,并采用线程安全式设计。 +

如需直接使用 +AIDL,您必须创建一个定义编程接口的 {@code .aidl} 文件。Android SDK +工具利用该文件生成一个实现接口并处理 IPC +的抽象类,您随后可在服务内对其进行扩展。

+
+
+ +

注:大多数应用“都不会”使用 +AIDL +来创建绑定服务,因为它可能要求具备多线程处理能力,并可能导致实现的复杂性增加。因此,AIDL +并不适合大多数应用,本文也不会阐述如何将其用于您的服务。如果您确定自己需要直接使用 +AIDL,请参阅 AIDL +文档。

+ + + + +

扩展 Binder 类

+ +

如果您的服务仅供本地应用使用,不需要跨进程工作,则可以实现自有 +{@link android.os.Binder} +类,让您的客户端通过该类直接访问服务中的公共方法。

+ +

注:此方法只有在客户端和服务位于同一应用和进程内这一最常见的情况下方才有效。 +例如,对于需要将 Activity 绑定到在后台播放音乐的自有服务的音乐应用,此方法非常有效。 + +

+ +

以下是具体的设置方法:

+
    +
  1. 在您的服务中,创建一个可满足下列任一要求的 {@link android.os.Binder} 实例: +
      +
    • 包含客户端可调用的公共方法
    • +
    • 返回当前 {@link android.app.Service} +实例,其中包含客户端可调用的公共方法
    • +
    • 或返回由服务承载的其他类的实例,其中包含客户端可调用的公共方法 +
    • +
    +
  2. 从 {@link +android.app.Service#onBind onBind()} 回调方法返回此 {@link android.os.Binder} 实例。
  3. +
  4. 在客户端中,从 {@link +android.content.ServiceConnection#onServiceConnected onServiceConnected()} 回调方法接收 +{@link android.os.Binder},并使用提供的方法调用绑定服务。
  5. +
+ +

注:之所以要求服务和客户端必须在同一应用内,是为了便于客户端转换返回的对象和正确调用其 +API。服务和客户端还必须在同一进程内,因为此方法不执行任何跨进程编组。 + +

+ +

例如,以下这个服务可让客户端通过 +{@link android.os.Binder} 实现访问服务中的方法:

+ +
+public class LocalService extends Service {
+    // Binder given to clients
+    private final IBinder mBinder = new LocalBinder();
+    // Random number generator
+    private final Random mGenerator = new Random();
+
+    /**
+     * Class used for the client Binder.  Because we know this service always
+     * runs in the same process as its clients, we don't need to deal with IPC.
+     */
+    public class LocalBinder extends Binder {
+        LocalService getService() {
+            // Return this instance of LocalService so clients can call public methods
+            return LocalService.this;
+        }
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return mBinder;
+    }
+
+    /** method for clients */
+    public int getRandomNumber() {
+      return mGenerator.nextInt(100);
+    }
+}
+
+ +

{@code LocalBinder} 为客户端提供 {@code getService()} 方法,以检索 {@code LocalService} +的当前实例。这样,客户端便可调用服务中的公共方法。 +例如,客户端可调用服务中的 {@code getRandomNumber()}。

+ +

点击按钮时,以下这个 Activity 会绑定到 {@code LocalService} 并调用 +{@code getRandomNumber()}:

+ +
+public class BindingActivity extends Activity {
+    LocalService mService;
+    boolean mBound = false;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.main);
+    }
+
+    @Override
+    protected void onStart() {
+        super.onStart();
+        // Bind to LocalService
+        Intent intent = new Intent(this, LocalService.class);
+        bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
+    }
+
+    @Override
+    protected void onStop() {
+        super.onStop();
+        // Unbind from the service
+        if (mBound) {
+            unbindService(mConnection);
+            mBound = false;
+        }
+    }
+
+    /** Called when a button is clicked (the button in the layout file attaches to
+      * this method with the android:onClick attribute) */
+    public void onButtonClick(View v) {
+        if (mBound) {
+            // Call a method from the LocalService.
+            // However, if this call were something that might hang, then this request should
+            // occur in a separate thread to avoid slowing down the activity performance.
+            int num = mService.getRandomNumber();
+            Toast.makeText(this, "number: " + num, Toast.LENGTH_SHORT).show();
+        }
+    }
+
+    /** Defines callbacks for service binding, passed to bindService() */
+    private ServiceConnection mConnection = new ServiceConnection() {
+
+        @Override
+        public void onServiceConnected(ComponentName className,
+                IBinder service) {
+            // We've bound to LocalService, cast the IBinder and get LocalService instance
+            LocalBinder binder = (LocalBinder) service;
+            mService = binder.getService();
+            mBound = true;
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName arg0) {
+            mBound = false;
+        }
+    };
+}
+
+ +

上例说明了客户端如何使用 {@link android.content.ServiceConnection} 的实现和 {@link +android.content.ServiceConnection#onServiceConnected onServiceConnected()} +回调绑定到服务。下文更详细介绍了绑定到服务的过程。 +

+ +

注:上例并未显式取消与服务的绑定,但所有客户端都应在适当的时间(例如当 Activity 暂停时)取消绑定。 +

+ +

如需查看更多示例代码,请参阅 ApiDemos 中的 {@code +LocalService.java} 类和 {@code +LocalServiceActivities.java} 类。

+ + + + + +

使用 Messenger

+ + + +

如需让服务与远程进程通信,则可使用 {@link android.os.Messenger} +为您的服务提供接口。利用此方法,您无需使用 +AIDL 便可执行进程间通信 (IPC)。

+ +

以下是 {@link android.os.Messenger} 的使用方法摘要:

+ +
    +
  • 服务实现一个 +{@link android.os.Handler},由其接收来自客户端的每个调用的回调
  • +
  • {@link android.os.Handler} 用于创建 {@link android.os.Messenger} +对象(对 {@link android.os.Handler} 的引用)
  • +
  • {@link android.os.Messenger} 创建一个 {@link android.os.IBinder},服务通过 {@link android.app.Service#onBind onBind()} +使其返回客户端
  • +
  • 客户端使用 {@link android.os.IBinder} +将 {@link android.os.Messenger}(引用服务的 {@link android.os.Handler})实例化,然后使用后者将 {@link android.os.Message} +对象发送给服务
  • +
  • 服务在其 {@link +android.os.Handler} 中(具体地讲,是在 {@link android.os.Handler#handleMessage +handleMessage()} 方法中)接收每个 {@link android.os.Message}
  • +
+ + +

这样,客户端并没有调用服务的“方法”。而客户端传递的“消息”({@link android.os.Message} +对象)是服务在其 {@link android.os.Handler} +中接收的。

+ +

以下是一个使用 {@link android.os.Messenger} 接口的简单服务示例:

+ +
+public class MessengerService extends Service {
+    /** Command to the service to display a message */
+    static final int MSG_SAY_HELLO = 1;
+
+    /**
+     * Handler of incoming messages from clients.
+     */
+    class IncomingHandler extends Handler {
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_SAY_HELLO:
+                    Toast.makeText(getApplicationContext(), "hello!", Toast.LENGTH_SHORT).show();
+                    break;
+                default:
+                    super.handleMessage(msg);
+            }
+        }
+    }
+
+    /**
+     * Target we publish for clients to send messages to IncomingHandler.
+     */
+    final Messenger mMessenger = new Messenger(new IncomingHandler());
+
+    /**
+     * When binding to the service, we return an interface to our messenger
+     * for sending messages to the service.
+     */
+    @Override
+    public IBinder onBind(Intent intent) {
+        Toast.makeText(getApplicationContext(), "binding", Toast.LENGTH_SHORT).show();
+        return mMessenger.getBinder();
+    }
+}
+
+ +

请注意,服务就是在 {@link android.os.Handler} +的 {@link android.os.Handler#handleMessage handleMessage()} 方法中接收传入的 +{@link android.os.Message},并根据 {@link android.os.Message#what} 成员决定下一步操作。

+ +

客户端只需根据服务返回的 {@link +android.os.IBinder} 创建一个 {@link android.os.Messenger},然后利用 {@link +android.os.Messenger#send send()} 发送一条消息。例如,以下就是一个绑定到服务并向服务传递 +{@code MSG_SAY_HELLO} 消息的简单 Activity:

+ +
+public class ActivityMessenger extends Activity {
+    /** Messenger for communicating with the service. */
+    Messenger mService = null;
+
+    /** Flag indicating whether we have called bind on the service. */
+    boolean mBound;
+
+    /**
+     * Class for interacting with the main interface of the service.
+     */
+    private ServiceConnection mConnection = new ServiceConnection() {
+        public void onServiceConnected(ComponentName className, IBinder service) {
+            // This is called when the connection with the service has been
+            // established, giving us the object we can use to
+            // interact with the service.  We are communicating with the
+            // service using a Messenger, so here we get a client-side
+            // representation of that from the raw IBinder object.
+            mService = new Messenger(service);
+            mBound = true;
+        }
+
+        public void onServiceDisconnected(ComponentName className) {
+            // This is called when the connection with the service has been
+            // unexpectedly disconnected -- that is, its process crashed.
+            mService = null;
+            mBound = false;
+        }
+    };
+
+    public void sayHello(View v) {
+        if (!mBound) return;
+        // Create and send a message to the service, using a supported 'what' value
+        Message msg = Message.obtain(null, MessengerService.MSG_SAY_HELLO, 0, 0);
+        try {
+            mService.send(msg);
+        } catch (RemoteException e) {
+            e.printStackTrace();
+        }
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.main);
+    }
+
+    @Override
+    protected void onStart() {
+        super.onStart();
+        // Bind to the service
+        bindService(new Intent(this, MessengerService.class), mConnection,
+            Context.BIND_AUTO_CREATE);
+    }
+
+    @Override
+    protected void onStop() {
+        super.onStop();
+        // Unbind from the service
+        if (mBound) {
+            unbindService(mConnection);
+            mBound = false;
+        }
+    }
+}
+
+ +

请注意,此示例并未说明服务如何对客户端作出响应。如果您想让服务作出响应,则还需要在客户端中创建一个 +{@link android.os.Messenger}。然后,当客户端收到 {@link android.content.ServiceConnection#onServiceConnected +onServiceConnected()} 回调时,会向服务发送一条 +{@link android.os.Message},并在其 +{@link android.os.Messenger#send send()} 方法的 {@link android.os.Message#replyTo} +参数中包含客户端的 {@link android.os.Messenger}。

+ +

如需查看如何提供双向消息传递的示例,请参阅 {@code +MessengerService.java}(服务)和 {@code +MessengerServiceActivities.java}(客户端)示例。

+ + + + + +

绑定到服务

+ +

应用组件(客户端)可通过调用 {@link android.content.Context#bindService bindService()} +绑定到服务。Android +系统随后调用服务的 {@link android.app.Service#onBind +onBind()} 方法,该方法返回用于与服务交互的 {@link android.os.IBinder}。

+ +

绑定是异步的。{@link android.content.Context#bindService +bindService()} 会立即返回,“绝对不会”使 {@link android.os.IBinder} +返回客户端。要接收 {@link android.os.IBinder},客户端必须创建一个 {@link +android.content.ServiceConnection} 实例,并将其传递给 {@link android.content.Context#bindService +bindService()}。{@link android.content.ServiceConnection} 包括一个回调方法,系统通过调用它来传递 +{@link android.os.IBinder}。

+ +

注:只有 Activity、服务和内容提供程序可以绑定到服务—您无法从广播接收器绑定到服务。 +

+ +

因此,要想从您的客户端绑定到服务,您必须:

+
    +
  1. 实现 {@link android.content.ServiceConnection}。 +

    您的实现必须重写两个回调方法:

    +
    +
    {@link android.content.ServiceConnection#onServiceConnected onServiceConnected()}
    +
    系统会调用该方法以传递服务的 {@link android.app.Service#onBind onBind()} +方法返回的 {@link android.os.IBinder}。
    +
    {@link android.content.ServiceConnection#onServiceDisconnected +onServiceDisconnected()}
    +
    Android +系统会在与服务的连接意外中断时(例如当服务崩溃或被终止时)调用该方法。当客户端取消绑定时,系统“绝对不会”调用该方法。 +
    +
    +
  2. +
  3. 调用 {@link +android.content.Context#bindService bindService()} 以传递 {@link +android.content.ServiceConnection} 实现。
  4. +
  5. 当系统调用您的 {@link android.content.ServiceConnection#onServiceConnected +onServiceConnected()} +回调方法时,您可以使用接口定义的方法开始调用服务。
  6. +
  7. 要断开与服务的连接,请调用 {@link +android.content.Context#unbindService unbindService()}。 +

    当您的客户端被销毁时,它将取消与服务的绑定,但您应该始终在完成与服务的交互时或您的 Activity 暂停时取消绑定,以便服务能够在未被占用时关闭。 + +(下文更详细地阐述了绑定和取消绑定的适当时机。) +

    +
  8. +
+ +

例如,以下代码段通过扩展 +Binder 类将客户端与上面创建的服务相连,因此它只需将返回的 {@link android.os.IBinder} +转换为 {@code LocalService} 类并请求 {@code +LocalService} 实例:

+ +
+LocalService mService;
+private ServiceConnection mConnection = new ServiceConnection() {
+    // Called when the connection with the service is established
+    public void onServiceConnected(ComponentName className, IBinder service) {
+        // Because we have bound to an explicit
+        // service that is running in our own process, we can
+        // cast its IBinder to a concrete class and directly access it.
+        LocalBinder binder = (LocalBinder) service;
+        mService = binder.getService();
+        mBound = true;
+    }
+
+    // Called when the connection with the service disconnects unexpectedly
+    public void onServiceDisconnected(ComponentName className) {
+        Log.e(TAG, "onServiceDisconnected");
+        mBound = false;
+    }
+};
+
+ +

客户端可通过将此 {@link android.content.ServiceConnection} 传递至 {@link android.content.Context#bindService bindService()} +绑定到服务。例如:

+ +
+Intent intent = new Intent(this, LocalService.class);
+bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
+
+ +
    +
  • {@link android.content.Context#bindService bindService()} +的第一个参数是一个 +{@link android.content.Intent},用于显式命名要绑定的服务(但 Intent 可能是隐式的)
  • +
  • 第二个参数是 {@link android.content.ServiceConnection} 对象
  • +
  • 第三个参数是一个指示绑定选项的标志。它通常应该是 {@link +android.content.Context#BIND_AUTO_CREATE},以便创建尚未激活的服务。 +其他可能的值为 {@link android.content.Context#BIND_DEBUG_UNBIND} +和 {@link android.content.Context#BIND_NOT_FOREGROUND},或 {@code 0}(表示无)。
  • +
+ + +

附加说明

+ +

以下是一些有关绑定到服务的重要说明:

+
    +
  • 您应该始终捕获 +{@link android.os.DeadObjectException} 异常,它们是在连接中断时引发的。这是远程方法引发的唯一异常
  • +
  • 对象是跨进程计数的引用
  • +
  • 您通常应该在客户端生命周期的匹配引入 (bring-up) 和退出 (tear-down) 时刻期间配对绑定和取消绑定。 +例如: +
      +
    • 如果您只需要在 Activity 可见时与服务交互,则应在 {@link android.app.Activity#onStart onStart()} +期间绑定,在 {@link +android.app.Activity#onStop onStop()} 期间取消绑定。
    • +
    • 如果您希望 Activity 在后台停止运行状态下仍可接收响应,则可在 +{@link android.app.Activity#onCreate onCreate()} 期间绑定,在 {@link android.app.Activity#onDestroy onDestroy()} +期间取消绑定。请注意,这意味着您的 Activity 在其整个运行过程中(甚至包括后台运行期间)都需要使用服务,因此如果服务位于其他进程内,那么当您提高该进程的权重时,系统终止该进程的可能性会增加 + + +
    • +
    +

    注:通常情况下,切勿在 Activity 的 {@link android.app.Activity#onResume onResume()} +和 {@link +android.app.Activity#onPause onPause()} +期间绑定和取消绑定,因为每一次生命周期转换都会发生这些回调,您应该使发生在这些转换期间的处理保持在最低水平。此外,如果您的应用内的多个 Activity 绑定到同一服务,并且其中两个 Activity 之间发生了转换,则如果当前 Activity 在下一次绑定(恢复期间)之前取消绑定(暂停期间),系统可能会销毁服务并重建服务。 + + +(Activity文档中介绍了这种有关 Activity 如何协调其生命周期的 Activity 转换。) + +

    +
+ +

如需查看更多显示如何绑定到服务的示例代码,请参阅 ApiDemos 中的 {@code +RemoteService.java} 类。

+ + + + + +

管理绑定服务的生命周期

+ +

当服务与所有客户端之间的绑定全部取消时,Android +系统便会销毁服务(除非还使用 {@link android.app.Service#onStartCommand onStartCommand()} 启动了该服务)。因此,如果您的服务是纯粹的绑定服务,则无需对其生命周期进行管理—Android +系统会根据它是否绑定到任何客户端代您管理。 +

+ +

不过,如果您选择实现 {@link android.app.Service#onStartCommand +onStartCommand()} +回调方法,则您必须显式停止服务,因为系统现在已将服务视为已启动。在此情况下,服务将一直运行到其通过 {@link android.app.Service#stopSelf()} +自行停止,或其他组件调用 {@link +android.content.Context#stopService stopService()} +为止,无论其是否绑定到任何客户端。

+ +

此外,如果您的服务已启动并接受绑定,则当系统调用您的 {@link android.app.Service#onUnbind onUnbind()} 方法时,如果您想在客户端下一次绑定到服务时接收 +{@link android.app.Service#onRebind +onRebind()} 调用(而不是接收 {@link +android.app.Service#onBind onBind()} 调用),则可选择返回 +{@code true}。{@link android.app.Service#onRebind +onRebind()} 返回空值,但客户端仍在其 {@link android.content.ServiceConnection#onServiceConnected onServiceConnected()} 回调中接收 +{@link android.os.IBinder}。下文图 1 +说明了这种生命周期的逻辑。

+ + + +

图 1. 允许绑定的已启动服务的生命周期。 +

+ + +

如需了解有关已启动服务生命周期的详细信息,请参阅服务文档。

+ + + + diff --git a/docs/html-intl/intl/zh-cn/guide/components/fragments.jd b/docs/html-intl/intl/zh-cn/guide/components/fragments.jd new file mode 100644 index 0000000000000000000000000000000000000000..a4c2cbb824991f8bf5c566ec8914e0fc0509fbea --- /dev/null +++ b/docs/html-intl/intl/zh-cn/guide/components/fragments.jd @@ -0,0 +1,812 @@ +page.title=片段 +parent.title=Activity +parent.link=activities.html +@jd:body + +
+
+

本文内容

+
    +
  1. 设计原理
  2. +
  3. 创建片段 +
      +
    1. 添加用户界面
    2. +
    3. 向 Activity 添加片段
    4. +
    +
  4. +
  5. 管理片段
  6. +
  7. 执行片段事务
  8. +
  9. 与 Activity 通信 +
      +
    1. 创建对 Activity 的事件回调
    2. +
    3. 向操作栏添加项目
    4. +
    +
  10. +
  11. 处理片段生命周期 +
      +
    1. 与 Activity 生命周期协调一致
    2. +
    +
  12. +
  13. 示例
  14. +
+ +

关键类

+
    +
  1. {@link android.app.Fragment}
  2. +
  3. {@link android.app.FragmentManager}
  4. +
  5. {@link android.app.FragmentTransaction}
  6. +
+ +

另请参阅

+
    +
  1. 利用片段构建动态 UI
  2. +
  3. 支持平板电脑和手机 +
  4. +
+
+
+ +

{@link android.app.Fragment} 表示 +{@link android.app.Activity} 中的行为或用户界面部分。您可以将多个片段组合在一个 Activity 中来构建多窗格 +UI,以及在多个 Activity 中重复使用某个片段。您可以将片段视为 Activity 的模块化组成部分,它具有自己的生命周期,能接收自己的输入事件,并且您可以在 Activity 运行时添加或删除片段(有点像您可以在不同 Activity 中重复使用的“子 Activity”)。 + + +

+ +

片段必须始终嵌入在 Activity 中,其生命周期直接受宿主 Activity 生命周期的影响。 +例如,当 Activity 暂停时,其中的所有片段也会暂停;当 Activity 被销毁时,所有片段也会被销毁。 +不过,当 Activity 正在运行(处于已恢复生命周期状态)时,您可以独立操纵每个片段,如添加或移除它们。 + +当您执行此类片段事务时,您也可以将其添加到由 Activity 管理的返回栈—Activity 中的每个返回栈条目都是一条已发生片段事务的记录。 + + +返回栈让用户可以通过按“返回”按钮撤消片段事务(后退)。 +

+ +

当您将片段作为 Activity 布局的一部分添加时,它存在于 Activity 视图层次结构的某个 {@link +android.view.ViewGroup} +内部,并且片段会定义其自己的视图布局。您可以通过在 Activity 的布局文件中声明片段,将其作为 +{@code <fragment>} +元素插入您的 Activity 布局中,或者通过将其添加到某个现有 +{@link android.view.ViewGroup},利用应用代码进行插入。不过,片段并非必须成为 Activity 布局的一部分;您还可以将没有自己 +UI +的片段用作 Activity 的不可见工作线程。

+ +

本文描述如何在开发您的应用时使用片段,包括将片段添加到 Activity 返回栈时如何保持其状态、如何与 Activity 及 Activity 中的其他片段共享事件、如何为 Activity 的操作栏发挥作用等等。 + + +

+ + +

设计原理

+ +

Android 在 Android 3.0(API 11 级)中引入了片段,主要是为了给大屏幕(如平板电脑)上更加动态和灵活的 UI +设计提供支持。由于平板电脑的屏幕比手机屏幕大得多,因此可用于组合和交换 +UI +组件的空间更大。利用片段实现此类设计时,您无需管理对视图层次结构的复杂更改。 +通过将 Activity 布局分成片段,您可以在运行时修改 Activity 的外观,并在由 Activity 管理的返回栈中保留这些更改。 + +

+ +

例如,新闻应用可以使用一个片段在左侧显示文章列表,使用另一个片段在右侧显示文章—两个片段并排显示在一个 Activity 中,每个片段都具有自己的一套生命周期回调方法,并各自处理自己的用户输入事件。 + + +因此,用户不需要使用一个 Activity 来选择文章,然后使用另一个 Activity 来阅读文章,而是可以在同一个 Activity 内选择文章并进行阅读,如图 +1 +中的平板电脑布局所示。

+ +

您应该将每个片段都设计为可重复使用的模块化 Activity 组件。也就是说,由于每个片段都会通过各自的生命周期回调来定义其自己的布局和行为,您可以将一个片段加入多个 Activity,因此,您应该采用可复用式设计,避免直接从某个片段直接操纵另一个片段。 + + +这特别重要,因为模块化片段让您可以通过更改片段的组合方式来适应不同的屏幕尺寸。 +在设计可同时支持平板电脑和手机的应用时,您可以在不同的布局配置中重复使用您的片段,以根据可用的屏幕空间优化用户体验。 + +例如,在手机上,如果不能在同一 Activity 内储存多个片段,可能必须利用单独片段来实现单窗格 +UI。 +

+ + +

图 1. 有关由片段定义的两个 +UI +模块如何适应不同设计的示例:通过组合成一个 Activity 来适应平板电脑设计,通过单独片段来适应手机设计。

+ +

例如—仍然以新闻应用为例—在平板电脑尺寸的设备上运行时,该应用可以在Activity +A 中嵌入两个片段。不过,在手机尺寸的屏幕上,没有足以储存两个片段的空间,因此Activity +A +只包括用于显示文章列表的片段,当用户选择文章时,它会启动Activity +B,其中包括用于阅读文章的第二个片段。因此,应用可通过重复使用不同组合的片段来同时支持平板电脑和手机,如图 +1 +所示。

+ +

如需了解有关通过利用不同片段组合来适应不同屏幕配置这种方法设计应用的详细信息,请参阅支持平板电脑和手机指南。 +

+ + + +

创建片段

+ +
+ +

图 2. 片段的生命周期(其 Activity 运行时)。 +

+
+ +

要想创建片段,您必须创建 {@link android.app.Fragment} +的子类(或已有其子类)。{@link android.app.Fragment} 类的代码与 +{@link android.app.Activity} 非常相似。它包含与 Activity 类似的回调方法,如 +{@link android.app.Fragment#onCreate onCreate()}、{@link android.app.Fragment#onStart onStart()}、{@link android.app.Fragment#onPause onPause()} +和 {@link android.app.Fragment#onStop onStop()}。实际上,如果您要将现有 +Android +应用转换为使用片段,可能只需将代码从 Activity 的回调方法移入片段相应的回调方法中。 +

+ +

通常,您至少应实现以下生命周期方法:

+ +
+
{@link android.app.Fragment#onCreate onCreate()}
+
系统会在创建片段时调用此方法。您应该在实现内初始化您想在片段暂停或停止后恢复时保留的必需片段组件。 + +
+
{@link android.app.Fragment#onCreateView onCreateView()}
+
系统会在片段首次绘制其用户界面时调用此方法。 +要想为您的片段绘制 +UI,您从此方法中返回的 {@link android.view.View} 必须是片段布局的根视图。如果片段未提供 +UI,您可以返回 null。
+
{@link android.app.Activity#onPause onPause()}
+
系统将此方法作为用户离开片段的第一个信号(但并不总是意味着此片段会被销毁)进行调用。 +您通常应该在此方法内确认在当前用户会话结束后仍然有效的任何更改(因为用户可能不会返回)。 + +
+
+ +

大多数应用都应该至少为每个片段实现这三个方法,但您还应该使用几种其他回调方法来处理片段生命周期的各个阶段。 + +处理片段生命周期部分对所有生命周期回调方法做了更详尽的阐述。 +

+ + +

您可能还想扩展几个子类,而不是 {@link +android.app.Fragment} 基类:

+ +
+
{@link android.app.DialogFragment}
+
显示浮动对话框。使用此类创建对话框可有效地替代使用 +{@link android.app.Activity} +类中的对话框帮助程序方法,因为您可以将片段对话框纳入由 Activity 管理的片段返回栈,从而使用户能够返回清除的片段。 +
+ +
{@link android.app.ListFragment}
+
显示由适配器(如 {@link +android.widget.SimpleCursorAdapter})管理的一系列项目,类似于 {@link android.app.ListActivity}。它提供了几种管理列表视图的方法,如用于处理点击事件的 +{@link +android.app.ListFragment#onListItemClick(ListView,View,int,long) onListItemClick()} +回调。
+ +
{@link android.preference.PreferenceFragment}
+
以列表形式显示 {@link android.preference.Preference} 对象的层次结构,类似于 +{@link android.preference.PreferenceActivity}。这在为您的应用创建“设置” Activity 时很有用处。 +
+
+ + +

添加用户界面

+ +

片段通常用作 Activity 用户界面的一部分,将其自己的布局融入 Activity。 +

+ +

要想为片段提供布局,您必须实现 {@link +android.app.Fragment#onCreateView onCreateView()} 回调方法,Android +系统会在片段需要绘制其布局时调用该方法。您对此方法的实现返回的 +{@link android.view.View} 必须是片段布局的根视图。

+ +

注:如果您的片段是 {@link +android.app.ListFragment} 的子类,则默认实现会从 +{@link android.app.Fragment#onCreateView onCreateView()} 返回一个 {@link android.widget.ListView},因此您无需实现它。

+ +

要想从 {@link +android.app.Fragment#onCreateView onCreateView()} 返回布局,您可以通过 XML 中定义的布局资源来扩展布局。为帮助您执行此操作,{@link android.app.Fragment#onCreateView onCreateView()} +提供了一个 +{@link android.view.LayoutInflater} 对象。

+ +

例如,以下这个 {@link android.app.Fragment} 子类从 {@code example_fragment.xml} +文件加载布局:

+ +
+public static class ExampleFragment extends Fragment {
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+                             Bundle savedInstanceState) {
+        // Inflate the layout for this fragment
+        return inflater.inflate(R.layout.example_fragment, container, false);
+    }
+}
+
+ + + +

传递至 {@link android.app.Fragment#onCreateView +onCreateView()} 的 {@code container} +参数是您的片段布局将插入到的父 +{@link android.view.ViewGroup}(来自 Activity 的布局)。{@code savedInstanceState} +参数是在恢复片段时,提供上一片段实例相关数据的 +{@link android.os.Bundle}(处理片段生命周期部分对恢复状态做了详细阐述)。 +

+ +

{@link android.view.LayoutInflater#inflate(int,ViewGroup,boolean) inflate()} +方法带有三个参数:

+
    +
  • 您想要扩展的布局的资源 ID;
  • +
  • 将作为扩展布局父项的 {@link android.view.ViewGroup}。传递 {@code +container} +对系统向扩展布局的根视图(由其所属的父视图指定)应用布局参数具有重要意义;
  • +
  • 指示是否应该在扩展期间将扩展布局附加至 {@link +android.view.ViewGroup}(第二个参数)的布尔值。(在本例中,其值为 +false,因为系统已经将扩展布局插入 {@code +container}—传递 true 值会在最终布局中创建一个多余的视图组。)
  • +
+ +

现在,您已经了解了如何创建提供布局的片段。接下来,您需要将该片段添加到您的 Activity 中。 +

+ + + +

向 Activity 添加片段

+ +

通常,片段向宿主 Activity 贡献一部分 +UI,作为 Activity 总体视图层次结构的一部分嵌入到 Activity 中。可以通过两种方式向 Activity 布局添加片段: +

+ +
    +
  • 在 Activity 的布局文件内声明片段 +

    在本例中,您可以将片段当作视图来为其指定布局属性。 +例如,以下是一个具有两个片段的 Activity 的布局文件: +

    +
    +<?xml version="1.0" encoding="utf-8"?>
    +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    +    android:orientation="horizontal"
    +    android:layout_width="match_parent"
    +    android:layout_height="match_parent">
    +    <fragment android:name="com.example.news.ArticleListFragment"
    +            android:id="@+id/list"
    +            android:layout_weight="1"
    +            android:layout_width="0dp"
    +            android:layout_height="match_parent" />
    +    <fragment android:name="com.example.news.ArticleReaderFragment"
    +            android:id="@+id/viewer"
    +            android:layout_weight="2"
    +            android:layout_width="0dp"
    +            android:layout_height="match_parent" />
    +</LinearLayout>
    +
    +

    {@code <fragment>} 中的 {@code android:name} 属性指定要在布局中实例化的 {@link +android.app.Fragment} 类。

    + +

    当系统创建此 Activity 布局时,会实例化在布局中指定的每个片段,并为每个片段调用 +{@link android.app.Fragment#onCreateView onCreateView()} +方法,以检索每个片段的布局。系统会直接插入片段返回的 {@link android.view.View} 来替代 +{@code <fragment>} 元素。

    + +
    +

    注:每个片段都需要一个唯一的标识符,重启 Activity 时,系统可以使用该标识符来恢复片段(您也可以使用该标识符来捕获片段以执行某些事务,如将其删除)。 + +可以通过三种方式为片段提供 +ID:

    +
      +
    • 为 {@code android:id} 属性提供唯一 ID
    • +
    • 为 {@code android:tag} 属性提供唯一字符串
    • +
    • 如果您未给以上两个属性提供值,系统会使用容器视图的 ID +
    • +
    +
    +
  • + +
  • 或者通过编程方式将片段添加到某个现有 {@link android.view.ViewGroup} +

    您可以在 Activity 运行期间随时将片段添加到 Activity 布局中。您只需指定要将片段放入哪个 +{@link +android.view.ViewGroup}。

    +

    要想在您的 Activity 中执行片段事务(如添加、删除或替换片段),您必须使用 +{@link android.app.FragmentTransaction} 中的 API。您可以像下面这样从 +{@link android.app.Activity} 获取一个 {@link android.app.FragmentTransaction} 实例:

    + +
    +FragmentManager fragmentManager = {@link android.app.Activity#getFragmentManager()}
    +FragmentTransaction fragmentTransaction = fragmentManager.{@link android.app.FragmentManager#beginTransaction()};
    +
    + +

    然后,您可以使用 {@link +android.app.FragmentTransaction#add(int,Fragment) add()} +方法添加一个片段,指定要添加的片段以及将其插入哪个视图。例如:

    + +
    +ExampleFragment fragment = new ExampleFragment();
    +fragmentTransaction.add(R.id.fragment_container, fragment);
    +fragmentTransaction.commit();
    +
    + +

    传递到 {@link android.app.FragmentTransaction#add(int,Fragment) add()} +的第一个参数是 {@link android.view.ViewGroup},即应该放置片段的位置,由资源 +ID 指定,第二个参数是要添加的片段。

    +

    一旦您通过 +{@link android.app.FragmentTransaction} 做出了更改,就必须调用 +{@link android.app.FragmentTransaction#commit} 以使更改生效。

    +
  • +
+ + +

添加没有 UI 的片段

+ +

上例展示了如何向您的 Activity 添加片段以提供 +UI。不过,您还可以使用片段为 Activity 提供后台行为,而不显示额外 +UI。

+ +

要想添加没有 UI 的片段,请使用 {@link +android.app.FragmentTransaction#add(Fragment,String)} 从 Activity 添加片段(为片段提供一个唯一的字符串“标记”,而不是视图 +ID)。这会添加片段,但由于它并不与 Activity 布局中的视图关联,因此不会收到对 +{@link +android.app.Fragment#onCreateView onCreateView()} 的调用。因此,您不需要实现该方法。

+ +

并非只能为非 UI 片段提供字符串标记—您也可以为具有 UI +的片段提供字符串标记—但如果片段没有 +UI,则字符串标记将是标识它的唯一方式。如果您想稍后从 Activity 中获取片段,则需要使用 +{@link android.app.FragmentManager#findFragmentByTag +findFragmentByTag()}。

+ +

如需查看将没有 UI 的片段用作后台工作线程的示例 Activity,请参阅 {@code +FragmentRetainInstance.java} 示例,该示例包括在 SDK 示例(通过 +Android SDK 管理器提供)中,以 +<sdk_root>/APIDemos/app/src/main/java/com/example/android/apis/app/FragmentRetainInstance.java 形式位于您的系统中。

+ + + +

管理片段

+ +

要想管理您的 Activity 中的片段,您需要使用 {@link android.app.FragmentManager}。要想获取它,请从您的 Activity 调用 +{@link android.app.Activity#getFragmentManager()}。

+ +

您可以使用 {@link android.app.FragmentManager} 执行的操作包括:

+ +
    +
  • 通过 {@link +android.app.FragmentManager#findFragmentById findFragmentById()}(对于在 Activity 布局中提供 UI +的片段)或 {@link android.app.FragmentManager#findFragmentByTag +findFragmentByTag()}(对于提供或不提供 UI 的片段)获取 Activity 中存在的片段
  • +
  • 通过 {@link +android.app.FragmentManager#popBackStack()}(模拟用户发出的 Back 命令)将片段从返回栈中弹出
  • +
  • 通过 {@link +android.app.FragmentManager#addOnBackStackChangedListener addOnBackStackChangedListener()} 注册一个侦听返回栈变化的侦听器
  • +
+ +

如需了解有关这些方法以及其他方法的详细信息,请参阅 {@link +android.app.FragmentManager} 类文档。

+ +

如上文所示,您也可以使用 {@link android.app.FragmentManager} +打开一个 +{@link android.app.FragmentTransaction},通过它来执行某些事务,如添加和删除片段。

+ + +

执行片段事务

+ +

在 Activity 中使用片段的一大优点是,可以根据用户行为通过它们执行添加、删除、替换以及其他操作。 +您提交给 Activity 的每组更改都称为事务,您可以使用 +{@link +android.app.FragmentTransaction} 中的 API 来执行一项事务。您也可以将每个事务保存到由 Activity 管理的返回栈内,从而让用户能够回退片段更改(类似于回退 Activity)。 + +

+ +

您可以像下面这样从 {@link +android.app.FragmentManager} 获取一个 {@link android.app.FragmentTransaction} 实例:

+ +
+FragmentManager fragmentManager = {@link android.app.Activity#getFragmentManager()};
+FragmentTransaction fragmentTransaction = fragmentManager.{@link android.app.FragmentManager#beginTransaction()};
+
+ +

每个事务都是您想要同时执行的一组更改。您可以使用 +{@link +android.app.FragmentTransaction#add add()}、{@link android.app.FragmentTransaction#remove remove()} 和 {@link android.app.FragmentTransaction#replace replace()} +等方法为给定事务设置您想要执行的所有更改。然后,要想将事务应用到 Activity,您必须调用 +{@link android.app.FragmentTransaction#commit()}。

+ + +

不过,在您调用 {@link +android.app.FragmentTransaction#commit()} 之前,您可能想调用 {@link +android.app.FragmentTransaction#addToBackStack addToBackStack()},以将事务添加到片段事务返回栈。 +该返回栈由 Activity 管理,允许用户通过按“返回” + 按钮返回上一片段状态。

+ +

例如,以下示例说明了如何将一个片段替换成另一个片段,以及如何在返回栈中保留先前状态: +

+ +
+// Create new fragment and transaction
+Fragment newFragment = new ExampleFragment();
+FragmentTransaction transaction = getFragmentManager().beginTransaction();
+
+// Replace whatever is in the fragment_container view with this fragment,
+// and add the transaction to the back stack
+transaction.replace(R.id.fragment_container, newFragment);
+transaction.addToBackStack(null);
+
+// Commit the transaction
+transaction.commit();
+
+ +

在上例中,{@code newFragment} +会替换目前在 {@code R.id.fragment_container} ID 所标识的布局容器中的任何片段(如有)。通过调用 {@link +android.app.FragmentTransaction#addToBackStack addToBackStack()} +可将替换事务保存到返回栈,以便用户能够通过按“返回” +按钮撤消事务并回退到上一片段。

+ +

如果您向事务添加了多个更改(如又一个 {@link +android.app.FragmentTransaction#add add()} 或 {@link android.app.FragmentTransaction#remove +remove()}),并且调用了 {@link +android.app.FragmentTransaction#addToBackStack addToBackStack()},则在调用 +{@link android.app.FragmentTransaction#commit commit()} 前应用的所有更改都将作为单一事务添加到返回栈,并且“返回” +按钮会将它们一并撤消。

+ +

向 {@link android.app.FragmentTransaction} +添加更改的顺序无关紧要,不过:

+
    +
  • 您必须最后调用 {@link android.app.FragmentTransaction#commit()}
  • +
  • 如果您要向同一容器添加多个片段,则您添加片段的顺序将决定它们在视图层次结构中的出现顺序 +
  • +
+ +

如果您没有在执行删除片段的事务时调用 {@link android.app.FragmentTransaction#addToBackStack(String) +addToBackStack()},则事务提交时该片段会被销毁,用户将无法回退到该片段。 +不过,如果您在删除片段时调用了 +{@link android.app.FragmentTransaction#addToBackStack(String) addToBackStack()},则系统会停止该片段,并在用户回退时将其恢复。 + +

+ +

提示:对于每个片段事务,您都可以通过在提交前调用 +{@link android.app.FragmentTransaction#setTransition setTransition()} +来应用过渡动画。

+ +

调用 {@link android.app.FragmentTransaction#commit()} +不会立即执行事务,而是在 Activity 的 UI +线程(“主”线程)可以执行该操作时再安排其在线程上运行。不过,如有必要,您也可以从 UI 线程调用 {@link +android.app.FragmentManager#executePendingTransactions()} 以立即执行 +{@link android.app.FragmentTransaction#commit()} 提交的事务。通常不必这样做,除非其他线程中的作业依赖该事务。 +

+ +

注意:您只能在 Activity保存其状态(用户离开 Activity)之前使用 {@link +android.app.FragmentTransaction#commit commit()} +提交事务。如果您试图在该时间点后提交,则会引发异常。 +这是因为如需恢复 Activity,则提交后的状态可能会丢失。 +对于丢失提交无关紧要的情况,请使用 {@link +android.app.FragmentTransaction#commitAllowingStateLoss()}。

+ + + + +

与 Activity 通信

+ +

尽管 {@link android.app.Fragment} 是作为独立于 +{@link android.app.Activity} +的对象实现,并且可在多个 Activity 内使用,但片段的给定实例会直接绑定到包含它的 Activity。

+ +

具体地说,片段可以通过 {@link +android.app.Fragment#getActivity()} +访问 {@link android.app.Activity} 实例,并轻松地执行在 Activity 布局中查找视图等任务。

+ +
+View listView = {@link android.app.Fragment#getActivity()}.{@link android.app.Activity#findViewById findViewById}(R.id.list);
+
+ +

同样地,您的 Activity 也可以使用 +{@link +android.app.FragmentManager#findFragmentById findFragmentById()} 或 {@link +android.app.FragmentManager#findFragmentByTag findFragmentByTag()},通过从 {@link android.app.FragmentManager} 获取对 {@link android.app.Fragment} 的引用来调用片段中的方法。例如:

+ +
+ExampleFragment fragment = (ExampleFragment) getFragmentManager().findFragmentById(R.id.example_fragment);
+
+ + +

创建对 Activity 的事件回调

+ +

在某些情况下,您可能需要通过片段与 Activity 共享事件。执行此操作的一个好方法是,在片段内定义一个回调接口,并要求宿主 Activity 实现它。 + +当 Activity 通过该接口收到回调时,可以根据需要与布局中的其他片段共享这些信息。 +

+ +

例如,如果一个新闻应用的 Activity 有两个片段 —一个用于显示文章列表(片段 A),另一个用于显示文章(片段 B)—,那么片段 A必须在列表项被选定后告知 Activity,以便它告知片段 B 显示该文章。 + +在本例中,{@code OnArticleSelectedListener} +接口在片段 A 内声明:

+ +
+public static class FragmentA extends ListFragment {
+    ...
+    // Container Activity must implement this interface
+    public interface OnArticleSelectedListener {
+        public void onArticleSelected(Uri articleUri);
+    }
+    ...
+}
+
+ +

然后,该片段的宿主 Activity 会实现 +{@code OnArticleSelectedListener} +接口并替代 {@code onArticleSelected()},将来自片段 A +的事件通知片段 B。为确保宿主 Activity 实现此界面,片段 A 的 {@link +android.app.Fragment#onAttach onAttach()} +回调方法(系统在向 Activity 添加片段时调用的方法)会通过转换传递到 +{@link android.app.Fragment#onAttach +onAttach()} 中的 {@link android.app.Activity} 来实例化 {@code OnArticleSelectedListener} 的实例:

+ +
+public static class FragmentA extends ListFragment {
+    OnArticleSelectedListener mListener;
+    ...
+    @Override
+    public void onAttach(Activity activity) {
+        super.onAttach(activity);
+        try {
+            mListener = (OnArticleSelectedListener) activity;
+        } catch (ClassCastException e) {
+            throw new ClassCastException(activity.toString() + " must implement OnArticleSelectedListener");
+        }
+    }
+    ...
+}
+
+ +

如果 Activity 未实现界面,则片段会引发 +{@link java.lang.ClassCastException}。实现时,{@code mListener} +成员会保留对 Activity 的 {@code OnArticleSelectedListener} 实现的引用,以便片段 A 可以通过调用 +{@code OnArticleSelectedListener} +界面定义的方法与 Activity 共享事件。例如,如果片段 A 是 +{@link android.app.ListFragment} +的一个扩展,则用户每次点击列表项时,系统都会调用片段中的 {@link android.app.ListFragment#onListItemClick +onListItemClick()},然后该方法会调用 {@code onArticleSelected()} +以与 Activity 共享事件:

+ +
+public static class FragmentA extends ListFragment {
+    OnArticleSelectedListener mListener;
+    ...
+    @Override
+    public void onListItemClick(ListView l, View v, int position, long id) {
+        // Append the clicked item's row ID with the content provider Uri
+        Uri noteUri = ContentUris.{@link android.content.ContentUris#withAppendedId withAppendedId}(ArticleColumns.CONTENT_URI, id);
+        // Send the event and Uri to the host activity
+        mListener.onArticleSelected(noteUri);
+    }
+    ...
+}
+
+ +

传递到 {@link +android.app.ListFragment#onListItemClick onListItemClick()} 的 {@code id} 参数是被点击项的行 +ID,即 Activity(或其他片段)用来从应用的 {@link +android.content.ContentProvider} 获取文章的 ID。

+ +

内容提供程序文档中提供了有关内容提供程序用法的更多详情。 +

+ + + +

向操作栏添加项目

+ +

您的片段可以通过实现 +{@link android.app.Fragment#onCreateOptionsMenu(Menu,MenuInflater) onCreateOptionsMenu()} 向 Activity 的选项菜单(并因此向操作栏)贡献菜单项。不过,为了使此方法能够收到调用,您必须在 +{@link +android.app.Fragment#onCreate(Bundle) onCreate()} 期间调用 {@link +android.app.Fragment#setHasOptionsMenu(boolean) setHasOptionsMenu()},以指示片段想要向选项菜单添加菜单项(否则,片段将不会收到对 +{@link android.app.Fragment#onCreateOptionsMenu onCreateOptionsMenu()} +的调用)。

+ +

您之后从片段添加到选项菜单的任何菜单项都将追加到现有菜单项之后。 +选定菜单项时,片段还会收到对 {@link +android.app.Fragment#onOptionsItemSelected(MenuItem) onOptionsItemSelected()} +的回调。

+ +

您还可以通过调用 {@link +android.app.Fragment#registerForContextMenu(View) registerForContextMenu()},在片段布局中注册一个视图来提供上下文菜单。用户打开上下文菜单时,片段会收到对 +{@link +android.app.Fragment#onCreateContextMenu(ContextMenu,View,ContextMenu.ContextMenuInfo) +onCreateContextMenu()} 的调用。当用户选择某个菜单项时,片段会收到对 {@link +android.app.Fragment#onContextItemSelected(MenuItem) onContextItemSelected()} 的调用。

+ +

注:尽管您的片段会收到与其添加的每个菜单项对应的菜单项选定回调,但当用户选择菜单项时,Activity 会首先收到相应的回调。 + +如果 Activity 对菜单项选定回调的实现不会处理选定的菜单项,则系统会将事件传递到片段的回调。 +这适用于选项菜单和上下文菜单。 +

+ +

如需了解有关菜单的详细信息,请参阅菜单操作栏开发者指南。

+ + + + +

处理片段生命周期

+ +
+ +

图 3. Activity 生命周期对片段生命周期的影响。 +

+
+ +

管理片段生命周期与管理 Activity 生命周期很相似。和 Activity 一样,片段也以三种状态存在: +

+ +
+
恢复
+
片段在运行中的 Activity 中可见。
+ +
暂停
+
另一个 Activity 位于前台并具有焦点,但此片段所在的 Activity 仍然可见(前台 Activity 部分透明,或未覆盖整个屏幕)。 + +
+ +
停止
+
片段不可见。宿主 Activity 已停止,或片段已从 Activity 中删除,但已添加到返回栈。 +停止片段仍然处于活动状态(系统会保留所有状态和成员信息)。 +不过,它对用户不再可见,如果 Activity 被终止,它也会被终止。 +
+
+ +

同样与 Activity 一样,假使 Activity 的进程被终止,而您需要在重建 Activity 时恢复片段状态,您也可以使用 {@link +android.os.Bundle} +保留片段的状态。您可以在片段的 {@link +android.app.Fragment#onSaveInstanceState onSaveInstanceState()} 回调期间保存状态,并可在 +{@link android.app.Fragment#onCreate onCreate()}、{@link +android.app.Fragment#onCreateView onCreateView()} 或 {@link +android.app.Fragment#onActivityCreated onActivityCreated()} 期间恢复状态。如需了解有关保存状态的详细信息,请参阅Activity文档。 + +

+ +

Activity 生命周期与片段生命周期之间的最显著差异在于它们在其各自返回栈中的存储方式。 +默认情况下,Activity 停止时会被放入由系统管理的 Activity 返回栈(以便用户通过“返回” + +按钮回退到Activity,任务和返回栈对此做了阐述)。不过,仅当您在删除片段的事务执行期间通过调用 +{@link +android.app.FragmentTransaction#addToBackStack(String) addToBackStack()} +显式请求保存实例时,系统才会将片段放入由宿主 Activity 管理的返回栈。 +

+ +

在其他方面,管理片段生命周期与管理 Activity 生命周期非常相似。 +因此,管理 Activity 生命周期的做法同样适用于片段。 +但您还需要了解 Activity 的生命周期对片段生命周期的影响。 +

+ +

注意:如需 {@link android.app.Fragment} +内的某个 {@link android.content.Context} +对象,可以调用 {@link android.app.Fragment#getActivity()}。但要注意,请仅在片段附加到 Activity 时调用 +{@link android.app.Fragment#getActivity()}。如果片段尚未附加,或在其生命周期结束期间分离,则 +{@link android.app.Fragment#getActivity()} 将返回 null。

+ + +

与 Activity 生命周期协调一致

+ +

片段所在的 Activity 的生命周期会影响片段的生命周期,其表现为,Activity 的每次生命周期回调都会引发每个片段的类似回调。 + +例如,当 Activity 收到 {@link android.app.Activity#onPause} 时,Activity 中的每个片段也会收到 +{@link android.app.Fragment#onPause}。

+ +

不过,片段还有几个额外的生命周期回调,用于处理与 Activity 的唯一交互,以执行构建和销毁片段 UI 等操作。这些额外的回调方法是: + +

+ +
+
{@link android.app.Fragment#onAttach onAttach()}
+
在片段已与 Activity 关联时调用({@link +android.app.Activity} 传递到此方法内)。
+
{@link android.app.Fragment#onCreateView onCreateView()}
+
调用它可创建与片段关联的视图层次结构。
+
{@link android.app.Fragment#onActivityCreated onActivityCreated()}
+
在 Activity 的 {@link android.app.Activity#onCreate +onCreate()} 方法已返回时调用。
+
{@link android.app.Fragment#onDestroyView onDestroyView()}
+
在删除与片段关联的视图层次结构时调用。
+
{@link android.app.Fragment#onDetach onDetach()}
+
在取消片段与 Activity 的关联时调用。
+
+ +

图 3 +图示说明了受其宿主 Activity 影响的片段生命周期流。在该图中,您可以看到 Activity 的每个连续状态如何决定片段可以收到的回调方法。 +例如,当 Activity 收到其 {@link +android.app.Activity#onCreate onCreate()} 回调时,Activity 中的片段只会收到 +{@link android.app.Fragment#onActivityCreated onActivityCreated()} 回调。

+ +

一旦 Activity 达到恢复状态,您就可以意向 Activity 添加片段和删除其中的片段。 +因此,只有当 Activity 处于恢复状态时,片段的生命周期才能独立变化。 +

+ +

不过,当 Activity 离开恢复状态时,片段会在 Activity 的推动下再次经历其生命周期。 +

+ + + + +

示例

+ +

为了将本文阐述的所有内容融会贯通,以下提供了一个示例,其中的 Activity 使用两个片段来创建一个双窗格布局。 +下面的 Activity 包括两个片段:一个用于显示莎士比亚戏剧标题列表,另一个用于从列表中选定戏剧时显示其摘要。 + +此外,它还展示了如何根据屏幕配置提供不同的片段配置。 +

+ +

注:{@code +FragmentLayout.java} +中提供了此 Activity 的完整源代码。

+ +

主 Activity 会在 {@link +android.app.Activity#onCreate onCreate()} 期间以常规方式应用布局:

+ +{@sample development/samples/ApiDemos/src/com/example/android/apis/app/FragmentLayout.java main} + +

应用的布局为 {@code fragment_layout.xml}:

+ +{@sample development/samples/ApiDemos/res/layout-land/fragment_layout.xml layout} + +

通过使用此布局,系统可在 Activity 加载布局时立即实例化 +{@code TitlesFragment}(列出戏剧标题),而 +{@link android.widget.FrameLayout}(用于显示戏剧摘要的片段所在位置)则会占用屏幕右侧的空间,但最初处于空白状态。 +正如您将在下文所见的那样,用户从列表中选择某个项目后,系统才会将片段放入 +{@link android.widget.FrameLayout}。

+ +

不过,并非所有屏幕配置都具有足够的宽度,可以并排显示戏剧列表和摘要。 +因此,以上布局仅用于横向屏幕配置(布局保存在 +{@code res/layout-land/fragment_layout.xml})。

+ +

因此,当屏幕纵向显示时,系统会应用以下布局(保存在 +{@code res/layout/fragment_layout.xml}):

+ +{@sample development/samples/ApiDemos/res/layout/fragment_layout.xml layout} + +

此布局仅包括 {@code TitlesFragment}。这意味着,当设备纵向显示时,只有戏剧标题列表可见。 +因此,当用户在此配置中点击某个列表项时,应用会启动一个新 Activity 来显示摘要,而不是加载另一个片段。 + +

+ +

接下来,您可以看到如何在片段类中实现此目的。第一个片段是 {@code +TitlesFragment},它显示莎士比亚戏剧标题列表。该片段扩展了 {@link +android.app.ListFragment},并依靠它来处理大多数列表视图工作。

+ +

当您检查此代码时,请注意,用户点击列表项时可能会出现两种行为:系统可能会创建并显示一个新片段,从而在同一活动中显示详细信息(将片段添加到 +{@link +android.widget.FrameLayout}),也可能会启动一个新活动(在该活动中可显示片段),具体取决于这两个布局中哪一个处于活动状态。 +

+ +{@sample development/samples/ApiDemos/src/com/example/android/apis/app/FragmentLayout.java titles} + +

第二个片段 {@code DetailsFragment} 显示从 +{@code TitlesFragment} 的列表中选择的项目的戏剧摘要:

+ +{@sample development/samples/ApiDemos/src/com/example/android/apis/app/FragmentLayout.java details} + +

从 {@code TitlesFragment} 类中重新调用,如果用户点击某个列表项,且当前布局“根本不”包括 {@code R.id.details} +视图(即 +{@code DetailsFragment} 所属视图),则应用会启动 {@code DetailsActivity} +Activity 以显示该项目的内容。

+ +

以下是 {@code DetailsActivity},它简单地嵌入了 +{@code DetailsFragment},以在屏幕为纵向时显示所选的戏剧摘要:

+ +{@sample development/samples/ApiDemos/src/com/example/android/apis/app/FragmentLayout.java +details_activity} + +

请注意,如果配置为横向,则此 Activity 会自行完成,以便主 Activity 可以接管并沿 {@code TitlesFragment} +显示 +{@code DetailsFragment}。如果用户在纵向显示时启动 +{@code DetailsActivity},但随后旋转为横向(这会重启当前 Activity),就可能出现这种情况。

+ + +

如需查看使用片段的更多示例(以及本示例的完整源文件),请参阅 + +ApiDemos(可从示例 SDK 组件下载)中提供的 API Demos 示例应用。

+ + diff --git a/docs/html-intl/intl/zh-cn/guide/components/fundamentals.jd b/docs/html-intl/intl/zh-cn/guide/components/fundamentals.jd new file mode 100644 index 0000000000000000000000000000000000000000..4ff22b64acbefd38f59de9c702ed8235f5d18620 --- /dev/null +++ b/docs/html-intl/intl/zh-cn/guide/components/fundamentals.jd @@ -0,0 +1,480 @@ +page.title=应用基础知识 +@jd:body + +
+ +
+ +

Android 应用采用 Java 编程语言编写。Android SDK +工具将您的代码—连同任何数据和资源文件—编译到一个 APK: Android 软件包,即带有 +{@code .apk} 后缀的存档文件中。一个 APK 文件包含 +Android 应用的所有内容,它是基于 Android 系统的设备用来安装应用的文件。

+ +

安装到设备后,每个 Android 应用都运行在自己的安全沙箱内:

+ +
    +
  • Android 操作系统是一种多用户 Linux +系统,其中的每个应用都是一位不同的用户;
  • + +
  • 默认情况下,系统会为每个应用分配一个唯一的 Linux 用户 ID(该 ID +仅由系统使用,应用并不知晓)。系统为应用中的所有文件设置权限,使得只有分配给该应用的用户 ID +才能访问这些文件;
  • + +
  • 每个进程都具有自己的虚拟机 +(VM),因此应用代码是在与其他应用隔离的环境中运行;
  • + +
  • 默认情况下,每个应用都在其自己的 Linux 进程内运行。Android +会在需要执行任何应用组件时启动该进程,然后在不再需要该进程或系统必须为其他应用恢复内存时关闭该进程。 +
  • +
+ +

Android 系统可以通过这种方式实现最小权限原则。也就是说,默认情况下,每个应用都只能访问执行其工作所需的组件,而不能访问其他组件。 + +这样便营造出一个非常安全的环境,在这个环境中,应用无法访问系统中其未获得权限的部分。 +

+ +

不过,应用仍然可以通过一些途径与其他应用共享数据以及访问系统服务: +

+ +
    +
  • 可以安排两个应用共享同一 Linux 用户 +ID,在这种情况下,它们能够相互访问彼此的文件。为了节省系统资源,可以安排具有相同用户 ID +的应用在同一 Linux 进程中运行,并共享同一 +VM(应用还必须使用相同的证书签署);
  • +
  • 应用可以请求访问设备数据(如用户的联系人、短信、可装入存储装置 +[SD 卡]、相机、蓝牙等)的权限。所有应用权限都必须由用户在安装时授予。 +
  • +
+ +

以上内容阐述了有关 Android 应用在系统内存在方式的基础知识。本文的其余部分将向您介绍以下内容: +

+
    +
  • 定义应用的核心框架组件
  • +
  • 您用来声明组件和应用必需设备功能的清单文件 +
  • +
  • 与应用代码分离并允许您的应用针对各种设备配置适当优化其行为的资源 +
  • +
+ + + +

应用组件

+ +

应用组件是 +Android +应用的基本构建基块。每个组件都是一个不同的点,系统可以通过它进入您的应用。并非所有组件都是用户的实际入口点,有些组件相互依赖,但每个组件都以独立实体形式存在,并发挥特定作用—每个组件都是唯一的构建基块,有助于定义应用的总体行为。 + +

+ +

共有四种不同的应用组件类型。每种类型都服务于不同的目的,并且具有定义组件的创建和销毁方式的不同生命周期。 +

+ +

以下便是这四种应用组件类型:

+ +
+ +
Activity
+ +
Activity表示具有用户界面的单一屏幕。例如,电子邮件应用可能具有一个显示新电子邮件列表的 Activity、一个用于撰写电子邮件的 Activity 以及一个用于阅读电子邮件的 Activity。 + +尽管这些 Activity 通过协作在电子邮件应用中形成了一种具有凝聚力的用户体验,但每一个 Activity 都独立于其他 Activity 而存在。 + +因此,其他应用可以启动其中任何一个 Activity(如果电子邮件应用允许)。 +例如,相机应用可以启动电子邮件应用内用于撰写新电子邮件的 Activity,以便用户共享图片。 + + +

Activity 作为 +{@link android.app.Activity} +的子类实现,您可以在Activity开发者指南中了解有关它的更多详情。

+
+ + +
服务
+ +
服务 是一种在后台运行的组件,用于执行长时间运行的操作或为远程进程执行作业。 +服务不提供用户界面。 +例如,当用户位于其他应用中时,服务可能在后台播放音乐或者通过网络获取数据,但不会阻断用户与 Activity 的交互。 + +诸如 Activity 等其他组件可以启动服务,让其运行或与其绑定以便与其进行交互。 + + +

服务作为 +{@link android.app.Service} +的子类实现,您可以在服务开发者指南中了解有关它的更多详情。

+
+ + +
内容提供程序
+ +
内容提供程序 管理一组共享的应用数据。您可以将数据存储在文件系统、SQLite +数据库、Web +上或您的应用可以访问的任何其他永久性存储位置。其他应用可以通过内容提供程序查询数据,甚至修改数据(如果内容提供程序允许)。 +例如,Android +系统可提供管理用户联系人信息的内容提供程序。因此,任何具有适当权限的应用都可以查询内容提供程序的某一部分(如 +{@link +android.provider.ContactsContract.Data}),以读取和写入有关特定人员的信息。 + +

内容提供程序也适用于读取和写入您的应用不共享的私有数据。 +例如,记事本示例应用使用内容提供程序来保存笔记。 +

+ +

内容提供程序作为 {@link android.content.ContentProvider} +的子类实现,并且必须实现让其他应用能够执行事务的一组标准 +API。如需了解详细信息,请参阅内容提供程序开发者指南。 +

+
+ + +
广播接收器
+ +
广播接收器 是一种用于响应系统范围广播通知的组件。 +许多广播都是由系统发起的—例如,通知屏幕已关闭、电池电量不足或已拍摄照片的广播。应用也可以发起广播—例如,通知其他应用某些数据已下载至设备,并且可供其使用。 + + +尽管广播接收器不会显示用户界面,但它们可以创建状态栏通知,在发生广播事件时提醒用户。 + +但广播接收器更常见的用途只是作为通向其他组件的“通道”,设计用于执行极少量的工作。 +例如,它可能会基于事件发起一项服务来执行某项工作。 + + +

广播接收器作为 {@link android.content.BroadcastReceiver} +的子类实现,并且每条广播都作为 {@link android.content.Intent} 对象进行传递。如需了解详细信息,请参阅 +{@link android.content.BroadcastReceiver} 类。

+
+ +
+ + + +

Android +系统设计的独特之处在于,任何应用都可以启动其他应用的组件。例如,如果您想让用户使用设备的相机拍摄照片,很可能有另一个应用可以执行该操作,那么您的应用就可以利用该应用,而不是开发一个 Activity 来自行拍摄照片。 + +您不需要集成甚至链接到该相机应用的代码,而是只需在拍摄照片的相机应用中启动该 Activity。 + + +完成拍摄时,系统甚至会将照片返回您的应用,以便您使用。对用户而言,就好像相机真正是您应用的组成部分。 +

+ +

当系统启动某个组件时,会启动该应用的进程(如果尚未运行),并实例化该组件所需的类。 +例如,如果您的应用启动相机应用中拍摄照片的 Activity,则该 Activity 会在属于相机应用的进程,而不是您的应用的进程中运行。因此,与大多数其他系统上的应用不同,Android +应用并没有单一入口点(例如,没有 +{@code main()} +功能)。 +

+ +

由于系统在单独的进程中运行每个应用,且其文件权限会限制对其他应用的访问,因此您的应用无法直接启动其他应用中的组件,但 +Android +系统却可以。因此,要想启动其他应用中的组件,您必须向系统传递一则消息,说明您想启动特定组件的 + Intent。 +系统随后便会为您启动该组件。

+ + +

启动组件

+ +

四种组件类型中的三种—Activity、服务和广播接收器—通过名为 + Intent +的异步消息进行启动。 Intent 会在运行时将各个组件相互绑定(您可以将 Intent 视为从其他组件请求操作的信使),无论组件属于您的应用还是其他应用。 + +

+ +

Intent 使用 {@link android.content.Intent} +对象创建,它定义的消息用于启动特定组件或特定类型的组件— Intent 可以是显式的,也可以是隐式的。 +

+ +

对于 Activity 和服务, Intent 定义要执行的操作(例如,“查看”或“发送”某个内容),并且可以指定要执行操作的数据的 +URI(以及正在启动的组件可能需要了解的信息)。 +例如, Intent 传达的请求可以是启动一个显示图像或打开网页的 Activity。 +在某些情况下,您可以启动 Activity 来接收结果,在这种情况下,Activity 也会在 {@link android.content.Intent} +中返回结果(例如,您可以发出一个 Intent,让用户选取某位联系人并将其返回给您 — 返回 Intent 包括指向所选联系人的 +URI)。 + +

+ +

对于广播接收器, Intent 只会定义要广播的通知(例如,指示设备电池电量不足的广播只包括指示“电池电量不足”的已知操作字符串)。 + +

+ +

Intent 不会启动另一个组件类型 - 内容提供程序,后者会在成为 +{@link android.content.ContentResolver} 的请求目标时启动。内容解析程序通过内容提供程序处理所有直接事务,使得通过提供程序执行事务的组件可以无需执行事务,而是改为在 +{@link +android.content.ContentResolver} +对象上调用方法。这会在内容提供程序与请求信息的组件之间留出一个抽象层(以确保安全)。 +

+ +

每种类型的组件有不同的启动方法:

+
    +
  • 您可以通过将 {@link android.content.Intent} +传递到 {@link android.content.Context#startActivity +startActivity()} 或 +{@link android.app.Activity#startActivityForResult startActivityForResult()}(当您想让 Activity 返回结果时)来启动 Activity(或为其安排新任务);
  • +
  • 您可以通过将 +{@link android.content.Intent} 传递到 {@link android.content.Context#startService +startService()} 来启动服务(或对执行中的服务下达新指令)。或者,您也可以通过将 {@link android.content.Intent} 传递到 +{@link android.content.Context#bindService bindService()} 来绑定到该服务;
  • +
  • 您可以通过将 {@link android.content.Intent} 传递到 +{@link android.content.Context#sendBroadcast(Intent) sendBroadcast()}、{@link +android.content.Context#sendOrderedBroadcast(Intent, String) sendOrderedBroadcast()} 或 {@link +android.content.Context#sendStickyBroadcast sendStickyBroadcast()} 等方法来发起广播;
  • +
  • 您可以通过在 {@link android.content.ContentResolver} 上调用 {@link +android.content.ContentProvider#query query()} 来对内容提供程序执行查询。
  • +
+ +

如需了解有关 Intent 用法的详细信息,请参阅 Intent 和 Intent 过滤器文档。 +以下文档中还提供了有关启动特定组件的详细信息: +Activity服务、{@link +android.content.BroadcastReceiver} 和内容提供程序

+ + +

清单文件

+ +

在 Android +系统启动应用组件之前,系统必须通过读取应用的 {@code AndroidManifest.xml} +文件(“清单”文件)确认组件存在。您的应用必须在此文件中声明其所有组件,该文件必须位于应用项目目录的根目录中。 +

+ +

除了声明应用的组件外,清单文件还有许多其他作用,如: +

+
    +
  • 确定应用需要的任何用户权限,如互联网访问权限或对用户联系人的读取权限 +
  • +
  • 根据应用使用的 +API,声明应用所需的最低API 级别
  • +
  • 声明应用使用或需要的硬件和软件功能,如相机、蓝牙服务或多点触摸屏幕 +
  • +
  • 应用需要链接的 API 库(Android 框架 +API 除外),如 Google Maps API +库
  • +
  • 其他功能
  • +
+ + +

声明组件

+ +

清单文件的主要任务是告知系统有关应用组件的信息。例如,清单文件可以想下面这样声明 Activity: +

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<manifest ... >
+    <application android:icon="@drawable/app_icon.png" ... >
+        <activity android:name="com.example.project.ExampleActivity"
+                  android:label="@string/example_label" ... >
+        </activity>
+        ...
+    </application>
+</manifest>
+ +

<application> +元素中,{@code android:icon} +属性指向标识应用的图标所对应的资源。

+ +

<activity> +元素中,{@code android:name} 属性指定 {@link +android.app.Activity} 子类的完全限定类名,{@code android:label} +属性指定用作 Activity 的用户可见标签的字符串。

+ +

您必须通过以下方式声明所有应用组件:

+ + +

您包括在源代码中,但未在清单文件中声明的 Activity、服务和内容提供程序对系统不可见,因此也永远不会运行。 +不过,广播接收器可以在清单文件中声明或在代码中动态创建(如 +{@link android.content.BroadcastReceiver} +对象)并通过调用 +{@link android.content.Context#registerReceiver registerReceiver()} +在系统中注册。

+ +

如需了解有关如何为您的应用构建清单文件的详细信息,请参阅 +AndroidManifest.xml 文件文档。

+ + + +

声明组件功能

+ +

如上文启动组件中所述,您可以使用 +{@link android.content.Intent} 来启动 Activity、服务和广播接收器。您可以通过在 Intent 中显式命名目标组件(使用组件类名)来执行此操作。 +不过,Intent 的真正强大之处在于隐式 Intent 概念。 +隐式 Intent 的作用无非是描述要执行的操作类型(还可选择描述您想执行的操作所针对的数据),让系统能够在设备上找到可执行该操作的组件,并启动该组件。 + + +如果有多个组件可以执行 Intent 所描述的操作,则由用户选择使用哪一个组件。 +

+ +

系统通过将接收到的 Intent 与设备上的其他应用的清单文件中提供的 Intent 过滤器进行比较来确定可以响应 Intent 的组件。 + +

+ +

当您在应用的清单文件中声明 Activity 时,可以选择性地加入声明 Activity 功能的 Intent 过滤器,以便响应来自其他应用的 Intent。 + +您可以通过将 +{@code +<intent-filter>} 元素作为组件声明元素的子项进行添加来为您的组件声明 Intent 过滤器。

+ +

例如,如果您开发的一个电子邮件应用包含一个用于撰写新电子邮件的 Activity,则可以像下面这样声明一个 Intent 过滤器来响应“send” Intent(以发送新电子邮件): +

+
+<manifest ... >
+    ...
+    <application ... >
+        <activity android:name="com.example.project.ComposeEmailActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.SEND" />
+                <data android:type="*/*" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </activity>
+    </application>
+</manifest>
+
+ +

然后,如果另一个应用创建了一个包含 +{@link +android.content.Intent#ACTION_SEND} 操作的 Intent,并将其传递到 {@link android.app.Activity#startActivity +startActivity()},则系统可能会启动您的 Activity,以便用户能够草拟并发送电子邮件。

+ +

如需了解有关创建 Intent 过滤器的详细信息,请参阅 Intent 和 Intent 过滤器文档。 +

+ + + +

声明应用要求

+ +

基于 Android +系统的设备多种多样,并非所有设备都提供相同的特性和功能。为防止将您的应用安装在缺少应用所需特性的设备上,您必须通过在清单文件中声明设备和软件要求,为您的应用支持的设备类型明确定义一个配置文件。 + + +其中的大多数声明只是为了提供信息,系统不会读取它们,但 +Google Play +等外部服务会读取它们,以便当用户在其设备中搜索应用时为用户提供过滤功能。

+ +

例如,如果您的应用需要相机,并使用 Android 2.1(API 7 级)中引入的 +API,您应该像下面这样在清单文件中以要求形式声明这些信息:

+ +
+<manifest ... >
+    <uses-feature android:name="android.hardware.camera.any"
+                  android:required="true" />
+    <uses-sdk android:minSdkVersion="7" android:targetSdkVersion="19" />
+    ...
+</manifest>
+
+ +

现在,没有相机且 +Android 版本低于 2.1 的设备将无法从 Google Play 安装您的应用。

+ +

不过,您也可以声明您的应用使用相机,但并不要求必须使用。 +在这种情况下,您的应用必须将 {@code required} +属性设置为 +{@code "false"},并在运行时检查设备是否具有相机,然后根据需要禁用任何相机功能。

+ +

设备兼容性文档中提供了有关如何管理应用与不同设备兼容性的详细信息。 + +

+ + + +

应用资源

+ +

Android +应用并非只包含代码—它还需要与源代码分离的资源,如图像、音频文件以及任何与应用的视觉呈现有关的内容。例如,您应该通过 +XML +文件定义 Activity 用户界面的动画、菜单、样式、颜色和布局。使用应用资源能够在不修改代码的情况下轻松地更新应用的各种特性,并可通过提供备用资源集让您能够针对各种设备配置(如不同的语言和屏幕尺寸)优化您的应用。 + + +

+ +

对于您的 Android 项目中包括的每一项资源,SDK +构建工具都会定义一个唯一的整型 ID,您可以利用它来引用应用代码或 XML +中定义的其他资源中的资源。例如,如果您的应用包含一个名为 {@code +logo.png} 的图像文件(保存在 {@code res/drawable/} 目录中),则 SDK 工具会生成一个名为 +{@code R.drawable.logo} 的资源 +ID,您可以利用它来引用该图像并将其插入您的用户界面。

+ +

提供与源代码分离的资源的其中一个最重要优点在于,您可以提供针对不同设备配置的备用资源。 + +例如,通过在 XML 中定义 UI +字符串,您可以将字符串翻译为其他语言,并将这些字符串保存在单独的文件中。然后,Android +系统会根据向资源目录名称追加的语言限定符(如为法语字符串值追加 {@code res/values-fr/})和用户的语言设置,对您的 +UI +应用相应的语言字符串。

+ +

Android 支持许多不同的备用资源限定符。限定符是一种加入到资源目录名称中,用来定义这些资源适用的设备配置的简短字符串。 + +再举一例,您应该经常会根据设备的屏幕方向和尺寸为 Activity 创建不同的布局。 + +例如,当设备屏幕为纵向(长型)时,您可能想要一种垂直排列按钮的布局;但当屏幕为横向(宽型)时,应按水平方向排列按钮。 + +要想根据方向更改布局,您可以定义两种不同的布局,然后对每个布局的目录名称应用相应的限定符。 + +然后,系统会根据当前设备方向自动应用相应的布局。 +

+ +

如需了解有关可以在应用中包括的不同资源类型以及如何针对不同设备配置创建备用资源的详细信息,请阅读提供资源。 +

+ + + +
+
+

继续阅读以下方面的内容:

+
+
Intent 和 Intent 过滤器 +
+
有关如何使用 {@link android.content.Intent} API 来启动应用组件(如 Activity 和服务)以及如何使您的应用组件可供其他应用使用的信息。 + +
+
Activity
+
有关如何创建 {@link android.app.Activity} 类实例的信息,该类可在您的应用内提供一个具有用户界面的独立屏幕。 +
+
提供资源
+
有关如何通过适当构建 Android 应用来使应用资源与应用代码分离的信息,包括如何针对特定设备配置提供备用资源。 + + +
+
+
+
+

您可能还对以下内容感兴趣:

+
+
设备兼容性
+
有关 Android 在不同设备类型上工作方式的信息,并介绍了如何针对不同设备优化您的应用,或如何限制您的应用在不同设备上的可用性。 + +
+
系统权限
+
有关 Android 如何通过一种权限系统来限制应用对特定 API 访问权限的信息,该系统要求征得用户同意,才允许您的应用使用这些 API。 +
+
+
+
+ diff --git a/docs/html-intl/intl/zh-cn/guide/components/index.jd b/docs/html-intl/intl/zh-cn/guide/components/index.jd new file mode 100644 index 0000000000000000000000000000000000000000..53e81849c550e2006670cd92336b1203bb9b4e6a --- /dev/null +++ b/docs/html-intl/intl/zh-cn/guide/components/index.jd @@ -0,0 +1,57 @@ +page.title=应用组件 +page.landing=true +page.landing.intro=利用 Android应用框架,您可以使用一组可重复使用的组件创建丰富的创新应用。此部分阐述您可以如何构建用于定义应用构建基块的组件,以及如何使用 Intent 将这些组件连接在一起。 +page.metaDescription=利用 Android应用框架,您可以使用一组可重复使用的组件创建丰富的创新应用。此部分阐述您可以如何构建用于定义应用构建基块的组件,以及如何使用 Intent 将这些组件连接在一起。 +page.landing.image=images/develop/app_components.png +page.image=images/develop/app_components.png + +@jd:body + +
+ + + + + +
diff --git a/docs/html-intl/intl/zh-cn/guide/components/intents-filters.jd b/docs/html-intl/intl/zh-cn/guide/components/intents-filters.jd new file mode 100644 index 0000000000000000000000000000000000000000..1c11a5b9609570bf3ee07104fb4235b984b829f9 --- /dev/null +++ b/docs/html-intl/intl/zh-cn/guide/components/intents-filters.jd @@ -0,0 +1,899 @@ +page.title=Intent 和 Intent 过滤器 +page.tags="IntentFilter" +@jd:body + + + + + + +

{@link android.content.Intent} +是一个消息传递对象,您可以使用它从其他应用组件请求操作。尽管 + Intent +可以通过多种方式促进组件之间的通信,但其基本用例主要包括以下三个:

+ +
    +
  • 启动 Activity: +

    {@link android.app.Activity} 表示应用中的一个屏幕。通过将 +{@link android.content.Intent} 传递给 {@link android.content.Context#startActivity startActivity()},您可以启动新的 +{@link android.app.Activity} 实例。{@link android.content.Intent} +描述了要启动的 Activity,并携带了任何必要的数据。

    + +

    如果您希望在 Activity 完成后收到结果,请调用 +{@link android.app.Activity#startActivityForResult +startActivityForResult()}。在 Activity 的 {@link +android.app.Activity#onActivityResult onActivityResult()} 回调中,您的 Activity 将结果作为单独的 +{@link android.content.Intent} +对象接收。如需了解详细信息,请参阅Activity指南。

  • + +
  • 启动服务: +

    {@link android.app.Service} +是一个不使用用户界面而在后台执行操作的组件。通过将 +{@link android.content.Intent} +传递给 {@link android.content.Context#startService startService()},您可以启动服务执行一次性操作(例如,下载文件)。{@link android.content.Intent} +描述了要启动的服务,并携带了任何必要的数据。

    + +

    如果服务旨在使用客户端-服务器接口,则通过将 {@link android.content.Intent} 传递给 +{@link +android.content.Context#bindService bindService()},您可以从其他组件绑定到此服务。如需了解详细信息,请参阅服务指南。

  • + +
  • 传递广播: +

    广播是任何应用均可接收的消息。系统将针对系统事件(例如:系统启动或设备开始充电时)传递各种广播。通过将 +{@link android.content.Intent} +传递给 +{@link android.content.Context#sendBroadcast(Intent) sendBroadcast()}、{@link +android.content.Context#sendOrderedBroadcast(Intent, String) +sendOrderedBroadcast()} 或 {@link +android.content.Context#sendStickyBroadcast sendStickyBroadcast()},您可以将广播传递给其他应用。

    +
  • +
+ + + + +

Intent 类型

+ +

Intent 分为两种类型:

+ +
    +
  • 显式 + Intent :按名称(完全限定类名)指定要启动的组件。通常,您会在自己的应用中使用显式 Intent +来启动组件,这是因为您知道要启动的 Activity 或服务的类名。例如,启动新 Activity 以响应用户操作,或者启动服务以在后台下载文件。 + +
  • + +
  • 隐式 + Intent :不会指定特定的组件,而是声明要执行的常规操作,从而允许其他应用中的组件来处理它。例如,如需在地图上向用户显示位置,则可以使用隐式 + Intent,请求另一具有此功能的应用在地图上显示指定的位置。 +
  • +
+ +

创建显式 Intent 启动 Activity 或服务时,系统将立即启动 +{@link android.content.Intent} 对象中指定的应用组件。

+ +
+ +

图 1. 隐式 + Intent 如何通过系统传递以启动其他 Activity 的图解:[1] Activity A 创建包含操作描述的 +{@link android.content.Intent},并将其传递给 {@link +android.content.Context#startActivity startActivity()}。[2] +Android 系统搜索所有应用中与 Intent 匹配的 Intent 过滤器。找到匹配项之后,[3] 该系统通过调用匹配 Activity(Activity +B)的 {@link +android.app.Activity#onCreate onCreate()} 方法并将其传递给 {@link android.content.Intent},以此启动匹配 Activity。 +

+
+ +

创建隐式 Intent +时,Android 系统通过将 Intent 的内容与在设备上其他应用的清单文件中声明的 + Intent 过滤器进行比较,从而找到要启动的相应组件。{@link android.content.Intent}如果 Intent 与 Intent +过滤器匹配,则系统将启动该组件,并将其传递给对象。如果多个 + Intent 过滤器兼容,则系统会显示一个对话框,支持用户选取要使用的应用。

+ +

Intent +过滤器是应用清单文件中的一个表达式,它指定该组件要接收的 Intent +类型。例如,通过为 Activity 声明 Intent 过滤器,您可以使其他应用能够直接使用某一特定类型的 + Intent 启动 Activity。同样,如果您没有为 Activity 声明任何 + Intent 过滤器,则 Activity 只能通过显式 + Intent 启动。

+ +

警告:为了确保应用的安全性,启动 +{@link android.app.Service} 时,请始终使用显式 + Intent,且不要为服务声明 Intent 过滤器。使用隐式 + Intent 启动服务存在安全隐患,因为您无法确定哪些服务将响应 + Intent,且用户无法看到哪些服务已启动。从 Android 5.0(API 级别 21)开始,如果使用隐式 + Intent +调用 {@link android.content.Context#bindService bindService()},系统会抛出异常。

+ + + + + +

构建 Intent

+ +

{@link android.content.Intent} +对象携带了 Android +系统用来确定要启动哪个组件的信息(例如,准确的组件名称或应当接收该 + Intent 的组件类别),以及收件人组件为了正确执行操作而使用的信息(例如,要采取的操作以及要处理的数据)。

+ + +

{@link android.content.Intent} 中包含的主要信息如下:

+ +
+ +
组件名称
+
要启动的组件名称。 + +

这是可选项,但也是构建显式 + Intent 的一项重要信息,这意味着 + Intent 应当仅传递给由组件名称定义的应用组件。如果没有组件名称,则 + Intent 是隐式的,且系统将根据其他 + Intent 信息(例如,以下所述的操作、数据和类别)决定哪个组件应当接收 Intent。因此,如需在应用中启动特定的组件,则应指定该组件的名称。 +

+ +

注意:启动 {@link android.app.Service} 时,您应 +始终指定组件名称。否则,您无法确定哪项服务会响应 + Intent,且用户无法看到哪项服务已启动。

+ +

{@link android.content.Intent} +的这一字段是 +{@link android.content.ComponentName} +对象,您可以使用目标组件的完全限定类名指定此对象,其中包括应用的软件包名称。例如,{@code com.example.ExampleActivity}。您可以使用 {@link +android.content.Intent#setComponent setComponent()}、{@link android.content.Intent#setClass +setClass()}、{@link android.content.Intent#setClassName(String, String) setClassName()} 或 +{@link android.content.Intent} 构造函数设置组件名称。

+ +
+ +

操作
+
指定要执行的通用操作(例如,“查看”或“选取”)的字符串。 + +

对于广播 + Intent,这是指已发生且正在报告的操作。操作在很大程度上决定了其余 Intent 的构成,特别是数据和 +extra 中包含的内容。 + +

您可以指定自己的操作,供 + Intent 在您的应用内使用(或者供其他应用在您的应用中调用组件)。但是,您通常应该使用由 +{@link android.content.Intent} 类或其他框架类定义的操作常量。以下是一些用于启动 Activity 的常见操作: +

+ +
+
{@link android.content.Intent#ACTION_VIEW}
+
如果您拥有一些某项 Activity 可向用户显示的信息(例如,要使用图库应用查看的照片;或者要使用地图应用查找的地址),请使用 + Intent +将此操作与 {@link +android.content.Context#startActivity startActivity()} 结合使用。
+ +
{@link android.content.Intent#ACTION_SEND}
+
这也称为“共享” Intent。如果您拥有一些用户可通过其他应用(例如,电子邮件应用或社交共享应用)共享的数据,则应使用 Intent 中将此操作与 +{@link +android.content.Context#startActivity startActivity()} 结合使用。
+
+ +

有关更多定义通用操作的常量,请参阅{@link android.content.Intent}类引用。 +其他操作在 +Android 框架中的其他位置定义。例如,对于在系统的设置应用中打开特定屏幕的操作,将在 {@link android.provider.Settings} +中定义。

+ +

您可以使用 {@link android.content.Intent#setAction +setAction()} 或 {@link android.content.Intent} 构造函数为 Intent 指定操作。

+ +

如果定义自己的操作,请确保将应用的软件包名称作为前缀。 +例如:

+
static final String ACTION_TIMETRAVEL = "com.example.action.TIMETRAVEL";
+
+ +
数据
+
引用待操作数据和/或该数据 +MIME 类型的 URI({@link android.net.Uri} 对象)。提供的数据类型通常由 Intent 的操作决定。例如,如果操作是 +{@link android.content.Intent#ACTION_EDIT},则数据应包含待编辑文档的 +URI。 + +

创建 + Intent 时,除了指定 +URI +以外,指定数据类型(其 +MIME 类型)往往也很重要。例如,能够显示图像的Activity可能无法播放音频文件,即便 URI 格式十分类似时也是如此。因此,指定数据的 +MIME 类型有助于 Android +系统找到接收 Intent 的最佳组件。但有时,MIME 类型可以从 URI 中推断得出,特别当数据是 +{@code content:} URI 时尤其如此。这表明数据位于设备中,且由 +{@link android.content.ContentProvider} 控制,这使得数据 MIME 类型对系统可见。

+ +

要仅设置数据 URI,请调用 +{@link android.content.Intent#setData setData()}。要仅设置 MIME 类型,请调用 {@link android.content.Intent#setType setType()}。如有必要,您可以使用 +{@link +android.content.Intent#setDataAndType setDataAndType()} 同时显式设置二者。

+ +

警告:若要同时设置 URI 和 MIME 类型,请勿调用 +{@link android.content.Intent#setData setData()} 和 +{@link android.content.Intent#setType setType()},因为它们会互相抵消彼此的值。请始终使用 +{@link android.content.Intent#setDataAndType setDataAndType()} 同时设置 +URI 和 MIME 类型。

+
+ +

类别
+
一个包含应处理 Intent +组件类型的附加信息的字符串。您可以将任意数量的类别描述放入一个 + Intent 中,但大多数 + Intent 均不需要类别。以下是一些常见类别: + +
+
{@link android.content.Intent#CATEGORY_BROWSABLE}
+
目标 Activity 允许本身通过 Web +浏览器启动,以显示链接引用的数据,如图像或电子邮件。 +
+
{@link android.content.Intent#CATEGORY_LAUNCHER}
+
该 Activity 是任务的初始 Activity,在系统的应用启动器中列出。 + +
+
+ +

有关类别的完整列表,请参阅 +{@link android.content.Intent} 类描述。

+ +

您可以使用 {@link android.content.Intent#addCategory addCategory()} 指定类别。

+
+
+ + +

以上列出的这些属性(组件名称、操作、数据和类别)表示 + Intent 的既定特征。通过读取这些属性,Android +系统能够解析应当启动哪个应用组件。

+ +

但是,Intent +也有可能会一些携带不影响其如何解析为应用组件的信息。Intent 还可以提供:

+ +
+
Extra
+
携带完成请求操作所需的附加信息的键值对。正如某些操作使用特定类型的数据 +URI 一样,有些操作也使用特定的附加数据。 + +

您可以使用各种 +{@link android.content.Intent#putExtra putExtra()} 方法添加附加数据,每种方法均接受两个参数:键名和值。您还可以创建一个包含所有附加数据的 {@link android.os.Bundle} +对象,然后使用 {@link +android.content.Intent#putExtras putExtras()} 将 +{@link android.os.Bundle} 插入 {@link android.content.Intent} 中。

+ +

例如,使用 +{@link android.content.Intent#ACTION_SEND} 创建用于发送电子邮件的 Intent 时,可以使用 +{@link android.content.Intent#EXTRA_EMAIL} 键指定“目标”收件人,并使用 +{@link android.content.Intent#EXTRA_SUBJECT} 键指定“主题”。

+ +

{@link android.content.Intent} 类将为标准化的数据类型指定多个 {@code EXTRA_*} +常量。如需声明自己的附加数据 +键(对于应用接收的 + Intent ),请确保将应用的软件包名称作为前缀。例如:

+
static final String EXTRA_GIGAWATTS = "com.example.EXTRA_GIGAWATTS";
+
+ +
标志
+
在 {@link android.content.Intent} 类中定义的、充当 + Intent 元数据的标志。标志可以指示 Android +系统如何启动 Activity(例如,Activity 应属于哪个 +任务 +),以及启动之后如何处理(例如,它是否属于最近的 Activity 列表)。 + +

如需了解详细信息,请参阅 {@link android.content.Intent#setFlags setFlags()} 方法。

+
+ +
+ + + + +

显式 Intent 示例

+ +

显式 + Intent 是指用于启动某个特定应用组件(例如,应用中的某个特定 Activity 或服务)的 Intent。要创建显式 Intent,请为 +{@link android.content.Intent} 对象定义组件名称。Intent +的所有其他属性均为可选属性。

+ +

例如,如果在应用中构建了一个名为 +{@code DownloadService}、旨在从 Web 中下载文件的服务,则可使用以下代码启动该服务:

+ +
+// Executed in an Activity, so 'this' is the {@link android.content.Context}
+// The fileUrl is a string URL, such as "http://www.example.com/image.png"
+Intent downloadIntent = new Intent(this, DownloadService.class);
+downloadIntent.setData({@link android.net.Uri#parse Uri.parse}(fileUrl));
+startService(downloadIntent);
+
+ +

{@link android.content.Intent#Intent(Context,Class)} +构造函数分别为应用和组件提供 {@link android.content.Context} 和 +{@link java.lang.Class} 对象。因此,此 + Intent 将显式启动该应用中的 {@code DownloadService} 类。

+ +

如需了解有关构建和启动服务的详细信息,请参阅服务指南。 +

+ + + + +

隐式 Intent 示例

+ +

隐式 + Intent 指定能够在可以执行相应操作的设备上调用任何应用的操作。如果您的应用无法执行该操作而其他应用可以,且您希望用户选取要使用的应用,则使用隐式 + Intent 非常有用。

+ +

例如,如果您希望用户与他人共享您的内容,请使用 +{@link android.content.Intent#ACTION_SEND} +操作创建 Intent,并添加指定共享内容的 Extra。使用该 + Intent 调用 +{@link android.content.Context#startActivity startActivity()} 时,用户可以选取共享内容所使用的应用。

+ +

警告:用户可能没有任何应用处理您发送到 +{@link android.content.Context#startActivity +startActivity()} 的隐式 Intent。如果出现这种情况,则调用将会失败,且应用会崩溃。要验证 Activity 是否会接收 + Intent,请对 {@link android.content.Intent} 对象调用 {@link android.content.Intent#resolveActivity +resolveActivity()}。如果结果为非空,则至少有一个应用能够处理该 + Intent,且可以安全调用 +{@link android.content.Context#startActivity startActivity()}。如果结果为空,则不应使用该 + Intent。如有可能,您应禁用发出该 + Intent 的功能。

+ + +
+// Create the text message with a string
+Intent sendIntent = new Intent();
+sendIntent.setAction(Intent.ACTION_SEND);
+sendIntent.putExtra(Intent.EXTRA_TEXT, textMessage);
+sendIntent.setType("text/plain");
+
+// Verify that the intent will resolve to an activity
+if (sendIntent.resolveActivity(getPackageManager()) != null) {
+    startActivity(sendIntent);
+}
+
+ +

注意:在这种情况下,系统并没有使用 URI,但已声明 + Intent 的数据类型,用于指定 Extra 携带的内容。

+ + +

调用 {@link android.content.Context#startActivity startActivity()} +时,系统将检查已安装的所有应用,确定哪些应用能够处理这种 Intent(即:含 {@link android.content.Intent#ACTION_SEND} +操作并携带“文本/纯”数据的 + Intent )。如果只有一个应用能够处理,则该应用将立即打开并提供给 + Intent。如果多个 Activity 接受 + Intent,则系统将显示一个对话框,使用户能够选取要使用的应用。

+ + +
+ +

图 2. 选择器对话框。

+
+ +

强制使用应用选择器

+ +

如果有多个应用响应隐式 + Intent,则用户可以选择要使用的应用,并将其设置为该操作的默认选项。 +如果用户可能希望今后一直使用相同的应用执行某项操作(例如,打开网页时,用户往往倾向于仅使用一种 +Web +浏览器),则这一点十分有用。

+ +

但是,如果多个应用可以响应 + Intent,且用户可能希望每次使用不同的应用,则应采用显式方式显示选择器对话框。选择器对话框要求用户选择每次操作要使用的应用(用户无法为该操作选择默认应用)。 + +例如,当应用使用 {@link +android.content.Intent#ACTION_SEND} +操作执行“共享”时,用户根据目前的状况可能需要使用另一不同的应用,因此应当始终使用选择器对话框,如图 2 中所示。

+ + + + +

要显示选择器,请使用 {@link +android.content.Intent#createChooser createChooser()} 创建 {@link android.content.Intent},并将其传递给 {@link +android.app.Activity#startActivity startActivity()}。例如:

+ +
+Intent sendIntent = new Intent(Intent.ACTION_SEND);
+...
+
+// Always use string resources for UI text.
+// This says something like "Share this photo with"
+String title = getResources().getString(R.string.chooser_title);
+// Create intent to show the chooser dialog
+Intent chooser = Intent.createChooser(sendIntent, title);
+
+// Verify the original intent will resolve to at least one activity
+if (sendIntent.resolveActivity(getPackageManager()) != null) {
+    startActivity(chooser);
+}
+
+ +

这将显示一个对话框,其中包含响应传递给 {@link +android.content.Intent#createChooser createChooser()} +方法的 Intent 的应用列表,并使用提供的文本作为对话框标题。

+ + + + + + + + + +

接收隐式 Intent

+ +

要公布应用可以接收哪些隐式 + Intent,请在清单文件中使用 {@code <intent-filter>} +元素为每个应用组件声明一个或多个 Intent 过滤器。每个 + Intent 过滤器均根据 Intent 的操作、数据和类别指定自身接受的 + Intent 类型。仅当隐式 + Intent 可以通过 Intent 过滤器之一传递时,系统才会将该 Intent 传递给应用组件。

+ +

注意:显式 + Intent 始终会传递给其目标,无论组件声明的 Intent 过滤器如何均是如此。

+ +

应用组件应当为自身可执行的每个独特作业声明单独的过滤器。例如,图像库应用中的一个 Activity 可能会有两个过滤器,分别用于查看图像和编辑图像。 + +当 Activity 启动时,它将检查 +{@link android.content.Intent} 并根据 +{@link android.content.Intent} 中的信息决定具体的行为(例如,是否显示编辑器控件)。

+ +

每个 + Intent 过滤器均由应用清单文件中的 {@code <intent-filter>} +元素定义,并嵌套在相应的应用组件(例如,{@code <activity>} +元素)中。在 {@code <intent-filter>} +内部,您可以使用以下三个元素中的一个或多个指定要接受的 + Intent 类型:

+ +
+
{@code <action>}
+
在 {@code name} 属性中,声明接受的 Intent 操作。该值必须是操作的文本字符串值,而不是类常量。 +
+
{@code <data>}
+
使用一个或多个指定 +数据 +URI(schemehostportpath 等)各个方面和 MIME 类型的属性,声明接受的数据类型。
+
{@code <category>}
+
在 {@code name} 属性中,声明接受的 Intent 类别。该值必须是操作的文本字符串值,而不是类常量。 + + +

注意:为了接收隐式 + Intent,必须将 +{@link android.content.Intent#CATEGORY_DEFAULT} 类别包括在 Intent 过滤器中。方法 +{@link android.app.Activity#startActivity startActivity()} 和 +{@link android.app.Activity#startActivityForResult startActivityForResult()} 将按照已申明 {@link android.content.Intent#CATEGORY_DEFAULT} 类别的方式处理所有 + Intent。 + 如果未在 Intent 过滤器中声明此类别,则隐式 + Intent 不会解析为您的 Activity。

+
+
+ +

例如,以下是一个使用 Intent 过滤器进行的 Activity 声明,当数据类型为文本时,系统将接收 +{@link android.content.Intent#ACTION_SEND} Intent :

+ +
+<activity android:name="ShareActivity">
+    <intent-filter>
+        <action android:name="android.intent.action.SEND"/>
+        <category android:name="android.intent.category.DEFAULT"/>
+        <data android:mimeType="text/plain"/>
+    </intent-filter>
+</activity>
+
+ +

您可以创建一个包括多个 +{@code <action>}{@code <data>} +或 +{@code <category>} +实例的过滤器。创建时,仅需确定组件能够处理这些过滤器元素的任何及所有组合即可。 +

+ +

如需仅以操作、数据和类别类型的特定组合来处理多种 Intent,则需创建多个 + Intent 过滤器。

+ + + + +

系统通过将 + Intent 与所有这三个元素进行比较,根据过滤器测试隐式 Intent。隐式 Intent 若要传递给组件,必须通过所有这三项测试。如果 + Intent 甚至无法匹配其中任何一项测试,则 Android +系统不会将其传递给组件。但是,由于一个组件可能有多个 + Intent +过滤器,因此未能通过某一组件过滤器的 Intent 可能会通过另一过滤器。如需了解有关系统如何解析 Intent 的详细信息,请参阅下文的 + Intent 解析部分。

+ +

警告:为了避免无意中运行不同应用的 +{@link android.app.Service},请始终使用显式 Intent 启动您自己的服务,且不必为该服务声明 + Intent 过滤器。

+ +

注意: +对于所有 Activity,您必须在清单文件中声明 + Intent 过滤器。但是,广播接收器的过滤器可以通过调用 +{@link android.content.Context#registerReceiver(BroadcastReceiver, IntentFilter, String, +Handler) registerReceiver()} 动态注册。稍后,您可以使用 {@link +android.content.Context#unregisterReceiver unregisterReceiver()} 注销该接收器。这样一来,应用便可仅在应用运行时的某一指定时间段内侦听特定的广播。 + +

+ + + + + + + +

过滤器示例

+ +

为了更好地了解一些 + Intent 过滤器的行为,我们一起来看看从社交共享应用的清单文件中截取的以下片段。

+ +
+<activity android:name="MainActivity">
+    <!-- This activity is the main entry, should appear in app launcher -->
+    <intent-filter>
+        <action android:name="android.intent.action.MAIN" />
+        <category android:name="android.intent.category.LAUNCHER" />
+    </intent-filter>
+</activity>
+
+<activity android:name="ShareActivity">
+    <!-- This activity handles "SEND" actions with text data -->
+    <intent-filter>
+        <action android:name="android.intent.action.SEND"/>
+        <category android:name="android.intent.category.DEFAULT"/>
+        <data android:mimeType="text/plain"/>
+    </intent-filter>
+    <!-- This activity also handles "SEND" and "SEND_MULTIPLE" with media data -->
+    <intent-filter>
+        <action android:name="android.intent.action.SEND"/>
+        <action android:name="android.intent.action.SEND_MULTIPLE"/>
+        <category android:name="android.intent.category.DEFAULT"/>
+        <data android:mimeType="application/vnd.google.panorama360+jpg"/>
+        <data android:mimeType="image/*"/>
+        <data android:mimeType="video/*"/>
+    </intent-filter>
+</activity>
+
+ +

第一个 Activity {@code MainActivity} +是应用的主要入口点。当用户最初使用启动器图标启动应用时,该 Activity 将打开:

+
    +
  • {@link android.content.Intent#ACTION_MAIN} 操作指示这是主要入口点,且不要求输入任何 + Intent 数据。
  • +
  • {@link android.content.Intent#CATEGORY_LAUNCHER} +类别指示此 Activity 的图标应放入系统的应用启动器。如果 +{@code <activity>} +元素未使用 {@code icon} 指定图标,则系统将使用 {@code <application>} 元素中的图标。
  • +
+

这两个元素必须配对使用,Activity 才会显示在应用启动器中。

+ +

第二个 Activity {@code ShareActivity} +旨在便于共享文本和媒体内容。尽管用户可以通过从 {@code MainActivity} 导航进入此 Activity,但也可以从发出隐式 + Intent(与两个 + Intent 过滤器之一匹配)的另一应用中直接进入 {@code ShareActivity}。

+ +

注意:MIME 类型 +{@code +application/vnd.google.panorama360+jpg} 是一个指定全景照片的特殊 +数据类型,您可以使用 Google +全景 API 对其进行处理。

+ + + + + + + + + + + + + +

使用待定 Intent

+ +

{@link android.app.PendingIntent} 对象是 {@link +android.content.Intent} 对象的包装器。{@link android.app.PendingIntent} +的主要目的是授权外部应用使用包含的 +{@link android.content.Intent},就像是它从您应用本身的进程中执行的一样。 +

+ +

待定 Intent 的主要用例包括:

+
    +
  • 声明用户使用您的通知执行操作时所要执行的 + Intent(Android 系统的 {@link android.app.NotificationManager} +执行 {@link android.content.Intent})。 +
  • 声明用户使用您的 +应用小工具执行操作时要执行的 + Intent(主屏幕应用执行 {@link android.content.Intent})。 +
  • 声明未来某一特定时间要执行的 Intent(Android +系统的 {@link android.app.AlarmManager} 执行 {@link android.content.Intent})。 +
+ +

由于每个 {@link android.content.Intent} +对象均设计为由特定类型的应用组件进行处理({@link android.app.Activity}、{@link android.app.Service} 或 +{@link android.content.BroadcastReceiver}),因此还必须基于相同的考虑因素创建 +{@link android.app.PendingIntent}。使用待定 + Intent 时,应用不会使用调用(如 {@link android.content.Context#startActivity +startActivity()})执行该 Intent。相反,通过调用相应的创建器方法创建 +{@link android.app.PendingIntent} 时,您必须声明所需的组件类型:

+ +
    +
  • {@link android.app.PendingIntent#getActivity PendingIntent.getActivity()},适用于启动 +{@link android.app.Activity} 的 {@link android.content.Intent}。
  • +
  • {@link android.app.PendingIntent#getService PendingIntent.getService()},适用于启动 +{@link android.app.Service} 的 {@link android.content.Intent}。
  • +
  • {@link android.app.PendingIntent#getBroadcast PendingIntent.getBroadcast()},适用于启动 +{@link android.content.BroadcastReceiver} 的 {@link android.content.Intent}。
  • +
+ +

除非您的应用正在从其他应用中接收待定 + Intent,否则上述用于创建 {@link android.app.PendingIntent} +的方法可能是您所需的唯一 {@link android.app.PendingIntent} 方法。

+ +

每种方法均会提取当前的应用 {@link android.content.Context}、您要包装的 +{@link android.content.Intent} 以及一个或多个指定应如何使用该 Intent 的标志(例如,是否可以多次使用该 + Intent)。

+ +

如需了解有关使用待定 + Intent 的详细信息,请参阅通知 +和应用小工具 API 指南等手册中每个相应用例的相关文档。

+ + + + + + + +

Intent 解析

+ + +

当系统收到隐式 + Intent 以启动 Activity 时,它根据以下三个方面将该 Intent 与 Intent 过滤器进行比较,搜索该 Intent 的最佳 Activity:

+ +
    +
  • Intent 操作 +
  • Intent 数据(URI 和数据类型) +
  • Intent 类别 +
+ +

下文根据如何在应用的清单文件中声明 + Intent 过滤器,描述 Intent 如何与相应的组件匹配。

+ + +

操作测试

+ +

要指定接受的 Intent 操作, Intent 过滤器既可以不声明任何 +{@code +<action>} 元素,也可以声明多个此类元素。例如:

+ +
+<intent-filter>
+    <action android:name="android.intent.action.EDIT" />
+    <action android:name="android.intent.action.VIEW" />
+    ...
+</intent-filter>
+
+ +

要通过此过滤器,您在 {@link android.content.Intent} +中指定的操作必须与过滤器中列出的某一操作匹配。

+ +

如果该过滤器未列出任何操作,则 + Intent 没有任何匹配项,因此所有 Intent 均无法通过测试。但是,如果 +{@link android.content.Intent} +未指定操作,则会通过测试(只要过滤器至少包含一个操作)。

+ + + +

类别测试

+ +

要指定接受的 Intent 类别, Intent 过滤器既可以不声明任何 +{@code +<category>} 元素,也可以声明多个此类元素。例如:

+ +
+<intent-filter>
+    <category android:name="android.intent.category.DEFAULT" />
+    <category android:name="android.intent.category.BROWSABLE" />
+    ...
+</intent-filter>
+
+ +

若要 Intent 通过类别测试,则 {@link android.content.Intent} +中的每个类别均必须与过滤器中的类别匹配。反之则未必然,Intent +过滤器声明的类别可以超出 {@link android.content.Intent} +中指定的数量,且 {@link android.content.Intent} 仍会通过测试。因此,不含类别的 + Intent 应当始终会通过此测试,无论过滤器中声明何种类别均是如此。

+ +

注意: +Android +会自动将 {@link android.content.Intent#CATEGORY_DEFAULT} +类别应用于传递给 {@link +android.content.Context#startActivity startActivity()} 和 {@link +android.app.Activity#startActivityForResult startActivityForResult()} 的所有隐式 + Intent。因此,如需 Activity 接收隐式 Intent,则必须将 {@code "android.intent.category.DEFAULT"} 的类别包括在其 Intent 过滤器中(如上文的 +{@code <intent-filter>} 示例所示)。

+ + + +

数据测试

+ +

要指定接受的 Intent 数据, Intent 过滤器既可以不声明任何 +{@code +<data>} 元素,也可以声明多个此类元素。例如:

+ +
+<intent-filter>
+    <data android:mimeType="video/mpeg" android:scheme="http" ... />
+    <data android:mimeType="audio/mpeg" android:scheme="http" ... />
+    ...
+</intent-filter>
+
+ +

每个 <data> +元素均可指定 URI 结构和数据类型(MIME 介质类型)。URI 的每个部分均包含单独的 +{@code scheme}、{@code host}、{@code port} +和 {@code path} 属性: +

+ +

{@code <scheme>://<host>:<port>/<path>}

+ +

+例如: +

+ +

{@code content://com.example.project:200/folder/subfolder/etc}

+ +

在此 URI 中,架构是 {@code content},主机是 +{@code com.example.project},端口是 {@code 200},路径是 {@code folder/subfolder/etc}。 +

+ +

{@code <data>} +元素中,上述每个属性均为可选,但存在线性依赖关系:

+
    +
  • 如果未指定架构,则会忽略主机。
  • +
  • 如果未指定主机,则会忽略端口。
  • +
  • 如果未指定架构和主机,则会忽略路径。
  • +
+ +

将 + Intent 中的 URI 与过滤器中的 URI 规范进行比较时,它仅与过滤器中包含的部分 URI 进行比较。例如:

+
    +
  • 如果过滤器仅指定架构,则具有该架构的所有 +URI 均与该过滤器匹配。
  • +
  • 如果过滤器指定架构和权限、但未指定路径,则具有相同架构和权限的所有 URI +都会通过过滤器,无论其路径如何均是如此。
  • +
  • 如果过滤器指定架构、权限和路径,则仅具有相同架构、权限和路径 +的 URI 才会通过过滤器。
  • +
+ +

注意:路径规范可以包含星号通配符 +(*),因此仅需部分匹配路径名即可。

+ +

数据测试会将 Intent 中的 URI 和 MIME +类型与过滤器中指定的 URI 和 MIME 类型进行比较。规则如下: +

+ +
    +
  1. 仅当过滤器未指定任何 URI 或 MIME 类型时,不含 URI 和 MIME 类型的 + Intent 才会通过测试。
  2. + +
  3. 对于包含 URI、但不含 MIME 类型(既未显式声明,也无法通过 +URI 推断得出)的 Intent,仅当其 URI +与过滤器的 URI 格式匹配、且过滤器同样未指定 MIME 类型时,才会通过测试。
  4. + +
  5. 仅当过滤器列出相同的 MIME 类型且未指定 URI 格式时,包含 MIME +类型、但不含 URI 的 Intent 才会通过测试。
  6. + +
  7. 仅当 MIME 类型与过滤器中列出的类型匹配时,包含 +URI 和 MIME 类型(通过显式声明,或可以通过 +URI 推断得出)的 Intent 才会通过测试的 MIME 类型部分。如果 Intent 的 URI 与过滤器中的 +URI 匹配,或者如果 Intent 具有 {@code content:} +或 {@code file:} URI +且过滤器未指定 URI,则 Intent 会通过测试的 URI 部分。换而言之,如果过滤器仅列出 MIME 类型,则假定组件支持 +{@code content:} 和 {@code file:} 数据。

  8. +
+ +

+最后一条规则,即规则 +(d),反映了期望组件能够从文件中或内容提供商处获得本地数据。因此,其过滤器可以仅列出数据类型,而不必显式命名 +{@code content:} 和 +{@code file:} +架构。这是一个典型的案例。例如,下文中的 {@code <data>} +元素向 +Android 指出,组件可从内容提供商处获得并显示图像数据: +

+ +
+<intent-filter>
+    <data android:mimeType="image/*" />
+    ...
+</intent-filter>
+ +

+由于大部分可用数据均由内容提供商分发,因此指定数据类型(而非 +URI)的过滤器也许最为常见。 +

+ +

+另一常见的配置是具有架构和数据类型的过滤器。例如,下文中的 {@code <data>} +元素向 +Android +指出,组件可从网络中检索视频数据以执行操作: +

+ +
+<intent-filter>
+    <data android:scheme="http" android:type="video/*" />
+    ...
+</intent-filter>
+ + + +

Intent 匹配

+ +

通过 + Intent +过滤器匹配 Intent,这不仅有助于发现要激活的目标组件,还有助于发现设备上组件集的相关信息。例如,主页应用通过使用指定 +{@link android.content.Intent#ACTION_MAIN} 操作和 +{@link android.content.Intent#CATEGORY_LAUNCHER} 类别的 + Intent 过滤器查找所有 Activity,以此填充应用启动器。

+ +

您的应用可以采用类似的方式使用 + Intent 匹配。{@link android.content.pm.PackageManager} 提供了一整套 {@code query...()} +方法来返回所有能够接受特定 Intent +的组件。此外,它还提供了一系列类似的 {@code resolve...()} 方法来确定响应 Intent +的最佳组件。例如,{@link android.content.pm.PackageManager#queryIntentActivities +queryIntentActivities()} +将返回能够执行那些作为参数传递的 + Intent 的所有 Activity 列表,而 +{@link +android.content.pm.PackageManager#queryIntentServices +queryIntentServices()} +则可返回类似的服务列表。这两种方法均不会激活组件,而只是列出能够响应的组件。对于广播接收器,有一种类似的方法: +{@link android.content.pm.PackageManager#queryBroadcastReceivers +queryBroadcastReceivers()}。 +

+ + + + diff --git a/docs/html-intl/intl/zh-cn/guide/components/loaders.jd b/docs/html-intl/intl/zh-cn/guide/components/loaders.jd new file mode 100644 index 0000000000000000000000000000000000000000..d8427b0cd109e2e75449fe82c65fade39677f6ef --- /dev/null +++ b/docs/html-intl/intl/zh-cn/guide/components/loaders.jd @@ -0,0 +1,494 @@ +page.title=加载器 +parent.title=Activity +parent.link=activities.html +@jd:body +
+
+

本文内容

+
    +
  1. Loader API 摘要
  2. +
  3. 在应用中使用加载器 +
      +
    1. +
    2. 启动加载器
    3. +
    4. 重启加载器
    5. +
    6. 使用 LoaderManager 回调
    7. +
    +
  4. +
  5. 示例 +
      +
    1. 更多示例
    2. +
    +
  6. +
+ +

关键类

+
    +
  1. {@link android.app.LoaderManager}
  2. +
  3. {@link android.content.Loader}
  4. + +
+ +

相关示例

+
    +
  1. +LoaderCursor
  2. +
  3. +LoaderThrottle
  4. +
+
+
+ +

Android 3.0 中引入了加载器,支持轻松在 Activity 或片段中异步加载数据。 +加载器具有以下特征:

+
    +
  • 可用于每个 {@link android.app.Activity} 和 {@link +android.app.Fragment}。
  • +
  • 支持异步加载数据。
  • +
  • 监控其数据源并在内容变化时传递新结果。 +
  • +
  • 在某一配置更改后重建加载器时,会自动重新连接上一个加载器的 Cursor。 +因此,它们无需重新查询其数据。 +
  • +
+ +

Loader API 摘要

+ +

在应用中使用加载器时,可能会涉及到多个类和接口。 +下表汇总了这些类和接口:

+ + + + + + + + + + + + + + + + + + + + + + + + + + +
类/接口描述
{@link android.app.LoaderManager}一种与 {@link android.app.Activity} 或 {@link android.app.Fragment} 相关联的的抽象类,用于管理一个或多个 {@link +android.content.Loader} 实例。 +这有助于应用管理与 +{@link android.app.Activity} +或 {@link android.app.Fragment} 生命周期相关联的、运行时间较长的操作。它最常见的用法是与 +{@link android.content.CursorLoader} +一起使用,但应用可自由写入其自己的加载器,用于加载其他类型的数据。 +
+
+ 每个 Activity 或片段中只有一个 {@link android.app.LoaderManager}。但一个 {@link android.app.LoaderManager} +可以有多个加载器。
{@link android.app.LoaderManager.LoaderCallbacks}一种回调接口,用于客户端与 {@link +android.app.LoaderManager} 进行交互。例如,您可使用 {@link +android.app.LoaderManager.LoaderCallbacks#onCreateLoader onCreateLoader()} +回调方法创建新的加载器。
{@link android.content.Loader}一种执行异步数据加载的抽象类。这是加载器的基类。 +您通常会使用 {@link +android.content.CursorLoader},但您也可以实现自己的子类。加载器处于Activity状态时,应监控其数据源并在内容变化时传递新结果。 + +
{@link android.content.AsyncTaskLoader}提供 {@link android.os.AsyncTask} 来执行工作的抽象加载器。
{@link android.content.CursorLoader}{@link android.content.AsyncTaskLoader} +的子类,它将查询 {@link android.content.ContentResolver} 并返回一个 {@link +android.database.Cursor}。此类采用标准方式为查询 Cursor 实现 {@link +android.content.Loader} +协议。它是以 {@link android.content.AsyncTaskLoader} +为基础而构建,在后台线程中执行 Cursor 查询,因此不会阻塞应用的 UI。使用此加载器是从 +{@link +android.content.ContentProvider} +异步加载数据的最佳方式,而不用通过片段或 Activity 的 API 来执行托管查询。
+ +

上表中的类和接口是您在应用中用于实现加载器的基本组件。 +并非您创建的每个加载器都要用到上述所有类和接口。但是,为了初始化加载器以及实现一个 {@link android.content.Loader} +类(如 {@link +android.content.CursorLoader}),您始终需要要引用 +{@link +android.app.LoaderManager}。下文将为您展示如何在应用中使用这些类和接口。 +

+ +

在应用中使用加载器

+

此部分描述如何在 Android 应用中使用加载器。使用加载器的应用通常包括: +

+
    +
  • {@link android.app.Activity} 或 {@link android.app.Fragment}。
  • +
  • {@link android.app.LoaderManager} 的实例。
  • +
  • 一个 {@link android.content.CursorLoader},用于加载由 {@link +android.content.ContentProvider} 支持的数据。您也可以实现自己的 +{@link android.content.Loader} +或 {@link android.content.AsyncTaskLoader} 子类,从其他源中加载数据。
  • +
  • 一个 +{@link android.app.LoaderManager.LoaderCallbacks} +实现。您可以使用它来创建新加载器,并管理对现有加载器的引用。
  • +
  • 一种显示加载器数据的方法,如 {@link +android.widget.SimpleCursorAdapter}。
  • +
  • 使用 +{@link android.content.CursorLoader} 时的数据源,如 {@link android.content.ContentProvider}。
  • +
+

启动加载器

+ +

{@link android.app.LoaderManager} 可在 {@link android.app.Activity} 或 +{@link android.app.Fragment} 内管理一个或多个 {@link +android.content.Loader} 实例。每个 Activity 或片段只有一个 {@link +android.app.LoaderManager}。

+ +

通常,您会使用 Activity 的 {@link +android.app.Activity#onCreate onCreate()} 方法或片段的 +{@link android.app.Fragment#onActivityCreated onActivityCreated()} +方法初始化 {@link android.content.Loader}。您执行操作如下: +

+ +
// Prepare the loader.  Either re-connect with an existing one,
+// or start a new one.
+getLoaderManager().initLoader(0, null, this);
+ +

{@link android.app.LoaderManager#initLoader initLoader()} +方法采用以下参数:

+
    +
  • 用于标识加载器的唯一 ID。在此示例中,ID 为 0。
  • +
  • 在构建时提供给加载器的可选参数(在此示例中为 null +)。
  • + +
  • {@link android.app.LoaderManager.LoaderCallbacks} 实现, +{@link android.app.LoaderManager} 将调用此实现来报告加载器事件。在此示例中,本地类实现 +{@link +android.app.LoaderManager.LoaderCallbacks} +接口,因此它会将引用 {@code this} 传递给自己。
  • +
+

{@link android.app.LoaderManager#initLoader initLoader()} +调用确保加载器已初始化且处于Activity状态。这可能会出现两种结果:

+
    +
  • 如果 ID +指定的加载器已存在,则将重复使用上次创建的加载器。
  • +
  • 如果 +ID 指定的加载器不存在,则 {@link android.app.LoaderManager#initLoader initLoader()} 将触发 +{@link android.app.LoaderManager.LoaderCallbacks} +方法 {@link android.app.LoaderManager.LoaderCallbacks#onCreateLoader onCreateLoader()}。在此方法中,您可以实现代码以实例化并返回新加载器。有关详细介绍,请参阅 onCreateLoader 部分。 +
  • +
+

无论何种情况,给定的 +{@link android.app.LoaderManager.LoaderCallbacks} +实现均与加载器相关联,且将在加载器状态变化时调用。如果在调用时,调用程序处于启动状态,且请求的加载器已存在并生成了数据,则系统将立即调用 +{@link +android.app +LoaderManager.LoaderCallbacks#onLoadFinished onLoadFinished()} +(在 {@link android.app.LoaderManager#initLoader initLoader()} +期间),因此您必须为此做好准备。有关此回调的详细介绍,请参阅 +onLoadFinished

+ +

请注意,{@link android.app.LoaderManager#initLoader initLoader()} +方法将返回已创建的 +{@link android.content.Loader},但您不必捕获其引用。{@link android.app.LoaderManager} +将自动管理加载器的生命周期。{@link android.app.LoaderManager} +将根据需要启动和停止加载,并维护加载器的状态及其相关内容。 +这意味着您很少直接与加载器进行交互(有关使用加载器方法调整加载器行为的示例,请参阅 +LoaderThrottle +示例)。当特殊事件发生时,您通常会使用 +{@link +android.app.LoaderManager.LoaderCallbacks} +方法干预加载进程。有关此主题的详细介绍,请参阅使用 LoaderManager 回调

+ +

重启加载器

+ +

当您使用 +{@link android.app.LoaderManager#initLoader initLoader()} +时(如上所述),它将使用含有指定 ID 的现有加载器(如有)。如果没有,则它会创建一个。但有时,您想放弃这些旧数据并重新开始。 +

+ +

要放弃旧数据,请使用 {@link +android.app.LoaderManager#restartLoader restartLoader()}。例如,当用户的查询更改时,此 {@link android.widget.SearchView.OnQueryTextListener} +实现将重启加载器。 +加载器需要重启,以便它能够使用修订后的搜索筛选器执行新查询: +

+ +
+public boolean onQueryTextChanged(String newText) {
+    // Called when the action bar search text has changed.  Update
+    // the search filter, and restart the loader to do a new query
+    // with this filter.
+    mCurFilter = !TextUtils.isEmpty(newText) ? newText : null;
+    getLoaderManager().restartLoader(0, null, this);
+    return true;
+}
+ +

使用 LoaderManager 回调

+ +

{@link android.app.LoaderManager.LoaderCallbacks} 是一个支持客户端与 +{@link android.app.LoaderManager} 交互的回调接口。

+

加载器(特别是 +{@link android.content.CursorLoader})在停止运行后,仍需保留其数据。这样,应用即可保留 Activity 或片段的 +{@link android.app.Activity#onStop +onStop()} 和 +{@link android.app.Activity#onStart onStart()} +方法中的数据。当用户返回应用时,无需等待它重新加载这些数据。您可使用 +{@link android.app.LoaderManager.LoaderCallbacks} +方法了解何时创建新加载器,并告知应用何时停止使用加载器的数据。

+ +

{@link android.app.LoaderManager.LoaderCallbacks} +包括以下方法:

+
    +
  • {@link android.app.LoaderManager.LoaderCallbacks#onCreateLoader onCreateLoader()}:针对指定的 +ID 进行实例化并返回新的 {@link android.content.Loader} +
+
    +
  • {@link android.app.LoaderManager.LoaderCallbacks#onLoadFinished onLoadFinished()} +:将在先前创建的加载器完成加载时调用 +
+
    +
  • {@link android.app.LoaderManager.LoaderCallbacks#onLoaderReset onLoaderReset()}: +将在先前创建的加载器重置且其数据因此不可用时调用 + +
  • +
+

下文更详细地描述了这些方法。

+ +

onCreateLoader

+ +

当您尝试访问加载器时(例如,通过 +{@link +android.app.LoaderManager#initLoader initLoader()}),该方法将检查是否已存在由该 ID 指定的加载器。如果没有,它将触发 {@link +android.app.LoaderManager.LoaderCallbacks} 方法 {@link +android.app.LoaderManager.LoaderCallbacks#onCreateLoader onCreateLoader()}。在此方法中,您可以创建新加载器。 +通常,这将是 {@link +android.content.CursorLoader},但您也可以实现自己的 {@link +android.content.Loader} 子类。

+ +

在此示例中,{@link +android.app.LoaderManager.LoaderCallbacks#onCreateLoader onCreateLoader()} +回调方法创建了 {@link android.content.CursorLoader}。您必须使用其构造函数方法来构建 +{@link android.content.CursorLoader}。该方法需要对 +{@link +android.content.ContentProvider} 执行查询时所需的一系列完整信息。具体地说,它需要:

+
    +
  • uri:用于检索内容的 URI
  • +
  • projection:要返回的列的列表。传递 +null 时,将返回所有列,这样会导致效率低下
  • +
  • selection:一种用于声明要返回哪些行的过滤器,其格式为 +SQL WHERE 子句(WHERE 本身除外)。传递 +null 时,将为指定的 URI 返回所有行
  • +
  • selectionArgs:您可以在 selection 中包含 ?s,它将按照在 selection 中显示的顺序替换为 +selectionArgs +中的值。该值将绑定为字串符
  • +
  • sortOrder:行的排序依据,其格式为 SQL +ORDER BY 子句(ORDER BY 自身除外)。传递 null + 时,将使用默认排序顺序(可能并未排序)
  • +
+

例如:

+
+ // If non-null, this is the current filter the user has provided.
+String mCurFilter;
+...
+public Loader<Cursor> onCreateLoader(int id, Bundle args) {
+    // This is called when a new Loader needs to be created.  This
+    // sample only has one Loader, so we don't care about the ID.
+    // First, pick the base URI to use depending on whether we are
+    // currently filtering.
+    Uri baseUri;
+    if (mCurFilter != null) {
+        baseUri = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI,
+                  Uri.encode(mCurFilter));
+    } else {
+        baseUri = Contacts.CONTENT_URI;
+    }
+
+    // Now create and return a CursorLoader that will take care of
+    // creating a Cursor for the data being displayed.
+    String select = "((" + Contacts.DISPLAY_NAME + " NOTNULL) AND ("
+            + Contacts.HAS_PHONE_NUMBER + "=1) AND ("
+            + Contacts.DISPLAY_NAME + " != '' ))";
+    return new CursorLoader(getActivity(), baseUri,
+            CONTACTS_SUMMARY_PROJECTION, select, null,
+            Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC");
+}
+

onLoadFinished

+ +

当先前创建的加载器完成加载时,将调用此方法。该方法必须在为此加载器提供的最后一个数据释放之前调用。 + +此时,您应移除所有使用的旧数据(因为它们很快会被释放),但不要自行释放这些数据,因为这些数据归其加载器所有,其加载器会处理它们。 + +

+ + +

当加载器发现应用不再使用这些数据时,即会释放它们。 +例如,如果数据是来自 {@link +android.content.CursorLoader} 的一个 Cursor,则您不应手动对其调用 {@link +android.database.Cursor#close close()}。如果 Cursor 放置在 +{@link android.widget.CursorAdapter} 中,则应使用 {@link +android.widget.SimpleCursorAdapter#swapCursor swapCursor()} 方法,使旧 +{@link android.database.Cursor} 不会关闭。例如:

+ +
+// This is the Adapter being used to display the list's data.
SimpleCursorAdapter mAdapter; +... + +public void onLoadFinished(Loader<Cursor> loader, Cursor data) { + // Swap the new cursor in.  (The framework will take care of closing the + // old cursor once we return.) + mAdapter.swapCursor(data); +}
+ +

onLoaderReset

+ +

此方法将在先前创建的加载器重置且其数据因此不可用时调用。 +通过此回调,您可以了解何时将释放数据,因而能够及时移除其引用。 +  

+

此实现调用值为 null +的{@link android.widget.SimpleCursorAdapter#swapCursor swapCursor()}: +

+ +
+// This is the Adapter being used to display the list's data.
+SimpleCursorAdapter mAdapter;
+...
+
+public void onLoaderReset(Loader<Cursor> loader) {
+    // This is called when the last Cursor provided to onLoadFinished()
+    // above is about to be closed.  We need to make sure we are no
+    // longer using it.
+    mAdapter.swapCursor(null);
+}
+ + +

示例

+ +

以下是一个 +{@link +android.app.Fragment} 完整实现示例。它展示了一个 {@link android.widget.ListView},其中包含针对联系人内容提供程序的查询结果。它使用 {@link +android.content.CursorLoader} 管理提供程序的查询。

+ +

应用如需访问用户联系人(正如此示例中所示),其清单文件必须包括权限 +{@link android.Manifest.permission#READ_CONTACTS READ_CONTACTS}。 +

+ +
+public static class CursorLoaderListFragment extends ListFragment
+        implements OnQueryTextListener, LoaderManager.LoaderCallbacks<Cursor> {
+
+    // This is the Adapter being used to display the list's data.
+    SimpleCursorAdapter mAdapter;
+
+    // If non-null, this is the current filter the user has provided.
+    String mCurFilter;
+
+    @Override public void onActivityCreated(Bundle savedInstanceState) {
+        super.onActivityCreated(savedInstanceState);
+
+        // Give some text to display if there is no data.  In a real
+        // application this would come from a resource.
+        setEmptyText("No phone numbers");
+
+        // We have a menu item to show in action bar.
+        setHasOptionsMenu(true);
+
+        // Create an empty adapter we will use to display the loaded data.
+        mAdapter = new SimpleCursorAdapter(getActivity(),
+                android.R.layout.simple_list_item_2, null,
+                new String[] { Contacts.DISPLAY_NAME, Contacts.CONTACT_STATUS },
+                new int[] { android.R.id.text1, android.R.id.text2 }, 0);
+        setListAdapter(mAdapter);
+
+        // Prepare the loader.  Either re-connect with an existing one,
+        // or start a new one.
+        getLoaderManager().initLoader(0, null, this);
+    }
+
+    @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+        // Place an action bar item for searching.
+        MenuItem item = menu.add("Search");
+        item.setIcon(android.R.drawable.ic_menu_search);
+        item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
+        SearchView sv = new SearchView(getActivity());
+        sv.setOnQueryTextListener(this);
+        item.setActionView(sv);
+    }
+
+    public boolean onQueryTextChange(String newText) {
+        // Called when the action bar search text has changed.  Update
+        // the search filter, and restart the loader to do a new query
+        // with this filter.
+        mCurFilter = !TextUtils.isEmpty(newText) ? newText : null;
+        getLoaderManager().restartLoader(0, null, this);
+        return true;
+    }
+
+    @Override public boolean onQueryTextSubmit(String query) {
+        // Don't care about this.
+        return true;
+    }
+
+    @Override public void onListItemClick(ListView l, View v, int position, long id) {
+        // Insert desired behavior here.
+        Log.i("FragmentComplexList", "Item clicked: " + id);
+    }
+
+    // These are the Contacts rows that we will retrieve.
+    static final String[] CONTACTS_SUMMARY_PROJECTION = new String[] {
+        Contacts._ID,
+        Contacts.DISPLAY_NAME,
+        Contacts.CONTACT_STATUS,
+        Contacts.CONTACT_PRESENCE,
+        Contacts.PHOTO_ID,
+        Contacts.LOOKUP_KEY,
+    };
+    public Loader<Cursor> onCreateLoader(int id, Bundle args) {
+        // This is called when a new Loader needs to be created.  This
+        // sample only has one Loader, so we don't care about the ID.
+        // First, pick the base URI to use depending on whether we are
+        // currently filtering.
+        Uri baseUri;
+        if (mCurFilter != null) {
+            baseUri = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI,
+                    Uri.encode(mCurFilter));
+        } else {
+            baseUri = Contacts.CONTENT_URI;
+        }
+
+        // Now create and return a CursorLoader that will take care of
+        // creating a Cursor for the data being displayed.
+        String select = "((" + Contacts.DISPLAY_NAME + " NOTNULL) AND ("
+                + Contacts.HAS_PHONE_NUMBER + "=1) AND ("
+                + Contacts.DISPLAY_NAME + " != '' ))";
+        return new CursorLoader(getActivity(), baseUri,
+                CONTACTS_SUMMARY_PROJECTION, select, null,
+                Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC");
+    }
+
+    public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
+        // Swap the new cursor in.  (The framework will take care of closing the
+        // old cursor once we return.)
+        mAdapter.swapCursor(data);
+    }
+
+    public void onLoaderReset(Loader<Cursor> loader) {
+        // This is called when the last Cursor provided to onLoadFinished()
+        // above is about to be closed.  We need to make sure we are no
+        // longer using it.
+        mAdapter.swapCursor(null);
+    }
+}
+

更多示例

+ +

ApiDemos +中还提供了一些不同的示例,阐述如何使用加载器:

+
    +
  • LoaderCursor:上述代码段的完整版本 + +
  • +
  • LoaderThrottle:此示例显示当数据变化时,如何使用限制来减少内容提供程序的查询次数 +
  • +
+ +

有关下载和安装 SDK +示例的信息,请参阅获取示例

+ diff --git a/docs/html-intl/intl/zh-cn/guide/components/processes-and-threads.jd b/docs/html-intl/intl/zh-cn/guide/components/processes-and-threads.jd new file mode 100644 index 0000000000000000000000000000000000000000..c88ecf4b81e813d5bd63aa5c552172feb9af16a2 --- /dev/null +++ b/docs/html-intl/intl/zh-cn/guide/components/processes-and-threads.jd @@ -0,0 +1,411 @@ +page.title=进程和线程 +page.tags=生命周期,后台 + +@jd:body + +
+
+ +

本文内容

+
    +
  1. 进程 +
      +
    1. 进程生命周期
    2. +
    +
  2. +
  3. 线程 +
      +
    1. 工作线程
    2. +
    3. 线程安全方法
    4. +
    +
  4. +
  5. 进程间通信
  6. +
+ +
+
+ +

当某个应用组件启动且该应用没有运行其他任何组件时,Android +系统会使用单个执行线程为应用启动新的 +Linux 进程。默认情况下,同一应用的所有组件在相同的进程和线程(称为“主”线程)中运行。 +如果某个应用组件启动且该应用已存在进程(因为存在该应用的其他组件),则该组件会在此进程内启动并使用相同的执行线程。 + +但是,您可以安排应用中的其他组件在单独的进程中运行,并为任何进程创建额外的线程。 + +

+ +

本文档介绍进程和线程在 Android 应用中的工作方式。

+ + +

进程

+ +

默认情况下,同一应用的所有组件均在相同的进程中运行,且大多数应用都不会改变这一点。 +但是,如果您发现需要控制某个组件所属的进程,则可在清单文件中执行此操作。 +

+ +

各类组件元素的清单文件条目—{@code +<activity>}{@code +<service>}{@code +<receiver>}{@code +<provider>}—均支持 +{@code android:process} 属性,此属性可以指定该组件应在哪个进程运行。您可以设置此属性,使每个组件均在各自的进程中运行,或者使一些组件共享一个进程,而其他组件则不共享。 +此外,您还可以设置 {@code android:process},使不同应用的组件在相同的进程中运行,但前提是这些应用共享相同的 Linux 用户 ID 并使用相同的证书进行签署。 + + +

+ +

此外,{@code +<application>} 元素还支持 {@code android:process} +属性,以设置适用于所有组件的默认值。

+ +

如果内存不足,而其他为用户提供更紧急服务的进程又需要内存时,Android +可能会决定在某一时刻关闭某一进程。在被终止进程中运行的应用组件也会随之销毁。 +当这些组件需要再次运行时,系统将为它们重启进程。 +

+ +

决定终止哪个进程时,Android +系统将权衡它们对用户的相对重要程度。例如,相对于托管可见 Activity 的进程而言,它更有可能关闭托管屏幕上不再可见的 Activity 进程。 +因此,是否终止某个进程的决定取决于该进程中所运行组件的状态。 +下面,我们介绍决定终止进程所用的规则。 +

+ + +

进程生命周期

+ +

Android 系统将尽量长时间地保持应用进程,但为了新建进程或运行更重要的进程,最终需要清除旧进程来回收内存。 +为了确定保留或终止哪些进程,系统会根据进程中正在运行的组件以及这些组件的状态,将每个进程放入“重要性层次结构”中。 + + +必要时,系统会首先消除重要性最低的进程,然后是重要性略逊的进程,依此类推,以回收系统资源。 + +

+ +

重要性层次结构一共有 5 级。以下列表按照重要程度列出了各类进程(第一个进程最重要,将是最后一个被终止的进程): + +

+ +
    +
  1. 前台进程 +

    用户当前操作所必需的进程。如果一个进程满足以下任一条件,即视为前台进程: +

    + +
      +
    • 托管用户正在交互的 {@link android.app.Activity}(已调用 {@link +android.app.Activity} 的 {@link android.app.Activity#onResume onResume()} +方法)
    • + +
    • 托管某个 {@link android.app.Service},后者绑定到用户正在交互的 Activity +
    • + +
    • 托管正在“前台”运行的 +{@link android.app.Service}(服务已调用 {@link android.app.Service#startForeground startForeground()}) + +
    • 托管正执行一个生命周期回调的 +{@link android.app.Service}({@link android.app.Service#onCreate onCreate()}、{@link android.app.Service#onStart +onStart()} 或 {@link android.app.Service#onDestroy onDestroy()})
    • + +
    • 托管正执行其 {@link + android.content.BroadcastReceiver#onReceive onReceive()} 方法的 {@link android.content.BroadcastReceiver}
    • +
    + +

    通常,在任意给定时间前台进程都为数不多。只有在内在不足以支持它们同时继续运行这一万不得已的情况下,系统才会终止它们。 +此时,设备往往已达到内存分页状态,因此需要终止一些前台进程来确保用户界面正常响应。 + +

  2. + +
  3. 可见进程 +

    没有任何前台组件、但仍会影响用户在屏幕上所见内容的进程。 +如果一个进程满足以下任一条件,即视为可见进程: +

    + +
      +
    • 托管不在前台、但仍对用户可见的 +{@link android.app.Activity}(已调用其 {@link android.app.Activity#onPause onPause()} 方法)。例如,如果前台 Activity 启动了一个对话框,允许在其后显示上一 Activity,则有可能会发生这种情况 + +
    • + +
    • 托管绑定到可见(或前台)Activity 的 +{@link android.app.Service}
    • +
    + +

    可见进程被视为是极其重要的进程,除非为了维持所有前台进程同时运行而必须终止,否则系统不会终止这些进程。 +

    +
  4. + +
  5. 服务进程 +

    正在运行已使用 {@link +android.content.Context#startService startService()} +方法启动的服务且不属于上述两个更高类别进程的进程。尽管服务进程与用户所见内容没有直接关联,但是它们通常在执行一些用户关心的操作(例如,在后台播放音乐或从网络下载数据)。因此,除非内存不足以维持所有前台进程和可见进程同时运行,否则系统会让服务进程保持运行状态。 + + +

    +
  6. + +
  7. 后台进程 +

    包含目前对用户不可见的 Activity 的进程(已调用 Activity 的 +{@link android.app.Activity#onStop onStop()} 方法)。这些进程对用户体验没有直接影响,系统可能随时终止它们,以回收内存供前台进程、可见进程或服务进程使用。 + + +通常会有很多后台进程在运行,因此它们会保存在 +LRU +(最近最少使用)列表中,以确保包含用户最近查看的 Activity 的进程最后一个被终止。如果某个 Activity 正确实现了生命周期方法,并保存了其当前状态,则终止其进程不会对用户体验产生明显影响,因为当用户导航回该 Activity 时,Activity 会恢复其所有可见状态。 + + +有关保存和恢复状态的信息,请参阅Activity文档。 +

    +
  8. + +
  9. 空进程 +

    不含任何活动应用组件的进程。保留这种进程的的唯一目的是用作缓存,以缩短下次在其中运行组件所需的启动时间。 + +为使总体系统资源在进程缓存和底层内核缓存之间保持平衡,系统往往会终止这些进程。 +

    +
  10. +
+ + +

根据进程中当前活动组件的重要程度,Android +会将进程评定为它可能达到的最高级别。例如,如果某进程托管着服务和可见 Activity,则会将此进程评定为可见进程,而不是服务进程。 +

+ +

此外,一个进程的级别可能会因其他进程对它的依赖而有所提高,即服务于另一进程的进程其级别永远不会低于其所服务的进程。 + +例如,如果进程 A +中的内容提供程序为进程 B 中的客户端提供服务,或者如果进程 A 中的服务绑定到进程 B 中的组件,则进程 A 始终被视为至少与进程 +B 同样重要。

+ +

由于运行服务的进程其级别高于托管后台 Activity 的进程,因此启动长时间运行操作的 Activity 最好为该操作启动服务,而不是简单地创建工作线程,当操作有可能比 Activity 更加持久时尤要如此。例如,正在将图片上传到网站的 Activity 应该启动服务来执行上传,这样一来,即使用户退出 Activity,仍可在后台继续执行上传操作。使用服务可以保证,无论 Activity 发生什么情况,该操作至少具备“服务进程”优先级。 + + + + + +同理,广播接收器也应使用服务,而不是简单地将耗时冗长的操作放入线程中。 +

+ + + + +

线程

+ +

应用启动时,系统会为应用创建一个名为“主线程”的执行线程。 +此线程非常重要,因为它负责将事件分派给相应的用户界面小工具,其中包括绘图事件。 +此外,它也是应用与 +Android UI 工具包组件(来自 {@link +android.widget} 和 {@link android.view} 软件包的组件)进行交互的线程。因此,主线程有时也称为 +UI 线程。

+ +

系统绝对不会为每个组件实例创建单独的线程。运行于同一进程的所有组件均在 +UI +线程中实例化,并且对每个组件的系统调用均由该线程进行分派。因此,响应系统回调的方法(例如,报告用户操作的 +{@link android.view.View#onKeyDown onKeyDown()} +或生命周期回调方法)始终在进程的 UI 线程中运行。

+ +

例如,当用户触摸屏幕上的按钮时,应用的 +UI +线程会将触摸事件分派给小工具,而小工具反过来又设置其按下状态,并将无效请求发布到事件队列中。UI +线程从队列中取消该请求并通知小工具应该重绘自身。

+ +

在应用执行繁重的任务以响应用户交互时,除非正确实施应用,否则这种单线程模式可能会导致性能低下。 +特别地,如果 +UI +线程需要处理所有任务,则执行耗时很长的操作(例如,网络访问或数据库查询)将会阻塞整个 +UI。一旦线程被阻塞,将无法分派任何事件,包括绘图事件。从用户的角度来看,应用显示为挂起。 +更糟糕的是,如果 UI +线程被阻塞超过几秒钟时间(目前大约是 5 秒钟),用户就会看到一个让人厌烦的“应用无响应”(ANR) +对话框。如果引起用户不满,他们可能就会决定退出并卸载此应用。 +

+ +

此外,Android UI 工具包并非线程安全工具包。因此,您不得通过工作线程操纵 +UI,而只能通过 +UI 线程操纵用户界面。因此,Android 的单线程模式必须遵守两条规则:

+ +
    +
  1. 不要阻塞 UI 线程 +
  2. 不要在 UI 线程之外访问 Android UI 工具包 +
+ +

工作线程

+ +

根据上述单线程模式,要保证应用 UI +的响应能力,关键是不能阻塞 UI 线程。如果执行的操作不能很快完成,则应确保它们在单独的线程(“后台”或“工作”线程)中运行。 + +

+ +

例如,以下代码演示了一个点击侦听器从单独的线程下载图像并将其显示在 +{@link android.widget.ImageView} 中:

+ +
+public void onClick(View v) {
+    new Thread(new Runnable() {
+        public void run() {
+            Bitmap b = loadImageFromNetwork("http://example.com/image.png");
+            mImageView.setImageBitmap(b);
+        }
+    }).start();
+}
+
+ +

乍看起来,这段代码似乎运行良好,因为它创建了一个新线程来处理网络操作。 +但是,它违反了单线程模式的第二条规则:不要在 +UI 线程之外访问 Android UI 工具包—此示例从工作线程(而不是 UI 线程)修改了 {@link +android.widget.ImageView}。这可能导致出现不明确、不可预见的行为,但要跟踪此行为困难而又费时。 +

+ +

为解决此问题,Android 提供了几种途径来从其他线程访问 UI +线程。以下列出了几种有用的方法:

+ +
    +
  • {@link android.app.Activity#runOnUiThread(java.lang.Runnable) +Activity.runOnUiThread(Runnable)}
  • +
  • {@link android.view.View#post(java.lang.Runnable) View.post(Runnable)}
  • +
  • {@link android.view.View#postDelayed(java.lang.Runnable, long) View.postDelayed(Runnable, +long)}
  • +
+ +

例如,您可以通过使用 {@link +android.view.View#post(java.lang.Runnable) View.post(Runnable)} 方法修复上述代码:

+ +
+public void onClick(View v) {
+    new Thread(new Runnable() {
+        public void run() {
+            final Bitmap bitmap = loadImageFromNetwork("http://example.com/image.png");
+            mImageView.post(new Runnable() {
+                public void run() {
+                    mImageView.setImageBitmap(bitmap);
+                }
+            });
+        }
+    }).start();
+}
+
+ +

现在,上述实现属于线程安全型:在单独的线程中完成网络操作,而在 UI 线程中操纵 +{@link android.widget.ImageView}。

+ +

但是,随着操作日趋复杂,这类代码也会变得复杂且难以维护。 +要通过工作线程处理更复杂的交互,可以考虑在工作线程中使用 +{@link android.os.Handler} +处理来自 UI 线程的消息。当然,最好的解决方案或许是扩展 {@link android.os.AsyncTask} 类,此类简化了与 +UI 进行交互所需执行的工作线程任务。

+ + +

使用 AsyncTask

+ +

{@link android.os.AsyncTask} +允许对用户界面执行异步操作。它会先阻塞工作线程中的操作,然后在 UI +线程中发布结果,而无需您亲自处理线程和/或处理程序。

+ +

要使用它,必须创建 {@link android.os.AsyncTask} 子类并实现 {@link +android.os.AsyncTask#doInBackground doInBackground()} +回调方法,该方法将在后台线程池中运行。要更新 UI,必须实现 {@link +android.os.AsyncTask#onPostExecute onPostExecute()} 以传递 {@link +android.os.AsyncTask#doInBackground doInBackground()} 返回的结果并在 UI 线程中运行,这样,您即可安全更新 UI。稍后,您可以通过从 UI 线程调用 +{@link android.os.AsyncTask#execute execute()} +来运行任务。

+ +

例如,您可以通过以下方式使用 +{@link android.os.AsyncTask} 来实现上述示例:

+ +
+public void onClick(View v) {
+    new DownloadImageTask().execute("http://example.com/image.png");
+}
+
+private class DownloadImageTask extends AsyncTask<String, Void, Bitmap> {
+    /** The system calls this to perform work in a worker thread and
+      * delivers it the parameters given to AsyncTask.execute() */
+    protected Bitmap doInBackground(String... urls) {
+        return loadImageFromNetwork(urls[0]);
+    }
+    
+    /** The system calls this to perform work in the UI thread and delivers
+      * the result from doInBackground() */
+    protected void onPostExecute(Bitmap result) {
+        mImageView.setImageBitmap(result);
+    }
+}
+
+ +

现在 UI 是安全的,代码也得到简化,因为任务分解成了两部分:一部分应在工作线程内完成,另一部分应在 +UI 线程内完成。

+ +

下面简要概述了 AsyncTask 的工作方法,但要全面了解如何使用此类,您应阅读 {@link android.os.AsyncTask} +参考文档:

+ +
    +
  • 可以使用泛型指定参数类型、进度值和任务最终值 +
  • +
  • 方法 +{@link android.os.AsyncTask#doInBackground doInBackground()} 会在工作线程上自动执行
  • +
  • {@link android.os.AsyncTask#onPreExecute onPreExecute()}、{@link +android.os.AsyncTask#onPostExecute onPostExecute()} 和 {@link +android.os.AsyncTask#onProgressUpdate onProgressUpdate()} 均在 UI 线程中调用
  • +
  • {@link android.os.AsyncTask#doInBackground doInBackground()} 返回的值将发送到 +{@link android.os.AsyncTask#onPostExecute onPostExecute()}
  • +
  • 您可以随时在 {@link +android.os.AsyncTask#doInBackground doInBackground()} 中调用{@link android.os.AsyncTask#publishProgress publishProgress()},以在 UI 线程中执行 {@link +android.os.AsyncTask#onProgressUpdate onProgressUpdate()}
  • +
  • 您可以随时取消任何线程中的任务
  • +
+ +

注意:使用工作线程时可能会遇到另一个问题,即:运行时配置变更(例如,用户更改了屏幕方向)导致 Activity 意外重启,这可能会销毁工作线程。 + +要了解如何在这种重启情况下坚持执行任务,以及如何在 Activity 被销毁时正确地取消任务,请参阅书架示例应用的源代码。 + +

+ + +

线程安全方法

+ +

在某些情况下,您实现的方法可能会从多个线程调用,因此编写这些方法时必须确保其满足线程安全的要求。 +

+ +

这一点主要适用于可以远程调用的方法,如绑定服务中的方法。如果对 +{@link android.os.IBinder} +中所实现方法的调用源自运行 +{@link android.os.IBinder IBinder} +的同一进程,则该方法在调用方的线程中执行。但是,如果调用源自其他进程,则该方法将在从线程池选择的某个线程中执行(而不是在进程的 UI 线程中执行),线程池由系统在与 {@link android.os.IBinder +IBinder} 相同的进程中维护。例如,即使服务的 +{@link android.app.Service#onBind onBind()} 方法将从服务进程的 UI +线程调用,在 +{@link android.app.Service#onBind +onBind()} 返回的对象中实现的方法(例如,实现 RPC 方法的子类)仍会从线程池中的线程调用。由于一个服务可以有多个客户端,因此可能会有多个池线程在同一时间使用同一 +{@link android.os.IBinder IBinder} 方法。因此,{@link android.os.IBinder +IBinder} 方法必须实现为线程安全方法。

+ +

同样,内容提供程序也可接收来自其他进程的数据请求。尽管 +{@link android.content.ContentResolver} 和 {@link android.content.ContentProvider} +类隐藏了如何管理进程间通信的细节,但响应这些请求的 {@link +android.content.ContentProvider} 方法({@link +android.content.ContentProvider#query query()}、{@link android.content.ContentProvider#insert +insert()}、{@link android.content.ContentProvider#delete delete()}、{@link +android.content.ContentProvider#update update()} 和 {@link android.content.ContentProvider#getType +getType()} 方法)将从内容提供程序所在进程的线程池中调用,而不是从进程的 +UI 线程调用。由于这些方法可能会同时从任意数量的线程调用,因此它们也必须实现为线程安全方法。 +

+ + +

进程间通信

+ +

Android 利用远程过程调用 +(RPC) 提供了一种进程间通信 +(IPC) +机制,通过这种机制,由 Activity 或其他应用组件调用的方法将(在其他进程中)远程执行,而所有结果将返回给调用方。这就要求把方法调用及其数据分解至操作系统可以识别的程度,并将其从本地进程和地址空间传输至远程进程和地址空间,然后在远程进程中重新组装并执行该调用。 + +然后,返回值将沿相反方向传输回来。 +Android 提供了执行这些 IPC +事务所需的全部代码,因此您只需集中精力定义和实现 RPC 编程接口即可。

+ +

要执行 IPC,必须使用 {@link +android.content.Context#bindService bindService()} 将应用绑定到服务上。如需了解详细信息,请参阅服务开发者指南。

+ + + diff --git a/docs/html-intl/intl/zh-cn/guide/components/recents.jd b/docs/html-intl/intl/zh-cn/guide/components/recents.jd new file mode 100644 index 0000000000000000000000000000000000000000..2bf1a5bd4ff35752c127d8399e086f615ab0ffce --- /dev/null +++ b/docs/html-intl/intl/zh-cn/guide/components/recents.jd @@ -0,0 +1,256 @@ +page.title=概览屏幕 +page.tags="recents","overview" + +@jd:body + +
+
+ +

本文内容

+
    +
  1. 将任务添加到概览屏幕 +
      +
    1. 使用 Intent 标志添加任务
    2. +
    3. 使用 Activity 属性添加任务
    4. +
    +
  2. +
  3. 删除任务 +
      +
    1. 使用 AppTask 类删除任务
    2. +
    3. 保留已完成的任务
    4. +
    +
  4. +
+ +

关键类

+
    +
  1. {@link android.app.ActivityManager.AppTask}
  2. +
  3. {@link android.content.Intent}
  4. +
+ +

示例代码

+
    +
  1. 以文档为中心的应用
  2. +
+ +
+
+ +

概览屏幕(也称为最新动态屏幕、最近任务列表或最近使用的应用)是一个系统级别 +UI,其中列出了最近访问过的Activity任务。 +用户可以浏览该列表并选择要恢复的任务,也可以通过滑动清除任务将其从列表中删除。 + +对于 +Android 5.0 版本(API 级别 21),包含多个文档的同一 Activity 的多个实例可能会以任务的形式显示在概览屏幕中。例如,Google Drive 可能对多个 +Google 文档中的每个文档均执行一个任务。每个文档均以任务的形式显示在概览屏幕中。 +

+ + +

图 1. 显示了三个 +Google Drive 文档的概览屏幕,每个文档分别以一个单独的任务表示。

+ +

通常,您应该允许系统定义任务和 Activity 在概览屏幕中的显示方法,并且无需修改此行为。不过,应用可以确定 Activity 在概览屏幕中的显示方式和时间。 + +您可以使用 {@link android.app.ActivityManager.AppTask} +类来管理任务,使用 +{@link android.content.Intent} +类的 Activity 标志来指定某 Activity 添加到概览屏幕或从中删除的时间。此外,您也可以使用 +<activity> 属性在清单文件中设置该行为。

+ +

将任务添加到概览屏幕

+ +

通过使用 {@link android.content.Intent} +类的标志添加任务,您可以更好地控制某文档在概览屏幕中打开或重新打开的时间和方式。使用 +<activity> +属性时,您可以选择始终在新任务中打开文档,或选择对文档重复使用现有任务。 +

+ +

使用 Intent 标志添加任务

+ +

为 Activity 创建新文档时,可调用 +{@link android.app.ActivityManager.AppTask} +类的 {@link android.app.ActivityManager.AppTask#startActivity(android.content.Context, android.content.Intent, android.os.Bundle) startActivity()} +方法,以向其传递启动 Activity 的 Intent。要插入逻辑换行符以便系统将 Activity 视为新任务显示在概览屏幕中,可在启动 Activity 的 +{@link android.content.Intent} +的 {@link android.content.Intent#addFlags(int) addFlags()} 方法中传递 {@link android.content.Intent#FLAG_ACTIVITY_NEW_DOCUMENT} +标志。

+ +

注:{@link android.content.Intent#FLAG_ACTIVITY_NEW_DOCUMENT} +标志取代了 {@link android.content.Intent#FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET} +标志,后者自 Android 5.0(API 级别 21)起已弃用。

+ +

如果在创建新文档时设置 +{@link android.content.Intent#FLAG_ACTIVITY_MULTIPLE_TASK} +标志,则系统始终会以目标 Activity 作为根创建新任务。此设置允许同一文档在多个任务中打开。以下代码演示了主 Activity 如何执行此操作: +

+ +

DocumentCentricActivity.java +

+
+public void createNewDocument(View view) {
+      final Intent newDocumentIntent = newDocumentIntent();
+      if (useMultipleTasks) {
+          newDocumentIntent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
+      }
+      startActivity(newDocumentIntent);
+  }
+
+  private Intent newDocumentIntent() {
+      boolean useMultipleTasks = mCheckbox.isChecked();
+      final Intent newDocumentIntent = new Intent(this, NewDocumentActivity.class);
+      newDocumentIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
+      newDocumentIntent.putExtra(KEY_EXTRA_NEW_DOCUMENT_COUNTER, incrementAndGet());
+      return newDocumentIntent;
+  }
+
+  private static int incrementAndGet() {
+      Log.d(TAG, "incrementAndGet(): " + mDocumentCounter);
+      return mDocumentCounter++;
+  }
+}
+
+ +

注:使用 {@code FLAG_ACTIVITY_NEW_DOCUMENT} +标志启动的 Activity 必须具有在清单文件中设置的 {@code android:launchMode="standard"} +属性值(默认)。

+ +

当主 Activity 启动新 Activity 时,系统会搜遍现有任务,看看是否有任务的 Intent 与 Activity 的 Intent 组件名称和 Intent 数据相匹配。 +如果未找到任务或者 Intent 包含 +{@link android.content.Intent#FLAG_ACTIVITY_MULTIPLE_TASK} +标志,则会以该 Activity 作为其根创建新任务。如果找到的话,则会将该任务转到前台并将新 + Intent +传递给 +{@link android.app.Activity#onNewIntent onNewIntent()}。新 Activity 将获得 Intent 并在概览屏幕中创建新文档,如下例所示:

+ +

NewDocumentActivity.java +

+
+@Override
+protected void onCreate(Bundle savedInstanceState) {
+    super.onCreate(savedInstanceState);
+    setContentView(R.layout.activity_new_document);
+    mDocumentCount = getIntent()
+            .getIntExtra(DocumentCentricActivity.KEY_EXTRA_NEW_DOCUMENT_COUNTER, 0);
+    mDocumentCounterTextView = (TextView) findViewById(
+            R.id.hello_new_document_text_view);
+    setDocumentCounterText(R.string.hello_new_document_counter);
+}
+
+@Override
+protected void onNewIntent(Intent intent) {
+    super.onNewIntent(intent);
+    /* If FLAG_ACTIVITY_MULTIPLE_TASK has not been used, this activity
+    is reused to create a new document.
+     */
+    setDocumentCounterText(R.string.reusing_document_counter);
+}
+
+ + +

使用 Activity 属性添加任务

+ +

此外,Activity 还可以在其清单文件中指定始终通过使用 +<activity> +属性 {@code android:documentLaunchMode} 进入新任务。 +此属性有四个值,会在用户使用该应用打开文档时产生以下效果: +

+ +
+
“{@code intoExisting}”
+
该 Activity 会对文档重复使用现有任务。这与不设置 +{@link android.content.Intent#FLAG_ACTIVITY_MULTIPLE_TASK} +标志、但设置 +{@link android.content.Intent#FLAG_ACTIVITY_NEW_DOCUMENT} 标志所产生的效果相同,如上文的使用 Intent 标志添加任务中所述。
+ +
“{@code always}”
+
该 Activity 为文档创建新任务,即便文档已打开也是如此。使用此值与同时设置 +{@link android.content.Intent#FLAG_ACTIVITY_NEW_DOCUMENT} +和 {@link android.content.Intent#FLAG_ACTIVITY_MULTIPLE_TASK} 标志所产生的效果相同。
+ +
“{@code none”}
+
该 Activity 不会为文档创建新任务。概览屏幕将按其默认方式对待此 Activity:为应用显示单个任务,该任务将从用户上次调用的任意 Activity 开始继续执行。 + +
+ +
“{@code never}”
+
该 Activity 不会为文档创建新任务。设置此值会替代 +{@link android.content.Intent#FLAG_ACTIVITY_NEW_DOCUMENT} +和 {@link android.content.Intent#FLAG_ACTIVITY_MULTIPLE_TASK} 标志的行为(如果在 + Intent +中设置了其中一个标志),并且概览屏幕将为应用显示单个任务,该任务将从用户上次调用的任意 Activity 开始继续执行。
+
+ +

注:对于除 {@code none} 和 {@code never} +以外的值,必须使用 {@code launchMode="standard"} 定义 Activity。如果未指定此属性,则使用 +{@code documentLaunchMode="none"}。

+ +

删除任务

+ +

默认情况下,在 Activity 结束后,文档任务会从概览屏幕中自动删除。 +您可以使用 {@link android.app.ActivityManager.AppTask} +类、{@link android.content.Intent} 标志或 +<activity> 属性替代此行为。

+ +

通过将 +<activity> +属性 +{@code android:excludeFromRecents} 设置为 {@code true},您可以始终将任务从概览屏幕中完全排除。

+ +

您可以通过将 +<activity> +属性 {@code android:maxRecents} +设置为整型值,设置应用能够包括在概览屏幕中的最大任务数。默认值为 16。达到最大任务数后,最近最少使用的任务将从概览屏幕中删除。 +{@code android:maxRecents} +的最大值为 50(内存不足的设备上为 25);小于 1 的值无效。

+ +

使用 AppTask 类删除任务

+ +

在于概览屏幕创建新任务的 Activity 中,您可以通过调用 +{@link android.app.ActivityManager.AppTask#finishAndRemoveTask() finishAndRemoveTask()} +方法指定何时删除该任务以及结束所有与之相关的 Activity。

+ +

NewDocumentActivity.java +

+
+public void onRemoveFromRecents(View view) {
+    // The document is no longer needed; remove its task.
+    finishAndRemoveTask();
+}
+
+ +

注:如下所述,使用 +{@link android.app.ActivityManager.AppTask#finishAndRemoveTask() finishAndRemoveTask()} +方法代替使用 {@link android.content.Intent#FLAG_ACTIVITY_RETAIN_IN_RECENTS} +标记。

+ +

保留已完成的任务

+ +

若要将任务保留在概览屏幕中(即使其 Activity 已完成),可在启动 Activity 的 + Intent 的 {@link android.content.Intent#addFlags(int) addFlags()} 方法中传递 +{@link android.content.Intent#FLAG_ACTIVITY_RETAIN_IN_RECENTS} 标志。

+ +

DocumentCentricActivity.java +

+
+private Intent newDocumentIntent() {
+    final Intent newDocumentIntent = new Intent(this, NewDocumentActivity.class);
+    newDocumentIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT |
+      android.content.Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS);
+    newDocumentIntent.putExtra(KEY_EXTRA_NEW_DOCUMENT_COUNTER, incrementAndGet());
+    return newDocumentIntent;
+}
+
+ +

要达到同样的效果,请将 +<activity> +属性 +{@code android:autoRemoveFromRecents} 设置为 {@code false}。文档 Activity 的默认值为 +{@code true},常规 Activity 的默认值为 {@code false}。如前所述,使用此属性替代 +{@link android.content.Intent#FLAG_ACTIVITY_RETAIN_IN_RECENTS} 标志。

+ + + + + + + diff --git a/docs/html-intl/intl/zh-cn/guide/components/services.jd b/docs/html-intl/intl/zh-cn/guide/components/services.jd new file mode 100644 index 0000000000000000000000000000000000000000..9a00e704fa38076e3d15e0c18582fb1c5e702f41 --- /dev/null +++ b/docs/html-intl/intl/zh-cn/guide/components/services.jd @@ -0,0 +1,813 @@ +page.title=服务 +@jd:body + + + + +

{@link android.app.Service} +是一个可以在后台执行长时间运行操作而不使用用户界面的应用组件。服务可由其他应用组件启动,而且即使用户切换到其他应用,服务仍将在后台继续运行。 + +此外,组件可以绑定到服务,以与之进行交互,甚至是执行进程间通信 (IPC)。 +例如,服务可以处理网络事务、播放音乐,执行文件 I/O +或与内容提供程序交互,而所有这一切均可在后台进行。 +

+ +

服务基本上分为两种形式:

+ +
+
启动
+
当应用组件(如 Activity)通过调用 +{@link android.content.Context#startService startService()} 启动服务时,服务即处于“启动”状态。一旦启动,服务即可在后台无限期运行,即使启动服务的组件已被销毁也不受影响。 +已启动的服务通常是执行单一操作,而且不会将结果返回给调用方。例如,它可能通过网络下载或上传文件。 + +操作完成后,服务会自行停止运行。 +
+
绑定
+
当应用组件通过调用 {@link +android.content.Context#bindService bindService()} 绑定到服务时,服务即处于“绑定”状态。绑定服务提供了一个客户端-服务器接口,允许组件与服务进行交互、发送请求、获取结果,甚至是利用进程间通信 (IPC) +跨进程执行这些操作。 +仅当与另一个应用组件绑定时,绑定服务才会运行。 +多个组件可以同时绑定到该服务,但全部取消绑定后,该服务即会被销毁。 +
+
+ +

虽然本文档是分开概括讨论这两种服务,但是您的服务可以同时以这两种方式运行,也就是说,它既可以是启动服务(以无限期运行),也允许绑定。问题只是在于您是否实现了一组回调方法:{@link +android.app.Service#onStartCommand onStartCommand()}(允许组件启动服务)和 {@link +android.app.Service#onBind onBind()}(允许绑定服务)。 + +

+ +

无论应用是处于启动状态还是绑定状态,抑或处于启动并且绑定状态,任何应用组件均可像使用活动那样通过调用 {@link android.content.Intent} 来使用服务(即使此服务来自另一应用)。 + +不过,您可以通过清单文件将服务声明为私有服务,并阻止其他应用访问。 +使用清单文件声明服务部分将对此做更详尽的阐述。 + +

+ +

注意:服务在其托管进程的主线程中运行,它既创建自己的线程,也在单独的进程中运行(除非另行指定)。 + +这意味着,如果服务将执行任何 +CPU 密集型工作或阻止性操作(例如 MP3 +播放或联网),则应在服务内创建新线程来完成这项工作。通过使用单独的线程,可以降低发生“应用无响应”(ANR) +错误的风险,而应用的主线程仍可继续专注于运行用户与 Activity 之间的交互。 +

+ + +

基础知识

+ + + +

要创建服务,您必须创建 +{@link android.app.Service} 的子类(或使用它的一个现有子类)。在实现中,您需要重写一些回调方法,以处理服务生命周期的某些关键方面并提供一种机制将组件绑定到服务(如适用)。 + +应重写的最重要的回调方法包括:

+ +
+
{@link android.app.Service#onStartCommand onStartCommand()}
+
当另一个组件(如 Activity)通过调用 +{@link android.content.Context#startService +startService()} 请求启动服务时,系统将调用此方法。一旦执行此方法,服务即会启动并可在后台无限期运行。 +如果您实现此方法,则在服务工作完成后,需要由您通过调用 +{@link android.app.Service#stopSelf stopSelf()} 或 {@link +android.content.Context#stopService stopService()} 来停止服务。(如果您只想提供绑定,则无需实现此方法。) +
+
{@link android.app.Service#onBind onBind()}
+
当另一个组件想通过调用 {@link android.content.Context#bindService +bindService()} +与服务绑定(例如执行 RPC)时,系统将调用此方法。在此方法的实现中,您必须通过返回 +{@link android.os.IBinder} 提供一个接口,供客户端用来与服务进行通信。请务必实现此方法,但如果您并不希望允许绑定,则应返回 null。 +
+
{@link android.app.Service#onCreate()}
+
首次创建服务时,系统将调用此方法来执行一次性设置程序(在调用 +{@link android.app.Service#onStartCommand onStartCommand()} 或 +{@link android.app.Service#onBind onBind()} 之前)。如果服务已在运行,则不会调用此方法。 +
+
{@link android.app.Service#onDestroy()}
+
当服务不再使用且将被销毁时,系统将调用此方法。服务应该实现此方法来清理所有资源,如线程、注册的侦听器、接收器等。 + +这是服务接收的最后一个调用。
+
+ +

如果组件通过调用 {@link +android.content.Context#startService startService()} 启动服务(这会导致对 {@link +android.app.Service#onStartCommand onStartCommand()} 的调用),则服务将一直运行,直到服务使用 +{@link android.app.Service#stopSelf()} +自行停止运行,或由其他组件通过调用 {@link android.content.Context#stopService stopService()} 停止它为止。

+ +

如果组件是通过调用 +{@link android.content.Context#bindService bindService()} 来创建服务(且调用 +{@link +android.app.Service#onStartCommand onStartCommand()}),则服务只会在该组件与其绑定时运行。一旦该服务与所有客户端之间的绑定全部取消,系统便会销毁它。 +

+ +

仅当内存过低且必须回收系统资源以供具有用户焦点的 Activity 使用时,Android +系统才会强制停止服务。如果将服务绑定到具有用户焦点的 Activity,则它不太可能会终止;如果将服务声明为在前台运行(稍后讨论),则它几乎永远不会终止。或者,如果服务已启动并要长时间运行,则系统会随着时间的推移降低服务在后台任务列表中的位置,而服务也将随之变得非常容易被终止;如果服务是启动服务,则您必须将其设计为能够妥善处理系统对它的重启。 + + + + +如果系统终止服务,那么一旦资源变得再次可用,系统便会重启服务(不过这还取决于从 +{@link +android.app.Service#onStartCommand onStartCommand()} 返回的值,本文稍后会对此加以讨论)。如需了解有关系统会在何时销毁服务的详细信息,请参阅进程和线程文档。 + +

+ +

在下文中,您将了解如何创建各类服务以及如何从其他应用组件使用服务。 +

+ + + +

使用清单文件声明服务

+ +

如同 Activity(以及其他组件)一样,您必须在应用的清单文件中声明所有服务。 +

+ +

要声明服务,请添加 {@code <service>} +元素作为 {@code <application>} +元素的子元素。例如:

+ +
+<manifest ... >
+  ...
+  <application ... >
+      <service android:name=".ExampleService" />
+      ...
+  </application>
+</manifest>
+
+ +

如需了解有关使用清单文件声明服务的详细信息,请参阅 +{@code <service>} 元素参考文档。

+ +

您还可将其他属性包括在 +{@code <service>} +元素中,以定义一些特性,如启动服务及其运行所在进程所需的权限。{@code android:name} +属性是唯一必需的属性,用于指定服务的类名。应用一旦发布,即不应更改此类名,如若不然,可能会存在因依赖显式 Intent 启动或绑定服务而破坏代码的风险(请阅读博客文章Things That Cannot Change[不能更改的内容])。 + + + + +

为了确保应用的安全性,请始终使用显式 Intent 启动或绑定 {@link android.app.Service},且不要为服务声明 Intent 过滤器。 +启动哪个服务存在一定的不确定性,而如果对这种不确定性的考量非常有必要,则可为服务提供 Intent 过滤器并从 +{@link +android.content.Intent} +中排除相应的组件名称,但随后必须使用 {@link +android.content.Intent#setPackage setPackage()} + 方法设置 Intent 的软件包,这样可以充分消除目标服务的不确定性。

+ +

此外,还可以通过添加 +{@code android:exported} +属性并将其设置为 {@code "false"},确保服务仅适用于您的应用。这可以有效阻止其他应用启动您的服务,即便在使用显式 Intent 时也如此。 +

+ + + + +

创建启动服务

+ +

启动服务由另一个组件通过调用 {@link +android.content.Context#startService startService()} 启动,这会导致调用服务的 +{@link android.app.Service#onStartCommand onStartCommand()} 方法。

+ +

服务启动之后,其生命周期即独立于启动它的组件,并且可以在后台无限期地运行,即使启动服务的组件已被销毁也不受影响。 + +因此,服务应通过调用 +{@link android.app.Service#stopSelf stopSelf()} 结束工作来自行停止运行,或者由另一个组件通过调用 +{@link android.content.Context#stopService stopService()} 来停止它。

+ +

应用组件(如 Activity)可以通过调用 {@link +android.content.Context#startService startService()} 方法并传递 {@link android.content.Intent} 对象 +(指定服务并包含待使用服务的所有数据)来启动服务。服务通过 +{@link android.app.Service#onStartCommand +onStartCommand()} 方法接收此 {@link android.content.Intent}。

+ +

例如,假设某 Activity 需要将一些数据保存到在线数据库中。该 Activity 可以启动一个协同服务,并通过向 +{@link +android.content.Context#startService startService()} 传递一个 Intent,为该服务提供要保存的数据。服务通过 +{@link +android.app.Service#onStartCommand onStartCommand()} 接收 Intent,连接到 Internet 并执行数据库事务。事务完成之后,服务会自行停止运行并随即被销毁。 +

+ +

注意:默认情况下,服务与服务声明所在的应用运行于同一进程,而且运行于该应用的主线程中。 +因此,如果服务在用户与来自同一应用的 Activity 进行交互时执行密集型或阻止性操作,则会降低 Activity 性能。 + +为了避免影响应用性能,您应在服务内启动新线程。 +

+ +

从传统上讲,您可以扩展两个类来创建启动服务:

+
+
{@link android.app.Service}
+
这是适用于所有服务的基类。扩展此类时,必须创建一个用于执行所有服务工作的新线程,因为默认情况下,服务将使用应用的主线程,这会降低应用正在运行的所有 Activity 的性能。 + + +
+
{@link android.app.IntentService}
+
这是 +{@link android.app.Service} 的子类,它使用工作线程逐一处理所有启动请求。如果您不要求服务同时处理多个请求,这是最好的选择。 +您只需实现 {@link +android.app.IntentService#onHandleIntent onHandleIntent()} +方法即可,该方法会接收每个启动请求的 Intent,使您能够执行后台工作。
+
+ +

下文介绍如何使用其中任一类实现服务。 +

+ + +

扩展 IntentService 类

+ +

由于大多数启动服务都不必同时处理多个请求(实际上,这种多线程情况可能很危险),因此使用 +{@link android.app.IntentService} +类实现服务也许是最好的选择。

+ +

{@link android.app.IntentService} 执行以下操作:

+ +
    +
  • 创建默认的工作线程,用于在应用的主线程外执行传递给 {@link +android.app.Service#onStartCommand onStartCommand()} +的所有 Intent。
  • +
  • 创建工作队列,用于将一个 Intent 逐一传递给 {@link +android.app.IntentService#onHandleIntent onHandleIntent()} +实现,这样您就永远不必担心多线程问题。
  • +
  • 在处理完所有启动请求后停止服务,因此您永远不必调用 +{@link android.app.Service#stopSelf}。
  • +
  • 提供 +{@link android.app.IntentService#onBind onBind()} 的默认实现(返回 null)。
  • +
  • 提供 {@link android.app.IntentService#onStartCommand +onStartCommand()} 的默认实现,可将 Intent 依次发送到工作队列和 {@link +android.app.IntentService#onHandleIntent onHandleIntent()} 实现。
  • +
+ +

综上所述,您只需实现 +{@link +android.app.IntentService#onHandleIntent onHandleIntent()} 来完成客户端提供的工作即可。(不过,您还需要为服务提供小型构造函数。)

+ +

以下是 {@link android.app.IntentService} 的实现示例:

+ +
+public class HelloIntentService extends IntentService {
+
+  /**
+   * A constructor is required, and must call the super {@link android.app.IntentService#IntentService}
+   * constructor with a name for the worker thread.
+   */
+  public HelloIntentService() {
+      super("HelloIntentService");
+  }
+
+  /**
+   * The IntentService calls this method from the default worker thread with
+   * the intent that started the service. When this method returns, IntentService
+   * stops the service, as appropriate.
+   */
+  @Override
+  protected void onHandleIntent(Intent intent) {
+      // Normally we would do some work here, like download a file.
+      // For our sample, we just sleep for 5 seconds.
+      long endTime = System.currentTimeMillis() + 5*1000;
+      while (System.currentTimeMillis() < endTime) {
+          synchronized (this) {
+              try {
+                  wait(endTime - System.currentTimeMillis());
+              } catch (Exception e) {
+              }
+          }
+      }
+  }
+}
+
+ +

您只需要一个构造函数和一个 {@link +android.app.IntentService#onHandleIntent onHandleIntent()} 实现即可。

+ +

如果您决定还重写其他回调方法(如 {@link +android.app.IntentService#onCreate onCreate()}、{@link +android.app.IntentService#onStartCommand onStartCommand()} 或 {@link +android.app.IntentService#onDestroy onDestroy()}),请确保调用超类实现,以便 +{@link android.app.IntentService} 能够妥善处理工作线程的生命周期。

+ +

例如,{@link android.app.IntentService#onStartCommand onStartCommand()} +必须返回默认实现(即,如何将 Intent 传递给 {@link +android.app.IntentService#onHandleIntent onHandleIntent()}):

+ +
+@Override
+public int onStartCommand(Intent intent, int flags, int startId) {
+    Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show();
+    return super.onStartCommand(intent,flags,startId);
+}
+
+ +

除 +{@link android.app.IntentService#onHandleIntent onHandleIntent()} 之外,您无需从中调用超类的唯一方法就是 {@link android.app.IntentService#onBind +onBind()}(仅当服务允许绑定时,才需要实现该方法)。

+ +

在下一部分中,您将了解如何在扩展 +{@link android.app.Service} +基类时实现同类服务。该基类包含更多代码,但如需同时处理多个启动请求,则更适合使用该基类。

+ + +

扩展服务类

+ +

正如上一部分中所述,使用 +{@link android.app.IntentService} 显著简化了启动服务的实现。但是,若要求服务执行多线程(而不是通过工作队列处理启动请求),则可扩展 +{@link android.app.Service} +类来处理每个 Intent。

+ +

为了便于比较,以下提供了 {@link +android.app.Service} 类实现的代码示例,该类执行的工作与上述使用 {@link +android.app.IntentService} 的示例完全相同。也就是说,对于每个启动请求,它均使用工作线程执行作业,且每次仅处理一个请求。 +

+ +
+public class HelloService extends Service {
+  private Looper mServiceLooper;
+  private ServiceHandler mServiceHandler;
+
+  // Handler that receives messages from the thread
+  private final class ServiceHandler extends Handler {
+      public ServiceHandler(Looper looper) {
+          super(looper);
+      }
+      @Override
+      public void handleMessage(Message msg) {
+          // Normally we would do some work here, like download a file.
+          // For our sample, we just sleep for 5 seconds.
+          long endTime = System.currentTimeMillis() + 5*1000;
+          while (System.currentTimeMillis() < endTime) {
+              synchronized (this) {
+                  try {
+                      wait(endTime - System.currentTimeMillis());
+                  } catch (Exception e) {
+                  }
+              }
+          }
+          // Stop the service using the startId, so that we don't stop
+          // the service in the middle of handling another job
+          stopSelf(msg.arg1);
+      }
+  }
+
+  @Override
+  public void onCreate() {
+    // Start up the thread running the service.  Note that we create a
+    // separate thread because the service normally runs in the process's
+    // main thread, which we don't want to block.  We also make it
+    // background priority so CPU-intensive work will not disrupt our UI.
+    HandlerThread thread = new HandlerThread("ServiceStartArguments",
+            Process.THREAD_PRIORITY_BACKGROUND);
+    thread.start();
+
+    // Get the HandlerThread's Looper and use it for our Handler
+    mServiceLooper = thread.getLooper();
+    mServiceHandler = new ServiceHandler(mServiceLooper);
+  }
+
+  @Override
+  public int onStartCommand(Intent intent, int flags, int startId) {
+      Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show();
+
+      // For each start request, send a message to start a job and deliver the
+      // start ID so we know which request we're stopping when we finish the job
+      Message msg = mServiceHandler.obtainMessage();
+      msg.arg1 = startId;
+      mServiceHandler.sendMessage(msg);
+
+      // If we get killed, after returning from here, restart
+      return START_STICKY;
+  }
+
+  @Override
+  public IBinder onBind(Intent intent) {
+      // We don't provide binding, so return null
+      return null;
+  }
+
+  @Override
+  public void onDestroy() {
+    Toast.makeText(this, "service done", Toast.LENGTH_SHORT).show();
+  }
+}
+
+ +

正如您所见,与使用 {@link android.app.IntentService} 相比,这需要执行更多工作。

+ +

但是,因为是由您自己处理对 {@link android.app.Service#onStartCommand +onStartCommand()} 的每个调用,因此可以同时执行多个请求。此示例并未这样做,但如果您希望如此,则可为每个请求创建一个新线程,然后立即运行这些线程(而不是等待上一个请求完成)。 + +

+ +

请注意,{@link android.app.Service#onStartCommand onStartCommand()} +方法必须返回整型数。整型数是一个值,用于描述系统应该如何在服务终止的情况下继续运行服务(如上所述,{@link +android.app.IntentService} +的默认实现将为您处理这种情况,不过您可以对其进行修改)。从 +{@link android.app.Service#onStartCommand onStartCommand()} +返回的值必须是以下常量之一:

+ +
+
{@link android.app.Service#START_NOT_STICKY}
+
如果系统在 +{@link android.app.Service#onStartCommand +onStartCommand()} 返回后终止服务,则除非有挂起 Intent 要传递,否则系统不会重建服务。这是最安全的选项,可以避免在不必要时以及应用能够轻松重启所有未完成的作业时运行服务。 +
+
{@link android.app.Service#START_STICKY}
+
如果系统在 +{@link android.app.Service#onStartCommand +onStartCommand()} 返回后终止服务,则会重建服务并调用 +{@link +android.app.Service#onStartCommand onStartCommand()},但绝对不会重新传递最后一个 Intent。相反,除非有挂起 Intent 要启动服务(在这种情况下,将传递这些 Intent ),否则系统会通过空 Intent 调用 +{@link android.app.Service#onStartCommand onStartCommand()}。这适用于不执行命令、但无限期运行并等待作业的媒体播放器(或类似服务)。 +
+
{@link android.app.Service#START_REDELIVER_INTENT}
+
如果系统在 {@link android.app.Service#onStartCommand +onStartCommand()} 返回后终止服务,则会重建服务,并通过传递给服务的最后一个 Intent 调用 +{@link +android.app.Service#onStartCommand onStartCommand()}。任何挂起 Intent 均依次传递。这适用于主动执行应该立即恢复的作业(例如下载文件)的服务。 +
+
+

有关这些返回值的更多详细信息,请查阅每个常量链接的参考文档。 +

+ + + +

启动服务

+ +

您可以通过将 +{@link android.content.Intent}(指定要启动的服务)传递给 {@link +android.content.Context#startService startService()},从 Activity 或其他应用组件启动服务。Android 系统调用服务的 {@link +android.app.Service#onStartCommand onStartCommand()} 方法,并向其传递 {@link +android.content.Intent}。(切勿直接调用 {@link android.app.Service#onStartCommand +onStartCommand()}。)

+ +

例如,Activity 可以结合使用显式 Intent 与 {@link android.content.Context#startService +startService()},启动上文中的示例服务 ({@code +HelloSevice}):

+ +
+Intent intent = new Intent(this, HelloService.class);
+startService(intent);
+
+ +

{@link android.content.Context#startService startService()} 方法将立即返回,且 +Android 系统调用服务的 {@link android.app.Service#onStartCommand +onStartCommand()} 方法。如果服务尚未运行,则系统会先调用 {@link +android.app.Service#onCreate onCreate()},然后再调用 {@link android.app.Service#onStartCommand +onStartCommand()}。

+ +

如果服务亦未提供绑定,则使用 +{@link +android.content.Context#startService startService()} 传递的 Intent 是应用组件与服务之间唯一的通信模式。但是,如果您希望服务返回结果,则启动服务的客户端可以为广播创建一个 +{@link android.app.PendingIntent} +(使用 {@link android.app.PendingIntent#getBroadcast getBroadcast()}),并通过启动服务的 +{@link android.content.Intent} 传递给服务。然后,服务就可以使用广播传递结果。 +

+ +

多个服务启动请求会导致多次对服务的 +{@link android.app.Service#onStartCommand onStartCommand()} 进行相应的调用。但是,要停止服务,只需一个服务停止请求(使用 +{@link android.app.Service#stopSelf stopSelf()} 或 {@link +android.content.Context#stopService stopService()})即可。

+ + +

停止服务

+ +

启动服务必须管理自己的生命周期。也就是说,除非系统必须回收内存资源,否则系统不会停止或销毁服务,而且服务在 +{@link android.app.Service#onStartCommand onStartCommand()} +返回后会继续运行。因此,服务必须通过调用 +{@link android.app.Service#stopSelf stopSelf()} 自行停止运行,或者由另一个组件通过调用 +{@link android.content.Context#stopService stopService()} 来停止它。

+ +

一旦请求使用 {@link android.app.Service#stopSelf stopSelf()} +或 {@link +android.content.Context#stopService stopService()} 停止服务,系统就会尽快销毁服务。

+ +

但是,如果服务同时处理多个 +{@link +android.app.Service#onStartCommand onStartCommand()} +请求,则您不应在处理完一个启动请求之后停止服务,因为您可能已经收到了新的启动请求(在第一个请求结束时停止服务会终止第二个请求)。为了避免这一问题,您可以使用 +{@link android.app.Service#stopSelf(int)} +确保服务停止请求始终基于最近的启动请求。也就说,在调用 {@link +android.app.Service#stopSelf(int)} 时,传递与停止请求的 ID 对应的启动请求的 ID(传递给 {@link android.app.Service#onStartCommand onStartCommand()} +的 startId) +。然后,如果在您能够调用 {@link +android.app.Service#stopSelf(int)} 之前服务收到了新的启动请求, ID 就不匹配,服务也就不会停止。

+ +

注意:为了避免浪费系统资源和消耗电池电量,应用必须在工作完成之后停止其服务。 +如有必要,其他组件可以通过调用 +{@link +android.content.Context#stopService stopService()} 来停止服务。即使为服务启用了绑定,一旦服务收到对 +{@link +android.app.Service#onStartCommand onStartCommand()} 的调用,您始终仍须亲自停止服务。

+ +

如需了解有关服务生命周期的详细信息,请参阅下面有关管理服务生命周期的部分。

+ + + +

创建绑定服务

+ +

绑定服务允许应用组件通过调用 {@link +android.content.Context#bindService bindService()} 与其绑定,以便创建长期连接(通常不允许组件通过调用 +{@link +android.content.Context#startService startService()} 来启动它)。

+ +

如需与 Activity 和其他应用组件中的服务进行交互,或者需要通过进程间通信 (IPC) 向其他应用公开某些应用功能,则应创建绑定服务。 + +

+ +

要创建绑定服务,必须实现 +{@link +android.app.Service#onBind onBind()} 回调方法以返回 {@link android.os.IBinder},用于定义与服务通信的接口。然后,其他应用组件可以调用 +{@link android.content.Context#bindService bindService()} +来检索该接口,并开始对服务调用方法。服务只用于与其绑定的应用组件,因此如果没有组件绑定到服务,则系统会销毁服务(您不必按通过 +{@link android.app.Service#onStartCommand onStartCommand()} +启动的服务那样来停止绑定服务)。 +

+ +

要创建绑定服务,首先必须定义指定客户端如何与服务通信的接口。 +服务与客户端之间的这个接口必须是 +{@link android.os.IBinder} 的实现,并且服务必须从 +{@link android.app.Service#onBind +onBind()} 回调方法返回它。一旦客户端收到 +{@link android.os.IBinder},即可开始通过该接口与服务进行交互。

+ +

多个客户端可以同时绑定到服务。客户端完成与服务的交互后,会调用 +{@link android.content.Context#unbindService unbindService()} 取消绑定。一旦没有客户端绑定到该服务,系统就会销毁它。 +

+ +

有多种方法实现绑定服务,其实现比启动服务更为复杂,因此绑定服务将在有关绑定服务的单独文档中专门讨论。 + +

+ + + +

向用户发送通知

+ +

一旦运行起来,服务即可使用 Toast 通知状态栏通知来通知用户所发生的事件。

+ +

Toast 通知是指出现在当前窗口的表面、片刻随即消失不见的消息,而状态栏通知则在状态栏提供内含消息的图标,用户可以选择该图标来采取操作(例如启动 Activity)。 + +

+ +

通常,当某些后台工作已经完成(例如文件下载完成)且用户现在可以对其进行操作时,状态栏通知是最佳方法。 + +当用户从展开视图中选定通知时,通知即可启动 Activity(例如查看已下载的文件)。 +

+ +

如需了解详细信息,请参阅 +Toast 通知状态栏通知开发者指南。

+ + + +

在前台运行服务

+ +

前台服务被认为是用户主动意识到的一种服务,因此在内存不足时,系统也不会考虑将其终止。 +前台服务必须为状态栏提供通知,状态栏位于“正在进行”标题下方,这意味着除非服务停止或从前台删除,否则不能清除通知。 + + +

+ +

例如,应该将从服务播放音乐的音乐播放器设置为在前台运行,这是因为用户明确意识到其操作。 + +状态栏中的通知可能表示正在播放的歌曲,并允许用户启动 Activity 来与音乐播放器进行交互。 +

+ +

要请求让服务运行于前台,请调用 {@link +android.app.Service#startForeground startForeground()}。此方法取两个参数:唯一标识通知的整型数和状态栏的 +{@link +android.app.Notification}。例如:

+ +
+Notification notification = new Notification(R.drawable.icon, getText(R.string.ticker_text),
+        System.currentTimeMillis());
+Intent notificationIntent = new Intent(this, ExampleActivity.class);
+PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);
+notification.setLatestEventInfo(this, getText(R.string.notification_title),
+        getText(R.string.notification_message), pendingIntent);
+startForeground(ONGOING_NOTIFICATION_ID, notification);
+
+ +

注意:提供给 {@link +android.app.Service#startForeground startForeground()} 的整型 ID 不得为 0。

+ + +

要从前台删除服务,请调用 +{@link android.app.Service#stopForeground stopForeground()}。此方法取一个布尔值,指示是否也删除状态栏通知。 +此方法绝对不会停止服务。 +但是,如果您在服务正在前台运行时将其停止,则通知也会被删除。 +

+ +

如需了解有关通知的详细信息,请参阅创建状态栏通知。 +

+ + + +

管理服务生命周期

+ +

服务的生命周期比 Activity 的生命周期要简单得多。但是,密切关注如何创建和销毁服务反而更加重要,因为服务可以在用户没有意识到的情况下运行于后台。 + +

+ +

服务生命周期(从创建到销毁)可以遵循两条不同的路径: +

+ +
    +
  • 启动服务 +

    该服务在其他组件调用 {@link +android.content.Context#startService startService()} 时创建,然后无限期运行,且必须通过调用 +{@link +android.app.Service#stopSelf() stopSelf()} 来自行停止运行。此外,其他组件也可以通过调用 +{@link android.content.Context#stopService +stopService()} 来停止服务。服务停止后,系统会将其销毁。

  • + +
  • 绑定服务 +

    该服务在另一个组件(客户端)调用 {@link +android.content.Context#bindService bindService()} 时创建。然后,客户端通过 +{@link android.os.IBinder} 接口与服务进行通信。客户端可以通过调用 +{@link android.content.Context#unbindService unbindService()} 关闭连接。多个客户端可以绑定到相同服务,而且当所有绑定全部取消后,系统即会销毁该服务。 +(服务不必自行停止运行。) +

  • +
+ +

这两条路径并非完全独立。也就是说,您可以绑定到已经使用 +{@link android.content.Context#startService startService()} 启动的服务。例如,可以通过使用 +{@link android.content.Intent}(标识要播放的音乐)调用 {@link android.content.Context#startService +startService()} 来启动后台音乐服务。随后,可能在用户需要稍加控制播放器或获取有关当前播放歌曲的信息时,Activity 可以通过调用 +{@link +android.content.Context#bindService bindService()} +绑定到服务。在这种情况下,除非所有客户端均取消绑定,否则 {@link +android.content.Context#stopService stopService()} 或 {@link android.app.Service#stopSelf +stopSelf()} 不会真正停止服务。

+ + +

实现生命周期回调

+ +

与 Activity 类似,服务也拥有生命周期回调方法,您可以实现这些方法来监控服务状态的变化并适时执行工作。 +以下框架服务展示了每种生命周期方法: +

+ +
+public class ExampleService extends Service {
+    int mStartMode;       // indicates how to behave if the service is killed
+    IBinder mBinder;      // interface for clients that bind
+    boolean mAllowRebind; // indicates whether onRebind should be used
+
+    @Override
+    public void {@link android.app.Service#onCreate onCreate}() {
+        // The service is being created
+    }
+    @Override
+    public int {@link android.app.Service#onStartCommand onStartCommand}(Intent intent, int flags, int startId) {
+        // The service is starting, due to a call to {@link android.content.Context#startService startService()}
+        return mStartMode;
+    }
+    @Override
+    public IBinder {@link android.app.Service#onBind onBind}(Intent intent) {
+        // A client is binding to the service with {@link android.content.Context#bindService bindService()}
+        return mBinder;
+    }
+    @Override
+    public boolean {@link android.app.Service#onUnbind onUnbind}(Intent intent) {
+        // All clients have unbound with {@link android.content.Context#unbindService unbindService()}
+        return mAllowRebind;
+    }
+    @Override
+    public void {@link android.app.Service#onRebind onRebind}(Intent intent) {
+        // A client is binding to the service with {@link android.content.Context#bindService bindService()},
+        // after onUnbind() has already been called
+    }
+    @Override
+    public void {@link android.app.Service#onDestroy onDestroy}() {
+        // The service is no longer used and is being destroyed
+    }
+}
+
+ +

注:与 Activity 生命周期回调方法不同,您不需要调用这些回调方法的超类实现。 +

+ + +

图 2. 服务生命周期左图显示了使用 +{@link android.content.Context#startService +startService()} 所创建的服务的生命周期,右图显示了使用 +{@link android.content.Context#bindService bindService()} 所创建的服务的生命周期。

+ +

通过实现这些方法,您可以监控服务生命周期的两个嵌套循环:

+ +
    +
  • 服务的整个生命周期从调用 {@link +android.app.Service#onCreate onCreate()} 开始起,到 {@link +android.app.Service#onDestroy} 返回时结束。与 Activity 类似,服务也在 +{@link android.app.Service#onCreate onCreate()} 中完成初始设置,并在 {@link +android.app.Service#onDestroy onDestroy()} 中释放所有剩余资源。例如,音乐播放服务可以在 {@link +android.app.Service#onCreate onCreate()} 中创建用于播放音乐的线程,然后在 {@link +android.app.Service#onDestroy onDestroy()} 中停止该线程。 + + +

    无论服务是通过 {@link android.content.Context#startService startService()} +还是 {@link +android.content.Context#bindService bindService()} 创建,都会为所有服务调用 {@link android.app.Service#onCreate onCreate()} 和 {@link android.app.Service#onDestroy +onDestroy()} 方法。

  • + +
  • 服务的有效生命周期从调用 {@link +android.app.Service#onStartCommand onStartCommand()} 或 {@link android.app.Service#onBind onBind()} +方法开始。每种方法均有 {@link +android.content.Intent} 对象,该对象分别传递到 {@link android.content.Context#startService +startService()} 或 {@link android.content.Context#bindService bindService()}。 +

    对于启动服务,有效生命周期与整个生命周期同时结束(即便是在 +{@link android.app.Service#onStartCommand +onStartCommand()} 返回之后,服务仍然处于活动状态)。对于绑定服务,有效生命周期在 {@link +android.app.Service#onUnbind onUnbind()} 返回时结束。

    +
  • +
+ +

注:尽管启动服务是通过调用 +{@link android.app.Service#stopSelf stopSelf()} 或 {@link +android.content.Context#stopService stopService()} 来停止,但是该服务并无相应的回调(没有 {@code onStop()} +回调)。因此,除非服务绑定到客户端,否则在服务停止时,系统会将其销毁—{@link +android.app.Service#onDestroy onDestroy()} 是接收到的唯一回调。 +

+ +

图 2 说明了服务的典型回调方法。尽管该图分开介绍通过 +{@link android.content.Context#startService startService()} 创建的服务和通过 +{@link android.content.Context#bindService bindService()} +创建的服务,但是请记住,不管启动方式如何,任何服务均有可能允许客户端与其绑定。因此,最初使用 +{@link android.app.Service#onStartCommand +onStartCommand()}(通过客户端调用 {@link android.content.Context#startService startService()})启动的服务仍可接收对 +{@link android.app.Service#onBind onBind()} 的调用(当客户端调用 +{@link android.content.Context#bindService bindService()} 时)。

+ +

如需了解有关创建提供绑定的服务的详细信息,请参阅绑定服务文档,该文档的管理绑定服务的生命周期部分提供了有关 +{@link android.app.Service#onRebind onRebind()} +回调方法的更多信息。 +

+ + + diff --git a/docs/html-intl/intl/zh-cn/guide/components/tasks-and-back-stack.jd b/docs/html-intl/intl/zh-cn/guide/components/tasks-and-back-stack.jd new file mode 100644 index 0000000000000000000000000000000000000000..07fdf6e80a027c98705f0060ad4a519fabc168ce --- /dev/null +++ b/docs/html-intl/intl/zh-cn/guide/components/tasks-and-back-stack.jd @@ -0,0 +1,578 @@ +page.title=任务和返回栈 +parent.title=Activity +parent.link=activities.html +@jd:body + + + + +

应用通常包含多个Activity。每个 Activity 均应围绕用户可以执行的特定操作设计,并且能够启动其他 Activity。 + +例如,电子邮件应用可能有一个 Activity 显示新邮件的列表。用户选择某邮件时,会打开一个新 Activity 以查看该邮件。 +

+ +

一个 Activity 甚至可以启动设备上其他应用中存在的 Activity。例如,如果应用想要发送电子邮件,则可将 Intent 定义为执行“发送”操作并加入一些数据,如电子邮件地址和电子邮件。 + +然后,系统将打开其他应用中声明自己处理此类 + Intent 的 Activity。在这种情况下, Intent +是要发送电子邮件,因此将启动电子邮件应用的“撰写”Activity(如果多个 Activity 支持相同 + Intent,则系统会让用户选择要使用的 Activity)。发送电子邮件时,Activity 将恢复,看起来好像电子邮件 Activity 是您的应用的一部分。 +即使这两个 Activity 可能来自不同的应用,但是 +Android 仍会将 Activity 保留在相同的任务中,以维护这种无缝的用户体验。 +

+ +

任务是指在执行特定作业时与用户交互的一系列 Activity。 +这些 Activity 按照各自的打开顺序排列在堆栈(即“返回栈”)中。 +

+ + + +

设备主屏幕是大多数任务的起点。当用户触摸应用启动器中的图标(或主屏幕上的快捷键)时,该应用的任务将出现在前台。 + +如果应用不存在任务(应用最近未曾使用),则会创建一个新任务,并且该应用的“主”Activity 将作为堆栈中的根 Activity 打开。 + +

+ +

当前 Activity 启动另一个 Activity 时,该新 Activity 会被推送到堆栈顶部,成为焦点所在。 +前一个 Activity 仍保留在堆栈中,但是处于停止状态。Activity 停止时,系统会保持其用户界面的当前状态。 +用户按“返回”按钮时,当前 Activity 会从堆栈顶部弹出(Activity 被销毁),而前一个 Activity 恢复执行(恢复其 UI 的前一状态)。 + + +堆栈中的 Activity 永远不会重新排列,仅推入和弹出堆栈:由当前 Activity 启动时推入堆栈;用户使用“返回”按钮退出时弹出堆栈。 + +因此,返回栈以“后进先出”对象结构运行。 + +图 1 通过时间线显示 Activity 之间的进度以及每个时间点的当前返回栈,直观呈现了这种行为。 + +

+ + +

图 1. 显示任务中的每个新 Activity 如何向返回栈添加项目。 +用户按“返回”按钮时,当前 Activity 随即被销毁,而前一个 Activity 恢复执行。 + +

+ + +

如果用户继续按“返回”,堆栈中的相应 Activity 就会弹出,以显示前一个 Activity,直到用户返回主屏幕为止(或者,返回任务开始时正在运行的任意 Activity)。 + + +当所有 Activity 均从堆栈中删除后,任务即不复存在。

+ +
+

图 2. 两个任务:任务 B +在前台接收用户交互,而任务 A 则在后台等待恢复。

+
+
+

图 3. 一个 Activity 将多次实例化。

+
+ +

任务是一个有机整体,当用户开始新任务或通过“主页”按钮转到主屏幕时,可以移动到“后台”。 +尽管在后台时,该任务中的所有 Activity 全部停止,但是任务的返回栈仍旧不变,也就是说,当另一个任务发生时,该任务仅仅失去焦点而已,如图 2 中所示。 + + +然后,任务可以返回到“前台”,用户就能够回到离开时的状态。 +例如,假设当前任务(任务 A)的堆栈中有三个 Activity,即当前 Activity 下方还有两个 Activity。 +用户先按“主页”按钮,然后从应用启动器启动新应用。 + +显示主屏幕时,任务 A +进入后台。新应用启动时,系统会使用自己的 Activity 堆栈为该应用启动一个任务(任务 +B)。与该应用交互之后,用户再次返回主屏幕并选择最初启动任务 A 的应用。现在,任务 A 出现在前台,其堆栈中的所有三个 Activity 保持不变,而位于堆栈顶部的 Activity 则会恢复执行。 + + + +此时,用户还可以通过转到主屏幕并选择启动该任务的应用(或者,通过从概览屏幕选择该应用的任务)切换回任务 B。这是 Android 系统中的一个多任务示例。 + + + +

+ +

注:后台可以同时运行多个任务。但是,如果用户同时运行多个后台任务,则系统可能会开始销毁后台 Activity,以回收内存资源,从而导致 Activity 状态丢失。请参阅下面有关 Activity 状态的部分。 + + +

+ +

由于返回栈中的 Activity 永远不会重新排列,因此如果应用允许用户从多个 Activity 中启动特定 Activity,则会创建该 Activity 的新实例并推入堆栈中(而不是将 Activity 的任一先前实例置于顶部)。 + + +因此,应用中的一个 Activity 可能会多次实例化(即使 Activity 来自不同的任务),如图 3 所示。 +因此,如果用户使用“返回”按钮向后导航,则会按 Activity 每个实例的打开顺序显示这些实例(每个实例的 UI 状态各不相同)。 + + +但是,如果您不希望 Activity 多次实例化,则可修改此行为。 +具体操作方法将在后面的管理任务部分中讨论。

+ + +

Activity 和任务的默认行为总结如下:

+ +
    +
  • 当 Activity A 启动 Activity B 时,Activity A 将会停止,但系统会保留其状态(例如,滚动位置和已输入表单中的文本)。如果用户在处于 Activity B 时按“返回”按钮,则 Activity A 将恢复其状态,继续执行。 + + +
  • +
  • 用户通过按“主页”按钮离开任务时,当前 Activity 将停止且其任务会进入后台。 + +系统将保留任务中每个 Activity 的状态。如果用户稍后通过选择开始任务的启动器图标来恢复任务,则任务将出现在前台并恢复执行堆栈顶部的 Activity。 + +
  • +
  • 如果用户按“返回”按钮,则当前 Activity 会从堆栈弹出并被销毁。 + +堆栈中的前一个 Activity 恢复执行。销毁 Activity 时,系统绝对不会保留该 Activity 的状态。 +
  • +
  • 即使来自其他任务,Activity 也可以多次实例化。
  • +
+ + +
+

导航设计

+

如需了解有关 Android 应用导航工作方式的详细信息,请阅读 Android 设计的导航指南。

+
+ + +

保存 Activity 状态

+ +

正如上文所述,当 Activity 停止时,系统的默认行为会保留其状态。 +这样一来,当用户导航回到上一个 Activity 时,其用户界面与用户离开时一样。 +但是,在 Activity 被销毁且必须重建时,您可以而且应当主动使用回调方法保留 Activity 的状态。 + +

+ +

系统停止您的一个 Activity 时(例如,新 Activity 启动或任务转到前台),如果系统需要回收系统内存资源,则可能会完全销毁该 Activity。 + +发生这种情况时,有关该 Activity 状态的信息将会丢失。如果发生这种情况,系统仍会知道该 Activity 存在于返回栈中,但是当该 Activity 被置于堆栈顶部时,系统一定会重建 Activity(而不是恢复 Activity)。 + + +为了避免用户的工作丢失,您应主动通过在 Activity 中实现 +{@link android.app.Activity#onSaveInstanceState onSaveInstanceState()} +回调方法来保留工作。 +

+ +

如需了解有关如何保存 Activity 状态的详细信息,请参阅Activity文档。 +

+ + + +

管理任务

+ +

Android 管理任务和返回栈的方式(如上所述,即:将所有连续启动的 Activity 放入同一任务和“后进先出”堆栈中)非常适用于大多数应用,而您不必担心 Activity 如何与任务关联或者如何存在于返回栈中。 + + +但是,您可能会决定要中断正常行为。 +也许您希望应用中的 Activity 在启动时开始新任务(而不是放置在当前任务中);或者,当启动 Activity 时,您希望将其现有实例上移一层(而不是在返回栈的顶部创建新实例);或者,您希望在用户离开任务时,清除返回栈中除根 Activity 以外的所有其他 Activity。 + + + +

+ +

通过使用 {@code <activity>} 清单文件元素中的属性和传递给 {@link android.app.Activity#startActivity startActivity()} +的 Intent 中的标志,您可以执行所有这些操作以及其他操作。 + +

+ +

在这一方面,您可以使用的主要 +{@code <activity>} 属性包括:

+ + + +

您可以使用的主要 Intent 标志包括:

+ +
    +
  • {@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK}
  • +
  • {@link android.content.Intent#FLAG_ACTIVITY_CLEAR_TOP}
  • +
  • {@link android.content.Intent#FLAG_ACTIVITY_SINGLE_TOP}
  • +
+ +

在下文中,您将了解如何使用这些清单文件属性和 Intent +标志定义 Activity 与任务的关联方式,以及 Activity 在返回栈中的行为方式。

+ +

此外,我们还单独介绍了有关如何在概览屏幕中显示和管理任务与 Activity 的注意事项。 +如需了解详细信息,请参阅概览屏幕。 +通常,您应该允许系统定义任务和 Activity 在概览屏幕中的显示方法,并且无需修改此行为。 +

+ +

注意:大多数应用都不得中断 Activity 和任务的默认行为: +如果确定您的 Activity 必须修改默认行为,当使用“返回”按钮从其他 Activity 和任务导航回到该 Activity 时,请务必要谨慎并确保在启动期间测试该 Activity 的可用性。请确保测试导航行为是否有可能与用户的预期行为冲突。 + + +

+ + +

定义启动模式

+ +

启动模式允许您定义 Activity 的新实例如何与当前任务关联。 +您可以通过两种方法定义不同的启动模式:

+
    +
  • 使用清单文件 +

    在清单文件中声明 Activity 时,您可以指定 Activity 在启动时应该如何与任务关联。 +

  • +
  • 使用 Intent 标志 +

    调用 {@link android.app.Activity#startActivity startActivity()} +时,可以在 {@link android.content.Intent} +中加入一个标志,用于声明新 Activity 如何(或是否)与当前任务关联。

  • +
+ +

因此,如果 Activity +A 启动 Activity B,则 Activity B 可以在其清单文件中定义它应该如何与当前任务关联(如果可能),并且 Activity A 还可以请求 Activity B +应该如何与当前任务关联。如果这两个 Activity 均定义 Activity B +应该如何与任务关联,则 Activity A 的请求(如 Intent 中所定义)优先级要高于 Activity +B 的请求(如其清单文件中所定义)。

+ +

注:某些适用于清单文件的启动 +模式不可用作 Intent 标志,同样,某些可用作 Intent +标志的启动模式无法在清单文件中定义。

+ + +

使用清单文件

+ +

在清单文件中声明 Activity 时,您可以使用 {@code <activity>} +元素的 {@code +launchMode} +属性指定 Activity 应该如何与任务关联。

+ +

{@code +launchMode} +属性指定有关应如何将 Activity 启动到任务中的指令。您可以分配给 +launchMode +属性的启动模式共有四种:

+ +
+
{@code "standard"}(默认模式)
+
默认。系统在启动 Activity 的任务中创建 Activity 的新实例并向其传送 + Intent。Activity 可以多次实例化,而每个实例均可属于不同的任务,并且一个任务可以拥有多个实例。 +
+
{@code "singleTop"}
+
如果当前任务的顶部已存在 Activity 的一个实例,则系统会通过调用该实例的 +{@link +android.app.Activity#onNewIntent onNewIntent()} +方法向其传送 Intent,而不是创建 Activity 的新实例。Activity 可以多次实例化,而每个实例均可属于不同的任务,并且一个任务可以拥有多个实例(但前提是位于返回栈顶部的 Activity 并不是 Activity 的现有实例)。 + + +

例如,假设任务的返回栈包含根 Activity A 以及 Activity B、C +和位于顶部的 D(堆栈是 A-B-C-D;D 位于顶部)。收到针对 D 类 Activity 的 Intent。如果 D +具有默认的 {@code "standard"} 启动模式,则会启动该类的新实例,且堆栈会变成 A-B-C-D-D。但是,如果 D 的启动模式是 {@code "singleTop"},则 D +的现有实例会通过 {@link +android.app.Activity#onNewIntent onNewIntent()} 接收 Intent,因为它位于堆栈的顶部;而堆栈仍为 +A-B-C-D。但是,如果收到针对 A 类 Activity 的 Intent,则会向堆栈添加 B 的新实例,即便其启动模式为 {@code "singleTop"} 也是如此。 + +

+

注:为某个 Activity 创建新实例时,用户可以按“返回”按钮返回到前一个 Activity。 +但是,当 Activity 的现有实例处理新 + Intent 时,则在新 Intent 到达 +{@link android.app.Activity#onNewIntent +onNewIntent()} +之前,用户无法按“返回”按钮返回到 Activity 的状态。 +

+
+ +
{@code "singleTask"}
+
系统创建新任务并实例化位于新任务底部的 Activity。但是,如果该 Activity 的一个实例已存在于一个单独的任务中,则系统会通过调用现有实例的 {@link +android.app.Activity#onNewIntent +onNewIntent()} 方法向其传送 + Intent,而不是创建新实例。一次只能存在 Activity 的一个实例。 + +

注:尽管 Activity 在新任务中启动,但是用户按“返回”按钮仍会返回到前一个 Activity。 +

+
{@code "singleInstance"}。
+
与 +{@code "singleTask"} 相同,只是系统不会将任何其他 Activity 启动到包含实例的任务中。该 Activity 始终是其任务唯一仅有的成员;由此 Activity 启动的任何 Activity 均在单独的任务中打开。 +
+
+ + +

我们再来看另一示例,Android 浏览器 +应用声明 Web 浏览器 Activity 应始终在其自己的任务中打开(通过在 {@code <activity>} 元素中指定 {@code singleTask} +启动模式)。这意味着,如果您的应用发出打开 +Android 浏览器的 + Intent,则其 Activity 与您的应用位于不同的任务中。相反,系统会为浏览器启动新任务,或者如果浏览器 +已有任务正在后台运行,则会将该任务上移一层以处理新 + Intent。

+ +

无论 Activity 是在新任务中启动,还是在与启动 Activity 相同的任务中启动,用户按“返回”按钮始终会转到前一个 Activity。 +但是,如果启动指定 +{@code singleTask} +启动模式的 Activity,则当某后台任务中存在该 Activity 的实例时,整个任务都会转移到前台。此时,返回栈包括上移到堆栈顶部的任务中的所有 Activity。 + +图 4 显示了这种情况。

+ + +

图 4. 显示如何将启动模式为“singleTask”的 Activity 添加到返回栈。 +如果 Activity 已经是某个拥有自己的返回栈的后台任务的一部分,则整个返回栈也会上移到当前任务的顶部。 + +

+ +

如需了解有关在清单文件中使用启动模式的详细信息,请参阅 +<activity> +元素文档,其中更详细地讨论了 {@code launchMode} +属性和可接受的值。

+ +

注:使用 {@code launchMode} +属性为 Activity 指定的行为可由 Intent +附带的 Activity 启动标志替代,下文将对此进行讨论。

+ + + +

使用 Intent 标志

+ +

启动 Activity 时,您可以通过在传递给 {@link +android.app.Activity#startActivity startActivity()} 的 Intent +中加入相应的标志,修改 Activity 与其任务的默认关联方式。可用于修改默认行为的标志包括: +

+ +

+

{@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK}
+
在新任务中启动 Activity。如果已为正在启动的 Activity 运行任务,则该任务会转到前台并恢复其最后状态,同时 Activity 会在 +{@link android.app.Activity#onNewIntent onNewIntent()} 中收到新 + Intent。 +

正如前文所述,这会产生与 {@code "singleTask"} {@code launchMode} +值相同的行为。

+
{@link android.content.Intent#FLAG_ACTIVITY_SINGLE_TOP}
+
如果正在启动的 Activity 是当前 Activity(位于返回栈的顶部),则 +现有实例会接收对 {@link android.app.Activity#onNewIntent onNewIntent()} +的调用,而不是创建 Activity 的新实例。 +

正如前文所述,这会产生与 {@code "singleTop"} {@code launchMode} +值相同的行为。

+
{@link android.content.Intent#FLAG_ACTIVITY_CLEAR_TOP}
+
如果正在启动的 Activity 已在当前任务中运行,则会销毁当前任务顶部的所有 Activity,并通过 {@link android.app.Activity#onNewIntent onNewIntent()} +将此 Intent 传递给 Activity 已恢复的实例(现在位于顶部),而不是启动该 Activity 的新实例。 + + +

产生这种行为的 {@code launchMode} +属性没有值。

+

{@code FLAG_ACTIVITY_CLEAR_TOP} 通常与 +{@code FLAG_ACTIVITY_NEW_TASK} +结合使用。一起使用时,通过这些标志,可以找到其他任务中的现有 Activity,并将其放入可从中响应 Intent +的位置。

+

注:如果指定 Activity 的启动模式为 +{@code "standard"},则该 Activity 也会从堆栈中删除,并在其位置启动一个新实例,以便处理传入的 Intent。 + +这是因为当启动模式为 {@code "standard"} +时,将始终为新 Intent 创建新实例。

+
+ + + + + + +

处理关联

+ +

“关联”指示 Activity 优先属于哪个任务。默认情况下,同一应用中的所有 Activity 彼此关联。 +因此,默认情况下,同一应用中的所有 Activity 优先位于相同任务中。 +不过,您可以修改 Activity 的默认关联。 +在不同应用中定义的 Activity 可以共享关联,或者可为在同一应用中定义的 Activity 分配不同的任务关联。 + +

+ +

可以使用 {@code <activity>} +元素的 +{@code taskAffinity} 属性修改任何给定 Activity 的关联。

+ +

{@code taskAffinity} +属性取字符串值,该值必须不同于 +在 +{@code <manifest>} +元素中声明的默认软件包名称,因为系统使用该名称标识应用的默认任务关联。 +

+ +

在两种情况下,关联会起作用:

+
    +
  • 启动 Activity 的 Intent 包含 +{@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK} +标志。 + +

    默认情况下,新 Activity 会启动到调用 +{@link android.app.Activity#startActivity startActivity()} 的 Activity 任务中。它将推入与调用方相同的返回栈。 +但是,如果传递给 +{@link android.app.Activity#startActivity startActivity()} +的 Intent 包含 {@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK} +标志,则系统会寻找其他任务来储存新 Activity。这通常是新任务,但未做强制要求。 +如果现有任务与新 Activity 具有相同关联,则会将 Activity 启动到该任务中。 +否则,将开始新任务。

    + +

    如果此标志导致 Activity 开始新任务,且用户按“主页”按钮离开,则必须为用户提供导航回任务的方式。 + +有些实体(如通知管理器)始终在外部任务中启动 Activity,而从不作为其自身的一部分启动 Activity,因此它们始终将 +{@code FLAG_ACTIVITY_NEW_TASK} 放入传递给 +{@link android.app.Activity#startActivity startActivity()} +的 Intent 中。请注意,如果 Activity 能够由可以使用此标志的外部实体调用,则用户可以通过独立方式返回到启动的任务,例如,使用启动器图标(任务的根 Activity 具有 +{@link android.content.Intent#CATEGORY_LAUNCHER} + Intent 过滤器;请参阅下面的启动任务部分)。 + +

    +
  • + +
  • Activity 将其 +{@code allowTaskReparenting} 属性设置为 {@code "true"}。 +

    在这种情况下,Activity 可以从其启动的任务移动到与其具有关联的任务(如果该任务出现在前台)。 +

    +

    例如,假设将报告所选城市天气状况的 Activity 定义为旅行应用的一部分。 +它与同一应用中的其他 Activity 具有相同的关联(默认应用关联),并允许利用此属性重定父级。当您的一个 Activity 启动天气预报 Activity 时,它最初所属的任务与您的 Activity 相同。 + + +但是,当旅行应用的任务出现在前台时,系统会将天气预报 Activity 重新分配给该任务并显示在其中。 +

    +
  • +
+ +

提示:如果从用户的角度来看,一个 {@code .apk} +文件包含多个“应用”,则您可能需要使用 {@code taskAffinity} +属性将不同关联分配给与每个“应用”相关的 Activity。

+ + + +

清理返回栈

+ +

如果用户长时间离开任务,则系统会清除所有 Activity 的任务,根任务除外。 +当用户再次返回到任务时,仅恢复根 Activity。系统这样做的原因是,经过很长一段时间后,用户可能已经放弃之前执行的操作,返回到任务是要开始执行新的操作。 + +

+ +

您可以使用下列几个 Activity 属性修改此行为:

+ +
+
alwaysRetainTaskState +
+
如果在任务的根 Activity 中将此属性设置为 {@code "true"},则不会发生刚才所述的默认行为。即使在很长一段时间后,任务仍将所有 Activity 保留在其堆栈中。 + +
+ +
clearTaskOnLaunch
+
如果在任务的根 Activity 中将此属性设置为 {@code "true"},则每当用户离开任务然后返回时,系统都会将堆栈清除到只剩下根 Activity。 + +换而言之,它与 +{@code alwaysRetainTaskState} 正好相反。 +即使只离开任务片刻时间,用户也始终会返回到任务的初始状态。 +
+ +
finishOnTaskLaunch +
+
此属性类似于 +{@code clearTaskOnLaunch},但它对单个 Activity 起作用,而非整个任务。 +此外,它还有可能会导致任何 Activity 停止,包括根 Activity。 +设置为 {@code "true"} +时,Activity 仍是任务的一部分,但是仅限于当前会话。如果用户离开然后返回任务,则任务将不复存在。 +
+
+ + + + +

启动任务

+ +

通过为 Activity 提供一个以 {@code "android.intent.action.MAIN"} +为指定操作、以{@code "android.intent.category.LAUNCHER"} +为指定类别的 Intent 过滤器,您可以将活动设置为任务的入口点。 +例如:

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

此类 Intent 过滤器会使 Activity 的图标和标签显示在应用启动器中,让用户能够启动 Activity 并在启动之后随时返回到创建的任务中。 + + +

+ +

第二个功能非常重要:用户必须能够在离开任务后,再使用此 Activity 启动器返回该任务。 +因此,只有在 Activity 具有 +{@link android.content.Intent#ACTION_MAIN} +和 {@link android.content.Intent#CATEGORY_LAUNCHER} +过滤器时,才应该使用将 Activity 标记为“始终启动任务”的两种启动模式,即 {@code "singleTask"} 和 +{@code "singleInstance"}。例如,我们可以想像一下如果缺少过滤器会发生什么情况: + Intent 启动一个 {@code "singleTask"} +Activity,从而启动一个新任务,并且用户花了些时间处理该任务。然后,用户按主页按钮。 +任务现已发送到后台,而且不可见。现在,用户无法返回到任务,因为该任务未显示在应用启动器中。 +

+ +

如果您并不想用户能够返回到 Activity,对于这些情况,请将 +<activity> +元素的 +{@code finishOnTaskLaunch} +设置为 {@code "true"}(请参阅清理堆栈)。

+ +

有关如何在概览屏幕中显示和管理任务与 Activity 的更多信息,请参阅概览屏幕。 + +

+ + diff --git a/docs/html-intl/intl/zh-cn/guide/index.jd b/docs/html-intl/intl/zh-cn/guide/index.jd new file mode 100644 index 0000000000000000000000000000000000000000..01ab3478a0f602e5a7c86933a826a567fea6e9cc --- /dev/null +++ b/docs/html-intl/intl/zh-cn/guide/index.jd @@ -0,0 +1,74 @@ +page.title=Android 简介 + +@jd:body + + + + +

Android +提供了一个内容丰富的应用框架,支持您在 Java 语言环境中为移动设备开发创新应用和游戏。在左侧导航窗格列出的文档中,提供了有关如何使用各种 +Android API 开发应用的详细信息。

+ +

如果您是 Android 应用开发新手,则需了解以下有关 +Android 应用框架的基本概念,这一点至关重要:

+ + +
+ +
+ +

应用提供多个入口点

+ +

Android +应用都是将各种可单独调用的不同组件加以组合构建而成。例如,组件可以是为用户界面提供一个屏幕的单个“Activity”,也可以是在后台独立执行工作的“服务”。 + +

+ +

您可以使用 Intent 从一个组件启动另一个组件。甚至,您还可以启动不同应用中的组件,例如,启动地图应用中的 Activity 以显示地址。 +此模式可为单个应用提供多个入口点,并使任何应用均能够像用户“默认设置”一样处理其他应用可能调用的操作。 + +

+ + +

了解详情

+ + +
+ + +
+ +

应用可适应不同的设备

+ +

Android +提供了一个自适应应用框架,您可以利用它为不同的设备配置提供独特的资源。例如,您可以针对不同的屏幕尺寸创建不同的 +XML +布局文件,系统将根据当前设备的屏幕尺寸确定要应用的布局。

+ +

如有任何应用功能需要相机等特定的硬件,则可在运行时查询设备功能的可用性。 +如有必要,您还可以声明您的应用所必需的功能,使 +Google Play +商店等应用市场不得在不支持这些功能的设备上安装您的应用。

+ + +

了解详情

+ + +
+ +
+ + + diff --git a/docs/html-intl/intl/zh-cn/guide/topics/manifest/manifest-intro.jd b/docs/html-intl/intl/zh-cn/guide/topics/manifest/manifest-intro.jd new file mode 100644 index 0000000000000000000000000000000000000000..c7ade4f392dbfd7679384f08471097a25e0b73f7 --- /dev/null +++ b/docs/html-intl/intl/zh-cn/guide/topics/manifest/manifest-intro.jd @@ -0,0 +1,517 @@ +page.title=应用清单文件 +@jd:body + + + +

+ 每个应用的根目录中都必须包含一个 +AndroidManifest.xml 文件(且文件名精确无误)。 清单文件为 +Android 系统提供有关您的应用的基本信息,系统必须获得这些信息才能运行任意应用代码。 + + 此外,清单文件还可执行以下操作: +

+ +
    +
  • 为应用的 Java +软件包命名。软件包名称充当应用的唯一标识符
  • + +
  • 描述应用的各个组件,即:构成应用的 Activity、服务、广播接收器和内容提供程序。 + +为实现每个组件的类命名并发布其功能(例如,它们可以处理的 +{@link android.content.Intent +Intent} 消息)。根据这些声明,Android +系统可以了解这组件具体是什么,以及在什么条件下可以启动它们
  • + +
  • 确定将托管应用组件的进程
  • + +
  • 声明应用必须具备哪些权限才能访问 +API 中受保护的部分并与其他应用交互
  • + +
  • 还声明其他应用与该应用组件交互所需具备的权限 +
  • + +
  • 列出 {@link android.app.Instrumentation} +类,这些类可在应用运行期间提供分析和其他信息。这些声明只会在应用处在开发和测试阶段时出现在清单文件中;它们会在应用发布之前被删除 + +
  • + +
  • 声明应用所需的最低 Android API +级别
  • + +
  • 列出应用必须链接到的库
  • +
+ + +

清单文件结构

+ +

+下图显示了清单文件的通用结构及其可包含的每个元素。 +每个元素及其所有属性全部记录在一个单独的文件中。 +要查看有关任何元素的详细信息,请点击该图中或其后按字母顺序排列的元素列表中相应的元素名称,或者点击任何其他地方提到的相应元素名称。 + + + +

+ +
+<?xml version="1.0" encoding="utf-8"?>
+
+<manifest>
+
+    <uses-permission />
+    <permission />
+    <permission-tree />
+    <permission-group />
+    <instrumentation />
+    <uses-sdk />
+    <uses-configuration />  
+    <uses-feature />  
+    <supports-screens />  
+    <compatible-screens />  
+    <supports-gl-texture />  
+
+    <application>
+
+        <activity>
+            <intent-filter>
+                <action />
+                <category />
+                <data />
+            </intent-filter>
+            <meta-data />
+        </activity>
+
+        <activity-alias>
+            <intent-filter> . . . </intent-filter>
+            <meta-data />
+        </activity-alias>
+
+        <service>
+            <intent-filter> . . . </intent-filter>
+            <meta-data/>
+        </service>
+
+        <receiver>
+            <intent-filter> . . . </intent-filter>
+            <meta-data />
+        </receiver>
+
+        <provider>
+            <grant-uri-permission />
+            <meta-data />
+            <path-permission />
+        </provider>
+
+        <uses-library />
+
+    </application>
+
+</manifest>
+
+ +

+可出现在清单文件中的所有元素按字母顺序罗列如下。 +这些是仅有的合法元素;您无法添加自己的元素或属性。 + +

+ +

+<action> +
<activity> +
<activity-alias> +
<application> +
<category> +
<data> +
<grant-uri-permission> +
<instrumentation> +
<intent-filter> +
<manifest> +
<meta-data> +
<permission> +
<permission-group> +
<permission-tree> +
<provider> +
<receiver> +
<service> +
<supports-screens> +
<uses-configuration> +
<uses-feature> +
<uses-library> +
<uses-permission> +
<uses-sdk> +

+ + + + +

文件约定

+ +

+有些约定和规则普遍适用于清单文件中的所有元素和属性: + +

+ +
+
元素
+
只有 +<manifest> 和 +<application> +元素是必需的,它们都必须存在并且只能出现一次。其他大部分元素可以出现多次或者根本不出现,即便清单文件中必须至少存在其中某些元素才能完成任何有意义的操作也是如此。 + + + + +

+如果一个元素包含某些内容,也就包含其他元素。所有值均通过属性进行设置,而不是通过元素内的字符数据设置。 + +

+ +

+同一级别的元素通常不分先后顺序。例如,<activity><provider> +和 +<service> +元素可以按任何顺序混合在一起。 +(<activity-alias> +元素不适用此规则: +它必须跟在别名所指的 +<activity> +之后。) +

+ +
属性
+
从某种意义上说,所有属性都是可选的。但是,有些属性必须指定给元素以实现其目的。 +请使用本文档作为参考。 +对于真正可选的属性,它将指定默认值或声明缺乏规范时将执行何种操作。 + + +

除了根 +<manifest> +元素的一些属性外,所有属性名称均以 {@code android:} +前缀开头,例如,{@code android:alwaysRetainTaskState}。由于该前缀是通用的,因此在按名称引用属性时,本文档通常会将其忽略。 + +

+ +
声明类名
+
许多元素对应于 +Java +对象,其中包括应用本身的元素(<application> +元素)及其主要组件 +— +Activity +(<activity>)、服务 +(<service>)、广播接收器 +(<receiver>) +以及内容提供程序 +(<provider>)。 + +

+如果按照您针对组件类({@link android.app.Activity}、{@link android.app.Service}、{@link android.content.BroadcastReceiver} +和 +{@link android.content.ContentProvider})几乎一直采用的方式来定义子类,则该子类需通过 {@code name} 属性来声明。 +该名称必须包含完整的软件包名称。 +例如,{@link android.app.Service} +子类可能会声明如下: +

+ +
<manifest . . . >
+    <application . . . >
+        <service android:name="com.example.project.SecretService" . . . >
+            . . .
+        </service>
+        . . .
+    </application>
+</manifest>
+ +

+但是,为了简便起见,如果字符串的第一个字符是句点,则字符串将追加到应用的软件包名称(正如 +<manifest> +元素的 +package +属性中所指定)。 +以下赋值与上述方法相同: +

+ +
<manifest package="com.example.project" . . . >
+    <application . . . >
+        <service android:name=".SecretService" . . . >
+            . . .
+        </service>
+        . . .
+    </application>
+</manifest>
+ +

+当启动组件时,Android 会创建已命名子类的实例。如果未指定子类,则会创建基类的实例。 + +

+ +
多个值
+
如果可以指定多个值,则几乎总是在重复此元素,而不是列出单个元素内的多个值。 +例如,Intent 过滤器可以列出多个操作: + + +
<intent-filter . . . >
+    <action android:name="android.intent.action.EDIT" />
+    <action android:name="android.intent.action.INSERT" />
+    <action android:name="android.intent.action.DELETE" />
+    . . .
+</intent-filter>
+ +
资源值
+
某些属性的值可以显示给用户,例如,Activity 的标签和图标。 +这些属性的值应该本地化,因此需要通过资源或主题进行设置。 +资源值用以下格式表示: +

+ +

{@code @[package:]type:name}

+ +

+其中,如果资源与应用位于同一软件包中,则可忽略 package 名称; + type 是资源类型,如“字串符”或“图片”; + name 是标识特定资源的名称。例如: + +

+ +
<activity android:icon="@drawable/smallPic" . . . >
+ +

+主题中的值用类似的方法表示,但是以“{@code ?}”开头而不是“{@code @}”: + +

+ +

{@code ?[package:]type:name} +

+ +
字串符值
+
如果属性值为字串符,则必须使用双反斜杠(“{@code \\}”) +转义字符。例如,使用“{@code \\n}”表示换行符或使用“{@code \\uxxxx}”表示 +Unicode 字符)。
+
+ + +

文件功能

+ +

+下文介绍如何在清单文件中利用某些 +Android 特性。 +

+ + +

Intent 过滤器

+ +

+应用的核心组件(其 Activity、服务和广播接收器)由 + Intent 激活。Intent 是一系列用于描述所需操作的信息({@link android.content.Intent} +对象),其中包括要执行操作的数据、应执行操作的组件类别以及其他相关说明。 + +Android +会找到合适的组件来响应 + Intent,根据需要启动组件的新实例,并将其传递到 + Intent 对象。 +

+ +

+组件将通过“Intent 过滤器”公布其功能,即它们可响应的 Intent 类型。 + 由于 +Android 系统在启动某组件之前必须了解该组件可以处理哪些 Intent,因此 Intent 过滤器在清单文件中被指定为 +<intent-filter> +元素。 +一个组件可能有任意数量的过滤器,其中每个过滤器描述一种不同的功能。 + +

+ +

+显式命名目标组件的 + Intent 将激活该组件;过滤器不起作用。但是,不按名称指定目标的 + Intent +只有在能够通过组件的一个过滤器时才可激活该组件。 +

+ +

+有关如何根据 + Intent 过滤器测试 + Intent 对象的信息,请参阅单独的文档 + Intent 和 Intent 过滤器。 +

+ + +

图标和标签

+ +

+对于可以显示给用户的小图标和文本标签,大量元素具有 {@code icon} 和 {@code label} +属性。此外,对于同样可以显示在屏幕上的较长说明文本,某些元素还具有 +{@code description} +属性。例如,<permission> +元素具有所有这三个属性。因此,当系统询问用户是否授权给请求获得权限的应用时,权限图标、权限名称以及所需信息的说明均可呈现给用户。 + + + + +

+ +

+无论何种情况下,在包含元素中设置的图标和标签都将成为所有容器子元素的默认 +{@code icon} 和 {@code label} 设置。因此,在 +<application> +元素中设置的图标和标签是每个应用组件的默认图标和标签。 +同样,为组件(例如,<activity> +元素)设置的图标和标签是组件每个 +<intent-filter> +元素的默认设置。 + +如果 <application> 元素设置标签,但是 Activity 及其 + Intent 过滤器不执行此操作,则应用标签将被视为 Activity 和 + Intent 过滤器的标签。 + + +

+ +

+在实现 Intent 过滤器公布的功能时,只要向用户呈现组件,系统便会使用为过滤器设置的图标和标签表示该组件。 + +例如,具有“{@code android.intent.action.MAIN}”和“{@code android.intent.category.LAUNCHER}”设置的过滤器将 Activity 公布为可启动应用的功能,即,公布为应显示在应用启动器中的功能。 + + + +因此,在过滤器中设置的图标和标签就是显示在启动器中的图标和标签。 + +

+ + +

权限

+ +

+ 权限 是一种限制,用于限制对部分代码或设备上数据的访问。 + 施加限制是为了保护可能被误用以致破坏或损害用户体验的关键数据和代码。 + +

+ +

+每种权限均由一个唯一的标签标识。标签通常指示受限制的操作。 +例如,以下是由 +Android 定义的一些权限: +

+ +

{@code android.permission.CALL_EMERGENCY_NUMBERS} +
{@code android.permission.READ_OWNER_DATA} +
{@code android.permission.SET_WALLPAPER} +
{@code android.permission.DEVICE_POWER}

+ +

+一个功能最多只能由一种权限保护。 +

+ +

+如果应用需要访问受权限保护的功能,则必须在清单文件中使用 +<uses-permission> +元素声明应用需要该权限。 +但是,将应用安装到设备上之后,安装程序会通过检查签署应用证书的颁发机构并(在某些情况下)询问用户,确定是否授予请求的权限。 + + +如果授予权限,则应用能够使用受保护的功能。 + +否则,其访问这些功能的尝试将会失败,并且不会向用户发送任何通知。 + +

+ +

+此外,应用也可以使用权限保护自己的组件(Activity、服务、广播接收器和内容提供程序)。 +它可以采用由 +Android +定义(如 +{@link android.Manifest.permission android.Manifest.permission} 中所列)或由其他应用声明的任何权限。或者,它也可以定义自己的权限。新权限用 +<permission> +元素来声明。 +例如,Activity 可受到如下保护: +

+ +
+<manifest . . . >
+    <permission android:name="com.example.project.DEBIT_ACCT" . . . />
+    <uses-permission android:name="com.example.project.DEBIT_ACCT" />
+    . . .
+    <application . . .>
+        <activity android:name="com.example.project.FreneticActivity"
+                  android:permission="com.example.project.DEBIT_ACCT"
+                  . . . >
+            . . .
+        </activity>
+    </application>
+</manifest>
+
+ +

+请注意,在此示例中,{@code DEBIT_ACCT} +权限不仅是通过 +<permission> +元素来声明,而且其使用也是通过 +<uses-permission> +元素来请求。要让应用的其他组件也能够启动受保护的 Activity,就必须请求其使用权限,即便保护是由应用本身施加的亦如此。 + + +

+ +

+同样还是在此示例中,如果将 +{@code permission} +属性设置为在其他位置(例如,{@code android.permission.CALL_EMERGENCY_NUMBERS})声明的权限,则无需使用 +<permission> +元素再次声明。 +但是,仍有必要通过 +<uses-permission> 请求使用它。 +

+ +

+<permission-tree> +元素为一组将在代码中定义的权限声明命名空间。 + +同时, +<permission-group> +为一组权限(包括在清单文件中使用 +<permission> +元素声明的权限以及在其他位置声明的权限)定义标签。它只影响如何对提供给用户的权限进行分组。 +<permission-group> +元素并不指定哪些权限属于该组,而只是为组提供名称。 + +通过向 +<permission> +元素的 +permissionGroup +属性分配组名,将权限放入组中。 + +

+ + +

+ +

+每个应用均链接到默认的 Android 库,该库中包括构建应用(以及通用类,如 Activity、服务、 Intent 、视图、按钮、应用、ContentProvider 等)的基本软件包。 + + + +

+ +

+但是,某些软件包驻留在自己的库中。如果应用使用来自其中任一软件包的代码,则必须明确要求其链接到这些软件包。 + +清单文件必须包含单独的 +<uses-library> +元素来命名其中每个库。(库名称可在软件包的文档中找到。) + +

diff --git a/docs/html-intl/intl/zh-cn/guide/topics/providers/calendar-provider.jd b/docs/html-intl/intl/zh-cn/guide/topics/providers/calendar-provider.jd new file mode 100644 index 0000000000000000000000000000000000000000..59682843897da6c49f4ffb8cc3c6d5c1ff5904cf --- /dev/null +++ b/docs/html-intl/intl/zh-cn/guide/topics/providers/calendar-provider.jd @@ -0,0 +1,1184 @@ +page.title=日历提供程序 +@jd:body + +
+
+

本文内容

+
    +
  1. 基础知识
  2. +
  3. 用户权限
  4. +
  5. 日历表 +
      +
    1. 查询日历
    2. +
    3. 修改日历
    4. +
    5. 插入日历
    6. +
    +
  6. +
  7. 事件表 +
      +
    1. 添加事件
    2. +
    3. 更新事件
    4. +
    5. 删除事件
    6. +
    +
  8. +
  9. 参加者表 +
      +
    1. 添加参加者
    2. +
    +
  10. +
  11. 提醒表 +
      +
    1. 添加提醒
    2. +
    +
  12. +
  13. 实例表 +
      +
    1. 查询实例表
    2. +
  14. +
  15. 日历 Intent 对象 +
      +
    1. 使用 Intent 对象插入事件
    2. +
    3. 使用 Intent 对象编辑事件
    4. +
    5. 使用 Intent 对象查看日历数据
    6. +
    +
  16. + +
  17. 同步适配器
  18. +
+ +

关键类

+
    +
  1. {@link android.provider.CalendarContract.Calendars}
  2. +
  3. {@link android.provider.CalendarContract.Events}
  4. +
  5. {@link android.provider.CalendarContract.Attendees}
  6. +
  7. {@link android.provider.CalendarContract.Reminders}
  8. +
+
+
+ +

日历提供程序是用户日历事件的存储库。您可以利用 +Calendar Provider API +对日历、事件、参加者、提醒等执行查询、插入、更新和删除操作。

+ + +

Calender Provider API 可供应用和同步适配器使用。规则因进行调用的程序类型而异。 +本文主要侧重于介绍使用 +Calendar Provider API 作为应用的情况。如需了解对各类同步适配器差异的阐述,请参阅同步适配器。 + +

+ + +

正常情况下,要想读取或写入日历数据,应用的清单文件必须包括用户权限中所述的适当权限。 + +为简化常见操作的执行,日历提供程序提供了一组 Intent 对象,日历 Intent 对象中对这些 Intent 做了说明。 + +这些 Intent 对象会将用户转到日历应用,执行插入事件、查看事件和编辑事件操作。 +用户与日历应用交互,然后返回原来的应用。 +因此,您的应用不需要请求权限,也不需要提供用于查看事件或创建事件的用户界面。 +

+ +

基础知识

+ +

内容提供程序存储数据并使其可供应用访问。 +Android +平台提供的内容提供程序(包括日历提供程序)通常以一组基于关系数据库模型的表格形式公开数据,在这个表格中,每一行都是一条记录,每一列都是特定类型和含义的数据。 +应用和同步适配器可以通过 +Calendar Provider API +获得对储存用户日历数据的数据库表的读取/写入权限。

+ +

每一个内容提供程序都会公开一个对其数据集进行唯一标识的公共 +URI(包装成一个 {@link android.net.Uri} 对象)。 +控制多个数据集(多个表)的内容提供程序会为每个数据集公开单独的 URI。 +所有提供程序 URI 都以字符串“content://”开头。 +这表示数据受内容提供程序的控制。 +日历提供程序会为其每个类(表)定义 +URI 常量。这些 +URI 的格式为 <class>.CONTENT_URI。例如,{@link android.provider.CalendarContract.Events#CONTENT_URI Events.CONTENT_URI}。 +

+ +

图 1 是对日历提供程序数据模型的图形化表示。它显示了将彼此链接在一起的主要表和字段。 +

+ +Calendar Provider Data Model +

图 1. 日历提供程序数据模型。

+ +

用户可以有多个日历,可将不同类型的日历与不同类型的帐户(Google 日历、Exchange 等)关联。

+ +

{@link android.provider.CalendarContract} 定义了日历和事件相关信息的数据模型。这些数据存储在以下所列的若干表中。

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
表(类)描述

{@link android.provider.CalendarContract.Calendars}

此表储存日历特定信息。 +此表中的每一行都包含一个日历的详细信息,例如名称、颜色、同步信息等。 +
{@link android.provider.CalendarContract.Events}此表储存事件特定信息。 +此表中的每一行都包含一个事件的信息—例如事件名称、地点、开始时间、结束时间等。 + +事件可一次性发生,也可多次重复发生。参加者、提醒和扩展属性存储在单独的表内。 +它们各自具有一个 {@link android.provider.CalendarContract.AttendeesColumns#EVENT_ID},用于引用 Events 表中的 {@link android.provider.BaseColumns#_ID}。 + +
{@link android.provider.CalendarContract.Instances}此表储存每个事件实例的开始时间和结束时间。 +此表中的每一行都表示一个事件实例。 +对于一次性事件,实例与事件为 1:1 +映射。对于重复事件,会自动生成多个行,分别对应多个事件实例。 +
{@link android.provider.CalendarContract.Attendees}此表储存事件参加者(来宾)信息。 +每一行都表示事件的一位来宾。 +它指定来宾的类型以及事件的来宾出席响应。 +
{@link android.provider.CalendarContract.Reminders}此表储存提醒/通知数据。 +每一行都表示事件的一个提醒。一个事件可以有多个提醒。 +每个事件的最大提醒数量在 + + +{@link android.provider.CalendarContract.CalendarColumns#MAX_REMINDERS} +中指定,后者由拥有给定日历的同步适配器设置。提醒以事件发生前的分钟数形式指定,其具有一个可决定用户提醒方式的方法。 +
+ +

Calendar Provider API 以灵活、强大为设计宗旨。提供良好的最终用户体验以及保护日历及其数据的完整性也同样重要。 + +因此,请在使用该 API +时牢记以下要点:

+ +
    + +
  • 插入、更新和查看日历事件。要想直接从日历提供程序插入事件、修改事件以及读取事件,您需要具备相应权限。不过,如果您开发的并不是完备的日历应用或同步适配器,则无需请求这些权限。您可以改用 Android 的日历应用支持的 Intent 对象将读取操作和写入操作转到该应用执行。当您使用 Intent 对象时,您的应用会将用户转到日历应用,在一个预填充表单中执行所需操作。 +完成操作后,用户将返回您的应用。通过将您的应用设计为通过日历执行常见操作,可以为用户提供一致、可靠的用户界面。 + +这是推荐您采用的方法。 +如需了解详细信息,请参阅日历 Intent 对象。 +

    + + +
  • 同步适配器。同步适配器用于将用户设备上的日历数据与其他服务器或数据源同步。 +在 +{@link android.provider.CalendarContract.Calendars} 和 + +{@link android.provider.CalendarContract.Events} +表中,预留了一些供同步适配器使用的列。提供程序和应用不应修改它们。实际上,除非以同步适配器形式进行访问,否则它们处于不可见状态。 +如需了解有关同步适配器的详细信息,请参阅同步适配器。 +
  • + +
+ + +

用户权限

+ +

如需读取日历数据,应用必须在其清单文件中加入 {@link +android.Manifest.permission#READ_CALENDAR} 权限。文件中必须包括用于删除、插入或更新日历数据的 +{@link android.Manifest.permission#WRITE_CALENDAR} +权限:

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"...>
+    <uses-sdk android:minSdkVersion="14" />
+    <uses-permission android:name="android.permission.READ_CALENDAR" />
+    <uses-permission android:name="android.permission.WRITE_CALENDAR" />
+    ...
+</manifest>
+
+ + +

日历表

+ +

{@link android.provider.CalendarContract.Calendars} 表包含各日历的详细信息。 +应用和同步适配器均可写入下列日历列。 +如需查看所支持字段的完整列表,请参阅 + +{@link android.provider.CalendarContract.Calendars} 参考资料。

+ + + + + + + + + + + + + + + + + + + + + + + + + +
常量描述
{@link android.provider.CalendarContract.Calendars#NAME}日历的名称。
{@link android.provider.CalendarContract.Calendars#CALENDAR_DISPLAY_NAME}该日历显示给用户时使用的名称。
{@link android.provider.CalendarContract.Calendars#VISIBLE}表示是否选择显示该日历的布尔值。值为 0 表示不应显示与该日历关联的事件。 + +值为 1 +表示应该显示与该日历关联的事件。此值影响 {@link +android.provider.CalendarContract.Instances} 表中行的生成。
{@link android.provider.CalendarContract.CalendarColumns#SYNC_EVENTS}一个布尔值,表示是否应同步日历并将其事件存储在设备上。 +值为 0 +表示不同步该日历,也不将其事件存储在设备上。值为 1 +表示同步该日历的事件,并将其事件存储在设备上。
+ +

查询日历

+ +

以下示例说明了如何获取特定用户拥有的日历。 +为了简便起见,在此示例中,查询操作显示在用户界面线程(“主线程”)中。 +实际上,此操作应该在一个异步线程而非主线程中完成。 +如需查看更详细的介绍,请参阅加载器。 +如果您的目的不只是读取数据,还要修改数据,请参阅 {@link android.content.AsyncQueryHandler}。 + +

+ + +
+// Projection array. Creating indices for this array instead of doing
+// dynamic lookups improves performance.
+public static final String[] EVENT_PROJECTION = new String[] {
+    Calendars._ID,                           // 0
+    Calendars.ACCOUNT_NAME,                  // 1
+    Calendars.CALENDAR_DISPLAY_NAME,         // 2
+    Calendars.OWNER_ACCOUNT                  // 3
+};
+  
+// The indices for the projection array above.
+private static final int PROJECTION_ID_INDEX = 0;
+private static final int PROJECTION_ACCOUNT_NAME_INDEX = 1;
+private static final int PROJECTION_DISPLAY_NAME_INDEX = 2;
+private static final int PROJECTION_OWNER_ACCOUNT_INDEX = 3;
+ + + + + +

在示例的下一部分,您需要构建查询。选定范围指定查询的条件。 +在此示例中,查询寻找的是 +ACCOUNT_NAME +为“sampleuser@google.com”、ACCOUNT_TYPE +为“com.google”、OWNER_ACCOUNT +为“sampleuser@google.com”的日历。如果您想查看用户查看过的所有日历,而不只是用户拥有的日历,请省略 +OWNER_ACCOUNT。您可以利用查询返回的 +{@link android.database.Cursor} +对象遍历数据库查询返回的结果集。 +如需查看有关在内容提供程序中使用查询的详细介绍,请参阅内容提供程序。 +

+ + +
// Run query
+Cursor cur = null;
+ContentResolver cr = getContentResolver();
+Uri uri = Calendars.CONTENT_URI;   
+String selection = "((" + Calendars.ACCOUNT_NAME + " = ?) AND (" 
+                        + Calendars.ACCOUNT_TYPE + " = ?) AND ("
+                        + Calendars.OWNER_ACCOUNT + " = ?))";
+String[] selectionArgs = new String[] {"sampleuser@gmail.com", "com.google",
+        "sampleuser@gmail.com"}; 
+// Submit the query and get a Cursor object back. 
+cur = cr.query(uri, EVENT_PROJECTION, selection, selectionArgs, null);
+ +

以下后续部分使用游标单步调试结果集。它使用在示例开头设置的常量来返回每个字段的值。 + +

+ +
// Use the cursor to step through the returned records
+while (cur.moveToNext()) {
+    long calID = 0;
+    String displayName = null;
+    String accountName = null;
+    String ownerName = null;
+      
+    // Get the field values
+    calID = cur.getLong(PROJECTION_ID_INDEX);
+    displayName = cur.getString(PROJECTION_DISPLAY_NAME_INDEX);
+    accountName = cur.getString(PROJECTION_ACCOUNT_NAME_INDEX);
+    ownerName = cur.getString(PROJECTION_OWNER_ACCOUNT_INDEX);
+              
+    // Do something with the values...
+
+   ...
+}
+
+ +

修改日历

+ +

如需执行日历更新,您可以通过 +URI 追加 +ID ({@link android.content.ContentUris#withAppendedId(android.net.Uri,long) withAppendedId()}) 或第一个选定项形式提供日历的 + +{@link +android.provider.BaseColumns#_ID}。选定范围应以 +"_id=?" 开头,并且第一个 +selectionArg 应为日历的 {@link +android.provider.BaseColumns#_ID}。 +您还可以通过在 URI +中编码 ID 来执行更新。下例使用 ({@link android.content.ContentUris#withAppendedId(android.net.Uri,long) withAppendedId()}) 方法更改日历的显示名称: + +

+ +
private static final String DEBUG_TAG = "MyActivity";
+...
+long calID = 2;
+ContentValues values = new ContentValues();
+// The new display name for the calendar
+values.put(Calendars.CALENDAR_DISPLAY_NAME, "Trevor's Calendar");
+Uri updateUri = ContentUris.withAppendedId(Calendars.CONTENT_URI, calID);
+int rows = getContentResolver().update(updateUri, values, null, null);
+Log.i(DEBUG_TAG, "Rows updated: " + rows);
+ +

插入日历

+ +

日历设计为主要由同步适配器进行管理,因此您只应以同步适配器形式插入新日历。 +在大多数情况下,应用只能对日历进行一些表面更改,如更改显示名称。 +如果应用需要创建本地日历,可以利用 +{@link +android.provider.CalendarContract#ACCOUNT_TYPE_LOCAL} +的 +{@link +android.provider.CalendarContract.SyncColumns#ACCOUNT_TYPE},通过以同步适配器形式执行日历插入来实现目的。{@link android.provider.CalendarContract#ACCOUNT_TYPE_LOCAL} +是一种特殊的帐户类型,用于未关联设备帐户的日历。 +这种类型的日历不与服务器同步。如需了解对同步适配器的阐述,请参阅同步适配器。 +

+ +

事件表

+ +

{@link android.provider.CalendarContract.Events} +表包含各事件的详细信息。要想添加、更新或删除事件,应用必须在其清单文件中加入 +{@link android.Manifest.permission#WRITE_CALENDAR} +权限。

+ +

应用和同步适配器均可写入下列事件列。 +如需查看所支持字段的完整列表,请参阅 {@link +android.provider.CalendarContract.Events} 参考资料。

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
常量描述
{@link android.provider.CalendarContract.EventsColumns#CALENDAR_ID}事件所属日历的 {@link android.provider.BaseColumns#_ID}。
{@link android.provider.CalendarContract.EventsColumns#ORGANIZER}事件组织者(所有者)的电子邮件。
{@link android.provider.CalendarContract.EventsColumns#TITLE}事件的名称。
{@link android.provider.CalendarContract.EventsColumns#EVENT_LOCATION}事件的发生地点。
{@link android.provider.CalendarContract.EventsColumns#DESCRIPTION}事件的描述。
{@link android.provider.CalendarContract.EventsColumns#DTSTART}事件开始时间,以从公元纪年开始计算的协调世界时毫秒数表示。
{@link android.provider.CalendarContract.EventsColumns#DTEND}事件结束时间,以从公元纪年开始计算的协调世界时毫秒数表示。
{@link android.provider.CalendarContract.EventsColumns#EVENT_TIMEZONE}事件的时区。
{@link android.provider.CalendarContract.EventsColumns#EVENT_END_TIMEZONE}事件结束时间的时区。
{@link android.provider.CalendarContract.EventsColumns#DURATION}RFC5545 +格式的事件持续时间。例如,值为 +"PT1H" 表示事件应持续一小时,值为 +"P2W" 表示持续 2 周。
{@link android.provider.CalendarContract.EventsColumns#ALL_DAY}值为 1 +表示此事件占用一整天(按照本地时区的定义)。值为 0 +表示它是常规事件,可在一天内的任何时间开始和结束。
{@link android.provider.CalendarContract.EventsColumns#RRULE}事件的重复发生规则格式。例如,"FREQ=WEEKLY;COUNT=10;WKST=SU"。 +您可以在此处找到更多示例。 +
{@link android.provider.CalendarContract.EventsColumns#RDATE}事件的重复发生日期。 +{@link android.provider.CalendarContract.EventsColumns#RDATE} +与 {@link android.provider.CalendarContract.EventsColumns#RRULE} +通常联合用于定义一组聚合重复实例。 +如需查看更详细的介绍,请参阅 RFC5545 规范
{@link android.provider.CalendarContract.EventsColumns#AVAILABILITY}将此事件视为忙碌时间还是可调度的空闲时间。 +
{@link android.provider.CalendarContract.EventsColumns#GUESTS_CAN_MODIFY}来宾是否可修改事件。
{@link android.provider.CalendarContract.EventsColumns#GUESTS_CAN_INVITE_OTHERS}来宾是否可邀请其他来宾。
{@link android.provider.CalendarContract.EventsColumns#GUESTS_CAN_SEE_GUESTS}来宾是否可查看参加者列表。
+ +

添加事件

+ +

当您的应用插入新事件时,我们建议您按照使用 Intent 对象插入事件中所述使用 +{@link android.content.Intent#ACTION_INSERT INSERT} Intent 对象。不过,如需,也可直接插入事件。 +本节描述如何执行此操作。 +

+ + +

以下是插入新事件的规则:

+
    + +
  • 您必须加入 {@link +android.provider.CalendarContract.EventsColumns#CALENDAR_ID} 和 {@link +android.provider.CalendarContract.EventsColumns#DTSTART}。
  • + +
  • 您必须加入 {@link +android.provider.CalendarContract.EventsColumns#EVENT_TIMEZONE}。如需获取系统中已安装时区 ID +的列表,请使用 {@link +java.util.TimeZone#getAvailableIDs()}。请注意,如果您按使用 Intent 对象插入事件中所述通过 +{@link +android.content.Intent#ACTION_INSERT INSERT} Intent 对象插入事件,则此规则不适用—在该情形下,系统会提供默认时区。 +
  • + +
  • 对于非重复事件,您必须加入 {@link +android.provider.CalendarContract.EventsColumns#DTEND}。
  • + + +
  • 对于重复事件,您必须加入 {@link +android.provider.CalendarContract.EventsColumns#DURATION} 以及 {@link +android.provider.CalendarContract.EventsColumns#RRULE} 或 {@link +android.provider.CalendarContract.EventsColumns#RDATE}。请注意,如果您按使用 Intent 对象插入事件中所述通过 +{@link +android.content.Intent#ACTION_INSERT INSERT} Intent 对象插入事件,则此规则不适用—在该情形下,您可以将 {@link +android.provider.CalendarContract.EventsColumns#RRULE} 与 {@link android.provider.CalendarContract.EventsColumns#DTSTART} 和 {@link android.provider.CalendarContract.EventsColumns#DTEND} +联用,日历应用会自动将其转换为持续时间。 +
  • + +
+ +

以下是一个插入事件的示例。为了简便起见,此操作是在 UI +线程内执行的。实际上,应该在异步线程中完成插入和更新,以便将操作移入后台线程。 +如需了解详细信息,请参阅 +{@link android.content.AsyncQueryHandler}。

+ + +
+long calID = 3;
+long startMillis = 0; 
+long endMillis = 0;     
+Calendar beginTime = Calendar.getInstance();
+beginTime.set(2012, 9, 14, 7, 30);
+startMillis = beginTime.getTimeInMillis();
+Calendar endTime = Calendar.getInstance();
+endTime.set(2012, 9, 14, 8, 45);
+endMillis = endTime.getTimeInMillis();
+...
+
+ContentResolver cr = getContentResolver();
+ContentValues values = new ContentValues();
+values.put(Events.DTSTART, startMillis);
+values.put(Events.DTEND, endMillis);
+values.put(Events.TITLE, "Jazzercise");
+values.put(Events.DESCRIPTION, "Group workout");
+values.put(Events.CALENDAR_ID, calID);
+values.put(Events.EVENT_TIMEZONE, "America/Los_Angeles");
+Uri uri = cr.insert(Events.CONTENT_URI, values);
+
+// get the event ID that is the last element in the Uri
+long eventID = Long.parseLong(uri.getLastPathSegment());
+// 
+// ... do something with event ID
+//
+//
+ +

注:请注意以上示例如何在事件创建后捕获事件 +ID。这是获取事件 ID +的最简单方法。您经常需要使用事件 ID +来执行其他日历操作—例如,向事件添加参加者或提醒。

+ + +

更新事件

+ +

当您的应用想允许用户编辑事件时,我们建议您按照使用 Intent 对象编辑事件中所述使用 +{@link android.content.Intent#ACTION_EDIT EDIT} + Intent 对象。不过,如需,也可以直接编辑事件。 +如需执行事件更新,您可以通过 URI 追加 ID ({@link + +android.content.ContentUris#withAppendedId(android.net.Uri,long) withAppendedId()}) + +或第一个选定项形式提供事件的 _ID。 +选定范围应以 "_id=?" 开头,并且第一个selectionArg 应为事件的 +_ID。您还可以使用不含 +ID +的选定范围执行更新。以下是一个更新事件的示例。它使用 + {@link android.content.ContentUris#withAppendedId(android.net.Uri,long) withAppendedId()} +方法更改事件的名称:

+ + +
private static final String DEBUG_TAG = "MyActivity";
+...
+long eventID = 188;
+...
+ContentResolver cr = getContentResolver();
+ContentValues values = new ContentValues();
+Uri updateUri = null;
+// The new title for the event
+values.put(Events.TITLE, "Kickboxing"); 
+updateUri = ContentUris.withAppendedId(Events.CONTENT_URI, eventID);
+int rows = getContentResolver().update(updateUri, values, null, null);
+Log.i(DEBUG_TAG, "Rows updated: " + rows);  
+ +

删除事件

+ +

您可以通过将事件 {@link +android.provider.BaseColumns#_ID} 作为 URI 追加 ID +或通过使用标准选定范围来删除事件。如果您使用追加 +ID,则将无法同时使用选定范围。共有两个版本的删除:应用删除和同步适配器删除。应用删除将 +deleted 列设置为 1。此标志告知同步适配器该行已删除,并且应将此删除操作传播至服务器。 + +同步适配器删除会将事件连同其所有关联数据从数据库中删除。 +以下是一个应用通过事件 {@link android.provider.BaseColumns#_ID} +删除事件的示例:

+ + +
private static final String DEBUG_TAG = "MyActivity";
+...
+long eventID = 201;
+...
+ContentResolver cr = getContentResolver();
+ContentValues values = new ContentValues();
+Uri deleteUri = null;
+deleteUri = ContentUris.withAppendedId(Events.CONTENT_URI, eventID);
+int rows = getContentResolver().delete(deleteUri, null, null);
+Log.i(DEBUG_TAG, "Rows deleted: " + rows);  
+
+ +

参加者表

+ +

{@link android.provider.CalendarContract.Attendees} +表的每一行都表示事件的一位参加者或来宾。调用 +{@link android.provider.CalendarContract.Reminders#query(android.content.ContentResolver, long, java.lang.String[]) query()} +会返回一个参加者列表,其中包含具有给定 {@link android.provider.CalendarContract.AttendeesColumns#EVENT_ID} +的事件的参加者。 +此 {@link android.provider.CalendarContract.AttendeesColumns#EVENT_ID} +必须匹配特定事件的 {@link +android.provider.BaseColumns#_ID}。

+ +

下表列出了可写入的字段。 +插入新参加者时,您必须加入除 ATTENDEE_NAME 之外的所有字段。 + +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
常量描述
{@link android.provider.CalendarContract.AttendeesColumns#EVENT_ID}事件的 ID。
{@link android.provider.CalendarContract.AttendeesColumns#ATTENDEE_NAME}参加者的姓名。
{@link android.provider.CalendarContract.AttendeesColumns#ATTENDEE_EMAIL}参加者的电子邮件地址。
{@link android.provider.CalendarContract.AttendeesColumns#ATTENDEE_RELATIONSHIP}

参加者与事件的关系。下列值之一:

+
    +
  • {@link android.provider.CalendarContract.AttendeesColumns#RELATIONSHIP_ATTENDEE}
  • +
  • {@link android.provider.CalendarContract.AttendeesColumns#RELATIONSHIP_NONE}
  • +
  • {@link android.provider.CalendarContract.AttendeesColumns#RELATIONSHIP_ORGANIZER}
  • +
  • {@link android.provider.CalendarContract.AttendeesColumns#RELATIONSHIP_PERFORMER}
  • +
  • {@link android.provider.CalendarContract.AttendeesColumns#RELATIONSHIP_SPEAKER}
  • +
+
{@link android.provider.CalendarContract.AttendeesColumns#ATTENDEE_TYPE}

参加者的类型。下列值之一:

+
    +
  • {@link android.provider.CalendarContract.AttendeesColumns#TYPE_REQUIRED}
  • +
  • {@link android.provider.CalendarContract.AttendeesColumns#TYPE_OPTIONAL}
  • +
{@link android.provider.CalendarContract.AttendeesColumns#ATTENDEE_STATUS}

参加者的出席状态。下列值之一:

+
    +
  • {@link android.provider.CalendarContract.AttendeesColumns#ATTENDEE_STATUS_ACCEPTED}
  • +
  • {@link android.provider.CalendarContract.AttendeesColumns#ATTENDEE_STATUS_DECLINED}
  • +
  • {@link android.provider.CalendarContract.AttendeesColumns#ATTENDEE_STATUS_INVITED}
  • +
  • {@link android.provider.CalendarContract.AttendeesColumns#ATTENDEE_STATUS_NONE}
  • +
  • {@link android.provider.CalendarContract.AttendeesColumns#ATTENDEE_STATUS_TENTATIVE}
  • +
+ +

添加参加者

+ +

以下是一个为事件添加一位参加者的示例。请注意,{@link android.provider.CalendarContract.AttendeesColumns#EVENT_ID} +是必填项: +

+ +
+long eventID = 202;
+...
+ContentResolver cr = getContentResolver();
+ContentValues values = new ContentValues();
+values.put(Attendees.ATTENDEE_NAME, "Trevor");
+values.put(Attendees.ATTENDEE_EMAIL, "trevor@example.com");
+values.put(Attendees.ATTENDEE_RELATIONSHIP, Attendees.RELATIONSHIP_ATTENDEE);
+values.put(Attendees.ATTENDEE_TYPE, Attendees.TYPE_OPTIONAL);
+values.put(Attendees.ATTENDEE_STATUS, Attendees.ATTENDEE_STATUS_INVITED);
+values.put(Attendees.EVENT_ID, eventID);
+Uri uri = cr.insert(Attendees.CONTENT_URI, values);
+
+ +

提醒表

+ +

{@link android.provider.CalendarContract.Reminders} +表的每一行都表示事件的一个提醒。调用 +{@link android.provider.CalendarContract.Reminders#query(android.content.ContentResolver, long, java.lang.String[]) query()} +会返回一个提醒列表,其中包含具有给定 {@link android.provider.CalendarContract.AttendeesColumns#EVENT_ID} 的事件的提醒。 +

+ + +

下表列出了提醒的可写入字段。插入新提醒时,必须加入所有字段。 +请注意,同步适配器指定它们在 +{@link +android.provider.CalendarContract.Calendars} 表中支持的提醒类型。详情请参阅 +{@link android.provider.CalendarContract.CalendarColumns#ALLOWED_REMINDERS} +。

+ + + + + + + + + + + + + + + + + + + +
常量描述
{@link android.provider.CalendarContract.RemindersColumns#EVENT_ID}事件的 ID。
{@link android.provider.CalendarContract.RemindersColumns#MINUTES}事件发生前的分钟数,应在达到该时间时发出提醒。
{@link android.provider.CalendarContract.RemindersColumns#METHOD}

服务器上设置的提醒方法。下列值之一:

+
    +
  • {@link android.provider.CalendarContract.RemindersColumns#METHOD_ALERT}
  • +
  • {@link android.provider.CalendarContract.RemindersColumns#METHOD_DEFAULT}
  • +
  • {@link android.provider.CalendarContract.RemindersColumns#METHOD_EMAIL}
  • +
  • {@link android.provider.CalendarContract.RemindersColumns#METHOD_SMS}
  • +
+ +

添加提醒

+ +

下例显示如何为事件添加提醒。提醒在事件发生前 15 +分钟发出。

+
+long eventID = 221;
+...
+ContentResolver cr = getContentResolver();
+ContentValues values = new ContentValues();
+values.put(Reminders.MINUTES, 15);
+values.put(Reminders.EVENT_ID, eventID);
+values.put(Reminders.METHOD, Reminders.METHOD_ALERT);
+Uri uri = cr.insert(Reminders.CONTENT_URI, values);
+ +

实例表

+ +

+{@link android.provider.CalendarContract.Instances} +表储存事件实例的开始时间和结束时间。此表中的每一行都表示一个事件实例。 +实例表无法写入,只提供查询事件实例的途径。 +

+ +

下表列出了一些您可以执行实例查询的字段。请注意, +时区由 +{@link android.provider.CalendarContract.CalendarCache#KEY_TIMEZONE_TYPE} +和 +{@link android.provider.CalendarContract.CalendarCache#KEY_TIMEZONE_INSTANCES} 定义。

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
常量描述
{@link android.provider.CalendarContract.Instances#BEGIN}实例的开始时间,以协调世界时毫秒数表示。
{@link android.provider.CalendarContract.Instances#END}实例的结束时间,以协调世界时毫秒数表示。
{@link android.provider.CalendarContract.Instances#END_DAY}与日历时区相应的实例儒略历结束日。 + + +
{@link android.provider.CalendarContract.Instances#END_MINUTE}从日历时区午夜开始计算的实例结束时间(分钟)。 +
{@link android.provider.CalendarContract.Instances#EVENT_ID}该实例对应事件的 _ID
{@link android.provider.CalendarContract.Instances#START_DAY}与日历时区相应的实例儒略历开始日。 +
{@link android.provider.CalendarContract.Instances#START_MINUTE}从日历时区午夜开始计算的实例开始时间(分钟)。 + +
+ +

查询实例表

+ +

如需查询实例表,您需要在 URI +中指定查询的时间范围。在以下示例中,{@link android.provider.CalendarContract.Instances} +通过其 {@link android.provider.CalendarContract.EventsColumns} 接口实现获得对 {@link +android.provider.CalendarContract.EventsColumns#TITLE} +字段的访问权限。 +换言之,{@link +android.provider.CalendarContract.EventsColumns#TITLE} 是通过数据库视图,而不是通过查询原始 +{@link +android.provider.CalendarContract.Instances} 表返回的。

+ +
+private static final String DEBUG_TAG = "MyActivity";
+public static final String[] INSTANCE_PROJECTION = new String[] {
+    Instances.EVENT_ID,      // 0
+    Instances.BEGIN,         // 1
+    Instances.TITLE          // 2
+  };
+  
+// The indices for the projection array above.
+private static final int PROJECTION_ID_INDEX = 0;
+private static final int PROJECTION_BEGIN_INDEX = 1;
+private static final int PROJECTION_TITLE_INDEX = 2;
+...
+
+// Specify the date range you want to search for recurring
+// event instances
+Calendar beginTime = Calendar.getInstance();
+beginTime.set(2011, 9, 23, 8, 0);
+long startMillis = beginTime.getTimeInMillis();
+Calendar endTime = Calendar.getInstance();
+endTime.set(2011, 10, 24, 8, 0);
+long endMillis = endTime.getTimeInMillis();
+  
+Cursor cur = null;
+ContentResolver cr = getContentResolver();
+
+// The ID of the recurring event whose instances you are searching
+// for in the Instances table
+String selection = Instances.EVENT_ID + " = ?";
+String[] selectionArgs = new String[] {"207"};
+
+// Construct the query with the desired date range.
+Uri.Builder builder = Instances.CONTENT_URI.buildUpon();
+ContentUris.appendId(builder, startMillis);
+ContentUris.appendId(builder, endMillis);
+
+// Submit the query
+cur =  cr.query(builder.build(), 
+    INSTANCE_PROJECTION, 
+    selection, 
+    selectionArgs, 
+    null);
+   
+while (cur.moveToNext()) {
+    String title = null;
+    long eventID = 0;
+    long beginVal = 0;    
+    
+    // Get the field values
+    eventID = cur.getLong(PROJECTION_ID_INDEX);
+    beginVal = cur.getLong(PROJECTION_BEGIN_INDEX);
+    title = cur.getString(PROJECTION_TITLE_INDEX);
+              
+    // Do something with the values. 
+    Log.i(DEBUG_TAG, "Event:  " + title); 
+    Calendar calendar = Calendar.getInstance();
+    calendar.setTimeInMillis(beginVal);  
+    DateFormat formatter = new SimpleDateFormat("MM/dd/yyyy");
+    Log.i(DEBUG_TAG, "Date: " + formatter.format(calendar.getTime()));    
+    }
+ }
+ +

日历 Intent 对象

+

您的应用不需要读取和写入日历数据的权限。它可以改用 Android 的日历应用支持的 Intent 对象将读取和写入操作转到该应用执行。下表列出了日历提供程序支持的 Intent 对象:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
操作URI描述Extra

+ {@link android.content.Intent#ACTION_VIEW VIEW}

content://com.android.calendar/time/<ms_since_epoch>

+ 您还可以通过 +{@link android.provider.CalendarContract#CONTENT_URI CalendarContract.CONTENT_URI} 引用 URI。 +如需查看使用该 Intent 对象的示例,请参阅使用 Intent 对象查看日历数据。 + +
打开日历后定位到 <ms_since_epoch> 指定的时间。无。

{@link android.content.Intent#ACTION_VIEW VIEW}

+ +

content://com.android.calendar/events/<event_id>

+ + 您还可以通过 +{@link android.provider.CalendarContract.Events#CONTENT_URI Events.CONTENT_URI} 引用 URI。 +如需查看使用该 Intent 对象的示例,请参阅使用 Intent 对象查看日历数据。 + +
查看 <event_id> 指定的事件。{@link android.provider.CalendarContract#EXTRA_EVENT_BEGIN_TIME CalendarContract.EXTRA_EVENT_BEGIN_TIME}
+
+
+ {@link android.provider.CalendarContract#EXTRA_EVENT_END_TIME CalendarContract.EXTRA_EVENT_END_TIME}
{@link android.content.Intent#ACTION_EDIT EDIT}

content://com.android.calendar/events/<event_id>

+ + 您还可以通过 +{@link android.provider.CalendarContract.Events#CONTENT_URI Events.CONTENT_URI} 引用 URI。 +如需查看使用该 Intent 对象的示例,请参阅使用 Intent 对象编辑事件。 + + +
编辑 <event_id> 指定的事件。{@link android.provider.CalendarContract#EXTRA_EVENT_BEGIN_TIME CalendarContract.EXTRA_EVENT_BEGIN_TIME}
+
+
+ {@link android.provider.CalendarContract#EXTRA_EVENT_END_TIME CalendarContract.EXTRA_EVENT_END_TIME}
{@link android.content.Intent#ACTION_EDIT EDIT}
+
+ {@link android.content.Intent#ACTION_INSERT INSERT}

content://com.android.calendar/events

+ + 您还可以通过 +{@link android.provider.CalendarContract.Events#CONTENT_URI Events.CONTENT_URI} 引用 URI。 +如需查看使用该 Intent 对象的示例,请参阅使用 Intent 对象插入事件。 + +
创建事件。下表列出的任一 Extra。
+ +

下表列出了日历提供程序支持的 Intent Extra: +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Intent Extra描述
{@link android.provider.CalendarContract.EventsColumns#TITLE Events.TITLE}事件的名称。
{@link android.provider.CalendarContract#EXTRA_EVENT_BEGIN_TIME +CalendarContract.EXTRA_EVENT_BEGIN_TIME}事件开始时间,以从公元纪年开始计算的毫秒数表示。
{@link android.provider.CalendarContract#EXTRA_EVENT_END_TIME +CalendarContract.EXTRA_EVENT_END_TIME}事件结束时间,以从公元纪年开始计算的毫秒数表示。
{@link android.provider.CalendarContract#EXTRA_EVENT_ALL_DAY +CalendarContract.EXTRA_EVENT_ALL_DAY}一个布尔值,表示事件属于全天事件。值可以是 +truefalse
{@link android.provider.CalendarContract.EventsColumns#EVENT_LOCATION +Events.EVENT_LOCATION}事件的地点。
{@link android.provider.CalendarContract.EventsColumns#DESCRIPTION +Events.DESCRIPTION}事件描述。
+ {@link android.content.Intent#EXTRA_EMAIL Intent.EXTRA_EMAIL}逗号分隔值形式的受邀者电子邮件地址列表。
+ {@link android.provider.CalendarContract.EventsColumns#RRULE Events.RRULE}事件的重复发生规则。
+ {@link android.provider.CalendarContract.EventsColumns#ACCESS_LEVEL +Events.ACCESS_LEVEL}事件是私人性质还是公共性质。
{@link android.provider.CalendarContract.EventsColumns#AVAILABILITY +Events.AVAILABILITY}将此事件视为忙碌时间还是可调度的空闲时间。
+

下文描述如何使用这些 Intent 对象。

+ + +

使用 Intent 对象插入事件

+ +

您的应用可以利用 {@link android.content.Intent#ACTION_INSERT INSERT} + Intent 对象将事件插入任务转到日历应用执行。使用此方法时,您的应用甚至不需要在其清单文件中加入 +{@link +android.Manifest.permission#WRITE_CALENDAR} 权限。

+ + +

当用户运行使用此方法的应用时,应用会将其转到日历来完成事件添加操作。 +{@link +android.content.Intent#ACTION_INSERT INSERT} Intent 利用 extra +字段为表单预填充日历中事件的详细信息。用户随后可取消事件、根据需要编辑表单或将事件保存到日历中。 + +

+ + + +

以下是一个代码段,用于安排一个在 2012 年 1 月 19 日上午 +7:30 开始、8:30 结束的事件。请注意该代码段中的以下内容:

+ +
    +
  • 它将 {@link android.provider.CalendarContract.Events#CONTENT_URI Events.CONTENT_URI} +指定为 URI。
  • + +
  • 它使用 {@link +android.provider.CalendarContract#EXTRA_EVENT_BEGIN_TIME +CalendarContract.EXTRA_EVENT_BEGIN_TIME} 和 {@link +android.provider.CalendarContract#EXTRA_EVENT_END_TIME +CalendarContract.EXTRA_EVENT_END_TIME} extra +字段为表单预填充事件的时间。这些时间的值必须以从公元纪年开始计算的协调世界时毫秒数表示。 +
  • + +
  • 它使用 {@link android.content.Intent#EXTRA_EMAIL Intent.EXTRA_EMAIL} +extra 字段提供以逗号分隔的受邀者电子邮件地址列表。
  • + +
+
+Calendar beginTime = Calendar.getInstance();
+beginTime.set(2012, 0, 19, 7, 30);
+Calendar endTime = Calendar.getInstance();
+endTime.set(2012, 0, 19, 8, 30);
+Intent intent = new Intent(Intent.ACTION_INSERT)
+        .setData(Events.CONTENT_URI)
+        .putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, beginTime.getTimeInMillis())
+        .putExtra(CalendarContract.EXTRA_EVENT_END_TIME, endTime.getTimeInMillis())
+        .putExtra(Events.TITLE, "Yoga")
+        .putExtra(Events.DESCRIPTION, "Group class")
+        .putExtra(Events.EVENT_LOCATION, "The gym")
+        .putExtra(Events.AVAILABILITY, Events.AVAILABILITY_BUSY)
+        .putExtra(Intent.EXTRA_EMAIL, "rowan@example.com,trevor@example.com");
+startActivity(intent);
+
+ +

使用 Intent 对象编辑事件

+ +

您可以按更新事件中所述直接更新事件。但使用 {@link +android.content.Intent#ACTION_EDIT EDIT} Intent 可以让不具有事件编辑权限的应用将事件编辑操作转到日历应用执行。当用户在日历中完成事件编辑后,将会返回原来的应用。 + + +

以下是一个 Intent 对象的示例,它为指定事件设置新名称,并允许用户在日历中编辑事件。 +

+ + +
long eventID = 208;
+Uri uri = ContentUris.withAppendedId(Events.CONTENT_URI, eventID);
+Intent intent = new Intent(Intent.ACTION_EDIT)
+    .setData(uri)
+    .putExtra(Events.TITLE, "My New Title");
+startActivity(intent);
+ +

使用 Intent 对象查看日历数据

+

日历提供程序提供了两种不同的 {@link android.content.Intent#ACTION_VIEW VIEW} Intent 对象使用方法:

+
    +
  • 打开日历并定位到特定日期。
  • +
  • 查看事件。
  • + +
+

下例显示如何打开日历并定位到特定日期:

+
// A date-time specified in milliseconds since the epoch.
+long startMillis;
+...
+Uri.Builder builder = CalendarContract.CONTENT_URI.buildUpon();
+builder.appendPath("time");
+ContentUris.appendId(builder, startMillis);
+Intent intent = new Intent(Intent.ACTION_VIEW)
+    .setData(builder.build());
+startActivity(intent);
+ +

下例显示如何打开事件进行查看:

+
long eventID = 208;
+...
+Uri uri = ContentUris.withAppendedId(Events.CONTENT_URI, eventID);
+Intent intent = new Intent(Intent.ACTION_VIEW)
+   .setData(uri);
+startActivity(intent);
+
+ + +

同步适配器

+ + +

应用和同步适配器在访问日历提供程序的方式上只存在微小差异: +

+ +
    +
  • 同步适配器需要通过将 {@link android.provider.CalendarContract#CALLER_IS_SYNCADAPTER} 设置为 true 来表明它是同步适配器。
  • + + +
  • 同步适配器需要提供 {@link +android.provider.CalendarContract.SyncColumns#ACCOUNT_NAME} 和 {@link +android.provider.CalendarContract.SyncColumns#ACCOUNT_TYPE} 作为 URI 中的查询参数。
  • + +
  • 与应用或小工具相比,同步适配器拥有写入权限的列更多。 + 例如,应用只能修改日历的少数几种特性, +例如其名称、显示名称、能见度设置以及是否同步日历。 +相比之下,同步适配器不仅可以访问这些列,还能访问许多其他列, +例如日历颜色、时区、访问级别、地点等等。不过,同步适配器受限于它指定的 +ACCOUNT_NAME 和 +ACCOUNT_TYPE
+ +

您可以利用以下 helper 方法返回供与同步适配器一起使用的 URI:

+
 static Uri asSyncAdapter(Uri uri, String account, String accountType) {
+    return uri.buildUpon()
+        .appendQueryParameter(android.provider.CalendarContract.CALLER_IS_SYNCADAPTER,"true")
+        .appendQueryParameter(Calendars.ACCOUNT_NAME, account)
+        .appendQueryParameter(Calendars.ACCOUNT_TYPE, accountType).build();
+ }
+
+

如需查看同步适配器的实现示例(并非仅限与日历有关的实现),请参阅 +SampleSyncAdapter。 diff --git a/docs/html-intl/intl/zh-cn/guide/topics/providers/contacts-provider.jd b/docs/html-intl/intl/zh-cn/guide/topics/providers/contacts-provider.jd new file mode 100644 index 0000000000000000000000000000000000000000..7125fb983d54d5763922600486778a5fc3e0e807 --- /dev/null +++ b/docs/html-intl/intl/zh-cn/guide/topics/providers/contacts-provider.jd @@ -0,0 +1,2356 @@ +page.title=联系人提供程序 +@jd:body +

+
+

内容快览

+
    +
  • Android 有关联系人的信息存储库。
  • +
  • + 与 Web 同步。 +
  • +
  • + 集成社交流数据。 +
  • +
+

本文内容

+
    +
  1. + 联系人提供程序组织 +
  2. +
  3. + 原始联系人 +
  4. +
  5. + 数据 +
  6. +
  7. + 联系人 +
  8. +
  9. + 来自同步适配器的数据 +
  10. +
  11. + 所需权限 +
  12. +
  13. + 用户个人资料 +
  14. +
  15. + 联系人提供程序元数据 +
  16. +
  17. + 联系人提供程序访问 +
  18. +
  19. +
  20. + 联系人提供程序同步适配器 +
  21. +
  22. + 社交流数据 +
  23. +
  24. + 其他联系人提供程序功能 +
  25. +
+

关键类

+
    +
  1. {@link android.provider.ContactsContract.Contacts}
  2. +
  3. {@link android.provider.ContactsContract.RawContacts}
  4. +
  5. {@link android.provider.ContactsContract.Data}
  6. +
  7. {@code android.provider.ContactsContract.StreamItems}
  8. +
+

相关示例

+
    +
  1. + +联系人管理器 + +
  2. +
  3. + +示例同步适配器 +
  4. +
+

另请参阅

+
    +
  1. + +内容提供程序基础知识 + +
  2. +
+
+
+

+ 联系人提供程序是一个强大而又灵活的 Android 组件,用于管理设备上有关联系人数据的中央存储库。 +联系人提供程序是您在设备的联系人应用中看到的数据源,您也可以在自己的应用中访问其数据,并可在设备与在线服务之间传送数据。 + +提供程序储存有多种数据源,由于它会试图为每个联系人管理尽可能多的数据,因此造成其组织结构非常复杂。 + +为此,该提供程序的 API 包含丰富的协定类和接口,为数据检索和修改提供便利。 + + +

+

+ 本指南介绍下列内容: +

+
    +
  • + 提供程序基本结构 +
  • +
  • + 如何从提供程序检索数据 +
  • +
  • + 如何修改提供程序中的数据 +
  • +
  • + 如何编写用于同步服务器数据与联系人提供程序数据的同步适配器。 + +
  • +
+

+ 本指南假定您了解 Android 内容提供程序的基础知识。如需了解有关 Android 内容提供程序的更多信息,请阅读 +内容提供程序基础知识指南。 + + +示例同步适配器示例应用是一个示例,展示如何使用同步适配器在联系人提供程序与 Google 网络服务托管的一个示例应用之间传送数据。 + + +

+

联系人提供程序组织

+

+ 联系人提供程序是 Android 内容提供程序的一个组件。它保留了三种类型的联系人数据,每一种数据都对应提供程序提供的一个表,如图 1 所示: + + +

+ +

+ 图 1. 联系人提供程序表结构。 +

+

+ 这三个表通常以其协定类的名称命名。这些类定义表所使用的内容 URI、列名称及列值相应的常量: + +

+
+
+ {@link android.provider.ContactsContract.Contacts} 表 +
+
+ 表示不同联系人的行,基于聚合的原始联系人行。 +
+
+ {@link android.provider.ContactsContract.RawContacts} 表 +
+
+ 包含联系人数据摘要的行,针对特定用户帐户和类型。 +
+
+ {@link android.provider.ContactsContract.Data} 表 +
+
+ 包含原始联系人详细信息(例如电子邮件地址或电话号码)的行。 +
+
+

+ 由 {@link android.provider.ContactsContract} +中的协定类表示的其他表是辅助表,联系人提供程序利用它们来管理其操作,或为设备的联系人或电话应用中的特定功能提供支持。 + +

+

原始联系人

+

+ 一个原始联系人表示来自某一帐户类型和帐户名称、有关某个联系人的数据。 +由于联系人提供程序允许将多个在线服务作为某一联系人的数据源,因此它允许同一联系人对应多个原始联系人。 + + 借助支持多个原始联系人的特性,用户还可以将某一联系人在帐户类型相同的多个帐户中的数据进行合并。 + +

+

+ 原始联系人的大部分数据并不存储在 +{@link android.provider.ContactsContract.RawContacts} 表内,而是存储在 +{@link android.provider.ContactsContract.Data} 表中的一行或多行内。每个数据行都有一个 +{@link android.provider.ContactsContract.DataColumns#RAW_CONTACT_ID Data.RAW_CONTACT_ID} 列,其中包含其父级 {@link android.provider.ContactsContract.RawContacts} 行的 {@code android.provider.BaseColumns#_ID RawContacts._ID} 值。 + + +

+

重要的原始联系人列

+

+ 表 1 列出了 {@link android.provider.ContactsContract.RawContacts} 表中的重要列。 +请阅读表后的说明: +

+

+ 表 1. 重要的原始联系人列。 +

+ + + + + + + + + + + + + + + + + + + + +
列名称用途备注
+ {@link android.provider.ContactsContract.SyncColumns#ACCOUNT_NAME} + + 作为该原始联系人来源的帐户类型的帐户名称。 + 例如,Google 帐户的帐户名称是设备所有者的某个 Gmail +地址。如需了解详细信息,请参阅有关 + {@link android.provider.ContactsContract.SyncColumns#ACCOUNT_TYPE} 的下一条目。 + + + 此名称的格式专用于其帐户类型。它不一定是电子邮件地址。 + +
+ {@link android.provider.ContactsContract.SyncColumns#ACCOUNT_TYPE} + + 作为该原始联系人来源的帐户类型。例如,Google 帐户的帐户类型是 com.google。 +请务必使用您拥有或控制的域的域标识符限定您的帐户类型。 +这可以确保您的帐户类型具有唯一性。 + + + 提供联系人数据的帐户类型通常关联有同步适配器,用于与联系人提供程序进行同步。 + +
+ {@link android.provider.ContactsContract.RawContactsColumns#DELETED} + + 原始联系人的“已删除”标志。 + + 此标志让联系人提供程序能够在内部保留该行,直至同步适配器能够从服务器删除该行,然后再从存储库中最终删除该行。 + + +
+

说明

+

+ 以下是关于 + {@link android.provider.ContactsContract.RawContacts} 表的重要说明: +

+
    +
  • + 原始联系人的姓名并不存储其在 +{@link android.provider.ContactsContract.RawContacts} 中的行内,而是存储在 +{@link android.provider.ContactsContract.Data} 表的 +{@link android.provider.ContactsContract.CommonDataKinds.StructuredName} 行内。一个原始联系人在 {@link android.provider.ContactsContract.Data} 表中只有一个该类型的行。 + +
  • +
  • + 注意:要想在原始联系人行中使用您自己的帐户数据,必须先在 {@link android.accounts.AccountManager} 中注册帐户。 +为此,请提示用户将帐户类型及其帐户名称添加到帐户列表。 +如果您不这样做,联系人提供程序将自动删除您的原始联系人行。 + +

    + 例如,如果您想让您的应用为您域名为 {@code com.example.dataservice}、基于 Web 的服务保留联系人数据,并且您的服务的用户帐户是 {@code becky.sharp@dataservice.example.com},则用户必须先添加帐户“类型”({@code com.example.dataservice}) 和帐户“名称”({@code becky.smart@dataservice.example.com}),然后您的应用才能添加原始联系人行。 + + + + + 您可以在文档中向用户解释这项要求,也可以提示用户添加类型和名称,或者同时采用这两种措施。 +下文对帐户类型和帐户名称做了更详尽的描述。 + +

  • +
+

原始联系人数据来源

+

+ 为理解原始联系人的工作方式,假设有一位用户“Emily Dickinson”,她的设备上定义了以下三个用户帐户: + +

+
    +
  • emily.dickinson@gmail.com
  • +
  • emilyd@gmail.com
  • +
  • Twitter 帐户“belle_of_amherst”
  • +
+

+ 该用户已在 Accounts 设置中为全部三个帐户启用了 +Sync Contacts。 +

+

+ 假定 Emily Dickinson 打开一个浏览器窗口,以 +emily.dickinson@gmail.com 身份登录 Gmail,然后打开 +“联系人”,并添加“Thomas Higginson”。后来,她以 +emilyd@gmail.com 身份登录 Gmail,并向“Thomas Higginson”发送一封电子邮件,此操作会自动将他添加为联系人。 +她还在 Twitter 上关注了“colonel_tom”(Thomas Higginson 的 Twitter ID)。 + +

+

+ 以上操作的结果是,联系人提供程序会创建以下这三个原始联系人: +

+
    +
  1. + 第一个原始联系人对应“Thomas Higginson”,关联帐户 emily.dickinson@gmail.com。 + 用户帐户类型是 Google。 +
  2. +
  3. + 第二个原始联系人对应“Thomas Higginson”,关联帐户 emilyd@gmail.com。 + 用户帐户类型也是 Google。由于添加的联系人对应的用户帐户不同,因此尽管名称与前一名称完全相同,也只能作为第二个原始联系人。 + + +
  4. +
  5. + 第三个原始联系人对应“Thomas Higginson”,关联帐户“belle_of_amherst”。用户帐户类型是 Twitter。 + +
  6. +
+

数据

+

+ 如前文所做的说明,原始联系人的数据存储在一个 +{@link android.provider.ContactsContract.Data} 行中,该行链接到原始联系人的 +_ID 值。这使一位原始联系人可以拥有多个具有相同数据类型的实例,例如电子邮件地址或电话号码。 +例如,如果对应 +{@code emilyd@gmail.com} 的“Thomas Higginson”(关联 Google 帐户 emilyd@gmail.com 的 Thomas Higginson +的原始联系人行)的住宅电子邮件地址为 +thigg@gmail.com,办公电子邮件地址为 +thomas.higginson@gmail.com,则联系人提供程序会存储这两个电子邮件地址行,并将它们都链接到原始联系人。 + +

+

+ 请注意,这个表中存储了不同类型的数据。显示姓名、电话号码、电子邮件、邮政地址、照片以及网站明细行都可以在 {@link android.provider.ContactsContract.Data} 表中找到。 + +为便于管理这些数据, +{@link android.provider.ContactsContract.Data} 表为一些列使用了描述性名称,为其他列使用了通用名称。 +使用描述性名称的列的内容具有相同的含义,与行中数据的类型无关,而使用通用名称的列的内容则会随数据类型的不同而具有不同的含义。 + + +

+

描述性列名称

+

+ 以下是一些描述性列名称的示例: +

+
+
+ {@link android.provider.ContactsContract.Data#RAW_CONTACT_ID} +
+
+ 该数据对应的原始联系人 _ID 列的值。 +
+
+ {@link android.provider.ContactsContract.Data#MIMETYPE} +
+
+ 该行中存储的数据类型,以自定义 MIME(多用途互联网邮件扩展)类型表示。联系人提供程序使用了 +{@link android.provider.ContactsContract.CommonDataKinds} 子类中定义的 MIME 类型。 +这些 MIME 类型为开源类型,可供与联系人提供程序协作的任何应用或同步适配器使用。 + +
+
+ {@link android.provider.ContactsContract.DataColumns#IS_PRIMARY} +
+
+ 如果一个原始联系人可能具有多个这种类型的数据行, +{@link android.provider.ContactsContract.DataColumns#IS_PRIMARY} 列会标记 +包含该类型主要数据的数据行。例如,如果用户长按某个联系人的电话号码,并选择 Set default,则包含该号码的 {@link android.provider.ContactsContract.Data} 行会将其 {@link android.provider.ContactsContract.DataColumns#IS_PRIMARY} 列设置为一个非零值。 + + + + +
+
+

通用列名称

+

+ 有 15 个通用列命名为 DATA1 至 +DATA15,可普遍适用;还有四个通用列命名为 SYNC1SYNC4,只应由同步适配器使用。 + +通用列名称常量始终有效,与行包含的数据类型无关。 + +

+

+ DATA1 列为索引列。联系人提供程序总是在此列中存储其预期会成为最频繁查询目标的数据。 +例如,在一个电子邮件行中,此列包含实际电子邮件地址。 + +

+

+ 按照惯例,DATA15 为预留列,用于存储照片缩略图等二进制大型对象 +(BLOB) 数据。 +

+

类型专用列名称

+

+ 为便于处理特定类型行的列,联系人提供程序还提供了 +{@link android.provider.ContactsContract.CommonDataKinds} 子类中定义的类型专用列名称常量。 +这些常量只是为同一列名称提供不同的常量名称,这有助于您访问特定类型行中的数据。 + + +

+

+ 例如,{@link android.provider.ContactsContract.CommonDataKinds.Email} 类为 {@link android.provider.ContactsContract.Data} 行定义类型专用列名称常量,该行的 MIME 类型为 {@link android.provider.ContactsContract.CommonDataKinds.Email#CONTENT_ITEM_TYPE + Email.CONTENT_ITEM_TYPE}。 + + +该类包含电子邮件地址列的 +{@link android.provider.ContactsContract.CommonDataKinds.Email#ADDRESS} +常量。 +{@link android.provider.ContactsContract.CommonDataKinds.Email#ADDRESS} 的实际值为“data1”,这与列的通用名称相同。 + +

+

+ 注意:请勿使用具有提供程序某个预定义 MIME 类型的行向 +{@link android.provider.ContactsContract.Data} 表中添加您自己的自定义数据。 +否则您可能会丢失数据,或导致提供程序发生故障。 +例如,如果某一行具有 MIME 类型 +{@link android.provider.ContactsContract.CommonDataKinds.Email#CONTENT_ITEM_TYPE + Email.CONTENT_ITEM_TYPE},并且 +DATA1 列包含的是用户名而不是电子邮件地址,您就不应添加该行。如果您为该行使用自定义的 MIME 类型,则可自由定义您的自定义类型专用的列名称,并随心所欲地使用这些列。 + +

+

+ 图 2 显示的是描述性列和数据列在 +{@link android.provider.ContactsContract.Data} 行中的显示情况,以及类型专用列名称“覆盖”通用列名称的情况 + +

+How type-specific column names map to generic column names +

+ 图 2. 类型专用列名称和通用列名称。 +

+

类型专用列名称类

+

+ 表 2 列出了最常用的类型专用列名称类: +

+

+ 表 2. 类型专用列名称类

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
映射类数据类型备注
{@link android.provider.ContactsContract.CommonDataKinds.StructuredName}与该数据行关联的原始联系人的姓名数据。一位原始联系人只有其中一行。
{@link android.provider.ContactsContract.CommonDataKinds.Photo}与该数据行关联的原始联系人的主要照片。一位原始联系人只有其中一行。
{@link android.provider.ContactsContract.CommonDataKinds.Email}与该数据行关联的原始联系人的电子邮件地址。一位原始联系人可有多个电子邮件地址。
{@link android.provider.ContactsContract.CommonDataKinds.StructuredPostal}与该数据行关联的原始联系人的邮政地址。一位原始联系人可有多个邮政地址。
{@link android.provider.ContactsContract.CommonDataKinds.GroupMembership}将原始联系人链接到联系人提供程序内其中一组的标识符。 + 组是帐户类型和帐户名称的一项可选功能。联系人组部分对其做了更详尽的描述。 + +
+

联系人

+

+ 联系人提供程序通过将所有帐户类型和帐户名称的原始联系人行合并来形成联系人。 +这可以为显示和修改用户针对某一联系人收集的所有数据提供便利。 +联系人提供程序管理新联系人行的创建,以及原始联系人与现有联系人行的合并。 +系统不允许应用或同步适配器添加联系人,并且联系人行中的某些列是只读列。 + +

+

+ 注:如果您试图通过 +{@link android.content.ContentResolver#insert(Uri,ContentValues) insert()} 向联系人提供程序添加联系人,会引发一个 {@link java.lang.UnsupportedOperationException} 异常。 +如果您试图更新一个列为“只读”的列,更新会被忽略。 + +

+

+ 如果添加的新原始联系人不匹配任何现有联系人,联系人提供程序会相应地创建新联系人。 +如果某个现有原始联系人的数据发生了变化,不再匹配其之前关联的联系人,则提供程序也会执行此操作。 + +如果应用或同步适配器创建的新原始联系人“的确”匹配某位现有联系人,则新原始联系人将与现有联系人合并。 + + +

+

+ 联系人提供程序通过 {@link android.provider.ContactsContract.Contacts Contacts} 表中联系人行的 +_ID 列将联系人行与其各原始联系人行链接起来。 +原始联系人表 {@link android.provider.ContactsContract.RawContacts} 的 CONTACT_ID 列包含对应于每个原始联系人行所关联联系人行的 _ID 值。 + + +

+

+ {@link android.provider.ContactsContract.Contacts} 表还有一个 +{@code android.provider.ContactsContract.ContactsColumns#LOOKUP_KEY} 列,它是一个指向联系人行的“永久性”链接。 +由于联系人提供程序会自动维护联系人,因此可能会在合并或同步时相应地更改联系人行的 {@code android.provider.BaseColumns#_ID} 值。 + +即使发生这种情况,合并了联系人 +{@code android.provider.ContactsContract.ContactsColumns#LOOKUP_KEY} 的内容 URI +{@link android.provider.ContactsContract.Contacts#CONTENT_LOOKUP_URI} 仍将指向联系人行,这样,您就能使用 +{@code android.provider.ContactsContract.ContactsColumns#LOOKUP_KEY} +保持指向“最喜爱”联系人的链接,以及执行其他操作。 +该列具有其自己的格式,与 {@code android.provider.BaseColumns#_ID} 列的格式无关。 + +

+

+ 图 3 显示的是这三个主要表的相互关系。 +

+Contacts provider main tables +

+ 图 3. 联系人表、原始联系人表与详细信息表之间的关系。 +

+

来自同步适配器的数据

+

+ 虽然用户是直接将联系人数据输入到设备中,但这些数据也会通过同步适配器从 Web 服务流入联系人提供程序中,这些同步适配器可自动化设备与服务之间的数据传送。 + +同步适配器在系统控制下在后台运行,它们会调用 {@link android.content.ContentResolver} 方法来管理数据。 + + +

+

+ 在 Android 中,与同步适配器协作的 Web 服务通过帐户类型加以标识。 + 每个同步适配器都与一个帐户类型协作,但它可以支持该类型的多个帐户名称。 +原始联系人数据来源部分对帐户类型和帐户名称做了简要描述。 +下列定义提供了更多详细信息,并描述了帐户类型及帐户名称与同步适配器及服务之间的关系。 + +

+
+
+ 帐户类型 +
+
+ 表示用户在其中存储数据的服务。在大多数时候,用户需要向服务验证身份。 +例如,Google Contacts 是一个以代码 google.com 标识的帐户类型。 +该值对应于 +{@link android.accounts.AccountManager} 使用的帐户类型。 +
+
+ 帐户名称 +
+
+ 表示某个帐户类型的特定帐户或登录名。Google Contacts 帐户与 Google 帐户相同,都是以电子邮件地址作为帐户名称。 + + 其他服务可能使用一个单词的用户名或数字 ID。 +
+
+

+ 帐户类型不必具有唯一性。用户可以配置多个 Google Contacts 帐户并将它们的数据下载到联系人提供程序;如果用户为个人帐户名称和工作帐户名称分别设置了一组联系人,就可能发生这种情况。 + +帐户名称通常具有唯一性。 +它们共同标识联系人提供程序与外部服务之间的特定数据流。 + +

+

+ 如果您想将服务的数据传送到联系人提供程序,则需编写您自己的同步适配器。 +联系人提供程序同步适配器部分对此做了更详尽的描述。 + +

+

+ 图 4 显示的是联系人提供程序如何融入联系人数据的流动。 +在名为“同步适配器”的方框中,每个适配器都以其帐户类型命名。 +

+Flow of data about people +

+ 图 4. 联系人提供程序数据流。 +

+

所需权限

+

+ 想要访问联系人提供程序的应用必须请求以下权限: + +

+
+
对一个或多个表的读取权限
+
+ {@link android.Manifest.permission#READ_CONTACTS},在 +AndroidManifest.xml 中指定,使用 + + <uses-permission> 元素作为 +<uses-permission android:name="android.permission.READ_CONTACTS">。 +
+
对一个或多个表的写入权限
+
+ {@link android.Manifest.permission#WRITE_CONTACTS},在 +AndroidManifest.xml 中指定,使用 + + <uses-permission> 元素作为 +<uses-permission android:name="android.permission.WRITE_CONTACTS">。 +
+
+

+ 这些权限不适用于用户个人资料数据。下面的用户个人资料部分对用户个人资料及其所需权限做了阐述。 + + +

+

+ 请切记,用户的联系人数据属于个人敏感数据。用户关心其隐私权,因此不希望应用收集有关其自身的数据或其联系人的数据。 + + 如需权限来访问其联系人数据的理由并不充分,用户可能给您的应用作出差评或干脆拒绝安装。 + +

+

用户个人资料

+

+ {@link android.provider.ContactsContract.Contacts} 表有一行包含设备用户的个人资料数据。 +这些数据描述设备的 user 而不是用户的其中一位联系人。 +对于每个使用个人资料的系统,该个人资料联系人行都链接到某个原始联系人行。 + + 每个个人资料原始联系人行可具有多个数据行。{@link android.provider.ContactsContract.Profile} 类中提供了用于访问用户个人资料的常量。 + +

+

+ 访问用户个人资料需要特殊权限。除了进行读取和写入所需的 +{@link android.Manifest.permission#READ_CONTACTS} 和 +{@link android.Manifest.permission#WRITE_CONTACTS} 权限外,如果想访问用户个人资料,还分别需要 {@code android.Manifest.permission#READ_PROFILE} 和 +{@code android.Manifest.permission#WRITE_PROFILE} 权限进行读取和写入访问。 + + +

+

+ 请切记,您应该将用户的个人资料视为敏感数据。{@code android.Manifest.permission#READ_PROFILE} 权限让您可以访问设备用户的个人身份识别数据。 + +请务必在您的应用的描述中告知用户您需要用户个人资料访问权限的原因。 + +

+

+ 要检索包含用户个人资料的联系人行,请调用 {@link android.content.ContentResolver#query(Uri,String[], String, String[], String) +ContentResolver.query()}。 +将内容 URI 设置为 +{@link android.provider.ContactsContract.Profile#CONTENT_URI} 并且不要提供任何选择条件。 +您还可以使用该内容 URI 作为检索原始联系人或个人资料数据的基本 URI。 +例如,以下代码段用于检索个人资料数据: +

+
+// Sets the columns to retrieve for the user profile
+mProjection = new String[]
+    {
+        Profile._ID,
+        Profile.DISPLAY_NAME_PRIMARY,
+        Profile.LOOKUP_KEY,
+        Profile.PHOTO_THUMBNAIL_URI
+    };
+
+// Retrieves the profile from the Contacts Provider
+mProfileCursor =
+        getContentResolver().query(
+                Profile.CONTENT_URI,
+                mProjection ,
+                null,
+                null,
+                null);
+
+

+ 注:如果您要检索多个联系人行并想要确定其中一个是否为用户个人资料,请测试该行的 +{@link android.provider.ContactsContract.ContactsColumns#IS_USER_PROFILE} 列。 +如果该联系人是用户个人资料,则此列设置为“1”。 + +

+

联系人提供程序元数据

+

+ 联系人提供程序管理用于追踪存储库中联系人数据状态的数据。 +这些有关存储库的元数据存储在各处,其中包括原始联系人表行、数据表行和联系人表行、 +{@link android.provider.ContactsContract.Settings} 表以及 +{@link android.provider.ContactsContract.SyncState} 表。 +下表显示的是每一部分元数据的作用: + +

+

+ 表 3. 联系人提供程序中的元数据

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
含义
{@link android.provider.ContactsContract.RawContacts}{@link android.provider.ContactsContract.SyncColumns#DIRTY}“0”:上次同步以来未发生变化。 + 标记设备上因发生变化而需要同步回服务器的原始联系人。 +当 Android 应用更新行时,联系人提供程序会自动设置该值。 + +

+ 修改原始联系人表或数据表的同步适配器应始终向他们使用的内容 URI 追加字符串 {@link android.provider.ContactsContract#CALLER_IS_SYNCADAPTER}。 + +这可以防止提供程序将行标记为已更新。 + 否则,即使服务器是修改的来源,同步适配器修改仍显示为本地修改,并会发送到服务器。 + +

+
“1”:上次同步以来发生了变化,需要同步回服务器。
{@link android.provider.ContactsContract.RawContacts}{@link android.provider.ContactsContract.SyncColumns#VERSION}此行的版本号。 + 每当行或其相关数据发生变化时,联系人提供程序都会自动增加此值。 + +
{@link android.provider.ContactsContract.Data}{@link android.provider.ContactsContract.DataColumns#DATA_VERSION}此行的版本号。 + 每当数据行发生变化时,联系人提供程序都会自动增加此值。 + +
{@link android.provider.ContactsContract.RawContacts}{@link android.provider.ContactsContract.SyncColumns#SOURCE_ID} + 一个字符串值,用于在创建此原始联系人的帐户中对该联系人进行唯一标识。 + + + 当同步适配器创建新原始联系人时,此列应设置为该原始联系人在服务器中的唯一 ID。 +当 Android 应用创建新原始联系人时,应将此列留空。 +这是为了向同步适配器表明,它应该在服务器上创建新原始联系人,并获取 + {@link android.provider.ContactsContract.SyncColumns#SOURCE_ID} 的值。 + +

+ 具体地讲,对于每个帐户类型,该源 ID 都必须是唯一的,并且应在所有同步中保持稳定: + +

+
    +
  • + 唯一:帐户的每个原始联系人都必须有自己的源 ID。如果您不强制执行此要求,会在联系人应用中引发问题。 + + 请注意,帐户类型相同的两个原始联系人可以具有相同的源 ID。 +例如,允许帐户 {@code emily.dickinson@gmail.com} 的原始联系人“Thomas Higginson”与帐户 +{@code emilyd@gmail.com} 的原始联系人“Thomas Higginson”具有相同的源 ID。 + + +
  • +
  • + 稳定:源 ID 是该原始联系人在在线服务中的数据的永久性组成部分。 +例如,如果用户从应用设置中清除存储的联系人数据并重新同步,则恢复的原始联系人的源 ID 应与以前相同。 + +如果您不强制执行此要求,快捷方式将停止工作。 + +
  • +
+
{@link android.provider.ContactsContract.Groups}{@link android.provider.ContactsContract.GroupsColumns#GROUP_VISIBLE}“0”:此组中的联系人在 Android 应用 UI 中不应处于可见状态。 + 此列用于兼容那些允许用户隐藏特定组中联系人的服务器。 + +
“1”:系统允许此组中的联系人在应用 UI 中处于可见状态。
{@link android.provider.ContactsContract.Settings} + {@link android.provider.ContactsContract.SettingsColumns#UNGROUPED_VISIBLE} + “0”:对于此帐户和帐户类型,未归入组的联系人在 Android 应用 UI 中处于不可见状态。 + + + 默认情况下,如果联系人的所有原始联系人都未归入组,则它们将处于不可见状态(原始联系人的组成员身份通过 {@link android.provider.ContactsContract.Data} 表中的一个或多个 +{@link android.provider.ContactsContract.CommonDataKinds.GroupMembership} 行指示)。 + + + 通过在 {@link android.provider.ContactsContract.Settings} 表行中为帐户类型和帐户设置此标志,您可以强制未归入组的联系人处于可见状态。 + + 此标志的一个用途是显示不使用组的服务器上的联系人。 +
+ “1”:对于此帐户和帐户类型,未归入组的联系人在应用 UI 中处于可见状态。 + +
{@link android.provider.ContactsContract.SyncState}(所有列) + 此表用于存储同步适配器的元数据。 + + 利用此表,您可以将同步状态及其他同步相关数据持久地存储在设备中。 + +
+

联系人提供程序访问

+

+ 本节描述访问联系人提供程序中数据的准则,侧重于阐述以下内容: + +

+
    +
  • + 实体查询。 +
  • +
  • + 批量修改。 +
  • +
  • + 通过 Intent 执行检索和修改。 +
  • +
  • + 数据完整性。 +
  • +
+

+ 联系人提供程序同步适配器部分也对通过同步适配器进行修改做了更详尽的阐述。 + +

+

查询实体

+

+ 由于联系人提供程序表是以层级形式组织,因此对于检索某一行以及与其链接的所有“子”行,往往很有帮助。 +例如,要想显示某位联系人的所有信息,您可能需要检索某个 +{@link android.provider.ContactsContract.Contacts} 行的所有{@link android.provider.ContactsContract.RawContacts} 行,或者检索某个 +{@link android.provider.ContactsContract.RawContacts} 行的所有 +{@link android.provider.ContactsContract.CommonDataKinds.Email} 行。 + +为便于执行此操作,联系人提供程序提供了实体构造,其作用类似于表间的数据库连接。 + + +

+

+ 实体类似于一个表,由父表及其子表中的选定列组成。 + 当您查询实体时,需要根据实体中的可用列提供投影和搜索条件。 +结果会得到一个 {@link android.database.Cursor},检索的每个子表行在其中都有一行与之对应。 +例如,如果您在 +{@link android.provider.ContactsContract.Contacts.Entity} 中查询某个联系人姓名以及该姓名所有原始联系人的所有 {@link android.provider.ContactsContract.CommonDataKinds.Email} 行,您会获得一个 {@link android.database.Cursor},每个 {@link android.provider.ContactsContract.CommonDataKinds.Email} 行在其中都有一行与之对应。 + + + +

+

+ 实体简化了查询。使用实体时,您可以一次性检索联系人或原始联系人的所有联系人数据,而不必先通过查询父表获得ID,然后通过该 ID 查询子表。此外,联系人提供程序可通过单一事务处理实体查询,这确保了所检索数据的内部一致性。 + + + + +

+

+ 注:实体通常不包含父表和子表的所有列。 +如果您试图使用的列名称并未出现在实体的列名称常量列表中,则会引发一个 {@link java.lang.Exception}。 + +

+

+ 以下代码段说明如何检索某位联系人的所有原始联系人行。该代码段是一个大型应用的组成部分,包含“主”和“详”两个 Activity。 +主 Activity 显示一个联系人行列表;当用户选择一行时,该 Activity 会将其 ID 发送至详 Activity。 + +详 Activity 使用 {@link android.provider.ContactsContract.Contacts.Entity} 显示与所选联系人关联的所有原始联系人中的所有数据行。 + + +

+

+ 以下代码段摘自“detail”Activity: +

+
+...
+    /*
+     * Appends the entity path to the URI. In the case of the Contacts Provider, the
+     * expected URI is content://com.google.contacts/#/entity (# is the ID value).
+     */
+    mContactUri = Uri.withAppendedPath(
+            mContactUri,
+            ContactsContract.Contacts.Entity.CONTENT_DIRECTORY);
+
+    // Initializes the loader identified by LOADER_ID.
+    getLoaderManager().initLoader(
+            LOADER_ID,  // The identifier of the loader to initialize
+            null,       // Arguments for the loader (in this case, none)
+            this);      // The context of the activity
+
+    // Creates a new cursor adapter to attach to the list view
+    mCursorAdapter = new SimpleCursorAdapter(
+            this,                        // the context of the activity
+            R.layout.detail_list_item,   // the view item containing the detail widgets
+            mCursor,                     // the backing cursor
+            mFromColumns,                // the columns in the cursor that provide the data
+            mToViews,                    // the views in the view item that display the data
+            0);                          // flags
+
+    // Sets the ListView's backing adapter.
+    mRawContactList.setAdapter(mCursorAdapter);
+...
+@Override
+public Loader<Cursor> onCreateLoader(int id, Bundle args) {
+
+    /*
+     * Sets the columns to retrieve.
+     * RAW_CONTACT_ID is included to identify the raw contact associated with the data row.
+     * DATA1 contains the first column in the data row (usually the most important one).
+     * MIMETYPE indicates the type of data in the data row.
+     */
+    String[] projection =
+        {
+            ContactsContract.Contacts.Entity.RAW_CONTACT_ID,
+            ContactsContract.Contacts.Entity.DATA1,
+            ContactsContract.Contacts.Entity.MIMETYPE
+        };
+
+    /*
+     * Sorts the retrieved cursor by raw contact id, to keep all data rows for a single raw
+     * contact collated together.
+     */
+    String sortOrder =
+            ContactsContract.Contacts.Entity.RAW_CONTACT_ID +
+            " ASC";
+
+    /*
+     * Returns a new CursorLoader. The arguments are similar to
+     * ContentResolver.query(), except for the Context argument, which supplies the location of
+     * the ContentResolver to use.
+     */
+    return new CursorLoader(
+            getApplicationContext(),  // The activity's context
+            mContactUri,              // The entity content URI for a single contact
+            projection,               // The columns to retrieve
+            null,                     // Retrieve all the raw contacts and their data rows.
+            null,                     //
+            sortOrder);               // Sort by the raw contact ID.
+}
+
+

+ 加载完成时,{@link android.app.LoaderManager} 会调用一个 +{@link android.app.LoaderManager.LoaderCallbacks#onLoadFinished(Loader, D) +onLoadFinished()} 回调。此方法的传入参数之一是一个 +{@link android.database.Cursor},其中包含查询的结果。在您自己的应用中,您可以从该 {@link android.database.Cursor} 获取数据,以进行显示或做进一步处理。 + +

+

批量修改

+

+ 您应尽可能地通过创建一个 {@link android.content.ContentProviderOperation} 对象 {@link java.util.ArrayList} +并调用 {@link android.content.ContentResolver#applyBatch(String, ArrayList) applyBatch()},以“批处理模式”在联系人提供程序中插入、更新和删除数据。 + +由于联系人提供程序是在 +{@link android.content.ContentResolver#applyBatch(String, ArrayList) applyBatch()} 中通过单一事务执行所有操作,因此您的修改绝不会使联系人存储库出现不一致问题。 + + +此外,批量修改还有便于同时插入原始联系人及其明细数据。 + +

+

+ 注:要修改单个原始联系人,可以考虑向设备的联系人应用发送一个 Intent,而不是在您的应用中处理修改。通过 Intent 执行检索和修改部分对此操作做了更详尽的描述。 + + + +

+

屈服点

+

+ 一个包含大量操作的批量修改可能会阻断其他进程,导致糟糕的总体用户体验。 +要将您想执行的所有修改组织到尽可能少的单独列表中,同时防止它们阻断系统,则应为一项或多项操作设置屈服点。 + + + 屈服点是一个 {@link android.content.ContentProviderOperation} 对象,其 +{@link android.content.ContentProviderOperation#isYieldAllowed()} 值设置为 +true。当联系人提供程序遇到屈服点时,它会暂停其工作,让其他进程运行,并关闭当前事务。 +当提供程序再次启动时,它会继续执行 {@link java.util.ArrayList} 中的下一项操作,并启动一个新的事务。 + + +

+

+ 屈服点会导致每次调用 +{@link android.content.ContentResolver#applyBatch(String, ArrayList) applyBatch()} 会产生多个事务。因此,您应该为针对一组相关行的最后一项操作设置屈服点。 + + 例如,您应该为一组操作中添加原始联系人行及其关联数据行的最后一项操作,或者针对一组与一位联系人相关的行的最后一项操作设置屈服点。 + + +

+

+ 屈服点也是一个原子操作单元。两个屈服点之间所有访问的成功或失败都将以一个单元的形式出现。 +如果您不设置任何屈服点,则最小的原子操作是整个批量操作。 +如果您使用了屈服点,则可以防止操作降低系统性能,还可确保一部分操作是原子操作。 + + +

+

修改向后引用

+

+ 当您将一个新原始联系人行及其关联的数据行作为一组 +{@link android.content.ContentProviderOperation} 对象插入时,需要通过将原始联系人的 +{@code android.provider.BaseColumns#_ID} 值作为 +{@link android.provider.ContactsContract.DataColumns#RAW_CONTACT_ID} 值插入,将数据行链接到原始联系人行。 +不过,当您为数据行创建 +{@link android.content.ContentProviderOperation} 时,该值不可用,因为您尚未对原始联系人行应用 +{@link android.content.ContentProviderOperation}。 +为解决此问题, +{@link android.content.ContentProviderOperation.Builder} 类使用了 +{@link android.content.ContentProviderOperation.Builder#withValueBackReference(String, int) withValueBackReference()} 方法。 + 该方法让您可以插入或修改包含上一操作结果的列。 + +

+

+ {@link android.content.ContentProviderOperation.Builder#withValueBackReference(String, int) withValueBackReference()} +方法具有两个参数: +

+
+
+ key +
+
+ 键-值对的键。此参数的值应为您要修改的表中某一列的名称。 + +
+
+ previousResult +
+
+ {@link android.content.ContentResolver#applyBatch(String, ArrayList) applyBatch()} 中 +{@link android.content.ContentProviderResult} 对象数组内某一值以 0 开始的索引。 +应用批处理操作时,每个操作的结果都存储在一个中间结果数组内。 + +previousResult 值是其中一个结果的索引,它通过 key +值进行检索和存储。 +这样,您就可以插入一条新的原始联系人记录,并取回其 +{@code android.provider.BaseColumns#_ID} 值,然后在添加 {@link android.provider.ContactsContract.Data} 行时“向后引用”该值。 + +

+ 系统会在您首次调用 +{@link android.content.ContentResolver#applyBatch(String, ArrayList) applyBatch()} 时创建整个结果数组,其大小与您提供的 {@link android.content.ContentProviderOperation} 对象的 {@link java.util.ArrayList} 大小相等。 + +不过,结果数组中的所有元素都设置为 null,如果您试图向后引用某个尚未应用的操作的结果, +{@link android.content.ContentProviderOperation.Builder#withValueBackReference(String, int) withValueBackReference()} +会引发一个 {@link java.lang.Exception}。 + + + +

+
+
+

+ 以下代码段说明如何批量插入新原始联系人和数据。代码段中包括用于建立屈服点和使用向后引用的代码。 +这些代码段是扩展版本的 createContacEntry() 方法,该方法是 + Contact Manager 示例应用中 ContactAdder 类的组成部分。 + + + +

+

+ 第一个代码段用于检索 UI 中的联系人数据。此时,用户已经选择了应添加新原始联系人的帐户。 + +

+
+// Creates a contact entry from the current UI values, using the currently-selected account.
+protected void createContactEntry() {
+    /*
+     * Gets values from the UI
+     */
+    String name = mContactNameEditText.getText().toString();
+    String phone = mContactPhoneEditText.getText().toString();
+    String email = mContactEmailEditText.getText().toString();
+
+    int phoneType = mContactPhoneTypes.get(
+            mContactPhoneTypeSpinner.getSelectedItemPosition());
+
+    int emailType = mContactEmailTypes.get(
+            mContactEmailTypeSpinner.getSelectedItemPosition());
+
+

+ 下一个代码段用于创建将该原始联系人行插入 +{@link android.provider.ContactsContract.RawContacts} 表的操作: +

+
+    /*
+     * Prepares the batch operation for inserting a new raw contact and its data. Even if
+     * the Contacts Provider does not have any data for this person, you can't add a Contact,
+     * only a raw contact. The Contacts Provider will then add a Contact automatically.
+     */
+
+     // Creates a new array of ContentProviderOperation objects.
+    ArrayList<ContentProviderOperation> ops =
+            new ArrayList<ContentProviderOperation>();
+
+    /*
+     * Creates a new raw contact with its account type (server type) and account name
+     * (user's account). Remember that the display name is not stored in this row, but in a
+     * StructuredName data row. No other data is required.
+     */
+    ContentProviderOperation.Builder op =
+            ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI)
+            .withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, mSelectedAccount.getType())
+            .withValue(ContactsContract.RawContacts.ACCOUNT_NAME, mSelectedAccount.getName());
+
+    // Builds the operation and adds it to the array of operations
+    ops.add(op.build());
+
+

+ 接着,代码会创建显示姓名行、电话行和电子邮件行的数据行。 +

+

+ 每个操作生成器对象都使用 +{@link android.content.ContentProviderOperation.Builder#withValueBackReference(String, int) withValueBackReference()} +来获取 +{@link android.provider.ContactsContract.DataColumns#RAW_CONTACT_ID}。引用指回来自第一次操作的 {@link android.content.ContentProviderResult} 对象,第一次操作就是添加原始联系人行并返回其新 {@code android.provider.BaseColumns#_ID} +值。 + +结果是,每个数据行都通过其 +{@link android.provider.ContactsContract.DataColumns#RAW_CONTACT_ID} +自动链接到其所属的 {@link android.provider.ContactsContract.RawContacts} 行。 +

+

+ 添加电子邮件行的 {@link android.content.ContentProviderOperation.Builder} 对象带有 +{@link android.content.ContentProviderOperation.Builder#withYieldAllowed(boolean) +withYieldAllowed()} 标志,用于设置屈服点: +

+
+    // Creates the display name for the new raw contact, as a StructuredName data row.
+    op =
+            ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
+            /*
+             * withValueBackReference sets the value of the first argument to the value of
+             * the ContentProviderResult indexed by the second argument. In this particular
+             * call, the raw contact ID column of the StructuredName data row is set to the
+             * value of the result returned by the first operation, which is the one that
+             * actually adds the raw contact row.
+             */
+            .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
+
+            // Sets the data row's MIME type to StructuredName
+            .withValue(ContactsContract.Data.MIMETYPE,
+                    ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE)
+
+            // Sets the data row's display name to the name in the UI.
+            .withValue(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, name);
+
+    // Builds the operation and adds it to the array of operations
+    ops.add(op.build());
+
+    // Inserts the specified phone number and type as a Phone data row
+    op =
+            ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
+            /*
+             * Sets the value of the raw contact id column to the new raw contact ID returned
+             * by the first operation in the batch.
+             */
+            .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
+
+            // Sets the data row's MIME type to Phone
+            .withValue(ContactsContract.Data.MIMETYPE,
+                    ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE)
+
+            // Sets the phone number and type
+            .withValue(ContactsContract.CommonDataKinds.Phone.NUMBER, phone)
+            .withValue(ContactsContract.CommonDataKinds.Phone.TYPE, phoneType);
+
+    // Builds the operation and adds it to the array of operations
+    ops.add(op.build());
+
+    // Inserts the specified email and type as a Phone data row
+    op =
+            ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
+            /*
+             * Sets the value of the raw contact id column to the new raw contact ID returned
+             * by the first operation in the batch.
+             */
+            .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
+
+            // Sets the data row's MIME type to Email
+            .withValue(ContactsContract.Data.MIMETYPE,
+                    ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE)
+
+            // Sets the email address and type
+            .withValue(ContactsContract.CommonDataKinds.Email.ADDRESS, email)
+            .withValue(ContactsContract.CommonDataKinds.Email.TYPE, emailType);
+
+    /*
+     * Demonstrates a yield point. At the end of this insert, the batch operation's thread
+     * will yield priority to other threads. Use after every set of operations that affect a
+     * single contact, to avoid degrading performance.
+     */
+    op.withYieldAllowed(true);
+
+    // Builds the operation and adds it to the array of operations
+    ops.add(op.build());
+
+

+ 最后一个代码段显示的是 +{@link android.content.ContentResolver#applyBatch(String, ArrayList) applyBatch()} 调用,用于插入新原始联系人行和数据行。 + +

+
+    // Ask the Contacts Provider to create a new contact
+    Log.d(TAG,"Selected account: " + mSelectedAccount.getName() + " (" +
+            mSelectedAccount.getType() + ")");
+    Log.d(TAG,"Creating contact: " + name);
+
+    /*
+     * Applies the array of ContentProviderOperation objects in batch. The results are
+     * discarded.
+     */
+    try {
+
+            getContentResolver().applyBatch(ContactsContract.AUTHORITY, ops);
+    } catch (Exception e) {
+
+            // Display a warning
+            Context ctx = getApplicationContext();
+
+            CharSequence txt = getString(R.string.contactCreationFailure);
+            int duration = Toast.LENGTH_SHORT;
+            Toast toast = Toast.makeText(ctx, txt, duration);
+            toast.show();
+
+            // Log exception
+            Log.e(TAG, "Exception encountered while inserting contact: " + e);
+    }
+}
+
+

+ 此外,您还可以利用批处理操作实现乐观并发控制,这是一种无需锁定底层存储库便可应用修改事务的控制方法。 + + 要使用此方法,您需要应用事务,然后检查是否存在可能已同时做出的其他修改。 +如果您发现了不一致的修改,请回滚事务并重试。 + +

+

+ 乐观并发控制对于移动设备很有用,因为在移动设备上,同一时间只有一位用户,并且同时访问数据存储库的情况很少见。 +由于未使用锁定功能,因此不用浪费时间设置锁定或等待其他事务解除锁定。 + +

+

+ 要在更新某个 +{@link android.provider.ContactsContract.RawContacts} 行时使用乐观并发控制,请按以下步骤操作: +

+
    +
  1. + 检索原始联系人的 {@link android.provider.ContactsContract.SyncColumns#VERSION} +列以及要检索的其他数据。 +
  2. +
  3. + 创建一个适合使用 +{@link android.content.ContentProviderOperation#newAssertQuery(Uri)} 方法强制执行约束 +的 {@link android.content.ContentProviderOperation.Builder} 对象。对于内容 URI,请使用追加有原始联系人 {@code android.provider.BaseColumns#_ID} 的 {@link android.provider.ContactsContract.RawContacts#CONTENT_URI +RawContacts.CONTENT_URI} +。 + +
  4. +
  5. + 对于 {@link android.content.ContentProviderOperation.Builder} 对象,请调用 +{@link android.content.ContentProviderOperation.Builder#withValue(String, Object) +withValue()},对 {@link android.provider.ContactsContract.SyncColumns#VERSION} +列与您刚检索的版本号进行比较。 +
  6. +
  7. + 对于同一 {@link android.content.ContentProviderOperation.Builder},请调用 +{@link android.content.ContentProviderOperation.Builder#withExpectedCount(int) +withExpectedCount()},确保此断言只对一行进行测试。 +
  8. +
  9. + 调用 {@link android.content.ContentProviderOperation.Builder#build()} 创建 +{@link android.content.ContentProviderOperation} 对象,然后将此对象添加为要传递至 +{@link android.content.ContentResolver#applyBatch(String, ArrayList) applyBatch()} 的 {@link java.util.ArrayList} 中的第一个对象。 + +
  10. +
  11. + 应用批处理事务。 +
  12. +
+

+ 如果在您读取原始联系人行到您试图对其进行修改这段时间有另一项操作更新了该行,“断言”{@link android.content.ContentProviderOperation} +将会失败,系统将终止整个批处理操作。 +此情况下,您可以选择重新执行批处理操作,或执行其他某操作。 + +

+

+ 以下代码段演示如何在使用 {@link android.content.CursorLoader} 查询一位原始联系人后创建一个“断言” +{@link android.content.ContentProviderOperation}: + +

+
+/*
+ * The application uses CursorLoader to query the raw contacts table. The system calls this method
+ * when the load is finished.
+ */
+public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
+
+    // Gets the raw contact's _ID and VERSION values
+    mRawContactID = cursor.getLong(cursor.getColumnIndex(BaseColumns._ID));
+    mVersion = cursor.getInt(cursor.getColumnIndex(SyncColumns.VERSION));
+}
+
+...
+
+// Sets up a Uri for the assert operation
+Uri rawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, mRawContactID);
+
+// Creates a builder for the assert operation
+ContentProviderOperation.Builder assertOp = ContentProviderOperation.netAssertQuery(rawContactUri);
+
+// Adds the assertions to the assert operation: checks the version and count of rows tested
+assertOp.withValue(SyncColumns.VERSION, mVersion);
+assertOp.withExpectedCount(1);
+
+// Creates an ArrayList to hold the ContentProviderOperation objects
+ArrayList ops = new ArrayList<ContentProviderOperationg>;
+
+ops.add(assertOp.build());
+
+// You would add the rest of your batch operations to "ops" here
+
+...
+
+// Applies the batch. If the assert fails, an Exception is thrown
+try
+    {
+        ContentProviderResult[] results =
+                getContentResolver().applyBatch(AUTHORITY, ops);
+
+    } catch (OperationApplicationException e) {
+
+        // Actions you want to take if the assert operation fails go here
+    }
+
+

通过 Intent 执行检索和修改

+

+ 通过向设备的联系人应用发送 Intent,您可以间接访问联系人提供程序。 + Intent 会启动设备的联系人应用 UI,用户可以在其中执行与联系人有关的操作。 +通过这种访问方式,用户可以: +

    +
  • 从列表中选取一位联系人并将其返回给您的应用以执行进一步操作。
  • +
  • 编辑现有联系人的数据。
  • +
  • 为其任一帐户插入新原始联系人。
  • +
  • 删除联系人或联系人数据。
  • +
+

+ 如果用户要插入或更新数据,您可以先收集数据,然后将其作为 Intent 的一部分发送。 + +

+

+ 当您使用 Intent 通过设备的联系人应用访问联系人提供程序时,您无需自行编写用于访问该提供程序的 UI 或代码。 +您也无需请求对提供程序的读取或写入权限。 +设备的联系人应用可以将联系人读取权限授予给您,而且您是通过另一个应用对该提供程序进行修改,不需要拥有写入权限。 + + +

+

+ 内容提供程序基础知识指南“通过 Intent 访问数据”部分详细描述了通过发送 Intent 来访问某提供程序的一般过程。 + +表 4 汇总了您为可用任务使用的操作、MIME 类型以及数据值,{@link android.provider.ContactsContract.Intents.Insert} 参考文档列出了您可用于{@link android.content.Intent#putExtra(String, String) putExtra()} 的 Extra 值: + + + + +

+

+ 表 4. 联系人提供程序 Intent。 +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
任务操作数据MIME 类型备注
从列表中选取一位联系人{@link android.content.Intent#ACTION_PICK} + 下列值之一: +
    +
  • +{@link android.provider.ContactsContract.Contacts#CONTENT_URI Contacts.CONTENT_URI},显示联系人列表。 + +
  • +
  • +{@link android.provider.ContactsContract.CommonDataKinds.Phone#CONTENT_URI Phone.CONTENT_URI},显示原始联系人的电话号码列表。 + +
  • +
  • +{@link android.provider.ContactsContract.CommonDataKinds.StructuredPostal#CONTENT_URI +StructuredPostal.CONTENT_URI},显示原始联系人的邮政地址列表。 + +
  • +
  • +{@link android.provider.ContactsContract.CommonDataKinds.Email#CONTENT_URI Email.CONTENT_URI},显示原始联系人的电子邮件地址列表。 + +
  • +
+
+ 未使用 + + 显示原始联系人列表或一位原始联系人的数据列表,具体取决于您提供的内容 URI 类型。 + +

+ 调用 + {@link android.app.Activity#startActivityForResult(Intent, int) startActivityForResult()} 方法,该方法返回所选行的内容 URI。 +该 URI 的形式为:追加有该行 LOOKUP_ID 的表的内容 URI。 + + 设备的联系人应用会在 Activity 的生命周期内将读取和写入权限授予给此内容 URI。 +如需了解更多详细信息,请参阅内容提供程序基础知识指南。 + + +

+
插入新原始联系人{@link android.provider.ContactsContract.Intents.Insert#ACTION Insert.ACTION}不适用 + {@link android.provider.ContactsContract.RawContacts#CONTENT_TYPE +RawContacts.CONTENT_TYPE},用于一组原始联系人的 MIME 类型。 + + 显示设备联系人应用的添加联系人屏幕。系统会显示您添加到 Intent 中的 Extra 值。 +如果是随 +{@link android.app.Activity#startActivityForResult(Intent, int) startActivityForResult()} + 发送,系统会将新添加的原始联系人的内容 URI 传回给 +{@link android.app.Activity#onActivityResult(int, int, Intent) onActivityResult()} +回调方法并作为后者 {@link android.content.Intent} 参数的“data”字段。 +要获取该值,请调用 {@link android.content.Intent#getData()}。 +
编辑联系人{@link android.content.Intent#ACTION_EDIT} + 该联系人的 {@link android.provider.ContactsContract.Contacts#CONTENT_LOOKUP_URI}。 +该编辑器 Activity 让用户能够对任何与该联系人关联的数据进行编辑。 + + + {@link android.provider.ContactsContract.Contacts#CONTENT_ITEM_TYPE +Contacts.CONTENT_ITEM_TYPE},一位联系人。 + 显示联系人应用中的“编辑联系人”屏幕。系统会显示您添加到 Intent 中的 Extra 值。 +当用户点击完成保存编辑时,您的 Activity 会返回前台。 + +
显示一个同样可以添加数据的选取器。{@link android.content.Intent#ACTION_INSERT_OR_EDIT} + 不适用 + + {@link android.provider.ContactsContract.Contacts#CONTENT_ITEM_TYPE} + + 此 Intent 始终显示联系人应用的选取器屏幕。用户可以选取要编辑的联系人,或添加新联系人。 +根据用户的选择,系统会显示编辑屏幕或添加屏幕,还会显示您使用 Intent 传递的 Extra 数据。 + +如果您的应用显示电子邮件或电话号码等联系人数据,请使用此 Intent 来允许用户向现有联系人添加数据。 + + +

+ 注:不需要通过此 Intent 的 Extra 发送姓名值,因为用户总是会选取现有姓名或添加新姓名。 +此外,如果您发送姓名,并且用户选择执行编辑操作,则联系人应用将显示您发送的姓名,该姓名将覆盖以前的值。 + +如果用户未注意这一情况便保存了编辑,原有值将会丢失。 + +

+
+

+ 设备的联系人应用不允许您使用 Intent 删除原始联系人或其任何数据。 +因此,要删除原始联系人,请使用 +{@link android.content.ContentResolver#delete(Uri, String, String[]) ContentResolver.delete()} +或 {@link android.content.ContentProviderOperation#newDelete(Uri) +ContentProviderOperation.newDelete()}。 +

+

+ 以下代码段说明如何构建和发送一个插入新原始联系人和数据的 Intent: + +

+
+// Gets values from the UI
+String name = mContactNameEditText.getText().toString();
+String phone = mContactPhoneEditText.getText().toString();
+String email = mContactEmailEditText.getText().toString();
+
+String company = mCompanyName.getText().toString();
+String jobtitle = mJobTitle.getText().toString();
+
+// Creates a new intent for sending to the device's contacts application
+Intent insertIntent = new Intent(ContactsContract.Intents.Insert.ACTION);
+
+// Sets the MIME type to the one expected by the insertion activity
+insertIntent.setType(ContactsContract.RawContacts.CONTENT_TYPE);
+
+// Sets the new contact name
+insertIntent.putExtra(ContactsContract.Intents.Insert.NAME, name);
+
+// Sets the new company and job title
+insertIntent.putExtra(ContactsContract.Intents.Insert.COMPANY, company);
+insertIntent.putExtra(ContactsContract.Intents.Insert.JOB_TITLE, jobtitle);
+
+/*
+ * Demonstrates adding data rows as an array list associated with the DATA key
+ */
+
+// Defines an array list to contain the ContentValues objects for each row
+ArrayList<ContentValues> contactData = new ArrayList<ContentValues>();
+
+
+/*
+ * Defines the raw contact row
+ */
+
+// Sets up the row as a ContentValues object
+ContentValues rawContactRow = new ContentValues();
+
+// Adds the account type and name to the row
+rawContactRow.put(ContactsContract.RawContacts.ACCOUNT_TYPE, mSelectedAccount.getType());
+rawContactRow.put(ContactsContract.RawContacts.ACCOUNT_NAME, mSelectedAccount.getName());
+
+// Adds the row to the array
+contactData.add(rawContactRow);
+
+/*
+ * Sets up the phone number data row
+ */
+
+// Sets up the row as a ContentValues object
+ContentValues phoneRow = new ContentValues();
+
+// Specifies the MIME type for this data row (all data rows must be marked by their type)
+phoneRow.put(
+        ContactsContract.Data.MIMETYPE,
+        ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE
+);
+
+// Adds the phone number and its type to the row
+phoneRow.put(ContactsContract.CommonDataKinds.Phone.NUMBER, phone);
+
+// Adds the row to the array
+contactData.add(phoneRow);
+
+/*
+ * Sets up the email data row
+ */
+
+// Sets up the row as a ContentValues object
+ContentValues emailRow = new ContentValues();
+
+// Specifies the MIME type for this data row (all data rows must be marked by their type)
+emailRow.put(
+        ContactsContract.Data.MIMETYPE,
+        ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE
+);
+
+// Adds the email address and its type to the row
+emailRow.put(ContactsContract.CommonDataKinds.Email.ADDRESS, email);
+
+// Adds the row to the array
+contactData.add(emailRow);
+
+/*
+ * Adds the array to the intent's extras. It must be a parcelable object in order to
+ * travel between processes. The device's contacts app expects its key to be
+ * Intents.Insert.DATA
+ */
+insertIntent.putParcelableArrayListExtra(ContactsContract.Intents.Insert.DATA, contactData);
+
+// Send out the intent to start the device's contacts app in its add contact activity.
+startActivity(insertIntent);
+
+

数据完整性

+

+ 联系人存储库包含用户认为是正确且是最新的重要敏感数据,因此联系人提供程序具有规定清晰的数据完整性规则。 +您有责任在修改联系人数据时遵守这些规则。 +以下列出了其中的重要规则: + +

+
+
+ 务必为您添加的每个 {@link android.provider.ContactsContract.RawContacts} 行添加一个 {@link android.provider.ContactsContract.CommonDataKinds.StructuredName} 行。 + +
+
+ 如果 {@link android.provider.ContactsContract.Data} 表中的 +{@link android.provider.ContactsContract.RawContacts} 行没有 +{@link android.provider.ContactsContract.CommonDataKinds.StructuredName} 行,可能会在聚合时引发问题。 + +
+
+ 务必将新 {@link android.provider.ContactsContract.Data} 行链接到其父 +{@link android.provider.ContactsContract.RawContacts} 行。 +
+
+ 如果 {@link android.provider.ContactsContract.Data} 行未链接到 +{@link android.provider.ContactsContract.RawContacts},则其在设备的联系人应用中将处于不可见状态,而且这可能会导致同步适配器出现问题。 + +
+
+ 请仅更改您拥有的那些原始联系人的数据。 +
+
+ 请切记,联系人提供程序所管理的数据通常来自多个不同帐户类型/在线服务。 +您需要确保您的应用仅修改或删除归您所有的行的数据,并且仅通过您控制的帐户类型和帐户名称插入数据。 + + +
+
+ 务必使用在 {@link android.provider.ContactsContract} 及其子类中为权限、内容 URI、URI 路径、列名称、MIME 类型以及 +{@link android.provider.ContactsContract.CommonDataKinds.CommonColumns#TYPE} 值定义的常量。 + +
+
+ 使用这些常量有助于您避免错误。如有任何常量被弃用,您还会从编译器警告收到通知。 + +
+
+

自定义数据行

+

+ 通过创建和使用自己的自定义 MIME 类型,您可以在 {@link android.provider.ContactsContract.Data} 表中插入、编辑、删除和检索您的自有数据行。 +这些行仅限使用 {@link android.provider.ContactsContract.DataColumns} 中定义的列,但您可以将您自己的类型专用列名称映射到默认列名称。 + + +在设备的联系人应用中,会显示这些行的数据,但无法对其进行编辑或删除,用户也无法添加其他数据。 + +要允许用户修改您的自定义数据行,您必须在自己的应用中提供编辑器 Activity。 + +

+

+ 要显示您的自定义数据,请提供一个 contacts.xml 文件,其中须包含一个 +<ContactsAccountType> 元素,及其一个或多个 +<ContactsDataKind> 子元素。<ContactsDataKind> element 部分对此做了更详尽的描述。 + +

+

+ 如需了解有关自定义 MIME 类型的更多信息,请阅读创建内容提供程序指南。 + + +

+

联系人提供程序同步适配器

+

+ 联系人提供程序专门设计用于处理设备与在线服务之间的联系人数据同步。 +借助同步功能,用户可以将现有数据下载到新设备,以及将现有数据上传到新帐户。 + + 此外,同步还能确保用户掌握最新数据,无需考虑数据增加和更改的来源。 +同步的另一个优点是,即使设备未连接网络,联系人数据同样可用。 + +

+

+ 虽然您可以通过各种方式实现同步,不过 Android 系统提供了一个插件同步框架,可自动化完成下列任务: + +

    + +
  • + 检查网络可用性。 +
  • +
  • + 根据用户偏好安排和执行同步。 +
  • +
  • + 重启已停止的同步。 +
  • +
+

+ 要使用此框架,您需要提供一个同步适配器插件。每个同步适配器都专用于某个服务和内容提供程序,但可以处理同一服务的多个帐户名称。 +该框架还允许同一服务和提供程序具有多个同步适配器。 + +

+

同步适配器类和文件

+

+ 您需要将同步适配器作为 +{@link android.content.AbstractThreadedSyncAdapter} 的子类进行实现,并作为 Android +应用的一部分进行安装。系统通过您的应用清单文件中的元素以及由清单文件指向的一个特殊 XML 文件了解有关同步适配器的信息。 +该 XML 文件定义在线服务的帐户类型和内容提供程序的权限,它们共同对适配器进行唯一标识。 + +用户为同步适配器的帐户类型添加一个帐户,并为与同步适配器同步的内容提供程序启用同步后,同步适配器才会激活。 + +激活后,系统将开始管理适配器,并在必要时调用它,以在内容提供程序与服务器之间同步数据。 + +

+

+ 注:将帐户类型用作同步适配器标识的一部分让系统可以发现从同一组织访问不同服务的同步适配器,并将它们组合在一起。 + +例如,Google 在线服务的同步适配器都具有相同的帐户类型 com.google。 +当用户向其设备添加 Google 帐户时,已安装的所有 Google 服务同步适配器将一起列出;列出的每个同步适配器都与设备上不同的内容提供程序同步。 + + +

+

+ 大多数服务都要求用户验证身份后才能访问数据,为此,Android 系统提供了一个身份验证框架,该框架与同步适配器框架类似,并且经常与其联用。 + +该身份验证框架使用的插件身份验证器是 +{@link android.accounts.AbstractAccountAuthenticator} 的子类。 +身份验证器通过下列步骤验证用户的身份: + +

    +
  1. + 收集用户名、用户密码或类似信息(用户的凭据)。 + +
  2. +
  3. + 将凭据发送给服务 +
  4. +
  5. + 检查服务的回复。 +
  6. +
+

+ 如果服务接受了凭据,身份验证器便可存储凭据以供日后使用。 +由于插件身份验证器框架的存在,{@link android.accounts.AccountManager} 可以提供对身份验证器支持并选择公开的任何身份验证令牌(例如 OAuth2 身份验证令牌)的访问。 + + +

+

+ 尽管身份验证并非必需,但大多数联系人服务都会使用它。 + 不过,您不一定要使用 Android 身份验证框架进行身份验证。 +

+

同步适配器实现

+

+ 要为联系人提供程序实现同步适配器,您首先要创建一个包含以下内容的 +Android 应用: +

+
+
+ 一个 {@link android.app.Service} 组件,用于响应系统发出的绑定到同步适配器的请求。 + +
+
+ 当系统想要运行同步时,它会调用服务的 +{@link android.app.Service#onBind(Intent) onBind()} 方法,为同步适配器获取一个 +{@link android.os.IBinder}。这样,系统便可跨进程调用适配器的方法。 + +

+ 在示例同步适配器示例应用中,该服务的类名是 com.example.android.samplesync.syncadapter.SyncService。 + + +

+
+
+ 作为 +{@link android.content.AbstractThreadedSyncAdapter} 具体子类实现的实际同步适配器。 +
+
+ 此类的作用是从服务器下载数据、从设备上传数据以及解决冲突。 +适配器的主要工作是在方法 {@link android.content.AbstractThreadedSyncAdapter#onPerformSync( +Account, Bundle, String, ContentProviderClient, SyncResult) +onPerformSync()} 中完成的。 +必须将此类实例化为单一实例。 +

+ 在示例同步适配器示例应用中,同步适配器是在 com.example.android.samplesync.syncadapter.SyncAdapter 类中定义的。 + + +

+
+
+ {@link android.app.Application} 的子类。 +
+
+ 此类充当同步适配器单一实例的工厂。使用 +{@link android.app.Application#onCreate()} 方法实例化同步适配器,并提供一个静态“getter”方法,使单一实例返回同步适配器服务的 +{@link android.app.Service#onBind(Intent) onBind()} 方法。 + + +
+
+ 可选:一个 {@link android.app.Service} 组件,用于响应系统发出的用户身份验证请求。 + +
+
+ {@link android.accounts.AccountManager} 会启动此服务以开始身份验证流程。 +该服务的 {@link android.app.Service#onCreate()} 方法会将一个身份验证器对象实例化。 +当系统想要对应用同步适配器的用户帐户进行身份验证时,它会调用该服务的 +{@link android.app.Service#onBind(Intent) onBind()} 方法,为该身份验证器获取一个 +{@link android.os.IBinder}。 +这样,系统便可跨进程调用身份验证器的方法。 + +

+ 在示例同步适配器示例应用中,该服务的类名是 com.example.android.samplesync.authenticator.AuthenticationService。 + + +

+
+
+ 可选:一个用于处理身份验证请求的 +{@link android.accounts.AbstractAccountAuthenticator} 具体子类。 + +
+
+ {@link android.accounts.AccountManager} 就是调用此类所提供的方法向服务器验证用户的凭据。 +详细的身份验证过程会因服务器所采用技术的不同而有很大差异。 +您应该参阅服务器软件的文档,了解有关身份验证的更多信息。 + +

+ 在示例同步适配器示例应用中,身份验证器是在 com.example.android.samplesync.authenticator.Authenticator 类中定义的。 + + +

+
+
+ 用于定义系统同步适配器和身份验证器的 XML 文件。 +
+
+ 之前描述的同步适配器和身份验证器服务组件都是在应用清单文件中的 +<service> +元素内定义的。 +这些元素包含以下用于向系统提供特定数据的 +<meta-data> +子元素: + + +
    +
  • + 同步适配器服务的 +<meta-data> +元素指向 +XML 文件 res/xml/syncadapter.xml。而该文件则指定将与联系人提供程序同步的 Web 服务的 URI,以及指定该 Web 服务的帐户类型。 + + +
  • +
  • + 可选:身份验证器的 +<meta-data> +元素指向 XML 文件 +res/xml/authenticator.xml。而该文件则指定此身份验证器所支持的帐户类型,以及指定身份验证过程中出现的 UI 资源。 + +在此元素中指定的帐户类型必须与为同步适配器指定的帐户类型相同。 + + +
  • +
+
+
+

社交流数据

+

+ {@code android.provider.ContactsContract.StreamItems} 表和 +{@code android.provider.ContactsContract.StreamItemPhotos} 表管理来自社交网络的传入数据。 +您可以编写一个同步适配器,用其将您自己社交网络中的流数据添加到这些表中,也可以从这些表读取流数据并将其显示在您的自有应用中,或者同时采用这两种方法。 + +利用这些功能,可以将您的社交网络服务和应用集成到 Android 的社交网络体验之中。 + +

+

社交流文本

+

+ 流项目始终与原始联系人关联。 +{@code android.provider.ContactsContract.StreamItemsColumns#RAW_CONTACT_ID} 链接到原始联系人的 _ID 值。 +原始联系人的帐户类型和帐户名称也存储在流项目行中。 + +

+

+ 将您的流数据存储在以下列: +

+
+
+ {@code android.provider.ContactsContract.StreamItemsColumns#ACCOUNT_TYPE} +
+
+ 必备。与该流项目关联的原始联系人对应的用户帐户类型。 +请记得在插入流项目时设置此值。 +
+
+ {@code android.provider.ContactsContract.StreamItemsColumns#ACCOUNT_NAME} +
+
+ 必备。与该流项目关联的原始联系人对应的用户帐户名称。 +请记得在插入流项目时设置此值。 +
+
+ 标识符列 +
+
+ 必备。您必须在插入流项目时插入下列标识符列: + +
    +
  • + {@code android.provider.ContactsContract.StreamItemsColumns#CONTACT_ID}:此流项目关联的联系人的 +{@code android.provider.BaseColumns#_ID} 值。 + +
  • +
  • + {@code android.provider.ContactsContract.StreamItemsColumns#CONTACT_LOOKUP_KEY}:此流项目关联的联系人的 +{@code android.provider.ContactsContract.ContactsColumns#LOOKUP_KEY} 值。 + +
  • +
  • + {@code android.provider.ContactsContract.StreamItemsColumns#RAW_CONTACT_ID}:此流项目关联的原始联系人的 +{@code android.provider.BaseColumns#_ID} 值。 + +
  • +
+
+
+ {@code android.provider.ContactsContract.StreamItemsColumns#COMMENTS} +
+
+ 可选。存储可在流项目开头显示的摘要信息。 +
+
+ {@code android.provider.ContactsContract.StreamItemsColumns#TEXT} +
+
+ 流项目的文本,或为项目来源发布的内容,或是对生成流项目的某项操作的描述。 +此列可包含可由 +{@link android.text.Html#fromHtml(String) fromHtml()} 渲染的任何格式设置和嵌入式资源图像。 +提供程序可能会截断或省略较长内容,但它会尽力避免破坏标记。 + +
+
+ {@code android.provider.ContactsContract.StreamItemsColumns#TIMESTAMP} +
+
+ 一个包含流项目插入时间或更新时间的文本字符串,以从公元纪年开始计算的毫秒数形式表示。 +此列由插入或更新流项目的应用负责维护;联系人提供程序不会自动对其进行维护。 + + +
+
+

+ 要显示您的流项目的标识信息,请使用 +{@code android.provider.ContactsContract.StreamItemsColumns#RES_ICON}、 +{@code android.provider.ContactsContract.StreamItemsColumns#RES_LABEL} 和 +{@code android.provider.ContactsContract.StreamItemsColumns#RES_PACKAGE} 链接到您的应用中的资源。 + +

+

+ {@code android.provider.ContactsContract.StreamItems} 表还包含供同步适配器专用的列 +{@code android.provider.ContactsContract.StreamItemsColumns#SYNC1} 至 +{@code android.provider.ContactsContract.StreamItemsColumns#SYNC4}。 + +

+

社交流照片

+

+ {@code android.provider.ContactsContract.StreamItemPhotos} 表存储与流项目关联的照片。 +该表的 +{@code android.provider.ContactsContract.StreamItemPhotosColumns#STREAM_ITEM_ID}列链接到 {@code android.provider.ContactsContract.StreamItems} 表 {@code android.provider.BaseColumns#_ID} 列中的值。 + +照片引用存储在表中的以下列: + +

+
+
+ {@code android.provider.ContactsContract.StreamItemPhotos#PHOTO} 列(一个二进制大型对象)。 +
+
+ 照片的二进制表示,为便于存储和显示,由提供程序调整了尺寸。 + 此列可用于向后兼容使用它来存储照片的旧版本联系人提供程序。 +不过,在当前版本中,您不应使用此列来存储照片, +而应使用 +{@code android.provider.ContactsContract.StreamItemPhotosColumns#PHOTO_FILE_ID} 或 +{@code android.provider.ContactsContract.StreamItemPhotosColumns#PHOTO_URI}(下文对两者都做了描述)将照片存储在一个文件内。 +此列现在包含可用于读取的照片缩略图。 + +
+
+ {@code android.provider.ContactsContract.StreamItemPhotosColumns#PHOTO_FILE_ID} +
+
+ 原始联系人照片的数字标识符。将此值追加到常量 +{@link android.provider.ContactsContract.DisplayPhoto#CONTENT_URI DisplayPhoto.CONTENT_URI},获取指向单一照片文件的内容 URI,然后调用 +{@link android.content.ContentResolver#openAssetFileDescriptor(Uri, String) +openAssetFileDescriptor()} 来获取照片文件的句柄。 + +
+
+ {@code android.provider.ContactsContract.StreamItemPhotosColumns#PHOTO_URI} +
+
+ 一个内容 URI,直接指向此行所表示的照片的照片文件。 + 通过此 URI 调用 {@link android.content.ContentResolver#openAssetFileDescriptor(Uri, String) +openAssetFileDescriptor()} 以获得照片文件的句柄。 +
+
+

使用社交流表

+

+ 这些表的工作方式与联系人提供程序中的其他主表基本相同,不同的是: +

+
    +
  • + 这些表需要额外的访问权限。要读取它们的数据,您的应用必须具有 {@code android.Manifest.permission#READ_SOCIAL_STREAM} 权限。 +要修改它们,您的应用必须具有 +{@code android.Manifest.permission#WRITE_SOCIAL_STREAM} 权限。 + +
  • +
  • + 对于 {@code android.provider.ContactsContract.StreamItems} 表,为每一位原始联系人存储的行数有限。 +一旦达到该限制,联系人提供程序即会自动删除 +{@code android.provider.ContactsContract.StreamItemsColumns#TIMESTAMP} 最早的行,为新流项目行腾出空间。 + +要获取该限制,请发出对内容 URI +{@code android.provider.ContactsContract.StreamItems#CONTENT_LIMIT_URI} 的查询。 +您可以将内容 URI 以外的所有其他参数保持设置为 null。 +查询会返回一个 Cursor,其中包含一行,并且只有 +{@code android.provider.ContactsContract.StreamItems#MAX_ITEMS} 一列。 + +
  • +
+ +

+ {@code android.provider.ContactsContract.StreamItems.StreamItemPhotos} 类定义了 + {@code android.provider.ContactsContract.StreamItemPhotos} 的一个子表,其中包含某个流项目的照片行。 + +

+

社交流交互

+

+ 通过将联系人提供程序管理的社交流数据与设备的联系人应用相结合,可以在您的社交网络系统与现有联系人之间建立起有效的连接。 + +这种结合实现了下列功能: +

+
    +
  • + 您可以通过同步适配器让您的社交网络服务与联系人提供程序同步,检索用户联系人的近期 Activity,并将其存储在 + {@code android.provider.ContactsContract.StreamItems} 表和 +{@code android.provider.ContactsContract.StreamItemPhotos} 表中,以供日后使用。 + +
  • +
  • + 除了定期同步外,您还可以在用户选择某位联系人进行查看时触发您的同步适配器以检索更多数据。 +这样,您的同步适配器便可检索该联系人的高分辨率照片和最近流项目。 + +
  • +
  • + 通过在设备的联系人应用以及联系人提供程序中注册通知功能,您可以在用户查看联系人时收到一个 Intent,并在那时通过您的服务更新联系人的状态。 + +与通过同步适配器执行完全同步相比,此方法可能更快速,占用的带宽也更少。 + +
  • +
  • + 用户可以在查看设备联系人应用中的联系人时,将其添加到您的社交网络服务。 +您可以通过“邀请联系人”功能实现此目的,而该功能则是通过将 Activity 与 XML 文件结合使用来实现的,前者将现有联系人添加到您的社交网络,后者为设备的联系人应用以及联系人提供程序提供有关您的应用的详细信息。 + + + +
  • +
+

+ 流项目与联系人提供程序的定期同步与其他同步相同。 +如需了解有关同步的更多信息,请参阅 +联系人提供程序同步适配器部分。接下来的两节介绍如何注册通知和邀请联系人。 + +

+

通过注册处理社交网络查看

+

+ 要注册您的同步适配器,以便在用户查看由您的同步适配器管理的联系人时收到通知,请执行以下步骤: + +

+
    +
  1. + 在您项目的 res/xml/ 目录中创建一个名为 contacts.xml 的文件。 +如果您已有该文件,可跳过此步骤。 +
  2. +
  3. + 在该文件中添加元素 +<ContactsAccountType xmlns:android="http://schemas.android.com/apk/res/android">。 + 如果该元素已存在,可跳过此步骤。 +
  4. +
  5. + 要注册一项服务,以便在用户于设备的联系人应用中打开某位联系人的详细信息页面时通知该服务,请为该元素添加 +viewContactNotifyService="serviceclass" 属性,其中 +serviceclass 是该服务的完全限定类名,应由该服务接收来自设备联系人应用的 Intent。 + +对于这个通知程序服务,请使用一个扩展 {@link android.app.IntentService} 的类,以让该服务能够接收 Intent。 + +传入 Intent 中的数据包含用户点击的原始联系人的内容 URI。 +您可以通过通知程序服务绑定到您的同步适配器,然后调用同步适配器来更新原始联系人的数据。 + +
  6. +
+

+ 要注册需要在用户点击流项目或照片(或同时点击这两者)时调用的 Activity,请执行以下步骤: +

+
    +
  1. + 在您项目的 res/xml/ 目录中创建一个名为 contacts.xml 的文件。 +如果您已有该文件,可跳过此步骤。 +
  2. +
  3. + 在该文件中添加元素 +<ContactsAccountType xmlns:android="http://schemas.android.com/apk/res/android">。 + 如果该元素已存在,可跳过此步骤。 +
  4. +
  5. + 要注册某个 Activity,以处理用户在设备联系人应用中点击某个流项目的操作,请为该元素添加 +viewStreamItemActivity="activityclass" 属性,其中 +activityclass 是该 Activity 的完全限定类名,应由该 Activity 接收来自设备联系人应用的 Intent。 + + +
  6. +
  7. + 要注册某个 Activity,以处理用户在设备联系人应用中点击某个流照片的操作,请为该元素添加 +viewStreamItemPhotoActivity="activityclass" 属性,其中 +activityclass 是该 Activity 的完全限定类名,应由该 Activity 接收来自设备联系人应用的 Intent。 + + +
  8. +
+

+ <ContactsAccountType> 元素部分对 <ContactsAccountType> 元素做了更详尽的描述。 + +

+

+ 传入 Intent 包含用户点击的项目或照片的内容 URI。 + 要让文本项目和照片具有独立的 Activity,请在同一文件中使用这两个属性。 +

+

与您的社交网络服务交互

+

+ 用户不必为了邀请联系人到您的社交网络网站而离开设备的联系人应用。 +取而代之是,您可以让设备的联系人应用发送一个 Intent,将联系人 +邀请到您的 Activity 之一。要设置此功能,请执行以下步骤: +

+
    +
  1. + 在您项目的 res/xml/ 目录中创建一个名为 contacts.xml 的文件。 +如果您已有该文件,可跳过此步骤。 +
  2. +
  3. + 在该文件中添加元素 +<ContactsAccountType xmlns:android="http://schemas.android.com/apk/res/android">。 + 如果该元素已存在,可跳过此步骤。 +
  4. +
  5. + 添加以下属性: +
      +
    • inviteContactActivity="activityclass"
    • +
    • + inviteContactActionLabel="@string/invite_action_label" +
    • +
    + activityclass 值是应该接收该 Intent 的 Activity 的完全限定类名。 +invite_action_label +值是一个文本字符串,将显示在设备联系人应用的 Add Connection 菜单中。 + +
  6. +
+

+ 注:ContactsSource 是 +ContactsAccountType 的一个已弃用的标记名称。 +

+

contacts.xml 引用

+

+ 文件 contacts.xml 包含一些 XML 元素,这些元素控制您的同步适配器和应用与联系人应用及联系人提供程序的交互。 +下文对这些元素做了描述。 + +

+

<ContactsAccountType> 元素

+

+ <ContactsAccountType> 元素控制您的应用与联系人应用的交互。 +它采用了以下语法: +

+
+<ContactsAccountType
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        inviteContactActivity="activity_name"
+        inviteContactActionLabel="invite_command_text"
+        viewContactNotifyService="view_notify_service"
+        viewGroupActivity="group_view_activity"
+        viewGroupActionLabel="group_action_text"
+        viewStreamItemActivity="viewstream_activity_name"
+        viewStreamItemPhotoActivity="viewphotostream_activity_name">
+
+

+ 包含它的文件: +

+

+ res/xml/contacts.xml +

+

+ 可能包含的内容: +

+

+ <ContactsDataKind> +

+

+ 描述: +

+

+ 声明 Android 组件和 UI 标签,让用户能够邀请他们的一位联系人加入社交网络,在他们的某个社交网络流更新时通知用户,以及执行其他操作。 + + +

+

+ 请注意,对 <ContactsAccountType> 的属性而言,属性前缀 android: 并非必需的。 + +

+

+ 属性: +

+
+
{@code inviteContactActivity}
+
+ 您的应用中某个 Activity 的完全限定类名,您想要在用户于设备的联系人应用中选择 Add connection 时激活该 Activity。 + + +
+
{@code inviteContactActionLabel}
+
+ Add connection 菜单中为 +{@code inviteContactActivity} 中指定的 Activity 显示的文本字符串。 + 例如,您可以使用字符串“Follow in my network”。您可以为此标签使用字符串资源标识符。 + +
+
{@code viewContactNotifyService}
+
+ 您的应用中某项服务的完全限定类名,当用户查看联系人时,应由该服务接收通知。 +此通知由设备的联系人应用发送;您的应用可以根据通知将数据密集型操作推迟到必要时再执行。 + +例如,您的应用对此通知的响应可以是:读入并显示联系人的高分辨率照片和最近的社交流项目。 + +社交流交互部分对此功能做了更详尽的描述。 +您可以在 +SampleSyncAdapter 示例应用的 NotifierService.java 文件中查看通知服务的示例。 + + +
+
{@code viewGroupActivity}
+
+ 您的应用中某个可显示组信息的 Activity 的完全限定类名。 +当用户点击设备联系人应用中的组标签时,将显示此 Activity 的 UI。 + +
+
{@code viewGroupActionLabel}
+
+ 联系人应用为某个 UI 控件显示的标签,用户可通过该控件查看您的应用中的组。 + +

+ 例如,如果您在设备上安装了 Google+ 应用,并将 +Google+ 与联系人应用同步,就会看到 Google+ 圈子以组的形式出现在您的联系人应用的 Groups 选项卡内。 +如果您点击某个 +Google+ 圈子,就会看到该圈子内的联系人以“组”的形式列出。在该显示页面的顶部,您会看到一个 Google+ 图标;如果您点击它,控制权将切换给 +Google+ 应用。联系人应用以 Google+ 图标作为 {@code viewGroupActionLabel} 的值,通过 +{@code viewGroupActivity} 来实现此目的。 + + +

+

+ 允许使用字符串资源标识符作为该属性的值。 +

+
+
{@code viewStreamItemActivity}
+
+ 您的应用中某个 Activity 的完全限定类名,设备的联系人应用会在用户点击原始联系人的流项目时启动该 Activity。 + +
+
{@code viewStreamItemPhotoActivity}
+
+ 您的应用中某个 Activity 的完全限定类名,设备的联系人应用会在用户点击原始联系人流项目中的照片时启动该 Activity。 + + +
+
+

<ContactsDataKind> 元素

+

+ <ContactsDataKind> 元素控制您的应用的自定义数据行在联系人应用 UI 中的显示。它采用了以下语法: + +

+
+<ContactsDataKind
+        android:mimeType="MIMEtype"
+        android:icon="icon_resources"
+        android:summaryColumn="column_name"
+        android:detailColumn="column_name">
+
+

+ 包含它的文件: +

+<ContactsAccountType> +

+ 描述: +

+

+ 此元素用于让联系人应用将自定义数据行的内容显示为原始联系人详细信息的一部分。 +<ContactsAccountType> 的每个 <ContactsDataKind> 子元素都代表您的同步适配器向 {@link android.provider.ContactsContract.Data} 表添加的某个自定义数据行类型。 + +请为您使用的每个自定义 MIME 类型添加一个 <ContactsDataKind> 元素。 +如果您不想显示任何自定义数据行的数据,则无需添加该元素。 + +

+

+ 属性: +

+
+
{@code android:mimeType}
+
+ 您为 +{@link android.provider.ContactsContract.Data} 表中某个自定义数据行类型定义的自定义 MIME 类型。例如,可将值 +vnd.android.cursor.item/vnd.example.locationstatus 作为记录联系人最后已知位置的数据行的自定义 MIME 类型。 + +
+
{@code android:icon}
+
+ 联系人应用在您的数据旁显示的 Android + Drawable资源。 +它用于向用户指示数据来自您的服务。 + +
+
{@code android:summaryColumn}
+
+ 从数据行检索的两个值中第一个值的列名。该值显示为该数据行的第一个输入行。 +第一行专用作数据摘要,不过它是可选项。 +另请参阅 +android:detailColumn。 +
+
{@code android:detailColumn}
+
+ 从数据行检索的两个值中第二个值的列名。该值显示为该数据行的第二个输入行。 +另请参阅 +{@code android:summaryColumn}。 +
+
+

其他联系人提供程序功能

+

+ 除了上文描述的主要功能外,联系人提供程序还为处理联系人数据提供了下列有用的功能: + +

+
    +
  • 联系人组
  • +
  • 照片功能
  • +
+

联系人组

+

+ 联系人提供程序可以选择性地为相关联系人集合添加数据标签。 +如果与某个用户帐户关联的服务器想要维护组,则与该帐户的帐户类型对应的同步适配器应在联系人提供程序与服务器之间传送组数据。 + +当用户向服务器添加一个新联系人,然后将该联系人放入一个新组时,同步适配器必须将这个新组添加到 + {@link android.provider.ContactsContract.Groups} 表中。 +原始联系人所属的一个或多个组使用 {@link android.provider.ContactsContract.CommonDataKinds.GroupMembership} MIME 类型存储在 {@link android.provider.ContactsContract.Data} 表内。 + + +

+

+ 如果您设计的同步适配器会将服务器中的原始联系人数据添加到联系人提供程序,并且您不使用组,则需要指示提供程序让您的数据可见。 + +在用户向设备添加帐户时执行的代码中,更新联系人提供程序为该帐户添加的 {@link android.provider.ContactsContract.Settings} +行。 +在该行中,将 +{@link android.provider.ContactsContract.SettingsColumns#UNGROUPED_VISIBLE +Settings.UNGROUPED_VISIBLE} 列的值设置为 1。执行此操作后,即使您不使用组,联系人提供程序也会让您的联系人数据始终可见。 + +

+

联系人照片

+

+ {@link android.provider.ContactsContract.Data} 表通过 MIME 类型 +{@link android.provider.ContactsContract.CommonDataKinds.Photo#CONTENT_ITEM_TYPE +Photo.CONTENT_ITEM_TYPE} 以行的形式存储照片。该行的 +{@link android.provider.ContactsContract.RawContactsColumns#CONTACT_ID} 列链接到其所属原始联系人的 +{@code android.provider.BaseColumns#_ID} 列。 + {@link android.provider.ContactsContract.Contacts.Photo} 类定义了一个 +{@link android.provider.ContactsContract.Contacts} 子表,其中包含联系人主要照片(联系人的主要原始联系人的主要照片)的照片信息。 +同样, +{@link android.provider.ContactsContract.RawContacts.DisplayPhoto} 类定义了一个 {@link android.provider.ContactsContract.RawContacts} 子表,其中包含原始联系人主要照片的照片信息。 + + +

+

+ {@link android.provider.ContactsContract.Contacts.Photo} 和 +{@link android.provider.ContactsContract.RawContacts.DisplayPhoto} 参考文档包含检索照片信息的示例。 +并没有可用来检索原始联系人主要缩略图的实用类,但您可以向 +{@link android.provider.ContactsContract.Data} 表发送查询,从而通过选定原始联系人的 +{@code android.provider.BaseColumns#_ID}、 +{@link android.provider.ContactsContract.CommonDataKinds.Photo#CONTENT_ITEM_TYPE +Photo.CONTENT_ITEM_TYPE} 以及 {@link android.provider.ContactsContract.Data#IS_PRIMARY} +列,找到原始联系人的主要照片行。 + +

+

+ 联系人的社交流数据也可能包含照片。这些照片存储在 +{@code android.provider.ContactsContract.StreamItemPhotos} 表中,社交流照片部分对该表做了更详尽的描述。 + +

diff --git a/docs/html-intl/intl/zh-cn/guide/topics/providers/content-provider-basics.jd b/docs/html-intl/intl/zh-cn/guide/topics/providers/content-provider-basics.jd new file mode 100644 index 0000000000000000000000000000000000000000..4c91d3a19c96cda97a775d370d873df1a641461d --- /dev/null +++ b/docs/html-intl/intl/zh-cn/guide/topics/providers/content-provider-basics.jd @@ -0,0 +1,1196 @@ +page.title=内容提供程序基础知识 +@jd:body +
+
+ +

本文内容

+
    +
  1. + 概览 +
      +
    1. + 访问提供程序 +
    2. +
    3. + 内容 URI +
    4. +
    +
  2. +
  3. + 从提供程序检索数据 +
      +
    1. + 请求读取访问权限 +
    2. +
    3. + 构建查询 +
    4. +
    5. + 显示查询结果 +
    6. +
    7. + 从查询结果中获取数据 +
    8. +
    +
  4. +
  5. + 内容提供程序权限 +
  6. +
  7. + 插入、更新和删除数据 +
      +
    1. + 插入数据 +
    2. +
    3. + 更新数据 +
    4. +
    5. + 删除数据 +
    6. +
    +
  8. +
  9. + 提供程序数据类型 +
  10. +
  11. + 提供程序访问的替代形式 +
      +
    1. + 批量访问 +
    2. +
    3. + 通过 Intent 访问数据 +
    4. +
    +
  12. +
  13. + 协定类 +
  14. +
  15. + MIME 类型引用 +
  16. +
+ + +

关键类

+
    +
  1. + {@link android.content.ContentProvider} +
  2. +
  3. + {@link android.content.ContentResolver} +
  4. +
  5. + {@link android.database.Cursor} +
  6. +
  7. + {@link android.net.Uri} +
  8. +
+ + +

相关示例

+
    +
  1. + +游标(联系人) +
  2. +
  3. + +游标(电话) +
  4. +
+ + +

另请参阅

+
    +
  1. + +创建内容提供程序 +
  2. +
  3. + +日历提供程序 +
  4. +
+
+
+ + +

+ 内容提供程序管理对中央数据存储库的访问。提供程序是 Android 应用的一部分,通常提供自己的 UI 来使用数据。 + +但是,内容提供程序主要旨在供其他应用使用,这些应用使用提供程序客户端对象来访问提供程序。 +提供程序与提供程序客户端共同提供一致的标准数据界面,该界面还可处理跨进程通信并保护数据访问的安全性。 + + +

+

+ 本主题介绍了以下基础知识: +

+
    +
  • 内容提供程序的工作方式。
  • +
  • 用于从内容提供程序检索数据的 API。
  • +
  • 用于在内容提供程序中插入、更新或删除数据的 API。
  • +
  • 其他有助于使用提供程序的 API 功能。
  • +
+ + +

概览

+

+ 内容提供程序以一个或多个表(与在关系数据库中找到的表类似)的形式将数据呈现给外部应用。 +行表示提供程序收集的某种数据类型的实例,行中的每个列表示为实例收集的每条数据。 + + +

+

+ 例如,Android 平台的内置提供程序之一是用户字典,它会存储用户想要保存的非标准字词的拼写。 +表 1 描述了数据在此提供程序表中的显示情况: + +

+

+ 表 1:用户字典示例表格。 +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
字词应用 id频率区域设置_ID
mapreduceuser1100en_US1
precompileruser14200fr_FR2
appletuser2225fr_CA3
constuser1255pt_BR4
intuser5100en_UK5
+

+ 在表 1 中,每行表示可能无法在标准词典中找到的字词实例。 +每列表示该字词的某些数据,如该字词首次出现时的区域设置。 +列标题是存储在提供程序中的列名称。 +要引用行的区域设置,需要引用其 locale 列。对于此提供程序,_ID 列充当由提供程序自动维护的“主键”列。 + + +

+

+ :提供程序无需具有主键,也无需将 _ID 用作其主键的列名称(如果存在主键)。 +但是,如果您要将来自提供程序的数据与 {@link android.widget.ListView} 绑定,则其中一个列名称必须是 _ID。 + +显示查询结果部分详细说明了此要求。 + +

+

访问提供程序

+

+ 应用从具有 {@link android.content.ContentResolver} 客户端对象的内容提供程序访问数据。 +此对象具有调用提供程序对象({@link android.content.ContentProvider} 的某个具体子类的实例)中同名方法的方法。 + + +{@link android.content.ContentResolver} 方法可提供持续存储的基本“CRUD”(创建、检索、更新和删除)功能。 + +

+

+ 客户端应用进程中的 {@link android.content.ContentResolver} 对象和拥有提供程序的应用中的 {@link android.content.ContentProvider} 对象可自动处理跨进程通信。 +{@link android.content.ContentProvider} 还可充当其数据存储库和表格形式的数据外部显示之间的抽象层。 + + + +

+

+ :要访问提供程序,您的应用通常需要在其清单文件中请求特定权限。 +内容提供程序权限部分详细介绍了此内容。 + +

+

+ 例如,要从用户字典提供程序中获取字词及其区域设置的列表,则需调用 {@link android.content.ContentResolver#query ContentResolver.query()}。 + + {@link android.content.ContentResolver#query query()} 方法会调用用户字典提供程序所定义的 +{@link android.content.ContentProvider#query ContentProvider.query()} 方法。 +以下代码行显示了 +{@link android.content.ContentResolver#query ContentResolver.query()} 调用: +

+

+// Queries the user dictionary and returns results
+mCursor = getContentResolver().query(
+    UserDictionary.Words.CONTENT_URI,   // The content URI of the words table
+    mProjection,                        // The columns to return for each row
+    mSelectionClause                    // Selection criteria
+    mSelectionArgs,                     // Selection criteria
+    mSortOrder);                        // The sort order for the returned rows
+
+

+ 表 2 显示了 + {@link android.content.ContentResolver#query + query(Uri,projection,selection,selectionArgs,sortOrder)} 的参数如何匹配 SQL SELECT 语句: +

+

+ 表 2:Query() 与 SQL 查询对比。 +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
query() 参数SELECT 关键字/参数备注
UriFROM table_nameUri 映射至名为 table_name 的提供程序中的表。
projectioncol,col,col,... + projection 是应该为检索到的每个行包含的列的数组。 + +
selectionWHERE col = valueselection 会指定选择行的条件。
selectionArgs + (没有完全等效项。选择参数会替换选择子句中 ? 的占位符。) + +
sortOrderORDER BY col,col,... + sortOrder 指定行在返回的 + {@link android.database.Cursor} 中的显示顺序。 +
+

内容 URI

+

+ 内容 URI 是用于在提供程序中标识数据的 URI。内容 URI 包括整个提供程序的符号名称(其权限)和一个指向表的名称(路径)。 + +当您调用客户端方法来访问提供程序中的表时,该表的内容 URI 将是其参数之一。 + + +

+

+ 在前面的代码行中,常量 + {@link android.provider.UserDictionary.Words#CONTENT_URI} 包含用户字典的“字词”表的内容 URI。 +{@link android.content.ContentResolver} + 对象会分析出 URI 的授权,并通过将该授权与已知提供程序的系统表进行比较,来“解析”提供程序。 +然后, +{@link android.content.ContentResolver} 可以将查询参数分派给正确的提供程序。 + +

+

+ {@link android.content.ContentProvider} 使用内容 URI 的路径部分来选择要访问的表。 +提供程序通常会为其公开的每个表显示一条路径。 +

+

+ 在前面的代码行中,“字词”表的完整 URI 是: +

+
+content://user_dictionary/words
+
+

+ 其中,user_dictionary 字符串是提供程序的授权, +words 字符串是表的路径。字符串 + content://架构)始终显示,并将此标识为内容 URI。 + +

+

+ 许多提供程序都允许您通过将 ID 值追加到 URI 末尾来访问表中的单个行。例如,要从用户字典中检索 _ID 为 + 4 的行,则可使用此内容 URI: + +

+
+Uri singleUri = ContentUris.withAppendedId(UserDictionary.Words.CONTENT_URI,4);
+
+

+ 在检索到一组行后想要更新或删除其中某一行时通常会用到 ID 值。 + +

+

+ :{@link android.net.Uri} 和 {@link android.net.Uri.Builder} 类 +包含根据字符串构建格式规范的 URI 对象的便利方法。 +{@link android.content.ContentUris} 包含一些可以将 ID 值轻松追加到 + URI 后的方法。前面的代码段就是使用 {@link android.content.ContentUris#withAppendedId + withAppendedId()} 将 ID 追加到用户字典内容 URI 后。 +

+ + + +

从提供程序检索数据

+

+ 本节将以用户字典提供程序为例,介绍如何从提供程序中检索数据。 + +

+

+ 为了明确进行说明,本节中的代码段将在“UI 线程”上调用 + {@link android.content.ContentResolver#query ContentResolver.query()}。但在实际代码中,您应该在单独线程上异步执行查询。 +执行此操作的方式之一是使用 {@link android.content.CursorLoader} 类,加载器指南中对此有更为详细的介绍。 + + +此外,前述代码行只是代码段;它们不会显示整个应用。 + +

+

+ 要从提供程序中检索数据,请按照以下基本步骤执行操作: +

+
    +
  1. + 请求对提供程序的读取访问权限。 +
  2. +
  3. + 定义将查询发送至提供程序的代码。 +
  4. +
+

请求读取访问权限

+

+ 要从提供程序检索数据,您的应需要具备对提供程序的“读取访问”权限。 +您无法在运行时请求此权限;相反,您需要使用<uses-permission>元素和提供程序定义的准确权限名称,在清单文件中指明您需要此权限。 + + + +在您的清单文件中指定此元素后,您将有效地为应用“请求”此权限。 +用户安装您的应用时,会隐式授予允许此请求。 + +

+

+ 要找出您正在使用的提供程序的读取访问权限的准确名称,以及提供程序使用的其他访问权限的名称,请查看提供程序的文档。 + + +

+

+ 内容提供程序权限部分详细介绍了权限在访问提供程序过程中的作用。 + +

+

+ 用户字典提供程序在其清单文件中定义了权限 + android.permission.READ_USER_DICTIONARY,因此希望从提供程序中进行读取的应用必需请求此权限。 + +

+ +

构建查询

+

+ 从提供程序中检索数据的下一步是构建查询。第一个代码段定义某些用于访问用户字典提供程序的变量: + +

+
+
+// A "projection" defines the columns that will be returned for each row
+String[] mProjection =
+{
+    UserDictionary.Words._ID,    // Contract class constant for the _ID column name
+    UserDictionary.Words.WORD,   // Contract class constant for the word column name
+    UserDictionary.Words.LOCALE  // Contract class constant for the locale column name
+};
+
+// Defines a string to contain the selection clause
+String mSelectionClause = null;
+
+// Initializes an array to contain selection arguments
+String[] mSelectionArgs = {""};
+
+
+

+ 下一个代码段以用户字典提供程序为例,显示了如何使用 + {@link android.content.ContentResolver#query ContentResolver.query()}。 +提供程序客户端查询与 SQL 查询类似,并且包含一组要返回的列、一组选择条件和排序顺序。 + +

+

+ 查询应该返回的列集被称为投影(变量 mProjection)。 + +

+

+ 用于指定要检索的行的表达式分割为选择子句和选择参数。 +选择子句是逻辑和布尔表达式、列名称和值(变量 mSelectionClause)的组合。 +如果您指定了可替换参数 ? 而非值,则查询方法会从选择参数数组(变量 mSelectionArgs)中检索值。 + + +

+

+ 在下一个代码段中,如果用户未输入字词,则选择子句将设置为 null,而且查询会返回提供程序中的所有字词。 +如果用户输入了字词,选择子句将设置为 UserDictionary.Words.WORD + " = ?" 且选择参数数组的第一个元素将设置为用户输入的字词。 + + +

+
+/*
+ * This defines a one-element String array to contain the selection argument.
+ */
+String[] mSelectionArgs = {""};
+
+// Gets a word from the UI
+mSearchString = mSearchWord.getText().toString();
+
+// Remember to insert code here to check for invalid or malicious input.
+
+// If the word is the empty string, gets everything
+if (TextUtils.isEmpty(mSearchString)) {
+    // Setting the selection clause to null will return all words
+    mSelectionClause = null;
+    mSelectionArgs[0] = "";
+
+} else {
+    // Constructs a selection clause that matches the word that the user entered.
+    mSelectionClause = UserDictionary.Words.WORD + " = ?";
+
+    // Moves the user's input string to the selection arguments.
+    mSelectionArgs[0] = mSearchString;
+
+}
+
+// Does a query against the table and returns a Cursor object
+mCursor = getContentResolver().query(
+    UserDictionary.Words.CONTENT_URI,  // The content URI of the words table
+    mProjection,                       // The columns to return for each row
+    mSelectionClause                   // Either null, or the word the user entered
+    mSelectionArgs,                    // Either empty, or the string the user entered
+    mSortOrder);                       // The sort order for the returned rows
+
+// Some providers return null if an error occurs, others throw an exception
+if (null == mCursor) {
+    /*
+     * Insert code here to handle the error. Be sure not to use the cursor! You may want to
+     * call android.util.Log.e() to log this error.
+     *
+     */
+// If the Cursor is empty, the provider found no matches
+} else if (mCursor.getCount() < 1) {
+
+    /*
+     * Insert code here to notify the user that the search was unsuccessful. This isn't necessarily
+     * an error. You may want to offer the user the option to insert a new row, or re-type the
+     * search term.
+     */
+
+} else {
+    // Insert code here to do something with the results
+
+}
+
+

+ 此查询与 SQL 语句相似: +

+
+SELECT _ID, word, locale FROM words WHERE word = <userinput> ORDER BY word ASC;
+
+

+ 在此 SQL 语句中,会使用实际的列名称而非协定类常量。 +

+

防止恶意输入

+

+ 如果内容提供程序管理的数据位于 SQL 数据库中,将不受信任的外部数据包括在原始 SQL 语句中可能会导致 SQL 注入。 + +

+

+ 考虑此选择子句: +

+
+// Constructs a selection clause by concatenating the user's input to the column name
+String mSelectionClause =  "var = " + mUserInput;
+
+

+ 如果您执行此操作,则会允许用户将恶意 SQL 串连到 SQL 语句上。 + 例如,用户可以为 mUserInput 输入“nothing; DROP TABLE *;”,这会生成选择子句 var = nothing; DROP TABLE *;。 +由于选择子句是作为 SQL 语句处理,因此这可能会导致提供程序擦除基础 SQLite 数据库中的所有表(除非提供程序设置为可捕获 SQL 注入尝试)。 + + + +

+

+ 要避免此问题,可使用一个用于将 ? 作为可替换参数的选择子句以及一个单独的选择参数数组。 +执行此操作时,用户输入直接受查询约束,而不解释为 SQL 语句的一部分。 + + 由于用户输入未作为 SQL 处理,因此无法注入恶意 SQL。请使用此选择子句,而不要使用串连来包括用户输入: + +

+
+// Constructs a selection clause with a replaceable parameter
+String mSelectionClause =  "var = ?";
+
+

+ 按如下所示设置选择参数数组: +

+
+// Defines an array to contain the selection arguments
+String[] selectionArgs = {""};
+
+

+ 按如下所示将值置于选择参数数组中: +

+
+// Sets the selection argument to the user's input
+selectionArgs[0] = mUserInput;
+
+

+ 一个用于将 ? 用作可替换参数的选择子句和一个选择参数数组是指定选择的首选方式,即使提供程序并未基于 SQL 数据库。 + + +

+ +

显示查询结果

+

+ {@link android.content.ContentResolver#query ContentResolver.query()} 客户端方法始终会返回符合以下条件的 {@link android.database.Cursor}:包含查询的投影为匹配查询选择条件的行指定的列。 + + +{@link android.database.Cursor} 对象为其包含的行和列提供随机读取访问权限。 +通过使用 {@link android.database.Cursor} 方法,您可以循环访问结果中的行、确定每个列的数据类型、从列中获取数据,并检查结果的其他属性。 + +某些 {@link android.database.Cursor} 实现会在提供程序的数据发生更改时自动更新对象和/或在 {@link android.database.Cursor} 更改时触发观察程序对象中的方法。 + + +

+

+ :提供程序可能会根据发出查询的对象的性质来限制对列的访问。 +例如,联系人提供程序会限定只有同步适配器才能访问某些列,因此不会将它们返回至 Activity 或服务。 + +

+

+ 如果没有与选择条件匹配的行,则提供程序会返回 {@link android.database.Cursor#getCount Cursor.getCount()} 为 0(空游标)的 {@link android.database.Cursor} 对象。 + + +

+

+ 如果出现内部错误,查询结果将取决于具体的提供程序。它可能会选择返回 null,或抛出 {@link java.lang.Exception}。 + +

+

+ 由于 {@link android.database.Cursor} 是行“列表”,因此显示 {@link android.database.Cursor} 内容的良好方式是通过 {@link android.widget.SimpleCursorAdapter} 将其与 {@link android.widget.ListView} 关联。 + + +

+

+ 以下代码段将延续上一代码段的代码。它会创建一个包含由查询检索到的 {@link android.database.Cursor} 的 {@link android.widget.SimpleCursorAdapter} 对象,并将此对象设置为 {@link android.widget.ListView} 的适配器: + + + +

+
+// Defines a list of columns to retrieve from the Cursor and load into an output row
+String[] mWordListColumns =
+{
+    UserDictionary.Words.WORD,   // Contract class constant containing the word column name
+    UserDictionary.Words.LOCALE  // Contract class constant containing the locale column name
+};
+
+// Defines a list of View IDs that will receive the Cursor columns for each row
+int[] mWordListItems = { R.id.dictWord, R.id.locale};
+
+// Creates a new SimpleCursorAdapter
+mCursorAdapter = new SimpleCursorAdapter(
+    getApplicationContext(),               // The application's Context object
+    R.layout.wordlistrow,                  // A layout in XML for one row in the ListView
+    mCursor,                               // The result from the query
+    mWordListColumns,                      // A string array of column names in the cursor
+    mWordListItems,                        // An integer array of view IDs in the row layout
+    0);                                    // Flags (usually none are needed)
+
+// Sets the adapter for the ListView
+mWordList.setAdapter(mCursorAdapter);
+
+

+ :要通过 {@link android.database.Cursor} 支持 {@link android.widget.ListView},游标必需包含名为 _ID 的列。 + + 正因如此,前文显示的查询会为“字词”表检索 _ID 列,即使 {@link android.widget.ListView} 未显示该列。 + + 此限制也解释了为什么大多数提供程序的每个表都具有 _ID 列。 + +

+ + +

从查询结果中获取数据

+

+ 您可以将查询结果用于其他任务,而不是仅显示它们。例如,您可以从用户字典中检索拼写,然后在其他提供程序中查找它们。 + +要执行此操作,您需要在 {@link android.database.Cursor} 中循环访问行: +

+
+
+// Determine the column index of the column named "word"
+int index = mCursor.getColumnIndex(UserDictionary.Words.WORD);
+
+/*
+ * Only executes if the cursor is valid. The User Dictionary Provider returns null if
+ * an internal error occurs. Other providers may throw an Exception instead of returning null.
+ */
+
+if (mCursor != null) {
+    /*
+     * Moves to the next row in the cursor. Before the first movement in the cursor, the
+     * "row pointer" is -1, and if you try to retrieve data at that position you will get an
+     * exception.
+     */
+    while (mCursor.moveToNext()) {
+
+        // Gets the value from the column.
+        newWord = mCursor.getString(index);
+
+        // Insert code here to process the retrieved word.
+
+        ...
+
+        // end of while loop
+    }
+} else {
+
+    // Insert code here to report an error if the cursor is null or the provider threw an exception.
+}
+
+

+ {@link android.database.Cursor} 实现包含多个用于从对象中检索不同类型的数据的“获取”方法。 +例如,上一个代码段使用 {@link android.database.Cursor#getString getString()}。 +它们还具有 {@link android.database.Cursor#getType getType()} 方法,该方法会返回指示列的数据类型的值。 + + +

+ + + +

内容提供程序权限

+

+ 提供程序的应用可以指定其他应用访问提供程序的数据所必需的权限。 +这些权限可确保用户了解应用将尝试访问的数据。 +根据提供程序的要求,其他应用会请求它们访问提供程序所需的权限。 +最终用户会在安装应用时看到所请求的权限。 + +

+

+ 如果提供程序的应用未指定任何权限,则其他应用将无权访问提供程序的数据。 +但是,无论指定权限为何,提供程序的应用中的组件始终具有完整的读取和写入访问权限。 + +

+

+ 如前所述,用户字典提供程序需要 + android.permission.READ_USER_DICTIONARY 权限才能从中检索数据。 + 提供程序具有用于插入、更新或删除数据的单独 android.permission.WRITE_USER_DICTIONARY 权限。 + +

+

+ 要获取访问提供程序所需的权限,应用将通过其清单文件中的 +<uses-permission> + 元素来请求这些权限。Android 软件包管理器安装应用时,用户必须批准该应用请求的所有权限。 +如果用户批准所有权限,软件包管理器将继续安装;如果用户未批准这些权限,软件包管理器将中止安装。 + + +

+

+ 以下 +<uses-permission> 元素会请求对用户字典提供程序的读取访问权限: + +

+
+    <uses-permission android:name="android.permission.READ_USER_DICTIONARY">
+
+

+ +安全与权限指南中详细介绍了权限对提供程序访问的影响。 +

+ + + +

插入、更新和删除数据

+

+ 与从提供程序检索数据的方式相同,也可以通过提供程序客户端和提供程序 {@link android.content.ContentProvider} 之间的交互来修改数据。 + + 您通过传递到 {@link android.content.ContentProvider} 的对应方法的参数来调用 {@link android.content.ContentResolver} 方法。 +提供程序和提供程序客户端会自动处理安全性和跨进程通信。 + +

+

插入数据

+

+ 要将数据插入提供程序,可调用 + {@link android.content.ContentResolver#insert ContentResolver.insert()} + 方法。此方法会在提供程序中插入新行并为该行返回内容 URI。 + 此代码段显示如何将新字词插入用户字典提供程序: +

+
+// Defines a new Uri object that receives the result of the insertion
+Uri mNewUri;
+
+...
+
+// Defines an object to contain the new values to insert
+ContentValues mNewValues = new ContentValues();
+
+/*
+ * Sets the values of each column and inserts the word. The arguments to the "put"
+ * method are "column name" and "value"
+ */
+mNewValues.put(UserDictionary.Words.APP_ID, "example.user");
+mNewValues.put(UserDictionary.Words.LOCALE, "en_US");
+mNewValues.put(UserDictionary.Words.WORD, "insert");
+mNewValues.put(UserDictionary.Words.FREQUENCY, "100");
+
+mNewUri = getContentResolver().insert(
+    UserDictionary.Word.CONTENT_URI,   // the user dictionary content URI
+    mNewValues                          // the values to insert
+);
+
+

+ 新行的数据会进入单个 {@link android.content.ContentValues} 对象中,该对象在形式上与单行游标类似。 +此对象中的列不需要具有相同的数据类型,如果您不想指定值,则可以使用 {@link android.content.ContentValues#putNull ContentValues.putNull()} 将列设置为 null。 + + +

+

+ 代码段不会添加 _ID 列,因为系统会自动维护此列。 +提供程序会向添加的每个行分配唯一的 _ID 值。 +通常,提供程序会将此值用作表的主键。 +

+

+ newUri 中返回的内容 URI 会按照以下格式标识新添加的行: + +

+
+content://user_dictionary/words/<id_value>
+
+

+ <id_value> 是新行的 _ID 内容。 + 大多数提供程序都能自动检测这种格式的内容 URI,然后在该特定行上执行请求的操作。 + +

+

+ 要从返回的 {@link android.net.Uri} 中获取 _ID 的值,请调用 + {@link android.content.ContentUris#parseId ContentUris.parseId()}。 +

+

更新数据

+

+ 要更新行,请按照执行插入的方式使用具有更新值的 {@link android.content.ContentValues} 对象,并按照执行查询的方式使用选择条件。 + + 您使用的客户端方法是 + {@link android.content.ContentResolver#update ContentResolver.update()}。您只需将值添加至您要更新的列的 {@link android.content.ContentValues} 对象。 +如果您要清除列的内容,请将值设置为 null。 + +

+

+ 以下代码段会将区域设置具有语言“en”的所有行的区域设置更改为 null。 +返回值是已更新的行数: +

+
+// Defines an object to contain the updated values
+ContentValues mUpdateValues = new ContentValues();
+
+// Defines selection criteria for the rows you want to update
+String mSelectionClause = UserDictionary.Words.LOCALE +  "LIKE ?";
+String[] mSelectionArgs = {"en_%"};
+
+// Defines a variable to contain the number of updated rows
+int mRowsUpdated = 0;
+
+...
+
+/*
+ * Sets the updated value and updates the selected words.
+ */
+mUpdateValues.putNull(UserDictionary.Words.LOCALE);
+
+mRowsUpdated = getContentResolver().update(
+    UserDictionary.Words.CONTENT_URI,   // the user dictionary content URI
+    mUpdateValues                       // the columns to update
+    mSelectionClause                    // the column to select on
+    mSelectionArgs                      // the value to compare to
+);
+
+

+ 您还应该在调用 + {@link android.content.ContentResolver#update ContentResolver.update()} 时检查用户输入。如需了解有关此内容的更多详情,请阅读防止恶意输入部分。 + +

+

删除数据

+

+ 删除行与检索行数据类似:为要删除的行指定选择条件,客户端方法会返回已删除的行数。 + + 以下代码段会删除应用 ID 与“用户”匹配的行。该方法会返回已删除的行数。 + +

+
+
+// Defines selection criteria for the rows you want to delete
+String mSelectionClause = UserDictionary.Words.APP_ID + " LIKE ?";
+String[] mSelectionArgs = {"user"};
+
+// Defines a variable to contain the number of rows deleted
+int mRowsDeleted = 0;
+
+...
+
+// Deletes the words that match the selection criteria
+mRowsDeleted = getContentResolver().delete(
+    UserDictionary.Words.CONTENT_URI,   // the user dictionary content URI
+    mSelectionClause                    // the column to select on
+    mSelectionArgs                      // the value to compare to
+);
+
+

+ 您还应该在调用 + {@link android.content.ContentResolver#delete ContentResolver.delete()} 时检查用户输入。如需了解有关此内容的更多详情,请阅读防止恶意输入部分。 + +

+ +

提供程序数据类型

+

+ 内容提供程序可以提供多种不同的数据类型。用户字典提供程序仅提供文本,但提供程序也能提供以下格式: + +

+
    +
  • + 整型 +
  • +
  • + 长整型(长) +
  • +
  • + 浮点型 +
  • +
  • + 长浮点型(双倍) +
  • +
+

+ 提供程序经常使用的另一种数据类型是作为 64KB 字节的数组实施的二进制大型对象 (BLOB)。 +您可以通过查看 + {@link android.database.Cursor} 类“获取”方法看到可用数据类型。 +

+

+ 提供程序文档中通常都列出了其每个列的数据类型。 + 用户字典提供程序的数据类型列在其协定类 {@link android.provider.UserDictionary.Words} 参考文档中(协定类部分对协定类进行了介绍)。 + + + 您也可以通过调用 {@link android.database.Cursor#getType + Cursor.getType()} 来确定数据类型。 +

+

+ 提供程序还会维护其定义的每个内容 URI 的 MIME(多用途互联网邮件扩展)数据类型信息。您可以使用 MIME 类型信息查明应用是否可以处理提供程序提供的数据,或根据 MIME 类型选择处理类型。 + +在使用包含复杂数据结构或文件的提供程序时,通常需要 MIME 类型。 + +例如,联系人提供程序中的 {@link android.provider.ContactsContract.Data} + 表会使用 MIME 类型来标记每行中存储的联系人数据类型。 +要获取与内容 URI 对应的 MIME 类型,请调用 + {@link android.content.ContentResolver#getType ContentResolver.getType()}。 +

+

+ MIME 类型引用部分介绍了标准和自定义 MIME 类型的语法。 + +

+ + + +

提供程序访问的替代形式

+

+ 提供程序访问的三种替代形式在应用开发过程中十分重要: +

+
    +
  • + 批量访问:您可以通过 + {@link android.content.ContentProviderOperation} 类中的方法创建一批访问调用,然后通过 + {@link android.content.ContentResolver#applyBatch ContentResolver.applyBatch()} 应用它们。 +
  • +
  • + 异步查询:您应该在单独线程中执行查询。执行此操作的方式之一是使用 {@link android.content.CursorLoader} 对象。 +加载器指南中的示例展示了如何执行此操作。 + + +
  • +
  • + 通过 Intent 访问数据:尽管您无法直接向提供程序发送 Intent,但可以向提供程序的应用发送 Intent,后者通常具有修改提供程序数据的最佳配置。 + + +
  • +
+

+ 下文将介绍批量访问和修改。 +

+

批量访问

+

+ 批量访问提供程序适用于插入大量行,或通过同一方法调用在多个表中插入行,或者通常用于跨进程界限将一组操作作为事务处理(原子操作)执行。 + + +

+

+ 要在“批量模式”下访问提供程序, +您可以创建 {@link android.content.ContentProviderOperation} 对象数组,然后使用 {@link android.content.ContentResolver#applyBatch ContentResolver.applyBatch()} +将其分派给内容提供程序。 +您需将内容提供程序的授权传递给此方法,而不是特定内容 URI。这样可使数组中的每个 {@link android.content.ContentProviderOperation} 对象都能适用于其他表。 + + +调用 {@link android.content.ContentResolver#applyBatch + ContentResolver.applyBatch()} 会返回结果数组。 +

+

+ {@link android.provider.ContactsContract.RawContacts} 协定类 +的说明包括展示批量注入的代码段。 +联系人管理器示例应用包含在其 ContactAdder.java + 源文件中进行批量访问的示例。 + +

+ +

通过 Intent 访问数据

+

+ Intent 可以提供对内容提供程序的间接访问。即使您的应用不具备访问权限,您也可以通过以下方式允许用户访问提供程序中的数据:从具有权限的应用中获取回结果 Intent,或者通过激活具有权限的应用,然后让用户在其中工作。 + + + +

+

通过临时权限获取访问权限

+

+ 即使您没有适当的访问权限,也可以通过以下方式访问内容提供程序中的数据:将 Intent 发送至具有权限的应用,然后接收回包含“URI”权限的结果 Intent。 + + + 这些是特定内容 URI 的权限,将持续至接收该权限的 Activity 结束。 +具有永久权限的应用将通过在结果 Intent 中设置标志来授予临时权限: + +

+
    +
  • + 读取权限: +{@link android.content.Intent#FLAG_GRANT_READ_URI_PERMISSION} +
  • +
  • + 写入权限: +{@link android.content.Intent#FLAG_GRANT_WRITE_URI_PERMISSION} +
  • +
+

+ :这些标志不会为其授权包含在内容 URI 中的提供程序 +提供常规的读取或写入访问权限。访问权限仅适用于 URI 本身。 +

+

+ 提供程序使用 +<provider> +元素的 +android:grantUriPermission +属性以及 +<provider> +元素的 +<grant-uri-permission> +子元素在其清单文件中定义内容 URI 的 URI 权限。安全与权限指南中“URI 权限”部分更加详细地说明了 URI 权限机制。 + + +

+

+ 例如,即使您没有 + {@link android.Manifest.permission#READ_CONTACTS} 权限,也可以在联系人提供程序中检索联系人的数据。您可能希望在向联系人发送电子生日祝福的应用中执行此操作。 +您更愿意让用户控制应用所使用的联系人,而不是请求 {@link android.Manifest.permission#READ_CONTACTS},让您能够访问用户的所有联系人及其信息。 + + +要执行此操作,您需要使用以下进程: +

+
    +
  1. + 您的应用会使用方法 {@link android.app.Activity#startActivityForResult + startActivityForResult()} 发送包含操作 + {@link android.content.Intent#ACTION_PICK} 和“联系人”MIME 类型 + {@link android.provider.ContactsContract.RawContacts#CONTENT_ITEM_TYPE} 的 Intent 对象。 + +
  2. +
  3. + 由于此 Intent 与“联系人”应用的“选择” Activity 的 Intent 过滤器相匹配,因此 Activity 会显示在前台。 + +
  4. +
  5. + 在选择 Activity 中,用户选择要更新的联系人。 +发生此情况时,选择 Activity 会调用 + {@link android.app.Activity#setResult setResult(resultcode, intent)} + 以设置用于返回至应用的 Intent。 Intent 包含用户选择的联系人的内容 URI,以及“extras”标志 + {@link android.content.Intent#FLAG_GRANT_READ_URI_PERMISSION}。 +这些标志会为您的应用授予读取内容 URI 所指向的联系人的数据的 URI + 权限。然后,选择 Activity 会调用 {@link android.app.Activity#finish()} 以返回对应用的控制。 + + +
  6. +
  7. + 您的 Activity 会返回至前台,系统会调用您的 Activity 的 + {@link android.app.Activity#onActivityResult onActivityResult()} + 方法。此方法会收到“联系人”应用中选择 Activity 所创建的结果 Intent。 + +
  8. +
  9. + 通过来自结果 Intent 的内容 URI,您可以读取来自联系人提供程序的联系人数据,即使您未在清单文件中请求对该提供程序的永久读取访问权限。 + +您可以获取联系人的生日信息或其电子邮件地址,然后发送电子祝福。 + +
  10. +
+

使用其他应用

+

+ 允许用户修改您无权访问的数据的简单方法是激活具有权限的应用,让用户在其中执行工作。 + +

+

+ 例如,日历应用接受 + {@link android.content.Intent#ACTION_INSERT} Intent,这让您可以激活应用的插入 UI。您可以在此 Intent(应用将使用该 Intent 来预先填充 UI)中传递“额外”数据,由于定期事件具有复杂的语法,因此将事件插入日历提供程序的首选方式是激活具有 + {@link android.content.Intent#ACTION_INSERT} 的日历应用,然后让用户在其中插入事件。 + + + +

+ +

协定类

+

+ 协定类定义帮助应用使用内容 URI、列名称、 Intent 操作以及内容提供程序的其他功能的常量。 +协定类未自动包含在提供程序中;提供程序的开发者需要定义它们,然后使其可用于其他开发者。 + +Android + 平台中包含的许多提供程序都在软件包 {@link android.provider} 中具有对应的协定类。 +

+

+ 例如,用户字典提供程序具有包含内容 URI 和列名称常量的协定类 {@link android.provider.UserDictionary}。 + +“字词”表的内容 URI 在常量 + {@link android.provider.UserDictionary.Words#CONTENT_URI UserDictionary.Words.CONTENT_URI} 中定义。 + {@link android.provider.UserDictionary.Words} 类也包含列名称常量,本指南的示例代码段中就使用了该常量。 +例如,查询投影可以定义为: + +

+
+String[] mProjection =
+{
+    UserDictionary.Words._ID,
+    UserDictionary.Words.WORD,
+    UserDictionary.Words.LOCALE
+};
+
+

+ 联系人提供程序的 {@link android.provider.ContactsContract} 也是一个协定类。 + 此类的参考文档包括示例代码段。其子类之一 {@link android.provider.ContactsContract.Intents.Insert} 是包含 Intent 和 Intent 数据的协定类。 + + +

+ + + +

MIME 类型引用

+

+ 内容提供程序可以返回标准 MIME 媒体类型和/或自定义 MIME 类型字符串。 +

+

+ MIME 类型具有格式 +

+
+type/subtype
+
+

+ 例如,众所周知的 MIME 类型 text/html 具有 text 类型和 + html 子类型。如果提供程序为 URI 返回此类型,则意味着使用该 URI 的查询会返回包含 HTML 标记的文本。 + +

+

+ 自定义 MIME 类型字符串(也称为“特定于供应商”的 MIME 类型)具有更加复杂的类型子类型值。 +类型值始终为 +

+
+vnd.android.cursor.dir
+
+

+ (多行)或 +

+
+vnd.android.cursor.item
+
+

+ (单行)。 +

+

+ 子类型特定于提供程序。Android 内置提供程序通常具有简单的子类型。 +例如,当联系人应用为电话号码创建行时,它会在行中设置以下 MIME 类型: + +

+
+vnd.android.cursor.item/phone_v2
+
+

+ 请注意,子类型值只是 phone_v2。 +

+

+ 其他提供程序开发者可能会根据提供程序的授权和表名称创建自己的子类型模式。 +例如,假设提供程序包含列车时刻表。 + 提供程序的授权是 com.example.trains,并包含表 + Line1、Line2 和 Line3。在响应表 Line1 的内容 URI +

+

+

+content://com.example.trains/Line1
+
+

+ 时,提供程序会返回 MIME 类型 +

+
+vnd.android.cursor.dir/vnd.example.line1
+
+

+ 在响应表 Line2 中第 5 行的内容 URI +

+
+content://com.example.trains/Line2/5
+
+

+ 时,提供程序会返回 MIME 类型 +

+
+vnd.android.cursor.item/vnd.example.line2
+
+

+ 大多数内容提供程序都会为其使用的 MIME 类型定义协定类常量。例如,联系人提供程序协定类 {@link android.provider.ContactsContract.RawContacts} 会为单个原始联系人行的 MIME 类型定义常量 + {@link android.provider.ContactsContract.RawContacts#CONTENT_ITEM_TYPE}。 + + + +

+

+ +内容 URI 部分介绍了单个行的内容 URI。 +

diff --git a/docs/html-intl/intl/zh-cn/guide/topics/providers/content-provider-creating.jd b/docs/html-intl/intl/zh-cn/guide/topics/providers/content-provider-creating.jd new file mode 100644 index 0000000000000000000000000000000000000000..6da57435f43dca7bddc743b3f9c514137182be9c --- /dev/null +++ b/docs/html-intl/intl/zh-cn/guide/topics/providers/content-provider-creating.jd @@ -0,0 +1,1214 @@ +page.title=创建内容提供程序 +@jd:body +
+
+ + +

本文内容

+
    +
  1. + 设计数据存储 +
  2. +
  3. + 设计内容 URI +
  4. +
  5. + 实现 ContentProvider 类 +
      +
    1. + 必需方法 +
    2. +
    3. + 实现 query() 方法 +
    4. +
    5. + 实现 insert() 方法 +
    6. +
    7. + 实现 delete() 方法 +
    8. +
    9. + 实现 update() 方法 +
    10. +
    11. + 实现 onCreate() 方法 +
    12. +
    +
  6. +
  7. + 实现内容提供程序 MIME 类型 +
      +
    1. + 表的 MIME 类型 +
    2. +
    3. + 文件的 MIME 类型 +
    4. +
    +
  8. +
  9. + 实现协定类 +
  10. +
  11. + 实现内容提供程序权限 +
  12. +
  13. + <Provider> 元素 +
  14. +
  15. + Intent 和数据访问 +
  16. +
+

关键类

+
    +
  1. + {@link android.content.ContentProvider} +
  2. +
  3. + {@link android.database.Cursor} +
  4. +
  5. + {@link android.net.Uri} +
  6. +
+

相关示例

+
    +
  1. + + 记事本示例应用 + +
  2. +
+

另请参阅

+
    +
  1. + + 内容提供程序基础知识 +
  2. +
  3. + + 日历提供程序 +
  4. +
+
+
+ + +

+ 内容提供程序管理对中央数据存储库的访问。您将 + 提供程序作为 Android 应用中的一个或多个类(连同清单文件 + 中的元素)实现。其中一个类会实现子类 + {@link android.content.ContentProvider},即您的提供程序与 + 其他应用之间的界面。尽管内容提供程序旨在向其他应用提供 + 数据,但您的应用中必定有这样一些 Activity,它们允许用户 + 查询和修改由提供程序管理的数据。 +

+

+ 本主题的其余部分列出了开发内容提供程序的基本步骤和 + 需要使用的 API。 +

+ + + +

着手开发前的准备工作

+

+ 请在着手开发提供程序之前执行以下操作: +

+
    +
  1. + 决定您是否需要内容提供程序。如果您想提供下列一项或多项功能,则需要开发内容 + 提供程序: +
      +
    • 您想为其他应用提供复杂的数据或文件
    • +
    • 您想允许用户将复杂的数据从您的应用复制到其他应用中
    • +
    • 您想使用搜索框架提供自定义搜索建议
    • +
    +

    + 如果完全是在 + 您自己的应用中使用,则“根本不”需要提供程序即可使用 SQLite 数据库。 +

    +
  2. +
  3. + 如果您尚未完成此项操作,请阅读 + + 内容提供程序基础知识主题,了解有关提供程序的详情。 +
  4. +
+

+ 接下来,请按照以下步骤开发您的提供程序: +

+
    +
  1. + 为您的数据设计原始存储。内容提供程序以两种方式提供数据: +
    +
    + 文件数据 +
    +
    + 通常存储在文件中的数据,如 + 照片、音频或视频。将文件存储在您的应用的私有 + 空间内。您的提供程序可以应其他应用发出的文件请求 + 提供文件句柄。 +
    +
    + “结构化”数据 +
    +
    + 通常存储在数据库、数组或类似结构中的数据。 + 以兼容行列表的形式存储数据。行 + 表示实体,如人员或库存项目。列表示 + 实体的某项数据,如人员的姓名或商品的价格。此类数据通常 + 存储在 SQLite 数据库中,但您可以使用任何类型的 + 持久存储。如需了解有关 + Android 系统中提供的存储类型的更多信息,请参阅 + 设计数据存储部分。 +
    +
    +
  2. +
  3. + 定义 {@link android.content.ContentProvider} 类及其 + 所需方法的具体实现。此类是您的数据与 + Android 系统其余部分之间的界面。如需了解有关此类的详细信息,请参阅 + 实现 ContentProvider 类部分。 +
  4. +
  5. + 定义提供程序的权限字符串、其内容 URI 以及列名称。如果您想让 + 提供程序的应用处理 Intent,则还要定义 Intent 操作、Extra 数据 + 以及标志。此外,还要定义想要访问您的数据的应用必须具备的权限。 +您应该考虑在一个单独的协定类中将所有这些值定义为常量;以后您可以将此类公开给其他开发者。 +如需了解有关内容 URI 的详细信息,请参阅设计内容 URI 部分。 + + + 如需了解有关 Intent 的详细信息,请参阅 Intent 和数据访问部分。 + +
  6. +
  7. + 添加其他可选部分,如示例数据或可以在提供程序与云数据之间同步数据的 {@link android.content.AbstractThreadedSyncAdapter} 实现。 + + +
  8. +
+ + + +

设计数据存储

+

+ 内容提供程序是用于访问以结构化格式保存的数据的界面。在您创建该界面之前,必须决定如何存储数据。 +您可以按自己的喜好以任何形式存储数据,然后根据需要设计读写数据的界面。 + +

+

+ 以下是 Android 中提供的一些数据存储技术: +

+
    +
  • + Android 系统包括一个 SQLite 数据库 API,Android 自己的提供程序使用它来存储面向表的数据。 +{@link android.database.sqlite.SQLiteOpenHelper} 类可帮助您创建数据库,{@link android.database.sqlite.SQLiteDatabase} 类是用于访问数据库的基类。 + + + +

    + 请记住,您不必使用数据库来实现存储库。提供程序在外部表现为一组表,与关系数据库类似,但这并不是对提供程序内部实现的要求; + + +

    +
  • +
  • + 对于存储文件数据,Android 提供了各种面向文件的 API。 + 如需了解有关文件存储的更多信息,请阅读数据存储主题。 +如果您要设计提供媒体相关数据(如音乐或视频)的提供程序,则可开发一个合并了表数据和文件的提供程序; + + +
  • +
  • + 要想使用基于网络的数据,请使用 {@link java.net} 和 {@link android.net} 中的类。 +您也可以将基于网络的数据与本地数据存储(如数据库)同步,然后以表或文件的形式提供数据。 + + 示例同步适配器示例应用展示了这类同步。 + +
  • +
+

+ 数据设计考虑事项 +

+

+ 以下是一些设计提供程序数据结构的技巧: +

+
    +
  • + 表数据应始终具有一个“主键”列,提供程序将其作为与每行对应的唯一数字值加以维护。 +您可以使用此值将该行链接到其他表中的相关行(将其用作“外键”)。 +尽管您可以为此列使用任何名称,但使用 {@link android.provider.BaseColumns#_ID BaseColumns._ID} 是最佳选择,因为将提供程序查询的结果链接到 {@link android.widget.ListView} 的条件是,检索到的其中一个列的名称必须是 _ID; + + + + +
  • +
  • + 如果您想提供位图图像或其他非常庞大的文件导向型数据,请将数据存储在一个文件中,然后间接提供这些数据,而不是直接将其存储在表中。 + +如果您执行了此操作,则需要告知提供程序的用户,他们需要使用 {@link android.content.ContentResolver} 文件方法来访问数据; + +
  • +
  • + 使用二进制大型对象 (BLOB) 数据类型存储大小或结构会发生变化的数据。 +例如,您可以使用 BLOB 列来存储协议缓冲区JSON 结构。 + + +

    + 您也可以使用 BLOB 来实现独立于架构的表。在这类表中,您需要以 BLOB 形式定义一个主键列、一个 MIME 类型列以及一个或多个通用列。 + +这些 BLOB 列中数据的含义通过 MIME 类型列中的值指示。 +这样一来,您就可以在同一表中存储不同类型的行。 +举例来说,联系人提供程序的“数据”表 {@link android.provider.ContactsContract.Data} 便是一个独立于架构的表。 + + +

    +
  • +
+ +

设计内容 URI

+

+ 内容 URI 是用于在提供程序中标识数据的 URI。内容 URI 包括整个提供程序的符号名称(其权限)和一个指向表或文件的名称(路径)。 + +可选 ID 部分指向 + 表中的单个行。 + {@link android.content.ContentProvider} 的每一个数据访问方法都将内容 URI 作为参数;您可以利用这一点确定要访问的表、行或文件。 + +

+

+ + 内容提供程序基础知识主题中描述了内容 URI 的基础知识。 + +

+

设计权限

+

+ 提供程序通常具有单一权限,该权限充当其 Android 内部名称。为避免与其他提供程序发生冲突,您应该使用 Internet 域所有权(反向)作为提供程序权限的基础。 + +由于此建议也适用于 Android 软件包名称,因此您可以将提供程序权限定义为包含该提供程序的软件包名称的扩展名。 + +例如,如果您的 Android 软件包名称为 + com.example.<appname>,则应为提供程序授予权限 com.example.<appname>.provider。 + +

+

设计路径结构

+

+ 开发者通常通过追加指向单个表的路径来根据权限创建内容 URI。 +例如,如果您有两个表:table1 和 + table2,则可以通过合并上一示例中的权限来生成 + 内容 URI + com.example.<appname>.provider/table1 和 + com.example.<appname>.provider/table2。路径并不限定于单个段,也无需为每一级路径都创建一个表。 + +

+

处理内容 URI ID

+

+ 按照惯例,提供程序通过接受末尾具有行所对应 ID 值的内容 URI 来提供对表中单个行的访问。同样按照惯例,提供程序会将该 ID 值与表的 _ID 列进行匹配,并对匹配的行执行请求的访问。 + + + +

+

+ 这一惯例为访问提供程序的应用的常见设计模式提供了便利。应用会对提供程序执行查询,并使用 {@link android.widget.CursorAdapter} 以 {@link android.widget.ListView} 显示生成的 {@link android.database.Cursor}。 + + + 定义 {@link android.widget.CursorAdapter} 的条件是, + {@link android.database.Cursor} 中的其中一个列必须是 _ID +

+

+ 用户随后从 UI 上显示的行中选取其中一行,以查看或修改数据。 +应用会从支持 + {@link android.widget.ListView} 的 {@link android.database.Cursor} 中获取对应行,获取该行的 _ID 值,将其追加到内容 URI,然后向提供程序发送访问请求。 +然后,提供程序便可对用户选取的特定行执行查询或修改。 + +

+

内容 URI 模式

+

+ 为帮助您选择对传入的内容 URI 执行的操作,提供程序 API 加入了实用类 {@link android.content.UriMatcher},它会将内容 URI“模式”映射到整型值。 + +您可以在一个 switch 语句中使用这些整型值,为匹配特定模式的一个或多个内容 URI 选择所需操作。 + +

+

+ 内容 URI 模式使用通配符匹配内容 URI: +

+
    +
  • + *:匹配由任意长度的任何有效字符组成的字符串 +
  • +
  • + #:匹配由任意长度的数字字符组成的字符串 +
  • +
+

+ 以设计和编码内容 URI 处理为例,假设一个具有权限 com.example.app.provider 的提供程序能识别以下指向表的内容 URI: + + +

+
    +
  • + content://com.example.app.provider/table1:一个名为 table1 的表 +
  • +
  • + content://com.example.app.provider/table2/dataset1:一个名为 + dataset1 的表 +
  • +
  • + content://com.example.app.provider/table2/dataset2:一个名为 + dataset2 的表 +
  • +
  • + content://com.example.app.provider/table3:一个名为 table3 的表 +
  • +
+

+ 提供程序也能识别追加了行 ID 的内容 URI,例如,content://com.example.app.provider/table3/1 对应由 table31 标识的行的内容 URI。 + + +

+

+ 可以使用以下内容 URI 模式: +

+
+
+ content://com.example.app.provider/* +
+
+ 匹配提供程序中的任何内容 URI。 +
+
+ content://com.example.app.provider/table2/*: +
+
+ 匹配表 dataset1 + 和表 dataset2 的内容 URI,但不匹配 table1 或 + table3 的内容 URI。 +
+
+ content://com.example.app.provider/table3/#:匹配 + table3 中单个行的内容 URI,如 content://com.example.app.provider/table3/6 对应由 + 6 标识的行的内容 URI。 + +
+
+

+ 以下代码段说明了 {@link android.content.UriMatcher} 中方法的工作方式。 + 此代码采用不同方式处理整个表的 URI 与单个行的 URI,它为表使用的内容 URI 模式是 + content://<authority>/<path>,为单个行使用的内容 URI 模式则是 + content://<authority>/<path>/<id>。 + +

+

+ 方法 {@link android.content.UriMatcher#addURI(String, String, int) addURI()} 会将权限和路径映射到一个整型值。 +方法 {@link android.content.UriMatcher#match(Uri) + match()} 会返回 URI 的整型值。switch 语句会在查询整个表与查询单个记录之间进行选择: + +

+
+public class ExampleProvider extends ContentProvider {
+...
+    // Creates a UriMatcher object.
+    private static final UriMatcher sUriMatcher;
+...
+    /*
+     * The calls to addURI() go here, for all of the content URI patterns that the provider
+     * should recognize. 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
+     */
+    sUriMatcher.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.
+     */
+    sUriMatcher.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 (sUriMatcher.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 is not recognized, you should do some error handling here.
+        }
+        // call the code to actually do the query
+    }
+
+

+ 另一个类 {@link android.content.ContentUris} 会提供一些工具方法,用于处理内容 URI 的 id 部分。 +{@link android.net.Uri} 类和 + {@link android.net.Uri.Builder} 类包括一些工具方法,用于解析现有 + {@link android.net.Uri} 对象和构建新对象。 +

+ + +

实现 ContentProvider 类

+

+ {@link android.content.ContentProvider} 实例通过处理来自其他应用的请求来管理对结构化数据集的访问。 +所有形式的访问最终都会调用 {@link android.content.ContentResolver},后者接着调用 + {@link android.content.ContentProvider} 的具体方法来获取访问权限。 + +

+

必需方法

+

+ 抽象类 {@link android.content.ContentProvider} 定义了六个抽象方法,您必须将这些方法作为自己具体子类的一部分加以实现。 +所有这些方法( + {@link android.content.ContentProvider#onCreate() onCreate()} 除外)都由一个尝试访问您的内容提供程序的客户端应用调用: + +

+
+
+ {@link android.content.ContentProvider#query(Uri, String[], String, String[], String) + query()} +
+
+ 从您的提供程序检索数据。使用参数选择要查询的表、要返回的行和列以及结果的排序顺序。 + + 将数据作为 {@link android.database.Cursor} 对象返回。 +
+
+ {@link android.content.ContentProvider#insert(Uri, ContentValues) insert()} +
+
+ 在您的提供程序中插入一个新行。使用参数选择目标表并获取要使用的列值。 +返回新插入行的内容 URI。 + +
+
+ {@link android.content.ContentProvider#update(Uri, ContentValues, String, String[]) + update()} +
+
+ 更新您提供程序中的现有行。使用参数选择要更新的表和行,并获取更新后的列值。 +返回已更新的行数。 +
+
+ {@link android.content.ContentProvider#delete(Uri, String, String[]) delete()} +
+
+ 从您的提供程序中删除行。使用参数选择要删除的表和行。 +返回已删除的行数。 +
+
+ {@link android.content.ContentProvider#getType(Uri) getType()} +
+
+ 返回内容 URI 对应的 MIME 类型。实现内容提供程序 MIME 类型部分对此方法做了更详尽的描述。 + +
+
+ {@link android.content.ContentProvider#onCreate() onCreate()} +
+
+ 初始化您的提供程序。Android 系统会在创建您的提供程序后立即调用此方法。 +请注意, + {@link android.content.ContentResolver} 对象尝试访问您的提供程序时,系统才会创建它。 +
+
+

+ 请注意,这些方法的签名与同名的 + {@link android.content.ContentResolver} 方法相同。 +

+

+ 您在实现这些方法时应考虑以下事项: +

+
    +
  • + 所有这些方法({@link android.content.ContentProvider#onCreate() onCreate()} + 除外)都可由多个线程同时调用,因此它们必须是线程安全方法。如需了解有关多个线程的更多信息,请参阅进程和线程主题; + + + +
  • +
  • + 避免在 {@link android.content.ContentProvider#onCreate() + onCreate()} 中执行长时间操作。将初始化任务推迟到实际需要时进行。 + 实现 onCreate() 方法部分对此做了更详尽的描述; + +
  • +
  • + 尽管您必须实现这些方法,但您的代码只需返回要求的数据类型,无需执行任何其他操作。 +例如,您可能想防止其他应用向某些表插入数据。 +要实现此目的,您可以忽略 + {@link android.content.ContentProvider#insert(Uri, ContentValues) insert()} 调用并返回 0。 + +
  • +
+

实现 query() 方法

+

+ + {@link android.content.ContentProvider#query(Uri, String[], String, String[], String) + ContentProvider.query()} 方法必须返回 {@link android.database.Cursor} 对象。如果失败,则会引发 {@link java.lang.Exception}。 +如果您使用 SQLite 数据库作为数据存储,则只需返回由 {@link android.database.sqlite.SQLiteDatabase} 类的其中一个 + query() 方法返回的 {@link android.database.Cursor}。 + + 如果查询不匹配任何行,您应该返回一个 {@link android.database.Cursor} + 实例(其 {@link android.database.Cursor#getCount()} 方法返回 0)。 + 只有当查询过程中出现内部错误时,您才应该返回 null。 +

+

+ 如果您不使用 SQLite 数据库作为数据存储,请使用 {@link android.database.Cursor} 的其中一个具体子类。 +例如,在 {@link android.database.MatrixCursor} 类实现的游标中,每一行都是一个 {@link java.lang.Object} 数组。 +对于此类,请使用 {@link android.database.MatrixCursor#addRow(Object[]) addRow()} 来添加新行。 + +

+

+ 请记住,Android 系统必须能够跨进程边界传播 {@link java.lang.Exception}。 +Android 可以为以下异常执行此操作,这些异常可能有助于处理查询错误: + +

+
    +
  • + {@link java.lang.IllegalArgumentException}(您可以选择在提供程序收到无效的内容 URI 时引发此异常) + +
  • +
  • + {@link java.lang.NullPointerException} +
  • +
+

实现 insert() 方法

+

+ {@link android.content.ContentProvider#insert(Uri, ContentValues) insert()} 方法会使用 {@link android.content.ContentValues} + 参数中的值向相应表中添加新行。 +如果 {@link android.content.ContentValues} 参数中未包含列名称,您可能想在您的提供程序代码或数据库架构中提供其默认值。 + + +

+

+ 此方法应该返回新行的内容 URI。要想构建此方法,请使用 + {@link android.content.ContentUris#withAppendedId(Uri, long) withAppendedId()} 向表的内容 URI 追加新行的 _ID(或其他主键)值。 + +

+

实现 delete() 方法

+

+ {@link android.content.ContentProvider#delete(Uri, String, String[]) delete()} 方法不需要从您的数据存储中实际删除行。 +如果您将同步适配器与提供程序一起使用,应该考虑为已删除的行添加“删除”标志,而不是将行整个移除。 + +同步适配器可以检查是否存在已删除的行,并将它们从服务器中移除,然后再将它们从提供程序中删除。 + +

+

实现 update() 方法

+

+ {@link android.content.ContentProvider#update(Uri, ContentValues, String, String[]) + update()} 方法采用 + {@link android.content.ContentProvider#insert(Uri, ContentValues) insert()} 所使用的相同 {@link android.content.ContentValues} 参数,以及 +{@link android.content.ContentProvider#delete(Uri, String, String[]) delete()} 和 + {@link android.content.ContentProvider#query(Uri, String[], String, String[], String) + ContentProvider.query()} 所使用的相同 selectionselectionArgs 参数。 +这样一来,您就可以在这些方法之间重复使用代码。 +

+

实现 onCreate() 方法

+

+ Android 系统会在启动提供程序时调用 {@link android.content.ContentProvider#onCreate() + onCreate()}。您只应在此方法中执行运行快速的初始化任务,并将数据库创建和数据加载推迟到提供程序实际收到数据请求时进行。 + +如果您在 + {@link android.content.ContentProvider#onCreate() onCreate()} 中执行长时间的任务,则会减慢提供程序的启动速度, +进而减慢提供程序对其他应用的响应速度。 + +

+

+ 例如,如果您使用 SQLite 数据库,可以在 + {@link android.content.ContentProvider#onCreate() ContentProvider.onCreate()} 中创建一个新的 {@link android.database.sqlite.SQLiteOpenHelper} 对象,然后在首次打开数据库时创建 SQL 表。 + +为简化这一过程,在您首次调用 {@link android.database.sqlite.SQLiteOpenHelper#getWritableDatabase + getWritableDatabase()} 时,它会自动调用 + {@link android.database.sqlite.SQLiteOpenHelper#onCreate(SQLiteDatabase) + SQLiteOpenHelper.onCreate()} 方法。 + +

+

+ 以下两个代码段展示了 + {@link android.content.ContentProvider#onCreate() ContentProvider.onCreate()} 与 + {@link android.database.sqlite.SQLiteOpenHelper#onCreate(SQLiteDatabase) + SQLiteOpenHelper.onCreate()} 之间的交互。第一个代码段是 + {@link android.content.ContentProvider#onCreate() ContentProvider.onCreate()} 的实现: +

+
+public class ExampleProvider extends ContentProvider
+
+    /*
+     * Defines a handle to the database helper object. The MainDatabaseHelper class is defined
+     * in a following snippet.
+     */
+    private MainDatabaseHelper mOpenHelper;
+
+    // Defines the database name
+    private static final String DBNAME = "mydb";
+
+    // Holds the database object
+    private SQLiteDatabase db;
+
+    public boolean onCreate() {
+
+        /*
+         * Creates a new helper object. This method always returns quickly.
+         * Notice that the database itself isn't created or opened
+         * until SQLiteOpenHelper.getWritableDatabase is called
+         */
+        mOpenHelper = new MainDatabaseHelper(
+            getContext(),        // the application context
+            DBNAME,              // the name of the database)
+            null,                // uses the default SQLite cursor
+            1                    // the version number
+        );
+
+        return true;
+    }
+
+    ...
+
+    // Implements the provider's insert method
+    public Cursor insert(Uri uri, ContentValues values) {
+        // Insert code here to determine which table to open, handle error-checking, and so forth
+
+        ...
+
+        /*
+         * Gets a writeable database. This will trigger its creation if it doesn't already exist.
+         *
+         */
+        db = mOpenHelper.getWritableDatabase();
+    }
+}
+
+

+ 下一个代码段是 + {@link android.database.sqlite.SQLiteOpenHelper#onCreate(SQLiteDatabase) + SQLiteOpenHelper.onCreate()} 的实现,其中包括一个帮助程序类: +

+
+...
+// A string that defines the SQL statement for creating a table
+private static final String SQL_CREATE_MAIN = "CREATE TABLE " +
+    "main " +                       // Table's name
+    "(" +                           // The columns in the table
+    " _ID INTEGER PRIMARY KEY, " +
+    " WORD TEXT"
+    " FREQUENCY INTEGER " +
+    " LOCALE TEXT )";
+...
+/**
+ * Helper class that actually creates and manages the provider's underlying data repository.
+ */
+protected static final class MainDatabaseHelper extends SQLiteOpenHelper {
+
+    /*
+     * Instantiates an open helper for the provider's SQLite data repository
+     * Do not do database creation and upgrade here.
+     */
+    MainDatabaseHelper(Context context) {
+        super(context, DBNAME, null, 1);
+    }
+
+    /*
+     * Creates the data repository. This is called when the provider attempts to open the
+     * repository and SQLite reports that it doesn't exist.
+     */
+    public void onCreate(SQLiteDatabase db) {
+
+        // Creates the main table
+        db.execSQL(SQL_CREATE_MAIN);
+    }
+}
+
+ + + +

实现 ContentProvider MIME 类型

+

+ {@link android.content.ContentProvider} 类具有两个返回 MIME 类型的方法: +

+
+
+ {@link android.content.ContentProvider#getType(Uri) getType()} +
+
+ 您必须为任何提供程序实现的必需方法之一。 +
+
+ {@link android.content.ContentProvider#getStreamTypes(Uri, String) getStreamTypes()} +
+
+ 系统在您的提供程序提供文件时要求实现的方法。 +
+
+

表的 MIME 类型

+

+ {@link android.content.ContentProvider#getType(Uri) getType()} 方法会返回一个 MIME 格式的 + {@link java.lang.String},后者描述内容 + URI 参数返回的数据类型。{@link android.net.Uri} 参数可以是模式,而不是特定 URI;在这种情况下,您应该返回与匹配该模式的内容 URI 关联的数据类型。 + + +

+

+ 对于文本、HTML 或 JPEG 等常见数据类型, + {@link android.content.ContentProvider#getType(Uri) getType()} 应该为该数据返回标准 + MIME 类型。 + IANA MIME Media Types + 网站上提供了这些标准类型的完整列表。 +

+

+ 对于指向一个或多个表数据行的内容 URI, + {@link android.content.ContentProvider#getType(Uri) getType()} 应该以 Android 供应商特有 MIME 格式返回 + MIME 类型: +

+
    +
  • + 类型部分:vnd +
  • +
  • + 子类型部分: +
      +
    • + 如果 URI 模式用于单个行:android.cursor.item/ +
    • +
    • + 如果 URI 模式用于多个行:android.cursor.dir/ +
    • +
    +
  • +
  • + 提供程序特有部分:vnd.<name>.<type> +

    + 您提供 <name><type>。 + <name> 值应具有全局唯一性, + <type> 值应在对应的 URI + 模式中具有唯一性。适合选择贵公司的名称或您的应用 Android 软件包名称的某个部分作为 <name>。 +适合选择 URI 关联表的标识字符串作为 <type>。 + + +

    + +
  • +
+

+ 例如,如果提供程序的权限是 + com.example.app.provider,并且它公开了一个名为 + table1 的表,则 table1 中多个行的 MIME 类型是: +

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

+ 对于 table1 的单个行,MIME 类型是: +

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

文件的 MIME 类型

+

+ 如果您的提供程序提供文件,请实现 + {@link android.content.ContentProvider#getStreamTypes(Uri, String) getStreamTypes()}。 + 该方法会为您的提供程序可以为给定内容 URI 返回的文件返回一个 MIME 类型 {@link java.lang.String} 数组。您应该通过 MIME 类型过滤器参数过滤您提供的 MIME 类型,以便只返回客户端想处理的那些 MIME 类型。 + + +

+

+ 例如,假设提供程序以 .jpg、 + .png.gif 格式文件形式提供照片图像。 + 如果应用调用 {@link android.content.ContentResolver#getStreamTypes(Uri, String) + ContentResolver.getStreamTypes()} 时使用了过滤器字符串 image/*(任何是“图像”的内容),则 {@link android.content.ContentProvider#getStreamTypes(Uri, String) + ContentProvider.getStreamTypes()} 方法应返回数组: + + +

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

+ 如果应用只对 .jpg 文件感兴趣,则可以在调用 + {@link android.content.ContentResolver#getStreamTypes(Uri, String) + ContentResolver.getStreamTypes()} 时使用过滤器字符串 *\/jpeg,{@link android.content.ContentProvider#getStreamTypes(Uri, String) + ContentProvider.getStreamTypes()} 应返回: + +

+{"image/jpeg"}
+
+

+ 如果您的提供程序未提供过滤器字符串中请求的任何 MIME 类型,则 + {@link android.content.ContentProvider#getStreamTypes(Uri, String) getStreamTypes()} + 应返回 null。 +

+ + + +

实现协定类

+

+ 协定类是一种 public final 类,其中包含对 URI、列名称、MIME 类型以及其他与提供程序有关的元数据的常量定义。 +该类可确保即使 URI、列名称等数据的实际值发生变化,也可以正确访问提供程序,从而在提供程序与其他应用之间建立合同。 + + + +

+

+ 协定类对开发者也有帮助,因为其常量通常采用助记名称,因此可以降低开发者为列名称或 URI 使用错误值的可能性。 +由于它是一种类,因此可以包含 Javadoc 文档。 +集成开发环境(如 + Eclipse)可以根据协定类自动完成常量名称,并为常量显示 Javadoc。 + +

+

+ 开发者无法从您的应用访问协定类的类文件,但他们可以通过您提供的 .jar 文件将其静态编译到其应用内。 + +

+

+ 举例来说,{@link android.provider.ContactsContract} 类及其嵌套类便属于协定类。 + +

+

实现内容提供程序权限

+

+ 安全与权限主题中详细描述了 Android 系统各个方面的权限和访问。 + + 数据存储主题也描述了各类存储实行中的安全与权限。 + + 其中的要点简述如下: +

+
    +
  • + 默认情况下,存储在设备内部存储上的数据文件是您的应用和提供程序的私有数据文件; + +
  • +
  • + 您创建的 {@link android.database.sqlite.SQLiteDatabase} 数据库是您的应用和提供程序的私有数据库; + +
  • +
  • + 默认情况下,您保存到外部存储的数据文件是公用可全局读取的数据文件。 +您无法使用内容提供程序来限制对外部存储内文件的访问,因为其他应用可以使用其他 API 调用来对它们执行读取和写入操作; + +
  • +
  • + 用于在您的设备的内部存储上打开或创建文件或 SQLite 数据库的方法调用可能会为所有其他应用同时授予读取和写入访问权限。 +如果您将内部文件或数据库用作提供程序的存储库,并向其授予“可全局读取”或“可全局写入”访问权限,则您在清单文件中为提供程序设置的权限不会保护您的数据。 + + +内部存储中文件和数据库的默认访问权限是“私有”,对于提供程序的存储库,您不应更改此权限。 + +
  • +
+

+ 如果您想使用内容提供程序权限来控制对数据的访问,则应将数据存储在内部文件、SQLite 数据库或“云”中(例如,远程服务器上),而且您应该保持文件和数据库为您的应用所私有。 + + +

+

实现权限

+

+ 即使底层数据为私有数据,所有应用仍可从您的提供程序读取数据或向其写入数据,因为在默认情况下,您的提供程序未设置权限。 +要想改变这种情况,请使用属性或 + + <provider> 元素的子元素在您的清单文件中为您的提供程序设置权限。 +您可以设置适用于整个提供程序、特定表、甚至特定记录的权限,或者设置同时适用于这三者的权限。 + +

+

+ 您可以通过清单文件中的一个或多个 + + <permission> 元素为您的提供程序定义权限。要使权限对您的提供程序具有唯一性,请为 + + android:name 属性使用 Java 风格作用域。 +例如,将读取权限命名为 + com.example.app.provider.permission.READ_PROVIDER。 + +

+

+ 以下列表描述了提供程序权限的作用域,从适用于整个提供程序的权限开始,然后逐渐细化。 + + 更细化的权限优先于作用域较大的权限: +

+
+
+ 统一读写提供程序级别权限 +
+
+ 一个同时控制对整个提供程序读取和写入访问的权限,通过 + + <provider> 元素的 + android:permission 属性指定。 + +
+
+ 单独的读取和写入提供程序级别权限 +
+
+ 针对整个提供程序的读取权限和写入权限。您可以通过 + + <provider> 元素的 + android:readPermission 属性和 + + android:writePermission 属性 + 指定它们。它们优先于 + + android:permission 所需的权限。 +
+
+ 路径级别权限 +
+
+ 针对提供程序中内容 URI 的读取、写入或读取/写入权限。您可以通过 + + <provider> 元素的 + <path-permission> 子元素指定您想控制的每个 URI。 + +对于您指定的每个内容 URI,您都可以指定读取/写入权限、读取权限或写入权限,或同时指定所有三种权限。 +读取权限和写入权限优先于读取/写入权限。 +此外,路径级别权限优先于提供程序级别权限。 + +
+
+ 临时权限 +
+
+ 一种权限级别,即使应用不具备通常需要的权限,该级别也能授予对应用的临时访问权限。 +临时访问功能可减少应用需要在其清单文件中请求的权限数量。 + +当您启用临时权限时,只有持续访问您的所有数据的应用才需要“永久性”提供程序访问权限。 + + +

+ 假设您需要实现电子邮件提供程序和应用的权限,如果您想允许外部图像查看器应用显示您的提供程序中的照片附件, + +为了在不请求权限的情况下为图像查看器提供必要的访问权限,可以为照片的内容 URI 设置临时权限。 +对您的电子邮件应用进行相应设计,使应用能够在用户想要显示照片时向图像查看器发送一个 Intent,其中包含照片的内容 URI 以及权限标志。 + +图像查看器可随后查询您的电子邮件提供程序以检索照片,即使查看器不具备对您提供程序的正常读取权限,也不受影响。 + + +

+

+ 要想启用临时权限,请设置 + + <provider> 元素的 + + android:grantUriPermissions 属性,或者向您的 + + <provider> 元素添加一个或多个 + + <grant-uri-permission> 子元素。如果您使用了临时权限,则每当您从提供程序中移除对某个内容 URI 的支持,并且该内容 URI 关联了临时权限时,都需要调用 + {@link android.content.Context#revokeUriPermission(Uri, int) + Context.revokeUriPermission()}。 + +

+

+ 该属性的值决定可访问的提供程序范围。 + 如果该属性设置为 true,则系统会向整个提供程序授予临时权限,该权限将替代您的提供程序级别或路径级别权限所需的任何其他权限。 + + +

+

+ 如果此标志设置为 false,则您必须向 + + <provider> 元素添加 + + <grant-uri-permission> 子元素。每个子元素都指定授予的临时权限所对应的一个或多个内容 URI。 + +

+

+ 要向应用授予临时访问权限, Intent 必须包含 + {@link android.content.Intent#FLAG_GRANT_READ_URI_PERMISSION} 和/或 + {@link android.content.Intent#FLAG_GRANT_WRITE_URI_PERMISSION} 标志。它们通过 {@link android.content.Intent#setFlags(int) setFlags()} 方法进行设置。 + +

+

+ 如果 + android:grantUriPermissions 属性不存在,则假设其为 + false。 +

+
+
+ + + + +

<Provider> 元素

+

+ 与 {@link android.app.Activity} 和 {@link android.app.Service} 组件类似,必须使用 + + <provider> 元素在清单文件中为其应用定义 + {@link android.content.ContentProvider} 的子类。 +Android 系统会从该元素获取以下信息: + +

+
+ 权限 + ({@code + android:authorities}) +
+
+ 用于在系统内标识整个提供程序的符号名称。设计内容 URI 部分对此属性做了更详尽的描述。 + + +
+
+ 提供程序类名 + ( +android:name + ) +
+
+ 实现 {@link android.content.ContentProvider} 的类。实现 ContentProvider 类中对此类做了更详尽的描述。 + + +
+
+ 权限 +
+
+ 指定其他应用访问提供程序的数据所必须具备权限的属性: + + +

+ 实现内容提供程序权限部分对权限及其对应属性做了更详尽的描述。 + + +

+
+
+ 启动和控制属性 +
+
+ 这些属性决定 Android 系统如何以及何时启动提供程序、提供程序的进程特性以及其他运行时设置: + + +

+ 开发指南中针对 + + <provider> + 元素的主题提供了这些属性的完整资料。 +

+
+
+ 信息属性 +
+
+ 提供程序的可选图标和标签: +
    +
  • + + android:icon:包含提供程序图标的 Drawable 资源。 + 该图标出现在设置 > 应用 > 全部 中应用列表内的提供程序标签旁; + +
  • +
  • + + android:label:描述提供程序和/或其数据的信息标签。 +该标签出现在设置 > 应用 > 全部中的应用列表内。 + +
  • +
+

+ 开发指南中针对 + + <provider> 元素的主题提供了这些属性的完整资料。 +

+
+
+ + +

Intent 和数据访问

+

+ 应用可以通过 {@link android.content.Intent} 间接访问内容提供程序。 + 应用不会调用 {@link android.content.ContentResolver} 或 + {@link android.content.ContentProvider} 的任何方法,而会发送一个启动 Activity 的 Intent,该 Activity 通常是提供程序自身应用的一部分。 +目标 Activity 负责检索和显示其 UI 中的数据。视 Intent 中的操作而定,目标 Activity 可能还会提示用户对提供程序的数据进行修改。 + + + Intent 可能还包含目标 Activity 在 UI 中显示的“extra”数据;用户随后可以选择更改此数据,然后使用它来修改提供程序中的数据。 + + +

+

+ +

+

+ 您可能想使用 Intent 访问权限来帮助确保数据完整性。您的提供程序可能依赖于根据严格定义的业务逻辑插入、更新和删除数据。 +如果是这种情况,则允许其他应用直接修改您的数据可能会导致无效的数据。 + +如果您想让开发者使用 Intent 访问权限,请务必为其提供详尽的参考资料。 + 向他们解释为什么使用自身应用 UI 的 Intent 访问比尝试通过代码修改数据更好。 + +

+

+ 处理想要修改您的提供程序数据的传入 Intent 与处理其他 Intent 没有区别。 +您可以通过阅读 Intent 和 Intent 过滤器主题了解有关 Intent 用法的更多信息。 + +

diff --git a/docs/html-intl/intl/zh-cn/guide/topics/providers/content-providers.jd b/docs/html-intl/intl/zh-cn/guide/topics/providers/content-providers.jd new file mode 100644 index 0000000000000000000000000000000000000000..80c778aad4bfeb2c17d3b4b47d4b36972d109c69 --- /dev/null +++ b/docs/html-intl/intl/zh-cn/guide/topics/providers/content-providers.jd @@ -0,0 +1,108 @@ +page.title=内容提供程序 +@jd:body + +

+ 内容提供程序管理对结构化数据集的访问。它们封装数据,并提供用于定义数据安全性的机制。 +内容提供程序是连接一个进程中的数据与另一个进程中运行的代码的标准界面。 + +

+

+ 如果您想要访问内容提供程序中的数据,可以将应用的 {@link android.content.Context} 中的 {@link android.content.ContentResolver} 对象用作客户端来与提供程序通信。 + + + {@link android.content.ContentResolver} 对象会与提供程序对象(即实现 {@link android.content.ContentProvider} 的类实例)通信。 +提供程序对象从客户端接收数据请求,执行请求的操作并返回结果。 + + +

+

+ 如果您不打算与其他应用共享数据,则无需开发自己的提供程序。 +不过,您需要通过自己的提供程序在您自己的应用中提供自定义搜索建议。 +如果您想将复杂的数据或文件从您的应用复制并粘贴到其他应用中,也需要创建您自己的提供程序。 + +

+

+ Android 本身包括的内容提供程序可管理音频、视频、图像和个人联系信息等数据。 +android.provider + 软件包参考文档中列出了部分提供程序。 + +任何 Android 应用都可以访问这些提供程序,但会受到某些限制。 + +

+ 以下主题对内容提供程序做了更详尽的描述: +

+
+
+ + 内容提供程序基础知识 +
+
+ 如何访问内容提供程序中以表形式组织的数据。 +
+
+ + 创建内容提供程序 +
+
+ 如何创建您自己的内容提供程序。 +
+
+ + 日历提供程序 +
+
+ 如何访问作为 Android 平台一部分的日历提供程序。 +
+
+ + 联系人提供程序 +
+
+ 如何访问作为 Android 平台一部分的联系人提供程序。 +
+
diff --git a/docs/html-intl/intl/zh-cn/guide/topics/providers/document-provider.jd b/docs/html-intl/intl/zh-cn/guide/topics/providers/document-provider.jd new file mode 100644 index 0000000000000000000000000000000000000000..fd36e2963606376d0f0c94202cfadfc4b2f1c12a --- /dev/null +++ b/docs/html-intl/intl/zh-cn/guide/topics/providers/document-provider.jd @@ -0,0 +1,916 @@ +page.title=存储访问框架 +@jd:body + + + +

Android 4.4(API 19 级)引入了存储访问框架 (SAF)。SAF +让用户能够在其所有首选文档存储提供程序中方便地浏览并打开文档、图像以及其他文件。 +用户可以通过易用的标准 UI,以统一方式在所有应用和提供程序中浏览文件和访问最近使用的文件。 +

+ +

云存储服务或本地存储服务可以通过实现封装其服务的 +{@link android.provider.DocumentsProvider} 参与此生态系统。只需几行代码,便可将需要访问提供程序文档的客户端应用与 +SAF +集成。

+ +

SAF 包括以下内容:

+ +
    +
  • 文档提供程序—一种内容提供程序,允许存储服务(如 Google +云端硬盘)显示其管理的文件。文档提供程序作为 +{@link android.provider.DocumentsProvider} +类的子类实现。文档提供程序的架构基于传统文件层次结构,但其实际数据存储方式由您决定。Android +平台包括若干内置文档提供程序,如 Downloads、Images 和 Videos; + +
  • + +
  • 客户端应用—一种自定义应用,它调用 +{@link android.content.Intent#ACTION_OPEN_DOCUMENT} 和/或 +{@link android.content.Intent#ACTION_CREATE_DOCUMENT} Intent 并接收文档提供程序返回的文件; +
  • + +
  • 选取器—一种系统 +UI,允许用户访问所有满足客户端应用搜索条件的文档提供程序内的文档。
  • +
+ +

SAF 提供的部分功能如下:

+
    +
  • 允许用户浏览所有文档提供程序而不仅仅是单个应用中的内容;
  • +
  • 让您的应用获得对文档提供程序所拥有文档的长期、持久性访问权限。 +用户可以通过此访问权限添加、编辑、保存和删除提供程序上的文件; +
  • +
  • 支持多个用户帐户和临时根目录,如只有在插入驱动器后才会出现的 USB +存储提供程序。
  • +
+ +

概览

+ +

SAF 围绕的内容提供程序是 +{@link android.provider.DocumentsProvider} 类的一个子类。在文档提供程序内,数据结构采用传统的文件层次结构: +

+

data model

+

图 1. 文档提供程序数据模型。根目录指向单个文档,后者随即启动整个结构树的扇出。 +

+ +

请注意以下事项:

+
    + +
  • 每个文档提供程序都会报告一个或多个作为探索文档结构树起点的“根目录”。每个根目录都有一个唯一的 +{@link android.provider.DocumentsContract.Root#COLUMN_ROOT_ID},并且指向表示该根目录下内容的文档(目录)。根目录采用动态设计,以支持多个帐户、临时 +USB +存储设备或用户登录/注销等用例; + + +
  • + +
  • 每个根目录下都有一个文档。该文档指向 1 至 N +个文档,而其中每个文档又可指向 1 至 N 个文档;
  • + +
  • 每个存储后端都会通过使用唯一的 +{@link android.provider.DocumentsContract.Document#COLUMN_DOCUMENT_ID} 引用各个文件和目录来显示它们。文档 +ID +必须具有唯一性,一旦发放便不得更改,因为它们用于所有设备重启过程中的永久性 +URI 授权;
  • + + +
  • 文档可以是可打开的文件(具有特定 MIME +类型)或包含附加文档的目录(具有 +{@link android.provider.DocumentsContract.Document#MIME_TYPE_DIR} MIME 类型);
  • + +
  • 每个文档都可以具有不同的功能,如 +{@link android.provider.DocumentsContract.Document#COLUMN_FLAGS COLUMN_FLAGS} 所述。例如,{@link android.provider.DocumentsContract.Document#FLAG_SUPPORTS_WRITE}、{@link android.provider.DocumentsContract.Document#FLAG_SUPPORTS_DELETE} +和 +{@link android.provider.DocumentsContract.Document#FLAG_SUPPORTS_THUMBNAIL}。多个目录中可以包含相同的 +{@link android.provider.DocumentsContract.Document#COLUMN_DOCUMENT_ID}。 + +
  • +
+ +

控制流

+

如前文所述,文档提供程序数据模型基于传统文件层次结构。 +不过,只要可以通过 +{@link android.provider.DocumentsProvider} API +访问数据,您实际上可以按照自己喜好的方式存储数据。例如,您可以使用基于标记的云存储来存储数据。

+ +

图 2 中的示例展示的是照片应用如何利用 SAF +访问存储的数据:

+

app

+ +

图 2. 存储访问框架流

+ +

请注意以下事项:

+
    + +
  • 在 SAF +中,提供程序和客户端并不直接交互。客户端请求与文件交互(即读取、编辑、创建或删除文件)的权限; +
  • + +
  • 交互在应用(在本示例中为照片应用)触发 Intent +{@link android.content.Intent#ACTION_OPEN_DOCUMENT} 或 {@link android.content.Intent#ACTION_CREATE_DOCUMENT} 后开始。Intent 可能包括进一步细化条件的过滤器—例如,“为我提供所有 MIME +类型为‘图像’的可打开文件”; +
  • + +
  • Intent 触发后,系统选取器将检索每个已注册的提供程序,并向用户显示匹配的内容根目录; +
  • + +
  • 选取器会为用户提供一个标准的文档访问界面,但底层文档提供程序可能与其差异很大。 +例如,图 2 +显示了一个 Google 云端硬盘提供程序、一个 USB 提供程序和一个云提供程序。
  • +
+ +

图 3 显示了一个选取器,一位搜索图像的用户在其中选择了一个 +Google 云端硬盘帐户:

+ +

picker

+ +

图 3. 选取器

+ +

当用户选择 Google +云端硬盘时,系统会显示图像,如图 4 所示。从这时起,用户就可以通过提供程序和客户端应用支持的任何方式与它们进行交互。 + + +

picker

+ +

图 4. 图像

+ +

编写客户端应用

+ +

对于 Android 4.3 +及更低版本,如果您想让应用从其他应用中检索文件,它必须调用 {@link android.content.Intent#ACTION_PICK} +或 {@link android.content.Intent#ACTION_GET_CONTENT} 等 Intent。然后,用户必须选择一个要从中选取文件的应用,并且所选应用必须提供一个用户界面,以便用户浏览和选取可用文件。 + +

+ +

对于 Android 4.4 及更高版本,您还可以选择使用 +{@link android.content.Intent#ACTION_OPEN_DOCUMENT} + Intent,后者会显示一个由系统控制的选取器 +UI,用户可以通过它浏览其他应用提供的所有文件。用户只需通过这一个 UI +便可从任何受支持的应用中选取文件。

+ +

{@link android.content.Intent#ACTION_OPEN_DOCUMENT} +并非设计用于替代 {@link android.content.Intent#ACTION_GET_CONTENT}。应使用的 Intent 取决于应用的需要: +

+ +
    +
  • 如果您只想让应用读取/导入数据,请使用 +{@link android.content.Intent#ACTION_GET_CONTENT}。使用此方法时,应用会导入数据(如图像文件)的副本; +
  • + +
  • 如果您想让应用获得对文档提供程序所拥有文档的长期、持久性访问权限,请使用 +{@link android.content.Intent#ACTION_OPEN_DOCUMENT}。 +例如,允许用户编辑存储在文档提供程序中的图像的照片编辑应用。 +
  • + +
+ + +

本节描述如何编写基于 +{@link android.content.Intent#ACTION_OPEN_DOCUMENT} 和 +{@link android.content.Intent#ACTION_CREATE_DOCUMENT} Intent 的客户端应用。

+ + + + +

+以下代码段使用 +{@link android.content.Intent#ACTION_OPEN_DOCUMENT} +来搜索包含图像文件的文档提供程序:

+ +
private static final int READ_REQUEST_CODE = 42;
+...
+/**
+ * Fires an intent to spin up the "file chooser" UI and select an image.
+ */
+public void performFileSearch() {
+
+    // ACTION_OPEN_DOCUMENT is the intent to choose a file via the system's file
+    // browser.
+    Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
+
+    // Filter to only show results that can be "opened", such as a
+    // file (as opposed to a list of contacts or timezones)
+    intent.addCategory(Intent.CATEGORY_OPENABLE);
+
+    // Filter to show only images, using the image MIME data type.
+    // If one wanted to search for ogg vorbis files, the type would be "audio/ogg".
+    // To search for all documents available via installed storage providers,
+    // it would be "*/*".
+    intent.setType("image/*");
+
+    startActivityForResult(intent, READ_REQUEST_CODE);
+}
+ +

请注意以下事项:

+
    +
  • 当应用触发 {@link android.content.Intent#ACTION_OPEN_DOCUMENT} + Intent 时,后者会启动一个选取器来显示所有匹配的文档提供程序
  • + +
  • 在 Intent 中添加类别 {@link android.content.Intent#CATEGORY_OPENABLE} +可对结果进行过滤,以仅显示可以打开的文档(如图像文件)
  • + +
  • 语句 {@code intent.setType("image/*")} 可做进一步过滤,以仅显示 +MIME 数据类型为图像的文档
  • +
+ +

处理结果

+ +

用户在选取器中选择文档后,系统就会调用 +{@link android.app.Activity#onActivityResult onActivityResult()}。指向所选文档的 URI +包含在 {@code resultData} +参数中。使用 {@link android.content.Intent#getData getData()} +提取 URI。获得 URI 后,即可使用它来检索用户想要的文档。例如: +

+ +
@Override
+public void onActivityResult(int requestCode, int resultCode,
+        Intent resultData) {
+
+    // The ACTION_OPEN_DOCUMENT intent was sent with the request code
+    // READ_REQUEST_CODE. If the request code seen here doesn't match, it's the
+    // response to some other intent, and the code below shouldn't run at all.
+
+    if (requestCode == READ_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
+        // The document selected by the user won't be returned in the intent.
+        // Instead, a URI to that document will be contained in the return intent
+        // provided to this method as a parameter.
+        // Pull that URI using resultData.getData().
+        Uri uri = null;
+        if (resultData != null) {
+            uri = resultData.getData();
+            Log.i(TAG, "Uri: " + uri.toString());
+            showImage(uri);
+        }
+    }
+}
+
+ +

检查文档元数据

+ +

获得文档的 URI 后,即可获得对其元数据的访问权限。以下代码段用于获取 URI +所指定文档的元数据并将其记入日志:

+ +
public void dumpImageMetaData(Uri uri) {
+
+    // The query, since it only applies to a single document, will only return
+    // one row. There's no need to filter, sort, or select fields, since we want
+    // all fields for one document.
+    Cursor cursor = getActivity().getContentResolver()
+            .query(uri, null, null, null, null, null);
+
+    try {
+    // moveToFirst() returns false if the cursor has 0 rows.  Very handy for
+    // "if there's anything to look at, look at it" conditionals.
+        if (cursor != null && cursor.moveToFirst()) {
+
+            // Note it's called "Display Name".  This is
+            // provider-specific, and might not necessarily be the file name.
+            String displayName = cursor.getString(
+                    cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME));
+            Log.i(TAG, "Display Name: " + displayName);
+
+            int sizeIndex = cursor.getColumnIndex(OpenableColumns.SIZE);
+            // If the size is unknown, the value stored is null.  But since an
+            // int can't be null in Java, the behavior is implementation-specific,
+            // which is just a fancy term for "unpredictable".  So as
+            // a rule, check if it's null before assigning to an int.  This will
+            // happen often:  The storage API allows for remote files, whose
+            // size might not be locally known.
+            String size = null;
+            if (!cursor.isNull(sizeIndex)) {
+                // Technically the column stores an int, but cursor.getString()
+                // will do the conversion automatically.
+                size = cursor.getString(sizeIndex);
+            } else {
+                size = "Unknown";
+            }
+            Log.i(TAG, "Size: " + size);
+        }
+    } finally {
+        cursor.close();
+    }
+}
+
+ +

打开文档

+ +

获得文档的 URI +后,即可打开文档或对其执行任何其他您想要执行的操作。

+ +

位图

+ +

以下示例展示了如何打开 {@link android.graphics.Bitmap}:

+ +
private Bitmap getBitmapFromUri(Uri uri) throws IOException {
+    ParcelFileDescriptor parcelFileDescriptor =
+            getContentResolver().openFileDescriptor(uri, "r");
+    FileDescriptor fileDescriptor = parcelFileDescriptor.getFileDescriptor();
+    Bitmap image = BitmapFactory.decodeFileDescriptor(fileDescriptor);
+    parcelFileDescriptor.close();
+    return image;
+}
+
+ +

请注意,您不应在 UI 线程上执行此操作。请使用 +{@link android.os.AsyncTask} 在后台执行此操作。打开位图后,即可在 +{@link android.widget.ImageView} 中显示它。 +

+ +

获取 InputStream

+ +

以下示例展示了如何从 URI 中获取 +{@link java.io.InputStream}。在此代码段中,系统将文件行读取到一个字符串中:

+ +
private String readTextFromUri(Uri uri) throws IOException {
+    InputStream inputStream = getContentResolver().openInputStream(uri);
+    BufferedReader reader = new BufferedReader(new InputStreamReader(
+            inputStream));
+    StringBuilder stringBuilder = new StringBuilder();
+    String line;
+    while ((line = reader.readLine()) != null) {
+        stringBuilder.append(line);
+    }
+    fileInputStream.close();
+    parcelFileDescriptor.close();
+    return stringBuilder.toString();
+}
+
+ +

创建新文档

+ +

您的应用可以使用 +{@link android.content.Intent#ACTION_CREATE_DOCUMENT} + Intent 在文档提供程序中创建新文档。要想创建文件,请为您的 Intent 提供一个 MIME +类型和文件名,然后通过唯一的请求代码启动它。系统会为您执行其余操作:

+ + +
+// Here are some examples of how you might call this method.
+// The first parameter is the MIME type, and the second parameter is the name
+// of the file you are creating:
+//
+// createFile("text/plain", "foobar.txt");
+// createFile("image/png", "mypicture.png");
+
+// Unique request code.
+private static final int WRITE_REQUEST_CODE = 43;
+...
+private void createFile(String mimeType, String fileName) {
+    Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
+
+    // Filter to only show results that can be "opened", such as
+    // a file (as opposed to a list of contacts or timezones).
+    intent.addCategory(Intent.CATEGORY_OPENABLE);
+
+    // Create a file with the requested MIME type.
+    intent.setType(mimeType);
+    intent.putExtra(Intent.EXTRA_TITLE, fileName);
+    startActivityForResult(intent, WRITE_REQUEST_CODE);
+}
+
+ +

创建新文档后,即可在 +{@link android.app.Activity#onActivityResult onActivityResult()} 中获取其 +URI,以便继续向其写入内容。

+ +

删除文档

+ +

如果您获得了文档的 +URI,并且文档的 +{@link android.provider.DocumentsContract.Document#COLUMN_FLAGS Document.COLUMN_FLAGS} +包含 +{@link android.provider.DocumentsContract.Document#FLAG_SUPPORTS_DELETE SUPPORTS_DELETE},便可以删除该文档。例如:

+ +
+DocumentsContract.deleteDocument(getContentResolver(), uri);
+
+ +

编辑文档

+ +

您可以使用 SAF +就地编辑文本文档。以下代码段会触发 +{@link android.content.Intent#ACTION_OPEN_DOCUMENT} Intent +并使用类别 {@link android.content.Intent#CATEGORY_OPENABLE} +以仅显示可以打开的文档。它会进一步过滤以仅显示文本文件:

+ +
+private static final int EDIT_REQUEST_CODE = 44;
+/**
+ * Open a file for writing and append some text to it.
+ */
+ private void editDocument() {
+    // ACTION_OPEN_DOCUMENT is the intent to choose a file via the system's
+    // file browser.
+    Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
+
+    // Filter to only show results that can be "opened", such as a
+    // file (as opposed to a list of contacts or timezones).
+    intent.addCategory(Intent.CATEGORY_OPENABLE);
+
+    // Filter to show only text files.
+    intent.setType("text/plain");
+
+    startActivityForResult(intent, EDIT_REQUEST_CODE);
+}
+
+ +

接下来,您可以从 +{@link android.app.Activity#onActivityResult onActivityResult()}(请参阅处理结果)调用代码以执行编辑。以下代码段可从 +{@link android.content.ContentResolver} 获取 +{@link java.io.FileOutputStream}。默认情况下,它使用“写入”模式。最佳做法是请求获得所需的最低限度访问权限,因此如果您只需要写入权限,就不要请求获得读取/写入权限。 + +

+ +
private void alterDocument(Uri uri) {
+    try {
+        ParcelFileDescriptor pfd = getActivity().getContentResolver().
+                openFileDescriptor(uri, "w");
+        FileOutputStream fileOutputStream =
+                new FileOutputStream(pfd.getFileDescriptor());
+        fileOutputStream.write(("Overwritten by MyCloud at " +
+                System.currentTimeMillis() + "\n").getBytes());
+        // Let the document provider know you're done by closing the stream.
+        fileOutputStream.close();
+        pfd.close();
+    } catch (FileNotFoundException e) {
+        e.printStackTrace();
+    } catch (IOException e) {
+        e.printStackTrace();
+    }
+}
+ +

保留权限

+ +

当您的应用打开文件进行读取或写入时,系统会为您的应用提供针对该文件的 +URI 授权。该授权将一直持续到用户设备重启时。但假定您的应用是图像编辑应用,而且您希望用户能够直接从应用中访问他们编辑的最后 +5 +张图像。如果用户的设备已经重启,您就需要将用户转回系统选取器以查找这些文件,这显然不是理想的做法。 + +

+ +

为防止出现这种情况,您可以保留系统为您的应用授予的权限。您的应用实际上是“获取”了系统提供的持久 +URI +授权。这使用户能够通过您的应用持续访问文件,即使设备已重启也不受影响: +

+ + +
final int takeFlags = intent.getFlags()
+            & (Intent.FLAG_GRANT_READ_URI_PERMISSION
+            | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+// Check for the freshest data.
+getContentResolver().takePersistableUriPermission(uri, takeFlags);
+ +

还有最后一个步骤。您可能已经保存了应用最近访问的 +URI,但它们可能不再有效—另一个应用可能已删除或修改了文档。 +因此,您应该始终调用 +{@code getContentResolver().takePersistableUriPermission()} +以检查有无最新数据。

+ +

编写自定义文档提供程序

+ +

+如果您要开发为文件提供存储服务(如云保存服务)的应用,可以通过编写自定义文档提供程序,通过 +SAF +提供您的文件。本节描述如何执行此操作。 +

+ + +

清单文件

+ +

要想实现自定义文档提供程序,请将以下内容添加到您的应用的清单文件: +

+
    + +
  • 一个 API 19 级或更高级别的目标;
  • + +
  • 一个声明自定义存储提供程序的 +<provider> 元素;
  • + +
  • 提供程序的名称(即其类名),包括软件包名称。例如:com.example.android.storageprovider.MyCloudProvider; +
  • + +
  • 权限的名称,即您的软件包名称(在本例中为 +com.example.android.storageprovider)加内容提供程序的类型 +(documents)。例如,{@code com.example.android.storageprovider.documents};
  • + +
  • 属性 android:exported 设置为 +"true"。您必须导出提供程序,以便其他应用可以看到;
  • + +
  • 属性 android:grantUriPermissions 设置为 +"true"。此设置允许系统向其他应用授予对提供程序中内容的访问权限。 +如需查看有关保留对特定文档授权的阐述,请参阅保留权限; +
  • + +
  • {@code MANAGE_DOCUMENTS} 权限。默认情况下,提供程序对所有人可用。 +添加此权限将限定您的提供程序只能供系统使用。此限制具有重要的安全意义; +
  • + +
  • {@code android:enabled} +属性设置为在资源文件中定义的一个布尔值。此属性的用途是,在运行 Android 4.3 +或更低版本的设备上禁用提供程序。例如,{@code android:enabled="@bool/atLeastKitKat"}。除了在清单文件中加入此属性外,您还需要执行以下操作; + +
      +
    • 在 {@code res/values/} 下的 {@code bool.xml} +资源文件中,添加以下行
      <bool name="atLeastKitKat">false</bool>
    • + +
    • 在 {@code res/values-v19/} 下的 {@code bool.xml} +资源文件中,添加以下行
      <bool name="atLeastKitKat">true</bool>
    • +
  • + +
  • 一个包括 +{@code android.content.action.DOCUMENTS_PROVIDER} 操作的 Intent +过滤器,以便在系统搜索提供程序时让您的提供程序出现在选取器中。
  • + +
+

以下是从一个包括提供程序的示例清单文件中摘录的内容:

+ +
<manifest... >
+    ...
+    <uses-sdk
+        android:minSdkVersion="19"
+        android:targetSdkVersion="19" />
+        ....
+        <provider
+            android:name="com.example.android.storageprovider.MyCloudProvider"
+            android:authorities="com.example.android.storageprovider.documents"
+            android:grantUriPermissions="true"
+            android:exported="true"
+            android:permission="android.permission.MANAGE_DOCUMENTS"
+            android:enabled="@bool/atLeastKitKat">
+            <intent-filter>
+                <action android:name="android.content.action.DOCUMENTS_PROVIDER" />
+            </intent-filter>
+        </provider>
+    </application>
+
+</manifest>
+ +

支持运行 Android 4.3 及更低版本的设备

+ +

{@link android.content.Intent#ACTION_OPEN_DOCUMENT} + Intent 仅可用于运行 +Android 4.4 及更高版本的设备。如果您想让应用支持 {@link android.content.Intent#ACTION_GET_CONTENT} +以适应运行 Android 4.3 +及更低版本的设备,则应在您的清单文件中为运行 Android 4.4 +或更高版本的设备禁用 {@link android.content.Intent#ACTION_GET_CONTENT} Intent +过滤器。应将文档提供程序和 +{@link android.content.Intent#ACTION_GET_CONTENT} +视为具有互斥性。如果您同时支持这两者,您的应用将在系统选取器 +UI +中出现两次,提供两种不同的方式来访问您存储的数据。这会给用户造成困惑。

+ +

建议按照以下步骤为运行 Android 4.4 版或更高版本的设备禁用 +{@link android.content.Intent#ACTION_GET_CONTENT} Intent +过滤器:

+ +
    +
  1. 在 {@code res/values/} 下的 {@code bool.xml} +资源文件中,添加以下行
    <bool name="atMostJellyBeanMR2">true</bool>
  2. + +
  3. 在 {@code res/values-v19/} 下的 {@code bool.xml} +资源文件中,添加以下行
    <bool name="atMostJellyBeanMR2">false</bool>
  4. + +
  5. 添加一个Activity别名,为 +4.4 版(API 19 +级)或更高版本禁用 {@link android.content.Intent#ACTION_GET_CONTENT} Intent +过滤器。例如: + +
    +<!-- This activity alias is added so that GET_CONTENT intent-filter
    +     can be disabled for builds on API level 19 and higher. -->
    +<activity-alias android:name="com.android.example.app.MyPicker"
    +        android:targetActivity="com.android.example.app.MyActivity"
    +        ...
    +        android:enabled="@bool/atMostJellyBeanMR2">
    +    <intent-filter>
    +        <action android:name="android.intent.action.GET_CONTENT" />
    +        <category android:name="android.intent.category.OPENABLE" />
    +        <category android:name="android.intent.category.DEFAULT" />
    +        <data android:mimeType="image/*" />
    +        <data android:mimeType="video/*" />
    +    </intent-filter>
    +</activity-alias>
    +
    +
  6. +
+

协定类

+ +

通常,当您编写自定义内容提供程序时,其中一项任务是实现协定类,如内容提供程序开发者指南中所述。 + + +协定类是一种 {@code public final} 类,它包含对 +URI、列名称、MIME +类型以及其他与提供程序有关的元数据的常量定义。SAF +会为您提供这些协定类,因此您无需自行编写: +

+ +
    +
  • {@link android.provider.DocumentsContract.Document}
  • +
  • {@link android.provider.DocumentsContract.Root}
  • +
+ +

例如,当系统在您的文档提供程序中查询文档或根目录时,您可能会在游标中返回以下列: +

+ +
private static final String[] DEFAULT_ROOT_PROJECTION =
+        new String[]{Root.COLUMN_ROOT_ID, Root.COLUMN_MIME_TYPES,
+        Root.COLUMN_FLAGS, Root.COLUMN_ICON, Root.COLUMN_TITLE,
+        Root.COLUMN_SUMMARY, Root.COLUMN_DOCUMENT_ID,
+        Root.COLUMN_AVAILABLE_BYTES,};
+private static final String[] DEFAULT_DOCUMENT_PROJECTION = new
+        String[]{Document.COLUMN_DOCUMENT_ID, Document.COLUMN_MIME_TYPE,
+        Document.COLUMN_DISPLAY_NAME, Document.COLUMN_LAST_MODIFIED,
+        Document.COLUMN_FLAGS, Document.COLUMN_SIZE,};
+
+ +

为 DocumentsProvider 创建子类

+ +

编写自定义文档提供程序的下一步是为抽象类 +{@link android.provider.DocumentsProvider} 创建子类。您至少需要实现以下方法: +

+ +
    +
  • {@link android.provider.DocumentsProvider#queryRoots queryRoots()}
  • + +
  • {@link android.provider.DocumentsProvider#queryChildDocuments queryChildDocuments()}
  • + +
  • {@link android.provider.DocumentsProvider#queryDocument queryDocument()}
  • + +
  • {@link android.provider.DocumentsProvider#openDocument openDocument()}
  • +
+ +

这些只是您需要严格实现的方法,但您可能还想实现许多其他方法。 +详情请参阅 +{@link android.provider.DocumentsProvider}。

+ +

实现 queryRoots

+ +

您实现的 +{@link android.provider.DocumentsProvider#queryRoots +queryRoots()} 必须使用在 +{@link android.provider.DocumentsContract.Root} 中定义的列返回一个指向文档提供程序所有根目录的 {@link android.database.Cursor}。

+ +

在以下代码段中,{@code projection} +参数表示调用方想要返回的特定字段。代码段会创建一个新游标,并为其添加一行—一个根目录,如 +Downloads 或 Images +等顶层目录。大多数提供程序只有一个根目录。有时您可能有多个根目录,例如,当您具有多个用户帐户时。 +在这种情况下,只需再为游标添加一行。 +

+ +
+@Override
+public Cursor queryRoots(String[] projection) throws FileNotFoundException {
+
+    // Create a cursor with either the requested fields, or the default
+    // projection if "projection" is null.
+    final MatrixCursor result =
+            new MatrixCursor(resolveRootProjection(projection));
+
+    // If user is not logged in, return an empty root cursor.  This removes our
+    // provider from the list entirely.
+    if (!isUserLoggedIn()) {
+        return result;
+    }
+
+    // It's possible to have multiple roots (e.g. for multiple accounts in the
+    // same app) -- just add multiple cursor rows.
+    // Construct one row for a root called "MyCloud".
+    final MatrixCursor.RowBuilder row = result.newRow();
+    row.add(Root.COLUMN_ROOT_ID, ROOT);
+    row.add(Root.COLUMN_SUMMARY, getContext().getString(R.string.root_summary));
+
+    // FLAG_SUPPORTS_CREATE means at least one directory under the root supports
+    // creating documents. FLAG_SUPPORTS_RECENTS means your application's most
+    // recently used documents will show up in the "Recents" category.
+    // FLAG_SUPPORTS_SEARCH allows users to search all documents the application
+    // shares.
+    row.add(Root.COLUMN_FLAGS, Root.FLAG_SUPPORTS_CREATE |
+            Root.FLAG_SUPPORTS_RECENTS |
+            Root.FLAG_SUPPORTS_SEARCH);
+
+    // COLUMN_TITLE is the root title (e.g. Gallery, Drive).
+    row.add(Root.COLUMN_TITLE, getContext().getString(R.string.title));
+
+    // This document id cannot change once it's shared.
+    row.add(Root.COLUMN_DOCUMENT_ID, getDocIdForFile(mBaseDir));
+
+    // The child MIME types are used to filter the roots and only present to the
+    //  user roots that contain the desired type somewhere in their file hierarchy.
+    row.add(Root.COLUMN_MIME_TYPES, getChildMimeTypes(mBaseDir));
+    row.add(Root.COLUMN_AVAILABLE_BYTES, mBaseDir.getFreeSpace());
+    row.add(Root.COLUMN_ICON, R.drawable.ic_launcher);
+
+    return result;
+}
+ +

实现 queryChildDocuments

+ +

您实现的 +{@link android.provider.DocumentsProvider#queryChildDocuments queryChildDocuments()} 必须使用在 +{@link android.provider.DocumentsContract.Document} +中定义的列返回一个指向指定目录中所有文件的 +{@link android.database.Cursor}。

+ +

当您在选取器 UI +中选择应用时,系统会调用此方法。它会获取根目录下某个目录内的子文档。可以在文件层次结构的任何级别调用此方法,并非只能从根目录调用。 +以下代码段可创建一个包含所请求列的新游标,然后向游标添加父目录中每个直接子目录的相关信息。子目录可以是图像、另一个目录乃至任何文件: + + +

+ +
@Override
+public Cursor queryChildDocuments(String parentDocumentId, String[] projection,
+                              String sortOrder) throws FileNotFoundException {
+
+    final MatrixCursor result = new
+            MatrixCursor(resolveDocumentProjection(projection));
+    final File parent = getFileForDocId(parentDocumentId);
+    for (File file : parent.listFiles()) {
+        // Adds the file's display name, MIME type, size, and so on.
+        includeFile(result, null, file);
+    }
+    return result;
+}
+
+ +

实现 queryDocument

+ +

您实现的 +{@link android.provider.DocumentsProvider#queryDocument queryDocument()} +必须使用在 {@link android.provider.DocumentsContract.Document} 中定义的列返回一个指向指定文件的 +{@link android.database.Cursor}。 +

+ +

除了特定文件的信息外,{@link android.provider.DocumentsProvider#queryDocument queryDocument()} +方法返回的信息与 +{@link android.provider.DocumentsProvider#queryChildDocuments queryChildDocuments()} +中传递的信息相同:

+ + +
@Override
+public Cursor queryDocument(String documentId, String[] projection) throws
+        FileNotFoundException {
+
+    // Create a cursor with the requested projection, or the default projection.
+    final MatrixCursor result = new
+            MatrixCursor(resolveDocumentProjection(projection));
+    includeFile(result, documentId, null);
+    return result;
+}
+
+ +

实现 queryDocument

+ +

您必须实现 {@link android.provider.DocumentsProvider#openDocument +openDocument()} 以返回表示指定文件的 +{@link android.os.ParcelFileDescriptor}。其他应用可以使用返回的 {@link android.os.ParcelFileDescriptor} +来流式传输数据。用户选择了文件,并且客户端应用通过调用 +{@link android.content.ContentResolver#openFileDescriptor openFileDescriptor()} +来请求对文件的访问权限,系统便会调用此方法。例如: +

+ +
@Override
+public ParcelFileDescriptor openDocument(final String documentId,
+                                         final String mode,
+                                         CancellationSignal signal) throws
+        FileNotFoundException {
+    Log.v(TAG, "openDocument, mode: " + mode);
+    // It's OK to do network operations in this method to download the document,
+    // as long as you periodically check the CancellationSignal. If you have an
+    // extremely large file to transfer from the network, a better solution may
+    // be pipes or sockets (see ParcelFileDescriptor for helper methods).
+
+    final File file = getFileForDocId(documentId);
+
+    final boolean isWrite = (mode.indexOf('w') != -1);
+    if(isWrite) {
+        // Attach a close listener if the document is opened in write mode.
+        try {
+            Handler handler = new Handler(getContext().getMainLooper());
+            return ParcelFileDescriptor.open(file, accessMode, handler,
+                        new ParcelFileDescriptor.OnCloseListener() {
+                @Override
+                public void onClose(IOException e) {
+
+                    // Update the file with the cloud server. The client is done
+                    // writing.
+                    Log.i(TAG, "A file with id " +
+                    documentId + " has been closed!
+                    Time to " +
+                    "update the server.");
+                }
+
+            });
+        } catch (IOException e) {
+            throw new FileNotFoundException("Failed to open document with id "
+            + documentId + " and mode " + mode);
+        }
+    } else {
+        return ParcelFileDescriptor.open(file, accessMode);
+    }
+}
+
+ +

安全性

+ +

假设您的文档提供程序是受密码保护的云存储服务,并且您想在开始共享用户的文件之前确保其已登录。如果用户未登录,您的应用应该执行哪些操作呢? + +解决方案是在您实现的 +{@link android.provider.DocumentsProvider#queryRoots +queryRoots()} 中返回零个根目录。也就是空的根目录游标:

+ +
+public Cursor queryRoots(String[] projection) throws FileNotFoundException {
+...
+    // If user is not logged in, return an empty root cursor.  This removes our
+    // provider from the list entirely.
+    if (!isUserLoggedIn()) {
+        return result;
+}
+
+ +

另一个步骤是调用 +{@code getContentResolver().notifyChange()}。还记得 {@link android.provider.DocumentsContract} 吗?我们将使用它来创建此 +URI。以下代码段会在每次用户的登录状态发生变化时指示系统查询文档提供程序的根目录。 +如果用户未登录,则调用 +{@link android.provider.DocumentsProvider#queryRoots queryRoots()} +会返回一个空游标,如上文所示。这可以确保只有在用户登录提供程序后其中的文档才可用。 +

+ +
private void onLoginButtonClick() {
+    loginOrLogout();
+    getContentResolver().notifyChange(DocumentsContract
+            .buildRootsUri(AUTHORITY), null);
+}
+
\ No newline at end of file diff --git a/docs/html-intl/intl/zh-cn/guide/topics/resources/accessing-resources.jd b/docs/html-intl/intl/zh-cn/guide/topics/resources/accessing-resources.jd new file mode 100644 index 0000000000000000000000000000000000000000..e8e583a7307f4389705b03f64476d8941f095736 --- /dev/null +++ b/docs/html-intl/intl/zh-cn/guide/topics/resources/accessing-resources.jd @@ -0,0 +1,337 @@ +page.title=访问资源 +parent.title=应用资源 +parent.link=index.html +@jd:body + +
+
+

内容快览

+
    +
  • 可以使用 +{@code R.drawable.myimage} 等来自 {@code R.java} 的整型数在代码中引用资源
  • +
  • 可以使用 {@code +@drawable/myimage} 等特殊 XML 语法在资源中引用资源
  • +
  • 您还可以通过 +{@link android.content.res.Resources} 中的方法访问您的应用资源
  • +
+ +

关键类

+
    +
  1. {@link android.content.res.Resources}
  2. +
+ +

本文内容

+
    +
  1. 在代码中访问资源
  2. +
  3. 在 XML 中访问资源 +
      +
    1. 引用样式属性
    2. +
    +
  4. +
  5. 访问平台资源
  6. +
+ +

另请参阅

+
    +
  1. 提供资源
  2. +
  3. 资源类型
  4. +
+
+
+ + + + +

您在应用中提供资源后(提供资源中对此做了阐述),可通过引用其资源 ID 来应用该资源。所有资源 ID 都在您项目的 {@code R} 类中定义,后者由 {@code aapt} 工具自动生成。 + +

+ +

编译应用时,{@code aapt} 会生成 {@code R} 类,其中包含您的 {@code +res/} 目录中所有资源的资源 ID。 +每个资源类型都有对应的 {@code R} +子类(例如,{@code R.drawable} +对应于所有 Drawable 资源),而该类型的每个资源都有对应的静态整型数(例如,{@code R.drawable.icon})。这个整型数就是可用来检索资源的资源 +ID。

+ +

尽管资源 ID 是在 {@code R} 类中指定的,但您应该永远都不需要在其中查找资源 +ID。资源 ID 始终由以下部分组成:

+
    +
  • 资源类型:每个资源都被分到一个“类型”组中,例如 {@code +string}、{@code drawable} 和 {@code layout}。如需了解有关不同类型的详细信息,请参阅资源类型。 +
  • +
  • 资源名称,它是不包括扩展名的文件名;或是 XML +{@code android:name} +属性中的值,如果资源是简单值的话(例如字符串)。
  • +
+ +

访问资源的方法有两种:

+
    +
  • 在代码中:使用来自 {@code R} +类的某个子类的静态整型数,例如: +
    R.string.hello
    +

    {@code string} 是资源类型,{@code hello} 是资源名称。当您提供此格式的资源 ID 时,有许多 +Android API 可以访问您的资源。请参阅在代码中访问资源。 +

    +
  • +
  • 在 XML 中:使用同样与您 +{@code R} 类中定义的资源 ID 对应的特殊 XML 语法,例如: +
    @string/hello
    +

    {@code string} 是资源类型,{@code hello} 是资源名称。您可以在 XML +资源中任何应该存在您在资源中所提供值的地方使用此语法。请参阅在 XML 中访问资源

    +
  • +
+ + + +

在代码中访问资源

+ +

您可以通过以方法参数的形式传递资源 ID,在代码中使用资源。例如,您可以设置一个 +{@link android.widget.ImageView},以利用 {@link android.widget.ImageView#setImageResource(int) setImageResource()} 使用 {@code res/drawable/myimage.png} +资源:

+
+ImageView imageView = (ImageView) findViewById(R.id.myimageview);
+imageView.setImageResource(R.drawable.myimage);
+
+ +

您还可以利用 {@link +android.content.res.Resources} 中的方法检索个别资源,您可以通过 {@link android.content.Context#getResources()} +获得资源实例。

+ + + + +

语法

+ +

以下是在代码中引用资源的语法:

+ +
+[<package_name>.]R.<resource_type>.<resource_name>
+
+ +
    +
  • {@code <package_name>} +是资源所在包的名称(如果引用的资源来自您自己的资源包,则不需要)。
  • +
  • {@code <resource_type>} 是资源类型的 {@code R} 子类。
  • +
  • {@code <resource_name>} +是不带扩展名的资源文件名,或 XML 元素中的 {@code android:name} +属性值(如果资源是简单值)。
  • +
+

如需了解有关各资源类型及其引用方法的详细信息,请参阅资源类型。 +

+ + +

用例

+ +

有许多方法接受资源 ID 参数,您可以利用 {@link android.content.res.Resources} +中的方法检索资源。您可以通过 {@link android.content.Context#getResources +Context.getResources()} 获得 {@link +android.content.res.Resources} 的实例。

+ + +

以下是一些在代码中访问资源的示例:

+ +
+// Load a background for the current screen from a drawable resource
+{@link android.app.Activity#getWindow()}.{@link
+android.view.Window#setBackgroundDrawableResource(int)
+setBackgroundDrawableResource}(R.drawable.my_background_image) ;
+
+// Set the Activity title by getting a string from the Resources object, because
+//  this method requires a CharSequence rather than a resource ID
+{@link android.app.Activity#getWindow()}.{@link android.view.Window#setTitle(CharSequence)
+setTitle}(getResources().{@link android.content.res.Resources#getText(int)
+getText}(R.string.main_title));
+
+// Load a custom layout for the current screen
+{@link android.app.Activity#setContentView(int)
+setContentView}(R.layout.main_screen);
+
+// Set a slide in animation by getting an Animation from the Resources object
+mFlipper.{@link android.widget.ViewAnimator#setInAnimation(Animation)
+setInAnimation}(AnimationUtils.loadAnimation(this,
+        R.anim.hyperspace_in));
+
+// Set the text on a TextView object using a resource ID
+TextView msgTextView = (TextView) findViewById(R.id.msg);
+msgTextView.{@link android.widget.TextView#setText(int)
+setText}(R.string.hello_message);
+
+ + +

注意:切勿手动修改 {@code +R.java} 文件 — 它是在编译您的项目时由 {@code aapt} +工具生成的。您下次编译时所有更改都会被替代。

+ + + +

在 XML 中访问资源

+ +

您可以利用对现有资源的引用为某些 XML +属性和元素定义值。创建布局文件时,为给您的小工具提供字符串和图像,您经常要这样做。 +

+ +

例如,如果您为布局添加一个 +{@link android.widget.Button},应该为按钮文本使用字符串资源

+ +
+<Button
+    android:layout_width="fill_parent"
+    android:layout_height="wrap_content"
+    android:text="@string/submit" />
+
+ + +

语法

+ +

以下是在 XML 资源中引用资源的语法:

+ +
+@[<package_name>:]<resource_type>/<resource_name>
+
+ +
    +
  • {@code <package_name>} +是资源所在包的名称(如果引用的资源来自同一包,则不需要)
  • +
  • {@code <resource_type>} 是资源类型的 +{@code R} 子类
  • +
  • {@code <resource_name>} +是不带扩展名的资源文件名,或 XML 元素中的 {@code android:name} +属性值(如果资源是简单值)。
  • +
+ +

如需了解有关各资源类型及其引用方法的详细信息,请参阅资源类型。 +

+ + +

用例

+ +

在某些情况下,您必须使用资源作为 XML +中的值(例如,对小工具应用可绘制图像),但您也可以在 XML 中任何接受简单值的地方使用资源。例如,如果您具有以下资源文件,其中包括一个颜色资源和一个字符串资源: +

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+   <color name="opaque_red">#f00</color>
+   <string name="hello">Hello!</string>
+</resources>
+
+ +

您可以在以下布局文件中使用这些资源来设置文本颜色和文本字符串: +

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<EditText xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    android:textColor="@color/opaque_red"
+    android:text="@string/hello" />
+
+ +

在此情况下,您无需在资源引用中指定包名称,因为资源来自您自己的资源包。 +要引用系统资源,您需要加入包名称。 +例如:

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<EditText xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    android:textColor="@android:color/secondary_text_dark"
+    android:text="@string/hello" />
+
+ +

注:您应该始终使用字符串 +资源,以便将您的应用本地化为其他语言。如需了解有关创建备用资源(例如本地化字符串)的信息,请参阅提供备用资源。 + + +如需查看将您的应用本地化为其他语言的完整指南,请参阅本地化。 +

+ +

您甚至可以在 XML 中使用资源创建别名。例如,您可以创建一个 Drawable 资源,将其作为另一个 Drawable 资源的别名: +

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
+    android:src="@drawable/other_drawable" />
+
+ +

这听起来多余,但对使用备用资源可能很有帮助。阅读更多关于创建别名资源的内容。 +

+ + + +

引用样式属性

+ +

您可以通过样式属性资源在当前应用的风格主题中引用某个属性的值。 +通过引用样式属性,您可以不采用为 UI +元素提供硬编码值这种方式,而是通过为 UI +元素设置样式,使其匹配当前风格主题提供的标准变型来定制这些元素的外观。引用样式属性的实质作用是,“在当前风格主题中使用此属性定义的样式”。 +

+ +

要引用样式属性,名称语法几乎与普通资源格式完全相同,只不过将 at 符号 ({@code @}) 改为问号 ({@code ?}),资源类型部分为可选项。 + +例如:

+ +
+?[<package_name>:][<resource_type>/]<resource_name>
+
+ +

例如,您可以通过以下代码引用一个属性,将文本颜色设置为与系统风格主题的“主要”文本颜色匹配: +

+ +
+<EditText id="text"
+    android:layout_width="fill_parent"
+    android:layout_height="wrap_content"
+    android:textColor="?android:textColorSecondary"
+    android:text="@string/hello_world" />
+
+ +

在以上代码中,{@code android:textColor} +属性表示当前风格主题中某个样式属性的名称。Android 现在会使用应用于 {@code android:textColorSecondary} +样式属性的值作为 {@code android:textColor} 在这个小工具中的值。由于系统资源工具知道此环境中肯定存在某个属性资源,因此您无需显式声明类型(类型应为 +?android:attr/textColorSecondary)— 您可以将 +{@code attr} +类型排除在外。

+ + + + +

访问平台资源

+ +

Android 包含许多标准资源,例如样式、风格主题和布局。要访问这些资源,请通过 +android +包名称限定您的资源引用。例如,您可以将 Android 提供的布局资源用于 +{@link android.widget.ListAdapter} 中的列表项:

+ +
+{@link android.app.ListActivity#setListAdapter(ListAdapter)
+setListAdapter}(new {@link
+android.widget.ArrayAdapter}<String>(this, android.R.layout.simple_list_item_1, myarray));
+
+ +

在上例中,{@link android.R.layout#simple_list_item_1} +是平台为 {@link android.widget.ListView} 中的项目定义的布局资源。您可以使用它,而不必自行创建列表项布局。 +如需了解详细信息,请参阅列表视图开发指南。 +

+ diff --git a/docs/html-intl/intl/zh-cn/guide/topics/resources/overview.jd b/docs/html-intl/intl/zh-cn/guide/topics/resources/overview.jd new file mode 100644 index 0000000000000000000000000000000000000000..21dbe6ff37a932704fc7a34ea62a803e27e45d59 --- /dev/null +++ b/docs/html-intl/intl/zh-cn/guide/topics/resources/overview.jd @@ -0,0 +1,103 @@ +page.title=资源概览 +@jd:body + +
+
+

主题

+
    +
  1. 提供资源
  2. +
  3. 访问资源
  4. +
  5. 处理运行时变更
  6. +
  7. 本地化
  8. +
+ +

参考文档

+
    +
  1. 资源类型
  2. +
+
+
+ + +

您应该始终外部化资源,例如图像和应用代码中的字符串,这样有利于您单独维护这些资源。 +此外,通过外部化资源,您还可以提供支持特定设备配置(例如,不同的语言或屏幕尺寸)的备用资源,随着采用 +Android + 技术且配置各异的设备越来越多,这些资源的重要性也日益增加。 +为了提供与不同配置的兼容性,您必须使用各种按类型和配置对资源进行分组的子目录,对项目 +{@code res/} +目录中的资源加以组织。 +

+ +
+ +

+图 1. 两种不同的设备,均使用默认布局(应用不提供备用布局)。 +

+
+ +
+ +

+图 2. 两种不同的设备,分别使用针对不同屏幕尺寸提供的不同布局。 +

+
+ +

对于任意类型的资源,您均可以为应用指定默认资源和多个备用资源: +

+
    +
  • 默认资源系指无论设备配置如何,或者在没有备用资源与当前配置相匹配时,均应使用的资源。 + +
  • +
  • 备用资源系指设计用于特定配置的资源。 +要指明某组资源适用于特定配置,请将相应的配置限定符追加到目录名称。 +
  • +
+ +

例如,尽管默认 UI +布局保存在 +{@code res/layout/} 目录中,但是您可以指定在屏幕处于横向时要使用的不同布局,方法是将其保存在 {@code res/layout-land/} +目录中。Android +可以通过将设备的当前配置与资源目录名称进行匹配,自动应用合适的资源。

+ +

图 1 +说明了在没有备用资源可用时,系统如何为两种不同的设备应用相同布局。图 2 +显示的是同一应用针对大屏幕添加了备用布局资源。

+ +

以下文档提供了有关如何组织应用资源、如何指定备用资源以及如何在应用中访问这些资源等的完整指南: +

+ +
+
提供资源
+
您可在应用中提供何种资源、可将这些资源保存在何处以及如何为特定设备配置创建备用资源。 +
+
访问资源
+
如何通过从应用代码或其他 +XML 资源中引用来使用所提供的资源
+
处理运行时变更
+
如何管理在 Activity 运行时发生的配置变更。
+
本地化
+
从点到面地指导您使用备用资源本地化应用。尽管这只是备用资源的一种具体运用,但是这对于赢得更多用户非常重要。 + +
+
资源类型
+
有关您可提供的各种资源类型的参考文档,其中描述了这些资源的 +XML 元素、属性和语法。例如,此参考文档向您展示了如何为应用菜单、可绘制对象、动画等创建资源。 +
+
+ + diff --git a/docs/html-intl/intl/zh-cn/guide/topics/resources/providing-resources.jd b/docs/html-intl/intl/zh-cn/guide/topics/resources/providing-resources.jd new file mode 100644 index 0000000000000000000000000000000000000000..ea46d86152a4a854ff1319302ab4105a8d7363bc --- /dev/null +++ b/docs/html-intl/intl/zh-cn/guide/topics/resources/providing-resources.jd @@ -0,0 +1,1094 @@ +page.title=提供资源 +parent.title=应用资源 +parent.link=index.html +@jd:body + +
+
+

内容快览

+
    +
  • 不同类型的资源属于 {@code res/} 的不同子目录
  • +
  • 备用资源提供特定于配置的资源文件
  • +
  • 始终包含默认资源,使您的应用不依赖于特定的设备配置 +
  • +
+

本文内容

+
    +
  1. 分组资源类型
  2. +
  3. 提供备用资源 +
      +
    1. 限定符命名规则
    2. +
    3. 创建别名资源
    4. +
    +
  4. +
  5. 利用资源提供最佳设备兼容性
  6. +
  7. Android 如何找到最匹配资源
  8. +
+ +

另请参阅

+
    +
  1. 访问资源
  2. +
  3. 资源类型
  4. +
  5. 支持多个屏幕 +
  6. +
+
+
+ +

您应该始终外部化应用资源,例如图像和代码中的字符串,这样有利于您单独维护这些资源。 +此外,您还应该为特定设备配置提供备用资源,方法是将它们分组到专门命名的资源目录中。 +在运行时,Android +会根据当前配置使用适当的资源。例如,您可能需要根据屏幕尺寸提供不同的 +UI +布局,或者根据语言设置提供不同的字符串。

+ +

外部化应用资源后,即可使用在项目 {@code R} 类中生成的资源 +ID 访问这些资源。有关如何在应用中使用资源,我们将在访问资源中讨论。 + +本文档介绍如何对 Android +项目中的资源进行分组,以及如何为特定的设备配置提供备用资源。

+ + +

分组资源类型

+ +

您应将各种资源放入项目 +{@code res/} 目录的特定子目录下。例如,以下是一个简单项目的文件层次结构:

+ +
+MyProject/
+    src/  
+        MyActivity.java  
+    res/
+        drawable/  
+            graphic.png  
+        layout/  
+            main.xml
+            info.xml
+        mipmap/  
+            icon.png 
+        values/  
+            strings.xml  
+
+ +

正如您在此示例中所看到的那样,{@code res/} 目录包含所有资源(在子目录下):一个图像资源、两个布局资源、启动器图标的 +{@code mipmap/} +目录以及一个字符串资源文件。资源目录名称非常重要,将在表 1 +中进行介绍。

+ +

注:如需了解有关使用 mipmap +文件夹的详细信息,请参阅管理项目概览

+ +

表 1. 项目 {@code res/} +目录内支持的资源目录

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
目录资源类型
animator/用于定义属性动画的 XML +文件。
anim/定义渐变动画的 XML +文件。(属性动画也可以保存在此目录中,但是为了区分这两种类型,属性动画首选 +{@code animator/} +目录。)
color/用于定义颜色状态列表的 XML 文件。请参阅颜色状态列表资源 +
drawable/

位图文件({@code .png}、{@code .9.png}、{@code .jpg}、{@code .gif})或编译为以下 Drawable 资源子类型的 +XML 文件:

+
    +
  • 位图文件
  • +
  • 九宫格(可调整大小的位图)
  • +
  • 状态列表
  • +
  • 形状
  • +
  • 动画 Drawable
  • +
  • 其他 Drawable
  • +
+

请参阅 Drawable 资源

+
mipmap/适用于不同启动器图标密度的 Drawable 文件。如需了解有关使用 {@code mipmap/} 文件夹管理启动器图标的详细信息,请参阅管理项目概览。 + +
layout/用于定义用户界面布局的 XML 文件。 + 请参阅布局资源
menu/用于定义应用菜单(如选项菜单、上下文菜单或子菜单)的 XML +文件。请参阅菜单资源
raw/

要以原始形式保存的任意文件。要使用原始 +{@link java.io.InputStream} 打开这些资源,请使用资源 ID(即 {@code R.raw.filename})调用 {@link android.content.res.Resources#openRawResource(int) +Resources.openRawResource()}。

+

但是,如需访问原始文件名和文件层次结构,则可以考虑将某些资源保存在 +{@code +assets/} 目录下(而不是 {@code res/raw/})。{@code assets/} 中的文件没有资源 ID,因此您只能使用 {@link android.content.res.AssetManager} 读取这些文件。 +

values/

包含字符串、整型数和颜色等简单值的 XML 文件。

+

其他 {@code res/} 子目录中的 XML +资源文件是根据 +XML +文件名定义单个资源,而目录中的 {@code values/} 文件可描述多个资源。对于此目录中的文件,{@code <resources>} 元素的每个子元素均定义一个资源。例如,{@code <string>} 元素创建 +{@code R.string} 资源,{@code <color>} 元素创建 {@code R.color} +资源。

+

由于每个资源均用其自己的 XML +元素定义,因此您可以根据自己的需要命名文件,并将不同的资源类型放在一个文件中。但是,为了清晰起见,您可能需要将独特的资源类型放在不同的文件中。 +例如,对于可在此目录中创建的资源,下面给出了相应的文件名约定: +

+ +

请参阅字符串资源样式资源更多资源类型。 + +

+
xml/可以在运行时通过调用 {@link +android.content.res.Resources#getXml(int) Resources.getXML()} 读取的任意 XML 文件。各种 XML +配置文件(如可搜索配置)都必须保存在此处。 +
+ +

注意:切勿将资源文件直接保存在 +{@code res/} 目录内,这会导致出现编译错误。

+ +

如需了解有关某些资源类型的详细信息,请参阅资源类型文档。

+ +

保存在表 1 +中定义的子目录下的资源是“默认”资源。即,这些资源定义应用的默认设计和内容。但是,采用 +Android +技术的不同设备类型可能需要不同类型的资源。例如,如果设备的屏幕尺寸大于标准屏幕,则应提供不同的布局资源,以充分利用额外的屏幕空间。 +或者,如果设备的语言设置不同,则应提供不同的字符串资源,以转换用户界面中的文本。 + +要为不同的设备配置提供这些不同资源,除了默认资源以外,您还需要提供备用资源。 + +

+ + +

提供备用资源

+ + +
+ +

+图 1. 两种不同的设备,均使用不同的布局资源。

+
+ +

几乎每个应用都应提供备用资源以支持特定的设备配置。 +例如,对于不同的屏幕密度和语言,您应分别包括备用 Drawable 资源和备用字符串资源。 +在运行时,Android +会检测当前设备配置并为应用加载合适的资源。 +

+ +

为一组资源指定特定于配置的备用资源:

+
    +
  1. 在 {@code res/} 中创建一个以 {@code +<resources_name>-<config_qualifier>} 形式命名的新目录。 +
      +
    • {@code <resources_name>} 是相应默认资源的目录名称(如表 1 +中所定义)。
    • +
    • {@code <qualifier>} 是指定要使用这些资源的各个配置的名称(如表 2 +中所定义)。
    • +
    +

    您可以追加多个 {@code <qualifier>}。以短划线将其分隔。 +

    +

    注意:追加多个限定符时,必须按照表 +2 中列出的相同顺序放置它们。如果限定符的顺序错误,则该资源将被忽略。 +

    +
  2. +
  3. 将相应的备用资源保存在此新目录下。这些资源文件的名称必须与默认资源文件完全一样。 +
  4. +
+ +

例如,以下是一些默认资源和备用资源:

+ +
+res/
+    drawable/   
+        icon.png
+        background.png    
+    drawable-hdpi/  
+        icon.png
+        background.png  
+
+ +

{@code hdpi} +限定符表示该目录中的资源适用于屏幕密度较高的设备。其中每个 Drawable 目录中的图像已针对特定的屏幕密度调整大小,但是文件名完全相同。 + +这样一来,用于引用 {@code icon.png} 或 {@code +background.png} +图像的资源 ID 始终相同,但是 Android +会通过将设备配置信息与资源目录名称中的限定符进行比较,选择最符合当前设备的各个资源版本。

+ +

Android +支持若干配置限定符,您可以通过使用短划线分隔每个限定符,向一个目录名称添加多个限定符。表 2 +按优先顺序列出了有效的配置限定符;如果对资源目录使用多个限定符,则必须按照表中列出的顺序将它们添加到目录名称。 + +

+ + +

表 2. 配置限定符名称。 +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
配置限定符值描述
MCC 和 MNC示例:
+ mcc310
+ mcc310-mnc004
+ mcc208-mnc00
+ 等等 +
+

移动国家代码 (MCC),(可选)后跟设备 SIM +卡中的移动网络代码 (MNC)。例如,mcc310 是指美国的任一运营商,mcc310-mnc004 是指美国的 Verizon +公司,mcc208-mnc00 是指法国的 Orange +公司。

+

如果设备使用无线电连接(GSM 手机),则 MCC 和 MNC 值来自 SIM +卡。

+

也可以单独使用 +MCC(例如,将国家/地区特定的合法资源包括在应用中)。如果只需根据语言指定,则改用“语言和区域”限定符(稍后进行介绍)。 +如果决定使用 MCC 和 +MNC 限定符,请谨慎执行此操作并测试限定符是否按预期工作。

+

另请参阅配置字段 +{@link +android.content.res.Configuration#mcc} 和 {@link +android.content.res.Configuration#mnc},这两个字段分别表示当前的移动国家代码和移动网络代码。

+
语言和区域示例:
+ en
+ fr
+ en-rUS
+ fr-rFR
+ fr-rCA
+ 等等 +

语言通过由两个字母组成的 ISO +639-1 语言代码定义,(可选)后跟两个字母组成的 +ISO +3166-1-alpha-2 区域码(前带小写字母“{@code r}”)。 +

+ 这些代码不区分大小写;{@code r} +前缀用于区分区域码。 + 不能单独指定区域。

+

如果用户更改系统设置中的语言,它有可能在应用生命周期中发生改变。 +如需了解这会在运行期间给应用带来哪些影响,请参阅处理运行时变更。 +

+

有关针对其他语言本地化应用的完整指南,请参阅本地化。 +

+

另请参阅 {@link android.content.res.Configuration#locale} 配置字段,该字段表示当前的区域设置。 +

+
布局方向ldrtl
+ ldltr
+

应用的布局方向。{@code ldrtl} 是指“布局方向从右到左”。{@code ldltr} +是指“布局方向从左到右”,这是默认的隐式值。 +

+

它适用于布局、图片或值等任何资源。 +

+

例如,若要针对阿拉伯语提供某种特定布局,并针对任何其他“从右到左”语言(如波斯语或希伯来语)提供某种通用布局,则可编码如下: + +

+
+res/
+    layout/   
+        main.xml  (Default layout)
+    layout-ar/  
+        main.xml  (Specific layout for Arabic)
+    layout-ldrtl/  
+        main.xml  (Any "right-to-left" language, except
+                  for Arabic, because the "ar" language qualifier
+                  has a higher precedence.)
+
+

注:要为应用启用从右到左的布局功能,必须将 {@code +supportsRtl} 设置为 +{@code "true"},并将 {@code targetSdkVersion} 设置为 17 或更高。

+

此项为API 级别 17 中新增配置。

+
smallestWidthsw<N>dp

+ 示例:
+ sw320dp
+ sw600dp
+ sw720dp
+ 等等 +
+

屏幕的基本尺寸,由可用屏幕区域的最小尺寸指定。 +具体来说,设备的 smallestWidth +是屏幕可用高度和宽度的最小尺寸(您也可以将其视为屏幕的“最小可能宽度”)。无论屏幕的当前方向如何,您均可使用此限定符确保应用 +UI 的可用宽度至少为 +{@code <N>}dp。

+

例如,如果布局要求屏幕区域的最小尺寸始终至少为 +600dp,则可使用此限定符创建布局资源 {@code +res/layout-sw600dp/}。仅当可用屏幕的最小尺寸至少为 +600dp 时,系统才会使用这些资源,而不考虑 +600dp 所代表的边是用户所认为的高度还是宽度。smallestWidth 是设备的固定屏幕尺寸特性;设备的 smallestWidth +不会随屏幕方向的变化而改变

+

设备的 smallestWidth 将屏幕装饰元素和系统 UI +考虑在内。例如,如果设备的屏幕上有一些永久性 UI 元素占据沿 +smallestWidth +轴的空间,则系统会声明 smallestWidth +小于实际屏幕尺寸,因为这些屏幕像素不适用于您的 +UI。因此,使用的值应该是布局所需要的实际最小尺寸(通常,无论屏幕的当前方向如何,此值都是布局支持的“最小宽度”)。

+

以下是一些可用于普通屏幕尺寸的值:

+
    +
  • 320,适用于屏幕配置如下的设备: +
      +
    • 240x320 ldpi(QVGA 手机)
    • +
    • 320x480 mdpi(手机)
    • +
    • 480x800 hdpi(高密度手机)
    • +
    +
  • +
  • 480,适用于 480x800 mdpi 之类的屏幕(平板电脑/手机)。
  • +
  • 600,适用于 600x1024 mdpi 之类的屏幕(7 英寸平板电脑)。
  • +
  • 720,适用于 720x1280 mdpi 之类的屏幕(10 英寸平板电脑)。
  • +
+

应用为多个资源目录提供不同的 +smallestWidth 限定符值时,系统会使用最接近(但未超出)设备 +smallestWidth 的值。

+

此项为 API 级别 13 中新增配置。

+

另请参阅 {@code +android:requiresSmallestWidthDp} 属性和 {@link +android.content.res.Configuration#smallestScreenWidthDp} 配置字段,前者声明与应用兼容的最小 +smallestWidth;后者存放设备的 +smallestWidth 值。

+

如需了解有关设计不同屏幕和使用此限定符的详细信息,请参阅支持多个屏幕开发者指南。 + +

+
可用宽度w<N>dp

+ 示例:
+ w720dp
+ w1024dp
+ 等等 +
+

指定资源应该使用的最小可用屏幕宽度,以 {@code dp} +为单位,由 <N> 值定义。在横向和纵向之间切换时,为了匹配当前实际宽度,此配置值也会随之发生变化。 + +

+

应用为多个资源目录提供不同的此配置值时,系统会使用最接近(但未超出)设备当前屏幕宽度的值。 + +此处的值考虑到了屏幕装饰元素,因此如果设备显示屏的左边缘或右边缘上有一些永久性 UI +元素,考虑到这些 UI +元素,它会使用小于实际屏幕尺寸的宽度值,这样会减少应用的可用空间。 + +

+

此项为 API 级别 13 中新增配置。

+

另请参阅 +{@link android.content.res.Configuration#screenWidthDp} 配置字段,该字段存放当前屏幕宽度。

+

如需了解有关设计不同屏幕和使用此限定符的详细信息,请参阅支持多个屏幕开发者指南。 + +

+
可用高度h<N>dp

+ 示例:
+ h720dp
+ h1024dp
+ 等等 +
+

指定资源应该使用的最小可用屏幕高度,以“dp”为单位,由 <N> 值定义。 +在横向和纵向之间切换时,为了匹配当前实际高度,此配置值也会随之发生变化。 + +

+

应用为多个资源目录提供不同的此配置值时,系统会使用最接近(但未超出)设备当前屏幕高度的值。 + +此处的值考虑到了屏幕装饰元素,因此如果设备显示屏的上边缘或下边缘有一些永久性 UI +元素,考虑到这些 UI +元素,同时为减少应用的可用空间,它会使用小于实际屏幕尺寸的高度值。 + +非固定的屏幕装饰元素(例如,全屏时可隐藏的手机状态栏)并不在考虑范围内,标题栏或操作栏等窗口装饰也不在考虑范围内,因此应用必须准备好处理稍小于其所指定值的空间。 + + + + +

此项为 API 级别 13 中新增配置。

+

另请参阅 +{@link android.content.res.Configuration#screenHeightDp} 配置字段,该字段存放当前屏幕宽度。

+

如需了解有关设计不同屏幕和使用此限定符的详细信息,请参阅支持多个屏幕开发者指南。 + +

+
屏幕尺寸 + small
+ normal
+ large
+ xlarge +
+
    +
  • {@code small}:尺寸类似于低密度 +QVGA 屏幕的屏幕。小屏幕的最小布局尺寸约为 +320x426 dp 单位。例如,QVGA 低密度屏幕和 VGA +高密度屏幕。
  • +
  • {@code normal}:尺寸类似于中等密度 +HVGA 屏幕的屏幕。标准屏幕的最小布局尺寸约为 +320x470 dp 单位。例如,WQVGA +低密度屏幕、HVGA 中等密度屏幕、WVGA +高密度屏幕。
  • +
  • {@code large}:尺寸类似于中等密度 +VGA 屏幕的屏幕。 + 大屏幕的最小布局尺寸约为 480x640 dp 单位。 + 例如,VGA 和 WVGA 中等密度屏幕。
  • +
  • {@code xlarge}:明显大于传统中等密度 +HVGA 屏幕的屏幕。超大屏幕的最小布局尺寸约为 +720x960 dp 单位。在大多数情况下,屏幕超大的设备体积过大,不能放进口袋,最常见的是平板式设备。 + +此项为 API 级别 9 中新增配置。
  • +
+

注:使用尺寸限定符并不表示资源仅适用于该尺寸的屏幕。 +如果没有为备用资源提供最符合当前设备配置的限定符,则系统可能使用其中最匹配的资源。 + +

+

注意:如果所有资源均使用大于当前屏幕的尺寸限定符,则系统会使用这些资源,并且应用在运行时将会崩溃(例如,如果所有布局资源均用 {@code +xlarge} 限定符标记,但设备是标准尺寸的屏幕)。 + +

+

此项为 API 级别 4 中新增配置。

+ +

如需了解详细信息,请参阅支持多个屏幕。 +

+

另请参阅 {@link android.content.res.Configuration#screenLayout} 配置字段,该字段表示屏幕是小尺寸、标准尺寸还是大尺寸。 + +

+
屏幕纵横比 + long
+ notlong +
+
    +
  • {@code long}:宽屏,如 WQVGA、WVGA、FWVGA
  • +
  • {@code notlong}:非宽屏,如 QVGA、HVGA 和 VGA
  • +
+

此项为 API 级别 4 中新增配置。

+

它完全基于屏幕的纵横比(宽屏较宽),而与屏幕方向无关。 +

+

另请参阅 {@link android.content.res.Configuration#screenLayout} 配置字段,该字段指示屏幕是否为宽屏。 +

+
屏幕方向 + port
+ land +
+
    +
  • {@code port}:设备处于纵向(垂直)
  • +
  • {@code land}:设备处于横向(水平)
  • + +
+

如果用户旋转屏幕,它有可能在应用生命周期中发生改变。 +如需了解这会在运行期间给应用带来哪些影响,请参阅处理运行时变更。 +

+

另请参阅 {@link android.content.res.Configuration#orientation} 配置字段,该字段指示当前的设备方向。 +

+
UI 模式 + car
+ desk
+ television
+ appliance +watch +
+
    +
  • {@code car}:设备正在车载手机座上显示
  • +
  • {@code desk}:设备正在桌面手机座上显示
  • +
  • {@code television}:设备正在电视上显示,为用户提供“十英尺”体验,其 +UI 位于远离用户的大屏幕上,主要面向方向键或其他非指针式交互 + +
  • +
  • {@code appliance}:设备用作不带显示屏的装置 +
  • +
  • {@code watch}:设备配有显示屏,戴在手腕上
  • +
+

此项为 API 级别 8 中新增配置,API 13 中新增电视配置,API 20 中新增手表配置。

+

如需了解应用在设备插入手机座或从中移除时的响应方式,请阅读确定并监控插接状态和类型。 + +

+

如果用户将设备放入手机座中,它有可能在应用生命周期中发生改变。 +可以使用 {@link +android.app.UiModeManager} 启用或禁用其中某些模式。如需了解这会在运行期间给应用带来哪些影响,请参阅处理运行时变更。 +

+
夜间模式 + night
+ notnight +
+
    +
  • {@code night}:夜间
  • +
  • {@code notnight}:白天
  • +
+

此项为 API 级别 8 中新增配置。

+

如果夜间模式停留在自动模式(默认),它有可能在应用生命周期中发生改变。在这种情况下,该模式会根据当天的时间进行调整。 +可以使用 +{@link android.app.UiModeManager} 启用或禁用此模式。如需了解这会在运行期间给应用带来哪些影响,请参阅处理运行时变更。 +

+
屏幕像素密度 (dpi) + ldpi
+ mdpi
+ hdpi
+ xhdpi
+ xxhdpi
+ xxxhdpi
+ nodpi
+ tvdpi +
+
    +
  • {@code ldpi}:低密度屏幕;约为 120dpi。
  • +
  • {@code mdpi}:中等密度(传统 HVGA)屏幕;约为 +160dpi。
  • +
  • {@code hdpi}:高密度屏幕;约为 240dpi。
  • +
  • {@code xhdpi}:超高密度屏幕;约为 320dpi。API +级别 8 中新增配置
  • +
  • {@code xxhdpi}:超超高密度屏幕;约为 480dpi。API +级别 16 中新增配置
  • +
  • {@code xxxhdpi}:超超超高密度屏幕使用(仅限启动器图标,请参阅“支持多个屏幕”中的注释);约为 +640dpi。 +API +级别 18 中新增配置
  • +
  • {@code nodpi}:它可用于您不希望缩放以匹配设备密度的位图资源。 +
  • +
  • {@code tvdpi}:密度介于 mdpi 和 hdpi 之间的屏幕;约为 213dpi。它并不是“主要”密度组, +主要用于电视,而大多数应用都不需要它。对于大多数应用而言,提供 mdpi 和 +hdpi +资源便已足够,系统将根据需要对其进行缩放。API 级别 13 中引入了此限定符。
  • +
+

六个主要密度之间的缩放比为 3:4:6:8:12:16(忽略 +tvdpi 密度)。因此,9x9 (ldpi) 位图相当于 12x12 (mdpi)、18x18 (hdpi)、24x24 (xhdpi) 位图,依此类推。 +

+

如果您认为图像资源在电视或其他某些设备上呈现的效果不够好,而想尝试使用 tvdpi 资源,则缩放比例为 +1.33*mdpi。例如,mdpi +屏幕的 100px x 100px 图像应该相当于 tvdpi 的133px x 133px。

+

注:使用密度限定符并不表示资源仅适用于该密度的屏幕。 +如果没有为备用资源提供最符合当前设备配置的限定符,则系统可能使用其中最匹配的资源。 + +

+

如需了解有关如何处理不同屏幕密度以及 Android 如何缩放位图以适应当前密度的详细信息,请参阅支持多个屏幕。 + +

+
触摸屏类型 + notouch
+ finger +
+
    +
  • {@code notouch}:设备没有触摸屏。
  • +
  • {@code finger}:设备有一个专供用户通过手指直接与其交互的触摸屏。 +
  • +
+

另请参阅 {@link android.content.res.Configuration#touchscreen} 配置字段,该字段指示设备上的触摸屏类型。 +

+
键盘可用性 + keysexposed
+ keyshidden
+ keyssoft +
+
    +
  • {@code keysexposed}:设备具有可用的键盘。如果设备启用了软键盘(不无可能),那么即使硬键盘没有展示给用户,哪怕设备没有硬键盘,也可以使用此限定符。 + +如果没有提供或已经禁用软键盘,则只有在显示硬键盘时才会使用此限定符。 + +
  • +
  • {@code keyshidden}:设备具有可用的硬键盘,但它处于隐藏状态,且设备没有启用软键盘。 +
  • +
  • {@code keyssoft}:设备已经启用软键盘(无论是否可见)。 +
  • +
+

如果提供了 keysexposed 资源,但未提供 keyssoft +资源,那么只要系统已经启用软键盘,就会使用 +keysexposed 资源,而不考虑键盘是否可见。

+

如果用户打开硬键盘,它有可能在应用生命周期中发生改变。 +如需了解这会在运行期间给应用带来哪些影响,请参阅处理运行时变更。 +

+

另请参阅配置字段 +{@link +android.content.res.Configuration#hardKeyboardHidden} 和 {@link +android.content.res.Configuration#keyboardHidden},这两个字段分别指示硬键盘的可见性和任何一种键盘(包括软键盘)的可见性。

+
主要文本输入法 + nokeys
+ qwerty
+ 12key +
+
    +
  • {@code nokeys}:设备没有用于文本输入的硬按键。
  • +
  • {@code qwerty}:设备具有标准硬键盘(无论是否对用户可见)。 + +
  • +
  • {@code 12key}:设备具有 12 键硬键盘(无论是否对用户可见)。 +
  • +
+

另请参阅 {@link android.content.res.Configuration#keyboard} 配置字段,该字段指示可用的主要文本输入法。 +

+
平台版本(API 级别)示例:
+ v3
+ v4
+ v7
+ 等等
+

设备支持的 API 级别。例如,v1 对应于 API 级别 +1(带有 Android 1.0 或更高版本系统的设备),v4 对应于 API 级别 4(带有 Android +1.6 或更高版本系统的设备)。如需了解有关这些值的详细信息,请参阅 +Android API 级别文档。

+
+ + +

注:有些配置限定符是从 Android 1.0 +才开始添加,因此并非所有版本的 Android 系统都支持所有限定符。使用新限定符会隐式添加平台版本限定符,因此较旧版本系统的设备必然会忽略它。 +例如,使用 +w600dp 限定符会自动包括 v13 限定符,因为可用宽度限定符是 API 级别 13 +中的新增配置。为了避免出现任何问题,请始终包含一组默认资源(一组“不带限定符”的资源)。 +如需了解详细信息,请参阅利用资源提供最佳设备兼容性部分。 + +

+ + + +

限定符命名规则

+ +

以下是一些关于使用配置限定符名称的规则:

+ +
    +
  • 您可以为单组资源指定多个限定符,并使用短划线分隔。例如,drawable-en-rUS-land +适用于横排美国英语设备。 +
  • +
  • 这些限定符必须遵循表 2 中列出的顺序。例如: + +
      +
    • 错误:drawable-hdpi-port/
    • +
    • 正确:drawable-port-hdpi/
    • +
    +
  • +
  • 不能嵌套备用资源目录。例如,您不能拥有 +res/drawable/drawable-en/
  • +
  • 值不区分大小写。在处理之前,资源编译器会将目录名称转换为小写,以避免不区分大小写的文件系统出现问题。 + +名称中使用的任何大写字母只是为了便于认读。
  • +
  • 对于每种限定符类型,仅支持一个值。例如,若要对西班牙语和法语使用相同的 Drawable 文件,则您肯定不能拥有名为 +drawable-rES-rFR/ +的目录,而是需要两个包含相应文件的资源目录,如 +drawable-rES/ +和 drawable-rFR/。然而,实际上您无需将相同的文件都复制到这两个位置。相反,您可以创建指向资源的别名。 +请参阅下面的创建别名资源。 +
  • +
+ +

将备用资源保存到以这些限定符命名的目录中之后,Android +会根据当前设备配置在应用中自动应用这些资源。 +每次请求资源时,Android +都会检查备用资源目录是否包含所请求的资源文件,然后找到最匹配资源(下文进行介绍)。 +如果没有与特定设备配置匹配的备用资源,则 +Android +会使用相应的默认资源(一组用于不含配置限定符的特定资源类型的资源)。 +

+ + + +

创建别名资源

+ +

如果您想将某一资源用于多种设备配置(但是不想作为默认资源提供),则无需将同一资源放入多个备用资源目录中。 + +相反,您可以(在某些情况下)创建备用资源,充当保存在默认资源目录下的资源的别名。 + +

+ +

注:并非所有资源都会提供相应机制让您创建指向其他资源的别名。 +特别是,{@code xml/} 目录中的动画资源、菜单资源、原始资源以及其他未指定资源均不提供此功能。 +

+ +

例如,假设您有一个应用图标 {@code icon.png},并且需要不同区域设置的独特版本。 +但是,加拿大英语和加拿大法语这两种区域设置需要使用同一版本。 +您可能会认为需要将相同的图像复制到加拿大英语和加拿大法语对应的资源目录中,但事实并非如此。 + +相反,您可以将用于二者的图像另存为 {@code icon_ca.png}(除 +{@code icon.png} +以外的任何名称),并将其放入默认 {@code res/drawable/} 目录中。然后,在 {@code +res/drawable-en-rCA/} +和 {@code res/drawable-fr-rCA/} 中创建 {@code icon.xml} 文件,使用 {@code <bitmap>} 元素引用 {@code icon_ca.png}资源。这样,您只需存储 PNG +文件的一个版本和两个指向该版本的小型 XML 文件。(XML 文件示例如下。)

+ + +

Drawable

+ +

要创建指向现有 Drawable 的别名,请使用 +{@code <bitmap>} 元素。例如:

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
+    android:src="@drawable/icon_ca" />
+
+ +

如果将此文件另存为 +{@code icon.xml}(例如,在备用资源目录中,另存为 {@code res/drawable-en-rCA/}),则会编译到可作为 +{@code R.drawable.icon} 引用的资源中,但实际上它是 {@code +R.drawable.icon_ca} 资源(保存在 {@code res/drawable/} 中)的别名。

+ + +

布局

+ +

要创建指向现有布局的别名,请使用包装在 {@code <merge>} 中的 +{@code <include>}元素。例如:

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<merge>
+    <include layout="@layout/main_ltr"/>
+</merge>
+
+ +

如果将此文件另存为 {@code main.xml},则会编译到可作为 +{@code R.layout.main} 引用的资源中,但实际上它是 {@code R.layout.main_ltr} +资源的别名。

+ + +

字符串和其他简单值

+ +

要创建指向现有字符串的别名,只需将所需字符串的资源 +ID 用作新字符串的值即可。例如:

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <string name="hello">Hello</string>
+    <string name="hi">@string/hello</string>
+</resources>
+
+ +

{@code R.string.hi} 资源现在是 {@code R.string.hello} 的别名。

+ +

其他简单值的原理相同。 +例如,颜色:

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <color name="yellow">#f00</color>
+    <color name="highlight">@color/red</color>
+</resources>
+
+ + + + +

利用资源提供最佳设备兼容性

+ +

要使应用支持多种设备配置,则务必为应用使用的每种资源类型提供默认资源,这一点非常重要。 +

+ +

例如,如果应用支持多种语言,请始终包含不带语言和区域限定符的 {@code +values/} 目录(用于保存字符串)。相反,如果您将所有字符串放入带有语言和区域限定符的目录中,则在语言设置不支持您的字符串的设备上运行应用时,应用将会崩溃。 + +但是,只要提供默认 {@code values/} 资源,应用就会正常运行(即使用户不理解该语言,这也总比崩溃要好)。 + +

+ +

同样,如果您根据屏幕方向提供不同的布局资源,则应选择一个方向作为默认方向。 +例如,不要在 {@code +layout-land/} 和 {@code layout-port/} 中分别提供横向和纵向的布局资源,而是保留其中之一作为默认设置,例如:{@code layout/} +用于横向,{@code layout-port/} 用于纵向。

+ +

提供默认资源至关重要,这不仅仅因为应用可能在超出预期的配置上运行,也因为新版 +Android +有时会添加旧版本不支持的配置限定符。若要使用新的资源限定符,又希望维持对旧版 +Android 的代码兼容性,则当旧版 +Android +运行应用时,如果不提供默认资源,应用将会崩溃,这是因为它无法使用以新限定符命名的资源。例如,如果将 {@code +minSdkVersion} 设置为 4,并使用夜间模式({@code night} 或 {@code notnight},API +级别 8 中新增配置)限定所有 Drawable 资源,则 API 级别 4 设备无法访问 Drawable 资源,而且会崩溃。在这种情况下,您可能希望 {@code notnight} 成为默认资源,为此,您应排除该限定符,使 Drawable 资源位于 {@code drawable/} 或 +{@code drawable-night/} +中。

+ +

因此,为了提供最佳设备兼容性,请始终为应用正确运行所必需的资源提供默认资源。 +然后,使用配置限定符为特定的设备配置创建备用资源。 +

+ +

这条规则有一个例外:如果应用的 {@code minSdkVersion} 为 4 或更高,则在提供带屏幕密度限定符的备用 Drawable 资源时,不需要默认 Drawable 资源。 + +即使没有默认 Drawable 资源,Android +也可以从备用屏幕密度中找到最佳匹配项并根据需要缩放位图。 +但是,为了在所有类型的设备上提供最佳体验,您应该为所有三种类型的密度提供备用 Drawable。 +

+ + + +

Android 如何找到最匹配资源

+ +

当您请求要为其提供备用资源的资源时,Android +会根据当前的设备配置选择要在运行时使用的备用资源。为演示 +Android 如何选择备用资源,假设以下 Drawable 目录分别包含相同图像的不同版本: +

+ +
+drawable/
+drawable-en/
+drawable-fr-rCA/
+drawable-en-port/
+drawable-en-notouch-12key/
+drawable-port-ldpi/
+drawable-port-notouch-12key/
+
+ +

同时,假设设备配置如下:

+ +

+区域设置 = en-GB
+屏幕方向 = port
+屏幕像素密度 = hdpi
+触摸屏类型 = notouch
+主要文本输入法 = 12key +

+ +

通过将设备配置与可用的备用资源进行比较,Android 从 +{@code drawable-en-port} 中选择 Drawable。

+ +

系统使用以下逻辑决定要使用的资源: +

+ + +
+ +

图 2. Android +如何找到最匹配资源的流程图。

+
+ + +
    +
  1. 淘汰与设备配置冲突的资源文件。 +

    drawable-fr-rCA/ 目录与 +en-GB 区域设置冲突,因而被淘汰。

    +
    +drawable/
    +drawable-en/
    +drawable-fr-rCA/
    +drawable-en-port/
    +drawable-en-notouch-12key/
    +drawable-port-ldpi/
    +drawable-port-notouch-12key/
    +
    +

    例外:屏幕像素密度是唯一一个未因冲突而被淘汰的限定符。 +尽管设备的屏幕密度为 +hdpi,但是 +drawable-port-ldpi/ 未被淘汰,因为此时每个屏幕密度均视为匹配。如需了解详细信息,请参阅支持多个屏幕文档。 +

  2. + +
  3. 选择列表(表 2)中(下一个)优先级最高的限定符。(先从 MCC +开始,然后下移。)
  4. +
  5. 是否有资源目录包括此限定符?
  6. +
      +
    • 若无,请返回到第 2 步,看看下一个限定符。(在该示例中,除非达到语言限定符,否则答案始终为“否”。) +
    • +
    • 若有,请继续执行第 4 步。
    • +
    + + +
  7. 淘汰不含此限定符的资源目录。在该示例中,系统会淘汰所有不含语言限定符的目录。 +
  8. +
    +drawable/
    +drawable-en/
    +drawable-en-port/
    +drawable-en-notouch-12key/
    +drawable-port-ldpi/
    +drawable-port-notouch-12key/
    +
    +

    例外:如果涉及的限定符是屏幕像素密度,则 +Android +会选择最接近设备屏幕密度的选项。通常,Android +倾向于缩小大型原始图像,而不是放大小型原始图像。请参阅支持多个屏幕。 +

    + + +
  9. 返回并重复第 2 步、第 3 步和第 4 步,直到只剩下一个目录为止。在此示例中,屏幕方向是下一个判断是否匹配的限定符。因此,未指定屏幕方向的资源被淘汰: + + +
    +drawable-en/
    +drawable-en-port/
    +drawable-en-notouch-12key/
    +
    +

    剩下的目录是 {@code drawable-en-port}。

    +
  10. +
+ +

尽管对所请求的每个资源均执行此程序,但是系统仍会对某些方面做进一步优化。 +例如,系统一旦知道设备配置,即会淘汰可能永远无法匹配的备用资源。 +比如说,如果配置语言是英语(“en”),则系统绝不会将语言限定符设置为非英语的任何资源目录包含在选中的资源池中(不过,仍会将不带语言限定符的资源目录包含在该池中)。 + + +

+ +

根据屏幕尺寸限定符选择资源时,如果没有更好的匹配资源,则系统将使用专为小于当前屏幕的屏幕而设计的资源(例如,如有必要,大尺寸屏幕将使用标准尺寸的屏幕资源)。 + +但是,如果唯一可用的资源大于当前屏幕,则系统不会使用这些资源,并且如果没有其他资源与设备配置匹配,应用将会崩溃(例如,如果所有布局资源均用 {@code xlarge} 限定符标记,但设备是标准尺寸的屏幕)。 + + + +

+ +

注:限定符的优先顺序(表 +2 中)比与设备完全匹配的限定符数量更加重要。例如,在上面的第 +4 步中,列表剩下的最后选项包括三个与设备完全匹配的限定符(方向、触摸屏类型和输入法),而 +drawable-en +只有一个匹配参数(语言)。但是,语言的优先顺序高于其他两个限定符,因此 +drawable-port-notouch-12key 被淘汰。

+ +

如需了解有关如何在应用中使用资源的更多信息,请转至访问资源

diff --git a/docs/html-intl/intl/zh-cn/guide/topics/resources/runtime-changes.jd b/docs/html-intl/intl/zh-cn/guide/topics/resources/runtime-changes.jd new file mode 100644 index 0000000000000000000000000000000000000000..0df99982de9a85194c6fd71fb36f2d86b8d21961 --- /dev/null +++ b/docs/html-intl/intl/zh-cn/guide/topics/resources/runtime-changes.jd @@ -0,0 +1,281 @@ +page.title=处理运行时变更 +page.tags=Activity,生命周期 +@jd:body + + + +

有些设备配置可能会在运行时发生变化(例如屏幕方向、键盘可用性及语言)。 +发生这种变化时,Android +会重启正在运行的 +{@link android.app.Activity}(先后调用 {@link android.app.Activity#onDestroy()} 和 {@link +android.app.Activity#onCreate(Bundle) onCreate()})。重启行为旨在通过利用与新设备配置匹配的备用资源自动重新加载您的应用,来帮助它适应新配置。 + +

+ +

要妥善处理重启行为,Activity 必须通过常规的Activity 生命周期恢复其以前的状态,在 Activity 生命周期中,Android +会在销毁 Activity 之前调用 +{@link android.app.Activity#onSaveInstanceState(Bundle) onSaveInstanceState()},以便您保存有关应用状态的数据。 + +然后,您可以在 +{@link android.app.Activity#onCreate(Bundle) onCreate()} 或 {@link +android.app.Activity#onRestoreInstanceState(Bundle) onRestoreInstanceState()} 期间恢复 Activity 状态。

+ +

要测试应用能否在保持应用状态完好的情况下自行重启,您应该在应用中执行各种任务时调用配置变更(例如,更改屏幕方向)。 + +您的应用应该能够在不丢失用户数据或状态的情况下随时重启,以便处理如下事件:配置发生变化,或者用户收到来电并在应用进程被销毁很久之后返回到应用。 + + +要了解如何恢复 Activity 状态,请阅读Activity 生命周期

+ +

但是,您可能会遇到这种情况:重启应用并恢复大量数据不仅成本高昂,而且给用户留下糟糕的使用体验。 +在这种情况下,您有两个其他选择: +

+ +
    +
  1. 在配置变更期间保留对象 +

    允许 Activity 在配置变更时重启,但是要将有状态对象传递给 Activity 的新实例。 +

    + +
  2. +
  3. 自行处理配置变更 +

    阻止系统在某些配置变更期间重启 Activity,但要在配置确实发生变化时接收回调,这样,您就能够根据需要手动更新 Activity。 + +

    +
  4. +
+ + +

在配置变更期间保留对象

+ +

如果重启 Activity 需要恢复大量数据、重新建立网络连接或执行其他密集操作,那么因配置变更而引起的完全重启可能会给用户留下应用运行缓慢的体验。 + +此外,依靠系统通过 {@link +android.app.Activity#onSaveInstanceState(Bundle) onSaveInstanceState()} 回调为您保存的 +{@link android.os.Bundle},可能无法完全恢复 Activity 状态,因为它 +并非设计用于携带大型对象(例如位图),而且其中的数据必须先序列化,再进行反序列化, +这可能会消耗大量内存并使得配置变更速度缓慢。在这种情况下,如果 Activity 因配置变更而重启,则可通过保留 +{@link +android.app.Fragment} 来减轻重新初始化 Activity 的负担。此片段可能包含对您要保留的有状态对象的引用。 +

+ +

当 +Android 系统因配置变更而关闭 Activity 时,不会销毁您已标记为要保留的 Activity 的片段。您可以将此类片段添加到 Activity 以保留有状态的对象。 +

+ +

要在运行时配置变更期间将有状态的对象保留在片段中,请执行以下操作:

+ +
    +
  1. 扩展 {@link android.app.Fragment} +类并声明对有状态对象的引用。
  2. +
  3. 在创建片段后调用 {@link android.app.Fragment#setRetainInstance(boolean)}。 +
  4. +
  5. 将片段添加到 Activity。
  6. +
  7. 重启 Activity 后,使用 {@link android.app.FragmentManager} +检索片段。
  8. +
+ +

例如,按如下所示定义片段:

+ +
+public class RetainedFragment extends Fragment {
+
+    // data object we want to retain
+    private MyDataObject data;
+
+    // this method is only called once for this fragment
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        // retain this fragment
+        setRetainInstance(true);
+    }
+
+    public void setData(MyDataObject data) {
+        this.data = data;
+    }
+
+    public MyDataObject getData() {
+        return data;
+    }
+}
+
+ +

注意:尽管您可以存储任何对象,但是切勿传递与 +{@link android.app.Activity} 绑定的对象,例如,{@link +android.graphics.drawable.Drawable}、{@link android.widget.Adapter}、{@link android.view.View} +或其他任何与 {@link android.content.Context} 关联的对象。否则,它将泄漏原始 Activity 实例的所有视图和资源。 +(泄漏资源意味着应用将继续持有这些资源,但是无法对其进行垃圾回收,因此可能会丢失大量内存。) + +

+ +

然后,使用 {@link android.app.FragmentManager} 将片段添加到 Activity。在运行时配置变更期间再次启动 Activity 时,您可以获得片段中的数据对象。 + +例如,按如下所示定义 Activity:

+ +
+public class MyActivity extends Activity {
+
+    private RetainedFragment dataFragment;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.main);
+
+        // find the retained fragment on activity restarts
+        FragmentManager fm = getFragmentManager();
+        dataFragment = (DataFragment) fm.findFragmentByTag(“data”);
+
+        // create the fragment and data the first time
+        if (dataFragment == null) {
+            // add the fragment
+            dataFragment = new DataFragment();
+            fm.beginTransaction().add(dataFragment, “data”).commit();
+            // load the data from the web
+            dataFragment.setData(loadMyData());
+        }
+
+        // the data is available in dataFragment.getData()
+        ...
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+        // store the data in the fragment
+        dataFragment.setData(collectMyLoadedData());
+    }
+}
+
+ +

在此示例中,{@link android.app.Activity#onCreate(Bundle) onCreate()} +添加了一个片段或恢复了对它的引用。此外,{@link android.app.Activity#onCreate(Bundle) onCreate()} +还将有状态的对象存储在片段实例内部。{@link android.app.Activity#onDestroy() onDestroy()} +对所保留的片段实例内的有状态对象进行更新。 +

+ + + + + +

自行处理配置变更

+ +

如果应用在特定配置变更期间无需更新资源,并且因性能限制您需要尽量避免重启,则可声明 Activity 将自行处理配置变更,这样可以阻止系统重启 Activity。 + + +

+ +

注:自行处理配置变更可能导致备用资源的使用更为困难,因为系统不会为您自动应用这些资源。 + +只能在您必须避免Activity因配置变更而重启这一万般无奈的情况下,才考虑采用自行处理配置变更这种方法,而且对于大多数应用并不建议使用此方法。 +

+ +

要声明由 Activity 处理配置变更,请在清单文件中编辑相应的 {@code <activity>} +元素,以包含 {@code +android:configChanges} +属性以及代表要处理的配置的值。{@code +android:configChanges}属性的文档中列出了该属性的可能值(最常用的值包括 {@code "orientation"} +和 +{@code "keyboardHidden"},分别用于避免因屏幕方向和可用键盘改变而导致重启)。您可以在该属性中声明多个配置值,方法是用管道 +{@code |} 字符分隔这些配置值。

+ +

例如,以下清单文件代码声明的 Activity 可同时处理屏幕方向变更和键盘可用性变更: +

+ +
+<activity android:name=".MyActivity"
+          android:configChanges="orientation|keyboardHidden"
+          android:label="@string/app_name">
+
+ +

现在,当其中一个配置发生变化时,{@code MyActivity} 不会重启。相反,{@code MyActivity} +会收到对 {@link +android.app.Activity#onConfigurationChanged(Configuration) onConfigurationChanged()} 的调用。向此方法传递 +{@link android.content.res.Configuration} +对象指定新设备配置。您可以通过读取 +{@link android.content.res.Configuration} +中的字段,确定新配置,然后通过更新界面中使用的资源进行适当的更改。调用此方法时,Activity 的 +{@link android.content.res.Resources} +对象会相应地进行更新,以根据新配置返回资源,这样,您就能够在系统不重启 Activity 的情况下轻松重置 +UI 的元素。

+ +

注意:从 +Android +3.2(API 级别 13)开始,当设备在纵向和横向之间切换时,“屏幕尺寸”也会发生变化。因此,在开发针对 +API 级别 13 或更高版本系统的应用时,若要避免由于设备方向改变而导致运行时重启(正如 {@code minSdkVersion}{@code targetSdkVersion} +属性中所声明),则除了 {@code +"orientation"} 值以外,您还必须添加 {@code "screenSize"} 值。即,您必须声明 {@code +android:configChanges="orientation|screenSize"}。但是,如果您的应用是面向 API 级别 +12 或更低版本的系统,则 Activity 始终会自行处理此配置变更(即便是在 +Android 3.2 或更高版本的设备上运行,此配置变更也不会重启 Activity)。

+ +

例如,以下 {@link +android.app.Activity#onConfigurationChanged(Configuration) onConfigurationChanged()} 实现 +检查当前设备方向:

+ +
+@Override
+public void onConfigurationChanged(Configuration newConfig) {
+    super.onConfigurationChanged(newConfig);
+
+    // Checks the orientation of the screen
+    if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
+        Toast.makeText(this, "landscape", Toast.LENGTH_SHORT).show();
+    } else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT){
+        Toast.makeText(this, "portrait", Toast.LENGTH_SHORT).show();
+    }
+}
+
+ +

{@link android.content.res.Configuration} +对象代表所有当前配置,而不仅仅是已经变更的配置。大多数时候,您并不在意配置具体发生了哪些变更,而且您可以轻松地重新分配所有资源,为您正在处理的配置提供备用资源。 + +例如,由于 {@link +android.content.res.Resources} 对象现已更新,因此您可以通过 +{@link android.widget.ImageView#setImageResource(int) +setImageResource()} +重置任何 {@link android.widget.ImageView},并且使用适合于新配置的资源(如提供资源中所述)。

+ +

请注意,{@link +android.content.res.Configuration} 字段中的值是与 +{@link android.content.res.Configuration} 类中的特定常量匹配的整型数。有关要对每个字段使用哪些常量的文档,请参阅 +{@link +android.content.res.Configuration} 参考文档中的相应字段。

+ +

请谨记:在声明由 Activity 处理配置变更时,您有责任重置要为其提供备用资源的所有元素。 +如果您声明由 Activity 处理方向变更,而且有些图像应该在横向和纵向之间切换,则必须在 +{@link +android.app.Activity#onConfigurationChanged(Configuration) onConfigurationChanged()} +期间将每个资源重新分配给每个元素。

+ +

如果无需基于这些配置变更更新应用,则可不用实现 +{@link +android.app.Activity#onConfigurationChanged(Configuration) onConfigurationChanged()}。在这种情况下,仍将使用在配置变更之前用到的所有资源,只是您无需重启 Activity。 + +但是,应用应该始终能够在保持之前状态完好的情况下关闭和重启,因此您不得试图通过此方法来逃避在正常 Activity 生命周期期间保持您的应用状态。 + +这不仅仅是因为还存在其他一些无法禁止重启应用的配置变更,还因为有些事件必须由您处理,例如用户离开应用,而在用户返回应用之前该应用已被销毁。 + + +

+ +

如需了解有关您可以在 Activity 中处理哪些配置变更的详细信息,请参阅 {@code +android:configChanges} 文档和 {@link android.content.res.Configuration} +类。

diff --git a/docs/html-intl/intl/zh-cn/guide/topics/ui/controls.jd b/docs/html-intl/intl/zh-cn/guide/topics/ui/controls.jd new file mode 100644 index 0000000000000000000000000000000000000000..0f1a543d1f66d06775cbb14f364abb69c1680892 --- /dev/null +++ b/docs/html-intl/intl/zh-cn/guide/topics/ui/controls.jd @@ -0,0 +1,90 @@ +page.title=输入控件 +parent.title=用户界面 +parent.link=index.html +@jd:body + +
+ +
+ +

输入控件是您的应用用户界面中的交互式组件。Android 提供了多种可在 UI 中使用的控件,如按钮、文本字段、定位栏、复选框、缩放按钮、切换按钮等。 + +

+ +

向 UI 中添加输入控件与向 XML 布局中添加 XML 元素一样简单。例如,以下是一个包含文本字段和按钮的布局: +

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    android:orientation="horizontal">
+    <EditText android:id="@+id/edit_message"
+        android:layout_weight="1"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:hint="@string/edit_message" />
+    <Button android:id="@+id/button_send"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/button_send"
+        android:onClick="sendMessage" />
+</LinearLayout>
+
+ +

每个输入控件都支持一组特定的输入事件,以便您处理用户输入文本或触摸按钮等事件。 +

+ + +

通用控件

+

以下列出了您可以在应用中使用的一些通用控件。点击链接可了解有关各控件用法的详情。 +

+ +

注:除了此处列出的控件外,Android 还提供了几种其他控件。 +浏览 {@link android.widget} 软件包可发现更多控件。如果您的应用需要特定类型的输入控件,则可以构建您自己的自定义组件。 +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
控件类型描述相关类
按钮可由用户按压或点击来执行操作的按钮。{@link android.widget.Button Button}
文本字段一种可编辑的文本字段。您可以使用 AutoCompleteTextView 小工具创建提供自动完成建议的文本输入小工具{@link android.widget.EditText EditText}、{@link android.widget.AutoCompleteTextView}
复选框可由用户切换的启用/禁用开关。您应该在向用户呈现一组不互斥的可选选项时使用复选框。{@link android.widget.CheckBox CheckBox}
单选按钮与复选框类似,不同的是只能选择组中的一个选项。{@link android.widget.RadioGroup RadioGroup} +
{@link android.widget.RadioButton RadioButton}
切换按钮一种具有指示灯的开/关按钮。{@link android.widget.ToggleButton ToggleButton}
微调框一种允许用户从值集中选择一个值的下拉列表。{@link android.widget.Spinner Spinner}
选取器一种供用户通过使用向上/向下按钮或轻扫手势选择值集中单个值的对话框。使用 DatePicker 小工具输入日期(月、日、年)值,或使用 TimePicker 小工具输入时间(小时、分钟、上午/下午)值,系统将根据用户的区域设置自动设置所输入内容的格式。{@link android.widget.DatePicker}、{@link android.widget.TimePicker}
diff --git a/docs/html-intl/intl/zh-cn/guide/topics/ui/declaring-layout.jd b/docs/html-intl/intl/zh-cn/guide/topics/ui/declaring-layout.jd new file mode 100644 index 0000000000000000000000000000000000000000..38e534eb091a571ca55cbfe1afda5aad89c136a0 --- /dev/null +++ b/docs/html-intl/intl/zh-cn/guide/topics/ui/declaring-layout.jd @@ -0,0 +1,492 @@ +page.title=布局 +page.tags=view,viewgroup +@jd:body + +
+
+

本文内容

+
    +
  1. 编写 XML
  2. +
  3. 加载 XML 资源
  4. +
  5. 属性 +
      +
    1. ID
    2. +
    3. 布局参数
    4. +
    +
  6. +
  7. 布局位置
  8. +
  9. 尺寸、内边距和外边距
  10. +
  11. 常见布局
  12. +
  13. 使用适配器构建布局 +
      +
    1. 使用数据填充适配器视图
    2. +
    3. 处理点击事件
    4. +
    +
  14. +
+ +

关键类

+
    +
  1. {@link android.view.View}
  2. +
  3. {@link android.view.ViewGroup}
  4. +
  5. {@link android.view.ViewGroup.LayoutParams}
  6. +
+ +

另请参阅

+
    +
  1. 构建简单的用户界面 +
+
+ +

布局定义用户界面的视觉结构,如Activity应用小工具的 +UI。您可以通过两种方式声明布局:

+
    +
  • 在 XML 中声明 UI 元素。Android 提供了对应于 View 类及其子类的简明 XML +词汇,如用于小工具和布局的词汇;
  • +
  • 运行时实例化布局元素。您的应用可以通过编程创建 +View 对象和 ViewGroup 对象(并操纵其属性)。
  • +
+ +

Android 框架让您可以灵活地使用以下一种或两种方法来声明和管理应用的 UI。例如,您可以在 XML 中声明应用的默认布局,包括将出现在布局中的屏幕元素及其属性。然后,您可以在应用中添加可在运行时修改屏幕对象(包括那些已在 XML 中声明的对象)状态的代码。

+ + + +

在 XML 中声明 UI 的优点在于,您可以更好地将应用的外观与控制应用行为的代码隔离。您的 UI 描述位于应用代码外部,这意味着您在修改或调整描述时无需修改您的源代码并重新编译。例如,您可以创建适用于不同屏幕方向、不同设备屏幕尺寸和不同语言的 XML 布局。此外,在 XML 中声明布局还能更轻松地显示 UI 的结构,从而简化问题调试过程。因此,本文将侧重于示范如何在 XML 中声明布局。如果您对在运行时实例化 View +对象感兴趣,请参阅 {@link android.view.ViewGroup} 类和 +{@link android.view.View} 类的参考资料。

+ +

一般而言,用于声明 UI 元素的 XML 词汇严格遵循类和方法的结构和命名方式,其中元素名称对应于类名称,属性名称对应于方法。实际上,这种对应关系往往非常直接,让您可以猜到对应于类方法的 XML 属性,或对应于给定 XML 元素的类。但请注意,并非所有词汇都完全相同。在某些情况下,在命名上略有差异。例如,EditText 元素具有的 +text 属性对应的类方法是 EditText.setText()。 +

+ +

提示:如需了解有关不同布局类型的更多信息,请参阅常见布局对象。 + +Hello 视图教程指南中也提供了一系列有关构建各种布局的教程。

+ +

编写 XML

+ +

您可以利用 Android 的 XML 词汇,按照在 HTML 中创建包含一系列嵌套元素的网页的相同方式快速设计 UI 布局及其包含的屏幕元素。

+ +

每个布局文件都必须只包含一个根元素,并且该元素必须是视图对象或 ViewGroup 对象。定义根元素之后,即可再以子元素的形式添加其他布局对象或小工具,从而逐步构建定义布局的视图层次结构。例如,以下这个 XML 布局使用垂直 {@link android.widget.LinearLayout} +来储存一个 {@link android.widget.TextView} 和一个 {@link android.widget.Button}:

+
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:layout_width="match_parent"
+              android:layout_height="match_parent"
+              android:orientation="vertical" >
+    <TextView android:id="@+id/text"
+              android:layout_width="wrap_content"
+              android:layout_height="wrap_content"
+              android:text="Hello, I am a TextView" />
+    <Button android:id="@+id/button"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Hello, I am a Button" />
+</LinearLayout>
+
+ +

在 XML 中声明布局后,请在您的 Android 项目 res/layout/ 目录中以 .xml 扩展名保存文件,以便其能够正确编译。 +

+ +

布局资源文档中提供了有关布局 XML 文件语法的更多信息。

+ +

加载 XML 资源

+ +

当您编译应用时,每个 XML 布局文件都会编译到一个 +{@link android.view.View} 资源中。您应该在 +{@link android.app.Activity#onCreate(android.os.Bundle) Activity.onCreate()} +回调实现中从您的应用代码加载布局资源。请通过调用 {@link android.app.Activity#setContentView(int) setContentView()},以 +R.layout.layout_file_name +形式向其传递对布局资源的引用来执行此操作。例如,如果您的 +XML 布局保存为 +main_layout.xml,则需要像下面这样为您的 Activity 加载该布局:

+
+public void onCreate(Bundle savedInstanceState) {
+    super.onCreate(savedInstanceState);
+    setContentView(R.layout.main_layout);
+}
+
+ +

启动您的 Activity 时,Android +框架会调用 Activity 中的 +onCreate() +回调方法(请参阅Activity文档中有关生命周期的阐述)。

+ + +

属性

+ +

每个视图对象和 ViewGroup 对象都支持各自的各类 XML +属性。某些属性是视图对象的专用属性(例如,TextView 支持 textSize +属性),但这些属性也会被任何可以扩展此类的视图对象继承。某些属性通用于所有 View +对象,因为它们继承自根 View 类(如 id +属性)。此外,其他属性被视为“布局参数”,即描述 View +对象特定布局方向的属性,如该对象的父 ViewGroup +对象所定义的属性。

+ +

ID

+ +

任何视图对象都可能具有关联的整型 ID,此 ID 用于在结构树中对 View +对象进行唯一标识。编译应用后,此 ID 将作为整型数引用,但在布局 XML +文件中,通常会在 id 属性中为该 ID 赋予字符串值。这是所有 +View 对象共用的 XML 属性(由 {@link android.view.View} +类定义),您会经常用到它。XML 标记内部的 ID +语法是:

+
android:id="@+id/my_button"
+ +

字符串开头处的 @ 符号指示 XML 解析程序应该解析并展开 +ID 字符串的其余部分,并将其标识为 ID 资源。加号 (+) +表示这是一个新的资源名称,必须创建该名称并将其添加到我们的资源(在 R.java 文件中)内。Android 框架还提供了许多其他 ID +资源。引用 Android 资源 ID 时,不需要加号,但必须添加 +android 软件包命名空间,如下所示:

+
android:id="@android:id/empty"
+

添加 android 软件包命名空间之后,现在,我们将从 android.R +资源类而非本地资源类引用 ID。

+ +

要想创建视图并从应用中引用它们,常见的模式是:

+
    +
  1. 在布局文件中定义一个视图/小工具,并为其分配一个唯一的 ID: +
    +<Button android:id="@+id/my_button"
    +        android:layout_width="wrap_content"
    +        android:layout_height="wrap_content"
    +        android:text="@string/my_button_text"/>
    +
    +
  2. +
  3. 然后创建一个 view +对象实例,并从布局中捕获它(通常使用 {@link android.app.Activity#onCreate(Bundle) onCreate()} 方法): +
    +Button myButton = (Button) findViewById(R.id.my_button);
    +
    +
  4. +
+

创建 {@link android.widget.RelativeLayout} +时,为 view 对象定义 ID 非常重要。在相对布局中,同级视图可以定义其相对于其他同级视图的布局,同级视图通过唯一的 ID +进行引用。

+

ID +不需要在整个结构树中具有唯一性,但在您要搜索的结构树部分应具有唯一性(要搜索的部分往往是整个结构树,因此最好尽可能具有全局唯一性)。 +

+ + +

布局参数

+ +

名为 layout_something 的 XML +布局属性可为视图定义与其所在的 ViewGroup 相适的布局参数。

+ +

每个 ViewGroup 类都会实现一个扩展 {@link +android.view.ViewGroup.LayoutParams} 的嵌套类。此子类包含的属性类型会根据需要为视图组的每个子视图定义尺寸和位置。 + +正如您在图 1 +中所见,父视图组为每个子视图(包括子视图组)定义布局参数。

+ + +

图 1. 以可视化方式表示的视图层次结构,其中包含与每个视图关联的布局参数。 +

+ +

请注意,每个 LayoutParams +子类都有自己的值设置语法。每个子元素都必须定义适合其父元素的 +LayoutParams,但父元素也可为其子元素定义不同的 LayoutParams。

+ +

所有视图组都包括宽度和高度(layout_width 和 +layout_height),并且每个视图都必须定义它们。许多 +LayoutParams 还包括可选的外边距和边框。

+ +

您可以指定具有确切尺寸的宽度和高度,但您多半不想经常这样做。 +在更多的情况下,您会使用以下常量之一来设置宽度或高度: +

+ +
    +
  • wrap_content 指示您的视图将其大小调整为内容所需的尺寸 +
  • +
  • match_parent (在 API 级别 8 之前名为 fill_parent )指示您的视图尽可能采用其父视图组所允许的最大尺寸 +
  • +
+ +

一般而言,建议不要使用绝对单位(如像素)来指定布局宽度和高度, +而是使用相对测量单位,如密度无关像素单位 +(dp)、 wrap_content 或 +match_parent,这种方法更好,因为它有助于确保您的应用在各类尺寸的设备屏幕上正确显示。可用资源文档中定义了可接受的测量单位类型。 + + + +

+ + +

布局位置

+

+ 视图的几何形状就是矩形的几何形状。视图具有一个位置(以一对水平向左垂直向上坐标表示)和两个尺寸(以宽度和高度表示)。 + +位置和尺寸的单位是像素。 + +

+ +

+ 可以通过调用方法 + {@link android.view.View#getLeft()} 和方法 {@link android.view.View#getTop()} 来检索视图的位置。前者会返回表示视图的矩形的水平向左(或称 X 轴) + 坐标。后者会返回表示视图的矩形的垂直向上(或称 Y 轴)坐标。 +这些方法都会返回视图相对于其父项的位置。 +例如,如果 getLeft() 返回 20,则意味着视图位于其直接父项左边缘向右 20 个像素处。 + + +

+ +

+ 此外,系统还提供了几种便捷方法来避免不必要的计算,即 {@link android.view.View#getRight()} 和 {@link android.view.View#getBottom()}。 + + 这些方法会返回表示视图的矩形的右边缘和下边缘的坐标。 +例如,调用 {@link android.view.View#getRight()} + 类似于进行以下计算:getLeft() + getWidth()。 +

+ + +

尺寸、内边距和外边距

+

+ 视图的尺寸通过宽度和高度表示。视图实际上具有两对宽度和高度值。 + +

+ +

+ 第一对称为测量宽度测量高度。 +这些尺寸定义视图想要在其父项内具有的大小。 + + 这些测量尺寸可以通过调用 {@link android.view.View#getMeasuredWidth()} + 和 {@link android.view.View#getMeasuredHeight()} 来获得。 +

+ +

+ 第二对简称为宽度高度,有时称为绘制宽度绘制高度。 +这些尺寸定义视图在绘制时和布局后在屏幕上的实际尺寸。 + +这些值可以(但不必)与测量宽度和测量高度不同。 +宽度和高度可以通过调用 + {@link android.view.View#getWidth()} 和 {@link android.view.View#getHeight()} 来获得。 +

+ +

+ 要想测量其尺寸,视图需要将其内边距考虑在内。内边距以视图左侧、顶部、右侧和底部各部分的像素数表示。 + + 内边距可用于以特定数量的 + 像素弥补视图的内容。例如,左侧内边距为 2,会将视图的内容从左边缘向右推 + 2 个像素。可以使用 + {@link android.view.View#setPadding(int, int, int, int)} 方法设置内边距,并通过调用 + {@link android.view.View#getPaddingLeft()}、{@link android.view.View#getPaddingTop()}、{@link android.view.View#getPaddingRight()} 和 {@link android.view.View#getPaddingBottom()} 进行查询。 + +

+ +

+ 尽管视图可以定义内边距,但它并不支持外边距。 +不过,视图组可以提供此类支持。如需了解更多信息,请参阅 + {@link android.view.ViewGroup} 和 + {@link android.view.ViewGroup.MarginLayoutParams}。 +

+ +

如需了解有关尺寸的详细信息,请参阅 + 尺寸值。 +

+ + + + + + + + + + + +

常见布局

+ +

{@link android.view.ViewGroup} +类的每个子类都提供了一种独特的方式来显示您在其中嵌套的视图。以下是 +Android 平台中内置的一些较为常见的布局类型。

+ +

注:尽管您可以通过将一个或多个布局嵌套在另一个布局内来实现您的 +UI +设计,但应该使您的布局层次结构尽可能简略。布局的嵌套布局越少,绘制速度越快(扁平的视图层次结构优于深层的视图层次结构)。 +

+ + + + +
+

线性布局

+ +

一种使用单个水平行或垂直行来组织子项的布局。它会在窗口长度超出屏幕长度时创建一个滚动条。 +

+
+ +
+

相对布局

+ +

让您能够指定子对象彼此之间的相对位置(子对象 A +在子对象 B 左侧)或子对象与父对象的相对位置(与父对象顶部对齐)。

+
+ +
+

Web 视图

+ +

显示网页。

+
+ + + + +

使用适配器构建布局

+ +

如果布局的内容是属于动态或未预先确定的内容,您可以使用这样一种布局:在运行时通过子类 +{@link android.widget.AdapterView} 用视图填充布局。{@link android.widget.AdapterView} +类的子类使用 {@link android.widget.Adapter} +将数据与其布局绑定。{@link android.widget.Adapter} +充当数据源与 {@link android.widget.AdapterView} +布局之间的中间人—{@link android.widget.Adapter}(从数组或数据库查询等来源)检索数据,并将每个条目转换为可以添加到 {@link android.widget.AdapterView} +布局中的视图。

+ +

适配器支持的常见布局包括:

+ +
+

列表视图

+ +

显示滚动的单列列表。

+
+ +
+

网格视图

+ +

显示滚动的行列网格。

+
+ + + +

使用数据填充适配器视图

+ +

您可以通过将 {@link android.widget.AdapterView} 实例与 {@link android.widget.Adapter} 绑定来填充 {@link android.widget.AdapterView}(如 {@link android.widget.ListView} 或 +{@link android.widget.GridView}),此操作会从外部来源检索数据,并创建表示每个数据条目的 +{@link +android.view.View}。

+ +

Android 提供了几个 {@link android.widget.Adapter} 子类,用于检索不同种类的数据和构建 +{@link android.widget.AdapterView} 的视图。两种最常见的适配器是: +

+ +
+
{@link android.widget.ArrayAdapter}
+
请在数据源为数组时使用此适配器。默认情况下,{@link +android.widget.ArrayAdapter} 会通过在每个项目上调用 {@link +java.lang.Object#toString()} 并将内容放入 {@link +android.widget.TextView} 来为每个数组项创建视图。 +

例如,如果您具有想要在 {@link +android.widget.ListView} 中显示的字符串数组,请使用构造函数初始化一个新的 +{@link android.widget.ArrayAdapter},为每个字符串和字符串数组指定布局:

+
+ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
+        android.R.layout.simple_list_item_1, myStringArray);
+
+

此构造函数的参数是:

+
    +
  • 您的应用 {@link android.content.Context}
  • +
  • 包含数组中每个字符串的 {@link android.widget.TextView} 的布局
  • +
  • 字符串数组
  • +
+

然后,只需在您的 {@link android.widget.ListView} 上调用 +{@link android.widget.ListView#setAdapter setAdapter()}:

+
+ListView listView = (ListView) findViewById(R.id.listview);
+listView.setAdapter(adapter);
+
+ +

要想自定义每个项的外观,您可以重写数组中各个对象的 {@link +java.lang.Object#toString()} 方法。或者,要想为 +{@link android.widget.TextView} 之外的每个项创建视图(例如,如果您想为每个数组项创建一个 +{@link android.widget.ImageView}),请扩展 {@link +android.widget.ArrayAdapter} 类并重写 {@link android.widget.ArrayAdapter#getView +getView()} 以返回您想要为每个项获取的视图类型。

+ +
+ +
{@link android.widget.SimpleCursorAdapter}
+
请在数据来自 {@link android.database.Cursor} 时使用此适配器。使用 +{@link android.widget.SimpleCursorAdapter} 时,您必须指定要为 {@link android.database.Cursor} +中的每个行使用的布局,以及应该在哪些布局视图中插入 {@link android.database.Cursor} +中的哪些列。例如,如果您想创建人员姓名和电话号码列表,则可以执行一个返回 +{@link +android.database.Cursor}(包含对应每个人的行,以及对应姓名和号码的列)的查询。 +然后,您可以创建一个字符串数组,指定您想要在每个结果的布局中包含 {@link +android.database.Cursor} +中的哪些列,并创建一个整型数组,指定应该将每个列放入的对应视图:

+
+String[] fromColumns = {ContactsContract.Data.DISPLAY_NAME,
+                        ContactsContract.CommonDataKinds.Phone.NUMBER};
+int[] toViews = {R.id.display_name, R.id.phone_number};
+
+

当您实例化 {@link android.widget.SimpleCursorAdapter} +时,请传递要用于每个结果的布局、包含结果的 {@link android.database.Cursor} 以及以下两个数组:

+
+SimpleCursorAdapter adapter = new SimpleCursorAdapter(this,
+        R.layout.person_name_and_number, cursor, fromColumns, toViews, 0);
+ListView listView = getListView();
+listView.setAdapter(adapter);
+
+

然后,{@link android.widget.SimpleCursorAdapter} 会使用提供的布局,将每个 +{@code +fromColumns} 项插入对应的 {@code toViews} 视图,为 {@link android.database.Cursor} 中的每个行创建一个视图。

.
+
+ + +

如果您在应用的生命周期中更改了适配器读取的底层数据,则应调用 +{@link android.widget.ArrayAdapter#notifyDataSetChanged()}。此操作会通知附加的视图,数据发生了变化,它应该自行刷新。 +

+ + + +

处理点击事件

+ +

您可以通过实现 {@link android.widget.AdapterView.OnItemClickListener} +界面来响应 {@link android.widget.AdapterView} 中每一项上的点击事件。例如:

+ +
+// Create a message handling object as an anonymous class.
+private OnItemClickListener mMessageClickedHandler = new OnItemClickListener() {
+    public void onItemClick(AdapterView parent, View v, int position, long id) {
+        // Do something in response to the click
+    }
+};
+
+listView.setOnItemClickListener(mMessageClickedHandler);
+
+ + + diff --git a/docs/html-intl/intl/zh-cn/guide/topics/ui/dialogs.jd b/docs/html-intl/intl/zh-cn/guide/topics/ui/dialogs.jd new file mode 100644 index 0000000000000000000000000000000000000000..84922b40e4454db00e07d512e3457f274e70dc53 --- /dev/null +++ b/docs/html-intl/intl/zh-cn/guide/topics/ui/dialogs.jd @@ -0,0 +1,798 @@ +page.title=对话框 +page.tags=提醒对话框,对话框片段 + +@jd:body + + + +
+
+

本文内容

+
    +
  1. 创建对话框片段
  2. +
  3. 构建提醒对话框 +
      +
    1. 添加按钮
    2. +
    3. 添加列表
    4. +
    5. 创建自定义布局
    6. +
    +
  4. +
  5. 将事件传递回对话框的宿主
  6. +
  7. 显示对话框
  8. +
  9. 全屏显示对话框或将其显示为嵌入式片段 +
      +
    1. 将 Activity 显示为大屏幕上的对话框
    2. +
    +
  10. +
  11. 清除对话框
  12. +
+ +

关键类

+
    +
  1. {@link android.app.DialogFragment}
  2. +
  3. {@link android.app.AlertDialog}
  4. +
+ +

另请参阅

+
    +
  1. 对话框设计指南
  2. +
  3. 选取器(日期/时间对话框)
  4. +
+
+
+ +

对话框是提示用户作出决定或输入额外信息的小窗口。 +对话框不会填充屏幕,通常用于需要用户采取行动才能继续执行的模式事件。 +

+ +
+

对话框设计

+

如需了解有关如何设计对话框的信息(包括语言建议),请阅读对话框设计指南。 +

+
+ + + +

{@link android.app.Dialog} +类是对话框的基类,但您应该避免直接实例化 +{@link android.app.Dialog},而是使用下列子类之一:

+
+
{@link android.app.AlertDialog}
+
此对话框可显示标题、最多三个按钮、可选择项列表或自定义布局。 +
+
{@link android.app.DatePickerDialog} 或 {@link android.app.TimePickerDialog}
+
此对话框带有允许用户选择日期或时间的预定义 UI。
+
+ + + +

这些类定义您的对话框的样式和结构,但您应该将 +{@link android.support.v4.app.DialogFragment} +用作对话框的容器。{@link android.support.v4.app.DialogFragment} +类提供您创建对话框和管理其外观所需的所有控件,而不是调用 {@link android.app.Dialog} +对象上的方法。

+ +

使用 {@link android.support.v4.app.DialogFragment} +管理对话框可确保它能正确处理生命周期事件,如用户按“返回”按钮或旋转屏幕时。 +此外,{@link +android.support.v4.app.DialogFragment} 类还允许您将对话框的 UI 作为嵌入式组件在较大 UI 中重复使用,就像传统 {@link +android.support.v4.app.Fragment} +一样(例如,当您想让对话框 UI +在大屏幕和小屏幕上具有不同外观时)。

+ +

本指南的后文将描述如何将 {@link +android.support.v4.app.DialogFragment} 与 {@link android.app.AlertDialog} +对象结合使用。如果您想创建一个日期或时间选取器,应改为阅读选取器指南。 +

+ +

注:由于 +{@link android.app.DialogFragment} 类最初是通过 +Android 3.0(API 11 级)添加的,因此本文描述的是如何使用支持库附带的 {@link +android.support.v4.app.DialogFragment} 类。通过将该库添加到您的应用,您可以在运行 +Android 1.6 或更高版本的设备上使用 {@link android.support.v4.app.DialogFragment} 以及各种其他 +API。如果您的应用支持的最低版本是 +API 11 级或更高版本,则可使用 {@link +android.app.DialogFragment} 的框架版本,但请注意,本文中的链接适用于支持库 +API。使用支持库时,请确保您导入的是 +android.support.v4.app.DialogFragment +类,而“绝对不”android.app.DialogFragment

+ + +

创建对话框片段

+ +

您可以完成各种对话框设计—包括自定义布局以及对话框设计指南中描述的布局—通过扩展 +{@link android.support.v4.app.DialogFragment} +并在 +{@link android.support.v4.app.DialogFragment#onCreateDialog +onCreateDialog()} +回调方法中创建 {@link android.app.AlertDialog}。

+ +

例如,以下是一个在 +{@link android.support.v4.app.DialogFragment} 内管理的基础 {@link android.app.AlertDialog}:

+ +
+public class FireMissilesDialogFragment extends DialogFragment {
+    @Override
+    public Dialog onCreateDialog(Bundle savedInstanceState) {
+        // Use the Builder class for convenient dialog construction
+        AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+        builder.setMessage(R.string.dialog_fire_missiles)
+               .setPositiveButton(R.string.fire, new DialogInterface.OnClickListener() {
+                   public void onClick(DialogInterface dialog, int id) {
+                       // FIRE ZE MISSILES!
+                   }
+               })
+               .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
+                   public void onClick(DialogInterface dialog, int id) {
+                       // User cancelled the dialog
+                   }
+               });
+        // Create the AlertDialog object and return it
+        return builder.create();
+    }
+}
+
+ +
+ +

图 1. 一个包含消息和两个操作按钮的对话框。 +

+
+ +

现在,当您创建此类的实例并调用该对象上的 {@link +android.support.v4.app.DialogFragment#show show()} 时,对话框将如图 +1 所示。

+ +

下文将详细描述如何使用 {@link android.app.AlertDialog.Builder} +API 创建对话框。

+ +

根据对话框的复杂程度,您可以在 +{@link android.support.v4.app.DialogFragment} +中实现各种其他回调方法,包括所有基础 片段生命周期方法。 + + + + + +

构建提醒对话框

+ + +

您可以通过 {@link android.app.AlertDialog} +类构建各种对话框设计,并且该类通常是您需要的唯一对话框类。如图 2 +所示,提醒对话框有三个区域:

+ +
+ +

图 2. 对话框的布局。

+
+ +
    +
  1. 标题 +

    这是可选项,只应在内容区域被详细消息、列表或自定义布局占据时使用。 +如需陈述的是一条简单消息或问题(如图 1 中的对话框),则不需要标题。 +

  2. +
  3. 内容区域 +

    它可以显示消息、列表或其他自定义布局。

  4. +
  5. 操作按钮 +

    对话框中的操作按钮不应超过三个。

  6. +
+ +

{@link android.app.AlertDialog.Builder} +类提供的 API 允许您创建具有这几种内容(包括自定义布局)的 +{@link android.app.AlertDialog}。

+ +

要想构建 {@link android.app.AlertDialog},请执行以下操作:

+ +
+// 1. Instantiate an {@link android.app.AlertDialog.Builder} with its constructor
+AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+
+// 2. Chain together various setter methods to set the dialog characteristics
+builder.setMessage(R.string.dialog_message)
+       .setTitle(R.string.dialog_title);
+
+// 3. Get the {@link android.app.AlertDialog} from {@link android.app.AlertDialog.Builder#create()}
+AlertDialog dialog = builder.create();
+
+ +

以下主题介绍如何使用 +{@link android.app.AlertDialog.Builder} 类定义各种对话框属性。

+ + + + +

添加按钮

+ +

要想添加如图 2 +所示的操作按钮,请调用 {@link android.app.AlertDialog.Builder#setPositiveButton setPositiveButton()} 和 +{@link android.app.AlertDialog.Builder#setNegativeButton setNegativeButton()} 方法:

+ +
+AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+// Add the buttons
+builder.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
+           public void onClick(DialogInterface dialog, int id) {
+               // User clicked OK button
+           }
+       });
+builder.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
+           public void onClick(DialogInterface dialog, int id) {
+               // User cancelled the dialog
+           }
+       });
+// Set other dialog properties
+...
+
+// Create the AlertDialog
+AlertDialog dialog = builder.create();
+
+ +

set...Button() +方法需要一个按钮标题(由字符串资源提供)和一个 +{@link android.content.DialogInterface.OnClickListener},后者用于定义用户按下该按钮时执行的操作。 +

+ +

您可以添加三种不同的操作按钮:

+
+
肯定
+
您应该使用此按钮来接受并继续执行操作(“确定”操作)。
+
否定
+
您应该使用此按钮来取消操作。
+
中性
+
您应该在用户可能不想继续执行操作,但也不一定想要取消操作时使用此按钮。 +它出现在肯定按钮和否定按钮之间。 +例如,实际操作可能是“稍后提醒我”。
+
+ +

对于每种按钮类型,您只能为 {@link +android.app.AlertDialog} 添加一个该类型的按钮。也就是说,您不能添加多个“肯定”按钮。

+ + + +
+ +

图 3. 一个包含标题和列表的对话框。 +

+
+ +

添加列表

+ +

可通过 {@link android.app.AlertDialog} API 提供三种列表:

+
    +
  • 传统单选列表
  • +
  • 永久性单选列表(单选按钮)
  • +
  • 永久性多选列表(复选框)
  • +
+ +

要想创建如图 3 所示的单选列表,请使用 +{@link android.app.AlertDialog.Builder#setItems setItems()} 方法:

+ +
+@Override
+public Dialog onCreateDialog(Bundle savedInstanceState) {
+    AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+    builder.setTitle(R.string.pick_color)
+           .setItems(R.array.colors_array, new DialogInterface.OnClickListener() {
+               public void onClick(DialogInterface dialog, int which) {
+               // The 'which' argument contains the index position
+               // of the selected item
+           }
+    });
+    return builder.create();
+}
+
+ +

由于列表出现在对话框的内容区域,因此对话框无法同时显示消息和列表,您应该通过 +{@link android.app.AlertDialog.Builder#setTitle setTitle()} +为对话框设置标题。要想指定列表项,请调用 +{@link +android.app.AlertDialog.Builder#setItems setItems()} 来传递一个数组。或者,您也可以使用 +{@link +android.app.AlertDialog.Builder#setAdapter setAdapter()} 指定一个列表。这样一来,您就可以使用 {@link android.widget.ListAdapter} +以动态数据(如来自数据库的数据)支持列表。

+ +

如果您选择通过 {@link android.widget.ListAdapter} +支持列表,请务必使用 +{@link android.support.v4.content.Loader},以便内容以异步方式加载。使用适配器构建布局加载程序指南中对此做了进一步描述。 + + +

+ +

注:默认情况下,触摸列表项会清除对话框,除非您使用的是下列其中一种永久性选择列表。 +

+ +
+ +

图 4. +多选项列表。

+
+ + +

添加永久性多选列表或单选列表

+ +

要想添加多选项(复选框)或单选项(单选按钮)列表,请分别使用 +{@link android.app.AlertDialog.Builder#setMultiChoiceItems(Cursor,String,String, +DialogInterface.OnMultiChoiceClickListener) setMultiChoiceItems()} 或 +{@link android.app.AlertDialog.Builder#setSingleChoiceItems(int,int,DialogInterface.OnClickListener) +setSingleChoiceItems()} +方法。

+ +

例如,以下示例展示了如何创建如图 4 +所示的多选列表,将选定项保存在一个 +{@link java.util.ArrayList} 中:

+ +
+@Override
+public Dialog onCreateDialog(Bundle savedInstanceState) {
+    mSelectedItems = new ArrayList();  // Where we track the selected items
+    AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+    // Set the dialog title
+    builder.setTitle(R.string.pick_toppings)
+    // Specify the list array, the items to be selected by default (null for none),
+    // and the listener through which to receive callbacks when items are selected
+           .setMultiChoiceItems(R.array.toppings, null,
+                      new DialogInterface.OnMultiChoiceClickListener() {
+               @Override
+               public void onClick(DialogInterface dialog, int which,
+                       boolean isChecked) {
+                   if (isChecked) {
+                       // If the user checked the item, add it to the selected items
+                       mSelectedItems.add(which);
+                   } else if (mSelectedItems.contains(which)) {
+                       // Else, if the item is already in the array, remove it 
+                       mSelectedItems.remove(Integer.valueOf(which));
+                   }
+               }
+           })
+    // Set the action buttons
+           .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
+               @Override
+               public void onClick(DialogInterface dialog, int id) {
+                   // User clicked OK, so save the mSelectedItems results somewhere
+                   // or return them to the component that opened the dialog
+                   ...
+               }
+           })
+           .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
+               @Override
+               public void onClick(DialogInterface dialog, int id) {
+                   ...
+               }
+           });
+
+    return builder.create();
+}
+
+ +

尽管传统列表和具有单选按钮的列表都能提供“单选”操作,但如果您想持久保存用户的选择,则应使用 +{@link +android.app.AlertDialog.Builder#setSingleChoiceItems(int,int,DialogInterface.OnClickListener) +setSingleChoiceItems()}。也就是说,如果稍后再次打开对话框时系统应指示用户的当前选择,那么您就需要创建一个具有单选按钮的列表。 + +

+ + + + + +

创建自定义布局

+ +
+ +

图 5. 自定义对话框布局。

+
+ +

如果您想让对话框具有自定义布局,请创建一个布局,然后通过调用 +{@link +android.app.AlertDialog.Builder} 对象上的 {@link +android.app.AlertDialog.Builder#setView setView()} 将其添加到 {@link android.app.AlertDialog}。

+ +

默认情况下,自定义布局会填充对话框窗口,但您仍然可以使用 +{@link android.app.AlertDialog.Builder} 方法来添加按钮和标题。

+ +

例如,以下是图 5 中对话框的布局文件:

+ +

res/layout/dialog_signin.xml

+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content">
+    <ImageView
+        android:src="@drawable/header_logo"
+        android:layout_width="match_parent"
+        android:layout_height="64dp"
+        android:scaleType="center"
+        android:background="#FFFFBB33"
+        android:contentDescription="@string/app_name" />
+    <EditText
+        android:id="@+id/username"
+        android:inputType="textEmailAddress"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="16dp"
+        android:layout_marginLeft="4dp"
+        android:layout_marginRight="4dp"
+        android:layout_marginBottom="4dp"
+        android:hint="@string/username" />
+    <EditText
+        android:id="@+id/password"
+        android:inputType="textPassword"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="4dp"
+        android:layout_marginLeft="4dp"
+        android:layout_marginRight="4dp"
+        android:layout_marginBottom="16dp"
+        android:fontFamily="sans-serif"
+        android:hint="@string/password"/>
+</LinearLayout>
+
+ +

提示:默认情况下,当您将 {@link android.widget.EditText} +元素设置为使用 {@code "textPassword"} +输入类型时,字体系列将设置为固定宽度。因此,您应该将其字体系列更改为 +{@code "sans-serif"},以便两个文本字段都使用匹配的字体样式。

+ +

要扩展 {@link android.support.v4.app.DialogFragment} +中的布局,请通过 {@link android.app.Activity#getLayoutInflater()} +获取一个 {@link android.view.LayoutInflater} 并调用 +{@link android.view.LayoutInflater#inflate inflate()},其中第一个参数是布局资源 +ID,第二个参数是布局的父视图。然后,您可以调用 +{@link android.app.AlertDialog#setView setView()} +将布局放入对话框。

+ +
+@Override
+public Dialog onCreateDialog(Bundle savedInstanceState) {
+    AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+    // Get the layout inflater
+    LayoutInflater inflater = getActivity().getLayoutInflater();
+
+    // Inflate and set the layout for the dialog
+    // Pass null as the parent view because its going in the dialog layout
+    builder.setView(inflater.inflate(R.layout.dialog_signin, null))
+    // Add action buttons
+           .setPositiveButton(R.string.signin, new DialogInterface.OnClickListener() {
+               @Override
+               public void onClick(DialogInterface dialog, int id) {
+                   // sign in the user ...
+               }
+           })
+           .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
+               public void onClick(DialogInterface dialog, int id) {
+                   LoginDialogFragment.this.getDialog().cancel();
+               }
+           });      
+    return builder.create();
+}
+
+ +
+

提示:如果您想要自定义对话框,可以改用对话框的形式显示 {@link android.app.Activity},而不是使用 {@link android.app.Dialog} +API。 +只需创建一个 Activity,并在 +{@code +<activity>} 清单文件元素中将其主题设置为 +{@link android.R.style#Theme_Holo_Dialog Theme.Holo.Dialog}:

+ +
+<activity android:theme="@android:style/Theme.Holo.Dialog" >
+
+

就这么简单。Activity 现在会显示在一个对话框窗口中,而非全屏显示。

+
+ + + +

将事件传回给对话框的宿主

+ +

当用户触摸对话框的某个操作按钮或从列表中选择某一项时,您的 +{@link android.support.v4.app.DialogFragment} +可能会自行执行必要的操作,但通常您想将事件传递给打开该对话框的 Activity 或片段。 +为此,请定义一个界面,为每种点击事件定义一种方法。然后在从该对话框接收操作事件的宿主组件中实现该界面。 + +

+ +

例如,以下 {@link android.support.v4.app.DialogFragment} +定义了一个界面,通过该界面将事件传回给宿主 Activity:

+ +
+public class NoticeDialogFragment extends DialogFragment {
+    
+    /* The activity that creates an instance of this dialog fragment must
+     * implement this interface in order to receive event callbacks.
+     * Each method passes the DialogFragment in case the host needs to query it. */
+    public interface NoticeDialogListener {
+        public void onDialogPositiveClick(DialogFragment dialog);
+        public void onDialogNegativeClick(DialogFragment dialog);
+    }
+    
+    // Use this instance of the interface to deliver action events
+    NoticeDialogListener mListener;
+    
+    // Override the Fragment.onAttach() method to instantiate the NoticeDialogListener
+    @Override
+    public void onAttach(Activity activity) {
+        super.onAttach(activity);
+        // Verify that the host activity implements the callback interface
+        try {
+            // Instantiate the NoticeDialogListener so we can send events to the host
+            mListener = (NoticeDialogListener) activity;
+        } catch (ClassCastException e) {
+            // The activity doesn't implement the interface, throw exception
+            throw new ClassCastException(activity.toString()
+                    + " must implement NoticeDialogListener");
+        }
+    }
+    ...
+}
+
+ +

对话框的宿主 Activity 会通过对话框片段的构造函数创建一个对话框实例,并通过实现的 +{@code NoticeDialogListener} +界面接收对话框的事件:

+ +
+public class MainActivity extends FragmentActivity
+                          implements NoticeDialogFragment.NoticeDialogListener{
+    ...
+    
+    public void showNoticeDialog() {
+        // Create an instance of the dialog fragment and show it
+        DialogFragment dialog = new NoticeDialogFragment();
+        dialog.show(getSupportFragmentManager(), "NoticeDialogFragment");
+    }
+
+    // The dialog fragment receives a reference to this Activity through the
+    // Fragment.onAttach() callback, which it uses to call the following methods
+    // defined by the NoticeDialogFragment.NoticeDialogListener interface
+    @Override
+    public void onDialogPositiveClick(DialogFragment dialog) {
+        // User touched the dialog's positive button
+        ...
+    }
+
+    @Override
+    public void onDialogNegativeClick(DialogFragment dialog) {
+        // User touched the dialog's negative button
+        ...
+    }
+}
+
+ +

由于宿主 Activity 会实现 +{@code NoticeDialogListener}—由以上显示的 +{@link android.support.v4.app.Fragment#onAttach onAttach()} +回调方法强制执行 — 因此对话框片段可以使用界面回调方法向 Activity 传递点击事件:

+ +
+public class NoticeDialogFragment extends DialogFragment {
+    ...
+
+    @Override
+    public Dialog onCreateDialog(Bundle savedInstanceState) {
+        // Build the dialog and set up the button click handlers
+        AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+        builder.setMessage(R.string.dialog_fire_missiles)
+               .setPositiveButton(R.string.fire, new DialogInterface.OnClickListener() {
+                   public void onClick(DialogInterface dialog, int id) {
+                       // Send the positive button event back to the host activity
+                       mListener.onDialogPositiveClick(NoticeDialogFragment.this);
+                   }
+               })
+               .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
+                   public void onClick(DialogInterface dialog, int id) {
+                       // Send the negative button event back to the host activity
+                       mListener.onDialogNegativeClick(NoticeDialogFragment.this);
+                   }
+               });
+        return builder.create();
+    }
+}
+
+ + + +

显示对话框

+ +

如果您想显示对话框,请创建一个 {@link +android.support.v4.app.DialogFragment} 实例并调用 {@link android.support.v4.app.DialogFragment#show +show()},以传递对话框片段的 {@link android.support.v4.app.FragmentManager} +和标记名称。

+ +

您可以通过从 {@link android.support.v4.app.FragmentActivity} +调用 {@link android.support.v4.app.FragmentActivity#getSupportFragmentManager()} 或从 +{@link +android.support.v4.app.Fragment} 调用 {@link +android.support.v4.app.Fragment#getFragmentManager()} 来获取 {@link android.support.v4.app.FragmentManager}。例如:

+ +
+public void confirmFireMissiles() {
+    DialogFragment newFragment = new FireMissilesDialogFragment();
+    newFragment.show(getSupportFragmentManager(), "missiles");
+}
+
+ +

第二个参数 {@code "missiles"} +是系统用于保存片段状态并在必要时进行恢复的唯一标记名称。该标记还允许您通过调用 +{@link android.support.v4.app.FragmentManager#findFragmentByTag +findFragmentByTag()} 获取片段的句柄。

+ + + + +

全屏显示对话框或将其显示为嵌入式片段

+ +

您可能采用以下 UI +设计:您想让一部分 UI +在某些情况下显示为对话框,但在其他情况下全屏显示或显示为嵌入式片段(也许取决于设备使用大屏幕还是小屏幕)。{@link android.support.v4.app.DialogFragment} +类便具有这种灵活性,因为它仍然可以充当嵌入式 {@link +android.support.v4.app.Fragment}。

+ +

但在这种情况下,您不能使用 {@link android.app.AlertDialog.Builder AlertDialog.Builder} +或其他 {@link android.app.Dialog} 对象来构建对话框。如果您想让 {@link android.support.v4.app.DialogFragment} +具有嵌入能力,则必须在布局中定义对话框的 +UI,然后在 +{@link android.support.v4.app.DialogFragment#onCreateView +onCreateView()} 回调中加载布局。

+ +

以下示例 {@link android.support.v4.app.DialogFragment} 可以显示为对话框或嵌入式片段(使用名为 +purchase_items.xml 的布局):

+ +
+public class CustomDialogFragment extends DialogFragment {
+    /** The system calls this to get the DialogFragment's layout, regardless
+        of whether it's being displayed as a dialog or an embedded fragment. */
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+            Bundle savedInstanceState) {
+        // Inflate the layout to use as dialog or embedded fragment
+        return inflater.inflate(R.layout.purchase_items, container, false);
+    }
+  
+    /** The system calls this only when creating the layout in a dialog. */
+    @Override
+    public Dialog onCreateDialog(Bundle savedInstanceState) {
+        // The only reason you might override this method when using onCreateView() is
+        // to modify any dialog characteristics. For example, the dialog includes a
+        // title by default, but your custom layout might not need it. So here you can
+        // remove the dialog title, but you must call the superclass to get the Dialog.
+        Dialog dialog = super.onCreateDialog(savedInstanceState);
+        dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
+        return dialog;
+    }
+}
+
+ +

以下代码可根据屏幕尺寸决定将片段显示为对话框还是全屏 +UI:

+ +
+public void showDialog() {
+    FragmentManager fragmentManager = getSupportFragmentManager();
+    CustomDialogFragment newFragment = new CustomDialogFragment();
+    
+    if (mIsLargeLayout) {
+        // The device is using a large layout, so show the fragment as a dialog
+        newFragment.show(fragmentManager, "dialog");
+    } else {
+        // The device is smaller, so show the fragment fullscreen
+        FragmentTransaction transaction = fragmentManager.beginTransaction();
+        // For a little polish, specify a transition animation
+        transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
+        // To make it fullscreen, use the 'content' root view as the container
+        // for the fragment, which is always the root view for the activity
+        transaction.add(android.R.id.content, newFragment)
+                   .addToBackStack(null).commit();
+    }
+}
+
+ +

如需了解有关执行片段事务的详细信息,请参阅片段指南。 +

+ +

在本示例中,mIsLargeLayout +布尔值指定当前设备是否应该使用应用的大布局设计(进而将此片段显示为对话框,而不是全屏显示)。 +设置这种布尔值的最佳方法是声明一个布尔资源值,其中包含适用于不同屏幕尺寸的备用资源值。 + +例如,以下两个版本的布尔资源适用于不同的屏幕尺寸: +

+ +

res/values/bools.xml

+
+<!-- Default boolean values -->
+<resources>
+    <bool name="large_layout">false</bool>
+</resources>
+
+ +

res/values-large/bools.xml

+
+<!-- Large screen boolean values -->
+<resources>
+    <bool name="large_layout">true</bool>
+</resources>
+
+ +

然后,您可以在 Activity 的 +{@link android.app.Activity#onCreate onCreate()} 方法执行期间初始化 {@code mIsLargeLayout} 值:

+ +
+boolean mIsLargeLayout;
+
+@Override
+public void onCreate(Bundle savedInstanceState) {
+    super.onCreate(savedInstanceState);
+    setContentView(R.layout.activity_main);
+
+    mIsLargeLayout = getResources().getBoolean(R.bool.large_layout);
+}
+
+ + + +

在大屏幕上将 Activity 显示为对话框

+ +

相对于在小屏幕上将对话框显示为全屏 +UI,您可以通过在大屏幕上将 {@link android.app.Activity} +显示为对话框来达到相同的效果。您选择的方法取决于应用设计,但当应用已经针对小屏幕进行设计,而您想要通过将短生存期 Activity 显示为对话框来改善平板电脑体验时,将 Activity 显示为对话框往往很有帮助。 + + +

+ +

要想仅在大屏幕上将 Activity 显示为对话框,请将 +{@link android.R.style#Theme_Holo_DialogWhenLarge Theme.Holo.DialogWhenLarge} +主题应用于 {@code +<activity>} 清单文件元素:

+ +
+<activity android:theme="@android:style/Theme.Holo.DialogWhenLarge" >
+
+ +

如需了解有关通过主题设置 Activity 样式的详细信息,请参阅样式和主题指南。

+ + + +

清除对话框

+ +

当用户触摸使用 +{@link android.app.AlertDialog.Builder} 创建的任何操作按钮时,系统会为您清除对话框。

+ +

系统还会在用户触摸某个对话框列表项时清除对话框,但列表使用单选按钮或复选框时除外。 +否则,您可以通过在 {@link +android.support.v4.app.DialogFragment} 上调用 +{@link android.support.v4.app.DialogFragment#dismiss()} 来手动清除对话框。

+ +

如需在对话框消失时执行特定操作,则可以在您的 +{@link +android.support.v4.app.DialogFragment} 中实现 {@link +android.support.v4.app.DialogFragment#onDismiss onDismiss()} 方法。

+ +

您还可以取消对话框。这是一个特殊事件,它表示用户显式离开对话框,而不完成任务。 +如果用户按“返回”按钮,触摸对话框区域外部的屏幕,或者您在 {@link +android.app.Dialog} 上显式调用 {@link android.app.Dialog#cancel()}(例如,为了响应对话框中的“取消”按钮),就会发生这种情况。 + +

+ +

如上例所示,您可以通过在您的 {@link +android.support.v4.app.DialogFragment} 中实现 +{@link android.support.v4.app.DialogFragment#onCancel onCancel()} 来响应取消事件。

+ +

注:系统会在每个调用 {@link android.support.v4.app.DialogFragment#onCancel onCancel()} +回调的事件发生时立即调用 +{@link android.support.v4.app.DialogFragment#onDismiss onDismiss()}。不过,如果您调用 {@link android.app.Dialog#dismiss Dialog.dismiss()} 或 +{@link +android.support.v4.app.DialogFragment#dismiss DialogFragment.dismiss()},系统会调用 +{@link android.support.v4.app.DialogFragment#onDismiss onDismiss()},“而绝不会”调用 +{@link android.support.v4.app.DialogFragment#onCancel onCancel()}。因此,当用户在对话框中按“肯定”按钮,从视图中移除对话框时,您通常应该调用 +{@link android.support.v4.app.DialogFragment#dismiss dismiss()}。 +

+ + diff --git a/docs/html-intl/intl/zh-cn/guide/topics/ui/menus.jd b/docs/html-intl/intl/zh-cn/guide/topics/ui/menus.jd new file mode 100644 index 0000000000000000000000000000000000000000..b77f3bf497daa663f7332d0f0e6662050520e8c6 --- /dev/null +++ b/docs/html-intl/intl/zh-cn/guide/topics/ui/menus.jd @@ -0,0 +1,1031 @@ +page.title=菜单 +parent.title=用户界面 +parent.link=index.html +@jd:body + +
+
+

本文内容

+
    +
  1. 使用 XML 定义菜单
  2. +
  3. 创建选项菜单 +
      +
    1. 处理点击事件
    2. +
    3. 在运行时更改菜单项
    4. +
    +
  4. +
  5. 创建上下文菜单 +
      +
    1. 创建浮动上下文菜单
    2. +
    3. 使用上下文操作模式
    4. +
    +
  6. +
  7. 创建弹出菜单 +
      +
    1. 处理点击事件
    2. +
    +
  8. +
  9. 创建菜单组 +
      +
    1. 使用可选中的菜单项
    2. +
    +
  10. +
  11. 添加基于 Intent 的菜单项 +
      +
    1. 允许将 Activity 添加到其他菜单中
    2. +
    +
  12. +
+ +

关键类

+
    +
  1. {@link android.view.Menu}
  2. +
  3. {@link android.view.MenuItem}
  4. +
  5. {@link android.view.ContextMenu}
  6. +
  7. {@link android.view.ActionMode}
  8. +
+ +

另请参阅

+
    +
  1. 操作栏
  2. +
  3. 菜单资源
  4. +
  5. 从此告别菜单按钮 +
  6. +
+
+
+ +

菜单是许多应用中常见的用户界面组件。要提供熟悉而一致的用户体验,您应使用 {@link android.view.Menu} API +呈现 Activity 中的用户操作和其他选项。 +

+ +

从 +Android 3.0(API 级别 11)开始,采用 Android 技术的设备不必再提供一个专用“菜单”按钮。随着这种改变,Android +应用需摆脱对包含 +6 个项目的传统菜单面板的依赖,取而代之的是要提供一个操作栏来呈现常见的用户操作。

+ +

尽管某些菜单项的设计和用户体验已发生改变,但定义一系列操作和选项所使用的语义仍是以 +{@link android.view.Menu} API 为基础。本指南将介绍所有 +Android 版本系统中三种基本菜单或操作呈现效果的创建方法: +

+ +
+
选项菜单和操作栏
+
选项菜单是某个 Activity 的主菜单项, +供您放置对应用产生全局影响的操作,如“搜索”、“撰写电子邮件”和“设置”。 + +

如果您的应用是针对 +Android 2.3 或更低版本的系统而开发,则用户可以通过按“菜单”按钮显示选项菜单面板。

+

在 +Android 3.0 及更高版本的系统中,操作栏以屏幕操作项和溢出选项的组合形式呈现选项菜单中的各项。从 Android 3.0 开始,“菜单”按钮已弃用(某些设备没有该按钮),因此您应改为使用操作栏,来提供对操作和其他选项的访问。 + + +

+

请参阅创建选项菜单部分。

+
+ +
上下文菜单和上下文操作模式
+ +
上下文菜单是用户长按某一元素时出现的浮动菜单。 +它提供的操作将影响所选内容或上下文框架。 + +

开发针对 Android 3.0 及更高版本系统的应用时,您应改为使用上下文操作模式,以便对所选内容启用操作。此模式在屏幕顶部栏显示影响所选内容的操作项目,并允许用户选择多项。 + +

+

请参阅创建上下文菜单部分。

+
+ +
弹出菜单
+
弹出菜单将以垂直列表形式显示一系列项目,这些项目将锚定到调用该菜单的视图中。 +它特别适用于提供与特定内容相关的大量操作,或者为命令的另一部分提供选项。 +弹出菜单中的操作不会直接影响对应的内容,而上下文操作则会影响。 + +相反,弹出菜单适用于与您 Activity 中的内容区域相关的扩展操作。 + +

请参阅创建弹出菜单部分。

+
+
+ + + +

使用 XML 定义菜单

+ +

对于所有菜单类型,Android +提供了标准的 XML 格式来定义菜单项。您应在 +XML 菜单资源中定义菜单及其所有项,而不是在 Activity 的代码中构建菜单。定义后,您可以在 Activity 或片段中扩充菜单资源(将其作为 +{@link android.view.Menu} +对象加载)。

+ +

使用菜单资源是一种很好的做法,原因如下:

+
    +
  • 更易于使用 XML 可视化菜单结构
  • +
  • 将菜单内容与应用的行为代码分离
  • +
  • 允许您利用应用资源框架,为不同的平台版本、屏幕尺寸和其他配置创建备用菜单配置 +
  • +
+ +

要定义菜单,请在项目 res/menu/ +目录内创建一个 XML 文件,并使用以下元素构建菜单:

+
+
<menu>
+
定义 {@link android.view.Menu},即菜单项的容器。<menu> +元素必须是该文件的根节点,并且能够包含一个或多个 +<item><group> 元素。
+ +
<item>
+
创建 {@link android.view.MenuItem},此元素表示菜单中的一项,可能包含嵌套的 <menu> +元素,以便创建子菜单。
+ +
<group>
+
{@code <item>} 元素的不可见容器(可选)。它支持您对菜单项进行分类,使其共享活动状态和可见性等属性。 +如需了解详细信息,请参阅创建菜单组部分。 +
+
+ + +

以下是名为 game_menu.xml 的菜单示例:

+
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:id="@+id/new_game"
+          android:icon="@drawable/ic_new_game"
+          android:title="@string/new_game"
+          android:showAsAction="ifRoom"/>
+    <item android:id="@+id/help"
+          android:icon="@drawable/ic_help"
+          android:title="@string/help" />
+</menu>
+
+ +

<item> +元素支持多个属性,您可使用这些属性定义项目的外观和行为。上述菜单中的项目包括以下属性:

+ +
+
{@code android:id}
+
项目特有的资源 +ID,让应用能够在用户选择项目时识别该项目。
+
{@code android:icon}
+
引用一个要用作项目图标的 Drawable 类。
+
{@code android:title}
+
引用一个要用作项目标题的字符串。
+
{@code android:showAsAction}
+
指定此项应作为操作项目显示在操作栏中的时间和方式。
+
+ +

这些是您应使用的最重要属性,但还有许多其他属性。有关所有受支持属性的信息,请参阅菜单资源文档。 +

+ +

您可以通过以 {@code <item>} +子元素的形式添加 {@code <menu>} 元素,向任何菜单(子菜单除外)中的某个菜单项添加子菜单。当应用具有大量可按主题进行组织的功能时,类似于 PC 应用程序菜单栏中的菜单项(“文件”、“编辑”、“视图”等),子菜单非常有用。 + +例如:

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:id="@+id/file"
+          android:title="@string/file" >
+        <!-- "file" submenu -->
+        <menu>
+            <item android:id="@+id/create_new"
+                  android:title="@string/create_new" />
+            <item android:id="@+id/open"
+                  android:title="@string/open" />
+        </menu>
+    </item>
+</menu>
+
+ +

要在 Activity 中使用菜单,您需要使用 {@link android.view.MenuInflater#inflate(int,Menu) +MenuInflater.inflate()} 扩充菜单资源(将 XML +资源转换为可编程对象)。在下文中,您将了解如何扩充每种类型的菜单。 +

+ + + +

创建选项菜单

+ +
+ +

图 1. Android 2.3 系统上浏览器 +中的选项菜单。

+
+ +

在选项菜单中,您应当包括与当前 Activity 上下文相关的操作和其他选项,如“搜索”、“撰写电子邮件”和“设置”。 +

+ +

选项菜单中的项目在屏幕上的显示位置取决于您开发的应用所适用的 Android 版本: +

+ +
    +
  • 如果您开发的应用是用于 +Android +2.3.x(API 级别 10)或更低版本的系统,则当用户按“菜单”按钮时,选项菜单的内容会出现在屏幕底部,如图 1 所示。打开时,第一个可见部分是图标菜单,其中包含多达 6 个菜单项。 + +如果菜单包括 6 个以上项目,则 +Android +会将第六项和其余项目放入溢出菜单。用户可以通过选择“更多”打开该菜单。
  • + +
  • 如果您开发的应用是用于 Android +3.0(API 级别 11)及更高版本的系统,则选项菜单中的项目将出现在操作栏中。默认情况下,系统会将所有项目均放入操作溢出菜单中。用户可以使用操作栏右侧的操作溢出菜单图标(或者,通过按设备“菜单”按钮(如有))显示操作溢出菜单。 + +要支持快速访问重要操作,您可以将 +{@code android:showAsAction="ifRoom"} +添加到对应的 +{@code <item>} 元素,从而将几个项目提升到操作栏中(请参阅图 +2)。

    如需了解有关操作项目和其他操作栏行为的详细信息,请参阅操作栏指南。

    +

    注:即使您的应用不是针对 +Android 3.0 或更高版本的系统而开发,但仍可构建自己的操作栏布局,以获得类似的效果。有关如何使用操作栏支持旧版 +Android +的示例,请参阅操作栏兼容性示例。

    +
  • +
+ + +

图 2. 摘自 +Honeycomb Gallery 应用的操作栏,其中显示了导航选项卡和相机操作项目(以及操作溢出菜单按钮)。

+ +

您可以通过 {@link android.app.Activity} +子类或 {@link android.app.Fragment} 子类为选项菜单声明项目。如果您的 Activity 和片段均为选项菜单声明项目,则这些项目将合并到 +UI 中。系统将首先显示 Activity 的项目,随后按每个片段添加到 Activity 中的顺序显示该片段的项目。 + +如有必要,您可以使用 {@code android:orderInCategory} +属性,对需要移动的每个 {@code <item>} 中的菜单项重新排序。

+ +

要为 Activity 指定选项菜单,请重写 {@link +android.app.Activity#onCreateOptionsMenu(Menu) onCreateOptionsMenu()}(Fragment 会提供自己的 +{@link android.app.Fragment#onCreateOptionsMenu onCreateOptionsMenu()} 回调)。通过此方法,您可以将菜单资源(使用 +XML 定义)扩充到回调中提供的 {@link +android.view.Menu} 中。例如:

+ +
+@Override
+public boolean onCreateOptionsMenu(Menu menu) {
+    MenuInflater inflater = {@link android.app.Activity#getMenuInflater()};
+    inflater.inflate(R.menu.game_menu, menu);
+    return true;
+}
+
+ +

此外,您还可以使用 {@link android.view.Menu#add(int,int,int,int) +add()} 添加菜单项,并使用 +{@link android.view.Menu#findItem findItem()} 检索项目,以便使用 {@link android.view.MenuItem} API 修改其属性。

+ +

如果您开发的应用是用于 Android 2.3.x 及更低版本的系统,则当用户首次打开选项菜单时,系统会调用 {@link +android.app.Activity#onCreateOptionsMenu(Menu) onCreateOptionsMenu()} +来创建该菜单。如果您开发的应用是用于 +Android 3.0 及更高版本的系统,则系统将在启动 Activity 时调用 +{@link android.app.Activity#onCreateOptionsMenu(Menu) onCreateOptionsMenu()},以便向操作栏显示项目。

+ + + +

处理点击事件

+ +

用户从选项菜单中选择项目(包括操作栏中的操作项目)时,系统将调用 Activity 的 +{@link android.app.Activity#onOptionsItemSelected(MenuItem) +onOptionsItemSelected()} 方法。此方法将传递所选的 {@link android.view.MenuItem}。您可以通过调用 +{@link android.view.MenuItem#getItemId()} 方法来识别项目,该方法将返回菜单项的唯一 ID(由菜单资源中的 {@code android:id} 属性定义,或通过提供给 {@link android.view.Menu#add(int,int,int,int) add()} +方法的整型数定义)。 +您可以将此 +ID 与已知的菜单项匹配,以执行适当的操作。例如:

+ +
+@Override
+public boolean onOptionsItemSelected(MenuItem item) {
+    // Handle item selection
+    switch (item.getItemId()) {
+        case R.id.new_game:
+            newGame();
+            return true;
+        case R.id.help:
+            showHelp();
+            return true;
+        default:
+            return super.onOptionsItemSelected(item);
+    }
+}
+
+ +

成功处理菜单项后,系统将返回 {@code true}。如果未处理菜单项,则应调用 {@link +android.app.Activity#onOptionsItemSelected(MenuItem) onOptionsItemSelected()} +的超类实现(默认实现将返回 false)。 +

+ +

如果 Activity 包括片段,则系统将依次为 Activity 和每个片段(按照每个片段的添加顺序)调用 {@link +android.app.Activity#onOptionsItemSelected(MenuItem) onOptionsItemSelected()} +,直到有一返回结果为 +{@code true} 或所有片段均调用完毕为止。

+ +

提示:Android 3.0 新增了一项功能,支持您在 XML 中使用 {@code android:onClick} +属性为菜单项定义点击行为。该属性的值必须是 Activity 使用菜单定义的方法的名称。 +该方法必须是公用的,且接受单个 +{@link android.view.MenuItem} +参数;当系统调用此方法时,它会传递所选的菜单项。如需了解详细信息和示例,请参阅菜单资源文档。

+ +

提示:如果应用包含多个 Activity,且其中某些 Activity 提供相同的选项菜单,则可考虑创建一个仅实现 +{@link android.app.Activity#onCreateOptionsMenu(Menu) +onCreateOptionsMenu()} 和 {@link android.app.Activity#onOptionsItemSelected(MenuItem) +onOptionsItemSelected()} +方法的 Activity。然后,为每个应共享相同选项菜单的 Activity 扩展此类。 +通过这种方式,您可以管理一个用于处理菜单操作的代码集,且每个子级类均会继承菜单行为。若要将菜单项添加到一个子级 Activity,请重写该 Activity 中的 +{@link android.app.Activity#onCreateOptionsMenu(Menu) +onCreateOptionsMenu()}。 + +调用 {@code super.onCreateOptionsMenu(menu)},以便创建原始菜单项,然后使用 +{@link +android.view.Menu#add(int,int,int,int) menu.add()} 添加新菜单项。此外,您还可以替代各个菜单项的超类行为。 +

+ + +

在运行时更改菜单项

+ +

系统调用 +{@link android.app.Activity#onCreateOptionsMenu(Menu) +onCreateOptionsMenu()} 后,将保留您填充的 {@link android.view.Menu} 实例。除非菜单由于某些原因而失效,否则不会再次调用 +{@link android.app.Activity#onCreateOptionsMenu(Menu) onCreateOptionsMenu()}。但是,您仅应使用 +{@link +android.app.Activity#onCreateOptionsMenu(Menu) onCreateOptionsMenu()} 来创建初始菜单状态,而不应使用它在 Activity 生命周期中执行任何更改。

+ +

如需根据在 Activity 生命周期中发生的事件修改选项菜单,则可通过 +{@link android.app.Activity#onPrepareOptionsMenu(Menu) onPrepareOptionsMenu()} +方法执行此操作。此方法向您传递 +{@link android.view.Menu} +对象(因为该对象目前存在),以便您能够对其进行修改,如添加、删除或禁用项目。(此外,片段还提供 {@link +android.app.Fragment#onPrepareOptionsMenu onPrepareOptionsMenu()} 回调。)

+ +

在 +Android 2.3.x 及更低版本中,每当用户打开选项菜单时(按“菜单”按钮),系统均会调用 {@link +android.app.Activity#onPrepareOptionsMenu(Menu) +onPrepareOptionsMenu()}。

+ +

在 +Android 3.0 及更高版本中,当菜单项显示在操作栏中时,选项菜单被视为始终处于打开状态。发生事件时,如果您要执行菜单更新,则必须调用 +{@link android.app.Activity#invalidateOptionsMenu invalidateOptionsMenu()} 来请求系统调用 +{@link android.app.Activity#onPrepareOptionsMenu(Menu) onPrepareOptionsMenu()}。

+ +

注:切勿根据目前处于焦点的 +{@link android.view.View} +更改选项菜单中的项目。处于触摸模式(用户未使用轨迹球或方向键)时,视图无法形成焦点,因此切勿根据焦点修改选项菜单中的项目。 + +若要为 {@link +android.view.View} 提供上下文相关的菜单项,请使用上下文菜单

+ + + + +

创建上下文菜单

+ +
+ +

图 3. 浮动上下文菜单(左)和上下文操作栏(右)的屏幕截图。 +

+
+ +

上下文菜单提供了许多操作,这些操作影响 UI 中的特定项目或上下文框架。您可以为任何视图提供上下文菜单,但这些菜单通常用于 +{@link +android.widget.ListView}、{@link android.widget.GridView} +或用户可直接操作每个项目的其他视图集合中的项目。

+ +

提供上下文操作的方法有两种:

+
    +
  • 使用浮动上下文菜单。用户长按(按住)一个声明支持上下文菜单的视图时,菜单显示为菜单项的浮动列表(类似于对话框)。 + +用户一次可对一个项目执行上下文操作。 +
  • + +
  • 使用上下文操作模式。此模式是 +{@link android.view.ActionMode} +的系统实现,它将在屏幕顶部显示上下文操作栏,其中包括影响所选项的操作项目。当此模式处于活动状态时,用户可以同时对多项执行操作(如果应用允许)。 +
  • +
+ +

注:上下文操作模式可用于 +Android 3.0(API +级别 11)及更高版本的系统,是显示上下文操作(如果可用)的首选方法。如果应用支持低于 +3.0 版本的系统,则应在这些设备上回退到浮动上下文菜单。

+ + +

创建浮动上下文菜单

+ +

要提供浮动上下文菜单,请执行以下操作:

+
    +
  1. 通过调用 +{@link android.app.Activity#registerForContextMenu(View) registerForContextMenu()},注册应与上下文菜单关联的 +{@link android.view.View} 并将其传递给 {@link android.view.View}。 +

    如果 Activity 使用 {@link android.widget.ListView} 或 {@link android.widget.GridView} +且您希望每个项目均提供相同的上下文菜单,请通过将 +{@link android.widget.ListView} 或 {@link android.widget.GridView} 传递给 {@link +android.app.Activity#registerForContextMenu(View) registerForContextMenu()},为上下文菜单注册所有项目。

    +
  2. + +
  3. 在 {@link android.app.Activity} 或 {@link android.app.Fragment} 中实现 {@link +android.view.View.OnCreateContextMenuListener#onCreateContextMenu onCreateContextMenu()} +方法。 +

    当注册后的视图收到长按事件时,系统将调用您的 {@link +android.view.View.OnCreateContextMenuListener#onCreateContextMenu onCreateContextMenu()} +方法。在此方法中,您通常可通过扩充菜单资源来定义菜单项。例如: +

    +
    +@Override
    +public void onCreateContextMenu(ContextMenu menu, View v,
    +                                ContextMenuInfo menuInfo) {
    +    super.onCreateContextMenu(menu, v, menuInfo);
    +    MenuInflater inflater = getMenuInflater();
    +    inflater.inflate(R.menu.context_menu, menu);
    +}
    +
    + +

    {@link android.view.MenuInflater} 允许您通过菜单资源扩充上下文菜单。回调方法参数包括用户所选的 +{@link android.view.View},以及一个提供有关所选项的附加信息的 +{@link android.view.ContextMenu.ContextMenuInfo} +对象。如果 Activity 有多个视图,每个视图均提供不同的上下文菜单,则可使用这些参数确定要扩充的上下文菜单。 + +

    +
  4. + +
  5. 实现 {@link android.app.Activity#onContextItemSelected(MenuItem) +onContextItemSelected()}。 +

    用户选择菜单项时,系统将调用此方法,以便您能够执行适当的操作。 +例如:

    + +
    +@Override
    +public boolean onContextItemSelected(MenuItem item) {
    +    AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo();
    +    switch (item.getItemId()) {
    +        case R.id.edit:
    +            editNote(info.id);
    +            return true;
    +        case R.id.delete:
    +            deleteNote(info.id);
    +            return true;
    +        default:
    +            return super.onContextItemSelected(item);
    +    }
    +}
    +
    + +

    {@link android.view.MenuItem#getItemId()} +方法将查询所选菜单项的 ID,您应使用 {@code +android:id} 属性将此 ID 分配给 XML 中的每个菜单项,如使用 +XML 定义菜单部分所示。

    + +

    成功处理菜单项后,系统将返回 {@code true}。如果未处理菜单项,则应将菜单项传递给超类实现。 +如果 Activity 包括片段,则 Activity 将先收到此回调。 +通过在未处理的情况下调用超类,系统将事件逐一传递给每个片段中相应的回调方法(按照每个片段的添加顺序),直到返回 +{@code true} 或 {@code false} +为止。({@link android.app.Activity} +和 {@code android.app.Fragment} 的默认实现返回 {@code +false},因此您始终应在未处理的情况下调用超类。)

    +
  6. +
+ + +

使用上下文操作模式

+ +

上下文操作模式是 +{@link android.view.ActionMode} 的一种系统实现,它将用户交互的重点转到执行上下文操作上。用户通过选择项目启用此模式时,屏幕顶部将出现一个“上下文操作栏”,显示用户可对当前所选项执行的操作。 + +启用此模式后,用户可以选择多个项目(若您允许)、取消选择项目以及继续在 Activity 内导航(在您允许的最大范围内)。 + +当用户取消选择所有项目、按“返回”按钮或选择操作栏左侧的“完成”操作时,该操作模式将会禁用,且上下文操作栏将会消失。 + +

+ +

注:上下文操作栏不一定与操作栏相关联。 +尽管表面上看来上下文操作栏取代了操作栏的位置,但事实上二者独立运行。 + +

+ +

如果您的应用是针对 +Android 3.0(API 级别 11)或更高版本的系统而开发,则通常应使用上下文操作模式(而不是浮动上下文菜单)显示上下文操作。

+ +

对于提供上下文操作的视图,当出现以下两个事件(或之一)时,您通常应调用上下文操作模式: +

+
    +
  • 用户长按视图。
  • +
  • 用户选中复选框或视图内的类似 UI 组件。
  • +
+ +

应用如何调用上下文操作模式以及如何定义每个操作的行为,具体取决于您的设计。 +设计基本上分为两种:

+
    +
  • 针对单个任意视图的上下文操作。
  • +
  • 针对 {@link +android.widget.ListView} 或 {@link android.widget.GridView} +中项目组的批处理上下文操作(允许用户选择多个项目并针对所有项目执行操作)。
  • +
+ +

下文介绍每种场景所需的设置。

+ + +

为单个视图启用上下文操作模式

+ +

如果希望仅当用户选择特定视图时才调用上下文操作模式,则应: +

+
    +
  1. 实现 {@link android.view.ActionMode.Callback} 接口。在其回调方法中,您既可以为上下文操作栏指定操作,又可以响应操作项目的点击事件,还可以处理操作模式的其他生命周期事件。 + +
  2. +
  3. 当需要显示操作栏时(例如,用户长按视图),请调用 +{@link android.app.Activity#startActionMode startActionMode()}。
  4. +
+ +

例如:

+ +
    +
  1. 实现 {@link android.view.ActionMode.Callback ActionMode.Callback} 接口: +
    +private ActionMode.Callback mActionModeCallback = new ActionMode.Callback() {
    +
    +    // Called when the action mode is created; startActionMode() was called
    +    @Override
    +    public boolean onCreateActionMode(ActionMode mode, Menu menu) {
    +        // Inflate a menu resource providing context menu items
    +        MenuInflater inflater = mode.getMenuInflater();
    +        inflater.inflate(R.menu.context_menu, menu);
    +        return true;
    +    }
    +
    +    // Called each time the action mode is shown. Always called after onCreateActionMode, but
    +    // may be called multiple times if the mode is invalidated.
    +    @Override
    +    public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
    +        return false; // Return false if nothing is done
    +    }
    +
    +    // Called when the user selects a contextual menu item
    +    @Override
    +    public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
    +        switch (item.getItemId()) {
    +            case R.id.menu_share:
    +                shareCurrentItem();
    +                mode.finish(); // Action picked, so close the CAB
    +                return true;
    +            default:
    +                return false;
    +        }
    +    }
    +
    +    // Called when the user exits the action mode
    +    @Override
    +    public void onDestroyActionMode(ActionMode mode) {
    +        mActionMode = null;
    +    }
    +};
    +
    + +

    请注意,这些事件回调与选项菜单的回调几乎完全相同,只是其中每个回调还会传递与事件相关联的 {@link +android.view.ActionMode} 对象。您可以使用 +{@link +android.view.ActionMode} API 对 CAB 进行各种更改,例如:使用 {@link android.view.ActionMode#setTitle setTitle()} +和 {@link +android.view.ActionMode#setSubtitle setSubtitle()}(这对指示要选择多少个项目非常有用)修改标题和副标题。

    + +

    另请注意,操作模式被销毁时,上述示例会将 {@code mActionMode} +变量设置为 null。在下一步中,您将了解如何初始化该变量,以及保存 Activity 或片段中的成员变量有何作用。 +

    +
  2. + +
  3. 调用 {@link android.app.Activity#startActionMode startActionMode()} +以便适时启用上下文操作模式,例如:响应对 {@link +android.view.View} 的长按操作:

    + +
    +someView.setOnLongClickListener(new View.OnLongClickListener() {
    +    // Called when the user long-clicks on someView
    +    public boolean onLongClick(View view) {
    +        if (mActionMode != null) {
    +            return false;
    +        }
    +
    +        // Start the CAB using the ActionMode.Callback defined above
    +        mActionMode = getActivity().startActionMode(mActionModeCallback);
    +        view.setSelected(true);
    +        return true;
    +    }
    +});
    +
    + +

    当您调用 {@link android.app.Activity#startActionMode startActionMode()} 时,系统将返回已创建的 +{@link android.view.ActionMode}。通过将其保存在成员变量中,您可以更改上下文操作栏来响应其他事件。 +在上述示例中, +{@link android.view.ActionMode} +用于在启动操作模式之前检查成员是否为空,以确保当 {@link android.view.ActionMode} +实例已激活时不再重建该实例。

    +
  4. +
+ + + +

在 ListView 或 GridView 中启用批处理上下文操作

+ +

如果您在 {@link android.widget.ListView} 或 {@link +android.widget.GridView} 中有一组项目(或 {@link android.widget.AbsListView} +的其他扩展),且需要允许用户执行批处理操作,则应:

+ +
    +
  • 实现 +{@link android.widget.AbsListView.MultiChoiceModeListener} 接口,并使用 {@link android.widget.AbsListView#setMultiChoiceModeListener +setMultiChoiceModeListener()} 为视图组设置该接口。在侦听器的回调方法中,您既可以为上下文操作栏指定操作,也可以响应操作项目的点击事件,还可以处理从 +{@link android.view.ActionMode.Callback} +接口继承的其他回调。
  • + +
  • 使用 {@link +android.widget.AbsListView#CHOICE_MODE_MULTIPLE_MODAL} 参数调用 {@link android.widget.AbsListView#setChoiceMode setChoiceMode()}。
  • +
+ +

例如:

+ +
+ListView listView = getListView();
+listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL);
+listView.setMultiChoiceModeListener(new MultiChoiceModeListener() {
+
+    @Override
+    public void onItemCheckedStateChanged(ActionMode mode, int position,
+                                          long id, boolean checked) {
+        // Here you can do something when items are selected/de-selected,
+        // such as update the title in the CAB
+    }
+
+    @Override
+    public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
+        // Respond to clicks on the actions in the CAB
+        switch (item.getItemId()) {
+            case R.id.menu_delete:
+                deleteSelectedItems();
+                mode.finish(); // Action picked, so close the CAB
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    @Override
+    public boolean onCreateActionMode(ActionMode mode, Menu menu) {
+        // Inflate the menu for the CAB
+        MenuInflater inflater = mode.getMenuInflater();
+        inflater.inflate(R.menu.context, menu);
+        return true;
+    }
+
+    @Override
+    public void onDestroyActionMode(ActionMode mode) {
+        // Here you can make any necessary updates to the activity when
+        // the CAB is removed. By default, selected items are deselected/unchecked.
+    }
+
+    @Override
+    public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
+        // Here you can perform updates to the CAB due to
+        // an {@link android.view.ActionMode#invalidate} request
+        return false;
+    }
+});
+
+ +

就这么简单。现在,当用户通过长按选择项目时,系统即会调用 {@link +android.widget.AbsListView.MultiChoiceModeListener#onCreateActionMode onCreateActionMode()} +方法,并显示包含指定操作的上下文操作栏。当上下文操作栏可见时,用户可以选择其他项目。 +

+ +

在某些情况下,如果上下文操作提供常用的操作项目,则您可能需要添加一个复选框或类似的 +UI +元素来支持用户选择项目,这是因为他们可能没有发现长按行为。用户选中该复选框时,您可以通过使用 +{@link android.widget.AbsListView#setItemChecked setItemChecked()} +将相应的列表项设置为选中状态,以此调用上下文操作模式。

+ + + + +

创建弹出菜单

+ +
+ +

图 4. Gmail 应用中的弹出菜单,锚定到右上角的溢出按钮。 +

+
+ +

{@link android.widget.PopupMenu} +是锚定到 {@link android.view.View} 的模态菜单。如果空间足够,它将显示在定位视图下方,否则显示在其上方。它适用于:

+
    +
  • 为与特定内容确切相关的操作提供溢出样式菜单(例如,Gmail +的电子邮件标头,如图 4 所示)。 +

    注:这与上下文菜单不同,后者通常用于影响所选内容的操作。 +对于影响所选内容的操作,请使用上下文操作模式浮动上下文菜单。 +

  • +
  • 提供命令语句的另一部分(例如,标记为“添加”且使用不同的“添加”选项生成弹出菜单的按钮)。 +
  • +
  • 提供类似于 +{@link android.widget.Spinner} 且不保留永久选择的下拉菜单。
  • +
+ + +

注:{@link android.widget.PopupMenu} 在 API +级别 11 及更高版本中可用。

+ +

如果使用 XML 定义菜单,则显示弹出菜单的方法如下:

+
    +
  1. 实例化 +{@link android.widget.PopupMenu} 及其构造函数,该函数将提取当前应用的 {@link android.content.Context} 以及菜单应锚定到的 +{@link android.view.View}。
  2. +
  3. 使用 {@link android.view.MenuInflater} 将菜单资源扩充到 {@link +android.widget.PopupMenu#getMenu() PopupMenu.getMenu()} 返回的 {@link +android.view.Menu} 对象中。在 API 级别 14 及更高版本中,您可以改为使用 +{@link android.widget.PopupMenu#inflate PopupMenu.inflate()}。
  4. +
  5. 调用 {@link android.widget.PopupMenu#show() PopupMenu.show()}。
  6. +
+ +

例如,以下是一个使用 +{@link android.R.attr#onClick android:onClick} 属性显示弹出菜单的按钮:

+ +
+<ImageButton
+    android:layout_width="wrap_content" 
+    android:layout_height="wrap_content" 
+    android:src="@drawable/ic_overflow_holo_dark"
+    android:contentDescription="@string/descr_overflow_button"
+    android:onClick="showPopup" />
+
+ +

稍后,Activity 可按照如下方式显示弹出菜单:

+ +
+public void showPopup(View v) {
+    PopupMenu popup = new PopupMenu(this, v);
+    MenuInflater inflater = popup.getMenuInflater();
+    inflater.inflate(R.menu.actions, popup.getMenu());
+    popup.show();
+}
+
+ +

在 API 级别 14 及更高版本中,您可以将两行合在一起,使用 {@link +android.widget.PopupMenu#inflate PopupMenu.inflate()} 扩充菜单。

+ +

当用户选择项目或触摸菜单以外的区域时,系统即会清除此菜单。 +您可使用 {@link +android.widget.PopupMenu.OnDismissListener} 侦听清除事件。

+ +

处理点击事件

+ +

要在用户选择菜单项时执行操作,您必须实现 +{@link +android.widget.PopupMenu.OnMenuItemClickListener} 接口,并通过调用 {@link android.widget.PopupMenu#setOnMenuItemClickListener +setOnMenuItemclickListener()} 将其注册到 {@link +android.widget.PopupMenu}。用户选择项目时,系统会在接口中调用 {@link +android.widget.PopupMenu.OnMenuItemClickListener#onMenuItemClick onMenuItemClick()} +回调。

+ +

例如:

+ +
+public void showMenu(View v) {
+    PopupMenu popup = new PopupMenu(this, v);
+
+    // This activity implements OnMenuItemClickListener
+    popup.setOnMenuItemClickListener(this);
+    popup.inflate(R.menu.actions);
+    popup.show();
+}
+
+@Override
+public boolean onMenuItemClick(MenuItem item) {
+    switch (item.getItemId()) {
+        case R.id.archive:
+            archive(item);
+            return true;
+        case R.id.delete:
+            delete(item);
+            return true;
+        default:
+            return false;
+    }
+}
+
+ + +

创建菜单组

+ +

菜单组是指一系列具有某些共同特征的菜单项。通过菜单组,您可以: +

+
    +
  • 使用 {@link android.view.Menu#setGroupVisible(int,boolean) +setGroupVisible()} 显示或隐藏所有项目
  • +
  • 使用 {@link android.view.Menu#setGroupEnabled(int,boolean) +setGroupEnabled()} 启用或禁用所有项目
  • +
  • 使用 {@link +android.view.Menu#setGroupCheckable(int,boolean,boolean) setGroupCheckable()} 指定所有项目是否可选中
  • +
+ +

通过将 {@code <item>} 元素嵌套在菜单资源中的 {@code <group>} +元素内,或者通过使用 {@link +android.view.Menu#add(int,int,int,int) add()} 方法指定组 ID,您可以创建组。

+ +

以下是包含组的菜单资源示例:

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:id="@+id/menu_save"
+          android:icon="@drawable/menu_save"
+          android:title="@string/menu_save" />
+    <!-- menu group -->
+    <group android:id="@+id/group_delete">
+        <item android:id="@+id/menu_archive"
+              android:title="@string/menu_archive" />
+        <item android:id="@+id/menu_delete"
+              android:title="@string/menu_delete" />
+    </group>
+</menu>
+
+ +

组中的项目出现在与第一项相同的级别,即:菜单中的所有三项均为同级。 +但是,您可以通过引用组 ID +并使用上面列出的方法,修改组中两项的特征。此外,系统也绝不会分离已分组的项目。 +例如,如果为每个项目声明 +{@code +android:showAsAction="ifRoom"},则它们会同时显示在操作栏或操作溢出菜单中。

+ + +

使用可选中的菜单项

+ +
+ +

图 5. 含可选中项目的子菜单的屏幕截图。 +

+
+ +

作为启用/禁用选项的接口,菜单非常实用,既可针对独立选项使用复选框,也可针对互斥选项组使用单选按钮。 + +图 5 显示了一个子菜单,其中的项目可使用单选按钮选中。 +

+ +

注:“图标菜单”(在选项菜单中)的菜单项无法显示复选框或单选按钮。 +如果您选择使“图标菜单”中的项目可选中,则必须在选中状态每次发生变化时交换图标和/或文本,手动指出该状态。 + +

+ +

您可以使用 {@code <item>} 元素中的 {@code +android:checkable} 属性为各个菜单项定义可选中的行为,或者使用 {@code <group>} 元素中的 +{@code android:checkableBehavior} 属性为整个组定义可选中的行为。例如,此菜单组中的所有项目均可使用单选按钮选中: +

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+    <group android:checkableBehavior="single">
+        <item android:id="@+id/red"
+              android:title="@string/red" />
+        <item android:id="@+id/blue"
+              android:title="@string/blue" />
+    </group>
+</menu>
+
+ +

{@code android:checkableBehavior} 属性接受以下任一选项: +

+
{@code single}
+
组中只有一个项目可以选中(单选按钮)
+
{@code all}
+
所有项目均可选中(复选框)
+
{@code none}
+
所有项目均无法选中
+
+ +

您可以使用 {@code <item>} +元素中的 {@code android:checked} 属性将默认的选中状态应用于项目,并可使用 {@link +android.view.MenuItem#setChecked(boolean) setChecked()} 方法在代码中更改此默认状态。

+ +

选择可选中项目后,系统将调用所选项目的相应回调方法(例如,{@link android.app.Activity#onOptionsItemSelected(MenuItem) onOptionsItemSelected()})。 +此时,您必须设置复选框的状态,因为复选框或单选按钮不会自动更改其状态。 + +您可以使用 {@link android.view.MenuItem#isChecked()} +查询项目的当前状态(正如用户选择该项目之前一样),然后使用 +{@link android.view.MenuItem#setChecked(boolean) setChecked()} 设置选中状态。例如:

+ +
+@Override
+public boolean onOptionsItemSelected(MenuItem item) {
+    switch (item.getItemId()) {
+        case R.id.vibrate:
+        case R.id.dont_vibrate:
+            if (item.isChecked()) item.setChecked(false);
+            else item.setChecked(true);
+            return true;
+        default:
+            return super.onOptionsItemSelected(item);
+    }
+}
+
+ +

如果未通过这种方式设置选中状态,则项目的可见状态(复选框或单选按钮)不会因为用户选择它而发生变化。 + +如果已设置该状态,则 Activity 会保留项目的选中状态。这样一来,当用户稍后打开菜单时,您设置的选中状态将会可见。 + +

+ +

注:可选中菜单项的使用往往因会话而异,且在应用销毁后不予保存。 + +如果您想为用户保存某些应用设置,则应使用共享首选项存储数据。 +

+ + + +

添加基于 Intent 的菜单项

+ +

有时,您希望菜单项通过使用 +{@link android.content.Intent} 启动 Activity(无论该 Activity 是位于您的应用还是其他应用中)。如果您知道自己要使用的 Intent,且具有启动 Intent 的特定菜单项,则可在相应的 +on-item-selected 回调方法(例如,{@link +android.app.Activity#onOptionsItemSelected(MenuItem) onOptionsItemSelected()} 回调)期间使用 +{@link android.app.Activity#startActivity(Intent) startActivity()} +执行 Intent。

+ +

但是,如果不确定用户的设备是否包含可处理 Intent 的应用,则添加调用 Intent 的菜单项可能会导致该菜单项无法正常工作,这是因为 Intent 可能无法解析为 Activity。 + + +为了解决这一问题,当 +Android 在设备上找到可处理 Intent 的 Activity 时,则允许您向菜单动态添加菜单项。

+ +

要根据接受 Intent 的可用 Activity 添加菜单项,请执行以下操作:

+
    +
  1. 使用类别 +{@link android.content.Intent#CATEGORY_ALTERNATIVE} 和/或 +{@link android.content.Intent#CATEGORY_SELECTED_ALTERNATIVE} 以及任何其他要求定义 Intent。
  2. +
  3. 调用 {@link +android.view.Menu#addIntentOptions(int,int,int,ComponentName,Intent[],Intent,int,MenuItem[]) +Menu.addIntentOptions()}。Android +随后即会搜索能够执行 Intent 的所有应用,并将其添加到菜单中。
  4. +
+ +

如果未安装可处理 Intent 的应用,则不会添加任何菜单项。 +

+ +

注: +{@link android.content.Intent#CATEGORY_SELECTED_ALTERNATIVE} +用于处理屏幕上当前所选的元素。因此,只有在 {@link +android.app.Activity#onCreateContextMenu(ContextMenu,View,ContextMenuInfo) +onCreateContextMenu()} 中创建菜单时,才能使用它。

+ +

例如:

+ +
+@Override
+public boolean onCreateOptionsMenu(Menu menu){
+    super.onCreateOptionsMenu(menu);
+
+    // Create an Intent that describes the requirements to fulfill, to be included
+    // in our menu. The offering app must include a category value of Intent.CATEGORY_ALTERNATIVE.
+    Intent intent = new Intent(null, dataUri);
+    intent.addCategory(Intent.CATEGORY_ALTERNATIVE);
+
+    // Search and populate the menu with acceptable offering applications.
+    menu.addIntentOptions(
+         R.id.intent_group,  // Menu group to which new items will be added
+         0,      // Unique item ID (none)
+         0,      // Order for the items (none)
+         this.getComponentName(),   // The current activity name
+         null,   // Specific items to place first (none)
+         intent, // Intent created above that describes our requirements
+         0,      // Additional flags to control items (none)
+         null);  // Array of MenuItems that correlate to specific items (none)
+
+    return true;
+}
+ +

如果发现 Activity 提供的 Intent 过滤器与定义的 Intent 匹配,则会添加菜单项,并使用 Intent 过滤器 +android:label +中的值作为菜单项标题,使用应用图标作为菜单项图标。{@link android.view.Menu#addIntentOptions(int,int,int,ComponentName,Intent[],Intent,int,MenuItem[]) +addIntentOptions()} +方法将返回已添加的菜单项数量。

+ +

注:调用 +{@link +android.view.Menu#addIntentOptions(int,int,int,ComponentName,Intent[],Intent,int,MenuItem[]) +addIntentOptions()} 方法时,它将使用第一个参数中指定的菜单组替代所有菜单项。

+ + +

允许将 Activity 添加到其他菜单中

+ +

此外,您还可以为其他应用提供您的 Activity 服务,以便您的应用能够包含在其他应用的菜单中(与上述角色相反)。 +

+ +

要包含在其他应用菜单中,您需要按常规方式定义 Intent 过滤器,但请确保为 Intent 过滤器类别添加 +{@link android.content.Intent#CATEGORY_ALTERNATIVE} +和/或 {@link android.content.Intent#CATEGORY_SELECTED_ALTERNATIVE} +值。例如:

+
+<intent-filter label="@string/resize_image">
+    ...
+    <category android:name="android.intent.category.ALTERNATIVE" />
+    <category android:name="android.intent.category.SELECTED_ALTERNATIVE" />
+    ...
+</intent-filter>
+
+ +

请仔细阅读 Intent 和 Intent 过滤器文档中更多有关编写 Intent 过滤器的内容。 +

+ +

有关使用此方法的应用示例,请参阅记事本示例代码。 + +

diff --git a/docs/html-intl/intl/zh-cn/guide/topics/ui/notifiers/notifications.jd b/docs/html-intl/intl/zh-cn/guide/topics/ui/notifiers/notifications.jd new file mode 100644 index 0000000000000000000000000000000000000000..c0bd74cdd3ac3cc3f8533d335b96aa401e7edde8 --- /dev/null +++ b/docs/html-intl/intl/zh-cn/guide/topics/ui/notifiers/notifications.jd @@ -0,0 +1,979 @@ +page.title=通知 +@jd:body + + +

+ 通知是您可以在应用的常规 +UI +外部向用户显示的消息。当您告知系统发出通知时,它将先以图标的形式显示在通知区域中。用户可以打开抽屉式通知栏查看通知的详细信息。 +通知区域和抽屉式通知栏均是由系统控制的区域,用户可以随时查看。 + +

+ +

+ 图 1. 通知区域中的通知。 +

+ +

+ 图 2. 抽屉式通知栏中的通知。 +

+ +

注:除非特别注明,否则本指南均引用版本 +4 支持库中的 {@link android.support.v4.app.NotificationCompat.Builder NotificationCompat.Builder} +类。Android +3.0(API 级别 11)中已添加类 +{@link android.app.Notification.Builder Notification.Builder}。

+ +

设计注意事项

+ +

作为 Android 用户界面的一个重要组成部分,通知具有自己的设计指导方针。Android +5.0(API 级别 21)中引入的材料设计变更尤为重要,您应查阅材料设计培训资料了解详细信息。 + +要了解如何设计通知及其交互,请阅读通知设计指南。 +

+ +

创建通知

+ +

您可以在 +{@link android.support.v4.app.NotificationCompat.Builder NotificationCompat.Builder} +对象中为通知指定 UI 信息和操作。要创建通知,请调用 +{@link android.support.v4.app.NotificationCompat.Builder#build NotificationCompat.Builder.build()},它将返回包含您的具体规范的 +{@link android.app.Notification} 对象。要发出通知,请通过调用 +{@link android.app.NotificationManager#notify NotificationManager.notify()} 将 {@link android.app.Notification} +对象传递给系统。

+ +

必需的通知内容

+

+ {@link android.app.Notification} 对象必须包含以下内容: +

+
    +
  • + 小图标,由 +{@link android.support.v4.app.NotificationCompat.Builder#setSmallIcon setSmallIcon()} 设置 +
  • +
  • + 标题,由 +{@link android.support.v4.app.NotificationCompat.Builder#setContentTitle setContentTitle()} 设置 +
  • +
  • + 详细文本,由 +{@link android.support.v4.app.NotificationCompat.Builder#setContentText setContentText()} 设置 +
  • +
+

可选通知内容和设置

+

+ 所有其他通知设置和内容都是可选的。如需了解有关它们的更多详情,请参阅 +{@link android.support.v4.app.NotificationCompat.Builder} 参考文档。 +

+ +

通知操作

+

+ 尽管通知操作都是可选的,但是您至少应向通知添加一个操作。 + 操作允许用户直接从通知转到应用中的 +{@link android.app.Activity},他们可在其中查看一个或多个事件或执行进一步的操作。 + +

+

+ 一个通知可以提供多个操作。您应该始终定义一个当用户点击通知时会触发的操作;通常,此操作会在应用中打开 +{@link android.app.Activity}。 +您也可以向通知添加按钮来执行其他操作,例如,暂停闹铃或立即答复短信;此功能自 +Android +4.1 起可用。如果使用其他操作按钮,则您还必须使这些按钮的功能在应用的 +{@link android.app.Activity} +中可用;请参阅处理兼容性部分,以了解更多详情。 +

+

+ 在 {@link android.app.Notification} 内部,操作本身由 +{@link android.app.PendingIntent} 定义,后者包含在应用中启动 +{@link android.app.Activity} +的 {@link android.content.Intent}。要将 +{@link android.app.PendingIntent} 与手势相关联,请调用 +{@link android.support.v4.app.NotificationCompat.Builder} 的适当方法。例如,如果您要在用户点击抽屉式通知栏中的通知文本时启动 +{@link android.app.Activity},则可通过调用 +{@link android.support.v4.app.NotificationCompat.Builder#setContentIntent setContentIntent()} +来添加 {@link android.app.PendingIntent}。 +

+

+ 在用户点击通知时启动 {@link android.app.Activity} +是最常见的操作场景。此外,您还可以在用户清除通知时启动 +{@link android.app.Activity}。在 Android 4.1 及更高版本中,您可以通过操作按钮启动 +{@link android.app.Activity}。如需了解更多信息,请阅读参考指南的 +{@link android.support.v4.app.NotificationCompat.Builder} 部分。 +

+ +

通知优先级

+

+ 您可以根据需要设置通知的优先级。优先级充当一个提示,提醒设备 +UI 应该如何显示通知。 + 要设置通知的优先级,请调用 {@link + android.support.v4.app.NotificationCompat.Builder#setPriority(int) + NotificationCompat.Builder.setPriority()} 并传入一个 {@link + android.support.v4.app.NotificationCompat} 优先级常量。有五个优先级别,范围从 {@link + android.support.v4.app.NotificationCompat#PRIORITY_MIN} (-2) 到 {@link + android.support.v4.app.NotificationCompat#PRIORITY_MAX} (2);如果未设置,则优先级默认为 {@link + android.support.v4.app.NotificationCompat#PRIORITY_DEFAULT} (0)。 + + +

+

有关设置适当优先级别的信息,请参阅通知设计指南中的“正确设置和管理通知优先级”。 + + +

+ +

创建简单通知

+

+ 以下代码段说明了一个指定某项 Activity 在用户点击通知时打开的简单通知。 +请注意,该代码将创建 +{@link android.support.v4.app.TaskStackBuilder} 对象并使用它来为操作创建 +{@link android.app.PendingIntent}。启动 Activity 时保留导航部分对此模式做了更详尽的阐述: + + +

+
+NotificationCompat.Builder mBuilder =
+        new NotificationCompat.Builder(this)
+        .setSmallIcon(R.drawable.notification_icon)
+        .setContentTitle("My notification")
+        .setContentText("Hello World!");
+// Creates an explicit intent for an Activity in your app
+Intent resultIntent = new Intent(this, ResultActivity.class);
+
+// The stack builder object will contain an artificial back stack for the
+// started Activity.
+// This ensures that navigating backward from the Activity leads out of
+// your application to the Home screen.
+TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);
+// Adds the back stack for the Intent (but not the Intent itself)
+stackBuilder.addParentStack(ResultActivity.class);
+// Adds the Intent that starts the Activity to the top of the stack
+stackBuilder.addNextIntent(resultIntent);
+PendingIntent resultPendingIntent =
+        stackBuilder.getPendingIntent(
+            0,
+            PendingIntent.FLAG_UPDATE_CURRENT
+        );
+mBuilder.setContentIntent(resultPendingIntent);
+NotificationManager mNotificationManager =
+    (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+// mId allows you to update the notification later on.
+mNotificationManager.notify(mId, mBuilder.build());
+
+

就这么简单。您的用户现已收到通知。

+ +

将扩展布局应用于通知

+

+ 要使通知出现在展开视图中,请先创建一个带有所需普通视图选项的 +{@link android.support.v4.app.NotificationCompat.Builder} +对象。接下来,调用以扩展布局对象作为其参数的 {@link android.support.v4.app.NotificationCompat.Builder#setStyle + Builder.setStyle()}。 +

+

+ 请记住,扩展通知在 Android 4.1 之前的平台上不可用。要了解如何处理针对 +Android 4.1 及更早版本平台的通知,请阅读处理兼容性部分。 + +

+

+ 例如,以下代码段演示了如何更改在前面的代码段中创建的通知,以便使用扩展布局: + +

+
+NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(this)
+    .setSmallIcon(R.drawable.notification_icon)
+    .setContentTitle("Event tracker")
+    .setContentText("Events received")
+NotificationCompat.InboxStyle inboxStyle =
+        new NotificationCompat.InboxStyle();
+String[] events = new String[6];
+// Sets a title for the Inbox in expanded layout
+inboxStyle.setBigContentTitle("Event tracker details:");
+...
+// Moves events into the expanded layout
+for (int i=0; i < events.length; i++) {
+
+    inboxStyle.addLine(events[i]);
+}
+// Moves the expanded layout object into the notification object.
+mBuilder.setStyle(inBoxStyle);
+...
+// Issue the notification here.
+
+ +

处理兼容性

+ +

+ 并非所有通知功能都可用于某特定版本,即便用于设置这些功能的方法位于支持库类 +{@link android.support.v4.app.NotificationCompat.Builder NotificationCompat.Builder} +中也是如此。 + 例如,依赖于扩展通知的操作按钮仅会显示在 Android +4.1 及更高版本的系统中,这是因为扩展通知本身仅在 +Android 4.1 及更高版本的系统中可用。 +

+

+ 为了确保最佳兼容性,请使用 +{@link android.support.v4.app.NotificationCompat NotificationCompat} 及其子类(特别是 +{@link android.support.v4.app.NotificationCompat.Builder + NotificationCompat.Builder})创建通知。此外,在实现通知时,请遵循以下流程: +

+
    +
  1. + 为所有用户提供通知的全部功能,无论他们使用何种版本的 Android 系统。 +为此,请验证是否可从应用的 +{@link android.app.Activity} 中获得所有功能。要执行此操作,您可能需要添加新的 +{@link android.app.Activity}。 +

    + 例如,若要使用 +{@link android.support.v4.app.NotificationCompat.Builder#addAction addAction()} 提供停止和启动媒体播放的控件,请先在应用的 +{@link android.app.Activity} +中实现此控件。 +

    +
  2. +
  3. + 确保所有用户均可通过点击通知启动 {@link android.app.Activity} 来获得该Activity中的功能。 +为此,请为 +{@link android.app.Activity} +创建 {@link android.app.PendingIntent}。调用 +{@link android.support.v4.app.NotificationCompat.Builder#setContentIntent + setContentIntent()} 以将 {@link android.app.PendingIntent} 添加到通知。 +
  4. +
  5. + 现在,将要使用的扩展通知功能添加到通知。请记住,您添加的任何功能还必须在用户点击通知时启动的 +{@link android.app.Activity} +中可用。 +
  6. +
+ + + + +

管理通知

+

+ 当您需要为同一类型的事件多次发出同一通知时,应避免创建全新的通知, +而是应考虑通过更改之前通知的某些值和/或为其添加某些值来更新通知。 + +

+

+ 例如,Gmail 通过增加未读消息计数并将每封电子邮件的摘要添加到通知,通知用户收到了新的电子邮件。 +这称为“堆叠”通知;通知设计指南对此进行了更详尽的描述。 + + +

+

+ 注:此 +Gmail 功能需要“收件箱”扩展布局,该布局是自 Android 4.1 版本起可用的扩展通知功能的一部分。 +

+

+ 下文介绍如何更新和删除通知。 +

+

更新通知

+

+ 要将通知设置为能够更新,请通过调用 +{@link android.app.NotificationManager#notify(int, android.app.Notification) NotificationManager.notify()} 发出带有通知 ID 的通知。 + 要在发出之后更新此通知,请更新或创建 +{@link android.support.v4.app.NotificationCompat.Builder} +对象,从该对象构建 {@link android.app.Notification} 对象,并发出与之前所用 ID 相同的 +{@link android.app.Notification}。如果之前的通知仍然可见,则系统会根据 +{@link android.app.Notification} +对象的内容更新该通知。相反,如果之前的通知已被清除,系统则会创建一个新通知。 + +

+

+ 以下代码段演示了经过更新以反映所发生事件数量的通知。 +它将通知堆叠并显示摘要: +

+
+mNotificationManager =
+        (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+// Sets an ID for the notification, so it can be updated
+int notifyID = 1;
+mNotifyBuilder = new NotificationCompat.Builder(this)
+    .setContentTitle("New Message")
+    .setContentText("You've received new messages.")
+    .setSmallIcon(R.drawable.ic_notify_status)
+numMessages = 0;
+// Start of a loop that processes data and then notifies the user
+...
+    mNotifyBuilder.setContentText(currentText)
+        .setNumber(++numMessages);
+    // Because the ID remains unchanged, the existing notification is
+    // updated.
+    mNotificationManager.notify(
+            notifyID,
+            mNotifyBuilder.build());
+...
+
+ + +

删除通知

+

+ 除非发生以下情况之一,否则通知仍然可见: +

+
    +
  • + 用户单独或通过使用“全部清除”清除了该通知(如果通知可以清除)。 + +
  • +
  • + 用户点击通知,且您在创建通知时调用了 +{@link android.support.v4.app.NotificationCompat.Builder#setAutoCancel setAutoCancel()}。 + +
  • +
  • + 您针对特定的通知 ID 调用了 +{@link android.app.NotificationManager#cancel(int) cancel()}。此方法还会删除当前通知。 +
  • +
  • + 您调用了 +{@link android.app.NotificationManager#cancelAll() cancelAll()} 方法,该方法将删除之前发出的所有通知。 +
  • +
+ + +

启动 Activity 时保留导航

+

+ 从通知中启动 +{@link android.app.Activity} 时,您必须保留用户的预期导航体验。 点击“返回”应该使用户将应用的正常工作流回退到主屏幕,而点击“最新动态”则应将 + {@link android.app.Activity} 显示为单独的任务。 +要保留导航体验,您应该在全新任务中启动 +{@link android.app.Activity}。如何设置 +{@link android.app.PendingIntent} 以获得全新任务取决于正在启动的 +{@link android.app.Activity} 的性质。一般有两种情况: +

+
+
+ 常规 Activity +
+
+ 您要启动的 +{@link android.app.Activity} 是应用的正常工作流的一部分。在这种情况下,请设置 {@link android.app.PendingIntent} +以启动全新任务并为 +{@link android.app.PendingIntent}提供返回栈,这将重现应用的正常“返回”行为。 +

+ Gmail 应用中的通知演示了这一点。点击一封电子邮件消息的通知时,您将看到消息具体内容。 +触摸返回将使您从 +Gmail 回退到主屏幕,就好像您是从主屏幕(而不是通知)进入 +Gmail 一样。 +

+

+ 无论您触摸通知时处于哪个应用,都会发生这种情况。 +例如,如果您在 +Gmail 中撰写消息时点击了一封电子邮件的通知,则会立即转到该电子邮件。 + 触摸“返回”会依次转到收件箱和主屏幕,而不是转到您在撰写的邮件。 + +

+
+
+ 特殊 Activity +
+
+ 仅当从通知中启动时,用户才会看到此 {@link android.app.Activity}。 + 从某种意义上说,{@link android.app.Activity} +是通过提供很难显示在通知本身中的信息来扩展通知。对于这种情况,请将 +{@link android.app.PendingIntent} 设置为在全新任务中启动。但是,由于启动的 +{@link android.app.Activity} +不是应用 Activity 流程的一部分,因此无需创建返回栈。点击“返回”仍会将用户带到主屏幕。 + +
+
+ +

设置常规 Activity PendingIntent

+

+ 要设置可启动直接进入 {@link android.app.Activity} 的 +{@link android.app.PendingIntent},请执行以下步骤: +

+
    +
  1. + 在清单文件中定义应用的 {@link android.app.Activity} 层次结构。 +
      +
    1. + 添加对 Android 4.0.3 及更低版本的支持。为此,请通过添加 +<meta-data> +元素作为 +<activity>的子项来指定正在启动的 +{@link android.app.Activity} 的父项。 +

      + 对于此元素,请设置 +android:name="android.support.PARENT_ACTIVITY"。 + 设置 +android:value="<parent_activity_name>",其中,<parent_activity_name> +是父 +<activity> +元素的 +android:name +值。请参阅下面的 XML 示例。 +

      +
    2. +
    3. + 同样添加对 Android 4.1 及更高版本的支持。为此,请将 +android:parentActivityName +属性添加到正在启动的 +{@link android.app.Activity} 的 +<activity> 元素中。 +
    4. +
    +

    + 最终的 XML 应如下所示: +

    +
    +<activity
    +    android:name=".MainActivity"
    +    android:label="@string/app_name" >
    +    <intent-filter>
    +        <action android:name="android.intent.action.MAIN" />
    +        <category android:name="android.intent.category.LAUNCHER" />
    +    </intent-filter>
    +</activity>
    +<activity
    +    android:name=".ResultActivity"
    +    android:parentActivityName=".MainActivity">
    +    <meta-data
    +        android:name="android.support.PARENT_ACTIVITY"
    +        android:value=".MainActivity"/>
    +</activity>
    +
    +
  2. +
  3. + 根据可启动 +{@link android.app.Activity} 的 {@link android.content.Intent} 创建返回栈: +
      +
    1. + 创建 {@link android.content.Intent} 以启动 {@link android.app.Activity}。 +
    2. +
    3. + 通过调用 {@link android.app.TaskStackBuilder#create + TaskStackBuilder.create()} 创建堆栈生成器。 +
    4. +
    5. + 通过调用 +{@link android.support.v4.app.TaskStackBuilder#addParentStack addParentStack()} 将返回栈添加到堆栈生成器。 + 对于在清单文件中所定义层次结构内的每个 +{@link android.app.Activity},返回栈均包含可启动 {@link android.app.Activity} 的 +{@link android.content.Intent} 对象。此方法还会添加一些可在全新任务中启动堆栈的标志。 + +

      + 注:尽管 +{@link android.support.v4.app.TaskStackBuilder#addParentStack addParentStack()} +的参数是对已启动 +{@link android.app.Activity} 的引用,但是方法调用不会添加可启动 {@link android.app.Activity} 的 +{@link android.content.Intent},而是留待下一步进行处理。 +

      +
    6. +
    7. + 通过调用 +{@link android.support.v4.app.TaskStackBuilder#addNextIntent addNextIntent()},添加可从通知中启动 {@link android.app.Activity} +的 {@link android.content.Intent}。 + 将在第一步中创建的 {@link android.content.Intent} +作为 +{@link android.support.v4.app.TaskStackBuilder#addNextIntent addNextIntent()} 的参数传递。 +
    8. +
    9. + 如需,请通过调用 {@link android.support.v4.app.TaskStackBuilder#editIntentAt + TaskStackBuilder.editIntentAt()} 向堆栈中的 {@link android.content.Intent} +对象添加参数。有时,需要确保目标 {@link android.app.Activity} 在用户使用“返回”导航回它时会显示有意义的数据。 + + +
    10. +
    11. + 通过调用 +{@link android.support.v4.app.TaskStackBuilder#getPendingIntent getPendingIntent()} 获得此返回栈的 {@link android.app.PendingIntent}。 + 然后,您可以使用此 {@link android.app.PendingIntent} 作为 +{@link android.support.v4.app.NotificationCompat.Builder#setContentIntent + setContentIntent()} 的参数。 +
    12. +
    +
  4. +
+

+ 以下代码段演示了该流程: +

+
+...
+Intent resultIntent = new Intent(this, ResultActivity.class);
+TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);
+// Adds the back stack
+stackBuilder.addParentStack(ResultActivity.class);
+// Adds the Intent to the top of the stack
+stackBuilder.addNextIntent(resultIntent);
+// Gets a PendingIntent containing the entire back stack
+PendingIntent resultPendingIntent =
+        stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
+...
+NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
+builder.setContentIntent(resultPendingIntent);
+NotificationManager mNotificationManager =
+    (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+mNotificationManager.notify(id, builder.build());
+
+ +

设置特殊 Activity PendingIntent

+

+ 下文介绍如何设置特殊 Activity +{@link android.app.PendingIntent}。 +

+

+ 特殊 +{@link android.app.Activity} 无需返回栈,因此您不必在清单文件中定义其 +{@link android.app.Activity} 层次结构,也不必调用 +{@link android.support.v4.app.TaskStackBuilder#addParentStack addParentStack()} +来构建返回栈。取而代之的是,您可使用清单文件设置 +{@link android.app.Activity} 任务选项,并通过调用 {@link android.app.PendingIntent#getActivity getActivity()} +创建 {@link android.app.PendingIntent}: +

+
    +
  1. + 在清单文件中,将以下属性添加到 {@link android.app.Activity} 的 +<activity> +元素 +
    +
    +android:name="activityclass" +
    +
    + Activity 的完全限定类名。 +
    +
    +android:taskAffinity="" +
    +
    + 与您在代码中设置的 +{@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK FLAG_ACTIVITY_NEW_TASK} +标志相结合,这可确保此 {@link android.app.Activity} +不会进入应用的默认任务。任何具有应用默认关联的现有任务均不受影响。 + +
    +
    +android:excludeFromRecents="true" +
    +
    + 将新任务从“最新动态”中排除,这样用户就不会在无意中导航回它。 + +
    +
    +

    + 以下代码段显示了该元素: +

    +
    +<activity
    +    android:name=".ResultActivity"
    +...
    +    android:launchMode="singleTask"
    +    android:taskAffinity=""
    +    android:excludeFromRecents="true">
    +</activity>
    +...
    +
    +
  2. +
  3. + 构建并发出通知: +
      +
    1. + 创建可启动 {@link android.app.Activity}的 +{@link android.content.Intent}。 +
    2. +
    3. + 通过使用 +{@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK FLAG_ACTIVITY_NEW_TASK} +和 +{@link android.content.Intent#FLAG_ACTIVITY_CLEAR_TASK FLAG_ACTIVITY_CLEAR_TASK} 标志调用 +{@link android.content.Intent#setFlags setFlags()},将 {@link android.app.Activity} 设置为在新的空任务中启动。 +
    4. +
    5. + 为 {@link android.content.Intent} 设置所需的任何其他选项。 +
    6. +
    7. + 通过调用 +{@link android.app.PendingIntent#getActivity getActivity()} 从 {@link android.content.Intent} 中创建 {@link android.app.PendingIntent}。 + 然后,您可以使用此 {@link android.app.PendingIntent} 作为 +{@link android.support.v4.app.NotificationCompat.Builder#setContentIntent + setContentIntent()} 的参数。 +
    8. +
    +

    + 以下代码段演示了该流程: +

    +
    +// Instantiate a Builder object.
    +NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
    +// Creates an Intent for the Activity
    +Intent notifyIntent =
    +        new Intent(this, ResultActivity.class);
    +// Sets the Activity to start in a new, empty task
    +notifyIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
    +                        | Intent.FLAG_ACTIVITY_CLEAR_TASK);
    +// Creates the PendingIntent
    +PendingIntent notifyPendingIntent =
    +        PendingIntent.getActivity(
    +        this,
    +        0,
    +        notifyIntent,
    +        PendingIntent.FLAG_UPDATE_CURRENT
    +);
    +
    +// Puts the PendingIntent into the notification builder
    +builder.setContentIntent(notifyPendingIntent);
    +// Notifications are issued by sending them to the
    +// NotificationManager system service.
    +NotificationManager mNotificationManager =
    +    (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
    +// Builds an anonymous Notification object from the builder, and
    +// passes it to the NotificationManager
    +mNotificationManager.notify(id, builder.build());
    +
    +
  4. +
+ + +

在通知中显示进度

+

+ 通知可能包括动画形式的进度指示器,向用户显示正在进行的操作状态。 +如果您可以估计操作所需的时间以及任意时刻的完成进度,则使用“限定”形式的指示器(进度栏)。 + +如果无法估计操作的时长,则使用“非限定”形式的指示器(Activity 指示器)。 + +

+

+ 平台的 +{@link android.widget.ProgressBar} 类实现中显示有进度指示器。 +

+

+ 要在 Android 4.0 及更高版本的平台上使用进度指示器,需调用 +{@link android.support.v4.app.NotificationCompat.Builder#setProgress setProgress()}。对于早期版本,您必须创建包括 {@link android.widget.ProgressBar} +视图的自定义通知布局。 + +

+

+ 下文介绍如何使用 +{@link android.support.v4.app.NotificationCompat.Builder#setProgress setProgress()} 在通知中显示进度。 +

+ +

显示持续时间固定的进度指示器

+

+ 要显示限定形式的进度栏,请通过调用 +{@link android.support.v4.app.NotificationCompat.Builder#setProgress + setProgress(max, progress, false)} 将进度栏添加到通知,然后发出通知。随着操作继续进行,递增 +progress 并更新通知。操作结束时, +progress 应该等于 max。调用 +{@link android.support.v4.app.NotificationCompat.Builder#setProgress setProgress()} +的常见方法是将 max 设置为 100,然后将 progress +作为操作的“完成百分比”值递增。 +

+

+ 您可以在操作完成后仍保留显示进度栏,也可以将其删除。无论哪种情况,都请记住更新通知文本以显示操作已完成。 + + 要删除进度栏,请调用 +{@link android.support.v4.app.NotificationCompat.Builder#setProgress + setProgress(0, 0, false)}。例如: +

+
+...
+mNotifyManager =
+        (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+mBuilder = new NotificationCompat.Builder(this);
+mBuilder.setContentTitle("Picture Download")
+    .setContentText("Download in progress")
+    .setSmallIcon(R.drawable.ic_notification);
+// Start a lengthy operation in a background thread
+new Thread(
+    new Runnable() {
+        @Override
+        public void run() {
+            int incr;
+            // Do the "lengthy" operation 20 times
+            for (incr = 0; incr <= 100; incr+=5) {
+                    // Sets the progress indicator to a max value, the
+                    // current completion percentage, and "determinate"
+                    // state
+                    mBuilder.setProgress(100, incr, false);
+                    // Displays the progress bar for the first time.
+                    mNotifyManager.notify(0, mBuilder.build());
+                        // Sleeps the thread, simulating an operation
+                        // that takes time
+                        try {
+                            // Sleep for 5 seconds
+                            Thread.sleep(5*1000);
+                        } catch (InterruptedException e) {
+                            Log.d(TAG, "sleep failure");
+                        }
+            }
+            // When the loop is finished, updates the notification
+            mBuilder.setContentText("Download complete")
+            // Removes the progress bar
+                    .setProgress(0,0,false);
+            mNotifyManager.notify(ID, mBuilder.build());
+        }
+    }
+// Starts the thread by calling the run() method in its Runnable
+).start();
+
+ + +

显示持续 Activity 指示器

+

+ 要显示非限定形式的 Activity 指示器,请使用 +{@link android.support.v4.app.NotificationCompat.Builder#setProgress setProgress(0, 0, true)} +将其添加到通知(忽略前两个参数),然后发出通知。这样一来,指示器的样式就与进度栏相同,只是其动画还在继续。 + +

+

+ 在操作开始之际发出通知。除非您修改通知,否则动画将一直运行。 +操作完成后,调用 +{@link android.support.v4.app.NotificationCompat.Builder#setProgress setProgress(0, 0, false)},然后更新通知以删除 Activity 指示器。 + + 请务必这样做;否则,即使操作完成,动画仍将运行。同时,请记得更改通知文本,以表明操作已完成。 + +

+

+ 要了解 Activity 指示器的工作方式,请参阅上述代码段。找到以下几行: +

+
+// Sets the progress indicator to a max value, the current completion
+// percentage, and "determinate" state
+mBuilder.setProgress(100, incr, false);
+// Issues the notification
+mNotifyManager.notify(0, mBuilder.build());
+
+

+ 将找到的这几行替换为以下几行: +

+
+ // Sets an activity indicator for an operation of indeterminate length
+mBuilder.setProgress(0, 0, true);
+// Issues the notification
+mNotifyManager.notify(0, mBuilder.build());
+
+ +

通知元数据

+ +

通知可根据您使用以下 +{@link android.support.v4.app.NotificationCompat.Builder} 方法分配的元数据进行排序:

+ +
    +
  • 当设备处于“优先”模式时,{@link android.support.v4.app.NotificationCompat.Builder#setCategory(java.lang.String) setCategory()} +会告知系统如何处理应用通知(例如,通知代表传入呼叫、即时消息还是闹铃)。 +
  • +
  • 如果优先级字段设置为 +{@code PRIORITY_MAX} 或 {@code PRIORITY_HIGH} 的通知还有声音或振动,则 {@link android.support.v4.app.NotificationCompat.Builder#setPriority(int) setPriority()} +会将其显示在小型浮动窗口中。
  • +
  • {@link android.support.v4.app.NotificationCompat.Builder#addPerson(java.lang.String) addPerson()} +允许您向通知添加人员名单。您的应用可以使用此名单指示系统将指定人员发出的通知归成一组,或者将这些人员发出的通知视为更重要的通知。 + +
  • +
+ +
+ +

+ 图 3. 显示浮动通知的全屏 Activity +

+
+ +

浮动通知

+ +

对于 Android 5.0(API 级别 21),当设备处于活动状态时(即,设备未锁定且其屏幕已打开),通知可以显示在小型浮动窗口中(也称为“浮动通知”)。 + +这些通知看上去类似于精简版的通知​​,只是浮动通知还显示操作按钮。 + +用户可以在不离开当前应用的情况下处理或清除浮动通知。 +

+ +

可能触发浮动通知的条件示例包括:

+ +
    +
  • 用户的 Activity 处于全屏模式中(应用使用 +{@link android.app.Notification#fullScreenIntent}),或者
  • +
  • 通知具有较高的优先级并使用铃声或振动 +
  • +
+ +

锁定屏幕通知

+ +

随着 +Android 5.0(API 级别 21)的发布,通知现在还可显示在锁定屏幕上。您的应用可以使用此功能提供媒体播放控件以及其他常用操作。 +用户可以通过“设置”选择是否将通知显示在锁定屏幕上,并且您可以指定您应用中的通知在锁定屏幕上是否可见。 +

+ +

设置可见性

+ +

您的应用可以控制在安全锁定屏幕上显示的通知中可见的详细级别。 +调用 {@link android.support.v4.app.NotificationCompat.Builder#setVisibility(int) setVisibility()} +并指定以下值之一:

+ +
    +
  • {@link android.support.v4.app.NotificationCompat#VISIBILITY_PUBLIC} +显示通知的完整内容。
  • +
  • {@link android.support.v4.app.NotificationCompat#VISIBILITY_SECRET} +不会在锁定屏幕上显示此通知的任何部分。
  • +
  • {@link android.support.v4.app.NotificationCompat#VISIBILITY_PRIVATE} +显示通知图标和内容标题等基本信息,但是隐藏通知的完整内容。
  • +
+ +

设置 {@link android.support.v4.app.NotificationCompat#VISIBILITY_PRIVATE} +后,您还可以提供其中隐藏了某些详细信息的替换版本通知内容。例如,短信 +应用可能会显示一条通知,指出“您有 +3 条新短信”,但是隐藏了短信内容和发件人。要提供此替换版本的通知,请先使用 +{@link android.support.v4.app.NotificationCompat.Builder} 创建替换通知。创建专用通知对象时,请通过 +{@link android.support.v4.app.NotificationCompat.Builder#setPublicVersion(android.app.Notification) setPublicVersion()} +方法为其附加替换通知。 +

+ +

在锁定屏幕上控制媒体播放

+ +

在 Android 5.0(API 级别 21)中,锁定屏幕不再基于 +{@link android.media.RemoteControlClient}(现已弃用)显示媒体控件。取而代之的是,将 +{@link android.app.Notification.MediaStyle} 模板与 +{@link android.app.Notification.Builder#addAction(android.app.Notification.Action) addAction()} +方法结合使用,后者可将操作转换为可点击的图标。

+ +

注:该模板和 +{@link android.app.Notification.Builder#addAction(android.app.Notification.Action) addAction()} +方法未包含在支持库中,因此这些功能只能在 Android 5.0 及更高版本的系统上运行。

+ +

要在 Android 5.0 系统的锁定屏幕上显示媒体播放控件,请将可见性设置为 +{@link android.support.v4.app.NotificationCompat#VISIBILITY_PUBLIC},如上文所述。然后,添加操作并设置 +{@link android.app.Notification.MediaStyle} +模板,如以下示例代码中所述:

+ +
+Notification notification = new Notification.Builder(context)
+    // Show controls on lock screen even when user hides sensitive content.
+    .setVisibility(Notification.VISIBILITY_PUBLIC)
+    .setSmallIcon(R.drawable.ic_stat_player)
+    // Add media control buttons that invoke intents in your media service
+    .addAction(R.drawable.ic_prev, "Previous", prevPendingIntent) // #0
+    .addAction(R.drawable.ic_pause, "Pause", pausePendingIntent)  // #1
+    .addAction(R.drawable.ic_next, "Next", nextPendingIntent)     // #2
+    // Apply the media style template
+    .setStyle(new Notification.MediaStyle()
+    .setShowActionsInCompactView(1 /* #1: pause button */)
+    .setMediaSession(mMediaSession.getSessionToken())
+    .setContentTitle("Wonderful music")
+    .setContentText("My Awesome Band")
+    .setLargeIcon(albumArtBitmap)
+    .build();
+
+ +

注:弃用 {@link android.media.RemoteControlClient} +会对控制媒体产生进一步的影响。如需了解有关用于管理媒体会话和控制播放的新 API 的详细信息,请参阅媒体播放控件。 + +

+ + + +

自定义通知布局

+

+ 您可以利用通知框架定义自定义通知布局,由该布局定义通知在 +{@link android.widget.RemoteViews} 对象中的外观。 + 自定义布局通知类似于常规通知,但是它们是基于 XML 布局文件中所定义的 +{@link android.widget.RemoteViews}。 +

+

+ 自定义通知布局的可用高度取决于通知视图。普通视图布局限制为 +64 dp,扩展视图布局限制为 256 dp。 +

+

+ 要定义自定义通知布局,请首先实例化 +{@link android.widget.RemoteViews} 对象来扩充 XML 布局文件。然后,调用 +{@link android.support.v4.app.NotificationCompat.Builder#setContent setContent()},而不是调用 +{@link android.support.v4.app.NotificationCompat.Builder#setContentTitle setContentTitle()} +等方法。要在自定义通知中设置内容详细信息,请使用 +{@link android.widget.RemoteViews} +中的方法设置视图子项的值: +

+
    +
  1. + 在单独的文件中为通知创建 XML 布局。您可以根据需要使用任何文件名,但必须使用扩展名 +.xml。 +
  2. +
  3. + 在您的应用中,使用 {@link android.widget.RemoteViews} +方法定义通知的图标和文本。通过调用 +{@link android.support.v4.app.NotificationCompat.Builder#setContent setContent()} 将此 {@link android.widget.RemoteViews} 对象放入 +{@link android.support.v4.app.NotificationCompat.Builder} 中。避免在 +{@link android.widget.RemoteViews} 对象上设置背景 +{@link android.graphics.drawable.Drawable},因为文本颜色可能使文本变得难以阅读。 +
  4. +
+

+ 此外,{@link android.widget.RemoteViews} 类中还有一些方法可供您轻松将 +{@link android.widget.Chronometer} 或 {@link android.widget.ProgressBar} +添加到通知布局。如需了解有关为通知创建自定义布局的详细信息,请参阅 +{@link android.widget.RemoteViews} 参考文档。 +

+

+ 注意:使用自定义通知布局时,要特别注意确保自定义布局适用于不同的设备方向和分辨率。 +尽管这条建议适用于所有“视图”布局,但对通知尤为重要,因为抽屉式通知栏中的空间非常有限。 + +不要让自定义布局过于复杂,同时确保在各种配置中对其进行测试。 + +

+ +

对自定义通知文本使用样式资源

+

+ 始终对自定义通知的文本使用样式资源。通知的背景颜色可能因设备和系统版本的不同而异,使用样式资源有助于您充分考虑到这一点。 + +从 +Android 2.3 开始,系统定义了标准通知布局文本的样式。若要在面向 Android +2.3 或更高版本系统的多个应用中使用相同样式,则应确保文本在显示背景上可见。 +

diff --git a/docs/html-intl/intl/zh-cn/guide/topics/ui/overview.jd b/docs/html-intl/intl/zh-cn/guide/topics/ui/overview.jd new file mode 100644 index 0000000000000000000000000000000000000000..5097c76e4027957c8b3cf4ea70681873eed31f91 --- /dev/null +++ b/docs/html-intl/intl/zh-cn/guide/topics/ui/overview.jd @@ -0,0 +1,71 @@ +page.title=UI 概览 +@jd:body + + +

Android 应用中的所有用户界面元素都是使用 {@link android.view.View} 和 +{@link android.view.ViewGroup} 对象构建而成。{@link android.view.View} +对象用于在屏幕上绘制可供用户交互的内容。{@link android.view.ViewGroup} +对象用于储存其他 {@link android.view.View}(和 +{@link android.view.ViewGroup})对象,以便定义界面的局部。

+ +

Android 提供了一系列 +{@link android.view.View} 和 {@link +android.view.ViewGroup} 子类,可为您提供常用输入控件(如按钮和文本字段)和各种布局模式(如线性布局或相对布局)。

+ + +

用户界面布局

+ +

如图 1 所示,每个应用组件的用户界面都是使用 {@link +android.view.View} 和 {@link android.view.ViewGroup} 对象的层次结构定义的。每个视图组都是一个用于组织子视图的不可见容器,而子视图可以是输入控件或其他可绘制某一 +UI +部分的小工具。此层次结构树可繁可简,随需而定(但是简单的结构可提供最佳性能)。 + +

+ + +

图 1. 视图层次结构的图示,它定义了一个 UI +布局。

+ +

要声明布局,您可以实例化代码中的 {@link android.view.View} 对象并开始构建树,但是定义布局最简单且最有效的方法是使用 XML +文件。如同 HTML 一样,XML +也为布局提供了一种用户可读结构。

+ +

视图的 XML 元素名称与其代表的 Android 类相对应。因此, +<TextView> 元素用于在 UI 中创建一个 {@link android.widget.TextView} 小工具,而 +<LinearLayout> 元素用于创建一个 {@link android.widget.LinearLayout} +视图组。

+ +

例如,包含文本视图和按钮的简单垂直布局如下所示:

+
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:layout_width="fill_parent" 
+              android:layout_height="fill_parent"
+              android:orientation="vertical" >
+    <TextView android:id="@+id/text"
+              android:layout_width="wrap_content"
+              android:layout_height="wrap_content"
+              android:text="I am a TextView" />
+    <Button android:id="@+id/button"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="I am a Button" />
+</LinearLayout>
+
+ +

在应用中加载布局资源时,Android +会将布局的每个节点初始化为运行时对象,供您定义其他行为、查询对象状态或修改布局。 +

+ +

有关创建 UI 布局的完整指南,请参阅 XML +布局。 + + +

用户界面组件

+ +

您无需使用 {@link android.view.View} 和 {@link +android.view.ViewGroup} 对象构建所有 UI。Android 提供了几个带有标准 UI 布局的应用组件,您只需定义内容。 +这些 UI +组件均拥有一组唯一的 API,具体描述可参阅相应的文档,如操作栏对话框状态通知

+ + diff --git a/docs/html-intl/intl/zh-cn/guide/topics/ui/settings.jd b/docs/html-intl/intl/zh-cn/guide/topics/ui/settings.jd new file mode 100644 index 0000000000000000000000000000000000000000..f9be97be8c2e1b91e6f1dfe0df627b4e2aa6cb2d --- /dev/null +++ b/docs/html-intl/intl/zh-cn/guide/topics/ui/settings.jd @@ -0,0 +1,1202 @@ +page.title=设置 +page.tags=首选项,首选项 Activity,首选项片段 + +@jd:body + + +
+
+ +

本文内容

+
    +
  1. 概览 +
      +
    1. 首选项
    2. +
    +
  2. +
  3. 使用 XML 定义首选项 +
      +
    1. 创建设置组
    2. +
    3. 使用 Intent
    4. +
    +
  4. +
  5. 创建首选项 Activity
  6. +
  7. 使用首选项片段
  8. +
  9. 设置默认值
  10. +
  11. 使用首选项标头 +
      +
    1. 创建标头文件
    2. +
    3. 显示标头
    4. +
    5. 使用首选项标头支持旧版本
    6. +
    +
  12. +
  13. 读取首选项 +
      +
    1. 侦听首选项变更
    2. +
    +
  14. +
  15. 管理网络使用情况
  16. +
  17. 构建自定义首选项 +
      +
    1. 指定用户界面
    2. +
    3. 保存设置的值
    4. +
    5. 初始化当前值
    6. +
    7. 提供默认值
    8. +
    9. 保存和恢复首选项的状态
    10. +
    +
  18. +
+ +

关键类

+
    +
  1. {@link android.preference.Preference}
  2. +
  3. {@link android.preference.PreferenceActivity}
  4. +
  5. {@link android.preference.PreferenceFragment}
  6. +
+ + +

另请参阅

+
    +
  1. 设置设计指南
  2. +
+
+
+ + + + +

应用通常包括允许用户修改应用特性和行为的设置。例如,有些应用允许用户指定是否启用通知,或指定应用与云端同步数据的频率。 + +

+ +

若要为应用提供设置,您应该使用 +Android 的 {@link android.preference.Preference} API +构建一个与其他 Android 应用中的用户体验一致的界面(包括系统设置)。本文旨在介绍如何使用 +{@link android.preference.Preference} API 构建应用设置。

+ +
+

设置设计

+

有关如何设计设置的信息,请阅读设置设计指南。

+
+ + + +

图 1. 来自 Android 信息应用的设置的屏幕截图。 +选择由 {@link android.preference.Preference} +定义的项目将打开一个用于更改设置的界面。

+ + + + +

概览

+ +

设置是使用您在 XML 文件中声明的 +{@link android.preference.Preference} +类的各种子类构建而成,而不是使用 {@link android.view.View} 对象构建用户界面。

+ +

{@link android.preference.Preference} +对象是单个设置的构建基块。每个 {@link android.preference.Preference} 均作为项目显示在列表中,并提供适当的 +UI 供用户修改设置。例如,{@link +android.preference.CheckBoxPreference} 可创建一个列表项用于显示复选框,{@link +android.preference.ListPreference} 可创建一个项目用于打开包含选择列表的对话框。

+ +

您添加的每个 {@link android.preference.Preference} 都有一个相应的键值对,可供系统用来将设置保存在应用设置的默认 +{@link android.content.SharedPreferences} +文件中。当用户更改设置时,系统会为您更新 +{@link android.content.SharedPreferences} 文件中的相应值。您只应在需要读取值以根据用户设置确定应用的行为时,才与关联的 +{@link android.content.SharedPreferences} +文件直接交互。

+ +

为每个设置保存在 {@link android.content.SharedPreferences} +中的值可能是以下数据类型之一:

+ +
    +
  • 布尔型
  • +
  • 浮点型
  • +
  • 整型
  • +
  • 长整型
  • +
  • 字符串
  • +
  • 字符串 {@link java.util.Set}
  • +
+ +

由于应用的设置 UI 是使用 {@link android.preference.Preference} +对象(而非 +{@link android.view.View} 对象)构建而成,因此您需要使用专门的 {@link android.app.Activity} 或 +{@link android.app.Fragment} 子类显示列表设置:

+ +
    +
  • 如果应用支持早于 3.0(API 级别 10 及更低级别)的 Android 版本,则您必须将 Activity 构建为 +{@link android.preference.PreferenceActivity} 类的扩展。
  • +
  • 对于 Android 3.0 及更高版本,您应改用传统 +{@link android.app.Activity},以托管可显示应用设置的 {@link android.preference.PreferenceFragment}。但是,如果您拥有多组设置,则还可以使用 +{@link android.preference.PreferenceActivity} +为大屏幕创建双窗格布局。
  • +
+ +

创建首选项 Activity使用首选项片段 +部分将讨论如何设置 {@link android.preference.PreferenceActivity} 以及 {@link +android.preference.PreferenceFragment} 实例。

+ + +

首选项

+ +

所有应用设置均由 {@link +android.preference.Preference} 类的特定子类表示。每个子类均包括一组核心属性,允许您指定设置标题和默认值等内容。 +此外,每个子类还提供自己的专用属性和用户界面。 +例如,图 1 显示的是“信息” +应用的设置屏幕截图。设置屏幕中的每个列表项均由不同的 {@link +android.preference.Preference} 对象提供支持。

+ +

一些最常用的首选项如下:

+ +
+
{@link android.preference.CheckBoxPreference}
+
显示一个包含已启用或已禁用设置复选框的项目。保存的值是布尔型(如果选中则为 +true)。
+ +
{@link android.preference.ListPreference}
+
打开一个包含单选按钮列表的对话框。保存的值可以是任一受支持的值类型(如上所列)。 +
+ +
{@link android.preference.EditTextPreference}
+
打开一个包含 {@link android.widget.EditText} 小工具的对话框。保存的值是 {@link +java.lang.String}。
+
+ +

有关所有其他子类及其对应属性的列表,请参阅 {@link android.preference.Preference} +类。

+ +

当然,内置类不能满足所有需求,您的应用可能需要更专业化的内容。 +例如,该平台目前不提供用于选取数字或日期的 {@link +android.preference.Preference} 类。因此,您可能需要定义自己的 +{@link android.preference.Preference} 子类。如需有关执行此操作的帮助,请参阅构建自定义首选项部分。

+ + + +

使用 XML 定义首选项

+ +

虽然您可以在运行时实例化新的 {@link android.preference.Preference} +对象,不过您还是应该使用 {@link android.preference.Preference} +对象的层次结构在 XML 中定义设置列表。使用 XML +文件定义设置的集合是首选方法,因为该文件提供了一个便于更新的易读结构。此外,应用的设置通常是预先确定的,不过您仍可在运行时修改此集合。 +

+ +

每个 {@link android.preference.Preference} 子类均可以使用与类名(如 {@code <CheckBoxPreference>})匹配的 XML 元素来声明。 +

+ +

您必须将 XML 文件保存在 {@code res/xml/} 目录中。尽管您可以随意命名该文件,但它通常命名为 +{@code preferences.xml}。您通常只需一个文件,因为层次结构中的分支(可打开各自的设置列表)是使用 +{@link android.preference.PreferenceScreen} +的嵌套实例声明的。

+ +

注:若要为设置创建多窗格布局,则需要为每个片段提供单独的 +XML 文件。

+ +

XML 文件的根节点必须是一个 {@link android.preference.PreferenceScreen +<PreferenceScreen>} 元素。您可以在此元素内添加每个 {@link +android.preference.Preference}。在 +{@link android.preference.PreferenceScreen <PreferenceScreen>} +元素内添加的每个子项均将作为单独的项目显示在设置列表中。

+ +

例如:

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <CheckBoxPreference
+        android:key="pref_sync"
+        android:title="@string/pref_sync"
+        android:summary="@string/pref_sync_summ"
+        android:defaultValue="true" />
+    <ListPreference
+        android:dependency="pref_sync"
+        android:key="pref_syncConnectionType"
+        android:title="@string/pref_syncConnectionType"
+        android:dialogTitle="@string/pref_syncConnectionType"
+        android:entries="@array/pref_syncConnectionTypes_entries"
+        android:entryValues="@array/pref_syncConnectionTypes_values"
+        android:defaultValue="@string/pref_syncConnectionTypes_default" />
+</PreferenceScreen>
+
+ +

在此示例中,有一个 {@link android.preference.CheckBoxPreference} 和 {@link +android.preference.ListPreference}。这两项均包括以下三个属性:

+ +
+
{@code android:key}
+
对于要保留数据值的首选项,必须拥有此属性。它指定系统在将此设置的值保存在 +{@link +android.content.SharedPreferences} 中时所用的唯一键(字符串)。 +

不需要此属性的仅有情形是:首选项是 +{@link android.preference.PreferenceCategory} 或{@link android.preference.PreferenceScreen},或者首选项指定要调用的 +{@link android.content.Intent}(使用 {@code <intent>} 元素)或要显示的 {@link android.app.Fragment}(使用 {@code +android:fragment} 属性)。

+
+
{@code android:title}
+
此属性为设置提供用户可见的名称。
+
{@code android:defaultValue}
+
此属性指定系统应该在 {@link +android.content.SharedPreferences} 文件中设置的初始值。您应该为所有设置提供默认值。 +
+
+ +

有关所有其他受支持属性的信息,请参阅 {@link +android.preference.Preference}(和相应子类)文档。

+ + +
+ +

图 2. 带标题的设置类别。 +
1.类别由 {@link +android.preference.PreferenceCategory <PreferenceCategory>} 元素指定。
2.标题由 +{@code android:title} 属性指定。

+
+ + +

当设置列表超过 10 项时,您可能需要添加标题来定义设置组或在单独的屏幕中显示这些组。 + +下文将介绍这些选项。

+ + +

创建设置组

+ +

如果您提供的列表包含 10 +项或更多设置,则用户可能难以浏览、理解和处理这些设置。若要弥补这一点,您可以将部分或全部设置分成若干组,从而有效地将一个长列表转化为多个短列表。 + +可以通过下列两种方法之一提供一组相关设置:

+ + + +

您可以使用其中一种或两种分组方法来组织应用的设置。决定要使用的技巧以及如何拆分设置时,应遵循 Android 设计的设置指南中的准则。 + +

+ + +

使用标题

+ +

若要以分隔线分隔两组设置并为其提供标题(如图 2 所示),请将每组 +{@link android.preference.Preference} 对象放入 {@link +android.preference.PreferenceCategory} 内。

+ +

例如:

+ +
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <PreferenceCategory 
+        android:title="@string/pref_sms_storage_title"
+        android:key="pref_key_storage_settings">
+        <CheckBoxPreference
+            android:key="pref_key_auto_delete"
+            android:summary="@string/pref_summary_auto_delete"
+            android:title="@string/pref_title_auto_delete"
+            android:defaultValue="false"... />
+        <Preference 
+            android:key="pref_key_sms_delete_limit"
+            android:dependency="pref_key_auto_delete"
+            android:summary="@string/pref_summary_delete_limit"
+            android:title="@string/pref_title_sms_delete"... />
+        <Preference 
+            android:key="pref_key_mms_delete_limit"
+            android:dependency="pref_key_auto_delete"
+            android:summary="@string/pref_summary_delete_limit"
+            android:title="@string/pref_title_mms_delete" ... />
+    </PreferenceCategory>
+    ...
+</PreferenceScreen>
+
+ + +

使用子屏幕

+ +

若要将设置组放入子屏幕(如图 3 所示),请将 +{@link android.preference.Preference} 对象组放入 {@link +android.preference.PreferenceScreen} 内。

+ + +

图 3. 设置子屏幕。{@code +<PreferenceScreen>} 元素创建的项目选中后,即会打开一个单独的列表来显示嵌套设置。 +

+ +

例如:

+ +
+<PreferenceScreen  xmlns:android="http://schemas.android.com/apk/res/android">
+    <!-- opens a subscreen of settings -->
+    <PreferenceScreen
+        android:key="button_voicemail_category_key"
+        android:title="@string/voicemail"
+        android:persistent="false">
+        <ListPreference
+            android:key="button_voicemail_provider_key"
+            android:title="@string/voicemail_provider" ... />
+        <!-- opens another nested subscreen -->
+        <PreferenceScreen
+            android:key="button_voicemail_setting_key"
+            android:title="@string/voicemail_settings"
+            android:persistent="false">
+            ...
+        </PreferenceScreen>
+        <RingtonePreference
+            android:key="button_voicemail_ringtone_key"
+            android:title="@string/voicemail_ringtone_title"
+            android:ringtoneType="notification" ... />
+        ...
+    </PreferenceScreen>
+    ...
+</PreferenceScreen>
+
+ + +

使用 Intent

+ +

在某些情况下,您可能需要首选项来打开不同的 Activity(而不是 Web +浏览器等设置屏幕)或查看网页。要在用户选择首选项时调用 {@link +android.content.Intent},请将{@code <intent>} +元素添加为相应 {@code <Preference>} 元素的子元素。

+ +

例如,您可以按如下方法使用首选项打开网页:

+ +
+<Preference android:title="@string/prefs_web_page" >
+    <intent android:action="android.intent.action.VIEW"
+            android:data="http://www.example.com" />
+</Preference>
+
+ +

您可以使用以下属性创建隐式和显式 Intent:

+ +
+
{@code android:action}
+
要分配的操作(按照 {@link android.content.Intent#setAction setAction()} +方法)。
+
{@code android:data}
+
要分配的数据(按照 {@link android.content.Intent#setData setData()} 方法)。
+
{@code android:mimeType}
+
要分配的 MIME 类型(按照 {@link android.content.Intent#setType setType()} +方法)。
+
{@code android:targetClass}
+
组件名称的类部分(按照 {@link android.content.Intent#setComponent +setComponent()} 方法)。
+
{@code android:targetPackage}
+
组件名称的软件包部分(按照 {@link +android.content.Intent#setComponent setComponent()} 方法)。
+
+ + + +

创建首选项 Activity

+ +

要在 Activity 中显示您的设置,请扩展 {@link +android.preference.PreferenceActivity} 类。这是传统 {@link +android.app.Activity} 类的扩展,该类根据 {@link +android.preference.Preference} 对象的层次结构显示设置列表。当用户进行更改时,{@link android.preference.PreferenceActivity} +会自动保留与每个 {@link +android.preference.Preference} 相关的设置。

+ +

注:如果您是开发针对 Android 3.0 及 +更高版本系统的应用,则应改为使用 {@link android.preference.PreferenceFragment}。转到下文有关使用首选项片段的部分。 +

+ +

请记住最重要的一点,就是不要在 {@link +android.preference.PreferenceActivity#onCreate onCreate()} 回调期间加载视图的布局。相反,请调用 {@link +android.preference.PreferenceActivity#addPreferencesFromResource addPreferencesFromResource()} 以将在 +XML 文件中声明的首选项添加到 Activity。例如,一个能够正常工作的 +{@link android.preference.PreferenceActivity} 至少需要如下代码:

+ +
+public class SettingsActivity extends PreferenceActivity {
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        addPreferencesFromResource(R.xml.preferences);
+    }
+}
+
+ +

实际上,对于某些应用而言此,代码就已足够,因为用户修改某首选项后,系统会立即将所做的更改保存到默认 {@link android.content.SharedPreferences} 文件中,如需检查用户的设置,可以使用您的其他应用组件读取该文件。 + +不过,许多应用需要的代码要稍微多一点,以侦听首选项发生的变化。有关侦听 +{@link android.content.SharedPreferences} +文件变化的信息,请参阅读取首选项部分。 +

+ + + + +

使用首选项片段

+ +

如果您是开发针对 Android 3.0(API 级别 11)及更高版本系统的应用,则应使用 {@link +android.preference.PreferenceFragment} 显示 {@link android.preference.Preference} +对象的列表。您可以将 {@link android.preference.PreferenceFragment} 添加到任何 Activity,而不必使用 +{@link android.preference.PreferenceActivity}。

+ +

与仅使用上述 Activity 相比,无论您在构建何种 Activity,片段都可为应用提供一个更加灵活的体系结构。 + +因此,我们建议您尽可能使用 {@link +android.preference.PreferenceFragment} 控制设置的显示,而不是使用 {@link +android.preference.PreferenceActivity}。

+ +

{@link android.preference.PreferenceFragment} 的实现就像定义 +{@link android.preference.PreferenceFragment#onCreate onCreate()} 方法以使用 +{@link android.preference.PreferenceFragment#addPreferencesFromResource +addPreferencesFromResource()} 加载首选项文件一样简单。例如:

+ +
+public static class SettingsFragment extends PreferenceFragment {
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        // Load the preferences from an XML resource
+        addPreferencesFromResource(R.xml.preferences);
+    }
+    ...
+}
+
+ +

然后,正如您对其他任何 +{@link android.app.Fragment} 的处理一样,您可以将此片段添加到 {@link android.app.Activity}。例如:

+ +
+public class SettingsActivity extends Activity {
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        // Display the fragment as the main content.
+        getFragmentManager().beginTransaction()
+                .replace(android.R.id.content, new SettingsFragment())
+                .commit();
+    }
+}
+
+ +

注:{@link android.preference.PreferenceFragment} 没有自己的 +{@link android.content.Context} 对象。如需 {@link android.content.Context} +对象,您可以调用 {@link android.app.Fragment#getActivity()}。但请注意,只应在该片段附加到 Activity 时才调用 +{@link android.app.Fragment#getActivity()}。如果该片段尚未附加或在其生命周期结束期间已分离,则 +{@link +android.app.Fragment#getActivity()} 将返回空 null。

+ + +

设置默认值

+ +

您创建的首选项可能会为应用定义一些重要行为,因此在用户首次打开应用时,您有必要使用每个 {@link android.preference.Preference} +的默认值初始化相关的 +{@link android.content.SharedPreferences} +文件。

+ +

首先,您必须使用 {@code android:defaultValue} 属性为 XML 文件中的每个 {@link +android.preference.Preference} +对象指定默认值。该值可以是适合相应 +{@link android.preference.Preference} 对象的任意数据类型。例如: +

+ +
+<!-- default value is a boolean -->
+<CheckBoxPreference
+    android:defaultValue="true"
+    ... />
+
+<!-- default value is a string -->
+<ListPreference
+    android:defaultValue="@string/pref_syncConnectionTypes_default"
+    ... />
+
+ +

然后,通过应用的主 Activity(以及用户首次进入应用所藉由的任何其他 Activity)中的 {@link android.app.Activity#onCreate onCreate()} +方法调用 {@link android.preference.PreferenceManager#setDefaultValues +setDefaultValues()}: +

+ +
+PreferenceManager.setDefaultValues(this, R.xml.advanced_preferences, false);
+
+ +

在 +{@link android.app.Activity#onCreate onCreate()} +期间调用此方法可确保使用默认设置正确初始化应用,而应用可能需要读取这些设置以确定某些行为(例如,是否在蜂窝网络中下载数据)。 +

+ +

此方法采用三个参数:

+
    +
  • 应用 {@link android.content.Context}。
  • +
  • 要为其设置默认值的首选项 XML 文件的资源 ID。
  • +
  • 一个布尔值,用于指示是否应该多次设置默认值。 +

    如果该值为 false,则仅当过去从未调用此方法时(或者默认值共享首选项文件中的 +{@link android.preference.PreferenceManager#KEY_HAS_SET_DEFAULT_VALUES}为 +false 时),系统才会设置默认值。

  • +
+ +

只要将第三个参数设置为 +false,您便可在每次启动 Activity 时安全地调用此方法,而不必通过重置为默认值来替代用户已保存的首选项。 +但是,如果将它设置为 +true,则需要使用默认值替代之前的所有值。

+ + + +

使用首选项标头

+ +

在极少数情况下,您可能需要设计设置,使第一个屏幕仅显示子屏幕的列表(例如在系统“设置”应用中,如图 4 和图 5 所示)。 + +在开发针对 Android 3.0 及更高版本系统的此类设计时,您应该使用 +Android 3.0 中的新“标头”功能,而非使用嵌套的 +{@link android.preference.PreferenceScreen} 元素构建子屏幕。

+ +

要使用标头构建设置,您需要:

+
    +
  1. 将每组设置分成单独的 {@link +android.preference.PreferenceFragment} 实例。即,每组设置均需要一个单独的 XML +文件。
  2. +
  3. 创建 XML +标头文件,其中列出每个设置组并声明哪个片段包含对应的设置列表。
  4. +
  5. 扩展 {@link android.preference.PreferenceActivity} 类以托管设置。
  6. +
  7. 实现 {@link +android.preference.PreferenceActivity#onBuildHeaders onBuildHeaders()} +回调以指定标头文件。
  8. +
+ +

使用此设计的一大好处是,在大屏幕上运行时,{@link android.preference.PreferenceActivity} +会自动提供双窗格布局(如图 4 所示)。

+ +

即使您的应用支持早于 3.0 的 Android 版本,您仍可将应用设计为使用 +{@link android.preference.PreferenceFragment} +在较新版本的设备上呈现双窗格布局,同时仍支持较旧版本设备上传统的多屏幕层次结构(请参阅使用首选项标头支持旧版本部分)。 + +

+ + +

图 4. 带标头的双窗格布局。
1.标头用 +XML 标头文件定义。
2.每组设置均由 +{@link android.preference.PreferenceFragment}(通过标头文件中的 {@code <header>} +元素指定)定义。

+ + +

图 5. 带设置标头的手机设备。选择项目后,相关的 +{@link android.preference.PreferenceFragment} +将替换标头。

+ + +

创建标头文件

+ +

标头列表中的每组设置均由根 {@code <preference-headers>} 元素内的单个 {@code <header>} +元素指定。例如:

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<preference-headers xmlns:android="http://schemas.android.com/apk/res/android">
+    <header 
+        android:fragment="com.example.prefs.SettingsActivity$SettingsFragmentOne"
+        android:title="@string/prefs_category_one"
+        android:summary="@string/prefs_summ_category_one" />
+    <header 
+        android:fragment="com.example.prefs.SettingsActivity$SettingsFragmentTwo"
+        android:title="@string/prefs_category_two"
+        android:summary="@string/prefs_summ_category_two" >
+        <!-- key/value pairs can be included as arguments for the fragment. -->
+        <extra android:name="someKey" android:value="someHeaderValue" />
+    </header>
+</preference-headers>
+
+ +

每个标头均可使用 {@code android:fragment} 属性声明在用户选择该标头时应打开的 {@link +android.preference.PreferenceFragment} 实例。

+ +

{@code <extras>} 元素允许您使用 {@link +android.os.Bundle} 将键值对传递给片段。该片段可以通过调用 {@link +android.app.Fragment#getArguments()} 检索参数。您向该片段传递参数的原因可能有很多,不过一个重要原因是,要对每个组重复使用 +{@link +android.preference.PreferenceFragment} +的相同子类,而且要使用参数来指定该片段应加载哪些首选项 XML 文件。

+ +

例如,当每个标头均使用 +{@code "settings"} 键定义 {@code <extra>} 参数时,则可以对多个设置组重复使用以下片段:

+ +
+public static class SettingsFragment extends PreferenceFragment {
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        String settings = getArguments().getString("settings");
+        if ("notifications".equals(settings)) {
+            addPreferencesFromResource(R.xml.settings_wifi);
+        } else if ("sync".equals(settings)) {
+            addPreferencesFromResource(R.xml.settings_sync);
+        }
+    }
+}
+
+ + + +

显示标头

+ +

要显示首选项标头,您必须实现 {@link +android.preference.PreferenceActivity#onBuildHeaders onBuildHeaders()} 回调方法并调用 +{@link android.preference.PreferenceActivity#loadHeadersFromResource +loadHeadersFromResource()}。例如:

+ +
+public class SettingsActivity extends PreferenceActivity {
+    @Override
+    public void onBuildHeaders(List<Header> target) {
+        loadHeadersFromResource(R.xml.preference_headers, target);
+    }
+}
+
+ +

当用户从标头列表中选择一个项目时,系统会打开相关的 {@link +android.preference.PreferenceFragment}。

+ +

注:使用首选项标头时,{@link +android.preference.PreferenceActivity} 的子类无需实现 {@link +android.preference.PreferenceActivity#onCreate onCreate()} +方法,因为 Activity 唯一所需执行的任务就是加载标头。

+ + +

使用首选项标头支持旧版本

+ +

如果您的应用支持早于 3.0 的 Android +版本,则在 Android 3.0 及更高版本系统上运行时,您仍可使用标头提供双窗格数据。为此,您只需另外创建 +一个使用基本 {@link android.preference.Preference +<Preference>} 元素的首选项 XML 文件即可,这些基本元素的行为方式与标头项目类似(供较旧版本的 Android + 系统使用)。

+ +

但是,每个 {@link +android.preference.Preference <Preference>} 元素均会向 {@link android.preference.PreferenceActivity} 发送一个 {@link android.content.Intent},指定要加载哪个首选项 XML 文件,而不是打开新的 +{@link android.preference.PreferenceScreen}。 +

+ +

例如,下面就是一个用于 Android 3.0 +及更高版本系统的首选项标头 XML 文件 ({@code res/xml/preference_headers.xml}):

+ +
+<preference-headers xmlns:android="http://schemas.android.com/apk/res/android">
+    <header 
+        android:fragment="com.example.prefs.SettingsFragmentOne"
+        android:title="@string/prefs_category_one"
+        android:summary="@string/prefs_summ_category_one" />
+    <header 
+        android:fragment="com.example.prefs.SettingsFragmentTwo"
+        android:title="@string/prefs_category_two"
+        android:summary="@string/prefs_summ_category_two" />
+</preference-headers>
+
+ +

下面是为早于 +Android 3.0 版本的系统提供相同标头的首选项文件 ({@code res/xml/preference_headers_legacy.xml}):

+ +
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <Preference 
+        android:title="@string/prefs_category_one"
+        android:summary="@string/prefs_summ_category_one"  >
+        <intent 
+            android:targetPackage="com.example.prefs"
+            android:targetClass="com.example.prefs.SettingsActivity"
+            android:action="com.example.prefs.PREFS_ONE" />
+    </Preference>
+    <Preference 
+        android:title="@string/prefs_category_two"
+        android:summary="@string/prefs_summ_category_two" >
+        <intent 
+            android:targetPackage="com.example.prefs"
+            android:targetClass="com.example.prefs.SettingsActivity"
+            android:action="com.example.prefs.PREFS_TWO" />
+    </Preference>
+</PreferenceScreen>
+
+ +

由于是从 Android 3.0 开始方添加对 {@code <preference-headers>} 的支持,因此只有在 Androd 3.0 或更高版本中运行时,系统才会在您的 {@link +android.preference.PreferenceActivity} +中调用 {@link android.preference.PreferenceActivity#onBuildHeaders onBuildHeaders()}。要加载“旧版”标头文件 +({@code preference_headers_legacy.xml}),您必须检查 Android +版本,如果版本低于 Android 3.0 ({@link +android.os.Build.VERSION_CODES#HONEYCOMB}),请调用 {@link +android.preference.PreferenceActivity#addPreferencesFromResource addPreferencesFromResource()} +来加载旧版标头文件。例如:

+ +
+@Override
+public void onCreate(Bundle savedInstanceState) {
+    super.onCreate(savedInstanceState);
+    ...
+
+    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
+        // Load the legacy preferences headers
+        addPreferencesFromResource(R.xml.preference_headers_legacy);
+    }
+}
+
+// Called only on Honeycomb and later
+@Override
+public void onBuildHeaders(List<Header> target) {
+   loadHeadersFromResource(R.xml.preference_headers, target);
+}
+
+ +

最后要做的就是处理传入 Activity 的 +{@link android.content.Intent},以确定要加载的首选项文件。因此,请检索 Intent 的操作,并将其与在首选项 XML 的 {@code <intent>} +标记中使用的已知操作字符串进行比较。

+ +
+final static String ACTION_PREFS_ONE = "com.example.prefs.PREFS_ONE";
+...
+
+@Override
+public void onCreate(Bundle savedInstanceState) {
+    super.onCreate(savedInstanceState);
+
+    String action = getIntent().getAction();
+    if (action != null && action.equals(ACTION_PREFS_ONE)) {
+        addPreferencesFromResource(R.xml.preferences);
+    }
+    ...
+
+    else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
+        // Load the legacy preferences headers
+        addPreferencesFromResource(R.xml.preference_headers_legacy);
+    }
+}
+
+ +

值得注意的是,连续调用 {@link +android.preference.PreferenceActivity#addPreferencesFromResource addPreferencesFromResource()} +会将所有首选项堆叠在一个列表中,因此请将条件与 +else-if 语句链接在一起,确保它只调用一次。

+ + + + + +

读取首选项

+ +

默认情况下,应用的所有首选项均保存到一个可通过调用静态方法 +{@link +android.preference.PreferenceManager#getDefaultSharedPreferences +PreferenceManager.getDefaultSharedPreferences()} 从应用内的任何位置访问的文件中。这将返回 {@link +android.content.SharedPreferences} 对象,其中包含与 +{@link +android.preference.PreferenceActivity} 中所用 {@link android.preference.Preference} 对象相关的所有键值对。

+ +

例如,从应用中的任何其他 Activity 读取某个首选项值的方法如下: +

+ +
+SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this);
+String syncConnPref = sharedPref.getString(SettingsActivity.KEY_PREF_SYNC_CONN, "");
+
+ + + +

侦听首选项变更

+ +

出于某些原因,您可能希望在用户更改任一首选项时立即收到通知。 +要在任一首选项发生更改时收到回调,请实现 +{@link android.content.SharedPreferences.OnSharedPreferenceChangeListener +SharedPreference.OnSharedPreferenceChangeListener} 接口,并通过调用 {@link +android.content.SharedPreferences#registerOnSharedPreferenceChangeListener +registerOnSharedPreferenceChangeListener()} 为 +{@link android.content.SharedPreferences} 对象注册侦听器。

+ +

该接口只有 {@link +android.content.SharedPreferences.OnSharedPreferenceChangeListener#onSharedPreferenceChanged +onSharedPreferenceChanged()} +一种回调方法,而且您可能会发现在 Activity 过程中实现该接口最为简单。例如:

+ +
+public class SettingsActivity extends PreferenceActivity
+                              implements OnSharedPreferenceChangeListener {
+    public static final String KEY_PREF_SYNC_CONN = "pref_syncConnectionType";
+    ...
+
+    public void onSharedPreferenceChanged(SharedPreferences sharedPreferences,
+        String key) {
+        if (key.equals(KEY_PREF_SYNC_CONN)) {
+            Preference connectionPref = findPreference(key);
+            // Set summary to be the user-description for the selected value
+            connectionPref.setSummary(sharedPreferences.getString(key, ""));
+        }
+    }
+}
+
+ +

在此示例中,该方法检查更改的设置是否是针对已知的首选项键。它调用 +{@link android.preference.PreferenceActivity#findPreference findPreference()} 来获取已更改的 +{@link android.preference.Preference} +对象,以便能够将项目摘要修改为对用户选择的说明。即,如果设置为 {@link +android.preference.ListPreference} 或其他多选设置时,则当设置更改为显示当前状态(例如,图 +5 所示的“Sleep”设置)时,您应调用 {@link +android.preference.Preference#setSummary setSummary()}。

+ +

注:正如 Android 设计有关设置的文档中所述,我们建议您在用户每次更改首选项时更新 +{@link android.preference.ListPreference} +的摘要,以描述当前设置。

+ +

若要妥善管理 Activity 生命周期,我们建议您在 +{@link +android.app.Activity#onResume} 和 {@link android.app.Activity#onPause} 回调期间分别注册和注销 {@link android.content.SharedPreferences.OnSharedPreferenceChangeListener}。

+ +
+@Override
+protected void onResume() {
+    super.onResume();
+    getPreferenceScreen().getSharedPreferences()
+            .registerOnSharedPreferenceChangeListener(this);
+}
+
+@Override
+protected void onPause() {
+    super.onPause();
+    getPreferenceScreen().getSharedPreferences()
+            .unregisterOnSharedPreferenceChangeListener(this);
+}
+
+ +

注意:目前,首选项管理器不会在您调用 {@link +android.content.SharedPreferences#registerOnSharedPreferenceChangeListener +registerOnSharedPreferenceChangeListener()} +时存储对侦听器的强引用。但是,您必须存储对侦听器的强引用,否则它将很容易被当作垃圾回收。 +我们建议您将对侦听器的引用保存在只要您需要侦听器就会存在的对象的实例数据中。 + +

+ +

例如,在以下代码中,调用方未保留对侦听器的引用。 +因此,侦听器将容易被当作垃圾回收,并在将来某个不确定的时间失败: +

+ +
+prefs.registerOnSharedPreferenceChangeListener(
+  // Bad! The listener is subject to garbage collection!
+  new SharedPreferences.OnSharedPreferenceChangeListener() {
+  public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
+    // listener implementation
+  }
+});
+
+ +

有鉴于此,请将对侦听器的引用存储在只要需要侦听器就会存在的对象的实例数据字段中: +

+ +
+SharedPreferences.OnSharedPreferenceChangeListener listener =
+    new SharedPreferences.OnSharedPreferenceChangeListener() {
+  public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
+    // listener implementation
+  }
+};
+prefs.registerOnSharedPreferenceChangeListener(listener);
+
+ +

管理网络使用情况

+ + +

从 Android 4.0 +开始,通过系统的“设置”应用,用户可以了解自己的应用在前台和后台使用的网络数据量。然后,用户可以据此禁止具体的应用使用后台数据。 +为了避免用户禁止您的应用从后台访问数据,您应该有效地使用数据连接,并允许用户通过应用设置优化应用的数据使用。 + +

+ +

例如,您可以允许用户控制应用同步数据的频率,控制应用是否仅在有 +Wi-Fi 时才执行上传/下载操作,以及控制应用能否在漫游时使用数据,等等。为用户提供这些控件后,即使数据使用量接近他们在系统“设置”中设置的限制,他们也不大可能禁止您的应用访问数据,因为他们可以精确地控制应用使用的数据量。 + + +

+ +

在 {@link android.preference.PreferenceActivity} +中添加必要的首选项来控制应用的数据使用习惯后,您应立即在清单文件中为 {@link +android.content.Intent#ACTION_MANAGE_NETWORK_USAGE} 添加 Intent 过滤器。例如:

+ +
+<activity android:name="SettingsActivity" ... >
+    <intent-filter>
+       <action android:name="android.intent.action.MANAGE_NETWORK_USAGE" />
+       <category android:name="android.intent.category.DEFAULT" />
+    </intent-filter>
+</activity>
+
+ +

此 Intent +过滤器指示系统此 Activity 控制应用的数据使用情况。因此,当用户从系统的“设置”应用检查应用所使用的数据量时,可以使用“查看应用设置”按钮启动 +{@link android.preference.PreferenceActivity},这样,用户就能够优化应用使用的数据量。 + +

+ + + + + + + +

构建自定义首选项

+ +

Android 框架包括各种 +{@link android.preference.Preference} +子类,您可以使用它们为各种不同类型的设置构建 UI。不过,您可能会发现自己需要的设置没有内置解决方案,例如,数字选取器或日期选取器。 +在这种情况下,您将需要通过扩展 +{@link android.preference.Preference} 类或其他子类之一来创建自定义首选项。

+ +

扩展 {@link android.preference.Preference} +类时,您需要执行以下几项重要操作:

+ +
    +
  • 指定在用户选择设置时显示的用户界面。
  • +
  • 适时保存设置的值。
  • +
  • 使用显示的当前(默认)值初始化 +{@link android.preference.Preference}。
  • +
  • 在系统请求时提供默认值。
  • +
  • 如果 {@link android.preference.Preference} +提供自己的 UI(例如对话框),请保存并恢复状态以处理生命周期变更(例如,用户旋转屏幕)。
  • +
+ +

下文介绍如何完成所有这些任务。

+ + + +

指定用户界面

+ +

如果您要直接扩展 {@link android.preference.Preference} 类,则需要实现 +{@link android.preference.Preference#onClick()} +来定义在用户选择该项时发生的操作。不过,大多数自定义设置都会扩展 {@link android.preference.DialogPreference} +以显示对话框,从而简化这一过程。扩展 {@link +android.preference.DialogPreference} 时,必须在类构造函数中调用 {@link +android.preference.DialogPreference#setDialogLayoutResource setDialogLayoutResourcs()} +来指定对话框的布局。

+ +

例如,自定义 {@link +android.preference.DialogPreference} +可以使用下面的构造函数来声明布局并为默认的肯定和否定对话框按钮指定文本:

+ +
+public class NumberPickerPreference extends DialogPreference {
+    public NumberPickerPreference(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        
+        setDialogLayoutResource(R.layout.numberpicker_dialog);
+        setPositiveButtonText(android.R.string.ok);
+        setNegativeButtonText(android.R.string.cancel);
+        
+        setDialogIcon(null);
+    }
+    ...
+}
+
+ + + +

保存设置的值

+ +

如果设置的值为整型数或是用于保存布尔值的 +{@link android.preference.Preference#persistBoolean persistBoolean()},则可通过调用 {@link +android.preference.Preference} 类的一个 {@code persist*()} 方法(如 {@link +android.preference.Preference#persistInt persistInt()})随时保存该值。

+ +

注:每个 {@link android.preference.Preference} +均只能保存一种数据类型,因此您必须使用适合自定义 +{@link android.preference.Preference} 所用数据类型的 {@code persist*()} 方法。

+ +

至于何时选择保留设置,则可能取决于要扩展的 {@link +android.preference.Preference} 类。如果扩展 +{@link +android.preference.DialogPreference},则只能在对话框因肯定结果(用户选择“确定”按钮)而关闭时保留该值。

+ +

当 {@link android.preference.DialogPreference} 关闭时,系统会调用 {@link +android.preference.DialogPreference#onDialogClosed onDialogClosed()} 方法。该方法包括一个布尔参数,用于指定用户结果是否为“肯定”;如果值为 +true,则表示用户选择的是肯定按钮且您应该保存新值。 +例如: +

+ +
+@Override
+protected void onDialogClosed(boolean positiveResult) {
+    // When the user selects "OK", persist the new value
+    if (positiveResult) {
+        persistInt(mNewValue);
+    }
+}
+
+ +

在此示例中,mNewValue +是一个类成员,可存放设置的当前值。调用 {@link android.preference.Preference#persistInt persistInt()} 会将该值保存到 +{@link android.content.SharedPreferences} 文件(自动使用在此 +{@link android.preference.Preference} 的 XML 文件中指定的键)。

+ + +

初始化当前值

+ +

系统将 {@link android.preference.Preference} 添加到屏幕时,会调用 +{@link android.preference.Preference#onSetInitialValue onSetInitialValue()} +来通知您设置是否具有保留值。如果没有保留值,则此调用将为您提供默认值。 +

+ +

{@link android.preference.Preference#onSetInitialValue onSetInitialValue()} +方法传递一个布尔值 (restorePersistedValue),以指示是否已为该设置保留值。 +如果值为 true,则应通过调用 +{@link +android.preference.Preference} 类的一个 {@code getPersisted*()} 方法(如整型值对应的 {@link +android.preference.Preference#getPersistedInt getPersistedInt()})来检索保留值。通常,您会需要检索保留值,以便能够正确更新 UI 来反映之前保存的值。 + +

+ +

如果 restorePersistedValue 为 +false,则应使用在第二个参数中传递的默认值。

+ +
+@Override
+protected void onSetInitialValue(boolean restorePersistedValue, Object defaultValue) {
+    if (restorePersistedValue) {
+        // Restore existing state
+        mCurrentValue = this.getPersistedInt(DEFAULT_VALUE);
+    } else {
+        // Set default state from the XML attribute
+        mCurrentValue = (Integer) defaultValue;
+        persistInt(mCurrentValue);
+    }
+}
+
+ +

每种 {@code getPersisted*()} +方法均采用一个参数,用于指定在实际上没有保留值或该键不存在时所要使用的默认值。在上述示例中,当 +{@link +android.preference.Preference#getPersistedInt getPersistedInt()} 不能返回保留值时,局部常量用于指定默认值。

+ +

注意:不能使用 +defaultValue 作为 {@code getPersisted*()} 方法中的默认值,因为当 +restorePersistedValuetrue 时,其值始终为 null。

+ + +

提供默认值

+ +

如果 {@link android.preference.Preference} +类的实例指定一个默认值(使用 {@code android:defaultValue} +属性),则在实例化对象以检索该值时,系统会调用 {@link android.preference.Preference#onGetDefaultValue +onGetDefaultValue()}。您必须实现此方法,系统才能将默认值保存在 {@link +android.content.SharedPreferences} 中。 +例如:

+ +
+@Override
+protected Object onGetDefaultValue(TypedArray a, int index) {
+    return a.getInteger(index, DEFAULT_VALUE);
+}
+
+ +

方法参数可提供您所需的一切:属性的数组和 +{@code android:defaultValue}(必须检索的值)的索引位置。之所以必须实现此方法以从该属性中提取默认值,是因为您必须为此属性指定在未定义属性值时所要使用的局部默认值。 + +

+ + + +

保存和恢复首选项的状态

+ +

正如布局中的 {@link android.view.View} +一样,在重启 Activity 或片段时(例如,用户旋转屏幕),{@link android.preference.Preference} +子类也负责保存并恢复其状态。要正确保存并恢复 +{@link android.preference.Preference} 类的状态,您必须实现生命周期回调方法 +{@link android.preference.Preference#onSaveInstanceState +onSaveInstanceState()} 和 {@link +android.preference.Preference#onRestoreInstanceState onRestoreInstanceState()}。

+ +

{@link android.preference.Preference} 的状态由实现 +{@link android.os.Parcelable} 接口的对象定义。Android 框架为您提供此类对象,作为定义状态对象({@link +android.preference.Preference.BaseSavedState} +类)的起点。

+ +

要定义 {@link android.preference.Preference} 类保存其状态的方式,您应该扩展 +{@link android.preference.Preference.BaseSavedState} 类。您只需重写几种方法并定义 +{@link android.preference.Preference.BaseSavedState#CREATOR} +对象。

+ +

对于大多数应用,如果 {@link android.preference.Preference} +子类保存除整型数以外的其他数据类型,则可复制下列实现并直接更改处理 +{@code value} 的行。

+ +
+private static class SavedState extends BaseSavedState {
+    // Member that holds the setting's value
+    // Change this data type to match the type saved by your Preference
+    int value;
+
+    public SavedState(Parcelable superState) {
+        super(superState);
+    }
+
+    public SavedState(Parcel source) {
+        super(source);
+        // Get the current preference's value
+        value = source.readInt();  // Change this to read the appropriate data type
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        super.writeToParcel(dest, flags);
+        // Write the preference's value
+        dest.writeInt(value);  // Change this to write the appropriate data type
+    }
+
+    // Standard creator object using an instance of this class
+    public static final Parcelable.Creator<SavedState> CREATOR =
+            new Parcelable.Creator<SavedState>() {
+
+        public SavedState createFromParcel(Parcel in) {
+            return new SavedState(in);
+        }
+
+        public SavedState[] newArray(int size) {
+            return new SavedState[size];
+        }
+    };
+}
+
+ +

如果将上述 {@link android.preference.Preference.BaseSavedState} +实现添加到您的应用(通常,作为 {@link android.preference.Preference} +子类的子类),则需要为 +{@link android.preference.Preference} 子类实现 {@link android.preference.Preference#onSaveInstanceState +onSaveInstanceState()} 和 {@link +android.preference.Preference#onRestoreInstanceState onRestoreInstanceState()} 方法。

+ +

例如:

+ +
+@Override
+protected Parcelable onSaveInstanceState() {
+    final Parcelable superState = super.onSaveInstanceState();
+    // Check whether this Preference is persistent (continually saved)
+    if (isPersistent()) {
+        // No need to save instance state since it's persistent,
+        // use superclass state
+        return superState;
+    }
+
+    // Create instance of custom BaseSavedState
+    final SavedState myState = new SavedState(superState);
+    // Set the state's value with the class member that holds current
+    // setting value
+    myState.value = mNewValue;
+    return myState;
+}
+
+@Override
+protected void onRestoreInstanceState(Parcelable state) {
+    // Check whether we saved the state in onSaveInstanceState
+    if (state == null || !state.getClass().equals(SavedState.class)) {
+        // Didn't save the state, so call superclass
+        super.onRestoreInstanceState(state);
+        return;
+    }
+
+    // Cast state to custom BaseSavedState and pass to superclass
+    SavedState myState = (SavedState) state;
+    super.onRestoreInstanceState(myState.getSuperState());
+    
+    // Set this Preference's widget to reflect the restored state
+    mNumberPicker.setValue(myState.value);
+}
+
+ diff --git a/docs/html-intl/intl/zh-cn/guide/topics/ui/ui-events.jd b/docs/html-intl/intl/zh-cn/guide/topics/ui/ui-events.jd new file mode 100644 index 0000000000000000000000000000000000000000..f9e976302d347003707e8d9f59a19aeb19330510 --- /dev/null +++ b/docs/html-intl/intl/zh-cn/guide/topics/ui/ui-events.jd @@ -0,0 +1,291 @@ +page.title=输入事件 +parent.title=用户界面 +parent.link=index.html +@jd:body + +
+ +
+ +

在 Android 系统中,从用户与应用的交互中截获事件的方法不止一种。如考虑截获用户界面内的事件,则可从用户与之交互的特定视图对象中捕获事件。 + +为此,View 类提供了多种方法。

+ +

在您将用于构建布局的各种 View 类中,您可能会注意到几种看起来适用于 +UI 事件的公共回调方法。当该对象上发生相应的操作时,Android +框架会调用这些方法。例如,在触摸一个视图对象(例如“按钮”)时,对该对象调用 +onTouchEvent() 方法。不过,为了截获此事件,您必须扩展 View 类并重写该方法。 +然而,为了处理此类事件而扩展每个视图对象并不现实。 +正因如此,View 类还包含一系列嵌套接口以及您可以更加轻松定义的回调。 +这些接口称为事件侦听器,是您捕获用户与 +UI 之间交互的票证。

+ +

尽管您通常会使用事件侦听器来侦听用户交互,但有时您确实需要扩展 View 类以构建自定义组件。 +也许,您想扩展 +{@link android.widget.Button} +类来丰富某些内容的样式。在这种情况下,您将能够使用该类的事件处理程序为类定义默认事件行为。 +

+ + +

事件侦听器

+ +

事件侦听器是 {@link android.view.View} +类中包含一个回调方法的接口。当用户与 UI 项目之间的交互触发已注册此视图的侦听器时,Android +框架将调用这些方法。

+ +

各事件侦听器接口包含的回调方法如下:

+ +
+
onClick()
+
在 {@link android.view.View.OnClickListener} 中。 +当用户触摸项目(处于触摸模式下)时,或者使用导航键或轨迹球聚焦于项目,然后按适用的“Enter”键或按下轨迹球时,将调用此方法。 + +
+
onLongClick()
+
在 {@link android.view.View.OnLongClickListener} 中。 +当用户触摸并按住项目(处于触摸模式下)者,或者使用导航键或轨迹球聚焦于项目,然后按住适用的“Enter”键或按住轨迹球(持续一秒钟)时,将调用此方法。 + +
+
onFocusChange()
+
在 {@link android.view.View.OnFocusChangeListener} 中。 +当用户使用导航键或轨迹球导航到或远离项目时,将调用此方法。
+
onKey()
+
在 {@link android.view.View.OnKeyListener} 中。 +当用户聚焦于项目并按下或释放设备上的硬按键时,将调用此方法。
+
onTouch()
+
在 {@link android.view.View.OnTouchListener} 中。 +当用户执行可视为触摸事件的操作时,其中包括按下、释放或屏幕上的任何移动手势(在项目边界内),将调用此方法。 +
+
onCreateContextMenu()
+
在 {@link android.view.View.OnCreateContextMenuListener} 中。 +当(因持续“长按”而)生成上下文菜单时,将调用此方法。请参阅菜单开发者指南中有关上下文菜单的阐述。 + +
+
+ +

这些方法是其相应接口的唯一成员。要定义其中一个方法并处理事件,请在 Activity 中实现嵌套接口或将其定义为匿名类。然后,将实现的实例传递给相应的 +View.set...Listener() +方法。 +(例如,调用 +{@link android.view.View#setOnClickListener(View.OnClickListener) setOnClickListener()} +并向其传递 {@link android.view.View.OnClickListener OnClickListener} 实现。)

+ +

以下示例显示了如何为按钮注册点击侦听器。

+ +
+// Create an anonymous implementation of OnClickListener
+private OnClickListener mCorkyListener = new OnClickListener() {
+    public void onClick(View v) {
+      // do something when the button is clicked
+    }
+};
+
+protected void onCreate(Bundle savedValues) {
+    ...
+    // Capture our button from layout
+    Button button = (Button)findViewById(R.id.corky);
+    // Register the onClick listener with the implementation above
+    button.setOnClickListener(mCorkyListener);
+    ...
+}
+
+ +

您可能还会发现,将 +OnClickListener 作为 Activity 的一部分来实现更为方便。这样可以避免加载额外的类和分配对象。例如:

+
+public class ExampleActivity extends Activity implements OnClickListener {
+    protected void onCreate(Bundle savedValues) {
+        ...
+        Button button = (Button)findViewById(R.id.corky);
+        button.setOnClickListener(this);
+    }
+
+    // Implement the OnClickListener callback
+    public void onClick(View v) {
+      // do something when the button is clicked
+    }
+    ...
+}
+
+ +

请注意,上述示例中的 +onClick() 回调没有返回值,但是其他某些事件侦听器方法必须返回布尔值。具体原因取决于事件。 +对于这几个事件侦听器,必须返回布尔值的原因如下:

+
    +
  • {@link android.view.View.OnLongClickListener#onLongClick(View) onLongClick()}: +此方法返回一个布尔值,表示您是否已处理完事件,以及是否应该将它继续传下去。 +也就是说,返回“true”表示您已经处理事件且事件应就此停止;如果您尚未处理事件和/或事件应该继续传递给其他任何点击侦听器,则返回“false”。 + +
  • +
  • {@link android.view.View.OnKeyListener#onKey(View,int,KeyEvent) onKey()}: +此方法返回一个布尔值,表示您是否已处理完事件,以及是否应该将它继续传下去。 + 也就是说,返回“true”表示您已经处理事件且事件应就此停止;如果您尚未处理事件和/或事件应该继续传递给其他任何按键侦听器,则返回“false”。 + +
  • +
  • {@link android.view.View.OnTouchListener#onTouch(View,MotionEvent) onTouch()}: +此方法返回一个布尔值,表示侦听器是否处理完此事件。重要的是,此事件可以拥有多个分先后顺序的操作。 +因此,如果在收到关闭操作事件时返回“false”,则表示您并未处理完此事件,而且对其后续操作也不感兴趣。 + +因此,您无需执行事件内的任何其他操作,如手势或最终操作事件。 +
  • +
+ +

请记住,硬按键事件总是传递给目前处于焦点的视图对象。它们从视图 +层次结构的顶部开始分派,然后向下,直至到达合适的目的地。如果您的视图对象(或视图对象的子项)目前具有焦点,那么您可以看到事件经由 +{@link android.view.View#dispatchKeyEvent(KeyEvent) +dispatchKeyEvent()} 方法的分派过程。除了通过视图捕获按键事件,您还可以使用 +{@link android.app.Activity#onKeyDown(int,KeyEvent) onKeyDown()} +和 {@link android.app.Activity#onKeyUp(int,KeyEvent) onKeyUp()} 接收 Activity 内部的所有事件。

+ +

此外,考虑应用的文本输入时,请记住:许多设备只有软件输入法。 +此类方法无需基于按键;某些可能使用语音输入、手写等。尽管输入法提供了类似键盘的界面,但它通常不会触发 +{@link android.app.Activity#onKeyDown(int,KeyEvent) onKeyDown()} +系列的事件。除非您要将应用限制为带有硬键盘的设备,否则,在设计 UI 时切勿要求必须通过特定按键进行控制。 + +特别是,当用户按下返回键时,不要依赖这些方法验证输入;请改用 +{@link android.view.inputmethod.EditorInfo#IME_ACTION_DONE} +等操作让输入法知晓您的应用预计会作何反应,这样,可以通过一种有意义的方式更改其 UI。不要推断软件输入法应如何工作,只要相信它能够为应用提供已设置格式的文本即可。 +

+ +

注:Android 会先调用事件处理程序,然后从类定义调用合适的默认处理程序。 +因此,从这些事件侦听器返回“true”会停止将事件传播到其他事件侦听器,还会阻止回调视图对象中的默认事件处理程序。 + +因此,在返回“true”时请确保您要终止事件。

+ + +

事件处理程序

+ +

如果您从视图构建自定义组件,则将能够定义几种用作默认事件处理程序的回调方法。在有关自定义组件的文档中,您将了解某些用于事件处理的常见回调,其中包括: + + + +

+
    +
  • {@link android.view.View#onKeyDown}:在发生新的按键事件时调用
  • +
  • {@link android.view.View#onKeyUp}:在发生按键弹起事件时调用
  • +
  • {@link android.view.View#onTrackballEvent}:在发生轨迹球运动事件时调用
  • +
  • {@link android.view.View#onTouchEvent}:在发生触摸屏运动事件时调用
  • +
  • {@link android.view.View#onFocusChanged}:在视图获得或失去焦点时调用
  • +
+

还有一些其他方法值得您注意,尽管它们并非 View 类的一部分,但可能会直接影响所能采取的事件处理方式。 +因此,在管理布局内更复杂的事件时,请考虑使用以下其他方法: +

+
    +
  • {@link android.app.Activity#dispatchTouchEvent(MotionEvent) + Activity.dispatchTouchEvent(MotionEvent)}:此方法允许 {@link + android.app.Activity} 在分派给窗口之前截获所有触摸事件。
  • +
  • {@link android.view.ViewGroup#onInterceptTouchEvent(MotionEvent) + ViewGroup.onInterceptTouchEvent(MotionEvent)}:此方法允许 {@link + android.view.ViewGroup} 监视分派给子视图的事件。
  • +
  • {@link android.view.ViewParent#requestDisallowInterceptTouchEvent(boolean) + ViewParent.requestDisallowInterceptTouchEvent(boolean)}: +对父视图调用此方法表明不应使用 {@link + android.view.ViewGroup#onInterceptTouchEvent(MotionEvent)} 截获触摸事件。
  • +
+ +

触摸模式

+

+当用户使用方向键或轨迹球导航用户界面时,必须聚焦到可操作项目上(如按钮),以便用户看到将接受输入的对象。 + +但是,如果设备具有触摸功能且用户开始通过触摸界面与之交互,则不再需要突出显示项目或聚焦到特定视图对象上。 + +因此,有一种交互模式称为“触摸模式”。 + +

+

+对于支持触摸功能的设备,当用户触摸屏幕时,设备会立即进入触摸模式。 +自此以后,只有 +{@link android.view.View#isFocusableInTouchMode} +为“true”的视图才可聚焦,如文本编辑小工具。其他可触摸的视图(如按钮)在用户触摸时不会获得焦点;按下时它们只是触发点击侦听器。 + +

+

+无论何时,只要用户点击方向键或滚动轨迹球,设备就会退出触摸模式并找到一个视图使其获得焦点。 +现在,用户可在不触摸屏幕的情况下继续与用户界面交互。 + +

+

+整个系统(所有窗口和 Activity)都将保持触摸模式状态。要查询当前状态,您可以调用 +{@link android.view.View#isInTouchMode} +来检查设备目前是否处于触摸模式。 +

+ + +

处理焦点

+ +

该框架将处理例行焦点移动来响应用户输入。其中包括在视图被删除或隐藏时或在新视图变得可用时更改焦点。 + +视图对象表示愿意通过 +{@link android.view.View#isFocusable()} 方法获得焦点。要设置视图能否获得焦点,请调用 +{@link android.view.View#setFocusable(boolean) setFocusable()}。在触摸模式中,您可以使用 +{@link android.view.View#isFocusableInTouchMode()} 查询视图是否允许聚焦,而且可以使用 +{@link android.view.View#setFocusableInTouchMode(boolean) setFocusableInTouchMode()} 对此进行更改。 +

+ +

焦点移动所使用的算法会查找指定方向上距离最近的元素。 +在极少数情况下,默认算法可能与开发者的期望行为不一致。 +在这些情况下,您可以在布局文件中显式重写以下 +XML 属性: +nextFocusDownnextFocusLeftnextFocusRight和 +nextFocusUp。将其中一个属性添加到失去焦点的视图。 +将属性的值定义为应该聚焦的视图的 +ID。例如:

+
+<LinearLayout
+    android:orientation="vertical"
+    ... >
+  <Button android:id="@+id/top"
+          android:nextFocusUp="@+id/bottom"
+          ... />
+  <Button android:id="@+id/bottom"
+          android:nextFocusDown="@+id/top"
+          ... />
+</LinearLayout>
+
+ +

一般来说,在此垂直布局中,从第一个按钮向上导航或从第二个按钮向下导航,焦点都不会移到任何其他位置。 +现在,顶部按钮已将底部按钮定义为 + nextFocusUp (反之亦然),因而导航焦点将自上而下和自下而上循环往复。 +

+ +

若要将一个视图声明为在 UI 中可聚焦(传统上并非如此),请在布局声明中将 android:focusable XML 属性添加到该视图。将值设置为 + + true。此外,您还可以使用 +android:focusableInTouchMode 将 Vew 声明为在触摸模式下可聚焦。

+

要请求要获得焦点的特定视图,请调用 {@link android.view.View#requestFocus()}

+

要侦听焦点事件(视图获得或失去焦点时会收到通知),请使用 +{@link android.view.View.OnFocusChangeListener#onFocusChange(View,boolean) onFocusChange()}, +如上文的事件侦听器部分中所述。

+ + + + diff --git a/docs/html-intl/intl/zh-cn/sdk/index.jd b/docs/html-intl/intl/zh-cn/sdk/index.jd new file mode 100644 index 0000000000000000000000000000000000000000..6703f15facfa64ed4172366cf8986618ac6bba85 --- /dev/null +++ b/docs/html-intl/intl/zh-cn/sdk/index.jd @@ -0,0 +1,487 @@ +page.title=下载 Android Studio 和 SDK 工具 +page.tags=sdk, android studio +page.template=sdk +header.hide=1 +page.metaDescription=下载官方 Android IDE 和开发者工具,构建适用于 Android 手机、平板电脑、可穿戴设备、电视等设备的应用。 + +studio.version=1.1.0 + +studio.linux_bundle_download=android-studio-ide-135.1740770-linux.zip +studio.linux_bundle_bytes=259336386 +studio.linux_bundle_checksum=e8d166559c50a484f83ebfec6731cc0e3f259208 + +studio.mac_bundle_download=android-studio-ide-135.1740770-mac.dmg +studio.mac_bundle_bytes=261303345 +studio.mac_bundle_checksum=f9745d0fec1eefd498f6160a2d6a1b5247d4cda3 + +studio.win_bundle_exe_download=android-studio-bundle-135.1740770-windows.exe +studio.win_bundle_exe_bytes=856233768 +studio.win_bundle_exe_checksum=7484b9989d2914e1de30995fbaa97a271a514b3f + +studio.win_notools_exe_download=android-studio-ide-135.1740770-windows.exe +studio.win_notools_exe_bytes=242135128 +studio.win_notools_exe_checksum=5ea77661cd2300cea09e8e34f4a2219a0813911f + +studio.win_bundle_download=android-studio-ide-135.1740770-windows.zip +studio.win_bundle_bytes=261667054 +studio.win_bundle_checksum=e903f17cc6a57c7e3d460c4555386e3e65c6b4eb + + + +sdk.linux_download=android-sdk_r24.1.2-linux.tgz +sdk.linux_bytes=168121693 +sdk.linux_checksum=68980e4a26cca0182abb1032abffbb72a1240c51 + +sdk.mac_download=android-sdk_r24.1.2-macosx.zip +sdk.mac_bytes=89151287 +sdk.mac_checksum=00e43ff1557e8cba7da53e4f64f3a34498048256 + +sdk.win_download=android-sdk_r24.1.2-windows.zip +sdk.win_bytes=159778618 +sdk.win_checksum=704f6c874373b98e061fe2e7eb34f9fcb907a341 + +sdk.win_installer=installer_r24.1.2-windows.exe +sdk.win_installer_bytes=111364285 +sdk.win_installer_checksum=e0ec864efa0e7449db2d7ed069c03b1f4d36f0cd + + + + + + +@jd:body + + + + + + + +
+ + + + + + + + + +
+ +
 
+ + + +
+ +

Android Studio

+ +

官方 Android IDE

+ +
    +
  • Android Studio IDE
  • +
  • Android SDK 工具
  • +
  • Android 5.0 (Lollipop) 平台
  • +
  • Android 5.0 模拟器系统映像(附带 Google API)
  • +
+ + +下载 Android Studio + + +

+如需获得 Android Studio 或独立 SDK 工具,请访问 developer.android.com/sdk/ +

+ + + +
+ + + + +

智能代码编辑器

+ +
+ +
+ +
+

Android Studio 的核心是一个智能代码编辑器,可进行高级代码完成、重构和代码分析。 +

+

这款功能强大的代码编辑器可帮助您成为更高产的 Android 应用开发者。

+
+ + + + + +

代码模板和 GitHub 集成

+ +
+ +
+ +
+

新项目向导让开始一个新项目变得前所未有的简单。

+ +

可使用适用于不同模式(如抽屉式导航栏和视图分页器)的模板代码开始项目,甚至可以从 GitHub +导入 Google 代码示例。

+
+ + + + +

多屏幕应用开发

+ +
+ +
+ +
+

构建适用于 Android 手机、平板电脑、Android Wear、Android TV、Android Auto +以及 Google Glass 的应用。

+

Android Studio 内全新的 Android +项目视图和模块支持让应用项目和资源管理变得更加轻松。 +

+ + + + +

用于模拟所有形状和尺寸的虚拟设备

+ +
+ +
+ +
+

Android Studio 预先配置了经过优化的模拟器映像。

+

经过更新和精简的虚拟设备管理器可为常见 Android +设备提供预定义设备配置文件。

+
+ + + + +

+Android 版本借助 Gradle 演进

+ +
+ +
+ +
+

使用同一项目为您的 Android 应用创建多个具有不同功能的 APK。

+

使用 Maven 管理应用依赖项。

+

使用 Android Studio 或命令行构建 APK。

+
+ + + + +

有关 Android Studio 的详细信息

+
+ +下载 Android Studio + +
    +
  • 这款由 JetBrains 推出并广受欢迎的 Java IDE 以 IntelliJ IDEA Community Edition 为基础构建
  • +
  • 基于 Gradle 的灵活构建系统
  • +
  • 构建变体和多 APK 生成
  • +
  • 为 Google 服务和各种设备类型提供扩展模板支持
  • +
  • 支持主题编辑的富布局编辑器
  • +
  • 可捕捉性能、可用性、版本兼容性以及其他问题的 Lint 工具
  • +
  • ProGuard 和应用签名功能
  • +
  • 内置对 Google 云平台的支持,可轻松集成 Google Cloud + Messaging 和应用引擎
  • +
+ +

+有关 Android Studio +所提供功能的更多详细信息,请阅读 Android Studio 基础知识指南。

+
+ + +

如果您一直在使用附带 ADT 的 Eclipse,请注意 Android Studio 现已成为 Android +的官方 IDE,因此您应迁移至 Android Studio,以获得所有最新的 IDE +更新。如果在迁移项目时需要帮助,请参阅迁移至 +Android +Studio

+ + + + + + + +

系统要求

+ +

Windows

+ +
    +
  • Microsoft® Windows® 8/7/Vista/2003(32 位或 64 位)
  • +
  • 最低:2GB RAM,推荐:4GB RAM
  • +
  • 400MB 硬盘空间
  • +
  • Android SDK、模拟器系统映像及缓存至少需要 1GB 空间
  • +
  • 最低屏幕分辨率:1280 x 800
  • +
  • Java 开发工具包 (JDK) 7
  • +
  • 用于加速模拟器的选件:支持 Intel® VT-x、Intel® EM64T (Intel® 64) 和禁止执行 +(XD) 位功能的 Intel® 处理器
  • +
+ + +

Mac OS X

+ +
    +
  • Mac® OS X® 10.8.5 或更高版本,直至 10.9 (Mavericks)
  • +
  • 最低:2GB RAM,推荐:4GB RAM
  • +
  • 400MB 硬盘空间
  • +
  • Android SDK、模拟器系统映像及缓存至少需要 1GB 空间
  • +
  • 最低屏幕分辨率:1280 x 800
  • +
  • Java 运行组件环境 (JRE) 6
  • +
  • Java 开发工具包 (JDK) 7
  • +
  • 用于加速模拟器的选件:支持 Intel® VT-x、Intel® EM64T (Intel® 64) +和禁止执行 (XD) 位功能的 Intel® 处理器
  • +
+ +

在 Mac OS 上运行附带 Java 运行组件环境 (JRE) 6 的 Android Studio +可优化字体渲染。您随后可将您的项目配置为使用 Java 开发工具包 (JDK) 6 或 JDK 7。

+ + + +

Linux

+ +
    +
  • GNU 网络对象模型环境或 KDE 桌面
  • +
  • GNU C Library (glibc) 2.15 或更高版本
  • +
  • 最低:2GB RAM,推荐:4GB RAM
  • +
  • 400MB 硬盘空间
  • +
  • Android SDK、模拟器系统映像及缓存至少需要 1GB 空间
  • +
  • 最低屏幕分辨率:1280 x 800
  • +
  • Oracle® Java 开发工具包 (JDK) 7
  • +
+

已在 +Ubuntu® 14.04 (Trusty Tahr)(能够运行 32 位应用的 64 位分发)上进行了测试。

+ + + + +

其他下载选项

+ + diff --git a/docs/html-intl/intl/zh-cn/sdk/installing/adding-packages.jd b/docs/html-intl/intl/zh-cn/sdk/installing/adding-packages.jd new file mode 100644 index 0000000000000000000000000000000000000000..04f55b10da972cab53721e0788e694cee70c5dd1 --- /dev/null +++ b/docs/html-intl/intl/zh-cn/sdk/installing/adding-packages.jd @@ -0,0 +1,227 @@ +page.title=添加 SDK 软件包 + +page.tags=sdk 管理器 +helpoutsWidget=true + +@jd:body + + + + +

+默认情况下,Android SDK +并不包括您着手开发所需的一切内容。SDK +将工具、平台和其他组件分成若干个软件包,您可以通过 +Android SDK 管理器根据需要下载这些软件包。因此您需要先为 Android SDK +添加几个软件包,然后才能着手开发。

+ +

要添加软件包,请先通过下列方式之一启动 Android SDK 管理器:

+
    +
  • 在 Android Studio 中,点击工具栏中的 SDK 管理器 +
  • +
  • 如果您未使用 Android Studio: +
      +
    • Windows:双击 Android + SDK 根目录中的 SDK Manager.exe 文件。
    • +
    • Mac/Linux:打开一个终端,导航到 Android SDK 安装 +位置的 tools/ 目录,然后执行 android sdk
    • +
    +
  • +
+ +

当您首次打开 SDK +管理器时,有几个软件包默认处于选定状态。保持它们的选定状态,但务必按照下列步骤获得着手开发所需的一切内容: +

+ + +
    +
  1. +

    获得最新的 SDK 工具

    + + + +

    在安装 Android SDK 时, +您至少应下载最新工具和 Android 平台:

    +
      +
    1. 打开 Tools 目录并选择: +
        +
      • Android SDK Tools
      • +
      • Android SDK Platform-tools
      • +
      • Android SDK Build-tools(最高版本)
      • +
      +
    2. +
    3. 打开第一个 Android X.X 文件夹(最新版本)并选择: +
        +
      • SDK Platform
      • +
      • 模拟器系统映像,例如
        + ARM EABI v7a System Image
      • +
      +
    4. +
    +
  2. + +
  3. +

    获得其他 API 的支持库

    + + + +

    Android 支持库提供了一组丰富的 API,可兼容大多数版本的 Android。 +

    + +

    打开 Extras 目录并选择:

    +
      +
    • Android Support Repository
    • +
    • Android Support Library
    • +
    + +

     

    +

     

    + +
  4. + + +
  5. +

    为更多 API 获得 Google Play 服务

    + + + +

    要使用 Google API 进行开发,您需要 Google Play 服务包:

    +

    打开 Extras 目录并选择:

    +
      +
    • Google Repository
    • +
    • Google Play services
    • +
    + +

    注:并非所有采用 + Android 技术的设备上都提供 Google Play Services API,但所有安装了 Google Play 商店的设备均提供这些 API。要在 Android 模拟器中使用这些 + API,您还必须安装 SDK 管理器中最新 Android X.X 目录内的 Google APIs + 系统映像。

    +
  6. + + +
  7. +

    安装软件包

    +

    选择想要安装的所有软件包后,即可继续执行安装:

    +
      +
    1. 点击 Install X packages
    2. +
    3. 在下一个窗口中,双击左侧的每个软件包名称, +以接受各软件包的许可协议。
    4. +
    5. 点击 Install
    6. +
    +

    SDK 管理器窗口底部显示下载进度。 + 切勿退出 SDK 管理器,否则它会取消下载。

    +
  8. + +
  9. +

    赶快着手开发吧!

    + +

    现在您的 Android SDK 已包含上述软件包,您随时可以着手开发 + Android 应用。当新工具以及其他 API 推出时,只需启动 SDK 管理器 +,为您的 SDK 下载新软件包。

    + +

    以下几个方案可为您的开发工作提供指导:

    + +
    +
    +

    入门指南

    +

    如果您是第一次开发 Android +应用,请按照开发您的第一款应用指南了解 Android 应用的基础知识。

    + +
    +
    +

    开发穿戴式设备应用

    +

    如果您已准备好着手开发 Android +穿戴式设备应用,请参阅开发 Android Wear 应用

    + +
    +
    +

    使用 Google API

    +

    要开始使用 Google API,例如 Google Maps API 或 Google +Play Game Services +API,请参阅设置 Google Play +服务指南。

    + +
    +
    + + +
  10. + +
+ + diff --git a/docs/html-intl/intl/zh-tw/guide/components/activities.jd b/docs/html-intl/intl/zh-tw/guide/components/activities.jd new file mode 100644 index 0000000000000000000000000000000000000000..ea0934964fedf6a24827003d9aea08246ccf86ab --- /dev/null +++ b/docs/html-intl/intl/zh-tw/guide/components/activities.jd @@ -0,0 +1,756 @@ +page.title=Activity +page.tags=Activity,意圖 +@jd:body + + + + + +

{@link android.app.Activity} 是提供畫面的應用程式元件,使用者可以與此畫面互動以執行動作,例如撥號、拍照、傳送電子郵件或檢視地圖。 + +每個 Activity 都會有專屬視窗,用於繪製其使用者介面。視窗一般會佔滿螢幕,但也可能小於螢幕或在其他視窗上方浮動。 + +

+ +

應用程式通常由多個 Activity 組成,這些 Activity 之間的繫結鬆散。 +一般來說,應用程式中的某個 Activity 會指定為「主要」Activity。使用者第一次啟動應用程式時,會將此 Activity 向使用者顯示。 +然後,每個 Activity 可以啟動另一個 Activity,以執行不同的動作。 +每次啟動新的 Activity 時,之前的 Activity 會停止,但系統將該 Activity 保留在堆疊中(即「返回堆疊」)。 + +新的 Activity 啟動時會推送至返回堆疊,然後取得使用者焦點。 +返回堆疊遵循基本的「後進先出」堆疊機制,因此,使用者完成目前的 Activity,按下 [返回] 按鈕時,目前的 Activity 會從堆疊被推出 (並終止),然後繼續之前的 Activity。 + +(返回堆疊在工作和返回堆疊文件中,有更詳細的說明)。 + +

+ +

Activity 因啟動新的 Activity 而停止時,Activity 的生命週期回呼方法會通知此狀態的變更。由於 Activity 狀態的變更 — 可能是系統建立、停止、繼續或終止 Activity 時 — Activity 會收到數個回呼方法,而且每個回呼都提供您執行適合該次狀態變更的特定工作機會。 + + + + +例如,停止時,您的 Activity 應釋放任何大型物件,例如網路或資料庫連線。 +Activity 繼續時,您可以重新取得必要資源,並繼續之前中斷的動作。 +這些狀態轉換都是 Activity 生命週期的一部分。 +

+ +

本文件的其他部分會討論建置和使用 Activity 的基本概念,包括完整討論 Activity 生命週期的運作方式,讓您可以正確地管理各種 Activity 狀態之間的轉換。 + +

+ + + +

建立 Activity

+ +

如要建立 Activity,您必須建立 {@link android.app.Activity} 的子類別 (或它現有的子類別)。 +在您的子類別中,您需要實作回呼方法,當 Activity 在其生命週期的各種狀態之間轉換時,系統可以呼叫此回呼方法。例如,Activity 建立、停止、繼續或終止時。 + +最重要的兩個回呼方法如下: +

+ +
+
{@link android.app.Activity#onCreate onCreate()}
+
您必須實作此方法。系統建立您的 Activity 時會呼叫此方法。 +在您的實作中,應該初始化 Activity 的基本元件。 + + 最重要的是,您必須在這裡呼叫 {@link android.app.Activity#setContentView + setContentView()},才能定義 Activity 使用者介面的版面配置。
+
{@link android.app.Activity#onPause onPause()}
+
系統呼叫此方法做為使用者離開您的 Activity 的第一個指標 (但並非一定表示該 Activity 遭到終止)。 +您通常需要透過這個方法提交要在目前的使用者工作階段以外保留的任何變更 (原因在於使用者可能不會返回)。 + +
+
+ +

還有一些您應使用的其他生命週期回呼方法,以便提供 Activity 之間流暢的使用者體驗,並處理讓您的 Activity 停止、甚至終止的非預期中斷。 + +如需所有生命週期回呼方法的相關資訊,請參閱管理 Activity 生命週期。 +

+ + + +

實作使用者介面

+ +

Activity 的使用者介面是由階層的檢視所提供 — 衍生自 {@link android.view.View} 類別的物件。 +每個檢視都控制 Activity 視窗內特定的長方形空間,並且可以回應使用者的互動。 +例如,檢視可能是按鈕,使用者觸碰此按鈕時會初始化一個動作。 +

+ +

Android 提供許多現成的檢視,您可以用於設計並組織您的版面配置。 +「小工具」是在畫面上提供視覺和互動元素的檢視,例如按鈕、文字欄位、核取方塊或只是一張影像。 +「版面配置」是衍生自 {@link +android.view.ViewGroup} 的檢視,讓子檢視可具有獨特的版面配置模型,例如線性版面配置、網格版面配置或相對版面配置。 +您也可以製作 {@link android.view.View} 和 +{@link android.view.ViewGroup} 類別 (或現有子類別) 的子類別,以建立您自己的小工具和版面配置,然後套用到您的 Activity 版面配置。 +

+ +

使用檢視定義版面配置的最常見方式,是使用 XML 版面配置檔案 (儲存於您的應用程式資源)。 +這樣一來,您可以分別維護使用者介面的設計和定義 Activity 行為的原始程式碼。 +您可以使用 {@link android.app.Activity#setContentView(int) setContentView()} 傳送版面配置的資源 ID,將版面配置設為 Activity 的 UI。 + +不過,您也可以透過將新的 {@link +android.view.View} 插入 {@link android.view.ViewGroup},然後藉由將根 +{@link android.view.ViewGroup} 傳送給 {@link android.app.Activity#setContentView(View) +setContentView()} 來使用該版面配置,以便在您的 Activity 程式碼中建立新的 {@link android.view.View},並且建置視圖層次。 +

+ +

如需關於建立使用者介面的詳細資訊,請參閱使用者介面

+ + + +

在宣示說明中宣告 Activity

+ +

您必須在宣示說明檔案中宣告 Activity,系統才能加以存取。 +如要宣告您的 Activity,請開啟宣示說明檔案,然後新增 {@code <activity>} 元素做為 {@code <application>} 元素的下層物件。 + +範例:

+ +
+<manifest ... >
+  <application ... >
+      <activity android:name=".ExampleActivity" />
+      ...
+  </application ... >
+  ...
+</manifest >
+
+ +

您可以包括此元素中的其他屬性 (attribute) 來定義屬性 (property),例如 Activity 的標籤、Activity 的圖示或 Activity UI 的設計風格。{@code android:name} 屬性 (attribute) 是唯一必須的屬性 — 它會指定 Activity 的類別名稱。 + + +一旦發佈應用程式,您就不得變更此名稱,這是因為這樣做可能會破壞一些功能,例如應用程式捷徑 (閱讀部落格貼文:不能變更的事項)。 + + +

+ +

請參閱 {@code <activity>} 元素參考文件,進一步瞭解如何在宣示說明中宣告 Activity。 +

+ + +

使用意圖篩選器

+ +

{@code +<activity>} 元素也可以指定各種意圖篩選器 — 使用 {@code +<intent-filter>} 元素 — 以便宣告其他應用程式元件啟動它的方式。 +

+ +

使用 Android SDK 工具建立新的應用程式時,自動為您建立的 +虛設常式 Activity 會內含意圖篩選器。含意圖篩選器會宣告回應 +「主要」動作的 Activity,並且應放置在「啟動器」類別。意圖篩選器看起來會如下所示: +

+ +
+<activity android:name=".ExampleActivity" android:icon="@drawable/app_icon">
+    <intent-filter>
+        <action android:name="android.intent.action.MAIN" />
+        <category android:name="android.intent.category.LAUNCHER" />
+    </intent-filter>
+</activity>
+
+ +

{@code +<action>} 元素指出這是應用程式的「主要」進入點。{@code +<category>} 元素指出此 Activity 應列於系統的應用程式啟動器 (以允許使用者啟動此 Activity)。 +

+ +

如果您本來就要讓應用程式獨自運作,不要讓其他應用程式啟動其 Activity,則不需要任何其他意圖篩選器。 +只有一個 Activity 可以有「主要」動作和「啟動器」類別,如同上一個範例所示。 +您不要讓其他應用程式使用的 Activity,則不應使用意圖篩選器,而且您可以使用明確的意圖自行加以啟動 (將於以下小節討論)。 + +

+ +

不過,如果您的 Activity 需要回應從其他應用程式 (和您自己的應用程式) 傳送過來的隱含式意圖,則必須為您的 Activity 定義額外的意圖篩選器。 + +針對您要回應的意圖類型,您必須包括 {@code +<intent-filter>},其中內含 +{@code +<action>} 元素和 {@code +<category>} 元素 (選用) 和/或 {@code +<data>} 元素。這些元素會指定您的 Activity 可以回應哪些類型的意圖。 +

+ +

如需關於您的 Activity 如何回應意圖的詳細資訊,請參閱意圖和意圖篩選器。 +

+ + + +

啟動 Activity

+ +

透過呼叫 {@link android.app.Activity#startActivity + startActivity()}、將 {@link android.content.Intent} (描述要啟動的 Activity) 傳給它,您可以啟動另一個 Activity。 +意圖會指出您要啟動的那個 Activity,或描述您要執行的動作類型 (而讓系統為您選取適當的 Activity,甚至可以是來自不同應用程式的 Activity)。 + + +意圖也可以攜帶少量的資料給已啟動的 Activity 使用。 +

+ +

在您自己的應用程式內運作時,通常只要啟動已知的 Activity。 + 建立意圖並明確定義您要啟動的 Activity (使用類別名稱),可以達成此目的。 +例如,以下示範 Activity 如何啟動另一個名為 {@code +SignInActivity} 的 Activity:

+ +
+Intent intent = new Intent(this, SignInActivity.class);
+startActivity(intent);
+
+ +

不過,您的應用程式也希望可以執行其他動作,例如使用您 Activity 中的資料以傳送電子郵件、文字訊息或狀態更新。 +在此情況下,您的應用程式可能就沒有專屬的 Activity 來執行這類動作。因此,您可以改為運用裝置上其他應用程式提供的 Activity。讓這些 Activity 為您執行所需的動作。 + +這正是意圖寶貴的地方 — 您可以建立意圖,在其中描述您要執行的動作,然後系統會從另一個應用程式啟動適當的 Activity。 + + +如果有多個 Activity 都可以處理意圖,則使用者可以選取要使用哪個 Activity。 +例如,如果要讓使用者傳送電子郵件訊息,您可以建立下列意圖: + +

+ +
+Intent intent = new Intent(Intent.ACTION_SEND);
+intent.putExtra(Intent.EXTRA_EMAIL, recipientArray);
+startActivity(intent);
+
+ +

加入意圖的 {@link android.content.Intent#EXTRA_EMAIL} 額外值是一個字串陣列,由應該要寄送電子郵件的電子郵件地址所組成。 +電子郵件應用程式回應此意圖時,它會讀取額外值中提供的字串陣列,並將這些內容放置在編寫電子郵件表單的「收件人」欄位。 + +在此情況下,電子郵件應用程式的 Activity 會啟動,並且會在使用者完成後,繼續您的 Activity。 +

+ + + + +

啟動 Activity 以取得結果

+ +

有時候,您會想要收到由您啟動 Activity 的結果。如要接收結果,請透過呼叫 {@link android.app.Activity#startActivityForResult + startActivityForResult()} (而非 {@link android.app.Activity#startActivity + startActivity()}) 以啟動 Activity。 +如要接收後續 Activity 的結果,請實作 {@link android.app.Activity#onActivityResult onActivityResult()} 回呼方法。 + +後續 Activity 完成時,會將 {@link +android.content.Intent} 中的結果傳回到您的 {@link android.app.Activity#onActivityResult onActivityResult()} 方法。 +

+ +

例如,您可能會讓使用者在聯絡人中挑選一位,讓您的 Activity 可以針對該聯絡人的資訊進行一些處理。 +以下示範如何建立這類意圖,並處理結果: +

+ +
+private void pickContact() {
+    // Create an intent to "pick" a contact, as defined by the content provider URI
+    Intent intent = new Intent(Intent.ACTION_PICK, Contacts.CONTENT_URI);
+    startActivityForResult(intent, PICK_CONTACT_REQUEST);
+}
+
+@Override
+protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+    // If the request went well (OK) and the request was PICK_CONTACT_REQUEST
+    if (resultCode == Activity.RESULT_OK && requestCode == PICK_CONTACT_REQUEST) {
+        // Perform a query to the contact's content provider for the contact's name
+        Cursor cursor = getContentResolver().query(data.getData(),
+        new String[] {Contacts.DISPLAY_NAME}, null, null, null);
+        if (cursor.moveToFirst()) { // True if the cursor is not empty
+            int columnIndex = cursor.getColumnIndex(Contacts.DISPLAY_NAME);
+            String name = cursor.getString(columnIndex);
+            // Do something with the selected contact's name...
+        }
+    }
+}
+
+ +

此範例顯示您應該在您的 {@link +android.app.Activity#onActivityResult onActivityResult()} 方法中使用的基本邏輯,以便處理 Activity 結果。 +第一個條件會檢查要求是否成功 — 如果成功,則{@code resultCode} 會是 {@link android.app.Activity#RESULT_OK} —,以及此結果回應的要求是否為已知的要求 — 範例中的 {@code requestCode} 符合以 {@link android.app.Activity#startActivityForResult +startActivityForResult()} 傳送的第二個參數。 + + +程式碼在這裡透過查詢 {@link android.content.Intent} ({@code data} 參數) 中傳回的資料,來處理 Activity 結果。 +

+ +

其中的過程是,{@link +android.content.ContentResolver} 針對內容供應程式執行查詢,所傳回的 +{@link android.database.Cursor} 可以讀取查詢到的資料。如需詳細資訊,請參閱內容供應程式。 +

+ +

如需關於使用意圖的詳細資訊,請參閱意圖和意圖篩選器。 +

+ + +

關閉 Activity

+ +

呼叫 Activity 的 {@link android.app.Activity#finish +finish()} 方法,可以關閉此 Activity。您也可以呼叫 +{@link android.app.Activity#finishActivity finishActivity()},關閉您之前啟動的個別 Activity。

+ +

注意:大多數情況,您不應使用這些方法明確地結束 Activity。 +如同下一節所討論的 Activity 生命週期,Android 系統會為您管理 Activity 的生命週期,所以您不需要結束您自己的 Activity。 + +呼叫這些方法對使用者體驗有負面的影響,只有在您十分確定不希望使用者返回 Activity 的此執行個體時,才加以呼叫。 + +

+ + +

管理 Activity 生命週期

+ +

實作回呼方法來管理 Activity 的生命週期,對於開發強大且有彈性的應用程式來說,相當重要。 + +Activity 的生命週期受到相關其他 Activity、本身的工作以及返回堆疊的直接影響。 +

+ +

Activity 基本上有以下三種狀態:

+ +
+
已繼續
+
Activity 位於螢幕的前景,具有使用者焦點 (此狀態有時候也稱為「執行中」)。 +
+ +
已暫停
+
前景中有其他具備焦點的 Activity,但系統仍然可以看到這個 Activity。也就是說,這個 Activity 上方有另一個,該 Activity 為半透明,或是未覆蓋整個螢幕。 + +已暫停的 Activity 是完全有效的 ({@link android.app.Activity} 物件會保留在記憶體中、維護所有狀態和成員資訊,以及繼續附加至視窗管理員),但在系統記憶體極低的情況下會遭到終止。 + +
+ +
已停止
+
另一個 Activity 已完全遮蓋原本的 Activity (原本的 Activity 現在位於「背景」)。 +已停止的 Activity 仍然是有效的 ({@link android.app.Activity} 物件會保留在記憶體中、維護所有狀態和成員資訊,但「不會」附加至視窗管理員)。 + +只是使用者不會再看到已停止的 Activity,而且其他地方需要記憶體時,系統會將它終止。 +
+
+ +

如果 Activity 已暫停或已停止,系統可以透過要求它結束 (呼叫其 {@link android.app.Activity#finish finish()} 方法) 或直接終止其處理程序,將它從記憶體中刪除。 + +Activity 遭到結束或終止後,再次開啟時,必須全部重新建立。 +

+ + + +

實作生命週期回呼

+ +

Activity 在不同的狀態之間轉換時 (如上所述),會透過各種回呼方法進行通知。 +所有的回呼方法都是虛設,請加以覆寫,以便在 Activity 狀態變更時,執行適當的工作。 +下列主要 Activity 包括每個基礎生命週期方法: +

+ + +
+public class ExampleActivity extends Activity {
+    @Override
+    public void {@link android.app.Activity#onCreate onCreate}(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        // The activity is being created.
+    }
+    @Override
+    protected void {@link android.app.Activity#onStart onStart()} {
+        super.onStart();
+        // The activity is about to become visible.
+    }
+    @Override
+    protected void {@link android.app.Activity#onResume onResume()} {
+        super.onResume();
+        // The activity has become visible (it is now "resumed").
+    }
+    @Override
+    protected void {@link android.app.Activity#onPause onPause()} {
+        super.onPause();
+        // Another activity is taking focus (this activity is about to be "paused").
+    }
+    @Override
+    protected void {@link android.app.Activity#onStop onStop()} {
+        super.onStop();
+        // The activity is no longer visible (it is now "stopped")
+    }
+    @Override
+    protected void {@link android.app.Activity#onDestroy onDestroy()} {
+        super.onDestroy();
+        // The activity is about to be destroyed.
+    }
+}
+
+ +

注意:實作這些生命週期方法時,一定要先呼叫超級類別實作,才能執行任何工作,如同以上範例所示。 +

+ +

總而言之,這些方法定義了 Activity 的整個生命週期。透過實作這些方法,您可以監視 Activity 生命週期中的三個巢狀迴圈: +

+ +
    +
  • Activity 的整個生命週期是介於 {@link +android.app.Activity#onCreate onCreate()} 呼叫和 {@link +android.app.Activity#onDestroy} 呼叫之間。您的 Activity 應在 {@link android.app.Activity#onCreate onCreate()} 中設定「全域」狀態 (例如定義版面配置),並且在 {@link android.app.Activity#onDestroy} 中釋放所有剩餘的資源。 + +例如,如果您的 Activity 有一個執行緒在背景執行,並從網路下載資料,那麼最好可以在 {@link android.app.Activity#onCreate onCreate()} 中建立該執行緒,然後在 {@link +android.app.Activity#onDestroy} 中將它停止。 + +
  • + +
  • Activity 的可見生命週期是介於 {@link +android.app.Activity#onStart onStart()} 呼叫和 {@link +android.app.Activity#onStop onStop()} 呼叫之間。在此期間,使用者可以在螢幕上看到該 Activity,並與之互動。 +例如,啟動新的 Activity,而這個 Activity 不再看得到時,會呼叫 {@link android.app.Activity#onStop onStop()}。 +在這兩個方法之間,您可以維護需要讓使用者看到的資源。 +例如,您可以在{@link +android.app.Activity#onStart onStart()} 中註冊 +{@link android.content.BroadcastReceiver},以監視影響到 UI 的變更,然後當使用者不再看到您顯示的內容時,在 {@link android.app.Activity#onStop onStop()} 中將它取消註冊。 + +系統可以在 Activity 的整個生命週期時,多次呼叫 {@link android.app.Activity#onStart onStart()} 和 {@link +android.app.Activity#onStop onStop()},因為 Activity 對使用者而言會一直在顯示和隱藏之間切換。 +

  • + +
  • Activity 的前景生命週期是介於 {@link +android.app.Activity#onResume onResume()} 呼叫和 {@link android.app.Activity#onPause +onPause()} 呼叫之間。在此期間,Activity 會在螢幕上所有其他 Activity 的前面,而且具有使用者輸入焦點。 +Activity 可以經常在前景和背景之間轉換 — 例如,裝置進入睡眠或顯示對話方塊時,會呼叫 {@link android.app.Activity#onPause onPause()}。 + +由於此狀態可能會經常轉換,因此這兩個方法中的程式碼應十分精簡,這樣可以避免使用者在轉換時等待。 +

  • +
+ +

圖 1 說明 Activity 在轉換狀態時的可能迴圈和路徑。長方形代表您可以實作的回呼方法,以便 Activity 在轉換狀態時執行操作。 + +

+ + +

圖 1.Activity 生命週期。

+ +

表 1 列出相同的生命週期回呼方法,其中詳細描述每個回呼方法,並且指出每個回呼方法在 Activity 整個生命週期中的位置,包括系統是否可以在回呼方法完成後終止 Activity。 + + +

+ +

表 1.Activity 生命週期回呼方法摘要。 +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
方法 描述 完成後是否可終止? 下一個方法
{@link android.app.Activity#onCreate onCreate()}一開始建立 Activity 時呼叫。 + 您應該在這裡所有的一般靜態設定 — 建立檢視、將資料繫結至清單等等。 +「套件」物件會傳送給此方法,如果有擷取到狀態,此物件會內含 Activity 的上一個狀態 (請參閱下文的儲存 Activity 狀態)。 + + + +

後面一定會接著 {@code onStart()}。

{@code onStart()}
    {@link android.app.Activity#onRestart +onRestart()}Activity 已停止後,即將再次啟動之前呼叫。 + +

後面一定會接著 {@code onStart()}。

{@code onStart()}
{@link android.app.Activity#onStart onStart()}Activity 即將要讓使用者看到之前呼叫。 +

如果 Activity 移到前景,後面會接著 {@code onResume()},如果變成隱藏,後面會接著 {@code onStop()}。 +

{@code onResume()}

{@code onStop()}
    {@link android.app.Activity#onResume onResume()}Activity 即將與使用者開始互動之前呼叫。 +此時,Activity 位於 Activity 堆疊的最上方,接受使用的輸入。 + +

後面一定會接著 {@code onPause()}。

{@code onPause()}
{@link android.app.Activity#onPause onPause()}系統即將開始繼續另一個 Activity 時呼叫。 +認可對永久資料的未儲存變更、停止動畫,以及會使用 CPU 等等資源的其他操作時,一般會使用此方法。 + +不論此操作會執行什麼動作,都要快速完成,這是因為此方法傳回後,才會繼續下一個 Activity。 + +

如果 Activity 返回前景,後面會接著 {@code onResume()},如果變成使用者看不到它,後面會接著 {@code onStop()}。 + +

{@code onResume()}

{@code onStop()}
{@link android.app.Activity#onStop onStop()}使用者看不到 Activity 時呼叫。Activity 遭到終止或另一個 Activity (不論是現有 Activity 或新的 Activity) 已經繼續,而且將它覆蓋住,就會發生此情形。 + + +

如果 Activity 回來與使用者互動,後面會接著 {@code onRestart()},如果 Activity 離開,後面會接著 + {@code onDestroy()}。 +

{@code onRestart()}

{@code onDestroy()}
{@link android.app.Activity#onDestroy +onDestroy()}在 Activity 終止前呼叫。Activity 會接收到的最後呼叫。 +Activity 正在完成 (有人在 Activity 上呼叫 {@link android.app.Activity#finish + finish()}),或系統正在暫時終止 Activity 的這個執行個體以節省空間時,會呼叫此方法。 + +您可以使用 {@link + android.app.Activity#isFinishing isFinishing()} 方法分辨這兩種情況。 +
+ +

此欄標示為「完成後是否可終止?」表示系統是否可以「在方法傳回後」隨時終止代管 Activity 的處理程序,而不需要執行另一行 Activity 的程式碼。 + +有三個方法標示為「是」:({@link +android.app.Activity#onPause +onPause()}、{@link android.app.Activity#onStop onStop()} 以及 {@link android.app.Activity#onDestroy +onDestroy()})。由於 {@link android.app.Activity#onPause onPause()} 是這三個方法中的第一個方法,Activity 建立後,{@link android.app.Activity#onPause onPause()} 是一定會呼叫的最後一個方法,之後處理程序才能加以終止 — 如果系統必須緊急收回記憶體,可能就不會呼叫 {@link +android.app.Activity#onStop onStop()} 和 {@link android.app.Activity#onDestroy onDestroy()}。 + + + +因此,您應該使用 {@link android.app.Activity#onPause onPause()} 將極為重要的永久資料 (例如使用者的編輯內容) 寫入儲存空間。 +不過,您應選擇哪些資訊必須在 {@link android.app.Activity#onPause onPause()} 期間加以保留,這是因為此方法中的任何程序遭到封鎖,都無法轉換到下一個 Activity,而且會讓使用者體驗變慢。 + + +

+ +

在「是否可終止」欄標示為「否」的方法,從呼叫這些方法的那一刻起,會保護代管 Activity 的處理程序不會遭到終止。 +因此,Activity 從 {@link android.app.Activity#onPause onPause()} 傳回到呼叫 + {@link android.app.Activity#onResume onResume()} 的期間為可終止。 +再次呼叫和傳回 +{@link android.app.Activity#onPause onPause()} 之前,Activity 為不可終止。

+ +

注意:表 1 中定義為不是「可終止」的 Activity 仍可遭到系統終止 — 但只會發生在無技可施的情況下。 + +Activity 可加以終止的時機,於處理和執行緒文件中有更詳細的討論。 + +

+ + +

儲存 Activity 狀態

+ +

管理 Activity 生命週期的簡介中提到,當 Activity 暫停或停止時,會保留 Activity 的狀態。 + +這點是成立的,原因在於當 {@link android.app.Activity} 物件暫停或停止時,它仍然保留在記憶體中 — 關於它的成員和目前狀態的所有資訊,仍然為有效的。 + +因此,使用者在 Activity 內所做的任何變更,都會保留下來。所以,當 Activity 返回前景 (當它「繼續」時),那些變更仍然會在原地。 + +

+ +

不過,當系統終止 Activity 以收回記憶體時,{@link +android.app.Activity} 物件會遭到終止,所以系統就無法將它及其狀態完好無缺地繼續。 +如果使用者瀏覽回 {@link android.app.Activity} 物件,系統必須加以重新建立。 +但是,使用者不會注意到系統已終止該 Activity 並加以重新建立,可能因此期待 Activity 就跟之前的狀態一樣。 + +如果是這樣,您可以實作額外的回呼方法,以確認 Activity 狀態相關的重要資訊會保留下來。此回呼方法讓您儲存關於 Activity 狀態的資訊:{@link +android.app.Activity#onSaveInstanceState onSaveInstanceState()}。 + +

+ +

系統會在終止 Activity 之前呼叫 {@link android.app.Activity#onSaveInstanceState onSaveInstanceState()}。 +系統會將 {@link android.os.Bundle} 傳送給此方法,您可以使用 {@link +android.os.Bundle#putString putString()} 和 {@link +android.os.Bundle#putInt putInt()} 之類的方法,將 Activity 相關的狀態資訊以名稱-值組的方式儲存。 + +然後,如果系統終止應用程式處理程序,並且使用者瀏覽回您的 Activity,則系統會重新建立 Activity,然後將 {@link android.os.Bundle} 傳送給 {@link android.app.Activity#onCreate onCreate()} 和 {@link +android.app.Activity#onRestoreInstanceState onRestoreInstanceState()}。 + +您可以使用以上其中一種方法,從 {@link android.os.Bundle} 擷取已儲存的狀態,然後還原 Activity 狀態。 + +如果沒有狀態資訊可供還原,則傳送過來的 {@link +android.os.Bundle} 為空值 (null) (第一次建立 Activity 時,就是這種情況)。 +

+ + +

圖 2.Activity 返回使用者焦點,同時具備完整狀態的兩種方式:Activity 遭到終止,然後重新建立,Activity 必須還原之前儲存的狀態;或者Activity 已停止,然後繼續,Activity 狀態維持完整。 + + +

+ +

注意:不保證在您的 Activity 遭到終止之前,一定會呼叫 {@link +android.app.Activity#onSaveInstanceState onSaveInstanceState()},這是因為有時會發生不需要儲存狀態的情形 (例如,使用者使用「返回」按鈕離開您的 Activity 時,原因在於使用者明確地關閉 Activity)。 + + + +如果系統要呼叫 {@link android.app.Activity#onSaveInstanceState +onSaveInstanceState()},會在 {@link +android.app.Activity#onStop onStop()} 之前呼叫,可能會在 {@link android.app.Activity#onPause +onPause()} 之前呼叫。

+ +

不過,即使您沒有做任何事,沒有實作 {@link +android.app.Activity#onSaveInstanceState onSaveInstanceState()},有些 Activity 狀態會經由{@link android.app.Activity} 類別的預設實作 {@link +android.app.Activity#onSaveInstanceState onSaveInstanceState()} 而還原。 +具體來說,預設實作會針對版面配置中的每一個 {@link +android.view.View} 呼叫對應的 {@link +android.view.View#onSaveInstanceState onSaveInstanceState()} 方法,這樣可以讓每個檢視提供本身應該要儲存的相關資訊。 + +在 Android 架構中,幾乎每個小工具都適當地實作此方法,因此 UI 中可見的變更都會自動儲存,並於 Activity 重新建立時加以還原。 + +例如,{@link android.widget.EditText} 小工具會儲存使用者輸入的任何文字,而 {@link android.widget.CheckBox} 小工具則會儲存是否勾選。 + +您只要針對需要儲存狀態的每個小工具,提供唯一的 ID (使用 {@code android:id} 屬性) 即可。 +如果小工具沒有 ID,則系統無法儲存其狀態。 +

+ + + +

儘管 {@link +android.app.Activity#onSaveInstanceState onSaveInstanceState()} 的預設實作會儲存 Activity UI 相關的實用資訊,您仍然需要加以覆寫,以儲存額外的資訊,例如,您需要儲存 Activity 生命期間變更的成員值 (此真可能與 UI 中要還原的值有關,但保留那些 UI 值的成員預設不會加以還原)。 + + + +

+ +

由於 {@link +android.app.Activity#onSaveInstanceState onSaveInstanceState()} 的預設實作可協助儲存 UI 的狀態,因此,如果您覆寫此方法以儲存額外的狀態資訊,一定要再執行任何動作之前,呼叫 {@link android.app.Activity#onSaveInstanceState onSaveInstanceState()} 的超級類別實作。 + + +同樣的情況,您也要呼叫 {@link +android.app.Activity#onRestoreInstanceState onRestoreInstanceState()} 的超級類別實作 (如果您將它覆寫),讓預設實作可以還原檢視狀態。 +

+ +

注意:由於不保證一定會呼叫 {@link android.app.Activity#onSaveInstanceState +onSaveInstanceState()},您只能用它來記錄 Activity 的短暫狀態 (UI 的狀態) — 不應該用它儲存永久資料。 + +而是要在使用者離開 Activity 時,利用 {@link +android.app.Activity#onPause onPause()} 來儲存永內資料 (例如要儲存到資料庫的資料)。 +

+ +

測試應用程式是否能夠還原其狀態的好方式,只要旋轉裝置,改變螢幕方向即可。 +螢幕方向改變時,系統會終止 Activity 並重新建立,以便套用針對新的螢幕設定而提供使用的替代資源。 + +單單就這一點而言,您的 Activity 在重新建立時可以完整還原,就非常重要了,這是因為使用者操作應用程式時會經常旋轉螢幕。 + +

+ + +

處理設定變更

+ +

有些裝置設定可以在執行階段期間進行變更 (例如,螢幕方向、鍵盤可用性和語言)。 +發生這類變更時,Android 會重新建立執行中的 Activity (系統呼叫 {@link android.app.Activity#onDestroy},然後立即呼叫 {@link +android.app.Activity#onCreate onCreate()})。 +此行為的設計透過自動重新載入應用程式與提供的替代資源 (例如針對不同的螢幕方向和大小的不同版面配置),可協助應用程式適應新的設定。 + + +

+ +

如果您如上所述正確地設計 Activity,處理由於螢幕方向變更的重新啟動,然後還原 Activity 的狀態,您的應用程式對於 Activity 生命週期中的不可預期事件,會更具有抗性。 + +

+ +

處理這類重新啟動的最佳方式,是使用 {@link +android.app.Activity#onSaveInstanceState onSaveInstanceState()} 和 {@link +android.app.Activity#onRestoreInstanceState onRestoreInstanceState()} (或 {@link +android.app.Activity#onCreate onCreate()}) 儲存並還原 Activity 的狀態,如同上一節所討論。 +

+ +

如需關於執行階段發生的設定變更,以及如何加以處理的詳細資訊,請參閱處理執行階段變更指南。 + +

+ + + +

協調 Activity

+ +

Activity 啟動另一個 Activity 時,它們兩者都經歷生命週期轉換。第一個 Activity 暫停並停止 (雖然如果仍然可以在背景看到它,表示它並未真的「停止」),而另一個 Activity 建立起來。 + +若這些 Activity 共用儲存到磁碟或其他地方的資料,第一個 Activity 在第二個 Activity 已建立之前,不會完全停止,瞭解這件事很重要。然而,啟動第二個 Activity 的處理程序與停止第一個 Activity 的處理程序會重疊。 + + +

+ +

生命週期回呼的順序定義的很好,尤其是當兩個 Activity 位於相同的處理程序,而其中一個 Activity 啟動另一個 Activity 時。 +Activity A 啟動 Activity B 時所發生的操作順利如下: +

+ +
    +
  1. Activity A 的 {@link android.app.Activity#onPause onPause()} 方法會執行。
  2. + +
  3. Activity B 按順序執行 {@link android.app.Activity#onCreate onCreate()}、{@link +android.app.Activity#onStart onStart()} 以及 {@link android.app.Activity#onResume onResume()} 方法。 +(Activity B 現在擁有使用者焦點)。
  4. + +
  5. 然後,如果螢幕上已經看不到 Activity A,就會執行 Activity A 的 {@link +android.app.Activity#onStop onStop()} 方法。
  6. +
+ +

這一段可預測的生命週期回呼,可以讓您管理 Activity 之間資訊的轉換。 +例如,如果第一個 Activity 停止時,您必須寫入資料庫,讓接下來的 Activity 可以讀取,那麼您應該在 {@link android.app.Activity#onPause onPause()} 期間寫入,而不是在 {@link +android.app.Activity#onStop onStop()} 期間寫入。 + +

+ + diff --git a/docs/html-intl/intl/zh-tw/guide/components/bound-services.jd b/docs/html-intl/intl/zh-tw/guide/components/bound-services.jd new file mode 100644 index 0000000000000000000000000000000000000000..da47634b894fa7be7a544427a5eed99d3d54bb60 --- /dev/null +++ b/docs/html-intl/intl/zh-tw/guide/components/bound-services.jd @@ -0,0 +1,658 @@ +page.title=繫結服務 +parent.title=服務 +parent.link=services.html +@jd:body + + +
+
    +

    本文件內容

    +
      +
    1. 基本概念
    2. +
    3. 建立繫結服務 +
        +
      1. 延伸 Binder 類別
      2. +
      3. 使用 Messenger
      4. +
      +
    4. +
    5. 繫結至服務
    6. +
    7. 管理繫結服務的週期
    8. +
    + +

    重要類別

    +
      +
    1. {@link android.app.Service}
    2. +
    3. {@link android.content.ServiceConnection}
    4. +
    5. {@link android.os.IBinder}
    6. +
    + +

    範例

    +
      +
    1. {@code + RemoteService}
    2. +
    3. {@code + LocalService}
    4. +
    + +

    另請參閱

    +
      +
    1. 服務
    2. +
    +
+ + +

繫結服務是主從介面中的伺服器。繫結服務讓元件 (例如 Activity) 可以繫結至服務、傳送要求、接收回應,甚至執行處理程序間通訊 (IPC)。 + +繫結服務通常只會在服務另一個應用程式元件時才存在,而且不會在背景中一直執行。 +

+ +

本文會告訴您如何建立繫結服務,包括如何從其他應用程式元件繫結至服務。 +不過,如需關於服務的一般其他資訊,可參閱服務文件,例如如何從服務傳遞通知、設定服務在前景執行等等。 + +

+ + +

基本概念

+ +

繫結服務是 {@link android.app.Service} 類別的實作,允許其他應用程式繫結至此實作,並與之互動。 +若服務要要提供繫結功能,您必須實作 {@link android.app.Service#onBind onBind()} 回呼方法。 +此方法會傳回 {@link android.os.IBinder} 物件,其中定義程式設計介面。用戶端可以使用此介面與服務互動。 + +

+ + + +

用戶端可以透過呼叫 {@link android.content.Context#bindService +bindService()} 繫結至服務。這麼做時,用戶端必須實作 {@link +android.content.ServiceConnection} 以監視與服務之間的連線狀況。{@link +android.content.Context#bindService bindService()} 方法會立即傳回 (不含值),當 Android 系統在用戶端和服務之間建立連線時,會呼叫 {@link +android.content.ServiceConnection#onServiceConnected onServiceConnected()} (位於 {@link +android.content.ServiceConnection}) 以傳遞 {@link android.os.IBinder} (用戶端可以用來與服務溝通)。 + + +

+ +

多個用戶端可以同時連線至該服務。不過,系統只會在用戶端第一次繫結時,呼叫服務的 +{@link android.app.Service#onBind onBind()} 方法,以擷取 {@link android.os.IBinder}。 +然後,系統會將同一個 {@link android.os.IBinder} 傳遞給其他任何繫結的用戶端,不會再次呼叫 {@link android.app.Service#onBind onBind()}。 +

+ +

最後一個用戶端從服務解除繫結時,系統會終結服務 (除非服務也是由 {@link android.content.Context#startService startService()} 所啟動)。 +

+ +

實作繫結服務時,最重要的部分是定義您的 {@link android.app.Service#onBind onBind()} 回呼方法傳回的介面。 +您可以使用幾種不同的方式來定義服務的 {@link android.os.IBinder} 介面,以下小節將討論其中的每一種技術。 + +

+ + + +

建立已繫結的服務

+ +

建立提供繫結功能的服務時,您必須提供 {@link android.os.IBinder} 程式設計介面。用戶端可以使用此介面與服務互動。 +定義介面有三種方式: +

+ +
+
延伸 Binder 類別
+
如果您的服務只讓您自己的應用程式使用,而且跟用戶端執行在同一個處理程序 (此作法很常見),則應該要透過延伸 {@link android.os.Binder} 類別,並從 +{@link android.app.Service#onBind onBind()} 傳回此類別的執行個體來建立介面。 + +用戶端接收 {@link android.os.Binder} 後,可以用它直接存取 {@link android.os.Binder} 實作或 {@link android.app.Service} 提供的公用方法。 + + +

若您的服務只是您自己應用程式的背景工作者,建議使用此方式。 +除非您的服務是由其他應用程式或跨個別處理程序使用,才不用以此方式建立自己的介面。 +

+ +
使用 Messenger
+
如果您的介面需要跨不同處理程序運作,則可以建立內含 {@link android.os.Messenger} 服務的介面。 +此服務定義了回應不同類型 {@link +android.os.Message} 物件的 {@link android.os.Handler}。 +這個 {@link android.os.Handler} 是 {@link android.os.Messenger} 的基礎,之後可以與用戶端分享 {@link android.os.IBinder},讓用戶端使用 {@link +android.os.Message} 物件傳送命令給此服務。 + +此外,用戶端可以定義專屬的 {@link android.os.Messenger},服務就可以傳回訊息。 + +

這是處理程序間通訊 (IPC) 最簡單的執行方式,因為 {@link +android.os.Messenger} 會將所有要求都排列到單一個執行緒,因此,就不用將服務設計成執行緒安全的形式。 +

+
+ +
使用 AIDL
+
AIDL (Android 介面定義語言) 的工作是將物件分解為作業系統瞭解的始類型,然後將這些原始類型在各個處理程序間進行封送,以執行 IPC。先前使用 {@link android.os.Messenger} 的技術,實際上就是以 AIDL 作為底層結構。 + + +如上所述,{@link android.os.Messenger} 會在單一執行緒中建立所有用戶端要求的佇列,所以服務一次會接收一個要求。 +不過,如果您要讓服務可以同時處理多個要求,則可以直接使用 AIDL。 + +在此情況下,您的服務必須具備多執行緒的功能,而且是以執行緒安全的形式建置。 +

如要直接使用 AIDL,您必須建立 {@code .aidl} 檔案,並在其中定義程式設計介面。 +Android SDK 工具會使用此檔案產生一個抽象類別,以便實作介面並處理 IPC。您就可以在服務內加以延伸。 + +

+
+
+ +

注意:大部分應用程式不應使用 AIDL 建立繫結服務,若自行建立的話,就需要實作多執行緒功能,可能會導致更複雜的實作。 + +因此,AIDL 不適用於大部分應用程式,而且本文不會討論如何在您的服務中使用 AIDL。 +如果您確定需要直接使用 AIDL,請參閱 AIDL 文件。 + +

+ + + + +

延伸 Binder 類別

+ +

如果您的服務只會在本機應用程式使用,而且不需要跨處理程序運作,則可以實作您自己的 {@link android.os.Binder} 類別,讓用戶端直接存取服務中的公用方法。 + +

+ +

注意:用戶端和服務都位於相同應用程式和處理程序時才適用,這也是最常見的情況。 +例如,需要將 Activity 繫結到其專屬服務 (在背景播放音樂)的音樂應用程式,就很適合。 + +

+ +

設定的方式如下:

+
    +
  1. 在您的服務中建立 {@link android.os.Binder} 的執行個體,以具備以下其中一種功用: +
      +
    • 包含用戶端可以呼叫的公用方法
    • +
    • 傳回目前的 {@link android.app.Service} 執行個體,其中含有用戶端可以呼叫的公用方法 +
    • +
    • 傳回由服務所裝載另一個類別的執行個體,而此服務含有用戶端可以呼叫的公用方法 +
    • +
    +
  2. 從 {@link +android.app.Service#onBind onBind()} 回呼方法傳回此 {@link android.os.Binder} 的執行個體。
  3. +
  4. 在用戶端方面,從 {@link +android.content.ServiceConnection#onServiceConnected onServiceConnected()} 回呼方法接收 {@link android.os.Binder},然後使用提供的方法呼叫繫結服務。 +
  5. +
+ +

注意:服務和用戶端必須位於相同的應用程式,是因為用戶端才可以轉換傳回的物件,然後正確地呼叫其 API。 +服務和用戶端也必須位於相同的處理程序,因為此技術不會執行任何跨處理程序間旳封送。 + +

+ +

例如,以下的服務可以讓用戶端透過實作的 {@link android.os.Binder} 存取服務中的方法: +

+ +
+public class LocalService extends Service {
+    // Binder given to clients
+    private final IBinder mBinder = new LocalBinder();
+    // Random number generator
+    private final Random mGenerator = new Random();
+
+    /**
+     * Class used for the client Binder.  Because we know this service always
+     * runs in the same process as its clients, we don't need to deal with IPC.
+     */
+    public class LocalBinder extends Binder {
+        LocalService getService() {
+            // Return this instance of LocalService so clients can call public methods
+            return LocalService.this;
+        }
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return mBinder;
+    }
+
+    /** method for clients */
+    public int getRandomNumber() {
+      return mGenerator.nextInt(100);
+    }
+}
+
+ +

{@code LocalBinder} 提供 {@code getService()} 方法,讓用戶端擷取 {@code LocalService} 目前的執行個體。 +這樣可以讓用戶端呼叫服務中的公用方法。 +例如,用戶端可以從服務呼叫 {@code getRandomNumber()}。

+ +

當按一下按鈕時,會發生繫結至 {@code LocalService} 並呼叫 {@code getRandomNumber()}的 Activity: +

+ +
+public class BindingActivity extends Activity {
+    LocalService mService;
+    boolean mBound = false;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.main);
+    }
+
+    @Override
+    protected void onStart() {
+        super.onStart();
+        // Bind to LocalService
+        Intent intent = new Intent(this, LocalService.class);
+        bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
+    }
+
+    @Override
+    protected void onStop() {
+        super.onStop();
+        // Unbind from the service
+        if (mBound) {
+            unbindService(mConnection);
+            mBound = false;
+        }
+    }
+
+    /** Called when a button is clicked (the button in the layout file attaches to
+      * this method with the android:onClick attribute) */
+    public void onButtonClick(View v) {
+        if (mBound) {
+            // Call a method from the LocalService.
+            // However, if this call were something that might hang, then this request should
+            // occur in a separate thread to avoid slowing down the activity performance.
+            int num = mService.getRandomNumber();
+            Toast.makeText(this, "number: " + num, Toast.LENGTH_SHORT).show();
+        }
+    }
+
+    /** Defines callbacks for service binding, passed to bindService() */
+    private ServiceConnection mConnection = new ServiceConnection() {
+
+        @Override
+        public void onServiceConnected(ComponentName className,
+                IBinder service) {
+            // We've bound to LocalService, cast the IBinder and get LocalService instance
+            LocalBinder binder = (LocalBinder) service;
+            mService = binder.getService();
+            mBound = true;
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName arg0) {
+            mBound = false;
+        }
+    };
+}
+
+ +

上述範例顯示:用戶端如何使用 +{@link android.content.ServiceConnection} 的實作和 {@link +android.content.ServiceConnection#onServiceConnected onServiceConnected()} 回呼,繫結至服務。下一節提供關於繫結至服務處理程序的詳細資訊。 +

+ +

注意:上述範例未明確從服務解除繫結,但所有用戶端都應該在適當時間解除繫結 (例如,Activity 暫停時)。 +

+ +

如要取得更多範例程式碼,請參閱 ApiDemos 中的 {@code +LocalService.java} 類別和 {@code +LocalServiceActivities.java} 類別。

+ + + + + +

使用 Messenger

+ + + +

如果服務要和遠端處理程序溝通,則可以使用 +{@link android.os.Messenger} 為您的服務提供介面。此技術讓您不需要使用 AIDL,就可以執行處理程序間通訊 (IPC)。 +

+ +

以下是使用 {@link android.os.Messenger} 的摘要:

+ +
    +
  • 此服務實作 {@link android.os.Handler},可以從用戶端接收每個呼叫的回呼。 +
  • +
  • {@link android.os.Handler} 用於建立 {@link android.os.Messenger} 物件 (這是 {@link android.os.Handler} 的參照)。 +
  • +
  • {@link android.os.Messenger} 會建立 {@link android.os.IBinder},服務會從 {@link android.app.Service#onBind onBind()} 傳回給用戶端。 +
  • +
  • 用戶端使用 {@link android.os.IBinder} 將 {@link android.os.Messenger} (參照服務的 {@link android.os.Handler}) 具現化,用戶端就可以用來將 +{@link android.os.Message} 物件傳送給服務。 +
  • +
  • 服務會在其 {@link +android.os.Handler} 中接收每個 {@link android.os.Message} — 更明確地說,就是在 {@link android.os.Handler#handleMessage +handleMessage()} 方法中加以接收。
  • +
+ + +

這樣一來,用戶端就不需要呼叫服務的任何「方法」。用戶端只要傳遞「訊息」({@link android.os.Message} 物件),服務就會在其 {@link android.os.Handler} 中接收。 + +

+ +

以下是使用 {@link android.os.Messenger} 介面的簡單範例服務:

+ +
+public class MessengerService extends Service {
+    /** Command to the service to display a message */
+    static final int MSG_SAY_HELLO = 1;
+
+    /**
+     * Handler of incoming messages from clients.
+     */
+    class IncomingHandler extends Handler {
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_SAY_HELLO:
+                    Toast.makeText(getApplicationContext(), "hello!", Toast.LENGTH_SHORT).show();
+                    break;
+                default:
+                    super.handleMessage(msg);
+            }
+        }
+    }
+
+    /**
+     * Target we publish for clients to send messages to IncomingHandler.
+     */
+    final Messenger mMessenger = new Messenger(new IncomingHandler());
+
+    /**
+     * When binding to the service, we return an interface to our messenger
+     * for sending messages to the service.
+     */
+    @Override
+    public IBinder onBind(Intent intent) {
+        Toast.makeText(getApplicationContext(), "binding", Toast.LENGTH_SHORT).show();
+        return mMessenger.getBinder();
+    }
+}
+
+ +

請注意, +{@link android.os.Handler} 中的 {@link android.os.Handler#handleMessage handleMessage()} 方法是服務接收傳入 {@link android.os.Message} 的位置,也是根據 {@link android.os.Message#what} 成員,決定後續執行動作的位置。 +

+ +

用戶端只需要根據服務傳回的 {@link +android.os.IBinder},建立 {@link android.os.Messenger},然後使用 {@link +android.os.Messenger#send send()} 傳送訊息。例如,以下的簡單 Activity 會繫結至服務,然後將 {@code MSG_SAY_HELLO} 訊息傳遞給服務: +

+ +
+public class ActivityMessenger extends Activity {
+    /** Messenger for communicating with the service. */
+    Messenger mService = null;
+
+    /** Flag indicating whether we have called bind on the service. */
+    boolean mBound;
+
+    /**
+     * Class for interacting with the main interface of the service.
+     */
+    private ServiceConnection mConnection = new ServiceConnection() {
+        public void onServiceConnected(ComponentName className, IBinder service) {
+            // This is called when the connection with the service has been
+            // established, giving us the object we can use to
+            // interact with the service.  We are communicating with the
+            // service using a Messenger, so here we get a client-side
+            // representation of that from the raw IBinder object.
+            mService = new Messenger(service);
+            mBound = true;
+        }
+
+        public void onServiceDisconnected(ComponentName className) {
+            // This is called when the connection with the service has been
+            // unexpectedly disconnected -- that is, its process crashed.
+            mService = null;
+            mBound = false;
+        }
+    };
+
+    public void sayHello(View v) {
+        if (!mBound) return;
+        // Create and send a message to the service, using a supported 'what' value
+        Message msg = Message.obtain(null, MessengerService.MSG_SAY_HELLO, 0, 0);
+        try {
+            mService.send(msg);
+        } catch (RemoteException e) {
+            e.printStackTrace();
+        }
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.main);
+    }
+
+    @Override
+    protected void onStart() {
+        super.onStart();
+        // Bind to the service
+        bindService(new Intent(this, MessengerService.class), mConnection,
+            Context.BIND_AUTO_CREATE);
+    }
+
+    @Override
+    protected void onStop() {
+        super.onStop();
+        // Unbind from the service
+        if (mBound) {
+            unbindService(mConnection);
+            mBound = false;
+        }
+    }
+}
+
+ +

注意,此範例並未顯示服務回應用戶端的方式。如果您希望 +服務有所回應,則同時需要在用戶端建立 {@link android.os.Messenger}。之後,用戶端接收 {@link android.content.ServiceConnection#onServiceConnected +onServiceConnected()} 回呼時,會傳送 {@link android.os.Message} 給服務,其中包括用戶端的 {@link android.os.Messenger} (位於 {@link android.os.Messenger#send send()} 方法的 {@link android.os.Message#replyTo} 參數中)。 + + +

+ +

您可以在 {@code +MessengerService.java} (服務) 和 {@code +MessengerServiceActivities.java} (用戶端) 的範例中看到如何進行雙向傳訊的例子。

+ + + + + +

繫結至服務

+ +

應用程式元件 (用戶端) 可以透過呼叫 +{@link android.content.Context#bindService bindService()},繫結至服務。然後,Android 系統會呼叫服務的 {@link android.app.Service#onBind +onBind()} 方法 (此方法傳回 {@link android.os.IBinder} 以便與服務互動)。 +

+ +

繫結為非同步。{@link android.content.Context#bindService +bindService()} 會立即傳回,但「不會」將 {@link android.os.IBinder} 傳回給用戶端。 +如要接收 {@link android.os.IBinder},用戶端必須建立 {@link +android.content.ServiceConnection} 的執行個體,然後將此執行個體傳送給 {@link android.content.Context#bindService +bindService()}。{@link android.content.ServiceConnection} 內含回呼方法,系統會呼叫此方法以傳遞 {@link android.os.IBinder}。 +

+ +

注意:只有 Activity、服務以及內容提供者可以繫結至服務 — 您不能從廣播接收者繫結至服務。 +

+ +

因此,如要從用戶端繫結至服務,必須符合下列條件:

+
    +
  1. 實作 {@link android.content.ServiceConnection}。 +

    實作中必須覆寫兩個回呼方法:

    +
    +
    {@link android.content.ServiceConnection#onServiceConnected onServiceConnected()}
    +
    系統會呼叫此方法來傳遞 {@link android.os.IBinder} (由服務的 {@link android.app.Service#onBind onBind()} 方法所傳回)。 +
    +
    {@link android.content.ServiceConnection#onServiceDisconnected +onServiceDisconnected()}
    +
    與服務之間的連線突然遺失時 (例如服務當機或遭到終止時),Android 系統會呼收此方法。 +用戶端解除繫結時,「不會」呼叫此方法。 +
    +
    +
  2. +
  3. 呼叫會傳送 {@link +android.content.ServiceConnection} 實作的 {@link +android.content.Context#bindService bindService()}。
  4. +
  5. 系統呼叫 {@link android.content.ServiceConnection#onServiceConnected +onServiceConnected()} 回呼方法時,您就可以使用介面定義的方法,開始呼叫服務。 +
  6. +
  7. 如要與服務中斷連線,請呼叫 {@link +android.content.Context#unbindService unbindService()}。 +

    用戶端遭到終結時,會與服務解除繫結。不過,您應該要在與服務完成互動時,或者 Activity 暫停,要讓服務未使用時可以加以關閉的情況下,一定要解除繫結。 + +(以下將有繫結和解除繫結適當時機的詳細討論)。 +

    +
  8. +
+ +

例如,下列程式碼片段會透過延伸 Binder 類別,將用戶端連線到上述建立的服務,因此用戶端只要將傳回的 +{@link android.os.IBinder} 轉換為 {@code LocalService} 類別,然後要求 {@code +LocalService} 執行個體: +

+ +
+LocalService mService;
+private ServiceConnection mConnection = new ServiceConnection() {
+    // Called when the connection with the service is established
+    public void onServiceConnected(ComponentName className, IBinder service) {
+        // Because we have bound to an explicit
+        // service that is running in our own process, we can
+        // cast its IBinder to a concrete class and directly access it.
+        LocalBinder binder = (LocalBinder) service;
+        mService = binder.getService();
+        mBound = true;
+    }
+
+    // Called when the connection with the service disconnects unexpectedly
+    public void onServiceDisconnected(ComponentName className) {
+        Log.e(TAG, "onServiceDisconnected");
+        mBound = false;
+    }
+};
+
+ +

使用此 {@link android.content.ServiceConnection},用戶端可以透過將它傳送給 {@link android.content.Context#bindService bindService()} 而繫結至服務。 +例如:

+ +
+Intent intent = new Intent(this, LocalService.class);
+bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
+
+ +
    +
  • {@link android.content.Context#bindService bindService()} 的第一個參數是 +{@link android.content.Intent},明確地指定服務進行繫結 (儘管意圖是隱含的)。 +
  • +
  • 第二個參數是 {@link android.content.ServiceConnection} 物件。
  • +
  • 第三個參數是旗標,用來指出繫結的選項。如果服務尚未存在,通常是使用 {@link +android.content.Context#BIND_AUTO_CREATE} 以建立服務。其他可能的值為 {@link android.content.Context#BIND_DEBUG_UNBIND}和 {@link android.content.Context#BIND_NOT_FOREGROUND},或者 {@code 0} 代表無。 + +
  • +
+ + +

其他注意事項

+ +

以下是關於繫結至服務的一些重要注意事項:

+
    +
  • 您一定要設陷 {@link android.os.DeadObjectException} 例外狀況 (連線中斷時會擲回此例外狀況)。 +遠端方法只會擲回這個例外狀況。
  • +
  • 物件會跨處理程序計算參照。
  • +
  • 繫結和解除繫結通常應成對使用,以符合用戶端的開始和結束週期。 +例如: +
      +
    • 如果您只要在 Activity 可見時與服務互動,則要在 {@link android.app.Activity#onStart onStart()} 時繫結,並於 {@link +android.app.Activity#onStop onStop()} 時解除繫結。 +
    • +
    • 如果您希望 Activity 即使在背景中停止時,仍然會接收回應,則可以在 {@link android.app.Activity#onCreate onCreate()} 時繫結,並於{@link android.app.Activity#onDestroy onDestroy()} 時解除繫結。 + +請注意,這表示您的 Activity 在整個執行期間 (即使是在背景執行也一樣) 都需要使用服務,因此,如果服務位於另一個處理程序,您要增加該處理程序的權重。但系統很可能因而將它終止。 + + +
    • +
    +

    注意:Activity 的 {@link android.app.Activity#onResume onResume()} 和 {@link +android.app.Activity#onPause onPause()} 期間,通常不要繫結和解除繫結,因為這些回呼會在每個週期轉換時發生,而且您應該要讓這些轉換期間所發生的處理動作保持在最少狀態。 + +另外,如果您的應用程式中有多個 Activity 繫結至同一個服務,而這兩個 Activity 之間會進行轉換,則服務會在目前的 Activity 解除繫結(暫停) 時,而下一個 Activity 繫結之前 (繼續時),先終結後再重新建立 + + +(此 Activity 轉換如何在 Activity 之間協調其週期的資訊,於 Activity 文件中說明)。 + +

    +
+ +

如要取得如何繫結至服務的更多範例程式碼,請參閱 ApiDemos 中的 {@code +RemoteService.java} 類別。

+ + + + + +

管理繫結服務的週期

+ +

服務與所有用戶端解除繫結時,Android 系統會將服務終結 (除非服務是和 {@link android.app.Service#onStartCommand onStartCommand()} 一起啟動的)。 +如果您的服務純粹是繫結服務,就不用管理它的週期— Android 系統會根據服務是否繫結至任何用戶端,為您管理服務。 + +

+ +

不過,如果您選擇實作 {@link android.app.Service#onStartCommand +onStartCommand()} 回呼方法,則必須明確停止服務,因為服務現在會視為「已啟動」。 +如果是此情形,除非服務本身使用 {@link android.app.Service#stopSelf()} 自行停止,或另一個元件呼叫 {@link +android.content.Context#stopService stopService()} 加以停止,否則服務會持續執行,不論它是否繫結至任何用戶端。 + +

+ +

此外,如果您的服務已啟動並且接受繫結,當系統呼叫您的 {@link android.app.Service#onUnbind onUnbind()} 方法時,可以選擇傳回 +{@code true} (如果您希望用戶端下次繫結至服務時,可以接收 {@link android.app.Service#onRebind +onRebind()} 呼叫,而不是接收 {@link +android.app.Service#onBind onBind()} 的呼叫)。{@link android.app.Service#onRebind +onRebind()} 會傳回空值,但用戶端仍然會在其 +{@link android.content.ServiceConnection#onServiceConnected onServiceConnected()} 回呼中接收到 {@link android.os.IBinder}。以下「圖 1」說明這類週期的邏輯。 + +

+ + + +

圖 1.服務的週期開始後,就允許繫結行為。 +

+ + +

如需關於已啟動服務週期的詳細資訊,請參閱服務文件。

+ + + + diff --git a/docs/html-intl/intl/zh-tw/guide/components/fragments.jd b/docs/html-intl/intl/zh-tw/guide/components/fragments.jd new file mode 100644 index 0000000000000000000000000000000000000000..e54769b0c88b39e1d6159cf7eac77ce7d32415c7 --- /dev/null +++ b/docs/html-intl/intl/zh-tw/guide/components/fragments.jd @@ -0,0 +1,812 @@ +page.title=片段 +parent.title=Activity +parent.link=activities.html +@jd:body + +
+
+

本文件內容

+
    +
  1. 設計概念
  2. +
  3. 建立片段 +
      +
    1. 新增使用者介面
    2. +
    3. 將片段新增到 Activity 中
    4. +
    +
  4. +
  5. 管理片段
  6. +
  7. 進行片段交易
  8. +
  9. 與 Activity 通訊 +
      +
    1. 為 Activity 建立事件回呼
    2. +
    3. 將項目新增到動作列中
    4. +
    +
  10. +
  11. 處理片段生命週期 +
      +
    1. 調整 Activity 生命週期
    2. +
    +
  12. +
  13. 範例說明
  14. +
+ +

重要類別

+
    +
  1. {@link android.app.Fragment}
  2. +
  3. {@link android.app.FragmentManager}
  4. +
  5. {@link android.app.FragmentTransaction}
  6. +
+ +

另請參閱

+
    +
  1. 使用片段建置動態 UI
  2. +
  3. 支援平板電腦和手機 +
  4. +
+
+
+ +

{@link android.app.Fragment} 代表一種行為或 +{@link android.app.Activity} 中的一部分使用者介面。您可以合併單一 Activity 中的多個片段,藉此建置 +多窗格 UI 以及在多個 Activity 中重複使用片段。您可以將片段想成是 Activity 的模組化區段,片段擁有自己的生命週期、接收自己的輸入事件,而且您可以在 Activity 執行時新增或移除片段 (有點像是您可以在不同 Activity 中重複使用的「子 Activity」)。 + + +

+ +

片段必須一律嵌入 Activity 中,而主要 Activity 的生命週期會直接影響片段的生命週期。 +例如,當 Activity 暫停時,其中的所有片段也會一併暫停;而當 Activity 遭到刪除時,所有片段也會一併刪除。 +不過,當 Activity 執行時 (該 Activity 會處於繼續進行生命週期狀態),您可以個別操縱所有片段,例如新增或移除片段。 + +當您進行片段交易這類操作時,您也可以將片段加到 Activity 所管理的返回堆疊中 — Activity 中的所有返回堆疊項目均為所發生片段交易的記錄。 + + +返回堆疊可讓使用者復原片段交易 (往回瀏覽),只要按下 [返回] 按鈕即可。 +

+ +

當您將片段新增為 Activity 版面配置的一部分後,片段就會位於 Activity 檢視階層中的 {@link +android.view.ViewGroup},而且片段會自行定義專屬的版面配置。您可以宣告 Activity 版面配置檔案中的片段,或是在應用程式的程式碼中將片段加到現有的 {@link android.view.ViewGroup} 中,藉此在 Activity 版面配置中將片段插入為 {@code <fragment>} 元素。 + + + +不過,片段未必要成為 Activity 版面配置的一部分;您也可以選擇不透過其 UI,以隱形工作人員的身分使用 Activity 的片段。 + +

+ +

本文說明如何建置應用程式以使用片段,包括片段如何在加到 Activity 返回堆疊時保持自身狀態、如何與 Activity 和 Activity 中的其他片段共用活動、如何製作 Activity 欄等等。 + + +

+ + +

設計概念

+ +

我們在 Android 3.0 (API 級別 11) 中導入了片段,主要目的是為了在大型螢幕 (例如平板電腦) 上支援更多動態和彈性 UI 設計。 +由於平板電腦的螢幕比手機大上許多,因此有更多空間可結合及交換 UI 元件。 + +片段可實現這種介面設計,而不必讓您管理複雜的檢視階層變更。 +將 Activity 的版面配置劃分成片段後,您就可以修改 Activity 在執行階段的外觀,以及保留 Activity 所管理返回堆疊的相關變更。 + +

+ +

例如,某個新聞應用程式可使用單一片段在畫面左側顯示文章清單,並且使用另一個片段在畫面右側顯示某篇文章 — 這兩個片段是以並排方式出現在某個 Activity 中,而每個片段都有自己的一組生命週期回呼方法,可自行處理其使用者輸入事件。 + + +因此,使用者可以在相同 Activity 中選取並閱讀某篇文章 (如圖 1 中的平板電腦版面配置所示),而不必使用不同 Activity 選取及閱讀文章。 + +

+ +

請務必將每個片段設計成模組化和可重複使用的 Activity 元件。這是因為每個片段會根據其生命週期回呼,定義專屬版面配置和行為,而您可將單一片段加到多個 Activity 中,故請將其設計成可重複使用的元件,同時避免直接操縱個別片段。 + + +由於模組化片段可讓您針對不同螢幕大小變更片段組合,因此請務必這麼做。 +設計您的應用程式以支援平板電腦和手機時,您可以在不同版面配置設定中重複使用片段,藉此根據可用的螢幕空間提供最佳的使用者體驗。 + +以手機為例說明,如果相同 Activity 中有多個片段不相符,則只要分割片段即可提供單一面板式的 UI。 + +

+ + +

圖 1.片段所定義的兩個 UI 模組如何針對平板電腦設計合併成單一 Activity、如何針對手機設計分割成個別 Activity。 + +

+ +

例如 — 延續新聞應用程式範例加以說明 — 在平板電腦大小的裝置上執行的應用程式可將兩個片段嵌入「Activity A」。 +不過,在手機大小的螢幕上,由於螢幕空間不足以容納兩個片段,因此「Activity A」只會包含文章清單的片段,而當使用者選取文章後,系統就會啟動內含第二個片段的「Activity B」,讓使用者閱讀文章。 + + +因此,應用程式可透過重複使用不同片段組合的方式,同時支援平板電腦和手機 (如圖 1 所示)。 + +

+ +

如要進一步瞭解如何使用不同片段組合針對各種螢幕設定設計應用程式,請參閱支援平板電腦和手機指南。 +

+ + + +

建立片段

+ +
+ +

圖 2.片段的生命週期 (當其中的 Activity 處於執行狀態時)。 +

+
+ +

如要建立片段,您必須建立 {@link android.app.Fragment} 的子類別 (或是其現有的子類別)。 +{@link android.app.Fragment} 類別內含與{@link android.app.Activity} 十分雷同的程式碼。 +該程式碼包括與 Activity 類似的回呼方法,例如 {@link android.app.Fragment#onCreate onCreate()}、{@link android.app.Fragment#onStart onStart()}、 +{@link android.app.Fragment#onPause onPause()} 和 {@link android.app.Fragment#onStop onStop()}。 +事實上,如果您是設定現有 Android 應用程式改用片段,只要將 Activity 的回呼方法中的程式碼移到片段的個別回呼方法即可。 + + +

+ +

一般來說,您至少必須實作下列生命週期方法:

+ +
+
{@link android.app.Fragment#onCreate onCreate()}
+
系統會在建立片段時呼叫這個方法。在實作這個方法時,您必須初始化您想保留的必要片段元件,以便恢復已暫停或停止的片段。 + +
+
{@link android.app.Fragment#onCreateView onCreateView()}
+
系統會在片段初次顯示其使用者介面時呼叫這個方法。 +您必須透過這個方法傳回 {@link android.view.View} (片段版面配置的根目錄),才能顯示片段的 UI。 +如果片段並未提供 UI 的話,則可以傳回空值。 +
+
{@link android.app.Activity#onPause onPause()}
+
系統會在使用者初次離開片段時呼叫這個方法 (即使使用者這麼做未必會刪除片段)。 +您通常需要透過這個方法提交要在目前的使用者工作階段以外保留的任何變更 (原因在於使用者可能不會返回)。 + +
+
+ +

大多數應用程式都至少必須針對每個片段實作這三個方法,不過您也必須使用幾個其他回呼方法來控制片段生命週期的各種狀態。 + +如要進一步瞭解所有回呼方法,請參閱處理片段生命週期。 +

+ + +

以下列出幾個您可能會想擴充的子類別 (基礎 {@link +android.app.Fragment} 類別除外):

+ +
+
{@link android.app.DialogFragment}
+
顯示浮動對話方塊。使用這個類別建立對話方塊是使用 {@link android.app.Activity} 類別中的對話方塊協助程式方法的推薦替代方法,這是因為使用此類別可將片段對話方塊納入 Activity 所管理的片段堆疊,讓使用者得已返回已關閉的片段。 + + +
+ +
{@link android.app.ListFragment}
+
顯示配接器 (例如 {@link +android.widget.SimpleCursorAdapter}) 所管理的項目清單;與 {@link android.app.ListActivity} 方法相似。這個方法可提供數種管理清單檢視畫面的方法,例如可處理點擊事件的 {@link +android.app.ListFragment#onListItemClick(ListView,View,int,long) onListItemClick()}回呼。 + +
+ +
{@link android.preference.PreferenceFragment}
+
列出 {@link android.preference.Preference} 物件的階層;與 +{@link android.preference.PreferenceActivity} 方法相似。為應用程式建立「設定」Activity 時,這個方法就非常實用。 +
+
+ + +

新增使用者介面

+ +

片段通常是當作某 Activity 的使用者介面使用,而且可將自身的版面配置提供給 Activity。 +

+ +

如要提供片段的版面配置,您必須實作 {@link +android.app.Fragment#onCreateView onCreateView()} 回呼方法,讓 Android 系統呼叫片段顯示其版面配置。 +實作這個方法時,您必須傳回 +{@link android.view.View} (片段版面配置的根目錄)。

+ +

注意:如果您的片段是 {@link +android.app.ListFragment} 的子類別,則實作完畢後系統預設會傳回 {@link android.app.Fragment#onCreateView onCreateView()} 的 +{@link android.widget.ListView},因此您不必加以實作。

+ +

如要從 {@link +android.app.Fragment#onCreateView onCreateView()} 傳回版面配置,您可以從 XML 中定義的l版面配置資源擴大它。為協助您完成這項作業,{@link android.app.Fragment#onCreateView onCreateView()} 提供了 +{@link android.view.LayoutInflater} 物件。 +

+ +

例如,以下是 {@link android.app.Fragment} 的子類別,可從 +{@code example_fragment.xml} 檔案載入版面配置:

+ +
+public static class ExampleFragment extends Fragment {
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+                             Bundle savedInstanceState) {
+        // Inflate the layout for this fragment
+        return inflater.inflate(R.layout.example_fragment, container, false);
+    }
+}
+
+ + + +

傳入 {@link android.app.Fragment#onCreateView +onCreateView()} 的 {@code container} 參數是上層 {@link android.view.ViewGroup} (來自 Activity 的版面配置),系統會將您的片段版面配置插入其中。 + +{@code savedInstanceState} 參數是 {@link android.os.Bundle},當片段即將恢復時 (如要進一步瞭解還原狀態,請參閱處理片段生命週期),這個參數就會提供先前的片段執行個體的相關資料。 + + +

+ +

{@link android.view.LayoutInflater#inflate(int,ViewGroup,boolean) inflate()} 方法採用三種引數: +

+
    +
  • 您想要擴大的版面配置的資源 ID。
  • +
  • 要設為擴大過後版面配置的上層檢視的 {@link android.view.ViewGroup}。請務必傳遞 {@code +container},以便讓系統將版面配置參數套用至擴大過後版面配置的根檢視 (由將做為其目標的父檢視所指定)。 +
  • +
  • 用於指示系統是否要在擴大期間將擴大過後的版面配置附加到 {@link +android.view.ViewGroup} (第二個參數) 的布林值 (由於系統已將擴大過後的版面配置插入 {@code +container},因此布林值應為 false — 如果您傳送 true,會導致系統在最終版面配置中建立多餘的檢視群組)。 +
  • +
+ +

您現在已瞭解如何建立可提供版面配置的片段了。接著,請將建立好的片段新增至 Activity。 +

+ + + +

將片段新增到 Activity 中

+ +

片段通常會將一部分 UI 嵌入主要 Activity 的整體檢視階層中。 +您有兩種方式可將片段新增到 Activity 版面配置: +

+ +
    +
  • 宣告 Activity 版面配置檔案內含的片段。 +

    選用這種方式時,您可以將片段視為檢視,為其指定版面配置屬性。 +例如,以下是內含兩個片段的 Activity 版面配置檔案: +

    +
    +<?xml version="1.0" encoding="utf-8"?>
    +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    +    android:orientation="horizontal"
    +    android:layout_width="match_parent"
    +    android:layout_height="match_parent">
    +    <fragment android:name="com.example.news.ArticleListFragment"
    +            android:id="@+id/list"
    +            android:layout_weight="1"
    +            android:layout_width="0dp"
    +            android:layout_height="match_parent" />
    +    <fragment android:name="com.example.news.ArticleReaderFragment"
    +            android:id="@+id/viewer"
    +            android:layout_weight="2"
    +            android:layout_width="0dp"
    +            android:layout_height="match_parent" />
    +</LinearLayout>
    +
    +

    {@code <fragment>} 中的 {@code android:name} 屬性可指定系統呼叫版面配置中的 {@link +android.app.Fragment} 類別。

    + +

    系統建立這個 Activity 版面配置後,就會呼叫版面配置中指定的任何片段,並為每個片段呼叫 {@link android.app.Fragment#onCreateView onCreateView()} 方法,藉此擷取所有片段的版面配置。 + +系統會插入片段所傳回的 {@link android.view.View} 來取代 {@code <fragment>} 元素。 +

    + +
    +

    注意:您必須為每個片段提供專屬識別碼,以便系統在 Activity 重新開始時復原片段 (您也可以使用此識別碼擷取要交易的片段,例如移除片段)。 + +您有三種方式可提供片段的 ID: +

    +
      +
    • 提供內含專屬 ID 的 {@code android:id} 屬性。
    • +
    • 提供內含不重複字串的 {@code android:tag} 屬性。
    • +
    • 如果您未提供上述兩項屬性,系統會採用容器檢視的 ID。 +
    • +
    +
    +
  • + +
  • 或者,利用程式將片段新增至現有的 {@link android.view.ViewGroup}。 +

    只要 Activity 處於執行狀態,您都可以將片段新增至 Activity 版面配置。方法很簡單,只要指定您想在其中加入片段的 {@link +android.view.ViewGroup} 即可。 +

    +

    如要在 Activity 中進行片段交易 (例如新增、移除或替換片段),請使用 {@link android.app.FragmentTransaction} 中的 API 進行。 +您可以透過以下方式取得 {@link android.app.Activity} 的 {@link android.app.FragmentTransaction} 執行個體: +

    + +
    +FragmentManager fragmentManager = {@link android.app.Activity#getFragmentManager()}
    +FragmentTransaction fragmentTransaction = fragmentManager.{@link android.app.FragmentManager#beginTransaction()};
    +
    + +

    接著,您就可以使用 {@link +android.app.FragmentTransaction#add(int,Fragment) add()} 方法指定要新增的片段,以及要插入片段的目標檢視。 +例如:

    + +
    +ExampleFragment fragment = new ExampleFragment();
    +fragmentTransaction.add(R.id.fragment_container, fragment);
    +fragmentTransaction.commit();
    +
    + +

    第一個傳入 {@link android.app.FragmentTransaction#add(int,Fragment) add()} 的引數是要在其中插入片段的 {@link android.view.ViewGroup} (使用資源 ID 加以指定),而第二個參數則是要新增的引數。 + +

    +

    透過 +{@link android.app.FragmentTransaction} 完成變更後,請呼叫 {@link android.app.FragmentTransaction#commit} 以便讓變更生效。 +

    +
  • +
+ + +

新增不顯示 UI 的片段

+ +

上述範例說明如何將片段新增至 Activity,以提供 UI。但事實上,您也可以使用片段為 Activity 提供背景行為,避免顯示額外的 UI。 + +

+ +

如要新增不顯示使 UI 的片段,請使用 {@link +android.app.FragmentTransaction#add(Fragment,String)} 從 Activity 新增片段 (請提供片段的不重複字串「標記」,而不是檢視 ID)。 +這樣即可新增片段,但由於該片段並未與 Activity 版面配置中的檢視相關聯,因此不會接收 {@link +android.app.Fragment#onCreateView onCreateView()} 的呼叫。 +如此一來,您就不必實作該方法。

+ +

提供片段的字串標記並不是採用非 UI 片段時的必要步驟 — 您也可以提供沒有 UI 的片段的字串標記 — 不過,如果片段沒有 UI,則字串標記將成為識別片段的唯一途徑。 + +如果您想之後再從 Activity 中取得片段,請使用 {@link android.app.FragmentManager#findFragmentByTag +findFragmentByTag()}。 +

+ +

如需使用沒有 UI 的片段做為背景工作者的 Activity 範例,請參閱 SDK 範例中位於以下路徑的 {@code +FragmentRetainInstance.java} 範例 (可透過 Android SDK Manager 存取)<sdk_root>/APIDemos/app/src/main/java/com/example/android/apis/app/FragmentRetainInstance.java。 + +

+ + + +

管理片段

+ +

如要管理 Activity 中的片段,請使用 {@link android.app.FragmentManager}。如要取得這些片段,請呼叫 Activity 中的 {@link android.app.Activity#getFragmentManager()}。 +

+ +

您可透過 {@link android.app.FragmentManager} 執行下列操作:

+ +
    +
  • 使用 {@link +android.app.FragmentManager#findFragmentById findFragmentById()} (針對在 Activity 版面配置中提供 UI 的片段) 或 {@link android.app.FragmentManager#findFragmentByTag +findFragmentByTag()} (針對未提供 UI 的片段) 取得 Activity 中的現有片段。 +
  • +
  • 使用 {@link +android.app.FragmentManager#popBackStack()} (模擬使用者的「返回」命令) 將片段從返回堆疊中推出。
  • +
  • 使用 {@link +android.app.FragmentManager#addOnBackStackChangedListener addOnBackStackChangedListener()} 針對返回堆疊的變更項目註冊監聽器。
  • +
+ +

如要進一步瞭解上述方法以及其他方法,請參閱 {@link +android.app.FragmentManager} 類別說明文件。

+ +

如上一節所述,您也可以使用 {@link android.app.FragmentManager} 開啟 {@link android.app.FragmentTransaction},以便進行片段交易 (例如新增及移除片段)。 + +

+ + +

進行片段交易

+ +

使用 Activity 中片段的一項實用功能,就是新增、移除、替換片段以及對它們執行其他動作,藉此反映使用者互動。 +您針對 Activity 提交的每組變更稱為交易,而您可以使用 {@link +android.app.FragmentTransaction} 中的進行這種交易。 +此外,您也可以儲存對 Activity 所管理的返回堆疊進行的交易,讓使用者能夠往回瀏覽片段變更 (如同往回瀏覽 Activity)。 + +

+ +

您可以從 {@link +android.app.FragmentManager} 中取得如下所示的 {@link android.app.FragmentTransaction}:

+ +
+FragmentManager fragmentManager = {@link android.app.Activity#getFragmentManager()};
+FragmentTransaction fragmentTransaction = fragmentManager.{@link android.app.FragmentManager#beginTransaction()};
+
+ +

每次交易都是您想同時進行的一組變更。您可以使用 {@link +android.app.FragmentTransaction#add add()}、{@link android.app.FragmentTransaction#remove remove()}和 {@link android.app.FragmentTransaction#replace replace()} 等方法設定您想針對特定交易進行的變更。 + +接著,只要呼叫 {@link android.app.FragmentTransaction#commit()},就能將該交易套用至 Activity。 +

+ + +

不過,您可能會為了新增交易至片段交易返回堆疊,先呼叫 {@link +android.app.FragmentTransaction#addToBackStack addToBackStack()},然後再呼叫 {@link +android.app.FragmentTransaction#commit()}。 +返回堆疊是由 Activity 所管理,可讓使用者透過按下 [返回] 按鈕的方式,返回先前的片段狀態。 +

+ +

以下範例可讓您替換片段,並且保留先前的返回堆疊狀態: +

+ +
+// Create new fragment and transaction
+Fragment newFragment = new ExampleFragment();
+FragmentTransaction transaction = getFragmentManager().beginTransaction();
+
+// Replace whatever is in the fragment_container view with this fragment,
+// and add the transaction to the back stack
+transaction.replace(R.id.fragment_container, newFragment);
+transaction.addToBackStack(null);
+
+// Commit the transaction
+transaction.commit();
+
+ +

在這個範例中,{@code newFragment} 會針對依據 {@code R.id.fragment_container} ID 識別的版面配置容器, +替換其中的任何現有片段 (如果有的話)。系統會呼叫 {@link +android.app.FragmentTransaction#addToBackStack addToBackStack()},將替換交易儲存到返回堆疊,以便使用者按下 [返回] 按鈕來復原交易以及返回上一個片段。 + +

+ +

如果您將多項變更新增至交易 (例如新增另一個 {@link +android.app.FragmentTransaction#add add()} 或 {@link android.app.FragmentTransaction#remove +remove()}),並且呼叫 {@link +android.app.FragmentTransaction#addToBackStack addToBackStack()},那麼您在呼叫 {@link android.app.FragmentTransaction#commit commit()} 之前套用的所有變更都會新增至返回堆疊做為單次交易,在這種情況下,按下 [返回] 按鈕就能一次復原所有變更。 + +

+ +

您將變更新增至 {@link android.app.FragmentTransaction} 的順序並不會造成任何影響,但請注意以下例外狀況: +

+
    +
  • 務必最後才呼叫 {@link android.app.FragmentTransaction#commit()}
  • +
  • 如果您是將多個片段新增至同一容器,那麼您新增片段的順序將決定這些片段在檢視階層中的顯示順序 +
  • +
+ +

如果您並未在進行移除片段的交易時呼叫 {@link android.app.FragmentTransaction#addToBackStack(String) +addToBackStack()},該片段會在您提交交易後遭到刪除,而且使用者無法往回瀏覽至該片段。 +不過,如果您在移除片段時呼叫 {@link android.app.FragmentTransaction#addToBackStack(String) addToBackStack()},則該片段將會遭到「停止」,而且會在使用者往回瀏覽時繼續進行。 + + +

+ +

提示:您可以在進行每次片段交易時套用交易動畫,方法是在提交交易前呼叫 {@link android.app.FragmentTransaction#setTransition setTransition()}。 + +

+ +

呼叫 {@link android.app.FragmentTransaction#commit()} 並不會立即進行交易, +而是會讓系統排定 UI 執行緒 (「主要」執行緒) 可執行這個方法時,立即加以執行。 +不過,您可以視需要透過 UI 執行緒呼叫 {@link +android.app.FragmentManager#executePendingTransactions()},立即執行 {@link android.app.FragmentTransaction#commit()} 所提交的交易。 +您通常不必這樣做,除非該交易是其他執行緒的工作的必要元件。 +

+ +

注意:您可以使用 {@link +android.app.FragmentTransaction#commit commit()} 來提交交易,但僅限於 Activity 儲存其狀態之前 (也就是使用者離開 Activity 之前)。 +如果您在這個時間點之後嘗試提交交易,就會發生例外狀況, +這是因為狀態會在交易提交後遺失 (如果需要復原 Activity 的話)。 +如果想確保狀態遺失不會造成任何影響,請使用 {@link +android.app.FragmentTransaction#commitAllowingStateLoss()} 提交交易。

+ + + + +

與 Activity 通訊

+ +

雖然 {@link android.app.Fragment} 是實作成不同於 +{@link android.app.Activity} 的物件,而且可在多個 Activity 中使用,特定片段執行個體仍會直接與含有該物件的 Activity 建立關聯。 +

+ +

因此,片段可存取內含{@link +android.app.Fragment#getActivity()} 的 {@link android.app.Activity} 執行個體,以及輕鬆進行在 Activity 版面配置中尋找檢視等工作: +

+ +
+View listView = {@link android.app.Fragment#getActivity()}.{@link android.app.Activity#findViewById findViewById}(R.id.list);
+
+ +

相同地,您的 Activity 可利用 {@link +android.app.FragmentManager#findFragmentById findFragmentById()} 或 {@link +android.app.FragmentManager#findFragmentByTag findFragmentByTag()} 從 {@link android.app.FragmentManager} 中取得 {@link android.app.Fragment} 參照資料,以呼叫片段中的方法。 +例如:

+ +
+ExampleFragment fragment = (ExampleFragment) getFragmentManager().findFragmentById(R.id.example_fragment);
+
+ + +

為 Activity 建立事件回呼

+ +

在某些情況下,您可能需要可用來與 Activity 分享事件的片段。如果您需要這種片段,建議您在片段內定義回呼介面,然後要求主要 Activity 實作該片段。 + +當 Activity 透過介面接收回呼後,即可視需要與版面配置中的其他片段分享這項資訊。 +

+ +

例如,假設新聞應用程式的 Activity 中有兩個片段 — 一個用於顯示文章清單 (片段 A),另一個用於顯示文章 (片段 B) — 其中的片段 A 必須告知 Activity 使用者選取清單項目的時間點,以便通知片段 B 顯示文章。 + +在這個範例中,{@code OnArticleSelectedListener} 介面是在片段 A 中完成宣告: +

+ +
+public static class FragmentA extends ListFragment {
+    ...
+    // Container Activity must implement this interface
+    public interface OnArticleSelectedListener {
+        public void onArticleSelected(Uri articleUri);
+    }
+    ...
+}
+
+ +

接著,代管片段的 Activity 會實作 {@code OnArticleSelectedListener} 介面,並且覆寫 {@code onArticleSelected()} 以便將片段 A 的事件告知片段 B。為了確保主要 Activity 可實作該介面,片段 A 的 {@link +android.app.Fragment#onAttach onAttach()} 回呼方法 (系統將片段新增至 Activity 時會呼叫這種方法) 轉換傳入 {@link android.app.Fragment#onAttach +onAttach()} 的 {@link android.app.Activity},藉此呼叫 {@code OnArticleSelectedListener} 執行個體: + + + + +

+ +
+public static class FragmentA extends ListFragment {
+    OnArticleSelectedListener mListener;
+    ...
+    @Override
+    public void onAttach(Activity activity) {
+        super.onAttach(activity);
+        try {
+            mListener = (OnArticleSelectedListener) activity;
+        } catch (ClassCastException e) {
+            throw new ClassCastException(activity.toString() + " must implement OnArticleSelectedListener");
+        }
+    }
+    ...
+}
+
+ +

如果 Activity 並未實作介面,那麼片段會擲回 +{@link java.lang.ClassCastException}。成功擲回時,{@code mListener} 成員會保留 Activity 所實作 +{@code OnArticleSelectedListener} 的參照資料,以便片段 A 呼叫 {@code OnArticleSelectedListener} 介面定義的方法與 Activity 分享事件。 + +例如,假設片段 A 是 +{@link android.app.ListFragment} 的延伸,則每當使用者點擊清單項目時,系統就會呼叫片段中的 {@link android.app.ListFragment#onListItemClick +onListItemClick()},讓該方法呼叫 {@code onArticleSelected()} 以便與 Activity 分享事件: + +

+ +
+public static class FragmentA extends ListFragment {
+    OnArticleSelectedListener mListener;
+    ...
+    @Override
+    public void onListItemClick(ListView l, View v, int position, long id) {
+        // Append the clicked item's row ID with the content provider Uri
+        Uri noteUri = ContentUris.{@link android.content.ContentUris#withAppendedId withAppendedId}(ArticleColumns.CONTENT_URI, id);
+        // Send the event and Uri to the host activity
+        mListener.onArticleSelected(noteUri);
+    }
+    ...
+}
+
+ +

傳入 {@link +android.app.ListFragment#onListItemClick onListItemClick()} 的 {@code id} 參數是使用者所點擊項目的資料列 ID,可讓 Activity (或其他片段) 用來從應用程式的 {@link +android.content.ContentProvider} 擷取文章。 +

+ +

如要進一步瞭解如何使用內容供應程式,請參閱內容供應程式。 +

+ + + +

將項目新增到動作列中

+ +

片段可實作 +{@link android.app.Fragment#onCreateOptionsMenu(Menu,MenuInflater) onCreateOptionsMenu()} 來為 Activity 的選項選單 (以及動作列) 提供選單項目。不過,您必須在呼叫 {@link +android.app.Fragment#onCreate(Bundle) onCreate()} 時呼叫 {@link +android.app.Fragment#setHasOptionsMenu(boolean) setHasOptionsMenu()},告知系統該片段會新增項目到「選項選單」,這個方法才能接收呼叫 (否則該片段將無法接收 +{@link android.app.Fragment#onCreateOptionsMenu onCreateOptionsMenu()} 的呼叫)。 + +

+ +

您之後透過片段新增到「選項選單」的任何物件都會附加到現有的選單項目。 +該片段也會在使用者選取選單項目時接收 {@link +android.app.Fragment#onOptionsItemSelected(MenuItem) onOptionsItemSelected()} 的回呼。 +

+ +

此外,您也可以在片段版面配置中註冊檢視來提供內容選單,方法是呼叫 {@link +android.app.Fragment#registerForContextMenu(View) registerForContextMenu()}。當使用者開啟內容選單時,片段就會接收 {@link +android.app.Fragment#onCreateContextMenu(ContextMenu,View,ContextMenu.ContextMenuInfo) +onCreateContextMenu()} 的呼叫。 +而當使用者選取某個項目時,片段則會接收 {@link +android.app.Fragment#onContextItemSelected(MenuItem) onContextItemSelected()} 的呼叫。

+ +

注意:雖然片段會在使用者選取項目時,針對所有新增的選單項目接收回呼,不過最先在使用者選取選單項目時接收個別回呼的是 Activity。 + +如果 Activity 在使用者選取項目時所實作的回呼無法處理所選項目,則系統會將該事件傳送到片段的回呼中。 +這種情況會發生在「選項選單」和內容選單。 +

+ +

如要進一步瞭解選單,請參閱選單動作列開發人員指南。

+ + + + +

處理片段生命週期

+ +
+ +

圖 3.Activity 生命週期對片段生命週期造成的影響。 +

+
+ +

管理片段生命週期的方式與管理 Activity 生命週期十分雷同。與 Activity 相同,片段有以下三種狀態: +

+ +
+
已繼續
+
系統會在執行中的 Activity 內顯示片段。
+ +
已暫停
+
前景中有其他具備焦點的 Activity,但系統仍會顯示含有這個片段的 Activity (前景 Activity 處於半透明狀態,或是未覆蓋整個螢幕)。 + +
+ +
已停止
+
系統不會顯示片段。這可能是因為主要 Activity 已停止,或是加到返回堆疊的片段已從 Activity 中移除。 +已停止的片段仍處於有效狀態 (系統會保留所有狀態和成員資訊), +但使用者無法看到這類片段,而且當 Activity 遭到刪除時,這些片段也會一併刪除。 +
+
+ +

與 Activity 相同,您可以使用 {@link +android.os.Bundle} 保留片段的狀態,以便在 Activity 的處理程序遭到刪除後想重新建立 Activity 時,還原片段狀態。 +您可以在片段的 {@link +android.app.Fragment#onSaveInstanceState onSaveInstanceState()} 回呼期間儲存狀態,並且在 +{@link android.app.Fragment#onCreate onCreate()}、{@link +android.app.Fragment#onCreateView onCreateView()} 或 {@link +android.app.Fragment#onActivityCreated onActivityCreated()} 時還原狀態。如要進一步瞭解如何儲存狀態,請參閱 Activity。 + +

+ +

Activity 與片段生命週期之間最明顯的差異是,生命週期儲存在個別返回堆疊的方式。 +在預設情況下,Activity 停止後會插入系統所管理的 Activity 堆疊,方便使用者按下 [返回] 按鈕來返回該 Activity (如工作和返回堆疊所述)。不過,片段只會在您進行移除片段的交易期間,呼叫 {@link +android.app.FragmentTransaction#addToBackStack(String) addToBackStack()} 來要求系統儲存執行個體時,插入主要 Activity 所管理的返回堆疊。 + + + + +

+ +

在其他情況下,管理片段生命週期的方式與管理 Activity 生命週期十分雷同。 +因此,管理 Activity 生命週期的做法同樣適用於片段。 +不過,建議您參考相關資源,瞭解 Activity 生命週期對片段生命週期造成的影響。 +

+ +

注意:如果您需要 {@link android.app.Fragment} 的 +{@link android.content.Context} 物件,請呼叫 {@link android.app.Fragment#getActivity()}。不過,請務必只在確定片段是附加到 Activity 的情況下,再呼叫 {@link android.app.Fragment#getActivity()}。 + +如果片段未附加到 Activity,或是片段因超過生命週期而遭到卸除,則 {@link android.app.Fragment#getActivity()} 將傳回空值。 +

+ + +

調整 Activity 生命週期

+ +

內含片段的 Activity 的生命週期會直接影響片段的生命週期,這樣一來,Activity 的每次生命週期回呼會針對每個片段產生相似的回呼。 + +例如,當 Activity 收到 {@link android.app.Activity#onPause} 後,Activity 中的每個片段都會收到 {@link android.app.Fragment#onPause}。 +

+ +

不過,片段有幾個額外的生命週期回呼,可用於處理與 Activity 之間的特殊互動,以執行建置或刪除片段 UI 等動作。以下是這些額外的回呼方法: + +

+ +
+
{@link android.app.Fragment#onAttach onAttach()}
+
當片段與 Activity 建立關聯時,系統就會呼叫這個方法 ({@link +android.app.Activity} 會傳入與片段相關聯的 Activity)。
+
{@link android.app.Fragment#onCreateView onCreateView()}
+
系統會呼叫這個方法來建立與片段相關聯的檢視階層。
+
{@link android.app.Fragment#onActivityCreated onActivityCreated()}
+
當 Activity 的 {@link android.app.Activity#onCreate +onCreate()} 方法成功傳回時,系統就會呼叫這個方法。
+
{@link android.app.Fragment#onDestroyView onDestroyView()}
+
當使用者移除與片段相關聯的檢視階層時,系統就會呼叫這個方法。
+
{@link android.app.Fragment#onDetach onDetach()}
+
當使用者將片段與 Activity 解除關聯時,系統就會呼叫這個方法。
+
+ +

如圖 3 所述,片段生命週期的流程會受到主要 Activity 的影響。 +您可以透過該圖片瞭解 Activity 的連續狀態如何決定片段要接收的回呼方法。 +例如,當 Activity 收到 {@link +android.app.Activity#onCreate onCreate()} 回呼後,Activity 中的片段就不會收到 +{@link android.app.Fragment#onActivityCreated onActivityCreated()} 以外的回呼。

+ +

Activity 一旦進入已恢復狀態,您便可視需要在 Activity 中新增或移除片段。 +因此,只有處於已恢復狀態的 Activity 會影響片段的生命週期。 +

+ +

不過,當 Activity 不再處於已恢復狀態後,Activity 就會再次推送片段的生命週期。 +

+ + + + +

範例說明

+ +

以下提供使用兩個片段建立兩個面板的版面配置的 Activity 範例,藉此綜合說明本文所述內容。 +下方 Activity 包含一個用於顯示莎士比亞劇作清單的片段,以及另一個用於在使用者選取清單項目時顯示劇作摘要的片段。 + +這個 Activity 範例同時示範了如何根據螢幕設定提供不同的片段設定。 +

+ +

注意:如需這個 Activity 的完整原始碼,請查閱 +{@code +FragmentLayout.java}

+ +

主要 Activity 會在 {@link +android.app.Activity#onCreate onCreate()} 時以常見的方式套用版面配置:

+ +{@sample development/samples/ApiDemos/src/com/example/android/apis/app/FragmentLayout.java main} + +

Activity 套用的版面配置為 {@code fragment_layout.xml}:

+ +{@sample development/samples/ApiDemos/res/layout-land/fragment_layout.xml layout} + +

使用這個版面配置可讓系統在 Activity 載入版面配置時,呼叫 {@code TitlesFragment} (這個類別會列出劇本名稱),而 {@link android.widget.FrameLayout} (顯示劇本摘要的片段將納入的目標類別) 則會佔用螢幕右側的空間,但在一開始會保持空白狀態。 + + +如下方所示,系統只會在使用者從清單中選取項目後,才將片段插入 {@link android.widget.FrameLayout}。 +

+ +

不過,並非所有螢幕設定都有足夠的空間同時並排顯示劇作清單以及劇作摘要。 +因此,上方版面配置只適用於橫向螢幕設定,系統會將它儲存在 {@code res/layout-land/fragment_layout.xml} 中。 +

+ +

而在螢幕為直向的情況下,系統會套用儲存在 {@code res/layout/fragment_layout.xml} 中的以下版面配置: +

+ +{@sample development/samples/ApiDemos/res/layout/fragment_layout.xml layout} + +

這個版面配置只包含 {@code TitlesFragment}。也就是說,如果裝置採用直向螢幕設定,系統只會顯示劇作名稱清單。 +因此,當使用者在採用這種設定的裝置上點擊清單項目後,應用程式就會啟動新 Activity 來顯示劇作摘要,而不是載入第二個片段。 + +

+ +

接著,請看看片段類別如何達到以上目標。首先是用於顯示莎士比亞劇作名稱清單的 {@code +TitlesFragment}。這個片段會延伸 {@link +android.app.ListFragment},並且依據該類別控制大多數的清單檢視工作。

+ +

如果您檢查這個程式碼,將會發現使用者點擊清單項目後會觸發兩種行為:視採用的版面配置而定,系統會建立並呈現新的片段,以便在同一 Activity 中顯示詳細資料 (將片段加到 {@link +android.widget.FrameLayout}),或是啟動新的 Activity (藉此顯示片段)。 + +

+ +{@sample development/samples/ApiDemos/src/com/example/android/apis/app/FragmentLayout.java titles} + +

第二個片段 {@code DetailsFragment} 則會針對 +{@code TitlesFragment} 中,使用者所選清單項目的劇本摘要:

+ +{@sample development/samples/ApiDemos/src/com/example/android/apis/app/FragmentLayout.java details} + +

針對 {@code TitlesFragment} 類別發出的回呼,如果使用者點擊清單項目,而且目前的版面配置「並未」包含 {@code R.id.details} 檢視 ({@code DetailsFragment} 所屬的檢視),則應用程式會執行 {@code DetailsActivity} Activity 來顯示項目內容。 + + +

+ +

以下是會在螢幕採用橫向版面設定時,嵌入 {@code DetailsFragment} 以顯示所選劇本摘要的 {@code DetailsActivity}: +

+ +{@sample development/samples/ApiDemos/src/com/example/android/apis/app/FragmentLayout.java +details_activity} + +

請注意,這個 Activity 會在螢幕採用橫向版面配置的情況下自行結束,因此主要 Activity 會接續顯示 {@code TitlesFragment} 旁的 {@code DetailsFragment}。如果使用者在採用直向版面配置的裝置上執行 {@code DetailsActivity},然後將該裝置旋轉成橫向 (這會重新執行目前的 Activity),就可能會發生這種情況。 + + +

+ + +

如需更多使用片段的範例 (以及本範例的原始檔案),請參閱ApiDemos 所提供的 API Demos 範例應用程式 (可透過 SDK 元件範本下載)。 + +

+ + diff --git a/docs/html-intl/intl/zh-tw/guide/components/fundamentals.jd b/docs/html-intl/intl/zh-tw/guide/components/fundamentals.jd new file mode 100644 index 0000000000000000000000000000000000000000..d3b3c2899614a70221f20bda44b06e3a52af2d26 --- /dev/null +++ b/docs/html-intl/intl/zh-tw/guide/components/fundamentals.jd @@ -0,0 +1,480 @@ +page.title=應用程式基礎知識 +@jd:body + + + +

Android 應用程式是以 Java 程式語言編寫而成。Android SDK 工具可將您的程式碼 — 連同任何相關資料和資源檔案 — 編入 APK ( + Android 套件,使用 {@code .apk} 後綴字詞的封存檔)。 +APK 檔案包含 Android 應用程式的所有內容,搭載 Android 作業系統的裝置會使用這種檔案來安裝應用程式。 +

+ +

Android 應用程式安裝到裝置之後,便可在專屬的安全性沙箱中執行:

+ +
    +
  • Android 作業系統是一種支援多位使用者的 Linux 系統;在這種系統中,每款應用程式即代表不同的使用者。 +
  • + +
  • 在預設情況下,系統會為每款應用程式指派一個不重複 Linux 使用者 ID (只有系統可使用這個 ID,應用程式無法取得這項資訊)。 +系統會為應用程式中的所有檔案設定權限,因此只有該應用程式指派的使用者 ID 可存取這些檔案。 +
  • + +
  • 所有處理程序都有專屬的虛擬機器 (VM),供系統在獨立環境中執行應用程式的程式碼。 +
  • + +
  • 在預設情況下,每款應用程式會在專屬的 Linux 處理程序中執行。Android 會在需要執行應用程式的任何元件時啟動處理程序,並且在不必執行應用程式元件,或系統必須復原記憶體供其他應用程式使用時關閉處理程序。 + +
  • +
+ +

如此一來,Android 系統就會實作「最低權限原則」。換句話說,在預設情況下,所有應用程式只能存取執行工作時所需的元件。 + +這樣一來,應用程式便無法存取系統的某些部分,藉此建立十分安全的執行環境。 +

+ +

不過,應用程式可透過一些方式與其他應用程式分享資料,以及存取系統服務: +

+ +
    +
  • 兩款應用程式可共用相同的 Linux 使用者 ID,以便存取彼此的檔案。 +為了節省系統資源,共用相同使用者 ID 的應用程式可在相同 Linux 處理程序中執行,以及共用相同的 VM (前提是應用程式必須使用相同的憑證進行簽署)。 + +
  • +
  • 應用程式可要求存取裝置資料 (例如使用者的聯絡人資料、簡訊、掛載式儲存空間 (SD 卡)、相機、藍牙等)。 +使用者必須在安裝期間授予所有應用程式權限。 +
  • +
+ +

本文提供有關 Android 應用程式如何在系統中運作的基本概念,其餘部分則說明以下幾點: +

+
    +
  • 定義應用程式的核心架構元件。
  • +
  • 用於宣告應用程式所需元件和裝置功能的宣示說明檔案。 +
  • +
  • 應用程式的程式碼以外的資源;這些資源可讓您的應用程式針對各種裝置設定最佳化本身的行為。 +
  • +
+ + + +

應用程式元件

+ +

應用程式元件是 Android 應用程式的重要設計模組。每個元件 都是系統進入您應用程式的不同要點。並非所有元件都是使用者的實際進入點;某些元件的定義取決於其他元件,但所有元件都是獨立的個體,扮演著特定角色 — 換句話說,每個元件都是獨特的設計模組,可協助定義您應用程式的整體行為。 + + + +

+ +

應用程式元件可分為 4 種不同類型。每種類型的用途和生命週期均不相同,可定義元件的建立及刪除方式。 +

+ +

以下是應用程式元件的 4 種類型:

+ +
+ +
Activity
+ +
單一 Activity 代表顯示使用者介面的一個畫面。例如,電子郵件應用程式可包含一個用於顯示新郵件清單的 Activity、一個用於撰寫郵件的 Activity,以及一個用於閱讀郵件的 Activity。 + +雖然電子郵件應用程式的 Activity 會共同運作,以提供豐富的使用者體驗,不過每個 Activity 都是不同的個體。 + +因此,其他應用程式可執行其中任何一項 Activity (如果電子郵件應用程式允許的話)。 +例如,相機應用程式可在電子郵件應用程式中,執行用於撰寫新郵件的 Activity,以便讓使用者分享相片。 + + +

Activity 是實作成 {@link android.app.Activity} 的子類別;詳情請參閱 Activity 開發人員指南。 + +

+
+ + +
服務
+ +
單一 服務 是在背景執行的元件,用於進行長期作業或遠端處理程序工作。 +服務並不會提供使用者介面。 +例如,服務可在使用者位於其他應用程式時在背景撥放音樂,或是透過網路擷取資料,同時允許使用者與 Activity 進行互動。 + +其他元件 (例如 Activity) 可啟動並讓服務執行,或是繫結至 Activity 以便與其進行互動。 + + +

服務是實作成 {@link android.app.Service} 的子類別;詳情請參閱服務開發人員指南。 + +

+
+ + +
內容供應程式
+ +
單一 內容供應程式 可管理一組已分享的應用程式資料。您可以將資料儲存在檔案系統、SQLite 資料庫、網路上,或是您應用程式可存取的任何其他永久儲存空間。 + +其他應用程式可透過內容供應程式查詢或甚至修改資料 (如果內容供應程式允許這麼做的話)。 +例如,Android 系統會提供用於管理使用者聯絡資訊的內容供應程式。 +因此,任何具備適當權限的應用程式均可查詢內容供應程式的一部分 (例如 {@link +android.provider.ContactsContract.Data}),以便讀取及寫入有關特定使用者的相關資訊。 + + +

此外,內容供應程式也可用於讀取及寫入只有您應用程式能存取的不公開資料。 +例如,Note Pad 範例應用程式可使用內容供應程式儲存記事。 +

+ +

內容供應程式是實作成 {@link android.content.ContentProvider} 的子類別,而且必須實作一組標準 API 以便讓其他應用程式執行交易。 + +如需詳細資訊,請參閱內容供應程式開發人員指南。 +

+
+ + +
廣播接收器
+ +
單一 廣播接收器 是一種元件,可回應整個系統的廣播通知。 +大多數廣播都是由系統所發出 — 例如,系統會發出廣播來通知使用者螢幕已關閉、電池電量不足,或相片已拍攝完成。此外,應用程式也可發出廣播 — 例如說發出廣播來通知其他應用程式特定資料已下載到裝置,可供它們使用。 + + +雖然廣播接收器無法顯示使用者介面,但它們可建立狀態列通知,告訴使用者發生了廣播事件。 + +具體而言,廣播接收器只是其他元件的「閘道」,用於執行極少量的工作。 +例如,廣播接收器可啟動服務依據事件執行特定工作。 + + +

廣播接收器是實作為成 {@link android.content.BroadcastReceiver} 的子類別,而每個廣播都是由 {@link android.content.Intent} 物件所發出。 +如需詳細資訊,請參閱 {@link android.content.BroadcastReceiver} 類別。 +

+
+ +
+ + + +

Android 系統設計的一項特色是,任何應用程式都可啟動其他應用程式的元件。 +例如,假設您想讓使用者透過裝置相機拍攝相片,您的應用程式可利用其他具備相關功能的應用程式 (如果有的話) 以達到這個目標,這樣您就不必自行建立用於拍攝相片的 Activity。 + +您不需要納入或連結相機應用程式的程式碼,只要啟動相機應用程式中用於拍攝像片的 Activity 即可。 + + +啟動相關 Activity 後,系統就會將相片傳回您的應用程式供您使用。對於使用者而言,相機就宛如是您應用程式的一部分。 +

+ +

當系統啟動某個元件後,就會啟動該應用程式的處理程序 (如果該應用程式目前並非處於執行中狀態) 並且呼叫該元件所需的類別。 +例如,假設您的應用程式啟動相機應用程式中用於拍攝相片的 Activity,則該 Activity 會在隸屬於相機應用程式的處理程序中執行,而不是您應用程式的處理程序。因此,與大多數其他系統的應用程式不同,Android 應用程式沒有單一進入點 (例如沒有 {@code main()} 函式)。 + + + +

+ +

系統是在個別處理程序 (具備檔案權限,可限制其他應用程式存取) 中執行每款應用程式,因此您的應用程式無法直接啟動其他應用程式的元件,不過 Android 系統可以。 + +基於這個原因,如要啟動其他應用程式的元件,您必須向指定「意圖」的系統發送訊息,以啟動特定元件。 + +系統隨後會為您啟用所需的元件。

+ + +

啟用元件

+ +

4 種元件類型的其中 3 種 — Activity、服務和廣播接收器 — 是透過「意圖」這種非同步訊息啟用。意圖會在執行階段將元件與彼此繫結 (您可以意圖想成要求其他元件進行動作的傳令員),不論元件是否屬於您的應用程式。 + + + +

+ +

意圖是使用 {@link android.content.Intent} 物件建立而成,該物件可定義訊息來啟用特定元件或特定「類型」的元件 — 意圖可以採取明確或隱含設定。 + +

+ +

針對 Activity 和服務,意圖會定義要執行的動作 (例如「查看」或「傳送」某項目),並且可能會指定執行動作的目標資料 URI (以及通知要啟用的元件)。 + +例如,意圖可傳達某 Activity 的要求,顯示圖片或開啟網頁。 +在某些情況下,您可以啟動 Activity 來接收結果,此時該 Activity 也會傳回{@link android.content.Intent} 的結果 (例如,您可以發出意圖讓使用者挑選聯絡人資料,並將該資訊傳回給您 — 傳回意圖會包含指向所選聯絡人的 URI )。 + + + +

+ +

針對廣播接收器,意圖只會定義要廣播的通知 (例如,用於通知裝置電量不足的廣播只會包含指出「電池電量不足」的已知動作字串)。 + +

+ +

其他元件類型和內容供應程式並非由意圖所啟用,而是在受 {@link android.content.ContentResolver} 發出的要求所指定時由系統啟用。 +內容解析程式可處理內容供應程式的所有直接交易,因此與供應程式進行交易的元件不必呼叫 {@link +android.content.ContentResolver} 物件的方法。 + +這樣會在內容供應程式與要求資訊 (基於安全目的) 之間保留抽象層。 +

+ +

用於啟用各種元件的方法有以下幾種:

+
    +
  • 將 {@link android.content.Intent} 傳送到 {@link android.content.Context#startActivity +startActivity()} 或 {@link android.app.Activity#startActivityForResult startActivityForResult()} (如果您想讓 Activity 傳回結果的話) 即可啟動 Activity (或是指派新工作給 Activity)。 + +
  • +
  • 將 {@link android.content.Intent} 傳送到 {@link android.content.Context#startService +startService()} 即可啟動服務 (或是指派新指示給正在執行的服務)。 +或者,您也可以將 {@link android.content.Intent} 傳送到 +{@link android.content.Context#bindService bindService()} 來繫結至服務。
  • +
  • 將 {@link android.content.Intent} 傳送到 +{@link android.content.Context#sendBroadcast(Intent) sendBroadcast()}、{@link +android.content.Context#sendOrderedBroadcast(Intent, String) sendOrderedBroadcast()} 或 {@link +android.content.Context#sendStickyBroadcast sendStickyBroadcast()} 等方法即可啟用廣播。
  • +
  • 對 {@link android.content.ContentResolver} 呼叫{@link +android.content.ContentProvider#query query()} 即可查詢內容供應程式。
  • +
+ +

如要進一步瞭解如何使用意圖,請參閱意圖和意圖篩選器。 +如要進一步瞭解如何啟用特定元件,請參閱下列說明文件: +Activity服務、{@link +android.content.BroadcastReceiver} 和內容供應程式

+ + +

宣示說明檔案

+ +

Android 系統必須先讀取應用程式的 {@code AndroidManifest.xml} 檔案 (「宣示說明」檔案) 確認應用程式元件確實存在,才能啟動該元件。 + +您的應用程式必須在這個檔案中宣告本身的所有元件,而該檔案必須位於應用程式專案目錄的根目錄。 +

+ +

除了宣告應用程式的元件以外,宣示說明還可以進行許多工作,包括: +

+
    +
  • 識別應用程式所需的任何使用者權限,例如網際網路存取權或使用者合約的讀取存取權。 +
  • +
  • 根據應用程式使用的 API,宣告應用程式所需的最低 API 級別。 +
  • +
  • 宣告應用程式所使用或所需的硬體和軟體,例如相機、藍牙服務或多點觸控螢幕。 +
  • +
  • 應用程式需要連結的 API 程式庫 (除了 Android 架構 API 以外),例如 Google 地圖程式庫。 + +
  • +
  • 還有其他工作
  • +
+ + +

宣告元件

+ +

宣告說明的主要工作是將應用程式的元件告知系統。例如,宣告說明檔案可用如下方式宣告 Activity: +

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<manifest ... >
+    <application android:icon="@drawable/app_icon.png" ... >
+        <activity android:name="com.example.project.ExampleActivity"
+                  android:label="@string/example_label" ... >
+        </activity>
+        ...
+    </application>
+</manifest>
+ +

<application>元素中,{@code android:icon} 屬性會指向識別應用程式的圖示資源。 + +

+ +

而在 <activity> 元素中, +{@code android:name} 屬性會指定 {@link +android.app.Activity} 子類別的完整類別名稱,{@code android:label} 屬性則會指定要當作 Activity 的使用者可見標籤使用的字串。 +

+ +

您必須用以下方式宣告所有應用程式元件:

+ + +

系統看不到您納入來源但未在宣示說明中宣告的 Activity、服務和內容供應程式,因此系統無法執行這些項目。 +不過,您可在宣示說明宣告廣播接收器,或是透過程式碼以動態方式建立廣播接收器 (將廣播接收器建立為 +{@link android.content.BroadcastReceiver} 物件),然後呼叫 {@link android.content.Context#registerReceiver registerReceiver()} +向系統註冊廣播接收器。 + +

+ +

如要進一步瞭解如何為應用程式建立宣示說明檔案,請參閱 AndroidManifest.xml 檔案。 +

+ + + +

宣告元件功能

+ +

啟用元件所述,您可以使用 +{@link android.content.Intent} 來啟動 Activity、服務和廣播接收器。如要這麼做,請在意圖中明確指定目標元件 (使用元件類別名稱)。 +不過,意圖最大的功能在於「隱含意圖」的概念。 +隱含意圖可簡單描述要執行的動作類型 (或是執行動作的資料依據) 以及讓系統在裝置中找出可執行動作的元件,然後加以啟動。 + + +如果意圖指出有多個元件可執行動作,則使用者可選取要使用的元件。 +

+ +

系統會比對接受到的意圖與裝置上其他應用程式的宣示說明檔案中提供的意圖篩選器,藉此識別可回應意圖的元件。 + +

+ +

在應用程式的宣示說明中宣告 Activity 時,您可視需要納入宣告 Activity 功能的意圖篩選器,以便讓 Activity 可回應其他應用程式的意圖。 + +您可以為元件宣告意圖篩選器,方法是將 {@code +<intent-filter>} 元素新增為元件宣告元素的子元素。 +

+ +

例如,假設您以用於撰寫新郵件的 Activity 建置電子郵件應用程式,您可以下列方式宣告意圖篩選器來回應「傳送」意圖 (藉此傳送新郵件): +

+
+<manifest ... >
+    ...
+    <application ... >
+        <activity android:name="com.example.project.ComposeEmailActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.SEND" />
+                <data android:type="*/*" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </activity>
+    </application>
+</manifest>
+
+ +

接著,如果其他應用程式透過 {@link +android.content.Intent#ACTION_SEND} 動作建立了意圖並傳送到 {@link android.app.Activity#startActivity +startActivity()},系統就可能會啟動您的 Activity 讓使用者撰寫及傳送郵件。 +

+ +

如要進一不瞭解如何建立意圖篩選器,請參閱意圖和意圖篩選器。 +

+ + + +

宣告應用程式需求

+ +

並非所有搭載 Android 作業系統的裝置都能提供完整功能。 +為了避免使用者在缺少應用程式所需功能的裝置上安裝您的應用程式,請務必在宣示說明檔案中宣告裝置和軟體需求,清楚定義您的應用程式支援的裝置類型。 + + +大多數宣告僅供使用者參考,系統無法讀取,但 Google Play 等外部服務可讀取這些宣示,以便在使用者透過自己的裝置搜尋應用程式時提供篩選功能。 + +

+ +

例如,假設您的應用程式需要相機且採用 Android 2.1 (API 級別 7) 導入的 API,建議您用下列方式在宣示說明檔案中宣告這些需求: +

+ +
+<manifest ... >
+    <uses-feature android:name="android.hardware.camera.any"
+                  android:required="true" />
+    <uses-sdk android:minSdkVersion="7" android:targetSdkVersion="19" />
+    ...
+</manifest>
+
+ +

如此一來,「沒有」相機且搭載 Android 2.1「以下版本」的裝置就無法從 Google Play 安裝您的應用程式。 +

+ +

不過,您也可以宣告您的應用程式會使用相機,但相機並非應用程式的「必要」配備。 +在這種情況下,應用程式的 {@code required}屬性必須設為 {@code "false"},而且應用程式必須在執行階段檢查裝置是否具備相機,並且視需要停用任何相機功能。 + +

+ +

如要進一步瞭解如何管理應用程式與不同裝置的相容性,請參閱裝置相容性。 + +

+ + + +

應用程式資源

+ +

Android 應用程式是以程式碼等其他要素開發而成 — 例如圖片、音訊檔案以及與應用程式視覺效果相關的其他資源。例如,您必須使用 XML 檔案定義 Activity 使用者介面的動畫、選單、樣式、顏色以及版面配置。 + + +使用應用程式資源可協助更新應用程式的各種特性,而不必修改程式碼 — 或是提供多組替代資源 — 藉此針對各種裝置設定 (例如不同的語言和螢幕大小) 最佳化您的應用程式。 + + +

+ +

針對您在 Android 專案中加入的所有資源,SDK 建置工具會定義一個整數 ID,讓您用於從應用程式的程式碼或 XML 中定義的其他資源參照特定資源。 + +例如,假設您的應用程式含有名為 {@code +logo.png} 的圖片檔案 (儲存在 {@code res/drawable/} 目錄中),SDK 工具會產生名為 {@code R.drawable.logo} 的資源 ID,讓您用於參照圖片並將其插入使用者介面。 + +

+ +

提供原始碼以外資源的一個重點是針對不同的裝置設定提供替代資源。 + +例如,您可以在 XML 中定義使用者介面字串,藉此將字串翻譯成其他語言,以及將這些字串儲存成個別檔案。 +接著,視您附加到資源目錄名稱的語言「修飾語」 (例如代表法文字串值的 {@code res/values-fr/}),以及使用者的語言設定而定,Android 系統會為您的 UI 套用適當的語言字串。 + + +

+ +

Android 針對替代資源支援各種「修飾語」。修飾語是一個簡短字串;您可在資源目錄名稱中加入修飾語,藉此定義應使用這些資源的裝置設定。 + +例如,您通常需要為 Activity 建立多種版面配置 (視裝置螢幕的方向和大小而定)。 + +例如,假設裝置螢幕的方向為縱向 (直版),版面配置的按鈕就必須以垂直方向排列;假設裝置螢幕的方向為橫向 (寬版),則版面配置的按鈕就必須以水平方向排列。 + +如要根據螢幕方向變更版面配置,請建立兩種版面配置,然後為每個版面配置目錄名稱套用適當的修飾語。 + +如此系統就會根據目前的裝置方向,自動套用適當的版面配置。 +

+ +

如要進一步瞭解您可在應用程式中加入的資源類型,以及如何針對不同的裝置設定建立替代資源,請詳閱提供資源。 +

+ + + +
+
+

繼續閱讀有關下列主題的說明文章:

+
+
意圖和意圖篩選器 +
+
說明如何使用 {@link android.content.Intent} API 來啟用應用程式元件 (例如 Activity 和服務),以及如何將應用程式元件提供給其他應用程式使用。 + +
+
Activity
+
說明如何建立 {@link android.app.Activity} 類別執行個體,以便讓應用程式的使用者介面提供不同內容。 +
+
提供資源
+
說明 Android 應用程式如何區別應用程式資源與程式碼,包括如何針對特定裝置設定供替代資源。 + + +
+
+
+
+

您可能也會想瞭解下列主題:

+
+
裝置相容性
+
說明 Android 如何在各種裝置上運作,以及如何針對各個裝置最佳化您的應用程式,或針對不同裝置限制應用程式提供的功能。 + +
+
系統權限
+
說明 Android 如何運用系統權限規定應用程式必須取得使用者同意才能使用特定 API。 +
+
+
+
+ diff --git a/docs/html-intl/intl/zh-tw/guide/components/index.jd b/docs/html-intl/intl/zh-tw/guide/components/index.jd new file mode 100644 index 0000000000000000000000000000000000000000..f34c7120e8ad39456e192bb91ca9274d7e605637 --- /dev/null +++ b/docs/html-intl/intl/zh-tw/guide/components/index.jd @@ -0,0 +1,57 @@ +page.title=應用程式元件 +page.landing=true +page.landing.intro=Android 的應用程式架構可讓您使用一系列可重複使用的元件,建立內容豐富的新穎應用程式。本節說明如何建置元件來定義應用程式的設計模組,以及如何使用意圖連結這些元件。 +page.metaDescription=Android 的應用程式架構可讓您使用一系列可重複使用的元件,建立內容豐富的新穎應用程式。本節說明如何建置元件來定義應用程式的設計模組,以及如何使用意圖連結這些元件。 +page.landing.image=images/develop/app_components.png +page.image=images/develop/app_components.png + +@jd:body + +
+ + + + + +
diff --git a/docs/html-intl/intl/zh-tw/guide/components/intents-filters.jd b/docs/html-intl/intl/zh-tw/guide/components/intents-filters.jd new file mode 100644 index 0000000000000000000000000000000000000000..d3edac3f817abb1d9b7238254d5006402c53d84c --- /dev/null +++ b/docs/html-intl/intl/zh-tw/guide/components/intents-filters.jd @@ -0,0 +1,899 @@ +page.title=意圖和意圖篩選器 +page.tags="IntentFilter" +@jd:body + + + + + + +

{@link android.content.Intent} 是可用來向另一個應用程式元件要求動作的傳訊物件。 + +雖然意圖有幾種方式可加速元件間的通訊,但共有三種基本使用案例: +

+ +
    +
  • 如何啟動 Activity: +

    {@link android.app.Activity} 代表應用程式中的單一畫面。您可以將 {@link android.content.Intent} 傳送至 {@link android.content.Context#startActivity startActivity()} 來啟動 +{@link android.app.Activity} 的新執行個體。 +{@link android.content.Intent} 可描述要啟動的 Activity 並攜帶任何必要資料。 +

    + +

    如果您想要在 Activity 完成時收到結果, +請呼叫 {@link android.app.Activity#startActivityForResult +startActivityForResult()}。Activity 的 {@link android.app.Activity#onActivityResult onActivityResult()}回呼中的個別 {@link android.content.Intent} 物件,就是 Activity 收到的結果。 + +如需詳細資訊,請參閱 Activity 指南。 +

  • + +
  • 如何啟動服務: +

    {@link android.app.Service} 是可以在背景中執行操作的元件,但沒有使用者介面。 +您可以將 {@link android.content.Intent} 傳送至 +{@link android.content.Context#startService startService()} 來啟動服務以執行一次性操作 (例如下載檔案)。 +{@link android.content.Intent} 可描述要啟動的服務並攜帶任何必要資料。 +

    + +

    如果服務是採用主從介面設計,您可以將 {@link android.content.Intent} 傳送至 {@link +android.content.Context#bindService bindService()} 來繫結至另一個元件的服務。 +如需詳細資訊,請參閱服務指南。

  • + +
  • 如何傳送廣播: +

    廣播是指任何應用程式都可接收的訊息。系統會傳送各種系統事件廣播,例如系統開機或裝置開始充電。 +您可以將 {@link android.content.Intent} 傳送至 {@link android.content.Context#sendBroadcast(Intent) sendBroadcast()}、 +{@link android.content.Context#sendOrderedBroadcast(Intent, String) 或{@link +android.content.Context#sendStickyBroadcast sendStickyBroadcast()},以向其他應用程式傳送廣播。 + + +

    +
  • +
+ + + + +

意圖類型

+ +

意圖類型分為兩種:

+ +
    +
  • 明確意圖:可依名稱 (完整類別名稱) 指定要啟動的元件。 +一般情況下,您會使用明確意圖啟動您應用程式中的元件,這是因為您知道 Activity 的類別名稱或您想要啟動的服務。 +例如,為回應使用者動作而啟動新的 Activity,或啟動服務以在背景下載檔案。 + +
  • + +
  • 隱含意圖:不會指定特定元件,而會宣告要執行的一般動作,讓另一個應用程式的元件來處理它。 +例如,如果您想要向使用者顯示地圖上的某個位置,可以使用隱含意圖,要求另一個支援應用程式在地圖上顯示指定的位置。 + +
  • +
+ +

當您建立明確意圖以啟動 Activity 或服務時,系統會立即啟動 +{@link android.content.Intent} 物件中指定的應用程式元件。

+ +
+ +

圖 1.說明如何透過系統傳送隱含意圖以啟動另一個 Activity:[1] Activity A 會建立含有動作描述的{@link android.content.Intent} 並傳送至 {@link +android.content.Context#startActivity startActivity()}。[2] Android 系統會搜尋所有應用程式,以找出符合該意圖的意圖篩選器。 + + +找到相符項目時,[3] 系統會呼叫其 {@link android.app.Activity#onCreate onCreate()} 方法,並將 {@link android.content.Intent} 傳送給它來啟動相符的 Activity (Activity B)。 + + +

+
+ +

當您建立隱含意圖時,Android 系統會比較意圖內容和裝置上其他應用程式的宣示說明檔案中宣告的「意圖篩選器」,以找出要啟動的適當元件。 + +如果意圖和意圖篩選器相符,系統會啟動該元件,並將 {@link android.content.Intent} 物件傳送給它。 +如果有多個意圖篩選器符合意圖,系統會顯示對話方塊,供使用者挑選要使用的應用程式。 +

+ +

意圖篩選器是應用程式宣示說明檔案中的運算式,可指定元件要接收的意圖類型。 + +例如,藉由宣告 Activity 的意圖篩選器,可讓其他應用程式使用特定意圖類型直接啟動您的 Activity。 +同樣地,如果您「不」為 Activity 宣告任何意圖篩選器,就只能以明確意圖啟動它。 + +

+ +

注意:為了確保您的應用程式安全,請一律使用明確意圖啟動 {@link android.app.Service},並且不要宣告服務的意圖篩選器。 + +使用隱含意圖啟動服務會危害安全性,原因在於您無法確定哪個服務會回應意圖,而且使用者無法得知系統會啟動哪項服務。 + +從 Android 5.0 (API 級別 21) 開始,如果您使用隱含意圖呼叫 {@link android.content.Context#bindService bindService()},系統都會擲回例外狀況。 + +

+ + + + + +

建置意圖

+ +

{@link android.content.Intent} 物件攜帶 Android 系統用來判斷要啟動哪個元件的資訊 (例如應接收意圖的確切元件名稱或元件類別),再加上接收者元件用以適當執行動作的資訊 (例如要執行的動作和據以執行的資料)。 + + +

+ + +

{@link android.content.Intent} 包含的主要資訊如下:

+ +
+ +
元件名稱
+
要啟動元件的名稱。 + +

雖可選擇是否使用,但這卻是讓意圖「明確」的重要資訊,表示意圖只能傳送至元件名稱所定義的應用程式元件。 + +如果不使用元件名稱,意圖會是「隱含」的,因此系統會根據其他意圖資訊來決定哪個元件應接收意圖 (例如動作、資料及類別 — 如下所述)。 + +如果您需要啟動應用程式中的特定元件,就應該指定元件名稱。 +

+ +

注意:啟動 {@link android.app.Service} 時,請務必指定元件名稱。 +否則,您無法確定哪個服務會回應意圖,而且使用者無法得知系統會啟動哪項服務。 +

+ +

{@link android.content.Intent} 的這個欄位是 +{@link android.content.ComponentName} 物件,您可以使用目標元件的完整類別名稱加以指定,包括應用程式的封裝名稱。例如, + +{@code com.example.ExampleActivity}。您可以使用 {@link +android.content.Intent#setComponent setComponent()}、{@link android.content.Intent#setClass +setClass()}、{@link android.content.Intent#setClassName(String, String) setClassName()} 或 +{@link android.content.Intent} 建構函式來設定元件名稱。

+ +
+ +

動作
+
以字串指定要執行的一般動作 (例如「檢視」或「挑選」)。 + +

就廣播意圖而言,這是指已發生且系統回報的動作。動作大半決定其餘意圖的建構方式 — 特別是資料與額外資料中包含的項目。 + + + +

您可以在應用程式內指定自己的動作以供意圖使用 (或由其他應用程式用來呼叫應用程式中的元件),但您應使用 {@link android.content.Intent} 類別或其他架構類別定義的動作常數。 + +以下是可啟動 Activity 的一些常見動作: +

+ +
+
{@link android.content.Intent#ACTION_VIEW}
+
當您有一些資訊可讓 Activity 向使用者顯示時,例如要在圖庫應用程式檢視的相片或要在地圖應用程式檢視的地址,就可以透過 {@link android.content.Context#startActivity startActivity()} 使用意圖中的這個動作。 + + +
+ +
{@link android.content.Intent#ACTION_SEND}
+
也稱為「分享」意圖,當您有一些資料可供使用者透過其他應用程式分享時,例如電子郵件應用程式或社交分享應用程式,才應透過 {@link android.content.Context#startActivity startActivity()} 使用意圖中的這個動作。 + +
+
+ +

如要進一步瞭解定義一般動作的常數,請參閱 {@link android.content.Intent} 類別參考文件。 +其他動作則是在 Android 架構的其他位置完成定義,例如可在系統設定應用程式中開啟特定畫面的動作位在 {@link android.provider.Settings} 中。 + +

+ +

您可以透過 {@link android.content.Intent#setAction +setAction()} 或 {@link android.content.Intent} 建構函式來指定意圖的動作。

+ +

如果您定義自己的動作,請務必加入您應用程式的封裝名稱做為前置詞。 +例如:

+
static final String ACTION_TIMETRAVEL = "com.example.action.TIMETRAVEL";
+
+ +
資料
+
URI ({@link android.net.Uri} 物件) 可參考據以執行的資料和/或該資料的 MIME 類型。 +提供的資料類型通常是由意圖的動作控制。例如,如果動作是 {@link android.content.Intent#ACTION_EDIT},資料就應包含欲編輯文件的 URI。 + + + +

建立意圖時,除了意圖的 URI 以外,請務必指定資料類型 (其 MIME 類型)。 + + +例如,能夠顯示影像的 Activity 可能無法播放音訊檔案,即使有類似的 URI 格式。 +因此,指定資料的 MIME 格式可協助 Android 系統找出最適合接收意圖的元件。 + +不過 — 尤其是當資料指出資料位在裝置何處且受 +{@link android.content.ContentProvider} 控制讓系統看見資料 MIME 類型的 +{@code content:} URI 時,有時能夠從 URI 推論出 MIME 類型。

+ +

如果您只想設定資料 URI,請呼叫 {@link android.content.Intent#setData setData()}。 +如要設定 MIME 類型,請呼叫 {@link android.content.Intent#setType setType()}。您還可以視需要利用 +{@link +android.content.Intent#setDataAndType setDataAndType()} 明確設定兩者。

+ +

注意:如果您想同時設定 URI 與 MIME 類型,「請勿」呼叫 {@link android.content.Intent#setData setData()} 和 +{@link android.content.Intent#setType setType()},原因是這兩者會抵銷彼此的值。 +請務必使用 {@link android.content.Intent#setDataAndType setDataAndType()} 來設定 URI 與 MIME 類型。 + +

+
+ +

類別
+
字串當中包含哪種元件應處理意圖的其他相關資訊。 +意圖中可放置的類別描述數目並沒有限制,但大多數意圖都不需要類別。 +以下是一些常見類別: + + +
+
{@link android.content.Intent#CATEGORY_BROWSABLE}
+
目標 Activity 允許自己由網頁瀏覽器啟動,以顯示連結所參照的資料 — 例如影像或電子郵件訊息。 + +
+
{@link android.content.Intent#CATEGORY_LAUNCHER}
+
Activity 是工作的初始 Activity,而且列在系統的應用程式啟動器中。 + +
+
+ +

如需類別的完整清單,請參閱 {@link android.content.Intent} 類別描述。 +

+ +

您可以使用 {@link android.content.Intent#addCategory addCategory()} 來指定類別。

+
+
+ + +

以上列出的屬性 (元件名稱、動作、資料及類別) 代表意圖的定義特性。 +藉由讀取這些屬性,Android 系統就能分析出應啟動的應用程式元件。 +

+ +

不過,意圖還能攜帶其他不影響如何將它解析成應用程式元件的資訊。 +意圖還能提供:

+ +
+
額外資料
+
鍵值對當中攜帶完成要求動作所需的其他資訊。 +和有些動作會使用特定種類的資料 URI 一樣,有些動作也會使用特定的額外資料。 + +

您可以使用各種 {@link android.content.Intent#putExtra putExtra()}方法來新增額外資料,每種方法都接受兩個參數:索引鍵名稱與值。 + + +您也可以使用所有的額外資料建立 {@link android.os.Bundle} 物件,再透過 {@link +android.content.Intent#putExtras putExtras()} 將 {@link android.content.Intent} 插入 {@link android.os.Bundle}。

+ +

例如,建立意圖以使用 +{@link android.content.Intent#ACTION_SEND} 來傳送電子郵件時,您可以利用 +{@link android.content.Intent#EXTRA_EMAIL} 索引鍵指定「收件者」,並使用 +{@link android.content.Intent#EXTRA_SUBJECT} 索引鍵指定「主旨」。

+ +

{@link android.content.Intent} 類別指定許多用於標準化資料類型的 +{@code EXTRA_*} 常數。如果您需要宣告自己的額外資料索引鍵 (用於您應用程式接收的意圖),請務必加入您應用程式的封裝名稱做為前置詞。 + +例如:

+
static final String EXTRA_GIGAWATTS = "com.example.EXTRA_GIGAWATTS";
+
+ +
旗標
+
{@link android.content.Intent} 類別中定義的旗標,可當成意圖的中繼資料使用。 +旗標可指示 Android 系統如何啟動 Activity (例如,Activity 屬於哪個 +工作) 以及如何處理已啟動的 Activity (例如,它是否屬於最近 Activity 清單)。 + + + +

如需詳細資訊,請參閱 {@link android.content.Intent#setFlags setFlags()} 方法。

+
+ +
+ + + + +

明確意圖範例

+ +

您用來啟動特定應用程式元件的就是明確意圖,例如應用程式中的特定 Activity 或服務。如要建立明確意圖,請定義 + +{@link android.content.Intent}物件的元件名稱 — 其他意圖屬性均為選用性質。 +

+ +

例如,您在應用程式中建置稱為 {@code DownloadService} 的服務,其設計為從網頁下載檔案,您可以使用下列程式碼來啟動該服務: +

+ +
+// Executed in an Activity, so 'this' is the {@link android.content.Context}
+// The fileUrl is a string URL, such as "http://www.example.com/image.png"
+Intent downloadIntent = new Intent(this, DownloadService.class);
+downloadIntent.setData({@link android.net.Uri#parse Uri.parse}(fileUrl));
+startService(downloadIntent);
+
+ +

{@link android.content.Intent#Intent(Context,Class)} 建構函式提供應用程式 {@link android.content.Context} 與元件 ({@link java.lang.Class} 物件)。 + +因此,這個意圖會明確啟動應用程式中的 +{@code DownloadService} 類別。

+ +

如要進一步瞭解如何建置及啟動服務,請參閱服務指南。 +

+ + + + +

隱含意圖範例

+ +

隱藏意圖指定的動作會呼叫裝置上任何能執行該動作的應用程式。 +當您的應用程式無法執行該動作,但其他應用程式或許能執行,而您想讓使用者挑選要使用的應用程式時,使用隱含意圖相當有用。 +

+ +

例如,您有想讓使用者與其他人分享的內容,可使用 +{@link android.content.Intent#ACTION_SEND} 動作來建立意圖,以及新增可指定要分享內容的額外資料。 +當您使用該意圖呼叫 +{@link android.content.Context#startActivity startActivity()} 時,使用者就能挑選要用以分享內容的應用程式。 +

+ +

注意:使用者可能會沒有「任何」 +應用程式可處理您傳送至 {@link android.content.Context#startActivity +startActivity()} 的隱含意圖。如果發生這種情況,呼叫會失敗且應用程式將會當機。如要確認 Activity 將可收到意圖,請在 +{@link android.content.Intent} 物件上呼叫 {@link android.content.Intent#resolveActivity +resolveActivity()}。如果結果不是 null,表示至少有一個應用程式能夠處理該意圖,可以放心呼叫 + +{@link android.content.Context#startActivity startActivity()}。如果結果為 null,表示您不應使用該意圖,可以的話您還必須將發出該意圖的功能停用。 + +

+ + +
+// Create the text message with a string
+Intent sendIntent = new Intent();
+sendIntent.setAction(Intent.ACTION_SEND);
+sendIntent.putExtra(Intent.EXTRA_TEXT, textMessage);
+sendIntent.setType("text/plain");
+
+// Verify that the intent will resolve to an activity
+if (sendIntent.resolveActivity(getPackageManager()) != null) {
+    startActivity(sendIntent);
+}
+
+ +

注意:在這種情況下,不會使用 URI,但會宣告意圖的資料類型,以指定額外資料所攜帶的內容。 +

+ + +

呼叫 {@link android.content.Context#startActivity startActivity()} 時,系統會檢查所有安裝的應用程式,判斷哪些可以處理這種意圖 (含有 {@link android.content.Intent#ACTION_SEND}動作和攜帶「純文字」資料的意圖)。 + + +如果只有一個應用程式能夠處理,該應用程式會立即開啟並獲派該意圖。 +如果有多個 Activity 接受該意圖,系統會顯示對話方塊,供使用者挑選要使用的應用程式。 +

+ + +
+ +

圖 2.選擇器對話方塊。

+
+ +

強制顯示應用程式選擇器

+ +

有多個應用程式均回應您的隱含意圖時,使用者可以選擇要使用的應用程式,並將該應用程式當成動作的預設選擇。 + +如果在執行動作時使用者希望之後都使用同一應用程式 (例如使用者偏好使用某個特定網頁瀏覽器開啟網頁,這項功能就非常實用。 + +

+ +

不過,如果有多個應用程式能回應該意圖,而使用者每次都想使用不同的應用程式,您應該明確顯示選擇器對話方塊。 +選擇器對話方塊每次都會要求使用者選取要用於該動作的應用程式 (使用者無法為該動作選取預設應用程式)。 + +例如,當您的應用程式使用 {@link +android.content.Intent#ACTION_SEND} 動作執行「分享」時,使用者可能希望根據當下的情況使用不同的應用程式來分享,此時您應該一律使用選擇器對話方塊,如圖 2 所示。 +

+ + + + +

如要顯示選擇器,請使用 {@link +android.content.Intent#createChooser createChooser()} 建立 {@link android.content.Intent},並將其傳送至 {@link +android.app.Activity#startActivity startActivity()}。例如:

+ +
+Intent sendIntent = new Intent(Intent.ACTION_SEND);
+...
+
+// Always use string resources for UI text.
+// This says something like "Share this photo with"
+String title = getResources().getString(R.string.chooser_title);
+// Create intent to show the chooser dialog
+Intent chooser = Intent.createChooser(sendIntent, title);
+
+// Verify the original intent will resolve to at least one activity
+if (sendIntent.resolveActivity(getPackageManager()) != null) {
+    startActivity(chooser);
+}
+
+ +

以上範例會顯示對話方塊 (將回應意圖的應用程式清單傳送至 {@link +android.content.Intent#createChooser createChooser()} 方法),並使用提供的文字做為對話方塊的標題。 +

+ + + + + + + + + +

接收隱含意圖

+ +

如要通知應用程式可接收的隱含內容,請在宣告說明檔案中利用 {@code <intent-filter>} 元素,針對您的每個應用程式元件宣告一或多個意圖篩選器。每個意圖篩選器都會根據意圖的動作、資料和類別,指定其接受的意圖類型。 + + + +只有在意圖通過您的其中一個意圖篩選器時,系統才會將隱含意圖傳送至您的應用程式元件。 +

+ +

注意:不論元件宣告的意圖篩選器為何,明確意圖一律會傳送至其目標。 +

+ +

應用程式元件應為其所能執行的各個獨特工作宣告不同的篩選器。例如,圖片庫應用程式中的一個 Activity 可能會有兩個篩選器:一個用於檢視圖片的篩選器,以及另一個用於編輯圖片的篩選器。 + +當 Activity 啟動時,它會檢查 +{@link android.content.Intent} 並根據 +{@link android.content.Intent} 中的資訊決定如何運作 (例如,是否要檢視編輯器控制項)。

+ +

每個意圖篩選器都是由 {@code <intent-filter>} 元素定義在應用程式的宣示說明檔案中,以巢狀方式置於對應的應用程式元件 (例如,{@code <activity>} 元素)。 + + +在 {@code <intent-filter>} 內,您可以使用以下三個元素當中的一或多個元素,指定要接受的意圖類型: + +

+ +
+
{@code <action>}
+
在 {@code name} 屬性中,宣告接受的意圖動作。值必須是動作的常值字串值,不得為類別常數。 +
+
{@code <data>}
+
使用一或多項屬性指定資料 URI ( +schemehostport、 +path 等) 與 MIME 類型的各層面,以宣告接受的資料類型。
+
{@code <category>}
+
在 {@code name} 屬性中,宣告接受的意圖類別。值必須是動作的常值字串值,不得為類別常數。 + + +

注意:如要接收隱含意圖,您「必須」在意圖篩選器中納入 {@link android.content.Intent#CATEGORY_DEFAULT} 類別。 + + +{@link android.app.Activity#startActivity startActivity()} 與 +{@link android.app.Activity#startActivityForResult startActivityForResult()} 方法對所有意圖進行處理時,就像已宣告 +{@link android.content.Intent#CATEGORY_DEFAULT} 類別一樣。 + 如果您未在意圖篩選器中宣告此類別,任何隱含意圖都不會解析為 Activity。 +

+
+
+ +

例如,假設資料類型為文字,以下 Activity 宣告的意圖篩選器都可接受 +{@link android.content.Intent#ACTION_SEND} 意圖:

+ +
+<activity android:name="ShareActivity">
+    <intent-filter>
+        <action android:name="android.intent.action.SEND"/>
+        <category android:name="android.intent.category.DEFAULT"/>
+        <data android:mimeType="text/plain"/>
+    </intent-filter>
+</activity>
+
+ +

想要建立包含多個 +{@code <action>}、 +{@code <data>} 或 +{@code <category>} 執行個體的篩選器也沒關係。 +如果您這樣做,只需要確定該元件能處理這些篩選器元素的任何組合。 +

+ +

當您想要處理多種意圖,但只限特定的動作、資料及類別類型組合時,您必須建立多個意圖篩選器。 +

+ + + + +

藉由將意圖與三個元素個別比較,針對篩選器來測試隱含意圖。 +如要傳送至元件,意圖必須通過共三個測試。 + +如果無一相符,Android 系統就不會將意圖傳送至該元件。不過,由於元件可能會有多個意圖篩選器,未通過其中一個元件篩選器的意圖可能會通過另一個篩選器。 + + +如要進一步瞭解系統如何解析意圖,請參閱下文的意圖解析

+ +

注意:如要避免一時疏忽而執行不同應用程式的 +{@link android.app.Service},請一律使用明確意圖啟動您自己的服務,同時不要宣告服務的意圖篩選器。 +

+ +

注意:對於所有 Activity,您都必須在宣示說明檔案中宣告意圖篩選器。 + +不過,廣播接收器的篩選器可以藉由呼叫 +{@link android.content.Context#registerReceiver(BroadcastReceiver, IntentFilter, String, +Handler) registerReceiver()} 進行動態註冊。您之後可以利用 {@link +android.content.Context#unregisterReceiver unregisterReceiver()} 來取消註冊接收器。這樣做可在您的應用程式執行時,讓應用程式只能在指定期間內接聽特定廣播。 + +

+ + + + + + + +

篩選器範例

+ +

如要進一步瞭解意圖篩選器行為,可以看看社交分享應用程式宣示說明檔案中的以下程式碼片段。 +

+ +
+<activity android:name="MainActivity">
+    <!-- This activity is the main entry, should appear in app launcher -->
+    <intent-filter>
+        <action android:name="android.intent.action.MAIN" />
+        <category android:name="android.intent.category.LAUNCHER" />
+    </intent-filter>
+</activity>
+
+<activity android:name="ShareActivity">
+    <!-- This activity handles "SEND" actions with text data -->
+    <intent-filter>
+        <action android:name="android.intent.action.SEND"/>
+        <category android:name="android.intent.category.DEFAULT"/>
+        <data android:mimeType="text/plain"/>
+    </intent-filter>
+    <!-- This activity also handles "SEND" and "SEND_MULTIPLE" with media data -->
+    <intent-filter>
+        <action android:name="android.intent.action.SEND"/>
+        <action android:name="android.intent.action.SEND_MULTIPLE"/>
+        <category android:name="android.intent.category.DEFAULT"/>
+        <data android:mimeType="application/vnd.google.panorama360+jpg"/>
+        <data android:mimeType="image/*"/>
+        <data android:mimeType="video/*"/>
+    </intent-filter>
+</activity>
+
+ +

第一個 Activity {@code MainActivity} 是應用程式的主要進入點 — 這個 Activity 會在使用者使用啟動器圖示初次啟動應用程式時開啟: +

+
    +
  • {@link android.content.Intent#ACTION_MAIN} 動作可指出這是主要進入點且預期沒有任何意圖資料。 +
  • +
  • {@link android.content.Intent#CATEGORY_LAUNCHER} 類別可指出此 Activity 的圖示應該放在系統的應用程式啟動器中。 +如果 {@code <activity>}元素未以 {@code icon} 指定圖示,系統會使用 {@code <application>} 元素中的圖示。 + +
  • +
+

上述兩項必須成對,Activity 才會顯示在應用程式啟動器中。

+ +

第二個 Activity {@code ShareActivity} 是用來加速分享文字與媒體內容。 +雖然使用者可能藉由從 {@code MainActivity} 來瀏覽它而進入此 Activity,但它們也能從發出隱含意圖 (符合兩個意圖篩選器之一) 的另一款應用程式直接進入 {@code ShareActivity}。 + +

+ +

注意: +{@code +application/vnd.google.panorama360+jpg} MIME 類型是指定全景相片的特殊資料類型,您可以透過 +Google 全景 API 來處理。 +

+ + + + + + + + + + + + + +

使用待處理意圖

+ +

{@link android.app.PendingIntent} 物件是環繞 {@link +android.content.Intent} 物件的包裝函式。{@link android.app.PendingIntent} 的主要用途是將權限授予外部應用程式,以便使用包含的 {@link android.content.Intent},有如從應用程式自己的程序執行一般。 + + +

+ +

待處理意圖的主要使用案例包括:

+
    +
  • 宣告當使用者透過您的通知執行動作時要執行的意圖 (Android 系統的 {@link android.app.NotificationManager} 會執行 {@link android.content.Intent})。 + + +
  • 宣告當使用者透過您的應用程式小工具執行動作時要執行的意圖 (主螢幕應用程式會執行 {@link android.content.Intent})。 + + +
  • 宣告要在未來的指定時間內執行的意圖 (Android 系統的 {@link android.app.AlarmManager} 會執行 {@link android.content.Intent})。 + +
+ +

由於每個 {@link android.content.Intent} 物件都設計為要由特定的應用程式元件類型來處理 ({@link android.app.Activity}、{@link android.app.Service} 或 {@link android.content.BroadcastReceiver}),因此請務必以相同的考量建立 {@link android.app.PendingIntent}。 + + +使用待處理意圖時,您的應用程式將不會透過像是 +{@link android.content.Context#startActivity +startActivity()} 的呼叫來執行意圖。當您藉由呼叫以下的個別建立者方法建立 +{@link android.app.PendingIntent} 時,您必須改為宣告意圖元件類型:

+ +
    +
  • {@link android.app.PendingIntent#getActivity PendingIntent.getActivity()} 適用於啟動 {@link android.app.Activity} 的 {@link android.content.Intent}。 +
  • +
  • {@link android.app.PendingIntent#getService PendingIntent.getService()} 適用於啟動 {@link android.app.Service} 的 {@link android.content.Intent}。 +
  • +
  • {@link android.app.PendingIntent#getBroadcast PendingIntent.getBroadcast()} 適用於啟動 {@link android.content.BroadcastReceiver} 的 {@link android.content.Intent}。 +
  • +
+ +

除非您的應用程式「收到」來自其他應用程式的待處理意圖,否則您可能只需要上述建立 +{@link android.app.PendingIntent} 的 +{@link android.app.PendingIntent} 方法。

+ +

每個方法會採用目前的應用程式 {@link android.content.Context}、您要包裝的 +{@link android.content.Intent} 以及一或多個指定應如何使用意圖的旗標 (例如該意圖是否能使用多次)。 +

+ +

如要進一步瞭解如何使用待處理意圖,請參閱個別使用案例的參考文件,例如通知以及應用程式小工具 API 指南。 + +

+ + + + + + + +

意圖解析

+ + +

當系統收到要啟動 Activity 的隱含意圖時,會根據三個層面來比較意圖與意圖篩選器,以便為該意圖搜尋最適合的 Activity。 +

+ +
    +
  • 意圖動作 +
  • 意圖資料 (URI 與資料類型) +
  • 意圖類別 +
+ +

下列各節就如何在應用程式宣示說明檔案中宣告意圖篩選器這點,說明如何比對意圖以找出適當的元件。 +

+ + +

動作測試

+ +

如要指定接受的意圖動作,意圖篩選器可以宣告零或多個 +{@code +<action>} 元素。例如:

+ +
+<intent-filter>
+    <action android:name="android.intent.action.EDIT" />
+    <action android:name="android.intent.action.VIEW" />
+    ...
+</intent-filter>
+
+ +

如要通過此篩選器,{@link android.content.Intent} 中指定的動作必須與篩選器中列出的其中一個動作相符。 +

+ +

如果篩選器未列出任何可供意圖比對的動作,所有意圖都無法通過測試。 +不過,如果 {@link android.content.Intent} 未指定任何動作,它就會通過測試 (只要篩選器至少包含一個動作即可)。 + +

+ + + +

類別測試

+ +

如要指定接受的意圖類別,意圖篩選器可以宣告零或多個 +{@code +<category>} 元素。例如:

+ +
+<intent-filter>
+    <category android:name="android.intent.category.DEFAULT" />
+    <category android:name="android.intent.category.BROWSABLE" />
+    ...
+</intent-filter>
+
+ +

{@link android.content.Intent} 中的每個類別都必須與篩選器中的類別相符,意圖才會通過類別測試。 +不需要反向進行 — 意圖篩選器宣告的類別可以比 +{@link android.content.Intent} 中指定的類別多,而 +{@link android.content.Intent} 仍可通過測試。因此,不論篩選器中宣告的類別為何,未包含類別的意圖一律可通過此測試。 +

+ +

注意:Android 會將 {@link android.content.Intent#CATEGORY_DEFAULT} 類別自動套用到所有傳送至 {@link +android.content.Context#startActivity startActivity()} 與 {@link +android.app.Activity#startActivityForResult startActivityForResult()} 的隱含意圖。因此,如果您希望 Activity 收到隱含意圖,Activity 的意圖篩選器就必須包含 +{@code "android.intent.category.DEFAULT"} 的類別 (如先前的 {@code <intent-filter>} 範例中所示)。 + + + +

+ + + +

資料測試

+ +

如要指定接受的意圖資料,意圖篩選器可以宣告零或多個 +{@code +<data>} 元素。例如:

+ +
+<intent-filter>
+    <data android:mimeType="video/mpeg" android:scheme="http" ... />
+    <data android:mimeType="audio/mpeg" android:scheme="http" ... />
+    ...
+</intent-filter>
+
+ +

每個 <data> + 元素都能指定 URI 結構與資料類型 (MIME 媒體類型)。URI 的每個部分都有個別的屬性: +{@code scheme}、{@code host}、{@code port} 和 {@code path}。 + +

+ +

{@code <scheme>://<host>:<port>/<path>}

+ +

+例如: +

+ +

{@code content://com.example.project:200/folder/subfolder/etc}

+ +

在這個 URI 中,配置為 {@code content}、主機為 {@code com.example.project}、連接埠為 {@code 200},而路徑為 {@code folder/subfolder/etc}。 + +

+ +

{@code <data>} 元素中,上述所有屬性均為選用性質,但具有線性相依性: +

+
    +
  • 如果未指定配置,就會忽略主機。
  • +
  • 如果未指定主機,就會忽略連接埠。
  • +
  • 如果配置與主機均未指定,就會忽略路徑。
  • +
+ +

將意圖中的 URI 與篩選器中的 URI 規格比較時,只會比較篩選器中所包含的 URI 部分。 +例如:

+
    +
  • 如果篩選器只指定配置,含有該配置的所有 URI 都會與篩選器相符。 +
  • +
  • 如果篩選器指定了配置與授權,但未指定路徑,不論其路徑為何,含有相同配置與授權的所有 URI 都會通過篩選器。 +
  • +
  • 如果篩選器指定了配置、授權和路徑,則只有包含相同配置、授權和路徑的 URI 才會通過篩選器。 +
  • +
+ +

注意:路徑規格可以包含萬用字元星號 (*),以要求僅部分相符的路徑名稱。 +

+ +

資料測試會比較意圖中的 URI 與 MIME 類型,以及篩選器中指定的 URI 與 MIME 類型。 +以下說明規則: +

+ +
    +
  1. 只有在篩選器未指定任何 URI 或 MIME 類型時,未包含 URI 或 MIME 類型的意圖才會通過測試。 +
  2. + +
  3. 只有在其 URI 與篩選器的 URI 格式相符,而且篩選器同樣未指定 MIME 類型時,包含 URI 但沒有 MIME 類型 (既非明確也無法從 URI 推測得出) 的意圖才會通過測試。 + +
  4. + +
  5. 只有在篩選器列出相同的 MIME 類型且未指定 URI 格式時,包含 MIME 類型但沒有 URI 的意圖才會通過測試。 +
  6. + +
  7. 只有在 MIME 類型與篩選器中列出的類型相符時,包含 URI 與 MIME 類型 (明確或可從 URI 推測得出) 的意圖才會通過 MIME 類型部分的測試。 + +如果它的 URI 與篩選器中的 URI 相符,或如果它有 +{@code content:} 或 {@code file:} URI,而且篩選器未指定 URI 時,就會通過 URI 部分的測試。換句話說,如果它的篩選器「只」列出 MIME 類型,就會假設元件支援 {@code content:} 與 {@code file:} 資料。 + + +

  8. +
+ +

+最後一項規則 (規則 (d)) 可反映出希望元件能夠從檔案或內容供應程式取得本機資料的期望。 + +因此,其篩選器可以只列出資料類型,同時不需要明確命名 +{@code content:} 與 {@code file:} 配置。 +此為典型案例。例如,如下所示的 {@code <data>} 元素可以告訴 Android,元件能夠從內容供應程式取得影像資料並加以顯示: + + +

+ +
+<intent-filter>
+    <data android:mimeType="image/*" />
+    ...
+</intent-filter>
+ +

+由於大部分可用資料都是由內容供應程式分配,因此指定資料類型但未指定 URI 的篩選器也許會最常見。 + +

+ +

+另一個常見的設定是含有配置與資料類型的篩選器。例如,如下所示的 +{@code <data>}元素可以告訴 Android,元件能夠從網路擷取影片資料以執行動作: + + +

+ +
+<intent-filter>
+    <data android:scheme="http" android:type="video/*" />
+    ...
+</intent-filter>
+ + + +

意圖比對

+ +

和意圖篩選器比對的意圖不只可探尋要啟動的目標元件,還能探尋裝置上元件集合的相關資料。 + +例如,主畫面應用程式會利用指定 +{@link android.content.Intent#ACTION_MAIN} 動作與 +{@link android.content.Intent#CATEGORY_LAUNCHER} 類別的意圖篩選器來尋找所有 Activity,以填入應用程式啟動器。 +

+ +

您的應用程式能以類似的方式使用意圖比對。 +{@link android.content.pm.PackageManager} 有一組 {@code query...()}方法可將接受特定意圖的所有元件傳回,還有一系列類似的 +{@code resolve...()} 方法可決定回應意圖的最佳元件。 + +例如, +{@link android.content.pm.PackageManager#queryIntentActivities +queryIntentActivities()} 會傳回所有 Activity 清單,上述的 Activity 都可以執行當成引數傳送的意圖,以及 +{@link +android.content.pm.PackageManager#queryIntentServices +queryIntentServices()} 可傳回類似的服務清單。 + +這些方法不會啟動元件,只會列出可以回應的元件。類似的方法 +{@link android.content.pm.PackageManager#queryBroadcastReceivers +queryBroadcastReceivers()} 可用於廣播接收器。 +

+ + + + diff --git a/docs/html-intl/intl/zh-tw/guide/components/loaders.jd b/docs/html-intl/intl/zh-tw/guide/components/loaders.jd new file mode 100644 index 0000000000000000000000000000000000000000..89bfc80744d9605aae4f8a5e9ff2b21a6fcea079 --- /dev/null +++ b/docs/html-intl/intl/zh-tw/guide/components/loaders.jd @@ -0,0 +1,494 @@ +page.title=載入器 +parent.title=Activity +parent.link=activities.html +@jd:body +
+
+

本文件內容

+
    +
  1. 載入器 API 摘要
  2. +
  3. 在應用程式中使用載入器 +
      +
    1. +
    2. 啟動載入器
    3. +
    4. 重新啟動載入器
    5. +
    6. 使用 LoaderManager 回呼
    7. +
    +
  4. +
  5. 範例說明 +
      +
    1. 其他範例
    2. +
    +
  6. +
+ +

重要類別

+
    +
  1. {@link android.app.LoaderManager}
  2. +
  3. {@link android.content.Loader}
  4. + +
+ +

相關範例

+
    +
  1. LoaderCursor +
  2. +
  3. LoaderThrottle +
  4. +
+
+
+ +

在 Android 3.0 導入的載入器可輕鬆在 Activity 或片段中,以非同步方式載入資料。 +載入器的特性如下:

+
    +
  • 適用於所有 {@link android.app.Activity} 和 {@link +android.app.Fragment} 。
  • +
  • 提供以非同步方式載入資料。
  • +
  • 監視其資料來源,並在內容變更時傳送新的結果。 +
  • +
  • 可在設定變更後重新建立時,自動重新連接到上次載入器的游標, +因此不需要重新查詢資料。 +
  • +
+ +

載入器 API 摘要

+ +

有多個類別和介面可能與在應用程式中使用載入器有關。 +請參閱下表的摘要說明:

+ + + + + + + + + + + + + + + + + + + + + + + + + + +
類別/介面說明
{@link android.app.LoaderManager}與 {@link android.app.Activity} 或 +{@link android.app.Fragment} 相關聯的抽象類別,可用於管理一或多個 {@link +android.content.Loader} 執行個體。這可以協助應用程式管理所需執行時間較長的操作與 {@link android.app.Activity} 或 {@link android.app.Fragment} 生命週期搭配使用,這最常與 +{@link android.content.CursorLoader} 搭配使用,不過應用程式能夠撰寫自己的載入器來載入其他類型的資料。 + + + +
+
+ 每個 Activity 或片段只能有一個 {@link android.app.LoaderManager},但 {@link android.app.LoaderManager} 可以有多個載入器。 +
{@link android.app.LoaderManager.LoaderCallbacks}可供用戶端與 {@link +android.app.LoaderManager} 互動的回呼介面。例如,您使用 {@link +android.app.LoaderManager.LoaderCallbacks#onCreateLoader onCreateLoader()} +回呼方法來建立新的載入器。
{@link android.content.Loader}以非同步方式載入資料的抽象類別。這是載入器的基本類別。 +您通常會使用 {@link +android.content.CursorLoader},但您也可以實作自己的子類別。載入器處於使用中時,應會監視其資料來源,並在內容有變更時傳送新的結果。 + +
{@link android.content.AsyncTaskLoader}提供 {@link android.os.AsyncTask} 以執行工作的抽象載入器。
{@link android.content.CursorLoader}查詢 +{@link android.content.ContentResolver} 並傳回 {@link +android.database.Cursor} 的 {@link android.content.AsyncTaskLoader} 子類別。此類別會以標準方式實作 {@link +android.content.Loader} 通訊協定,用來查詢建置於 {@link android.content.AsyncTaskLoader} 的游標,以便在背景執行緒中執行游標查詢,藉此避免封鎖應用程式的 UI。 + + +以非同步方式從 {@link +android.content.ContentProvider} 載入資料,而不是透過片段或 Activity 的 API 來管理查詢,最好的方法就是使用此載入器。 +
+ +

上表中的類別和介面就是您將用來在應用程式中實作載入器的基本元件。 +上述元件不需要在您建立載入器時全部使用,但您必須一律參照到 {@link +android.app.LoaderManager},才能初始化載入器並實作 +{@link android.content.Loader} 類別,例如 {@link +android.content.CursorLoader}。 +以下各節說明如何在應用程式中使用這些類別和介面。 +

+ +

在應用程式中使用載入器

+

本節說明如何在 Android 應用程式中使用載入器。使用載入器的應用程式通常包括下列物件: +

+
    +
  • {@link android.app.Activity} 或 {@link android.app.Fragment}。
  • +
  • {@link android.app.LoaderManager} 的執行個體。
  • +
  • 可載入 {@link +android.content.ContentProvider} 所備份資料的 {@link android.content.CursorLoader}。或者,您可以實作自己的 +{@link android.content.Loader} 子類別或 {@link android.content.AsyncTaskLoader},從其他來源載入資料。 +
  • +
  • {@link android.app.LoaderManager.LoaderCallbacks} 的實作。 +您可以在這裡建立新的載入器和管理對現有載入器的參照。 +
  • +
  • 顯示載入器資料的一種方式,例如 {@link +android.widget.SimpleCursorAdapter}。
  • +
  • 使用 {@link android.content.CursorLoader} 時的資料來源,例如 {@link android.content.ContentProvider}。 +
  • +
+

啟動載入器

+ +

{@link android.app.LoaderManager} 可在 {@link android.app.Activity} 或 {@link android.app.Fragment} 內管理一或多個 {@link +android.content.Loader} 執行個體。 +每個 Activity 或片段只能有一個 {@link +android.app.LoaderManager}。

+ +

您通常會在 Activity 的 {@link +android.app.Activity#onCreate onCreate()} 方法內或在片段的 {@link android.app.Fragment#onActivityCreated onActivityCreated()} 方法內,初始化 {@link android.content.Loader}, + +如下所示: +

+ +
// Prepare the loader.  Either re-connect with an existing one,
+// or start a new one.
+getLoaderManager().initLoader(0, null, this);
+ +

{@link android.app.LoaderManager#initLoader initLoader()} 方法採用下列參數: +

+
    +
  • 可識別載入器的不重複 ID。在本範例中,此 ID 為 0。
  • +
  • 可在建構時提供給載入器的選用引數 (在本範例中為null)。 +
  • + +
  • {@link android.app.LoaderManager.LoaderCallbacks} 實作, +{@link android.app.LoaderManager} 會呼叫此實作來回報載入器事件。在本範例中,本機類別會實作 {@link +android.app.LoaderManager.LoaderCallbacks} 執行個體,以將參照傳送給它自己 ({@code this})。 + +
  • +
+

{@link android.app.LoaderManager#initLoader initLoader()} 呼叫可確保載入器已初始化且處於有效狀態。 +可能會有兩種結果:

+
    +
  • 如果 ID 所指定的載入器已經存在,就會重複使用上次建立的載入器。 +
  • +
  • 如果 ID 所指定的載入器「不存在」, +{@link android.app.LoaderManager#initLoader initLoader()} 會觸發 +{@link android.app.LoaderManager.LoaderCallbacks} 方法 {@link android.app.LoaderManager.LoaderCallbacks#onCreateLoader onCreateLoader()}。 +您可以在這裡實作程式碼以具現化及傳回新的載入器。 +如需詳細資訊,請參閱 onCreateLoader
  • +
+

在任一情況下,指定的 {@link android.app.LoaderManager.LoaderCallbacks}實作會與載入器建立關聯且會在載入器狀態變更時呼叫。 + +如果進行此呼叫時,呼叫器處於已啟動狀態,而要求的載入器已經存在並產生自己的資料,那麼系統會立即呼叫 {@link +android.app.LoaderManager.LoaderCallbacks#onLoadFinished onLoadFinished()} (在 {@link android.app.LoaderManager#initLoader initLoader()} 期間),請務必做好發生這種情況的準備。 + + + +如要進一步瞭解此回呼,請參閱 onLoadFinished +

+ +

請注意,{@link android.app.LoaderManager#initLoader initLoader()}方法會傳回建立的 {@link android.content.Loader},但您不需要擷取它的參照。 + +{@link android.app.LoaderManager} 會自動管理載入器的生命週期。 +{@link android.app.LoaderManager} 會在必要時啟動及停止載入,並維護載入器的狀態與其相關內容。 + +顧名思義,您鮮少會直接與載入器互動 (但如需使用載入器方法微調載入器行為的範例,請參閱 LoaderThrottle 範例)。 + +當發生特定事件時,您最常會使用 {@link +android.app.LoaderManager.LoaderCallbacks} 方法來介入載入程序。 + +如要進一步瞭解此主題,請參閱使用 LoaderManager 回呼

+ +

重新啟動載入器

+ +

當您使用 {@link android.app.LoaderManager#initLoader initLoader()} (如上所示),它會使用已指定 ID 的現有載入器 (如果有的話)。 + +如果沒有,就會自行建立載入器。不過,有時候您會想捨棄舊資料並從頭開始。 +

+ +

如要捨棄舊資料,請使用 {@link +android.app.LoaderManager#restartLoader restartLoader()}。例如,當使用者的查詢改變時,實作 {@link android.widget.SearchView.OnQueryTextListener} 會重新啟動載入器。 + +您必須重新啟動載入器,才能使用修訂後的搜尋篩選器執行新的查詢: +

+ +
+public boolean onQueryTextChanged(String newText) {
+    // Called when the action bar search text has changed.  Update
+    // the search filter, and restart the loader to do a new query
+    // with this filter.
+    mCurFilter = !TextUtils.isEmpty(newText) ? newText : null;
+    getLoaderManager().restartLoader(0, null, this);
+    return true;
+}
+ +

使用 LoaderManager 回呼

+ +

{@link android.app.LoaderManager.LoaderCallbacks} 是回呼介面,可讓用戶端與 {@link android.app.LoaderManager} 互動。 +

+

載入器 (特別是 {@link android.content.CursorLoader}) 在停止之後,應該會保留它們的資料。 +這可讓應用程式保留其在 Activity 或片段的 {@link android.app.Activity#onStop +onStop()} 與 {@link android.app.Activity#onStart onStart()} 方法間的資料,好讓使用者回到應用程式時,不必枯等資料重新載入。 + + +您可以使用 {@link android.app.LoaderManager.LoaderCallbacks} 方法,藉以得知何時該建立新的載入器,以及指示應用程式何時該停止使用載入器的資料。 + +

+ +

{@link android.app.LoaderManager.LoaderCallbacks} 包含以下方法: +

+
    +
  • {@link android.app.LoaderManager.LoaderCallbacks#onCreateLoader onCreateLoader()} — 具現化及傳回指定 ID 的新 {@link android.content.Loader}。 + +
+
    +
  • {@link android.app.LoaderManager.LoaderCallbacks#onLoadFinished onLoadFinished()} — 當先前建立的載入器已完成其載入工作時呼叫。 + +
+
    +
  • {@link android.app.LoaderManager.LoaderCallbacks#onLoaderReset onLoaderReset()} — 正要重設先前建立的載入器時呼叫,使其資料無法使用。 + + +
  • +
+

以上方法在下列幾節有更詳細的說明。

+ +

onCreateLoader

+ +

當您嘗試存取載入器 (例如,透過 {@link +android.app.LoaderManager#initLoader initLoader()}) 時,系統會檢查根據 ID 指定的載入器是否已存在。 +如果不存在,就會觸發 {@link +android.app.LoaderManager.LoaderCallbacks} 方法 {@link +android.app.LoaderManager.LoaderCallbacks#onCreateLoader onCreateLoader()}。您可以在這裡建立新的載入器。 +這通常會是 {@link +android.content.CursorLoader},但您也可以實作自己的 {@link +android.content.Loader} 子類別。

+ +

在此範例中,{@link +android.app.LoaderManager.LoaderCallbacks#onCreateLoader onCreateLoader()} 回呼方法會建立 {@link android.content.CursorLoader}。 +您必須使用其建構函式方法來建置 +{@link android.content.CursorLoader},其需要一組完整的資訊才能對 {@link +android.content.ContentProvider} 執行查詢。 +具體來說,建構函式方法需要以下項目:

+
    +
  • uri - 要擷取內容的 URI。
  • +
  • projection - 要傳回的欄清單。傳送 +null 將會傳回無效的所有欄。
  • +
  • selection - 篩選器會採用 SQL WHERE 子句的格式 (WHERE 本身除外) 宣告要傳回的列。 +傳送 +null 將會傳回指定 URI 的所有列。
  • +
  • selectionArgs - 您可能會在選取項目中包含 ?s,而其會由來自 selectionArgs 的值按照其出現在選取項目中的順序所取代。 + +值將會繫結為字串。
  • +
  • sortOrder - 如何採用 SQL ORDER BY 子句的格式 (ORDER BY 本身除外) 來排列各列的順序。 +傳遞 null 將會使用預設的排序順序,其可能是無排序順序。 +
  • +
+

例如:

+
+ // If non-null, this is the current filter the user has provided.
+String mCurFilter;
+...
+public Loader<Cursor> onCreateLoader(int id, Bundle args) {
+    // This is called when a new Loader needs to be created.  This
+    // sample only has one Loader, so we don't care about the ID.
+    // First, pick the base URI to use depending on whether we are
+    // currently filtering.
+    Uri baseUri;
+    if (mCurFilter != null) {
+        baseUri = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI,
+                  Uri.encode(mCurFilter));
+    } else {
+        baseUri = Contacts.CONTENT_URI;
+    }
+
+    // Now create and return a CursorLoader that will take care of
+    // creating a Cursor for the data being displayed.
+    String select = "((" + Contacts.DISPLAY_NAME + " NOTNULL) AND ("
+            + Contacts.HAS_PHONE_NUMBER + "=1) AND ("
+            + Contacts.DISPLAY_NAME + " != '' ))";
+    return new CursorLoader(getActivity(), baseUri,
+            CONTACTS_SUMMARY_PROJECTION, select, null,
+            Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC");
+}
+

onLoadFinished

+ +

這個方法是在先前建立的載入器已完成其載入工作時呼叫。 +此方法一律是在提供給此載入器的最後資料發佈之前呼叫。 +此時,您應要移除所有使用的舊資料 (由於資料即將發佈),但不應自行發佈,因為載入器擁有該資料且將會負責處理。 + +

+ + +

一旦應用程式不再使用資料時,載入器就會立即發佈資料。 +例如,如果資料是來自 {@link +android.content.CursorLoader} 的游標,您不應該自行對它呼叫 {@link +android.database.Cursor#close close()}。如果正要將該游標放入 +{@link android.widget.CursorAdapter},您應該使用 {@link +android.widget.SimpleCursorAdapter#swapCursor swapCursor()} 方法,如此才不會關閉舊的 +{@link android.database.Cursor}。例如:

+ +
+// This is the Adapter being used to display the list's data.
SimpleCursorAdapter mAdapter; +... + +public void onLoadFinished(Loader<Cursor> loader, Cursor data) { + // Swap the new cursor in.  (The framework will take care of closing the + // old cursor once we return.) + mAdapter.swapCursor(data); +}
+ +

onLoaderReset

+ +

這個方法是在正要重設先前建立的載入器時呼叫,以便使其資料無法使用。 +此回呼方法可讓您知道即將要發佈資料,而能先行將其參照移除。 +  

+

此實作會以 null 的值呼叫 +{@link android.widget.SimpleCursorAdapter#swapCursor swapCursor()}: +

+ +
+// This is the Adapter being used to display the list's data.
+SimpleCursorAdapter mAdapter;
+...
+
+public void onLoaderReset(Loader<Cursor> loader) {
+    // This is called when the last Cursor provided to onLoadFinished()
+    // above is about to be closed.  We need to make sure we are no
+    // longer using it.
+    mAdapter.swapCursor(null);
+}
+ + +

範例說明

+ +

例如,以下是 {@link +android.app.Fragment} 的完整實作, +其顯示的 {@link android.widget.ListView} 包含聯絡人內容供應程式的查詢結果。它使用 {@link +android.content.CursorLoader} 管理對供應程式的查詢。

+ +

如本範例所示,針對要存取使用者聯絡人的應用程式,它的宣示說明必須包含 {@link android.Manifest.permission#READ_CONTACTS READ_CONTACTS} 權限。 + +

+ +
+public static class CursorLoaderListFragment extends ListFragment
+        implements OnQueryTextListener, LoaderManager.LoaderCallbacks<Cursor> {
+
+    // This is the Adapter being used to display the list's data.
+    SimpleCursorAdapter mAdapter;
+
+    // If non-null, this is the current filter the user has provided.
+    String mCurFilter;
+
+    @Override public void onActivityCreated(Bundle savedInstanceState) {
+        super.onActivityCreated(savedInstanceState);
+
+        // Give some text to display if there is no data.  In a real
+        // application this would come from a resource.
+        setEmptyText("No phone numbers");
+
+        // We have a menu item to show in action bar.
+        setHasOptionsMenu(true);
+
+        // Create an empty adapter we will use to display the loaded data.
+        mAdapter = new SimpleCursorAdapter(getActivity(),
+                android.R.layout.simple_list_item_2, null,
+                new String[] { Contacts.DISPLAY_NAME, Contacts.CONTACT_STATUS },
+                new int[] { android.R.id.text1, android.R.id.text2 }, 0);
+        setListAdapter(mAdapter);
+
+        // Prepare the loader.  Either re-connect with an existing one,
+        // or start a new one.
+        getLoaderManager().initLoader(0, null, this);
+    }
+
+    @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+        // Place an action bar item for searching.
+        MenuItem item = menu.add("Search");
+        item.setIcon(android.R.drawable.ic_menu_search);
+        item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
+        SearchView sv = new SearchView(getActivity());
+        sv.setOnQueryTextListener(this);
+        item.setActionView(sv);
+    }
+
+    public boolean onQueryTextChange(String newText) {
+        // Called when the action bar search text has changed.  Update
+        // the search filter, and restart the loader to do a new query
+        // with this filter.
+        mCurFilter = !TextUtils.isEmpty(newText) ? newText : null;
+        getLoaderManager().restartLoader(0, null, this);
+        return true;
+    }
+
+    @Override public boolean onQueryTextSubmit(String query) {
+        // Don't care about this.
+        return true;
+    }
+
+    @Override public void onListItemClick(ListView l, View v, int position, long id) {
+        // Insert desired behavior here.
+        Log.i("FragmentComplexList", "Item clicked: " + id);
+    }
+
+    // These are the Contacts rows that we will retrieve.
+    static final String[] CONTACTS_SUMMARY_PROJECTION = new String[] {
+        Contacts._ID,
+        Contacts.DISPLAY_NAME,
+        Contacts.CONTACT_STATUS,
+        Contacts.CONTACT_PRESENCE,
+        Contacts.PHOTO_ID,
+        Contacts.LOOKUP_KEY,
+    };
+    public Loader<Cursor> onCreateLoader(int id, Bundle args) {
+        // This is called when a new Loader needs to be created.  This
+        // sample only has one Loader, so we don't care about the ID.
+        // First, pick the base URI to use depending on whether we are
+        // currently filtering.
+        Uri baseUri;
+        if (mCurFilter != null) {
+            baseUri = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI,
+                    Uri.encode(mCurFilter));
+        } else {
+            baseUri = Contacts.CONTENT_URI;
+        }
+
+        // Now create and return a CursorLoader that will take care of
+        // creating a Cursor for the data being displayed.
+        String select = "((" + Contacts.DISPLAY_NAME + " NOTNULL) AND ("
+                + Contacts.HAS_PHONE_NUMBER + "=1) AND ("
+                + Contacts.DISPLAY_NAME + " != '' ))";
+        return new CursorLoader(getActivity(), baseUri,
+                CONTACTS_SUMMARY_PROJECTION, select, null,
+                Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC");
+    }
+
+    public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
+        // Swap the new cursor in.  (The framework will take care of closing the
+        // old cursor once we return.)
+        mAdapter.swapCursor(data);
+    }
+
+    public void onLoaderReset(Loader<Cursor> loader) {
+        // This is called when the last Cursor provided to onLoadFinished()
+        // above is about to be closed.  We need to make sure we are no
+        // longer using it.
+        mAdapter.swapCursor(null);
+    }
+}
+

其他範例

+ +

ApiDemos 中有數個不同的範例,示範如何使用載入器: +

+
    +
  • LoaderCursor - 上述程式碼片段的完整版本在此。 + +
  • +
  • LoaderThrottle - 以範例說明如何使用節流功能在其資料變更時降低內容供應程式執行的查詢數目。 +
  • +
+ +

如要進一步瞭解如何下載及安裝 SDK 範例,請參閱取得範例。 +

+ diff --git a/docs/html-intl/intl/zh-tw/guide/components/processes-and-threads.jd b/docs/html-intl/intl/zh-tw/guide/components/processes-and-threads.jd new file mode 100644 index 0000000000000000000000000000000000000000..74dbb8ebb3f151dfa70b65a11e0bd869c9058077 --- /dev/null +++ b/docs/html-intl/intl/zh-tw/guide/components/processes-and-threads.jd @@ -0,0 +1,411 @@ +page.title=處理程序和執行緒 +page.tags=生命週期、背景 + +@jd:body + + + +

當應用程式元件啟動且該應用程式未執行任何其他元件時,Android 系統會以執行單一執行緒的方式,為該應用程式啟動新的 Linux 處理程序。 + +預設會以相同的處理程序和執行緒 (稱為「主要」執行緒) 執行相同應用程式的所有元件。 +如果應用程式元件啟動且已有該應用程式的處理程序存在 (由於應用程式還有另一個元件存在),那麼元件會在該處理程序中啟動,並使用相同的執行緒執行。 + +不過,您可以安排應用程式中的不同元件以個別處理程序執行,還可以為任何處理程序建立額外的執行緒。 + +

+ +

本文件說明處理程序和執行緒如何在 Android 應用程式中運作。

+ + +

處理程序

+ +

在預設情況下,系統會以相同的處理程序執行相同應用程式的所有元件,而且大部分應用程式都是如此。 +不過,如果您需要控制特定元件所屬的處理程序,可以在宣示說明檔案中這麼做。 +

+ +

每種元件元素 — {@code +<activity>}{@code +<service>}{@code +<receiver>}{@code +<provider>} — 的宣示說明項目都支援 {@code android:process} 屬性,這項屬性能指定元件應在哪個處理程序執行。 +您可以設定此屬性讓每個元件都以自己的處理程序執行,或只讓當中的部分元件共用同一處理程序。 +您也可以設定 +{@code android:process},讓不同應用程式的元件以相同的處理程序執行,只要這些應用程式分享相同的 Linux 使用者 ID 並以相同的憑證簽署。 + +

+ +

{@code +<application>} 元素也支援 {@code android:process} 屬性,以設定要套用到所有元件的預設值。 +

+ +

Android 可能會在記憶體不足且需要其他處理程序更立即為使用者提供服務時,決定關閉處理程序。 +使用已終止的處理程序執行的應用程式元件會因此而終結。 +當再次有工作需要執行時,就會為這些元件再次啟動處理程序。 +

+ +

Android 系統會將處理程序對使用者的相對重要性加權,以決定要終止的處理程序。 +例如,相較於代管可見 Activity 的處理程序,系統較容易關閉代管已不在螢幕上顯示的 Activity 的處理程序。 +因此,是否要終止處理程序,取決於以該處理程序執行中的元件狀態。 +如要瞭解用於決定是否終止處理程序的規則,請參閱下文。 +

+ + +

處理程序生命週期

+ +

Android 系統會儘可能持續維護處理程序,但最終仍必須移除舊的處理程序,以便回收記憶體供新的或更重要的處理程序使用。 +系統會根據以該處理程序執行的元件和那些元件的狀態,將每個處理程序放入「重要性階層」,藉此決定要保留以及要終止的處理程序。 + + +重要性最低的處理程序會最先遭到終止,接著是重要性次低的處理程序,依此類推,視需要收回系統資源。 + +

+ +

重要性階層共有五個層級。下方清單依照重要性的順序列出不同類型的處理程序 (第一個處理程序為「最重要」且會「最後終止」): + +

+ +
    +
  1. 前景處理程序 +

    這種處理程序是指使用者目前執行的工作所需的處理程序。針對下列任一情況,系統或將處理程序視為位於前景中: +

    + +
      +
    • 使用者正與其代管的 {@link android.app.Activity} 互動 (已呼叫 {@link +android.app.Activity} 的 {@link android.app.Activity#onResume onResume()} 方法)。 +
    • + +
    • 其代管的 {@link android.app.Service} 已繫結至正在與使用者互動的 Activity。 +
    • + +
    • 其代管的 {@link android.app.Service} 正「在前景」執行中 (服務已呼叫 {@link android.app.Service#startForeground startForeground()})。 + + +
    • 其代管的 {@link android.app.Service} 正在執行本身的其中一個生命週期回呼 ({@link android.app.Service#onCreate onCreate()}、{@link android.app.Service#onStart onStart()} 或 {@link android.app.Service#onDestroy onDestroy()})。 + +
    • + +
    • 其代管的 {@link android.content.BroadcastReceiver} 正在執行本身的 {@link +android.content.BroadcastReceiver#onReceive onReceive()} 方法。
    • +
    + +

    一般來說,在任何指定時間內只會有幾個前景處理程序存在。只有在記憶體過低而全都無法繼續執行時,才會採取這最後的手段來終止它們。 +在這種情況下,裝置通常已達到記憶體分頁處理狀態,因此必須終止一些前景處理程序,才能讓使用者介面保持回應。 + +

  2. + +
  3. 可見處理程序 +

    這種處理程序是指沒有任何前景元件的處理程序,但仍會影響使用者在螢幕上看見的內容。 +針對下列任一情況,系統會將處理程序視為可見: +

    + +
      +
    • 其代管的 {@link android.app.Activity} 不在前景中,但使用者仍可看見 (已呼叫它的 {@link android.app.Activity#onPause onPause()} 方法)。 +例如,如果前景 Activity 啟動的對話方塊允許在它身後看見先前的 Activity,就會發生這種情況。 + +
    • + +
    • 其代管的 {@link android.app.Service} 已繫結至可見 (或前景) Activity。 +
    • +
    + +

    可見處理程序相當重要而且不會遭到終止,除非系統為了讓所有前景處理程序維持執行而必須終止這類處理程序。 +

    +
  4. + +
  5. 服務處理程序 +

    這種處理程序是指正在執行已使用 {@link +android.content.Context#startService startService()} 方法啟動的服務的處理程序;此處理程序不屬於上述兩種較重要的類別。 +雖然服務處理程序是間接繫結至使用者所見內容,但通常會執行使用者重視的工作 (例如,在背景中播放音樂,或下載網路上的資料),因此除非記憶體不足,無法讓所有前景與可見處理程序保持執行,否則系統會讓這類處理程序繼續執行。 + + +

    +
  6. + +
  7. 背景處理程序 +

    這種處理程序會保留使用者目前看不見的 Activity (已呼叫 Activity 的 +{@link android.app.Activity#onStop onStop()} 方法)。這些處理程序會間接影響使用者體驗,且系統能隨時將其終止,藉此回收記憶體以供前景、可見或服務處理程序使用。 + + +通常會有許多背景處理程序處於執行中,因此會將它們放在 LRU (最近最少使用) 清單中,以確保在最後才將包含使用者最近最常見 Activity 的處理程序終止。 + +如果 Activity 正確實作其生命週期方法並儲存其目前狀態,終止其處理程序不會對使用者體驗造成任何可察覺的影響,原因是當使用者瀏覽回 Activity 時,該 Activity 會還原它的所有可見狀態。 + + +如要進一步瞭解如何儲存及還原狀態,請參閱 Activity。 +

    +
  8. + +
  9. 空白處理程序 +

    這種處理程序是指未保留任何使用中應用程式元件的處理程序。讓這類處理程序保持有效的唯一目的是將其用於快取,以改善元件下次執行時所需的啟動時間。 + +系統通常會終止這些處理程序,以平衡處理程序快取與底層核心快取之間的整體系統資源。 +

    +
  10. +
+ + +

Android 會根據在處理程序中目前處於使用中的元件重要性,將處理程序盡量排在最高層級。 +例如,如果處理程序代管一項服務和一個可見 Activity,此處理程序的會排為可見處理程序,而不是服務處理程序。 +

+ +

此外,還可能因為它有其他相依處理程序,而導致處理程序的排名提升:為另一個處理程序提供服務的處理程序,其排名絕不能低於為其提供服務的處理程序。 + +例如,如果處理程序 A 的內容供應程式為處理程序 B 中的用戶端提供服務,或處理程序 A 中的服務繫結至處理程序 B 中的元件,則系統至少會將處理程序 A 視為和處理程序 B 一樣重要。 + +

+ +

由於執行服務的處理程序排名會比包含背景 Activity 的處理程序排名高,因此初始化長時間執行操作的 Activity 可能適合啟動該操作的服務,而不只是建立工作者執行緒 — 特別是該操作可能會比 Activity 持久。例如,將圖片上傳至網站 Activity 應該啟動要執行上傳的服務,如果使用者離開 Activity,上傳處理程序也能在背景中繼續進行。如果使用服務,不論 Activity 發生什麼情況,都可保證該操作的優先順序至少會是「服務處理程序」。 + + + + + +廣播接收器應該採用服務,而不是只在執行緒中放置時間耗用操作,也是相同的理由。 +

+ + + + +

執行緒

+ +

當應用程式啟動時,系統會為執行該應用程式建立一個稱為「主要」的執行緒。 +這個執行緒非常重要,原因是它負責將事件分配給適當的使用者介面小工具,包括繪製事件。 +您的應用程式與 Android UI 工具組中的元件 ({@link +android.widget} 和 {@link android.view} 中的元件) 互動時,也需要使用這個執行緒。 +因此,主要執行緒有時也稱為 UI 執行緒。 +

+ +

系統「不會」為每個元件執行個體建立個別的執行緒。以相同處理程序執行的所有元件都是利用 UI 執行緒來具現化,而且都是由該執行緒分配系統呼叫的每個元件。 + +因此,回應系統回呼的方法 (例如報告使用者動作的 {@link android.view.View#onKeyDown onKeyDown()} 或生命週期回呼方法) 一律會以該處理程序的 UI 執行緒執行。 + +

+ +

例如,當使用者輕觸畫面上的按鈕時,您應用程式的 UI 執行緒會將輕觸事件分配給小工具,接著設定其按下狀態並對事件佇列張貼失效要求。 + +UI 執行緒會將要求從佇列中移除,然後通知小工具應重新繪製本身。 +

+ +

當您的應用程式密集執行作業以回應使用者互動時,除非您適當實作應用程式,否則這種單一執行緒模型會降低效能。 +具體來說,假設一切都在 UI 執行緒中進行,如果執行像是網路存取或資料庫查詢的長時間操作,將會封鎖整個 UI。當執行緒遭到封鎖時,會無法分配任何事件 (包括繪製事件)。 + + +從使用者的觀點來看,應用程式似乎閒置不動。 +更糟的是,如果 UI 執行緒遭到封鎖長達數秒 (目前約為 5 秒),就會向使用者顯示的「應用程式沒有回應」(ANR) 對話方塊。 + +使用者接著會決定結束您的應用程式,並可能因感到不滿而將其解除安裝。 +

+ +

此外,Andoid UI 工具組「並非」安全執行緒,因此請勿透過工作者執行緒操縱 UI — 使用者介面的所有操縱作業都必須從 UI 執行緒來執行。 + +基於上述原因,Android 的單一執行緒模型只有兩項簡單規則:

+ +
    +
  1. 不要封鎖 UI 執行緒 +
  2. 不要從 UI 執行緒以外的位置存取 Android UI 工具組 +
+ +

工作者執行緒

+ +

因為上述的單一執行緒模型,所以不封鎖 UI 執行緒對於應用程式 UI 的回應能力至關重要。 +如果您有不會立即執行的操作,請務必以不同的執行緒 (「背景」或「工作者」執行緒) 執行這類操作。 + +

+ +

例如,以下是從個別執行緒下載圖片並顯示在 {@link android.widget.ImageView} 的部分點擊接聽器程式碼: +

+ +
+public void onClick(View v) {
+    new Thread(new Runnable() {
+        public void run() {
+            Bitmap b = loadImageFromNetwork("http://example.com/image.png");
+            mImageView.setImageBitmap(b);
+        }
+    }).start();
+}
+
+ +

首先,由於這會建立新的執行緒來處理網路操作,所以看起來似乎可以正常運作。 +不過,它違反單一執行緒模型的第二項規則:「不要從 UI 執行緒以外的位置存取 Android UI 工具組」— 這個範例修改工作者執行緒中的 {@link +android.widget.ImageView},而不是 UI 執行緒。 +這樣會產生未定義且預期外的行為,不但難以追蹤且耗費時間。 +

+ +

為修正這個問題,Android 提供數種可從其他執行緒存取 UI 執行緒的方法。 +以下是可協助修正此問題的方法清單:

+ +
    +
  • {@link android.app.Activity#runOnUiThread(java.lang.Runnable) +Activity.runOnUiThread(Runnable)}
  • +
  • {@link android.view.View#post(java.lang.Runnable) View.post(Runnable)}
  • +
  • {@link android.view.View#postDelayed(java.lang.Runnable, long) View.postDelayed(Runnable, +long)}
  • +
+ +

例如,您可以使用 {@link +android.view.View#post(java.lang.Runnable) View.post(Runnable)} 方法來修正上述程式碼:

+ +
+public void onClick(View v) {
+    new Thread(new Runnable() {
+        public void run() {
+            final Bitmap bitmap = loadImageFromNetwork("http://example.com/image.png");
+            mImageView.post(new Runnable() {
+                public void run() {
+                    mImageView.setImageBitmap(bitmap);
+                }
+            });
+        }
+    }).start();
+}
+
+ +

現在這個實作才是安全執行緒:雖然從不同的執行緒完成網路操作,但卻是從 UI 執行緒操縱 {@link android.widget.ImageView}。 +

+ +

不過,隨著操作複雜度日益增加,這種程式碼也會變得複雜且難以維護。 +如要利用工作者執行緒處理更複雜的互動,您可能要考慮在工作者執行緒中使用 {@link android.os.Handler},以處理從 UI 執行緒傳送的訊息。 + +最佳解決方案也許是擴充 {@link android.os.AsyncTask} 類別,將必須與 UI 互動的工作者執行緒工作執行簡化。 +

+ + +

使用 AsyncTask

+ +

{@link android.os.AsyncTask} 可讓您透過使用者介面執行非同步工作。 +它會以工作者執行緒執行封鎖操作,然後將結果發行在 UI 執行緒,完全不需要您自行處理執行緒和/或處理常式。 +

+ +

使用方法是您必須要有子類別 {@link android.os.AsyncTask},並實作以背景執行緒集區執行的 {@link +android.os.AsyncTask#doInBackground doInBackground()} 回呼方法。 +如要更新您的 UI,請實作 {@link +android.os.AsyncTask#onPostExecute onPostExecute()} 來傳送 {@link +android.os.AsyncTask#doInBackground doInBackground()} 的結果,然後以 UI 執行緒執行,如此您才能安全地更新 UI。 +接著,您可以從 UI 執行緒呼叫 {@link android.os.AsyncTask#execute execute()} 來執行該工作。 +

+ +

例如,您可以使用 {@link android.os.AsyncTask} 這種方法實作先前的範例: +

+ +
+public void onClick(View v) {
+    new DownloadImageTask().execute("http://example.com/image.png");
+}
+
+private class DownloadImageTask extends AsyncTask<String, Void, Bitmap> {
+    /** The system calls this to perform work in a worker thread and
+      * delivers it the parameters given to AsyncTask.execute() */
+    protected Bitmap doInBackground(String... urls) {
+        return loadImageFromNetwork(urls[0]);
+    }
+    
+    /** The system calls this to perform work in the UI thread and delivers
+      * the result from doInBackground() */
+    protected void onPostExecute(Bitmap result) {
+        mImageView.setImageBitmap(result);
+    }
+}
+
+ +

現在 UI 很安全,而且程式碼變得更簡單,這是因為它將工作分成兩部分,一部分應在工作者執行緒上完成,而另一部分應在 UI 執行緒上完成。 +

+ +

建議您參閱 {@link android.os.AsyncTask} 參考資料,以全面瞭解如何使用此類別;以下是其如何運作的快速總覽: +

+ +
    +
  • 您可以使用泛型來指定參數類型、進度值以及工作的最終值 +
  • +
  • {@link android.os.AsyncTask#doInBackground doInBackground()} 方法會在工作者執行緒中自動執行 +
  • +
  • {@link android.os.AsyncTask#onPreExecute onPreExecute()}、{@link +android.os.AsyncTask#onPostExecute onPostExecute()} 和 {@link +android.os.AsyncTask#onProgressUpdate onProgressUpdate()} 全都是透過 UI 執行緒呼叫。
  • +
  • {@link android.os.AsyncTask#doInBackground doInBackground()} 傳回的值會傳送至 +{@link android.os.AsyncTask#onPostExecute onPostExecute()}
  • +
  • 您隨時都可用 {@link +android.os.AsyncTask#doInBackground doInBackground()} 呼叫 {@link android.os.AsyncTask#publishProgress publishProgress()},以便透過 UI 執行緒執行 {@link +android.os.AsyncTask#onProgressUpdate onProgressUpdate()}。
  • +
  • 您可以從任何執行緒隨時取消工作
  • +
+ +

注意:使用工作者執行緒可能會遇到的另一個問題是,由於執行階段設定變更 (例如使用者變更螢幕方向時) 使您的 Activity 意外重新啟動,而可能會終止您的工作者站執行緒。 + +請參閱 Shelves 範例應用程式的原始程式碼,以瞭解如何在遇到其中一種重新啟動情況時保留您的工作,以及如何在 Activity 遭到終止時適當取消該工作。 + +

+ + +

安全執行緒方法

+ +

在某些情況下,您實作的方法可能是從多個執行緒呼叫,因此務必要撰寫成安全執行緒。 +

+ +

這種情況主要發生在可從遠端呼叫的方法,例如已繫結服務中的方法。當在源自執行 {@link android.os.IBinder IBinder} 的相同處理程序中實作 {@link android.os.IBinder} 方法上的呼叫時,該方法是以呼叫端的執行緒執行。 + + + +不過,當呼叫源自另一個處理程序時,會從系統在相同處理程序中當成 {@link android.os.IBinder +IBinder} (未以處理程序的 UI 執行緒執行) 維護的執行緒集區,以選擇的執行緒來執行該方法。例如,雖然服務的 +{@link android.app.Service#onBind onBind()} 方法可從服務處理程序的 UI 執行緒呼叫,但以 {@link android.app.Service#onBind +onBind()} 傳回的物件實作的方法會從集區中的執行緒呼叫。 + +由於服務能有多個用戶端,同時也能有多個集區執行緒採用相同的 +{@link android.os.IBinder IBinder} 方法。因此 {@link android.os.IBinder +IBinder} 方法必須實作為安全執行緒。

+ +

同樣地,內容供應程式能接收源自其他處理程序的資料要求。 +雖然 {@link android.content.ContentResolver} 與 {@link android.content.ContentProvider} +類別會隱藏如何管理處理程序間通訊的詳細資料,但回應這些要求的 {@link +android.content.ContentProvider} 方法 — {@link +android.content.ContentProvider#query query()}、{@link android.content.ContentProvider#insert +insert()}、{@link android.content.ContentProvider#delete delete()}、{@link +android.content.ContentProvider#update update()} 和 {@link android.content.ContentProvider#getType +getType()} — 是在內容供應程式的處理程序中從執行緒集區呼叫,而不是該處理程序的 UI 執行緒。 +由於可能會同時有任意數目的執行緒呼叫這些方法,因此它們也要實作為安全執行緒。 +

+ + +

處理程序間通訊

+ +

Android 提供一項使用遠端程序呼叫 (RPC) 進行處理程序間通訊 (IPC) 的機制,RPC 是指 (以另一個處理程序) 從遠端執行由 Activity 或其他應用程式元件呼叫的方法,再加上要傳回給呼叫端的任何結果。 + + +這需要將方法呼叫與其資料分解成作業系統能夠瞭解的程度,將它從本機處理程序與位址空間傳輸到遠端處理程序與位址空間,然後再重新組合和重新實作呼叫。 + +接著,再以相反的方向傳輸傳回值。 +Android 提供進行這些 IPC 交易的所有程式碼,讓您可以專心定義及實作 RPC 程式設計介面。 +

+ +

如要執行 IPC,您的應用程式必須使用 {@link +android.content.Context#bindService bindService()} 來繫結至服務。如需詳細資訊,請參閱服務開發人員指南。

+ + + diff --git a/docs/html-intl/intl/zh-tw/guide/components/recents.jd b/docs/html-intl/intl/zh-tw/guide/components/recents.jd new file mode 100644 index 0000000000000000000000000000000000000000..d56c12c0e87bbc8a00d91f4b1a243337a3511e04 --- /dev/null +++ b/docs/html-intl/intl/zh-tw/guide/components/recents.jd @@ -0,0 +1,256 @@ +page.title=總覽畫面 +page.tags="recents","overview" + +@jd:body + +
+
+ +

本文件內容

+
    +
  1. 新增工作到總覽畫面 +
      +
    1. 使用意圖旗標來新增工作
    2. +
    3. 使用 Activity 屬性來新增工作
    4. +
    +
  2. +
  3. 移除工作 +
      +
    1. 使用 AppTask 類別來移除工作
    2. +
    3. 保留結束的工作
    4. +
    +
  4. +
+ +

重要類別

+
    +
  1. {@link android.app.ActivityManager.AppTask}
  2. +
  3. {@link android.content.Intent}
  4. +
+ +

程式碼範例

+
    +
  1. 以文件為中心的應用程式
  2. +
+ +
+
+ +

總覽畫面 (也被稱為最近畫面、最近工作清單,或最近的應用程式) 是系統層級的 UI,可以列出最近存取的 Activity工作。 + +使用者可以透過清單導覽並選擇要繼續的工作,或是使用者可以滑動的方式從清單移除工作。 + +使用 Android 5.0 版本 (API 級別 21),相同 Activity (包含不同文件) 的多個執行個體可以在總覽畫面中顯示為工作。 +例如:對多份 Google 文件,Google 雲端硬碟能讓每份文件都對應一個工作。 +每份文件在總覽畫面中都會顯示為工作。 +

+ + +

圖 1.總覽畫面會顯示三份 Google 雲端硬碟文件,每份都分別顯示為一項工作。 +

+ +

一般而言,您應該允許系統定義如何在總覽畫面中呈現工作與 Activity,而且不需要修改此行為。 +然而,您的應用程式可以決定要如何與在何時於總覽畫面中顯示 Activity。 + +{@link android.app.ActivityManager.AppTask} 類別讓您可以管理工作,而 {@link android.content.Intent} 類別的 Activity 旗標可以讓您指定何時從總覽畫面新增或移除 Activity。 + +此外, +<activity> 屬性可以讓您設定宣示說明中的行為。

+ +

新增工作到總覽畫面

+ +

使用 {@link android.content.Intent} 類別的旗標可以新增工作,該工作針對何時與如何在總覽畫面中開啟或重新開啟文件,可給予更多控制權。 +當您使用 +<activity>屬性時,您可以選擇永遠在新工作開啟文件或對文件重複使用現有工作。 + +

+ +

使用意圖旗標來新增工作

+ +

當您建立 Activity 的新文件時,您可以呼叫 + {@link android.app.ActivityManager.AppTask} 類別的 {@link android.app.ActivityManager.AppTask#startActivity(android.content.Context, android.content.Intent, android.os.Bundle) startActivity()}方法,傳送啟動 Activity 的意圖至新文件。 + +如要插入邏輯中斷,讓系統可以將您的 Activity 當作總覽視窗中的新工作,傳送啟動 Activity 的 {@link android.content.Intent}其 {@link android.content.Intent#addFlags(int) addFlags()} 方法中的 {@link android.content.Intent#FLAG_ACTIVITY_NEW_DOCUMENT} 旗標。 + + +

+ +

注意:{@link android.content.Intent#FLAG_ACTIVITY_NEW_DOCUMENT} 旗標會取代 {@link android.content.Intent#FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET} 旗標,後者已從 Android 5.0 (API 級別 21) 起失效。 + +

+ +

如果您在建立新文件時設定 {@link android.content.Intent#FLAG_ACTIVITY_MULTIPLE_TASK} 旗標,則系統永遠會在建立新工作時也在根目錄建立目標 Activity。此設定允許可以在一個以上的工作中開啟相同的文件。 + +下列程式碼示範主要 Activity 如何處理: +

+ +

+DocumentCentricActivity.java

+
+public void createNewDocument(View view) {
+      final Intent newDocumentIntent = newDocumentIntent();
+      if (useMultipleTasks) {
+          newDocumentIntent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
+      }
+      startActivity(newDocumentIntent);
+  }
+
+  private Intent newDocumentIntent() {
+      boolean useMultipleTasks = mCheckbox.isChecked();
+      final Intent newDocumentIntent = new Intent(this, NewDocumentActivity.class);
+      newDocumentIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
+      newDocumentIntent.putExtra(KEY_EXTRA_NEW_DOCUMENT_COUNTER, incrementAndGet());
+      return newDocumentIntent;
+  }
+
+  private static int incrementAndGet() {
+      Log.d(TAG, "incrementAndGet(): " + mDocumentCounter);
+      return mDocumentCounter++;
+  }
+}
+
+ +

注意:與 {@code FLAG_ACTIVITY_NEW_DOCUMENT} 旗標一起啟動的 Activity 務必要在宣示說明中設定 {@code android:launchMode="standard"} 屬性值 (預設)。 + +

+ +

當主要 Activity 啟動新的 Activity 時,系統會透過現有工作搜尋其意圖和 Activity 意圖元件名稱及意圖資料相同的 Activity。 +如果找不到工作,或意圖已包含 {@link android.content.Intent#FLAG_ACTIVITY_MULTIPLE_TASK} 旗標,則會建立新的工作並使用 Activity 做為其根目錄。 + +如果找到工作,則會將該工作帶到前面並傳送新的意圖到 {@link android.app.Activity#onNewIntent onNewIntent()}。 +新的 Activity 會取得意圖並在總覽視窗中建立新的文件,如下列範例所示: + +

+ +

NewDocumentActivity.java +

+
+@Override
+protected void onCreate(Bundle savedInstanceState) {
+    super.onCreate(savedInstanceState);
+    setContentView(R.layout.activity_new_document);
+    mDocumentCount = getIntent()
+            .getIntExtra(DocumentCentricActivity.KEY_EXTRA_NEW_DOCUMENT_COUNTER, 0);
+    mDocumentCounterTextView = (TextView) findViewById(
+            R.id.hello_new_document_text_view);
+    setDocumentCounterText(R.string.hello_new_document_counter);
+}
+
+@Override
+protected void onNewIntent(Intent intent) {
+    super.onNewIntent(intent);
+    /* If FLAG_ACTIVITY_MULTIPLE_TASK has not been used, this activity
+    is reused to create a new document.
+     */
+    setDocumentCounterText(R.string.reusing_document_counter);
+}
+
+ + +

使用 Activity 屬性來新增工作

+ +

Activity 也可以在宣示說明中指定為永遠啟動新工作,這可透過使用<activity> 屬性的 +{@code android:documentLaunchMode} 達成。 + +此屬性有四個值,在使用者使用應用程式開啟文件時,會產生下列效果: +

+ +
+
"{@code intoExisting}"
+
Activity 會對文件重複使用現有的工作。設定 +{@link android.content.Intent#FLAG_ACTIVITY_NEW_DOCUMENT} 旗標,但「不」設定 +{@link android.content.Intent#FLAG_ACTIVITY_MULTIPLE_TASK} 旗標,會與上述使用意圖旗標來新增工作達到相同效果。 +
+ +
"{@code always}"
+
Activity 會為文件建立新的工作,就算文件已開始也會建立新的工作。使用此值與設定 {@link android.content.Intent#FLAG_ACTIVITY_NEW_DOCUMENT} 與 {@link android.content.Intent#FLAG_ACTIVITY_MULTIPLE_TASK} 旗標會達到相同效果。 + +
+ +
"{@code none”}"
+
Activity 不會為文件建立新的工作。總覽視窗會將 Activity 當作預設:會顯示應用程式的單一工作,該工作會從使用者最後呼叫的 Activity 繼續。 + +
+ +
"{@code never}"
+
Activity 不會為文件建立新的工作。設定此值會覆寫 {@link android.content.Intent#FLAG_ACTIVITY_NEW_DOCUMENT} 與 {@link android.content.Intent#FLAG_ACTIVITY_MULTIPLE_TASK} 旗標的行為,如果任一個已於意圖中設定,總覽視窗會顯示應用程式的單一工作,該工作會從使用者最後呼叫的 Activity 繼續。 + + + +
+
+ +

注意:如果值不是 {@code none} 與 {@code never},則 Activity 必須使用 {@code launchMode="standard"} 定義。 +如果沒有指定此屬性,則會使用 +{@code documentLaunchMode="none"}。

+ +

移除工作

+ +

依照預設,當 Activity 結束時,會自動將文件工作從總覽畫面移除。 +您可以使用 {@link android.app.ActivityManager.AppTask} 類別,搭配 {@link android.content.Intent} 旗標或 +<activity> 屬性,來覆寫此行為。 +

+ +

您可以完全從總覽畫面排除工作,設定方式為將 +<activity> 屬性的 +{@code android:excludeFromRecents} 設為 {@code true}。 +

+ +

您可以設定應用程式可以納入總覽視窗的最大工作數目,方法是對 <activity> 屬性 {@code android:maxRecents} 設定整數值。 + + +預設值為 16。當達到工作的最大值時,會從總覽視窗移除最近最少使用的工作。 +{@code android:maxRecents} 的最大值是 50 (在低記憶體裝置上是 25);數值不可以小於 1。 +

+ +

使用 AppTask 類別來移除工作

+ +

在總覽畫面建立新工作的 Activity 中,您可以指定何時移除工作與結束所有相關 Activity,方法為呼叫{@link android.app.ActivityManager.AppTask#finishAndRemoveTask() finishAndRemoveTask()} 方法。 + +

+ +

NewDocumentActivity.java +

+
+public void onRemoveFromRecents(View view) {
+    // The document is no longer needed; remove its task.
+    finishAndRemoveTask();
+}
+
+ +

注意:使用 +{@link android.app.ActivityManager.AppTask#finishAndRemoveTask() finishAndRemoveTask()} 方法覆寫 {@link android.content.Intent#FLAG_ACTIVITY_RETAIN_IN_RECENTS} 標籤的使用,會在下面討論。 + +

+ +

保留結束的工作

+ +

如果您要保留總覽畫面中的工作 (就算其 Activity 已結束), 方法為傳送啟動 Activity 的意圖其 {@link android.content.Intent#addFlags(int) addFlags()} 方法中的 +{@link android.content.Intent#FLAG_ACTIVITY_RETAIN_IN_RECENTS} 旗標。 +

+ +

DocumentCentricActivity.java +

+
+private Intent newDocumentIntent() {
+    final Intent newDocumentIntent = new Intent(this, NewDocumentActivity.class);
+    newDocumentIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT |
+      android.content.Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS);
+    newDocumentIntent.putExtra(KEY_EXTRA_NEW_DOCUMENT_COUNTER, incrementAndGet());
+    return newDocumentIntent;
+}
+
+ +

如要達到相同效果,可以設定 +<activity> 屬性的 +{@code android:autoRemoveFromRecents} 為 {@code false}。 +對文件 Activity 的預設值為 {@code true},對定期 Activity 的預設值則為 {@code false}。 +如同之前的討論,使用此屬性可以覆寫 {@link android.content.Intent#FLAG_ACTIVITY_RETAIN_IN_RECENTS} 旗標。 +

+ + + + + + + diff --git a/docs/html-intl/intl/zh-tw/guide/components/services.jd b/docs/html-intl/intl/zh-tw/guide/components/services.jd new file mode 100644 index 0000000000000000000000000000000000000000..d6a440d88dd6c8e63b794cc7d7d7ee7acc015b2d --- /dev/null +++ b/docs/html-intl/intl/zh-tw/guide/components/services.jd @@ -0,0 +1,813 @@ +page.title=服務 +@jd:body + + + + +

{@link android.app.Service} 是可以在背景中長時間執行操作的應用程式元件,且不提供使用者介面。 +也是另一個可以啟動服務的應用程式元件,就算使用者切換至其他應用程式,也會繼續在背景中執行。 + +此外,元件也可以繫結至服務,以便與其互動,甚至執行處理程序間通訊 (IPC)。 +例如,服務可能處理網路交易、播放音樂、執行檔案輸入/輸出或與內容供應程式互動,這些都可以從背景執行。 + +

+ +

服務基本上可以採取兩種形式:

+ +
+
啟動
+
服務「啟動」表示應用程式元件 (例如 Activity) 透過呼叫 +{@link android.content.Context#startService startService()} 來啟動服務。一旦啟動,服務可以無限次數地在背景中執行,就算啟動服務的元件已終結也不會影響。 +通常,已啟動的服務會執行單一操作且不會將結果傳回呼叫端。例如,服務可能會透過網路下載或上傳檔案。 + +當操作完成時,服務應該會自行終結。 +
+
繫結
+
服務「繫結」表示應用程式元件透過呼叫 {@link +android.content.Context#bindService bindService()} 來繫結至服務。已繫結的服務提供主從式介面,讓元件可以與服務互動、傳送要求、取得結果,甚至可以使用處理程序間通訊 (IPC) 來跨程序達到目的。 + +已繫結的服務伴隨另一個繫結至服務的應用程式元件執行。 +多重元件可以一次繫結至服務,但是當所有元件都取消繫結時,服務就會被終結。 +
+
+ +

雖然此文件通常分別討論服務的這兩種類別,但您的服務兩種方式都可以執行 — 可以啟動服務 (無限次數地執行) 也允許繫結。 +這僅與您是否實作兩種回呼方法而定:{@link +android.app.Service#onStartCommand onStartCommand()} 允許元件啟動服務,而 {@link +android.app.Service#onBind onBind()} 則允許繫結。 +

+ +

不論您的應用程式是否啟動、繫結或兩者都有,任何應用程式元件可以使用服務 (就算來自不同的應用程式),與任何元件可以使用 Activity 的方式相同 — 使用 {@link android.content.Intent} 啟動服務。 + +然而,您可以宣告服務為私用、位於宣示說明檔案之中,與封鎖從其他應用程式存取。 +如需更多討論資訊,請詳見在宣示說明中宣告服務。 + +

+ +

注意:服務會在其託管程序的主執行緒中執行 — 服務不會建立自己的執行緒且不會在另外的程序中執行 (除非您另行指定)。 + +這代表如果您的服務即將從事任何 CPU 密集的作業或封鎖操作 (如播放 MP3 或連線網路),您應該在服務中建立新的執行緒來執行這些工作。 + +透過使用另外的執行緒,您會降低應用程式不回應 (ANR) 錯誤的風險,且應用程式的主執行緒仍可以專務於使用者與您的 Activity 互動。 + +

+ + +

基本概念

+ + + +

如要建立服務,您必須建立 {@link android.app.Service} 的子類別 (或是其現有的子類別之一)。 +在您的實作中,如果可以,您必須覆寫某些回呼方法,這些方法負責處理服務生命週期的關鍵層面,並提供元件繫結至服務的機制。 + +您應該覆寫的最重要回呼方法為:

+ +
+
{@link android.app.Service#onStartCommand onStartCommand()}
+
當另一個元件 (如 Activity) 透過呼叫 {@link android.content.Context#startService +startService()} 來要求啟動服務時,系統會呼叫此方法。 +一旦執行此方法,服務會被啟動且可以無限次數地在背景中執行。 +如果您實作此方法,當作業完成時,您必須負責停止服務,方式為呼叫 {@link android.app.Service#stopSelf stopSelf()} 或 {@link +android.content.Context#stopService stopService()}。 +(如果您只想要提供繫結功能,您不需要實作此方法)。 +
+
{@link android.app.Service#onBind onBind()}
+
當其他元件想要與服務 (如執行 RPC) 繫結時,系統會透過呼叫 {@link android.content.Context#bindService +bindService()} 來呼叫此方法。 +在您實作此方法時,透過傳回 {@link android.os.IBinder},您必須提供用戶端可用來與服務通訊的介面。 +您必須永遠實作此方法,但如果您不想允許繫結,則您應該傳回 null。 +
+
{@link android.app.Service#onCreate()}
+
當第一次建立服務時,系統會呼叫此方法來執行一次性的設定程序 (在其呼叫 {@link android.app.Service#onStartCommand onStartCommand()} 或 +{@link android.app.Service#onBind onBind()} 之前)。 +如果已經執行服務,則不會呼叫此方法。 +
+
{@link android.app.Service#onDestroy()}
+
當不再使用服務且正在終結服務時,系統會呼叫此方法。您的服務應該實作此方法來清除任何如執行緒、註冊的接聽器,接收器等資源。 + +這會是服務接收到的最後呼叫。
+
+ +

如果透過呼叫 {@link +android.content.Context#startService startService()} (這是呼叫至 {@link +android.app.Service#onStartCommand onStartCommand()} 的結果) 讓元件啟動服務,則服務仍會保持執行狀態,直到使用 {@link android.app.Service#stopSelf()} 讓服務自行停止或透過呼叫 +{@link android.content.Context#stopService stopService()} 由其他元件停止服務。 +

+ +

如果元件呼叫 +{@link android.content.Context#bindService bindService()} 來建立服務 (且「沒有」呼叫 {@link +android.app.Service#onStartCommand onStartCommand()}),則服務會伴隨繫結至服務的元件執行。 +一旦服務與所有用戶端解除繫結時,系統會終結服務。 +

+ +

只有在記憶體不足且必須復原擁有使用者焦點的 Activity 其系統資源時,Android 系統才會強制停止服務。 +如果服務已繫結至擁有使用者焦點的 Activity,則該服務不太容易被終止,且如果服務被宣告為在前景中執行 (稍後會討論),則該服務幾乎不會被終止。 +否則,如果長時間執行於前景中啟動的服務,則系統會隨時間降低該服務在背景工作清單中的位置,且該服務會很容易被終止 — 如果已啟動您的服務,則您必須設計讓系統可以完美地處理重新啟動。 + + + +如果系統終止服務,則系統會在可以再度取得資源時立即重新啟動服務 (雖然這也會依據您從 {@link +android.app.Service#onStartCommand onStartCommand()} 傳回的值而有所不同,稍後會討論)。 +如需更多有關系統何時可能終結服務的資訊,請參閱程序與執行緒文件。 + +

+ +

在下列小節,您將看到您如何可以建立每種類型的服務與如何從不同應用程式元件使用。 +

+ + + +

在宣示說明中宣告服務

+ +

就如同 Activity (與其他元件),您必須在您應用程式的宣示說明檔案中宣告所有服務。 +

+ +

如要宣告您的服務,可以新增 {@code <service>} 元素為 {@code <application>}元素的子項。 + +例如:

+ +
+<manifest ... >
+  ...
+  <application ... >
+      <service android:name=".ExampleService" />
+      ...
+  </application>
+</manifest>
+
+ +

如需更多有關在宣示說明中宣告您服務的行系資訊,請參閱 {@code <service>} 元素。 +

+ +

您還可以在 {@code <service>} 元素中包含其他屬性,用來定義如啟動服務所需權限的屬性,與服務應該執行的程序。 + +{@code android:name} 屬性是唯一必備的屬性 — 用來指定服務的類別名稱。 +一旦您發佈應用程式,您就不應該變更此名稱,因為如果這樣做,會由於依賴明確的意圖來啟動或繫結服務 (閱讀部落格貼文、不能變更的事項),造成程式碼中斷的風險。 + + + + +

如要確認您的應用程式是安全的,當啟動或繫結您的 {@link android.app.Service} 時,永遠使用明確意圖,且不要宣告服務的意圖篩選器。 +如果允許一定程度的模糊來決定啟動的服務是重要的,您可以提供您的服務意圖篩選器,並從 {@link +android.content.Intent} 排除元件名稱,但您仍需使用 {@link +android.content.Intent#setPackage setPackage()} 設定意圖的封裝,這會提供目標服務足夠明確、避免含糊的指示。 + + +

+ +

此外,您可以確保只有您的應用程式可以使用您的服務,方法為包含 {@code android:exported} 屬性並將其設定為 {@code "false"}。 + +這可以有效地避免其他應用程式啟動您的服務,就算使用明確意圖時也是如此。 +

+ + + + +

建立已啟動的服務

+ +

已啟動的服務是由其他應用程式呼叫 {@link +android.content.Context#startService startService()} 所啟動的,由呼叫至服務的 +{@link android.app.Service#onStartCommand onStartCommand()} 方法所導致。

+ +

當服務啟動,其生命週期與啟動服務的元件無關,且服務可以無限次數地在背景中執行,就算啟動服務的元件已終結也不會影響。 + +因此,當服務的工作藉由呼叫 {@link android.app.Service#stopSelf stopSelf()} 而完成時,服務應該會自行停止,或藉由呼叫 {@link android.content.Context#stopService stopService()},其他元件也可以停止服務。 + +

+ +

如 Activity 等應用程式元件可以透過呼叫 {@link +android.content.Context#startService startService()} 與傳送用來指定服務及包含服務所要使用任何資料的 +{@link android.content.Intent},來啟動服務。服務會接收 {@link android.app.Service#onStartCommand +onStartCommand()} 方法中的此 {@link android.content.Intent}。 +

+ +

例如,假設 Activity 需要一些資料到線上資料庫。藉由傳送意圖至 {@link +android.content.Context#startService startService()},Activity 可以啟動伴隨服務並傳送要儲存的資料。 +服務會接收 {@link +android.app.Service#onStartCommand onStartCommand()} 中的意圖,連線到網際網路,並執行資料庫交易。 +當操作完成時,服務應該會自行終結。 +

+ +

注意:服務會在與應用程式相同的程序中執行,服務會在該程序中被宣告,且依照預設,會位於該應用程式的主執行緒之中。 +所以,在使用者與來自相同應用程式的 Activity 互動時,如果您的服務執行密集的操作或封鎖操作,則該服務將會降低 Activity 的效能。 + +要避免影響應用程式的效能,您應該在服務之中啟動新的執行緒。 +

+ +

傳統上,有兩種類別可以延伸為建立已啟動的服務:

+
+
{@link android.app.Service}
+
這是所有服務的基本類別。當您延伸此類別時,建立用來執行所有服務工作的新執行緒是非常重要的,因為依照預設,服務會使用您應用程式的主執行緒,這會降低任何您應用程式所執行 Activity 的效能。 + + +
+
{@link android.app.IntentService}
+
這是 {@link android.app.Service} 的子類別,會使用 worker 執行緒來處理所有的啟動要求,一次一個。 +如果您不需要您的服務同時處理多個要求,則這是最佳的選項。 +您唯一要做的就是實作 {@link +android.app.IntentService#onHandleIntent onHandleIntent()},這會接收每個起始要求的意圖 ,所以您可以在背景執行。 +
+
+ +

下列小節說明如何可以使用這些類別之一來實作您的服務。 +

+ + +

延伸 IntentService 類別

+ +

因為大多數的已啟動服務不需要同時處理多個要求(多重執行緒策略事實上是危險的),如果您使用 {@link android.app.IntentService} 類別來實作您的服務可能是最佳的方法。 + +

+ +

{@link android.app.IntentService} 會達到下列目的:

+ +
    +
  • 建立預設的 worker 執行緒,該執行緒會執行所有傳送至 {@link +android.app.Service#onStartCommand onStartCommand()} 的意圖,並與您應用程式的主執行緒有所分別。 +
  • +
  • 建立工作佇列,該佇列會一次傳送一個意圖到您的 {@link +android.app.IntentService#onHandleIntent onHandleIntent()} 實作,所以您絕對不需要擔心多重執行緒的問題。 +
  • +
  • 在所有啟動要求都已處理後,停止服務,這樣您永遠不需要呼叫 +{@link android.app.Service#stopSelf}。
  • +
  • 提供傳回 null 的 {@link android.app.IntentService#onBind onBind()} 其預設實作。 +
  • +
  • 提供傳送意圖至工作佇列的 {@link android.app.IntentService#onStartCommand +onStartCommand()} 其預設實作,然後傳送至您的 {@link +android.app.IntentService#onHandleIntent onHandleIntent()} 實作。
  • +
+ +

所有這一切都說明了您要做的就是實作 {@link +android.app.IntentService#onHandleIntent onHandleIntent()} 來完成用戶端提供的工作。 +(雖然,您也需要提供小型建構函式給服務)。

+ +

下列是 {@link android.app.IntentService} 實作的範例:

+ +
+public class HelloIntentService extends IntentService {
+
+  /**
+   * A constructor is required, and must call the super {@link android.app.IntentService#IntentService}
+   * constructor with a name for the worker thread.
+   */
+  public HelloIntentService() {
+      super("HelloIntentService");
+  }
+
+  /**
+   * The IntentService calls this method from the default worker thread with
+   * the intent that started the service. When this method returns, IntentService
+   * stops the service, as appropriate.
+   */
+  @Override
+  protected void onHandleIntent(Intent intent) {
+      // Normally we would do some work here, like download a file.
+      // For our sample, we just sleep for 5 seconds.
+      long endTime = System.currentTimeMillis() + 5*1000;
+      while (System.currentTimeMillis() < endTime) {
+          synchronized (this) {
+              try {
+                  wait(endTime - System.currentTimeMillis());
+              } catch (Exception e) {
+              }
+          }
+      }
+  }
+}
+
+ +

您需要的是:{@link +android.app.IntentService#onHandleIntent onHandleIntent()} 的建構函式與實作。

+ +

如果您決定也要覆寫其他回呼方法,如 {@link +android.app.IntentService#onCreate onCreate()}、{@link +android.app.IntentService#onStartCommand onStartCommand()} 或 {@link +android.app.IntentService#onDestroy onDestroy()},請確認呼叫超級實作,這樣 +{@link android.app.IntentService} 才可以適當處理 worker 執行緒的生命。

+ +

例如,{@link android.app.IntentService#onStartCommand onStartCommand()} 必須傳回預設的實作 (這就是意圖如何被傳送至 {@link +android.app.IntentService#onHandleIntent onHandleIntent()}): +

+ +
+@Override
+public int onStartCommand(Intent intent, int flags, int startId) {
+    Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show();
+    return super.onStartCommand(intent,flags,startId);
+}
+
+ +

除了 {@link android.app.IntentService#onHandleIntent onHandleIntent()},您不需要呼叫超級類別的唯一方法就是 {@link android.app.IntentService#onBind +onBind()} (但只有在您的服務允許繫結時才需要實作)。 +

+ +

在下一節,您會看到在延伸基本 {@link android.app.Service} 類別時,如何實作同類的服務,這需要寫更多程式碼,但是如果您需要同時處理多個啟動要求時,這可能是比較合適的處理方式。 + +

+ + +

延伸服務類別

+ +

如同您在前一節看到的,使用 {@link android.app.IntentService} 可讓您已啟動服務的實作變得非常容易。 +然而,如果您要求您的服務執行多重執行緒 (取代透過工作佇列處理啟動要求),則您可以延伸 {@link android.app.Service} 類別來處理每個意圖。 + +

+ +

為了對比之用,下列程式碼範例是 {@link +android.app.Service} 類別的實作,該類別執行與上述使用 {@link +android.app.IntentService} 範例相同的工作。也就是,對每個啟動要求,會使用 worker 執行緒來執行工作,且一次只處理一個要求。 +

+ +
+public class HelloService extends Service {
+  private Looper mServiceLooper;
+  private ServiceHandler mServiceHandler;
+
+  // Handler that receives messages from the thread
+  private final class ServiceHandler extends Handler {
+      public ServiceHandler(Looper looper) {
+          super(looper);
+      }
+      @Override
+      public void handleMessage(Message msg) {
+          // Normally we would do some work here, like download a file.
+          // For our sample, we just sleep for 5 seconds.
+          long endTime = System.currentTimeMillis() + 5*1000;
+          while (System.currentTimeMillis() < endTime) {
+              synchronized (this) {
+                  try {
+                      wait(endTime - System.currentTimeMillis());
+                  } catch (Exception e) {
+                  }
+              }
+          }
+          // Stop the service using the startId, so that we don't stop
+          // the service in the middle of handling another job
+          stopSelf(msg.arg1);
+      }
+  }
+
+  @Override
+  public void onCreate() {
+    // Start up the thread running the service.  Note that we create a
+    // separate thread because the service normally runs in the process's
+    // main thread, which we don't want to block.  We also make it
+    // background priority so CPU-intensive work will not disrupt our UI.
+    HandlerThread thread = new HandlerThread("ServiceStartArguments",
+            Process.THREAD_PRIORITY_BACKGROUND);
+    thread.start();
+
+    // Get the HandlerThread's Looper and use it for our Handler
+    mServiceLooper = thread.getLooper();
+    mServiceHandler = new ServiceHandler(mServiceLooper);
+  }
+
+  @Override
+  public int onStartCommand(Intent intent, int flags, int startId) {
+      Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show();
+
+      // For each start request, send a message to start a job and deliver the
+      // start ID so we know which request we're stopping when we finish the job
+      Message msg = mServiceHandler.obtainMessage();
+      msg.arg1 = startId;
+      mServiceHandler.sendMessage(msg);
+
+      // If we get killed, after returning from here, restart
+      return START_STICKY;
+  }
+
+  @Override
+  public IBinder onBind(Intent intent) {
+      // We don't provide binding, so return null
+      return null;
+  }
+
+  @Override
+  public void onDestroy() {
+    Toast.makeText(this, "service done", Toast.LENGTH_SHORT).show();
+  }
+}
+
+ +

如同您看到的,比起使用 {@link android.app.IntentService},這需要花更多功夫。

+ +

然而,因為您自行處理每個對 {@link android.app.Service#onStartCommand +onStartCommand()} 的呼叫,您可以同時執行多個要求。那不是這個範例要做的,但如果那是您想要的,那麼您可以針對每個要求建立新的執行緒,並立即執行 (代替等待之前的要求結束)。 + +

+ +

請注意:{@link android.app.Service#onStartCommand onStartCommand()} 方法必須傳回整數。 +整數是一個數值,說明在系統終止服務的事件中,系統該如何繼續服務 (如上述的討論,雖然您可以修改,但 {@link +android.app.IntentService} 的預設實作會替您處理)。 +從 +{@link android.app.Service#onStartCommand onStartCommand()} 傳回的值必須是下列常數之一: +

+ +
+
{@link android.app.Service#START_NOT_STICKY}
+
如果系統在 {@link android.app.Service#onStartCommand +onStartCommand()} 回傳後終止服務,除非有待決的意圖要傳送,否則請「不要」建立服務。 +在非必要時與應用程式可以簡單地重新啟動任何未完成的工作時,這是避免執行服務的最安全方式。 +
+
{@link android.app.Service#START_STICKY}
+
如果系統在 {@link android.app.Service#onStartCommand +onStartCommand()} 回傳後終止服務,請重新建立服務並呼叫 {@link +android.app.Service#onStartCommand onStartCommand()},但「不要」重新傳送最後的意圖。 +相反地,除非有待決的意圖要啟動服務傳送,否則系統會使用 null 意圖呼叫 {@link android.app.Service#onStartCommand onStartCommand()},如果有待決的意圖要啟動服務,則會傳送那些意圖。 + +這適用於媒體播放程式 (或類似服務) 這類不執行命令,但可以無次數限制執行與等待工作的服務。 +
+
{@link android.app.Service#START_REDELIVER_INTENT}
+
如果系統在 {@link android.app.Service#onStartCommand +onStartCommand()} 回傳後終止服務,請重新建立服務並使用傳送至服務的最後意圖呼叫 {@link +android.app.Service#onStartCommand onStartCommand()}。 +任何待決的意圖會反過來由後往前傳送。這適用的服務為主動執行如下載檔案等應該立即繼續的工作。 +
+
+

如需有關這些傳回值的詳細資訊,請參閱每個常數的連結參考文件。 +

+ + + +

啟動服務

+ +

您可以透過傳送 +{@link android.content.Intent} (指定要啟動的服務) 到 {@link +android.content.Context#startService startService()},從 Activity 或其他應用程式元件來啟動服務。Android 系統會呼叫服務的 {@link +android.app.Service#onStartCommand onStartCommand()} 方法並傳送 {@link +android.content.Intent} 給該方法。(絕對不要直接呼叫 {@link android.app.Service#onStartCommand +onStartCommand()})。

+ +

例如,使用明確意圖並搭配 {@link android.content.Context#startService +startService()},Activity 可以啟動前小節範例中的服務 ({@code +HelloSevice}):

+ +
+Intent intent = new Intent(this, HelloService.class);
+startService(intent);
+
+ +

{@link android.content.Context#startService startService()} 方法會立即回傳且Android 系統會呼叫服務的 {@link android.app.Service#onStartCommand +onStartCommand()} 方法。 +如果尚未開始執行服務,系統會先呼叫 {@link +android.app.Service#onCreate onCreate()},然後呼叫 {@link android.app.Service#onStartCommand +onStartCommand()}。

+ +

如果服務也不提供繫結,則與意圖一同傳送的 {@link +android.content.Context#startService startService()} 是在應用程式元件與服務間通訊的唯一模式。 +然而,如果您想要服務將結果送回,則啟動服務的用戶端會針對廣播建立 {@link android.app.PendingIntent}(搭配 {@link android.app.PendingIntent#getBroadcast getBroadcast()}) 並將其傳送至服務,該服務在啟動服務的 {@link android.content.Intent} 之中。 + + +然後服務就可使用廣播來傳送結果。 +

+ +

多個啟動服務的要求會導致多個相關呼叫至服務的 +{@link android.app.Service#onStartCommand onStartCommand()}。然而,只允許一個要求可以停止服務 (使用 {@link android.app.Service#stopSelf stopSelf()} 或 {@link +android.content.Context#stopService stopService()})。 +

+ + +

停止服務

+ +

已啟動的服務必須管理本身的生命週期。也就是,系統不會停止或終結服務,除非系統必須復原系統記憶體,而服務會在 {@link android.app.Service#onStartCommand onStartCommand()} 回傳後繼續執行。 + +所以,服務務必自我停止,可透過呼叫 {@link android.app.Service#stopSelf stopSelf()} 達成,或透過呼叫 {@link android.content.Context#stopService stopService()} 讓其他元件可以停止服務。 + +

+ +

一旦要求使用 {@link android.app.Service#stopSelf stopSelf()} 或 {@link +android.content.Context#stopService stopService()} 停止,系統會盡快終結服務。 +

+ +

然而,如果您的服務同時處理多個對 {@link +android.app.Service#onStartCommand onStartCommand()} 的要求,則在您開始處理啟動要求時,您不應該停止服務,因為您仍可能會收到新的啟動要求 (在第一個要求結束時停止可能會終止第二個要求)。 + +如要避免發生此問題,您可以使用 {@link android.app.Service#stopSelf(int)} 來確保您停止服務的要求會總是基於最新的啟動要求。 + +也就是,當您呼叫 {@link +android.app.Service#stopSelf(int)} 時,會傳送啟動要求的 ID (startId 已傳送至 {@link android.app.Service#onStartCommand onStartCommand()}) 至您相關的停止要求。 + +然而,如果服務在您可以呼叫 {@link +android.app.Service#stopSelf(int)} 之前,就收到新的啟動要求,則 ID 將不會相符,服務也不會停止。

+ +

注意:在您的應用程式完成工作時,應用程式停止其服務是非常重要的,這可以避免浪費系統資源與消耗電池電力。 +如果必要,透過呼叫 {@link +android.content.Context#stopService stopService()},其他元件可以停止服務。 +就算您啟動服務的繫結,如果曾接收對 {@link +android.app.Service#onStartCommand onStartCommand()} 的呼叫,您永遠仍必須自行停止服務。 +

+ +

如需有關服務生命週期的詳細資訊,請參閱下節管理服務的生命週期

+ + + +

建立已繫結的服務

+ +

已繫結的服務是允許應用程式元件透過呼叫 {@link +android.content.Context#bindService bindService()} 來建立繫結的服務,這是為了建立長期的連線 (一般而言,並不允許元件透過呼叫 {@link +android.content.Context#startService startService()} 來「啟動」 )。 +

+ +

當您想要透過處理程序間通訊 (IPC),與來自 Activity 的服務及您應用程式中的其他元件互動時,或是想要揭露您應用程式的特定功能給其他應用程式時,您應該建立已繫結的服務。 + +

+ +

如要建立已繫結的服務,您必須實作 {@link +android.app.Service#onBind onBind()} 回呼方法並傳回 +{@link android.os.IBinder} (這用來定義與服務溝通的介面)。接著其他應用程式元件可以呼叫 +{@link android.content.Context#bindService bindService()} 來擷取介面並開始服務上的呼叫方法。 +服務只會為了服務所繫結應用程式元件才存在, +所以當沒有繫結服務的元件時,系統會終結服務 (當服務透過 +{@link android.app.Service#onStartCommand onStartCommand()} 啟動時,您「不」需要停止已繫結的服務)。 +

+ +

如要建立已繫結的服務,您必須做的第一件事就是定義用來指定用戶端如何與服務通訊的介面。 +介於服務與用戶端間的介面必須是 {@link android.os.IBinder} 的實作,也是您服務必須從 {@link android.app.Service#onBind +onBind()} 回呼方法傳回的。 + +一旦用戶端收到 {@link android.os.IBinder},可以透過介面開始與服務互動。 +

+ +

服務一次可以繫結多個用戶端。當用戶端完成與服務互動時,會呼叫 {@link android.content.Context#unbindService unbindService()} 來取消繫結。 +一旦沒有用戶端與服務繫結,系統就會終結服務。 +

+ +

有多個方法可以實作已繫結的服務,且實作比已啟動服務更加複雜,所以會在另一份有關已繫結的服務文件中討論已繫結的服務。 + +

+ + + +

傳送通知給使用者

+ +

一旦執行,服務可以使用快顯通知狀態列通知來通知事件的使用者。

+ +

快顯通知是一個會出現在目前視窗表面上短暫時間然後消失的訊息,此時狀態列通知會在狀態列提供一個圖示與訊息,使用者可以選擇然後採取行動 (例如啟動 Activity)。 + +

+ +

通常,當已完成某些背景作業(如完成檔案下載) 且使用者現在可以採取行動時,狀態列通知是最佳的方法。 + +當使用者從擴展的檢視選取通知時,通知可以啟動 Activity (如檢視已下載檔案)。 +

+ +

如需更多資訊,請參閱快顯通知狀態列通知開發人員指南。 +

+ + + +

在前景中執行服務

+ +

前景服務是被認為使用者已主動意識到、就算在記憶體不足時系統也不會終結的服務。 +前景服務必須提供通知給狀態列,狀態列會放置在「進行中」標題之下,這表示除非服務已被停止或從前景移除,否則無法解除通知。 + + +

+ +

例如,播放來自某服務音樂的音樂播放器應該被設為在前景中執行,因為使用者明確意識到這項操作。 + +狀態列中的通知可能會指出目前播放的歌曲,並允許使用者啟動 Activity 來與音樂播放器互動。 +

+ +

如要要求您的服務在前景中執行,可以呼叫 {@link +android.app.Service#startForeground startForeground()}。此方法有兩個參數:一個整數用來唯一識別通知,以及 {@link +android.app.Notification} 供狀態列使用。 +例如:

+ +
+Notification notification = new Notification(R.drawable.icon, getText(R.string.ticker_text),
+        System.currentTimeMillis());
+Intent notificationIntent = new Intent(this, ExampleActivity.class);
+PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);
+notification.setLatestEventInfo(this, getText(R.string.notification_title),
+        getText(R.string.notification_message), pendingIntent);
+startForeground(ONGOING_NOTIFICATION_ID, notification);
+
+ +

注意:您給 {@link +android.app.Service#startForeground startForeground()} 的整數 ID 不能為 0。

+ + +

如要從前景移除服務,可以呼叫 {@link +android.app.Service#stopForeground stopForeground()}。此方法有一個布林數,表示是否同時移除狀態列通知。 +此方法「不會」停止服務。 +然而,如果您在服務仍於前景執行時停止服務,則也會移除通知。 +

+ +

如需更多有關通知的資訊,請參閱建立狀態列通知。 +

+ + + +

管理服務的生命週期

+ +

服務的生命週期比 Activity 的生命週期要簡單多了。然而,密切關注如何建立與終結服務就更重要了,因為在使用者沒有意識到的狀況下可在背景中執行服務。 + +

+ +

服務生命週期 — 從何時建立到何時終結 — 可以遵循兩種不同的路徑: +

+ +
    +
  • 已啟動的服務 +

    當其他元件呼叫 {@link +android.content.Context#startService startService()} 時,會建立服務。然後服務可無限次數執行,且必須自行停止,方法是呼叫 {@link +android.app.Service#stopSelf() stopSelf()}。 +透過呼叫 {@link android.content.Context#stopService +stopService()},其他元件可以停止服務。 +當服務停止時,系統會終結服務。

  • + +
  • 已繫結的服務 +

    當其他元件 (一個用戶端) 呼叫 {@link +android.content.Context#bindService bindService()} 時,會建立服務。然後用戶端會透過 +{@link android.os.IBinder} 介面與服務通訊。用戶端也可以呼叫 +{@link android.content.Context#unbindService unbindService()} 來切斷連線。多重用戶端可以繫結至相同的服務,但是當所有用戶端都取消繫結時,系統就會終結服務。 +(服務「不」需要自行停止)。 +

  • +
+ +

這兩種路徑不是完全獨立的。也就是,您可以繫結至已經使用 +{@link android.content.Context#startService startService()} 啟動的服務。例如,可以啟動背景音樂服務,方法是呼叫 {@link android.content.Context#startService +startService()} 並搭配可識別要播放音樂的 {@link android.content.Intent}。 +之後,使用者可能會想要透過播放器試試某些控制或取得關於目前歌曲的資訊,可以將 Activity 繫結至服務,方法為呼叫 {@link +android.content.Context#bindService bindService()}。 + +就這個的案子而言,除非所有的用戶端都取消繫結,否則 {@link +android.content.Context#stopService stopService()} 或 {@link android.app.Service#stopSelf +stopSelf()} 不會實際上停止服務。

+ + +

實作生命週期回呼

+ +

就如同 Activity,服務有您可以實作來監控服務狀態變更與在適合時段執行作業的生命週期回呼方法。 +下列服務會示範每個生命週期方法: +

+ +
+public class ExampleService extends Service {
+    int mStartMode;       // indicates how to behave if the service is killed
+    IBinder mBinder;      // interface for clients that bind
+    boolean mAllowRebind; // indicates whether onRebind should be used
+
+    @Override
+    public void {@link android.app.Service#onCreate onCreate}() {
+        // The service is being created
+    }
+    @Override
+    public int {@link android.app.Service#onStartCommand onStartCommand}(Intent intent, int flags, int startId) {
+        // The service is starting, due to a call to {@link android.content.Context#startService startService()}
+        return mStartMode;
+    }
+    @Override
+    public IBinder {@link android.app.Service#onBind onBind}(Intent intent) {
+        // A client is binding to the service with {@link android.content.Context#bindService bindService()}
+        return mBinder;
+    }
+    @Override
+    public boolean {@link android.app.Service#onUnbind onUnbind}(Intent intent) {
+        // All clients have unbound with {@link android.content.Context#unbindService unbindService()}
+        return mAllowRebind;
+    }
+    @Override
+    public void {@link android.app.Service#onRebind onRebind}(Intent intent) {
+        // A client is binding to the service with {@link android.content.Context#bindService bindService()},
+        // after onUnbind() has already been called
+    }
+    @Override
+    public void {@link android.app.Service#onDestroy onDestroy}() {
+        // The service is no longer used and is being destroyed
+    }
+}
+
+ +

注意:不像 Activity 生命週期回呼方法,您「不」需要呼叫這些回呼方法的超級類別實作。 +

+ + +

圖 2.服務生命週期。左圖顯示使用 {@link android.content.Context#startService +startService()} 建立服務的生命週期,右圖顯示使用 +{@link android.content.Context#bindService bindService()} 建立服務的生命週期。 +

+ +

透過實作這些方法,您可以監控服務生命週期中的兩個巢狀迴圈:

+ +
    +
  • 服務的整個生命發生在 {@link +android.app.Service#onCreate onCreate()} 被呼叫的時間與 {@link +android.app.Service#onDestroy} 回傳的時間之間。就像 Activity,服務於 +{@link android.app.Service#onCreate onCreate()} 中開始其初始設定,並於 {@link +android.app.Service#onDestroy onDestroy()} 中釋出所有剩餘的資訊。例如,音樂播放服務可以建立執行緒,且音樂會於 {@link +android.app.Service#onCreate onCreate()} 中播放,然後在 {@link +android.app.Service#onDestroy onDestroy()} 中停止執行緒。 + + +

    {@link android.app.Service#onCreate onCreate()} 與 {@link android.app.Service#onDestroy +onDestroy()} 方法可供所有服務呼叫,不論服務是否由 {@link android.content.Context#startService startService()} 或 {@link +android.content.Context#bindService bindService()} 所建立。 +

  • + +
  • 服務的使用生命從對 {@link +android.app.Service#onStartCommand onStartCommand()} 或 {@link android.app.Service#onBind onBind()} 的呼叫開始。 +每個方法都被遞交 {@link +android.content.Intent},也會被分別傳送至 {@link android.content.Context#startService +startService()} 或 {@link android.content.Context#bindService bindService()}。 +

    如果服務已啟動,使用生命的結束時間與整個生命結束的時間相同 (就算在 {@link android.app.Service#onStartCommand +onStartCommand()} 回傳後,服務仍在作用中)。 +如果服務已繫結,使用生命的會於 {@link +android.app.Service#onUnbind onUnbind()} 回傳時結束。

    +
  • +
+ +

注意:雖然已啟動的服務可藉由對 + {@link android.app.Service#stopSelf stopSelf()} 或 {@link +android.content.Context#stopService stopService()} 的呼叫來停止,但沒有服務的相對應回呼 (沒有 {@code onStop()} 回呼)。 +所有,除非服務已繫結至用戶端,系統會在服務停止時終結服務 — {@link +android.app.Service#onDestroy onDestroy()} 是唯一收到的回呼。 +

+ +

圖 2 說明服務的回呼方法。雖然圖示區隔由 +{@link android.content.Context#startService startService()} 所建立的服務與由 +{@link android.content.Context#bindService bindService()} 所建立的服務,但請記住,任何服務不論是如何建立的,都可能可以允許使用者繫結。 + +所以,一開始使用 {@link android.app.Service#onStartCommand +onStartCommand()} 啟動的服務 (由用戶端呼叫 {@link android.content.Context#startService startService()})仍可以接收對 {@link android.app.Service#onBind onBind()} 的呼叫 (當用戶端呼叫 +{@link android.content.Context#bindService bindService()} 時)。 +

+ +

針對建立提供已繫結的服務,如需更多詳細資訊,請參閱已繫結的服務文件,其中在管理已繫結服務的生命週期包含更多有關 {@link android.app.Service#onRebind onRebind()}回呼方法的詳細資訊。 + + +

+ + + diff --git a/docs/html-intl/intl/zh-tw/guide/components/tasks-and-back-stack.jd b/docs/html-intl/intl/zh-tw/guide/components/tasks-and-back-stack.jd new file mode 100644 index 0000000000000000000000000000000000000000..e23301d641bcf28fa13f02f5bdc077352e599436 --- /dev/null +++ b/docs/html-intl/intl/zh-tw/guide/components/tasks-and-back-stack.jd @@ -0,0 +1,578 @@ +page.title=工作和返回堆疊 +parent.title=Activity +parent.link=activities.html +@jd:body + + + + +

一個應用程式通常包含多個 Activity。每個 Activity 應根據使用者可執行的特定動作類型加以設計,且要能啟動其他 Activity。 + +例如,電子郵件應用程式可能會有一個可顯示新訊息清單的 Activity。 +當使用者選擇一則訊息,會開啟新的 Activity 以檢視該訊息。

+ +

Activity 甚至可啟動裝置上其他應用程式的 Activity。例如,如果您的應用程式想要傳送一封電子郵件訊息,您可以定義一個意圖以執行「傳送」動作並包含一些資料,像是電子郵件地址和訊息。 + +其他應用程式中宣告處理此意圖類型的 Activity 就會開啟。 +在這種情況下,意圖就是要傳送電子郵件,因此電子郵件應用程式會啟動「撰寫」Activity (如果有多個 Activity 支援相同的意圖,則系統會讓使用者選擇要使用的 Activity)。 + +電子郵件傳送後,您的 Activity 就會繼續,並將電子郵件 Activity 視為您應用程式的一部分。 +雖然 Activity 可能來自不同的應用程式,但 Android 會將兩個 Activity 放在相同的工作中,以維護使用者體驗的流暢性。 + +

+ +

工作是執行特定工作時,與使用者互動的 Activity 集合。 +Activity 會在堆疊 (返回堆疊) 中依照每個 Activity 開啟的順序加以排列。 +

+ + + +

裝置主螢幕是大多數工作開始的地方。當使用者輕觸應用程式啟動組件上的某個圖示 (或主螢幕上的捷徑 ) 時,應用程式工作會移到前景。 + +如果應用程式沒有工作 (最近未使用應用程式),則會建立新的工作,且該應用程式的「主要」Activity 會以堆疊中的根 Activity 形式開啟。 + +

+ +

當目前的 Activity 啟動另一個 Activity 時,會將新的 Activity 推到堆疊的頂端並取得焦點。 +之前的 Activity 會留在堆疊中,但已停止。Activity 停止後,系統會保留其使用者介面的目前狀態。 +當使用者按下 [返回] 按鈕,會將目前的 Activity 從堆疊頂端推出 (Activity 已終結),並繼續進行之前的 Activity (還原其 UI 之前的狀態)。 + + +堆疊中的 Activity 不會重新整理,只會從堆疊推入和推出 — 由目前 Activity 啟動時推入堆疊,而當使用者使用 [返回] 按鈕離開時推出堆疊。 + +因此,返回堆疊會以「後進先出」的物件結構進行運作。 + +圖 1 透過時間軸將此行為視覺化,以時間軸顯示 Activity 間的進度以及每個時間點的目前返回堆疊。 + +

+ + +

圖 1.顯示工作中每個新 Activity 如何將項目新增到返回堆疊。 +當使用者按下 [返回] 按鈕,目前的 Activity 將會終結,而之前的 Activity 則會繼續進行。 + +

+ + +

如果使用者持續按下 [返回],則會持續推出堆疊中的每個 Activity 以顯示之前的 Activity,直到使用者返回主螢幕 (或到工作開始時執行的 Activity)。 + + +當堆疊中的 Activity 全部移除後,工作將不再存在。

+ +
+

圖 2.兩個工作:工作 B 在前景收到使用者互動,而工作 A 在背景等待繼續。 +

+
+
+

圖 3.單一 Activity 會具現化很多次。

+
+ +

工作是一個緊密結合的單位,當使用者開始新的工作時可以移到「背景」,或透過 [首頁] 按鈕前往主螢幕。 +在背景時,工作中的所有 Activity 都會停止,但該工作的返回堆疊會保留下來 — 該工作純粹失去焦點,由另一個工作取而代之,如圖 2 所示。 + + +之後,工作可以返回「前景」,讓使用者繼續未完成的工作。 +例如,假設目前的工作 (工作 A) 的堆疊中有三個 Activity — 兩個位於目前的 Activity 下。 +使用者按下 [首頁] 按鈕,然後從應用程式啟動新的應用程式。 + +當主螢幕出現時,工作 A 會移到背景。 +新的應用程式啟動時,系統會啟動該應用程式的工作 (工作 B),該應用程式會有自己的 Activity 堆疊。 +與該應用程式互動之後,使用者會再次回到首頁,並選取原來啟動工作 A 的應用程式。現在,工作 A 移到了前景 — 堆疊中的三個 Activity 全部保持不變,而堆疊中最頂端的 Activity 則會繼續進行。 + + + +此時,使用者也能切換回工作 B,前往首頁並選取啟動該工作的應用程式圖示 (或從總覽畫面選取應用程式工作)。這是在 Android 執行多工作業的範例。 + + + +

+ +

注意:背景可以一次執行多個工作。 +不過,如果使用者同時執行多個背景工作,系統可能會開始終結背景 Activity 以復原記憶體,導致 Activity 狀態遺失。 +請參閱下列有關 Activity 狀態的章節。 +

+ +

由於返回堆疊中的 Activity 不會重新整理,如果您的應用程式允許使用者從一個以上的 Activity 中啟動特定 Activity,則會建立該 Activity 的新執行個體並推入堆疊 (而不會將 Activity 任何之前的執行個體移到最頂端)。 + + +因此,您應用程式中的一個 Activity 可能會具現化很多次 (甚至來自不同的工作),如圖 3 所示。 +也因為這樣,如果使用者使用 [返回] 按鈕瀏覽之前的資訊,Activity 的每個執行個體會依開啟的順序顯示 (每個會有自己的 UI 狀態)。 + + +不過,如果您不希望 Activity 具現化一次以上,則可以修改這個行為。 +如需詳細步驟,請參閱下文的管理工作

+ + +

摘要說明 Activity 和工作的預設行為:

+ +
    +
  • 當 Activity A 啟動 Activity B,Activity A 會停止,但系統會保留其狀態(例如捲軸位置和輸入表單的文字)。 + +如果使用者在 Activity B 按下 [返回] 按鈕,Activity A 的狀態會復原並繼續執行。 +
  • +
  • 當使用者按下 [首頁] 按鈕離開工作,目前的 Activity 會停止且其工作會移到背景。 + +系統會保留工作中所有 Activity 的狀態。如果使用者稍後選取啟動工作的啟動組件圖示繼續執行工作,工作會移到前景並在堆疊頂端繼續執行 Activity。 + +
  • +
  • 如果使用者按下 [返回] 按鈕,會將目前的 Activity 從堆疊推出並終結。 + +堆疊中之前的 Activity 會繼續進行。Activity 終結後,系統將不會保留 Activity 的狀態。 +
  • +
  • Activity 可以具現化很多次,即使來自其他工作也一樣。
  • +
+ + +
+

導覽設計

+

如需應用程式導覽如何在 Android 運作的詳細資訊,請參閱 Android 設計的導覽指南。

+
+ + +

儲存 Activity 狀態

+ +

如上所述,系統的預設行為會在 Activity 停止時保留 Activity 的狀態。 +如此一來,當使用者瀏覽之前的 Activity 時,其使用者介面的顯示方式將與離開時一樣。 +不過,您可以 — 也應該 — 使用回呼方法主動保留 Activity 的狀態,以防止 Activity 遭到終結且必須重新建立的情況。 + +

+ +

如果系統停止您其中一個 Activity (例如,當新的 Activity 開始或工作移到背景),當系統需要復原系統記憶體時,可能會完全終結該 Activity。 + +發生這種情況時,與 Activity 狀態相關的資訊都將遺失。如果發生這種情況,系統仍然知道該 Activity 位於返回堆疊中,但是當 Activity 移到堆疊頂端時,系統必須重新建立該 Activity (而不是繼續執行)。 + + +如果不想讓使用者的工作遺失,您應該在 Activity 中實作 {@link android.app.Activity#onSaveInstanceState onSaveInstanceState()} 回呼方法,主動保留該工作。 + + +

+ +

如需儲存 Activity 狀態的詳細資訊,請參閱 Activity 文件。 +

+ + + +

管理工作

+ +

如上所述,Android 管理工作和返回堆疊的方式 — 將連續啟動的所有 Activity 放在相同的工作及「後進先出」堆疊中 — 對大多數應用程式而言非常好用,而且您不需擔心 Activity 與工作關聯的方式或它們如何存在於返回堆疊中。 + + +不過,您也許會想中斷一般的行為。 +您或許會希望應用程式的 Activity 可以在啟動時開始一個新的工作 (而不是放入目前的工作中);或者當您啟動一個 Activity 時,您可能會想使用其現有的執行個體 (而不是在返回堆疊頂端建立新的執行個體);又或者當使用者離開工作時,您想要清除返回堆疊中的所有 Activity,只留下根 Activity。 + + + +

+ +

如要執行這些工作及更多工作,您可以使用 {@code <activity>} 宣示說明元素中的屬性,以及您傳送到 {@link android.app.Activity#startActivity startActivity()} 之意圖中的旗標。 + + +

+ +

就這個情況而言,您可以使用的主要 +{@code <activity>} 屬性包括:

+ + + +

而您可以使用的主要意圖旗標包括:

+ +
    +
  • {@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK}
  • +
  • {@link android.content.Intent#FLAG_ACTIVITY_CLEAR_TOP}
  • +
  • {@link android.content.Intent#FLAG_ACTIVITY_SINGLE_TOP}
  • +
+ +

在以下各節中,您將瞭解如何使用這些宣示說明屬性和意圖旗標,定義 Activity 與工作關聯的方式以及它們在返回堆疊中的行為。 +

+ +

同時,還會分開討論工作與 Activity 如何在總覽畫面表示和進行管理。 +請參閱總覽畫面以取得詳細資訊。 +一般而言,您應該允許系統定義如何在總覽畫面中呈現工作與 Activity,而且不需要修改此行為。 +

+ +

注意:大多數應用程式不應中斷 Activity 和工作的預設行為: +如果您判斷修改 Activity 的預設行為是必要的,請謹慎小心並記得測試啟動期間 Activity 的可用性,以及使用 [返回] 按鈕從其他 Activity 和工作瀏覽到此 Activity 的情況。請記得測試可能會與使用者預期的行為衝突的瀏覽行為。 + + +

+ + +

定義啟動模式

+ +

啟動模式可讓您定義 Activity 的新執行個體與目前工作關聯的方式。 +您可用兩種方法定義不同的啟動模式:

+
    +
  • 使用宣示說明檔案 +

    當您在宣示說明檔案中宣告 Activity 時,您可以指定 Activity 啟動時該如何與工作關聯。 +

  • +
  • 使用意圖旗標 +

    當您呼叫 {@link android.app.Activity#startActivity startActivity()} 時,您可以在 {@link android.content.Intent} 包含一個旗標,宣告新 Activity 應如何 (或是否) 與目前的工作關聯。 + +

  • +
+ +

因此,如果 Activity A 啟動 Activity B,Activity B 可以在其宣示說明中定義它應如何與目前的工作 (如果有) 關聯,而且 Activity A 也能要求 Activity B 應如何與目前的工作關聯。 + +如果這兩個 Activity 皆定義 Activity B 應如何與工作關聯,相較於 Activity B 的要求 (如宣示說明中所定義),會優先採用 Activity A 的要求 (如意圖中所定義)。 + +

+ +

注意:某些宣示說明檔案中提供的啟動模式可能沒有對應的意圖旗標,同樣地,某些意圖旗標提供的啟動模式無法在宣示說明中定義。 + +

+ + +

使用宣示說明檔案

+ +

當您在宣示說明檔案中宣告 Activity 時,您可以使用 {@code <activity>} 元素的 {@code +launchMode} 屬性,指定 Activity 應如何與工作關聯。 + +

+ +

{@code +launchMode} 屬性可指定應如何將 Activity 啟動至工作內的指示。 +您可以為 +launchMode 屬性指定四種不同的啟動模式: +

+ +
+
{@code "standard"} (預設模式)
+
預設。系統在啟動 Activity 的工作中建立新的執行個體,並將意圖路由至該處。 +Activity 可以具現化很多次,每個執行個體可屬於不同的工作,而且一個工作可以有多個執行個體。 +
+
{@code "singleTop"}
+
如果 Activity 的執行個體已經出現在目前工作的頂端,系統會透過呼叫其 {@link +android.app.Activity#onNewIntent onNewIntent()} 方法,將意圖路由至該執行個體,而不是建立新的 Activity 執行個體。 + +Activity 可以具現化很多次,每個執行個體可屬於不同的工作,而且一個工作可以有多個執行個體 (但僅限於返回堆疊頂端的 Activity 不是現有的 Activity 執行個體時)。 + + +

例如,假設工作的返回堆疊包含根 Activity A 及 Activity B、C 及在最頂端的 D (堆疊為 A-B-C-D;D 在最頂端)。 +類型 D Activity 的意圖抵達。 +如果 D 有預設的 {@code "standard"} 啟動模式,則會啟動新的類別執行個體,且堆疊會變成 A-B-C-D-D。不過,如果 D 啟動模式為 {@code "singleTop"},D 的現有執行個體會透過 {@link +android.app.Activity#onNewIntent onNewIntent()} 接收意圖,這是因為它位於堆疊的最頂端 — 堆疊會維持 A-B-C-D。不過,如果類型 B Activity 的意圖抵達,則 B 的新執行個體會新增到堆疊中,即使其啟動模式為 {@code "singleTop"} 也是如此。 + + + +

+

注意:建立新的 Activity 執行個體之後,使用者可按下 [返回] 按鈕,返回之前的 Activity。 +但是,如果處理新意圖的是現有的 Activity 執行個體,則使用者無法按下 [返回] 按鈕回到新意圖抵達 {@link android.app.Activity#onNewIntent +onNewIntent()} 之前的 Activity 狀態。 + + + +

+
+ +
{@code "singleTask"}
+
系統會建立新的工作並在新工作的根目錄將 Activity 具現化。不過,如果 Activity 的執行個體已經出現在其他工作中,系統會透過呼叫其 {@link +android.app.Activity#onNewIntent onNewIntent()} 方法,將意圖路由至現有的執行個體,而不是建立新的執行個體。 + +一次只能有一個 Activity 執行個體。 + +

注意:雖然 Activity 是在新工作中啟動,使用者仍可使用 [返回] 按鈕返回之前的 Activity。 +

+
{@code "singleInstance"}。
+
與 {@code "singleTask"} 一樣,差別在於系統不會將任何其他 Activity 啟動至保留執行個體的工作中。 +Activity 一律是其工作的唯一成員;使用此項目啟動的任何 Activity 會在個別的工作中開啟。 +
+
+ + +

另外一個例子,Android 瀏覽器應用程式宣告網頁瀏覽器 Activity 應永遠在自己的工作中開啟 — 透過在 {@code <activity>} 元素指定 {@code singleTask} 啟動模式。 +這表示如果您的應用程式發出開啟 Android 瀏覽器的意圖,其 Activity 不會與您的應用程式放在同一個工作中。 + + +而是會為瀏覽器啟動新的工作,或者如果瀏覽器已經有工作在背景執行,會將該工作帶出來處理新的意圖。 + +

+ +

無論 Activity 在新工作啟動或與啟動該 Activity 之 Activity 的相同工作中啟動,使用者都能使用 [返回] 按鈕返回之前的 Activity。 +不過,如果您啟動指定 {@code singleTask} 啟動模式的 Activity,如果該 Activity 的執行個體存在於背景工作中,則該工作會整個移到前景。 + +此時,返回堆疊現在包含已帶出且位於堆疊頂端之工作的所有 Activity。 + +圖 4 說明這種類型的情況。

+ + +

圖 4.顯示含有啟動模式 "singleTask" 的 Activity 如何新增到返回堆疊。 +如果 Activity 已經是背景工作的一部份且有自己的返回堆疊,則整個返回堆疊都會帶出來,位於目前工作的最頂端。 + +

+ +

如要進一步瞭解如何使用宣示說明檔案中的啟動模式,請參閱 <activity> 元素,其中會有 {@code launchMode} 屬性和可接受值的詳細說明。 + + +

+ +

注意:您使用 {@code launchMode} 屬性指定的 Activity 行為可被啟動 Activity 之意圖所含的旗標所覆寫,如下一節所述。 + +

+ + + +

使用意圖旗標

+ +

啟動 Activity 時,您可以在傳送到 {@link +android.app.Activity#startActivity startActivity()} 的意圖中包含旗標,以修改 Activity 及其工作的預設關聯。 +您可以用來修改預設行為的旗標包括: +

+ +

+

{@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK}
+
在新工作中啟動 Activity。如果工作已為您目前啟動的 Activity 執行,該工作會移到前景並復原至上個狀態,而且 Activity 會在 {@link android.app.Activity#onNewIntent onNewIntent()} 收到新的意圖。 + + +

這會產生與 {@code "singleTask"} {@code launchMode} 值相同的行為,如上節所述。 +

+
{@link android.content.Intent#FLAG_ACTIVITY_SINGLE_TOP}
+
如果現在正在啟動的 Activity 是目前的 Activity (位於返回堆疊的頂端),則現有執行個體會收到 {@link android.app.Activity#onNewIntent onNewIntent()} 呼叫,而不會建立新的 Activity 執行個體。 + + +

這會產生與 {@code "singleTop"} {@code launchMode} 值相同的行為,如上節所述。 +

+
{@link android.content.Intent#FLAG_ACTIVITY_CLEAR_TOP}
+
如果正在啟動的 Activity 已在目前的工作中執行,則不會啟動新的 Activity 執行個體,而是會終結位於其上方的所有其他 Activity,且此意圖會透過 {@link android.app.Activity#onNewIntent onNewIntent()} 傳送到繼續執行的 Activity 執行個體 (現在位於頂端)。 + + + +

沒有任何 {@code launchMode} 屬性值可以產生此行為。 +

+

{@code FLAG_ACTIVITY_CLEAR_TOP} 最常與 {@code FLAG_ACTIVITY_NEW_TASK} 搭配使用。 +一起使用時,這些旗標可以找出位於其他工作中的現有 Activity,然後將它放置於可以回應意圖的地方。 + +

+

注意:如果指定 Activity 的啟動模式為 {@code "standard"},它也會從堆疊中移除,改為啟動新的執行個體處理傳入的意圖。 + + +這是因為當啟動模式為 {@code "standard"} 時,一律會為新的意圖建立新的執行個體。 +

+
+ + + + + + +

處理親和性

+ +

親和性可指出 Activity 偏好屬於哪個工作。根據預設,相同應用程式的所有 Activity 間互相都有親和性。 +因此,根據預設,相同應用程式的所有 Activity 都偏好位於相同的工作。 +不過,您可以修改 Activity 的預設親和性。 +不同應用程式中定義的 Activity 可以共用親和性,或者相同應用程式中定義的 Activity 可以指派不同的工作親和性。 + +

+ +

您可以使用 {@code <activity>} 元素的 {@code taskAffinity} 屬性修改任何指定 Activity 的親和性。 + +

+ +

{@code taskAffinity} 屬性使用字串值,但必須與 +{@code <manifest>} 元素中宣告的預設封裝名稱不同,因為系統使用該名稱來識別應用程式的預設工作親和性。 + + + +

+ +

在兩種情況下會用到親和性:

+
    +
  • 當啟動 Activity 的意圖包含 +{@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK} 旗標。 + + +

    根據預設,新的 Activity 會啟動至 Activity (名為 {@link android.app.Activity#startActivity startActivity()}) 的工作中。 +系統會將它推入至與呼叫端相同的返回堆疊。 +不過,如果傳送至 +{@link android.app.Activity#startActivity startActivity()} 的意圖包含 {@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK} 旗標,則系統會找尋不同的工作來放置新的 Activity。 + +這通常是新工作。 +不過,它不一定要是新工作。如果現有工作中有與新 Activity 相同的親和性,Activity 會啟動至該工作中。 +如果沒有,會開始新的工作。

    + +

    如果此旗標導致 Activity 開始新的工作,而使用者按 [首頁] 按鈕離開它,必須要有方法可以讓使用者回來瀏覽這個工作。 + +有些實體 (例如,通知管理員) 總是從外部工作開始 Activity,從來不使用自己的工作,因此他們都會將 {@code FLAG_ACTIVITY_NEW_TASK} 放入傳送到 +{@link android.app.Activity#startActivity startActivity()} 的意圖。 + +如果您的 Activity 可以由外部實體呼叫且可能使用此旗標,記得要提供使用者獨立的方法回到啟動的工作,例如,透過啟動組件圖示 (工作的根 Activity 有一個 {@link android.content.Intent#CATEGORY_LAUNCHER} 意圖篩選器;請參閱下方的開始工作)。 + + + +

    +
  • + +
  • 當 Activity 的 +{@code allowTaskReparenting} 屬性設為 {@code "true"}。 +

    在這種情況下,當工作移到前景時,Activity 可以從其啟動的工作移到與其有親和性的工作。 +

    +

    例如,假設將報告所選城市天氣狀況的 Activity 定義為旅遊應用程式的一部份。 +它與相同應用程式中的其他 Activity 有相同的親和性 (預設的應用程式親和性),而且它允許與此屬性重設父代。 +當您的其中一個 Activity 開始氣象報告程式 Activity,它一開始屬於與您 Activity 相同的工作。 + +不過,當旅遊應用程式工作移到前景,氣象報告程式 Activity 就會重新指派給該工作,並在其中顯示。 +

    +
  • +
+ +

提示:如果從使用者的角度來看 {@code .apk} 檔案包含一個以上的「應用程式」,您可能會想要使用 {@code taskAffinity} 屬性對與每個「應用程式」關聯的 Activity 指派不同的親和性。 + +

+ + + +

清除返回堆疊

+ +

如果使用者離開工作一段很長的時間,系統會清除根 Activity 以外所有 Activity 的工作 +。當使用者再次回到工作,只會復原根 Activity。 +系統會有這樣的行為是因為在一段很長的時間後,使用者很可能會放棄他們之前在做的工作,並回到工作開始其他新的工作。 +

+ +

您可以使用下列一些 Activity 屬性來修改這個行為:

+ +
+
alwaysRetainTaskState +
+
如果這項屬性在工作的根 Activity 中設為 {@code "true"},則剛描述的預設行為不會發生。 +即使過了很長的一段時間,工作仍然會在堆疊保留所有的 Activity。 +
+ +
clearTaskOnLaunch
+
如果這項屬性在工作的根 Activity 中設為 {@code "true"},則剛描述的預設行為不會發生。 +即使過了很長的一段時間,工作仍然會在堆疊保留所有的 Activity。 +換句話說,它與 + +{@code alwaysRetainTaskState} 相反。即便使用者只離開工作一小段時間,使用者還是會回到工作的起始狀態。 +
+ +
finishOnTaskLaunch +
+
這項屬性與 {@code clearTaskOnLaunch} 相似,但它在單一 Activity 上作業,而不是在整個工作。 + +它也會導致任何 Activity 離開,包含根 Activity。 +如果設成 {@code "true"},Activity 只會在目前的工作階段留在此工作中。 +如果使用者離開後再回到工作,該工作將不再存在。 +
+
+ + + + +

開始工作

+ +

您可以給予 Activity 一個意圖篩選器,將 +{@code "android.intent.action.MAIN"} 設定為指定的動作, +{@code "android.intent.category.LAUNCHER"} 設定為指定的類別,以便將該 Activity 設定為工作的進入點。 +例如:

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

這類意圖篩選器可在應用程式啟動組件顯示 Activity 的圖示和標籤,讓使用者啟動 Activity 並回到 Activity 啟動後任何時間建立的工作。 + + +

+ +

第二項功能很重要:使用者必須能夠在離開工作後,使用此 Activity 啟動組件回到此工作。 +由於這個原因,兩個將 Activity 標示為一律啟動工作的啟動模式 {@code "singleTask"} 和 +{@code "singleInstance"},應只能在 Activity 有 {@link android.content.Intent#ACTION_MAIN} 和 {@link android.content.Intent#CATEGORY_LAUNCHER} 篩選器時才能使用。 + + +例如,試想如果缺少篩選器會發生什麼情況: +意圖會啟動 {@code "singleTask"} Activity、起始新工作,然後使用者會花一些時間在該工作進行作業。 +之後,使用者按下 [首頁] 按鈕。 +此工作現在會傳送到背景而且不會顯示。現在,使用者沒有辦法回到工作,這是因為應用程式啟動組件沒有代表此工作的項目。 +

+ +

在您不希望使用者返回 Activity 的情況下,將 +<activity> 元素的 +{@code finishOnTaskLaunch} 設定為 {@code "true"} (請參閱清除堆疊)。 + +

+ +

如要進一步瞭解工作和 Activity 在總覽畫面中的顯示及管理方式,請參閱總覽畫面。 + +

+ + diff --git a/docs/html-intl/intl/zh-tw/guide/index.jd b/docs/html-intl/intl/zh-tw/guide/index.jd new file mode 100644 index 0000000000000000000000000000000000000000..f7ad966d73fa68d344efed515c155f442d47f273 --- /dev/null +++ b/docs/html-intl/intl/zh-tw/guide/index.jd @@ -0,0 +1,74 @@ +page.title=Android 簡介 + +@jd:body + + + + +

Android 提供內容豐富的應用程式架構,可讓您在 Java 語言環境中建置適用於行動裝置的新穎應用程式和遊戲。 +您可以參閱左側導覽區所列的文件,進一步瞭解如何使用 Android 的各種 API 建置應用程式。 +

+ +

如果您是剛開始接觸 Android 開發環境,請務必詳閱下列有關 Android 應用程式架構的基本概念: +

+ + +
+ +
+ +

應用程式可提供多個進入點

+ +

Android 應用程式是由許多不同元件建置而成,應用程式可個別呼叫每個元件。 +例如,「Activity」可在單一畫面中顯示使用者介面,而「服務」則個別可在背景中執行作業。 + +

+ +

您可以透過某個元件使用「意圖」啟動另一個元件。您甚至可以啟動其他應用程式中的元件,例如啟動地圖應用程式的 Activity 來顯示地址。 +這個模型可為單一應用程式提供多個進入點,還能讓任何應用程式針對其他應用程式可能呼叫的動作,以使用者設定的「預設值」運作。 + +

+ + +

瞭解詳情:

+ + +
+ + +
+ +

應用程式會針對不同裝置進行調整

+ +

Android 提供的應用程式架構可視情況進行調整,讓您能夠針對不同的裝置設定提供專屬資源。 +例如,您可以針對不同的螢幕大小建立各種 XML 版面配置檔案,藉此讓系統根據目前裝置的螢幕大小決定要套用的版面配置設定。 + +

+ +

如果有應用程式功能需要特定硬體 (例如相機) 才能運作,您可以在執行階段查詢裝置功能的可用性。 +此外,您還可以視需要宣告您的應用程式所需的功能,以便讓 Google Play 商店等應用程式市集禁止使用者在不支援相關功能的裝置上安裝您的應用程式。 + +

+ + +

瞭解詳情:

+ + +
+ +
+ + + diff --git a/docs/html-intl/intl/zh-tw/guide/topics/manifest/manifest-intro.jd b/docs/html-intl/intl/zh-tw/guide/topics/manifest/manifest-intro.jd new file mode 100644 index 0000000000000000000000000000000000000000..5e42e37d91fc54598dae72ab85ec940a46f89fc8 --- /dev/null +++ b/docs/html-intl/intl/zh-tw/guide/topics/manifest/manifest-intro.jd @@ -0,0 +1,517 @@ +page.title=應用程式宣示說明 +@jd:body + + + +

+ 每個應用程式的根目錄都必須包含 AndroidManifest.xml 檔案 (名稱要一字不差)。 + 宣示說明檔案可向 Android 系統顯示應用程式的基本資訊,也就是系統在執行該應用程式的任何程式碼之前必須具備的資訊。 + + + 宣示說明可執行下列動作: +

+ +
    +
  • 為應用程式的 Java 封裝命名。 +封裝名稱可當成應用程式的唯一識別碼使用。
  • + +
  • 描述應用程式的元件 — 組成應用程式的 Activity、服務、廣播接收器和內容供應程式。 + +為實作每個元件的類別命名以及發佈類別的功能 (例如,類別可處理的 {@link android.content.Intent +Intent} 訊息)。 +這些宣告可讓 Android 系統瞭解元件為何以及可在哪些情況下啟動。 +
  • + +
  • 決定代管應用程式元件的程序。
  • + +
  • 宣告應用程式必須擁有哪些權限,才能存取 API 受保護的部分以及與其他應用程式互動。 +
  • + +
  • 宣示說明亦可宣告其他項目必須擁有哪些權限,才能與應用程式的元件互動。 +
  • + +
  • 列出可在應用程式執行時提供分析和其他資訊的 {@link android.app.Instrumentation} 類別。 +只有在應用程式開發及測試完成的情況下,宣示說明中才會顯示這些宣告;這些宣告會在應用程式發佈之前移除。 + +
  • + +
  • 宣告應用程式要求的最低 Android API 級別。 +
  • + +
  • 列出應用程式必須連結的程式庫。
  • +
+ + +

宣示說明檔案結構

+ +

+下圖顯示宣示說明檔案的一般結構和可納入其中的元素。 +每個元素和其所有屬性都會完全記錄在個別檔案中。 +如要查看任一元素的詳細資訊,只要按一下圖表中的元素名稱、圖表後方按字母順序列出的元素清單,或在他處提及的任何元素名稱。 + + + +

+ +
+<?xml version="1.0" encoding="utf-8"?>
+
+<manifest>
+
+    <uses-permission />
+    <permission />
+    <permission-tree />
+    <permission-group />
+    <instrumentation />
+    <uses-sdk />
+    <uses-configuration />  
+    <uses-feature />  
+    <supports-screens />  
+    <compatible-screens />  
+    <supports-gl-texture />  
+
+    <application>
+
+        <activity>
+            <intent-filter>
+                <action />
+                <category />
+                <data />
+            </intent-filter>
+            <meta-data />
+        </activity>
+
+        <activity-alias>
+            <intent-filter> . . . </intent-filter>
+            <meta-data />
+        </activity-alias>
+
+        <service>
+            <intent-filter> . . . </intent-filter>
+            <meta-data/>
+        </service>
+
+        <receiver>
+            <intent-filter> . . . </intent-filter>
+            <meta-data />
+        </receiver>
+
+        <provider>
+            <grant-uri-permission />
+            <meta-data />
+            <path-permission />
+        </provider>
+
+        <uses-library />
+
+    </application>
+
+</manifest>
+
+ +

+下方按字母順序列出可出現在宣示說明檔案中的所有元素。 +只有這些才是符合資格的元素,您無法新增自己的元素或屬性。 + +

+ +

+<action> +
<activity> +
<activity-alias> +
<application> +
<category> +
<data> +
<grant-uri-permission> +
<instrumentation> +
<intent-filter> +
<manifest> +
<meta-data> +
<permission> +
<permission-group> +
<permission-tree> +
<provider> +
<receiver> +
<service> +
<supports-screens> +
<uses-configuration> +
<uses-feature> +
<uses-library> +
<uses-permission> +
<uses-sdk> +

+ + + + +

檔案轉換

+ +

+某些轉換和規則通常適用於宣示說明中的所有元素與屬性: + +

+ +
+
元素
+
只有 +<manifest> 與 +<application> 是必要元素,務必顯示兩者且這些元素只能出現一次。 +雖然您至少必須顯示當中的一些元素,才能完成有意義的作業,但大部分其他元素可以出現數次或完全不出現。 + + + + +

+如果可以的話,元素還可以包含其他元素。所有值並非當成元素內的字元資料使用,而是透過屬性設定。 + +

+ +

+系統通常不會將位於相同層級的元素排序。例如, +<activity>、 +<provider> 和 +<service> 元素能以任何順序排列組合。 +( +<activity-alias> 元素是這項規則的例外狀況: +由於它是 +<activity> 的別名,因此必須跟在該元素的後面)。 + +

+ +
屬性
+
形式上而言,所有屬性均為選用性質。不過,您必須為元素指定某些屬性,才能達到其目的。 +請使用本文件當成參考指南。 +真正的選用屬性會提及在缺少規格時要使用的預設值或狀態。 + + +

除了 +<manifest> 根元素的某些屬性以外,所有屬性名稱都是以前置詞 {@code android:}為開頭,例如 {@code android:alwaysRetainTaskState}。 + +由於前置詞是通用的,因此按名稱參照屬性時,文件通常會加以省略。 + +

+ +
宣告類別名稱
+
許多元素都會對應到 Java 物件,包括應用程式本身的元素 ( +<application> 元素) 與其主要元件:Activity (<activity>)、服務 (<service>)、廣播接收器 (<receiver>) 以及內容供應程式 (<provider>)。 + + + + + + + + + + +

+如果您一如往常定義元件類別 ({@link android.app.Activity}、{@link android.app.Service}、 +{@link android.content.BroadcastReceiver} 及 {@link android.content.ContentProvider}) 般定義子類別,就會透過 {@code name} 屬性宣告子類別。 + +該名稱必須包含完整的封裝指定名稱。 +例如,{@link android.app.Service} 子類別可能會以下列格式宣告: + +

+ +
<manifest . . . >
+    <application . . . >
+        <service android:name="com.example.project.SecretService" . . . >
+            . . .
+        </service>
+        . . .
+    </application>
+</manifest>
+ +

+不過,採用速記法時,如果字串的第一個字元是句點,就會將字串附加到應用程式的封裝名稱 (如同由 +<manifest> 元素的 +package 屬性指定)。 + + +下列的指派結果會和上述相同: +

+ +
<manifest package="com.example.project" . . . >
+    <application . . . >
+        <service android:name=".SecretService" . . . >
+            . . .
+        </service>
+        . . .
+    </application>
+</manifest>
+ +

+啟動元件時,Android 會建立具名子類別的執行個體。如果未指定子類別,就會建立基本類別的執行個體。 + +

+ +
多個值
+
如果可以指定多個值,該元素幾乎會一直重複,而不是在單一元素內列出多個值。 +例如,意圖篩選器能列出數種動作: + + +
<intent-filter . . . >
+    <action android:name="android.intent.action.EDIT" />
+    <action android:name="android.intent.action.INSERT" />
+    <action android:name="android.intent.action.DELETE" />
+    . . .
+</intent-filter>
+ +
資源值
+
有些屬性的值可以供使用者查看 — 例如 Activity 的標籤和圖示。 +您必須將這些屬性的值本地化,以便從資源或主題設定這些值。 +資源值是採用下列格式表示: +

+ +

{@code @[package:]type:name}

+ +

+其中的package 名稱可以省略 (如果資源所在的封裝和應用程式相同的話), + type 是指資源類型 — 例如「字串」或「可繪項目」 — 而 + name 則是可識別特定資源的名稱。範例: + +

+ +
<activity android:icon="@drawable/smallPic" . . . >
+ +

+主題中的值會以類似的方式表示,但字首會是 '{@code ?}',而不是 '{@code @}': + +

+ +

{@code ?[package:]type:name} +

+ +
字串值
+
如果屬性值為字串,必須使用雙反斜線 ('{@code \\}')來溢出字元,例如 '{@code \\n}'表示換行字元,或 '{@code \\uxxxx}' 表示 Unicode 字元。 + +
+
+ + +

檔案功能

+ +

+下列各節說明如何在宣示說明檔案中反映部分 Android 功能。 + +

+ + +

意圖篩選器

+ +

+應用程式的核心元件 (即應用程式的 Activity、服務和廣播接收器) 是由 + 意圖啟動。意圖是一組資訊組合 ({@link android.content.Intent} 物件),用於說明要採取的動作 — 包括執行依據的資料、應執行動作的元件類別,以及其他相關的指示。 + + +Android 會找出適當的元件來回應意圖、視需要啟動元件的新執行個體,以及將意圖物件傳送給它。 + + + +

+ +

+元件會通知其功能 (元件可回應的意圖類型),而通知途徑是 + 意圖篩選器。由於 Android 系統必須先瞭解元件能夠處理哪些意圖,才能啟動該元件,因此意圖篩選器在宣示說明中會指定為 +<intent-filter> 元素。 + + +元件可包含的篩選器數目不拘,每個篩選器描述的功能各不相同。 + +

+ +

+明確命名目標元件的意圖會啟動該元件,而不必使用篩選器。 +但未指定目標名稱的意圖,只有在其通過其中一個元件的篩選器後,才能啟動元件。 + + +

+ +

+如要瞭解意圖物件測試意圖篩選器的方式,請參閱意圖和意圖篩選器。 + + + +

+ + +

圖示和標籤

+ +

+許多元素都有可供小型圖示與文字標籤使用的 {@code icon} 和 {@code label} 屬性,而使用者可看到這些圖示和標籤。 +有些元素還包含可供較長說明文字使用的 +{@code description} 屬性,這個說明文字亦可顯示在螢幕上。 +例如,假設 +<permission> 元素含有上述三種屬性,當系統詢問使用者是否將權限授予發出要求的應用程式時,可以將代表權限的圖示、該權限的名稱以及所需的描述全都向使用者顯示。 + + + + +

+ +

+在各種情況下,元件中設定的圖示和標籤會成為所有容器下層元素的預設 +{@code icon} 與 {@code label} 設定因此, +<application> 元素中設定的圖示和標籤會是應用程式各元件的預設圖示和標籤。 + +同樣地,為元件 (例如 +<activity> 元素) 設定的圖示和標籤會是元件的 +<intent-filter> 元素預設值。 + + +如果 +<application> 元素設有一個標籤,但 Activity 與其意圖篩選器並未設定該標籤,系統會將應用程式標籤視為 Activity 和意圖篩選器的標籤。 + + + +

+ +

+每當執行篩選器通告的功能,要向使用者顯示元件時,就會將為意圖篩選器設定的圖示和標籤用來代表元件。 + +例如,包含 +"{@code android.intent.action.MAIN}" 與 +"{@code android.intent.category.LAUNCHER}" 設定的篩選器會將某 Activity 宣告為啟動應用程式的 Activity,也就是應顯示在應用程式啟動器中的 Activity。 + +因此,顯示在啟動器中的會是篩選器中設定的圖示和標籤。 + +

+ + +

權限

+ +

+單一 權限 是指一項限制,可限制某部分程式碼或裝置上資料的存取權。 + 系統會強制實施這項限制,以保護會遭到誤用而扭曲或損害使用者體驗的重要資料與程式碼。 + +

+ +

+各項權限都是用不重複的標籤加以辨識。該標籤通常會指出受到限制的動作。 +例如,以下是 Android 定義的一些權限: + +

+ +

{@code android.permission.CALL_EMERGENCY_NUMBERS} +
{@code android.permission.READ_OWNER_DATA} +
{@code android.permission.SET_WALLPAPER} +
{@code android.permission.DEVICE_POWER}

+ +

+單一功能最多只能利用一項權限來加以保護。 +

+ +

+如果應用程式需要存取受權限保護的功能,它必須在宣示說明中利用 +<uses-permission> 元素來宣告其需要該項權限。 + +接著,要在裝置上安裝該應用程式時,安裝程式會檢查簽署該應用程式憑證的授權單位 (在某些情況下還會詢問使用者),然後決定是否授予要求的權限。 + + +如果授予權限,該應用程式就能夠使用受保護的功能。 + +如果不授予權限,存取相關功能的嘗試就會失敗,但使用者不會收到任何通知。 + +

+ +

+應用程式也能利用權限來保護自己的元件 (Activity、服務、廣播接收器和內容供應程式)。 +它能使用 Android 定義的任何權限 (列於 +{@link android.Manifest.permission android.Manifest.permission}) 或其他應用程式宣告的任何權限。 + +此外,應用程式也能自行定義權限。新的權限是以 +<permission> 元素宣告。 + +例如,您可以利用下列權限保護 Activity: +

+ +
+<manifest . . . >
+    <permission android:name="com.example.project.DEBIT_ACCT" . . . />
+    <uses-permission android:name="com.example.project.DEBIT_ACCT" />
+    . . .
+    <application . . .>
+        <activity android:name="com.example.project.FreneticActivity"
+                  android:permission="com.example.project.DEBIT_ACCT"
+                  . . . >
+            . . .
+        </activity>
+    </application>
+</manifest>
+
+ +

+請注意,在本範例中,不只以 +<permission> 元素宣告 {@code DEBIT_ACCT} 權限,還使用 +<uses-permission> 元素來要求使用此權限。 + + +即使保護是由應用程式本身強制施行,還是必須要求使用該權限,應用程式的其他元件才能啟動受保護的 Activity。 + + +

+ +

+在相同的範例中,如果 {@code permission} 屬性設定為在別處宣告的權限 (例如 {@code android.permission.CALL_EMERGENCY_NUMBERS}),就不必再次使用 +<permission> 元素來宣告。 + + + +不過,還是必須利用 +<uses-permission> 來要求使用。 +

+ +

+ +<permission-tree> 元素可為程式碼將定義的一組權限宣告命名空間。 + +此外, +<permission-group> 可為一組權限定義標籤 (以 +<permission> 元素在宣示說明中宣告的權限或在別處宣告的權限)。 + +它只會影響在向使用者呈現權限時的分組方式。 + +<permission-group> 元素不會指定各權限所屬的群組,只會指定群組的名稱。 + +只要將群組名稱指派給 +<permission> 元素 +permissionGroup 的屬性,就能將權限放入群組中。 + + + +

+ + +

程式庫

+ +

+每款應用程式都會與預設的 Android 程式庫連結,該程式庫中包含的基本封裝可用於建置應用程式 (使用 Activity、Service、Intent、View、Button、Application、ContentProvider 等一般類別)。 + + + +

+ +

+不過,有些封裝是存放在其專屬的程式庫中。如果您的應用程式使用來自這類封裝的程式碼,您必須明確要求與其建立連結。 + +宣示說明必須包含個別的 +<uses-library> 元素,才能命名各個程式庫。 +(您可以在封裝的說明文件中找到程式庫名稱)。 + +

diff --git a/docs/html-intl/intl/zh-tw/guide/topics/providers/calendar-provider.jd b/docs/html-intl/intl/zh-tw/guide/topics/providers/calendar-provider.jd new file mode 100644 index 0000000000000000000000000000000000000000..42434e4b30e8a393571004d564ca507210e44920 --- /dev/null +++ b/docs/html-intl/intl/zh-tw/guide/topics/providers/calendar-provider.jd @@ -0,0 +1,1184 @@ +page.title=日曆供應程式 +@jd:body + +
+
+

本文件內容

+
    +
  1. 基本概念
  2. +
  3. 使用者權限
  4. +
  5. 日曆表格 +
      +
    1. 查詢日曆
    2. +
    3. 修改日曆
    4. +
    5. 插入日曆
    6. +
    +
  6. +
  7. 活動表格 +
      +
    1. 新增活動
    2. +
    3. 更新活動
    4. +
    5. 刪除活動
    6. +
    +
  8. +
  9. 參與者表格 +
      +
    1. 新增參與者
    2. +
    +
  10. +
  11. 提醒表格 +
      +
    1. 新增提醒
    2. +
    +
  12. +
  13. 執行個體表格 +
      +
    1. 查詢執行個體表格
    2. +
  14. +
  15. 日曆意圖 +
      +
    1. 使用意圖插入活動
    2. +
    3. 使用意圖編輯活動
    4. +
    5. 使用意圖檢視日曆資料
    6. +
    +
  16. + +
  17. 同步配接器
  18. +
+ +

重要類別

+
    +
  1. {@link android.provider.CalendarContract.Calendars}
  2. +
  3. {@link android.provider.CalendarContract.Events}
  4. +
  5. {@link android.provider.CalendarContract.Attendees}
  6. +
  7. {@link android.provider.CalendarContract.Reminders}
  8. +
+
+
+ +

「日曆供應程式」是使用者日曆活動的存放庫。「日曆供應程式」API 允許您在日曆、活動、參與者、提醒等等項目中執行查詢、插入、更新以及刪除操作。 + +

+ + +

「日曆供應程式」API 可以由應用程式和同步配接器使用。規則取決於發出呼叫的程式類型。 +本文件主要著重於將「日曆供應程式」API 作為應用程式使用。 +如要瞭解同步配接器不一樣的資訊,請參閱同步配接器。 + +

+ + +

一般而言,如要讀取或寫入日曆資料,應用程式的宣示說明必須含有適當的權限 (於使用者權限中描述)。 + +如要更輕鬆執行常見操作,「日曆供應程式」提供一組意圖 (於日曆意圖中描述)。 + +這些意圖會將使用者帶往「日曆」應用程式,以插入、檢視以及編輯活動。 +使用者與「日曆」應用程式互動後,回到原來的應用程式。 +因此,您的應用程式不需要要求權限,也不需要提供檢視或建立活動的使用者介面。 +

+ +

基本概念

+ +

內容供應程式會儲存資料,讓應用程式可以存取這些資料。 +內容供應程式是由 Android 平台所提供 (包括「日曆供應程式」),通常會根據關聯式資料庫模型,以一組表格的形式公開資料。表格中的每一列都是一筆記錄,而每一欄則是特定類型和意義的資料。 + +透過「日曆供應程式」API,應用程式和同步配接器可以取得資料庫表格 (此資料庫表格含有使用者日曆資料) 的讀取/寫入存取權。 + +

+ +

每個內容供應程式都會公開一個公用 URI (包裝為 +{@link android.net.Uri} 物件),可唯一識別其資料集。 +控制多個資料集 (多個表格) 的內容供應程式會為每個資料集公開個別的 URI。 +供應程式所有 URI 的開頭字串為「content://」。 +這表示資料是受到內容供應程式的控制。 +「日曆供應程式」會為每個類別 (表格) 的 URI 定義常數。 +這些 URI 的格式為 <class>.CONTENT_URI。 +例如,{@link android.provider.CalendarContract.Events#CONTENT_URI Events.CONTENT_URI}。 +

+ +

「圖 1」顯示「日曆供應程式」資料模型的圖形表示。它顯示主要表格以及將每個表格連接在一起的欄位。 +

+ +Calendar Provider Data Model +

圖 1.「日曆供應程式」資料模型。

+ +

使用者可以有多個日曆,而且不同的日曆可以與不同類型的帳戶 (Google 日曆、Exchange 等等) 關聯。

+ +

{@link android.provider.CalendarContract} 會定義日曆的資料模型和活動相關資訊。此資料儲存於一些表格,列示如下。

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
表格 (類別)描述

{@link android.provider.CalendarContract.Calendars}

此表格內含日曆特定的資訊。 +此表格中的每一列包含單一日曆的詳細資訊,例如名稱、色彩、同步資訊等等。 +
{@link android.provider.CalendarContract.Events}此表格內含活動特定的資訊。 +此表格中的每一列包含單一活動的資訊 — 例如,活動標題、位置、開始時間、結束時間等等。 + +活動可以只發生一次或發生多次。參與者、提醒以及延伸屬性儲存於個別的表格。 +它們每一個都有 {@link android.provider.CalendarContract.AttendeesColumns#EVENT_ID},會參照「活動」表格中的 {@link android.provider.BaseColumns#_ID}。 + +
{@link android.provider.CalendarContract.Instances}此表格內含活動每次發生的開始和結束時間。 +此表格的每一列代表單一活動發生。 +單次活動執行個體和活動的對應為 1:1。 +對於週期性活動,則會自動產生多個列,對應到該活動的多次發生。 +
{@link android.provider.CalendarContract.Attendees}此表格內含活動參與者 (邀請對象) 的資訊。 +每一列代表一個活動的單一邀請對象。 +其中會指出邀請對象類型,以及邀請對象是否出席該活動的回應。 +
{@link android.provider.CalendarContract.Reminders}此表格內含警示/通知資訊。 +每一列代表一個活動的單一警示。一個活動可以設定多個提醒。 +每個活動的最大數量提醒指定於 +{@link android.provider.CalendarContract.CalendarColumns#MAX_REMINDERS},此項是由擁有指定日曆的同步配接器所設定。 + + +提醒是以分鐘數指定活動發生前的時間,而且有一個方法用於決定通知使用者的方式。 +
+ +

「日曆供應程式」API 的設計具備彈性且功能強大。提供良好的使用者體驗,以及保護日曆及其資料的完整性,兩者一樣重要。 + +關於這一點,使用此 API 時,請記得以下事項: +

+ +
    + +
  • 插入、更新以及檢視日曆活動。如要從「日曆供應程式」直接插入、修改以及讀取活動,您需要具備適當的權限。然而,如果您不是要建置功能豐富的日曆應用程式,則不需要要求這些權限。您可以改用 Android「日曆」應用程式支援的意圖,將讀取和寫入操作交給您的應用程式。使用意圖時,您的應用程式可以將使用者傳送到「日曆」應用程式,在預先填好的表單中執行想要的操作。 +完成之後,使用者會回到您的應用程式。將應用程式設計成透過「日曆」來執行常見的操作,即可為使用者提供一致且完整的使用者介面。 + +我們建議您採用此方式。 +如需詳細資訊,請參閱日曆意圖。 +

    + + +
  • 同步配接器。同步配接器會將使用者裝置的日曆資料與另一台伺服器或資料來源進行同步。 +在 +{@link android.provider.CalendarContract.Calendars} 和 +{@link android.provider.CalendarContract.Events} 表格中,會保留某些欄讓同步配接器使用。供應程式和應用程式不應加以修改。 + +事實上,除非以同步配接器進行存取,否則看不到這些保留的欄。 +如需關於同步配接器的詳細資訊,請參閱同步配接器。 +
  • + +
+ + +

使用者權限

+ +

如要讀取日曆資料,應用程式必須在其宣示說明檔案中包含 {@link +android.Manifest.permission#READ_CALENDAR} 權限。宣示說明檔案必須包含 {@link android.Manifest.permission#WRITE_CALENDAR} 權限,才能刪除、插入或更新日曆資料: + +

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"...>
+    <uses-sdk android:minSdkVersion="14" />
+    <uses-permission android:name="android.permission.READ_CALENDAR" />
+    <uses-permission android:name="android.permission.WRITE_CALENDAR" />
+    ...
+</manifest>
+
+ + +

日曆表格

+ +

{@link android.provider.CalendarContract.Calendars} 表格包含個別日曆的詳細資訊。 +下列「日曆」欄可以讓應用程式和同步配接器寫入。 +如需關於支援欄位的完整清單,請參閱 +{@link android.provider.CalendarContract.Calendars} 參照。 +

+ + + + + + + + + + + + + + + + + + + + + + + + + +
常數描述
{@link android.provider.CalendarContract.Calendars#NAME}日曆的名稱。
{@link android.provider.CalendarContract.Calendars#CALENDAR_DISPLAY_NAME}使用者看到此日曆的名稱。
{@link android.provider.CalendarContract.Calendars#VISIBLE}指出是否選擇要顯示日曆的布林值。值 0 表示與此日曆關聯的活動不應顯示。 + +值 1 表示與此日曆關聯的活動應顯示。 +此值會影響 {@link +android.provider.CalendarContract.Instances} 表格中列的產生。
{@link android.provider.CalendarContract.CalendarColumns#SYNC_EVENTS}指出日曆是否應同步,並且讓日曆的活動儲存在裝置上的布林值。 +值 0 表示不同步此日曆,並且不要在裝置上儲存其活動。 +值 1 表示同步此日曆的活動,並且在裝置上儲存其活動。 +
+ +

查詢日曆

+ +

以下的範例顯示如何取得特定使用者擁有的日曆。 +為了簡化起見,本範例中的查詢操作顯示於使用者介面執行緒 (即「主要執行緒」) 中。 +實務上,應該以非同步執行緒來完成,而不是使用主要執行緒。 +如需更多討論,請參閱載入器。 +如果您不只要讀取資料,還要加以修改,請參閱 {@link android.content.AsyncQueryHandler}。 + +

+ + +
+// Projection array. Creating indices for this array instead of doing
+// dynamic lookups improves performance.
+public static final String[] EVENT_PROJECTION = new String[] {
+    Calendars._ID,                           // 0
+    Calendars.ACCOUNT_NAME,                  // 1
+    Calendars.CALENDAR_DISPLAY_NAME,         // 2
+    Calendars.OWNER_ACCOUNT                  // 3
+};
+  
+// The indices for the projection array above.
+private static final int PROJECTION_ID_INDEX = 0;
+private static final int PROJECTION_ACCOUNT_NAME_INDEX = 1;
+private static final int PROJECTION_DISPLAY_NAME_INDEX = 2;
+private static final int PROJECTION_OWNER_ACCOUNT_INDEX = 3;
+ + + + + +

在範例的下一個部分,您將建構查詢。選項會指定查詢的條件。 +在此範例中,查詢會尋找日曆中有 ACCOUNT_NAME "sampleuser@google.com"、ACCOUNT_TYPE "com.google" 以及 OWNER_ACCOUNT "sampleuser@google.com" 的內容。 + + + +如果您要查看使用者可以檢視的所有日曆,不是只查看使用者擁有的日曆,請略過 OWNER_ACCOUNT。此查詢會傳回 {@link android.database.Cursor} 物件。您可以使用此物件周遊資料庫查詢傳回的結果集。 + + + +如需關於在內容供應程式中使用查詢的詳細討論,請參閱內容供應程式。 +

+ + +
// Run query
+Cursor cur = null;
+ContentResolver cr = getContentResolver();
+Uri uri = Calendars.CONTENT_URI;   
+String selection = "((" + Calendars.ACCOUNT_NAME + " = ?) AND (" 
+                        + Calendars.ACCOUNT_TYPE + " = ?) AND ("
+                        + Calendars.OWNER_ACCOUNT + " = ?))";
+String[] selectionArgs = new String[] {"sampleuser@gmail.com", "com.google",
+        "sampleuser@gmail.com"}; 
+// Submit the query and get a Cursor object back. 
+cur = cr.query(uri, EVENT_PROJECTION, selection, selectionArgs, null);
+ +

下一節會使用游標逐步檢視結果集。它會使用範例一開始設定好的常數,傳回每個欄位的值。 + +

+ +
// Use the cursor to step through the returned records
+while (cur.moveToNext()) {
+    long calID = 0;
+    String displayName = null;
+    String accountName = null;
+    String ownerName = null;
+      
+    // Get the field values
+    calID = cur.getLong(PROJECTION_ID_INDEX);
+    displayName = cur.getString(PROJECTION_DISPLAY_NAME_INDEX);
+    accountName = cur.getString(PROJECTION_ACCOUNT_NAME_INDEX);
+    ownerName = cur.getString(PROJECTION_OWNER_ACCOUNT_INDEX);
+              
+    // Do something with the values...
+
+   ...
+}
+
+ +

修改日曆

+ +

如要執行日曆的更新,您可以提供日曆的 {@link +android.provider.BaseColumns#_ID},可以是 URI 的附加 ID +({@link android.content.ContentUris#withAppendedId(android.net.Uri,long) withAppendedId()}) 或以第一個選擇項目方式提供。 + + +選項的開頭應該是 "_id=?",而且第一個 +selectionArg 應該是日曆的 {@link +android.provider.BaseColumns#_ID}。 +您也可以透過將 ID 編碼在 URI 中,以執行更新。此範例會使用 +({@link android.content.ContentUris#withAppendedId(android.net.Uri,long) withAppendedId()}) 方式變更日曆的顯示名稱: + + +

+ +
private static final String DEBUG_TAG = "MyActivity";
+...
+long calID = 2;
+ContentValues values = new ContentValues();
+// The new display name for the calendar
+values.put(Calendars.CALENDAR_DISPLAY_NAME, "Trevor's Calendar");
+Uri updateUri = ContentUris.withAppendedId(Calendars.CONTENT_URI, calID);
+int rows = getContentResolver().update(updateUri, values, null, null);
+Log.i(DEBUG_TAG, "Rows updated: " + rows);
+ +

插入日曆

+ +

日曆的設計主要是由同步配接器進行管理,因此您只能以同步配接器的方式插入新日曆。 +在大多數情況下,應用程式只能對日曆進行與外觀相關的變更,例如變更顯示名稱 +。如果應用程式需要建立本機日曆,請以同步配接器的方式插入日曆,方法是使用 {@link android.provider.CalendarContract#ACCOUNT_TYPE_LOCAL} 的 {@link android.provider.CalendarContract.SyncColumns#ACCOUNT_TYPE}。 +{@link android.provider.CalendarContract#ACCOUNT_TYPE_LOCAL} 是特殊的日曆帳戶類型,它沒有與裝置帳戶關聯。 + + + + + +此類型的日曆不會同步至伺服器。如需關於同步配接器的相關討論,請參閱同步配接器。 +

+ +

活動表格

+ +

{@link android.provider.CalendarContract.Events} 表格包含個人活動的詳細資訊。 +如要新增、更新或刪除活動,應用程式必須在宣示說明檔案中包括 {@link android.Manifest.permission#WRITE_CALENDAR}權限。 + +

+ +

下列「活動」欄可以讓應用程式和同步配接器寫入。 +如需關於支援欄位的完整清單,請參閱 {@link +android.provider.CalendarContract.Events} 參照。

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
常數描述
{@link android.provider.CalendarContract.EventsColumns#CALENDAR_ID}活動所屬日曆的 {@link android.provider.BaseColumns#_ID}。
{@link android.provider.CalendarContract.EventsColumns#ORGANIZER}活動主辦人 (擁有者) 的電子郵件。
{@link android.provider.CalendarContract.EventsColumns#TITLE}活動的標題。
{@link android.provider.CalendarContract.EventsColumns#EVENT_LOCATION}舉辦活動的地點。
{@link android.provider.CalendarContract.EventsColumns#DESCRIPTION}活動的描述。
{@link android.provider.CalendarContract.EventsColumns#DTSTART}活動開始的時間,以紀元元年 1 月 1 日零時起算經過的 UTC 毫秒數為單位。
{@link android.provider.CalendarContract.EventsColumns#DTEND}活動結束的時間,以紀元元年 1 月 1 日零時起算經過的 UTC 毫秒數為單位。
{@link android.provider.CalendarContract.EventsColumns#EVENT_TIMEZONE}活動的時區。
{@link android.provider.CalendarContract.EventsColumns#EVENT_END_TIMEZONE}活動結束時間的時區。
{@link android.provider.CalendarContract.EventsColumns#DURATION}活動的持續期間,以 RFC5545 格式表示。例如,值 "PT1H" 表示活動持續一小時,而值 "P2W" 指出持續 2 週。 + + +
{@link android.provider.CalendarContract.EventsColumns#ALL_DAY}值 1 表示此活動需要整天,如同當地時區所定義。 +值 0 表示定期活動,會在一天中的任何時間開始和結束。 +
{@link android.provider.CalendarContract.EventsColumns#RRULE}活動的週期規則。例如,"FREQ=WEEKLY;COUNT=10;WKST=SU"。 +您可以在這裡查看更多範例。 +
{@link android.provider.CalendarContract.EventsColumns#RDATE}活動重複發生的日期。您通常會將 {@link android.provider.CalendarContract.EventsColumns#RDATE} 和 {@link android.provider.CalendarContract.EventsColumns#RRULE} 一起使用,以定義週期性活動的彙總集合。 + + + +如需更多討論,請參閱 RFC5545 規格
{@link android.provider.CalendarContract.EventsColumns#AVAILABILITY}活動要視為忙碌或有空 (可以安排其他活動) 的時間。 +
{@link android.provider.CalendarContract.EventsColumns#GUESTS_CAN_MODIFY}邀請對象是否可以修改活動。
{@link android.provider.CalendarContract.EventsColumns#GUESTS_CAN_INVITE_OTHERS}邀請對象是否可以邀請其他人。
{@link android.provider.CalendarContract.EventsColumns#GUESTS_CAN_SEE_GUESTS}邀請對象是否可以看到參與者清單。
+ +

新增活動

+ +

您的應用程式插入新活動時,我們建議您使用 +{@link android.content.Intent#ACTION_INSERT INSERT} 意圖 (如同使用意圖插入活動所述)。不過,如果需要,您也可以直接插入活動。 +本節將描述如何執行此動作。 +

+ + +

以下是插入新活動的規則:

+
    + +
  • 您必須包括 {@link +android.provider.CalendarContract.EventsColumns#CALENDAR_ID} 和 {@link +android.provider.CalendarContract.EventsColumns#DTSTART}。
  • + +
  • 您必須包括 {@link +android.provider.CalendarContract.EventsColumns#EVENT_TIMEZONE}。如要取得系統已安裝時區 ID 的清單,請使用 {@link +java.util.TimeZone#getAvailableIDs()}。 +請注意,如果您是透過 {@link +android.content.Intent#ACTION_INSERT INSERT} 意圖 (如同使用意圖插入活動 — 所描述的案例) 插入活動,則不適用此規則,將會提供預設時區。 + +
  • + +
  • 對於非週期性活動,您必須包括 {@link +android.provider.CalendarContract.EventsColumns#DTEND}。
  • + + +
  • 對於週期性活動,您必須包括 {@link +android.provider.CalendarContract.EventsColumns#DURATION},以及 {@link +android.provider.CalendarContract.EventsColumns#RRULE} 或 {@link +android.provider.CalendarContract.EventsColumns#RDATE}。請注意,如果您是透過 {@link +android.content.Intent#ACTION_INSERT INSERT} 意圖 (如同使用意圖插入活動 — 所描述的案例) 插入活動,則不適用此規則。您可以使用 {@link +android.provider.CalendarContract.EventsColumns#RRULE} 搭配 {@link android.provider.CalendarContract.EventsColumns#DTSTART} 和 {@link android.provider.CalendarContract.EventsColumns#DTEND},然後「日曆」應用程式就會自動將它轉換為一段持續時間。 + + +
  • + +
+ +

以下是插入活動的範例。為了簡化,會在 UI 執行緒中執行此示範。 +實際運作時,插入和更新應該在非同步執行緒中完成,以便將動作移至背景執行緒。 +如需詳細資訊,請參閱 {@link android.content.AsyncQueryHandler}。 +

+ + +
+long calID = 3;
+long startMillis = 0; 
+long endMillis = 0;     
+Calendar beginTime = Calendar.getInstance();
+beginTime.set(2012, 9, 14, 7, 30);
+startMillis = beginTime.getTimeInMillis();
+Calendar endTime = Calendar.getInstance();
+endTime.set(2012, 9, 14, 8, 45);
+endMillis = endTime.getTimeInMillis();
+...
+
+ContentResolver cr = getContentResolver();
+ContentValues values = new ContentValues();
+values.put(Events.DTSTART, startMillis);
+values.put(Events.DTEND, endMillis);
+values.put(Events.TITLE, "Jazzercise");
+values.put(Events.DESCRIPTION, "Group workout");
+values.put(Events.CALENDAR_ID, calID);
+values.put(Events.EVENT_TIMEZONE, "America/Los_Angeles");
+Uri uri = cr.insert(Events.CONTENT_URI, values);
+
+// get the event ID that is the last element in the Uri
+long eventID = Long.parseLong(uri.getLastPathSegment());
+// 
+// ... do something with event ID
+//
+//
+ +

注意:查看本範例如何在建立活動後擷取活動 ID。 +這是取得活動 ID 的最簡單方式。您經常需要活動 ID 來執行其他日曆操作 — 例如,在活動中新增參與者或提醒。 + +

+ + +

更新活動

+ +

您的應用程式允許使用者編輯活動時,我們建議您使用 {@link android.content.Intent#ACTION_EDIT EDIT} 意圖編輯活動 (如同使用意圖插入活動所述)。不過,如果需要,您也可以直接編輯活動。 + + +如要執行活動的更新,您要提供活動的 _ID,可以是 URI 的附加 ID ({@link +android.content.ContentUris#withAppendedId(android.net.Uri,long) withAppendedId()}) 或以第一個選擇項目方式提供。 + + +選項的開頭應該是 "_id=?",而且第一個 +selectionArg 應該是活動的 _ID。 +您也可以使用不含 ID 的選項進行更新。以下是更新活動的範例。 + +它使用 +{@link android.content.ContentUris#withAppendedId(android.net.Uri,long) withAppendedId()} 方式變更活動的標題: +

+ + +
private static final String DEBUG_TAG = "MyActivity";
+...
+long eventID = 188;
+...
+ContentResolver cr = getContentResolver();
+ContentValues values = new ContentValues();
+Uri updateUri = null;
+// The new title for the event
+values.put(Events.TITLE, "Kickboxing"); 
+updateUri = ContentUris.withAppendedId(Events.CONTENT_URI, eventID);
+int rows = getContentResolver().update(updateUri, values, null, null);
+Log.i(DEBUG_TAG, "Rows updated: " + rows);  
+ +

刪除活動

+ +

您可以透過活動 URI 的附加 ID {@link +android.provider.BaseColumns#_ID} 或使用標準選擇方式來刪除活動。 +如果您使用附加 ID,就不能進行選擇。刪除有兩種方式:以應用程式和以同步配接器。 +應用程式刪除會將 deleted 欄設定為 1。 +此旗標會告訴同步配接器該列已刪除,而且此刪除應傳播到伺服器。 + +同步配接器會從資料庫刪除活動及其相關的所有資料。 +以下是應用程式透過活動的 {@link android.provider.BaseColumns#_ID}刪除活動的範例: +

+ + +
private static final String DEBUG_TAG = "MyActivity";
+...
+long eventID = 201;
+...
+ContentResolver cr = getContentResolver();
+ContentValues values = new ContentValues();
+Uri deleteUri = null;
+deleteUri = ContentUris.withAppendedId(Events.CONTENT_URI, eventID);
+int rows = getContentResolver().delete(deleteUri, null, null);
+Log.i(DEBUG_TAG, "Rows deleted: " + rows);  
+
+ +

參與者表格

+ +

{@link android.provider.CalendarContract.Attendees} 表格的每一列都代表活動的單一參與者或邀請對象。 +呼叫 +{@link android.provider.CalendarContract.Reminders#query(android.content.ContentResolver, long, java.lang.String[]) query()} 會傳回指定 {@link android.provider.CalendarContract.AttendeesColumns#EVENT_ID} 活動的參與者清單。 + +此 {@link android.provider.CalendarContract.AttendeesColumns#EVENT_ID} 必須符合特定活動的 {@link +android.provider.BaseColumns#_ID}。 + +

+ +

下表列出可寫入的欄位。 +插入新的參與者時,您必須包括 ATTENDEE_NAME 以外的所有項目。 + +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
常數描述
{@link android.provider.CalendarContract.AttendeesColumns#EVENT_ID}活動的 ID。
{@link android.provider.CalendarContract.AttendeesColumns#ATTENDEE_NAME}參與者的名稱。
{@link android.provider.CalendarContract.AttendeesColumns#ATTENDEE_EMAIL}參與者的電子郵件地址。
{@link android.provider.CalendarContract.AttendeesColumns#ATTENDEE_RELATIONSHIP}

參與者與活動的關係。可以是以下其中一種:

+
    +
  • {@link android.provider.CalendarContract.AttendeesColumns#RELATIONSHIP_ATTENDEE}
  • +
  • {@link android.provider.CalendarContract.AttendeesColumns#RELATIONSHIP_NONE}
  • +
  • {@link android.provider.CalendarContract.AttendeesColumns#RELATIONSHIP_ORGANIZER}
  • +
  • {@link android.provider.CalendarContract.AttendeesColumns#RELATIONSHIP_PERFORMER}
  • +
  • {@link android.provider.CalendarContract.AttendeesColumns#RELATIONSHIP_SPEAKER}
  • +
+
{@link android.provider.CalendarContract.AttendeesColumns#ATTENDEE_TYPE}

參與者的類型。可以是以下其中一種:

+
    +
  • {@link android.provider.CalendarContract.AttendeesColumns#TYPE_REQUIRED}
  • +
  • {@link android.provider.CalendarContract.AttendeesColumns#TYPE_OPTIONAL}
  • +
{@link android.provider.CalendarContract.AttendeesColumns#ATTENDEE_STATUS}

參與者的出席狀態。可以是以下其中一種:

+
    +
  • {@link android.provider.CalendarContract.AttendeesColumns#ATTENDEE_STATUS_ACCEPTED}
  • +
  • {@link android.provider.CalendarContract.AttendeesColumns#ATTENDEE_STATUS_DECLINED}
  • +
  • {@link android.provider.CalendarContract.AttendeesColumns#ATTENDEE_STATUS_INVITED}
  • +
  • {@link android.provider.CalendarContract.AttendeesColumns#ATTENDEE_STATUS_NONE}
  • +
  • {@link android.provider.CalendarContract.AttendeesColumns#ATTENDEE_STATUS_TENTATIVE}
  • +
+ +

新增參與者

+ +

以下是將單一參與者新增至活動的範例。請注意, +{@link android.provider.CalendarContract.AttendeesColumns#EVENT_ID} 為必要的: +

+ +
+long eventID = 202;
+...
+ContentResolver cr = getContentResolver();
+ContentValues values = new ContentValues();
+values.put(Attendees.ATTENDEE_NAME, "Trevor");
+values.put(Attendees.ATTENDEE_EMAIL, "trevor@example.com");
+values.put(Attendees.ATTENDEE_RELATIONSHIP, Attendees.RELATIONSHIP_ATTENDEE);
+values.put(Attendees.ATTENDEE_TYPE, Attendees.TYPE_OPTIONAL);
+values.put(Attendees.ATTENDEE_STATUS, Attendees.ATTENDEE_STATUS_INVITED);
+values.put(Attendees.EVENT_ID, eventID);
+Uri uri = cr.insert(Attendees.CONTENT_URI, values);
+
+ +

提醒表格

+ +

{@link android.provider.CalendarContract.Reminders} 表格的每一列都代表活動的單一提醒。 +呼叫 +{@link android.provider.CalendarContract.Reminders#query(android.content.ContentResolver, long, java.lang.String[]) query()} 會傳回指定 +{@link android.provider.CalendarContract.AttendeesColumns#EVENT_ID} 活動的提醒清單。 +

+ + +

下表列出提醒可寫入的欄位。插入新的提醒時,必須包括所有項目。 +請注意,同步配接器會在 {@link +android.provider.CalendarContract.Calendars} 表格中指出同步配接器支援的提醒類型。 +如需詳細資料,請參閱 +{@link android.provider.CalendarContract.CalendarColumns#ALLOWED_REMINDERS}。 +

+ + + + + + + + + + + + + + + + + + + +
常數描述
{@link android.provider.CalendarContract.RemindersColumns#EVENT_ID}活動的 ID。
{@link android.provider.CalendarContract.RemindersColumns#MINUTES}活動之前的幾分鐘要觸發提醒。
{@link android.provider.CalendarContract.RemindersColumns#METHOD}

鬧鐘方法 (如伺服器上所設定)。可以是以下其中一種:

+
    +
  • {@link android.provider.CalendarContract.RemindersColumns#METHOD_ALERT}
  • +
  • {@link android.provider.CalendarContract.RemindersColumns#METHOD_DEFAULT}
  • +
  • {@link android.provider.CalendarContract.RemindersColumns#METHOD_EMAIL}
  • +
  • {@link android.provider.CalendarContract.RemindersColumns#METHOD_SMS}
  • +
+ +

新增提醒

+ +

此範例會在活動新增提醒。此提醒會在活動 15 分鐘之前觸發。 +

+
+long eventID = 221;
+...
+ContentResolver cr = getContentResolver();
+ContentValues values = new ContentValues();
+values.put(Reminders.MINUTES, 15);
+values.put(Reminders.EVENT_ID, eventID);
+values.put(Reminders.METHOD, Reminders.METHOD_ALERT);
+Uri uri = cr.insert(Reminders.CONTENT_URI, values);
+ +

執行個體表格

+ +

+{@link android.provider.CalendarContract.Instances} 表格內含活動每次發生的開始和結束時間。 +此表格的每一列代表單一活動發生。 +執行個體表格無法寫入,僅供查詢活動的發生。 +

+ +

下表列出您可以針對執行個體查詢的欄位。請注意,時區是由 +{@link android.provider.CalendarContract.CalendarCache#KEY_TIMEZONE_TYPE} 和 +{@link android.provider.CalendarContract.CalendarCache#KEY_TIMEZONE_INSTANCES} 所定義。 + +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
常數描述
{@link android.provider.CalendarContract.Instances#BEGIN}執行個體的開始時間,以 UTC 毫秒數為單位。
{@link android.provider.CalendarContract.Instances#END}執行個體的結束時間,以 UTC 毫秒數為單位。
{@link android.provider.CalendarContract.Instances#END_DAY}執行個體的凱撒曆結束日,與「日曆」的時區相關。 + + +
{@link android.provider.CalendarContract.Instances#END_MINUTE}執行個體的結束分鐘,從「日曆」時區的午夜開始計算。 +
{@link android.provider.CalendarContract.Instances#EVENT_ID}此執行個體活動的 _ID
{@link android.provider.CalendarContract.Instances#START_DAY}執行個體的凱撒曆開始日,與「日曆」的時區相關。 +
{@link android.provider.CalendarContract.Instances#START_MINUTE}執行個體的開始分鐘,從午夜開始計算,與「日曆」的時區相關。 + +
+ +

查詢執行個體表格

+ +

如要查詢「執行個體」表格,您需要在 URI 中指定查詢的範圍時間。在本範例中,{@link android.provider.CalendarContract.Instances} 是透過 {@link android.provider.CalendarContract.EventsColumns} 介面的實作,得以存取 {@link +android.provider.CalendarContract.EventsColumns#TITLE} 欄位。 + + +換句話說,{@link +android.provider.CalendarContract.EventsColumns#TITLE} 是透過資料庫視觀表所傳回,而不是透過查詢原始 {@link +android.provider.CalendarContract.Instances} 表格。 + +

+ +
+private static final String DEBUG_TAG = "MyActivity";
+public static final String[] INSTANCE_PROJECTION = new String[] {
+    Instances.EVENT_ID,      // 0
+    Instances.BEGIN,         // 1
+    Instances.TITLE          // 2
+  };
+  
+// The indices for the projection array above.
+private static final int PROJECTION_ID_INDEX = 0;
+private static final int PROJECTION_BEGIN_INDEX = 1;
+private static final int PROJECTION_TITLE_INDEX = 2;
+...
+
+// Specify the date range you want to search for recurring
+// event instances
+Calendar beginTime = Calendar.getInstance();
+beginTime.set(2011, 9, 23, 8, 0);
+long startMillis = beginTime.getTimeInMillis();
+Calendar endTime = Calendar.getInstance();
+endTime.set(2011, 10, 24, 8, 0);
+long endMillis = endTime.getTimeInMillis();
+  
+Cursor cur = null;
+ContentResolver cr = getContentResolver();
+
+// The ID of the recurring event whose instances you are searching
+// for in the Instances table
+String selection = Instances.EVENT_ID + " = ?";
+String[] selectionArgs = new String[] {"207"};
+
+// Construct the query with the desired date range.
+Uri.Builder builder = Instances.CONTENT_URI.buildUpon();
+ContentUris.appendId(builder, startMillis);
+ContentUris.appendId(builder, endMillis);
+
+// Submit the query
+cur =  cr.query(builder.build(), 
+    INSTANCE_PROJECTION, 
+    selection, 
+    selectionArgs, 
+    null);
+   
+while (cur.moveToNext()) {
+    String title = null;
+    long eventID = 0;
+    long beginVal = 0;    
+    
+    // Get the field values
+    eventID = cur.getLong(PROJECTION_ID_INDEX);
+    beginVal = cur.getLong(PROJECTION_BEGIN_INDEX);
+    title = cur.getString(PROJECTION_TITLE_INDEX);
+              
+    // Do something with the values. 
+    Log.i(DEBUG_TAG, "Event:  " + title); 
+    Calendar calendar = Calendar.getInstance();
+    calendar.setTimeInMillis(beginVal);  
+    DateFormat formatter = new SimpleDateFormat("MM/dd/yyyy");
+    Log.i(DEBUG_TAG, "Date: " + formatter.format(calendar.getTime()));    
+    }
+ }
+ +

日曆意圖

+

您的應用程式不需要權限,即可讀取和寫入日曆資料。您可以改用 Android「日曆」應用程式支援的意圖,將讀取和寫入操作交給您的應用程式。下表列出「日曆供應程式」支援的意圖:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
動作URI描述Extra

+ {@link android.content.Intent#ACTION_VIEW VIEW}

content://com.android.calendar/time/<ms_since_epoch>

+ 您也可以使用 +{@link android.provider.CalendarContract#CONTENT_URI CalendarContract.CONTENT_URI} 參照 URI。如需使用此意圖的範例,請參閱使用意圖檢視日曆資料。 + + +
開啟日曆到 <ms_since_epoch> 指定的時間。無。

{@link android.content.Intent#ACTION_VIEW VIEW}

+ +

content://com.android.calendar/events/<event_id>

+ + 您也可以使用 +{@link android.provider.CalendarContract.Events#CONTENT_URI Events.CONTENT_URI} 參照 URI。如需使用此意圖的範例,請參閱使用意圖檢視日曆資料。 + + +
檢視 <event_id> 指定的活動。{@link android.provider.CalendarContract#EXTRA_EVENT_BEGIN_TIME CalendarContract.EXTRA_EVENT_BEGIN_TIME}
+
+
+ {@link android.provider.CalendarContract#EXTRA_EVENT_END_TIME CalendarContract.EXTRA_EVENT_END_TIME}
{@link android.content.Intent#ACTION_EDIT EDIT}

content://com.android.calendar/events/<event_id>

+ + 您也可以使用 +{@link android.provider.CalendarContract.Events#CONTENT_URI Events.CONTENT_URI} 參照 URI。如需使用此意圖的範例,請參閱使用意圖編輯活動。 + + + +
編輯 <event_id> 指定的活動。{@link android.provider.CalendarContract#EXTRA_EVENT_BEGIN_TIME CalendarContract.EXTRA_EVENT_BEGIN_TIME}
+
+
+ {@link android.provider.CalendarContract#EXTRA_EVENT_END_TIME CalendarContract.EXTRA_EVENT_END_TIME}
{@link android.content.Intent#ACTION_EDIT EDIT}
+
+ {@link android.content.Intent#ACTION_INSERT INSERT}

content://com.android.calendar/events

+ + 您也可以使用 +{@link android.provider.CalendarContract.Events#CONTENT_URI Events.CONTENT_URI} 參照 URI。如需使用此意圖的範例,請參閱使用意圖插入活動。 + + +
建立活動。Extra 列於下表。
+ +

下表列出「日曆供應程式」支援的意圖 Extra: +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
意圖 Extra描述
{@link android.provider.CalendarContract.EventsColumns#TITLE Events.TITLE}活動的名稱。
{@link android.provider.CalendarContract#EXTRA_EVENT_BEGIN_TIME +CalendarContract.EXTRA_EVENT_BEGIN_TIME}活動開始時間,以紀元元年 1 月 1 日零時起算經過的毫秒數為單位。
{@link android.provider.CalendarContract#EXTRA_EVENT_END_TIME +CalendarContract.EXTRA_EVENT_END_TIME}活動結束時間,以紀元元年 1 月 1 日零時起算經過的毫秒數為單位。
{@link android.provider.CalendarContract#EXTRA_EVENT_ALL_DAY +CalendarContract.EXTRA_EVENT_ALL_DAY}指出活動為整天的布林值。值可以是 +truefalse
{@link android.provider.CalendarContract.EventsColumns#EVENT_LOCATION +Events.EVENT_LOCATION}活動的地點。
{@link android.provider.CalendarContract.EventsColumns#DESCRIPTION +Events.DESCRIPTION}活動描述。
+ {@link android.content.Intent#EXTRA_EMAIL Intent.EXTRA_EMAIL}邀請對象的電子郵件地址 (以逗號分隔的清單)。
+ {@link android.provider.CalendarContract.EventsColumns#RRULE Events.RRULE}活動的週期規則。
+ {@link android.provider.CalendarContract.EventsColumns#ACCESS_LEVEL +Events.ACCESS_LEVEL}活動為私人或公開性質。
{@link android.provider.CalendarContract.EventsColumns#AVAILABILITY +Events.AVAILABILITY}活動要視為忙碌或有空 (可以安排其他活動) 的時間。
+

以下各節說明如何使用這些意圖。

+ + +

使用意圖插入活動

+ +

使用 {@link android.content.Intent#ACTION_INSERT INSERT} 意圖讓您的應用程式將活動插入工作交給「日曆」本身。使用此方式,您的應用程式就不需要將 {@link +android.Manifest.permission#WRITE_CALENDAR} 權限包括在其宣示說明檔案中。 + +

+ + +

使用者執行採用此方式的應用程式時,此應用程式會將使用者 +傳送到「日曆」以完成新增活動的操作。{@link +android.content.Intent#ACTION_INSERT INSERT} 意圖會使用額外的欄位將「日曆」中活動的詳細資訊,預先填入表單。 +然後,使用者可以取消活動、視需要編輯表單或將活動儲存到其日曆。 + +

+ + + +

以下的程式碼片段會在 2012 年 1 月 19 日安排活動,此活動的期間是從上午 7:30 到上午 8:30。 +請注意下列關於此程式碼片段的事項:

+ +
    +
  • 它指定 {@link android.provider.CalendarContract.Events#CONTENT_URI Events.CONTENT_URI} 作為 URI。 +
  • + +
  • 它使用 {@link +android.provider.CalendarContract#EXTRA_EVENT_BEGIN_TIME +CalendarContract.EXTRA_EVENT_BEGIN_TIME} 和 {@link +android.provider.CalendarContract#EXTRA_EVENT_END_TIME +CalendarContract.EXTRA_EVENT_END_TIME} 額外欄位,將活動的時間預先填入表單。 +這些時間值必須以自紀元元年 1 月 1 日零時起算經過的 UTC 毫秒數為單位。 +
  • + +
  • 它使用 {@link android.content.Intent#EXTRA_EMAIL Intent.EXTRA_EMAIL} 額外欄位提供逗號分隔的受邀者清單 (以電子郵件地址指定)。 +
  • + +
+
+Calendar beginTime = Calendar.getInstance();
+beginTime.set(2012, 0, 19, 7, 30);
+Calendar endTime = Calendar.getInstance();
+endTime.set(2012, 0, 19, 8, 30);
+Intent intent = new Intent(Intent.ACTION_INSERT)
+        .setData(Events.CONTENT_URI)
+        .putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, beginTime.getTimeInMillis())
+        .putExtra(CalendarContract.EXTRA_EVENT_END_TIME, endTime.getTimeInMillis())
+        .putExtra(Events.TITLE, "Yoga")
+        .putExtra(Events.DESCRIPTION, "Group class")
+        .putExtra(Events.EVENT_LOCATION, "The gym")
+        .putExtra(Events.AVAILABILITY, Events.AVAILABILITY_BUSY)
+        .putExtra(Intent.EXTRA_EMAIL, "rowan@example.com,trevor@example.com");
+startActivity(intent);
+
+ +

使用意圖編輯活動

+ +

您可以直接更新活動,如同更新活動所述。不過,使用 {@link +android.content.Intent#ACTION_EDIT EDIT} 意圖可以讓不具備權限的應用程式將活動編輯操作交給「日曆」應用程式。使用者在「日曆」中完成活動的編輯操作時,使用者就會回到原來的應用程式。 + + +

以下的意圖範例會為指定的活動設定名稱,然後讓使用者在「日曆」中編輯此活動。 +

+ + +
long eventID = 208;
+Uri uri = ContentUris.withAppendedId(Events.CONTENT_URI, eventID);
+Intent intent = new Intent(Intent.ACTION_EDIT)
+    .setData(uri)
+    .putExtra(Events.TITLE, "My New Title");
+startActivity(intent);
+ +

使用意圖檢視日曆資料

+

「日曆供應程式」提供兩種不同的方式來使用 {@link android.content.Intent#ACTION_VIEW VIEW} 意圖:

+
    +
  • 開啟「日曆」到特定的日期。
  • +
  • 檢視某個活動。
  • + +
+

以下的範例顯示如何開啟「日曆」到特定的日期:

+
// A date-time specified in milliseconds since the epoch.
+long startMillis;
+...
+Uri.Builder builder = CalendarContract.CONTENT_URI.buildUpon();
+builder.appendPath("time");
+ContentUris.appendId(builder, startMillis);
+Intent intent = new Intent(Intent.ACTION_VIEW)
+    .setData(builder.build());
+startActivity(intent);
+ +

以下的範例顯示如何開啟某個活動以便檢視。

+
long eventID = 208;
+...
+Uri uri = ContentUris.withAppendedId(Events.CONTENT_URI, eventID);
+Intent intent = new Intent(Intent.ACTION_VIEW)
+   .setData(uri);
+startActivity(intent);
+
+ + +

同步配接器

+ + +

應用程式和同步配接器存取「日曆供應程式」的方式只有些微差異: +

+ +
    +
  • 同步配接器需要透過將 {@link android.provider.CalendarContract#CALLER_IS_SYNCADAPTER} 設定為 true,以指出這是同步配接器。
  • + + +
  • 同步配接器需要在 URI 中提供 {@link +android.provider.CalendarContract.SyncColumns#ACCOUNT_NAME} 和 {@link +android.provider.CalendarContract.SyncColumns#ACCOUNT_TYPE} 作為查詢參數。
  • + +
  • 同步配接器與應用程式或小工具相比,有更多欄的寫入權限。 + 例如,應用程式只能修改日曆的一些特性,例如名稱、顯示名稱、能見度設定,以及日曆是否同步。 + +相較之下,同步配接器不只能存取這些欄,還可以存取很多其他欄,例如日曆色彩、時區、存取級別、位置等等。然而,同步配接器受限於所指定的 ACCOUNT_NAME 和 +ACCOUNT_TYPE。 + +
+ +

以下是您可用於傳回 URI 的協助程式方法,以便與同步配接器搭配使用:

+
 static Uri asSyncAdapter(Uri uri, String account, String accountType) {
+    return uri.buildUpon()
+        .appendQueryParameter(android.provider.CalendarContract.CALLER_IS_SYNCADAPTER,"true")
+        .appendQueryParameter(Calendars.ACCOUNT_NAME, account)
+        .appendQueryParameter(Calendars.ACCOUNT_TYPE, accountType).build();
+ }
+
+

如需實作同步配接器的範例 (並非與「日曆」特別相關),請參閱 +SampleSyncAdapter。 diff --git a/docs/html-intl/intl/zh-tw/guide/topics/providers/contacts-provider.jd b/docs/html-intl/intl/zh-tw/guide/topics/providers/contacts-provider.jd new file mode 100644 index 0000000000000000000000000000000000000000..b5f888012eed280ec7edb763c824179e49512d33 --- /dev/null +++ b/docs/html-intl/intl/zh-tw/guide/topics/providers/contacts-provider.jd @@ -0,0 +1,2356 @@ +page.title=聯絡人供應程式 +@jd:body +

+
+

快速檢視

+
    +
  • Android 關於人員資訊的存放庫。
  • +
  • + 與網頁同步。 +
  • +
  • + 整合社交串流資料。 +
  • +
+

本文件內容

+
    +
  1. + 聯絡人供應程式組織架構 +
  2. +
  3. + 原始聯絡人 +
  4. +
  5. + 資料 +
  6. +
  7. + 聯絡人 +
  8. +
  9. + 來自同步配接器的資料 +
  10. +
  11. + 必要權限 +
  12. +
  13. + 使用者設定檔 +
  14. +
  15. + 聯絡人供應程式中繼資料 +
  16. +
  17. + 聯絡人供應程式存取 +
  18. +
  19. +
  20. + 聯絡人供應程式同步配接器 +
  21. +
  22. + 社交串流資料 +
  23. +
  24. + 其他聯絡人供應程式功能 +
  25. +
+

重要類別

+
    +
  1. {@link android.provider.ContactsContract.Contacts}
  2. +
  3. {@link android.provider.ContactsContract.RawContacts}
  4. +
  5. {@link android.provider.ContactsContract.Data}
  6. +
  7. {@code android.provider.ContactsContract.StreamItems}
  8. +
+

相關範例

+
    +
  1. + 聯絡人管理員 + + +
  2. +
  3. + +範例同步配接器 +
  4. +
+

另請參閱

+
    +
  1. + + 內容供應程式基本概念 + +
  2. +
+
+
+

+ 聯絡人供應程式是強大且有彈性的 Android 元件,負責管理裝置上與人員相關的中央資料存放庫。 +聯絡人供應程式是裝置中聯絡人應用程式的資料來源,您也可以在自己的應用程式中存取其資料,並且在裝置和線上服務之間傳輸資料。 + +供應程式內含範圍寬廣的資料來源,而且嘗試管理每個人愈來愈多的資料,組織架構因而變得很複雜。 + +基於這個原因,供應程式的 API 包括豐富的合約類別和介面,可幫助資料的擷取和修改。 + + +

+

+ 本指南描述以下各項: +

+
    +
  • + 基本的供應程式結構。 +
  • +
  • + 如何從供應程式擷取資料。 +
  • +
  • + 如何修改供應程式中的資料。 +
  • +
  • + 如何針對從伺服器到聯絡人供應程式的資料同步編寫同步配接器。 + +
  • +
+

+ 本指南假設您瞭解 Android 內容供應程式基本概念。如要更瞭解 Android 內容供應程式的詳細資訊,請閱讀內容供應程式基本概念指南。 + + +範例同步配接器範例應用程式是使用同步配接器在聯絡人供應程式和 Google 網路服務代管的範例應用程式之間傳輸資料的例子。 + + + +

+

聯絡人供應程式組織架構

+

+ 聯絡人供應程式是 Android 內容供應程式元件。負責維護三種與人有關的資料類型,每一種類型都會對應到供應程式所提供的表格,如圖 1 所示: + + +

+ +

+ 圖 1.聯絡人供應程式表格結構。 +

+

+ 這三個表格通常會以其合約類別的名稱來稱呼。類別會定義內容 URI 的常數、欄名稱以及表格所使用的欄值: + +

+
+
+ {@link android.provider.ContactsContract.Contacts} 表格 +
+
+ 根據原始聯絡人列的彙總,代表不同人員的列。 +
+
+ {@link android.provider.ContactsContract.RawContacts} 表格 +
+
+ 內含人員資料摘要的列,針對使用者帳戶和類型。 +
+
+ {@link android.provider.ContactsContract.Data} 表格 +
+
+ 內含原始聯絡人詳細資料的列,例如電子郵件地址或電話號碼。 +
+
+

+ 合約類別在 {@link android.provider.ContactsContract} 中呈現的其他表格都是輔助表格。聯絡人供應程式使用輔助表格來管理其操作,或支援裝置中聯絡人或電話應用程式的特定功能。 + + +

+

原始聯絡人

+

+ 原始聯絡人是來自單一帳戶類型和帳戶名稱的人員資料。 +因為聯絡人供應程式允許一個人有多種線上服務做為資料來源,所以聯絡人供應程式可以讓同一個人有多個原始聯絡人。 + + 多個原始聯絡人也可以讓使用者,將相同帳戶類型中多個帳戶的人員資料合併。 + +

+

+ 原始聯絡人的大部分資料不是儲存在 +{@link android.provider.ContactsContract.RawContacts} 表格,而是儲存在 {@link android.provider.ContactsContract.Data} 表格中的一或多個列。 +每個資料列都有一欄 +{@link android.provider.ContactsContract.DataColumns#RAW_CONTACT_ID Data.RAW_CONTACT_ID},當中包含其上層資料列 {@link android.provider.ContactsContract.RawContacts} 的 {@code android.provider.BaseColumns#_ID RawContacts._ID} 值。 + + +

+

重要的原始聯絡人欄

+

+ {@link android.provider.ContactsContract.RawContacts} 表格中的重要欄列於表 1。 +請詳閱表格下方的注意事項: +

+

+ 表 1.重要的原始聯絡人欄。 +

+ + + + + + + + + + + + + + + + + + + + +
欄名稱用途備註
+ {@link android.provider.ContactsContract.SyncColumns#ACCOUNT_NAME} + + 帳戶類型 (此原始聯絡人的來源) 的帳戶名稱。 + 例如,Google 帳戶的帳戶名稱是裝置擁有者的其中一個 Gmail 地址。 +請參閱下一個項目,進一步瞭解 + {@link android.provider.ContactsContract.SyncColumns#ACCOUNT_TYPE}。 + + + 不同的帳戶類型的名稱格式各不相同。帳戶名稱不一定是電子郵件地址。 + +
+ {@link android.provider.ContactsContract.SyncColumns#ACCOUNT_TYPE} + + 帳戶類型是此原始聯絡人的來源。例如,Google 帳戶的帳戶類型為 com.google。 +一定要將帳戶類型加上您所擁有或控制網域的網域識別碼。 +這樣可以確認您的帳戶類型是唯一的。 + + + 提供聯絡人資料的帳戶類型通常有關聯的同步配接器,可以與聯絡人供應程式同步。 + +
+ {@link android.provider.ContactsContract.RawContactsColumns#DELETED} + + 原始聯絡人的「已刪除」旗標。 + + 此旗標讓聯絡人供應程式可以在內部維護列,直到同步配接器從其伺服器刪除該列,最後從存放庫刪除該列。 + + +
+

備註

+

+ 以下是有關 + {@link android.provider.ContactsContract.RawContacts} 表格的注意事項: +

+
    +
  • + 原始聯絡人的名稱不是儲存於本身在 +{@link android.provider.ContactsContract.RawContacts} 的列,而是儲存在 {@link android.provider.ContactsContract.Data} 表格的 + {@link android.provider.ContactsContract.CommonDataKinds.StructuredName} 列中。 +原始聯絡人在 {@link android.provider.ContactsContract.Data} 表格中只有一列這種類型。 + +
  • +
  • + 注意:如要使用您在原始聯絡人列中自己的帳戶資料,必須先向 {@link android.accounts.AccountManager} 註冊。 +如要註冊,請提示使用者將帳戶類型及其帳戶名稱新增至帳戶清單。 +如果沒有這麼做,聯絡人供應程式將自動刪除您的原始聯絡人列。 + +

    + 例如,如果您希望應用程式維護網域為 {@code com.example.dataservice} 網頁式服務的聯絡人資料,此服務的使用者帳戶是 {@code becky.sharp@dataservice.example.com},這名使用者必須先新增帳戶「類型」({@code com.example.dataservice}) 和帳戶「名稱」 +({@code becky.smart@dataservice.example.com}),您的應用程式才能新增原始聯絡人列。 + + + + 您可以在文件中向使用者說明此需求,或提示使用者同時新增類型和名稱。 +帳戶類型和帳戶名稱在下一節有更詳細的說明。 + +

  • +
+

原始聯絡人資料的來源

+

+ 如要瞭解原始聯絡人的運作方式,假設一位使用者 Emily Dickinson 在其裝置上定義了下列三個使用者帳戶: + +

+
    +
  • emily.dickinson@gmail.com
  • +
  • emilyd@gmail.com
  • +
  • Twitter 帳戶「belle_of_amherst」
  • +
+

+ 此使用者在「帳戶」設定中為這三個帳戶啟用了「同步聯絡人」。 + +

+

+ 假設 Emily Dickinson 開啟瀏覽器視窗,使用 + emily.dickinson@gmail.com登入 Gmail,開啟[聯絡人] 並新增「Thomas Higginson」。 +接著,她使用 + emilyd@gmail.com登入 Gmail,並寄送電子郵件給「Thomas Higginson」(系統已自動將他新增為聯絡人)。 +她也在 Twitter 上關注「colonel_tom」(Thomas Higginson 的 Twitter ID)。 + +

+

+ 聯絡人供應程式建立三個原始聯絡人的方式如下: +

+
    +
  1. + 「Thomas Higginson」的原始聯絡人與 emily.dickinson@gmail.com 相關聯。 + 該使用者帳戶類型為 Google。 +
  2. +
  3. + 「Thomas Higginson」的第二個原始聯絡人與 emilyd@gmail.com 相關聯。 + 該使用者帳戶類型也是 Google。儘管第二個原始聯絡人的名稱與前一個名稱完全相同,但此人是針對不同使用者帳戶所新增的。 + + +
  4. +
  5. + 「Thomas Higginson」的第三個原始聯絡人與「belle_of_amherst」相關聯。該使用者帳戶類型是 Twitter。 + +
  6. +
+

資料

+

+ 如前所述,原始聯絡人的資料是儲存在 + {@link android.provider.ContactsContract.Data} 列,而此列會連結到原始聯絡人的 + _ID 值。這樣讓原始聯絡人的相同資料類型可以有多個執行個體,例如電子郵件地址或電話號碼。 +例如,如果{@code emilyd@gmail.com} 的 "Thomas Higginson" (Thomas Higginson 的原始聯絡人列,與 Google 帳戶 emilyd@gmail.com 關聯) 的住家電子郵件地址為 + thigg@gmail.com,而工作電子郵件地址為 + thomas.higginson@gmail.com,則聯絡人供應程式會儲存這兩個電子郵件地址列,並將兩者連結到原始聯絡人。 + + + +

+

+ 請注意,此單一表格中儲存了不同類型的資料。{@link android.provider.ContactsContract.Data} 表格中可以看到顯示名稱、電話號碼、電子郵件、郵寄地址、相片以及網站詳細資料等列。 + +為了協助管理這些內容, +{@link android.provider.ContactsContract.Data} 表格有一些欄附有描述性名稱,而其他欄則有一般名稱。 +描述性名稱欄的內容有相同的意義 (無論列中的資料類型為何),而一般名稱欄的內容則視資料的類型會有不同的意義。 + + +

+

描述性欄名稱

+

+ 以下提供幾個描述性欄名稱範例: +

+
+
+ {@link android.provider.ContactsContract.Data#RAW_CONTACT_ID} +
+
+ 原始聯絡人 _ID 欄的資料值。 +
+
+ {@link android.provider.ContactsContract.Data#MIMETYPE} +
+
+ 儲存在此列中的資料類型,以自訂 MIME 類型表示。聯絡人供應程式會使用 + {@link android.provider.ContactsContract.CommonDataKinds} 子類別中定義的MIME 類型。 +這些 MIME 類型屬開放原始碼,可以和聯絡人供應程式搭配的任何應用程式或同步配接器都可以加以使用。 + +
+
+ {@link android.provider.ContactsContract.DataColumns#IS_PRIMARY} +
+
+ 如果原始聯絡人的此類型資料列出現多次,則 + {@link android.provider.ContactsContract.DataColumns#IS_PRIMARY} 欄會在包含主要資料類型的資料列加上旗標。 +例如,使用者長按聯絡人的電話號碼並選取 [設為預設值],則包含該號碼的 {@link android.provider.ContactsContract.Data} 列就會將它的 {@link android.provider.ContactsContract.DataColumns#IS_PRIMARY} 欄設為零以外的值。 + + + + +
+
+

一般欄名稱

+

+ 一般欄有 15 個 (名稱為 DATA1 到 + DATA15),系統通常會提供這些欄。另外有 4 個一般欄(SYNC1SYNC4) 只能透過同步配接器使用。 + +不管列中的資料類型為何,一定可以使用一般欄名稱常數。 + +

+

+ DATA1 欄會建立索引。聯絡人供應程式一律會使用此欄的資料,而且供應程式預期此欄為最常查詢的目標。 +例如,在電子郵件列中,此欄內含實際的電子郵件地址。 + +

+

+ 一般來說,DATA15 欄會保留用於儲存「二進位大型物件」(BLOB) 資料,例如相片縮圖。 + +

+

類型特定的欄名稱

+

+ 為了要協助欄處理具有特定類型的列,聯絡人供應程式也提供類型特定的欄名稱常數。這些常數會在 + {@link android.provider.ContactsContract.CommonDataKinds}的子類別中定義。 +常數只是為相同欄名稱指定不同的常數名稱,以協助您存取列中特定類型的資料。 + + +

+

+ 例如,{@link android.provider.ContactsContract.CommonDataKinds.Email} 類別定義了 {@link android.provider.ContactsContract.Data} 列的類型特定欄名稱常數。此列內含 MIME 類型 {@link android.provider.ContactsContract.CommonDataKinds.Email#CONTENT_ITEM_TYPE +Email.CONTENT_ITEM_TYPE}。 + + +類別含有電子郵件地址欄的常數 + {@link android.provider.ContactsContract.CommonDataKinds.Email#ADDRESS}。 + + {@link android.provider.ContactsContract.CommonDataKinds.Email#ADDRESS}的實際值是「data1」。此值與欄的一般名稱相同。 + +

+

+ 注意:如果 + {@link android.provider.ContactsContract.Data} 表格使用供應程式預先定義 MIME 類型的其中一種,請不要將您自訂的資料新增至此表格。 +假如將您自訂的資料新增至此表格,可能會遺失資料或讓供應程式發生故障。 +例如,您不應該將含有 MIME 類型 {@link android.provider.ContactsContract.CommonDataKinds.Email#CONTENT_ITEM_TYPE +Email.CONTENT_ITEM_TYPE} (內含使用者名稱,而不是電子郵件地址) 的列新增至 DATA1 欄。 + +如果您的列使用自訂 MIME 類型,那麼您可以自行定義專屬的類型特定的欄名稱,並按照您的需求使用這些欄。 + +

+

+ 圖 2 顯示描述性欄和資料欄顯示在 + {@link android.provider.ContactsContract.Data} 列的樣式,以及類型特定欄名稱與一般欄名稱的「重疊」方式。 + +

+How type-specific column names map to generic column names +

+ 圖 2.特定類型欄名稱與一般欄名稱。 +

+

特定類型欄名稱類別

+

+ 表 2 列出最常用的特定類型欄名稱類別: +

+

+ 表 2.特定類型欄名稱類別

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
對應類別資料類型備註
{@link android.provider.ContactsContract.CommonDataKinds.StructuredName}原始聯絡人 (與此資料列相關聯) 的名稱資料。原始聯絡人只有一列此資料。
{@link android.provider.ContactsContract.CommonDataKinds.Photo}原始聯絡人 (與此資料列相關聯) 的主要相片。原始聯絡人只有一列此資料。
{@link android.provider.ContactsContract.CommonDataKinds.Email}原始聯絡人 (與此資料列相關聯) 的電子郵件地址。原始聯絡人可以有多個電子郵件地址。
{@link android.provider.ContactsContract.CommonDataKinds.StructuredPostal}原始聯絡人 (與此資料列相關聯) 的郵寄地址。原始聯絡人可以有多個郵寄地址。
{@link android.provider.ContactsContract.CommonDataKinds.GroupMembership}將原始聯絡人連結至聯絡人供應程式內其中一個群組的識別碼。 + 群組是帳戶類型和帳戶名稱的選用功能。如要進一步瞭解群組,請參閱聯絡人群組。 + +
+

聯絡人

+

+ 聯絡人供應程式會合併所有帳戶類型和帳戶名稱的原始聯絡人,而成為「聯絡人」。 +藉此協助使用者顯示及修改針對某個人所收集的所有資料。 +聯絡人供應程式負責建立新的聯絡人列,以及彙總原始聯絡人與現有的聯絡人列。 +應用程式或同步配接器都可以新增聯絡人,而聯絡人列中的某些欄屬於唯讀性質。 + +

+

+ 注意:如果您嘗試使用 + {@link android.content.ContentResolver#insert(Uri,ContentValues) insert()} 將聯絡人新增至聯絡人供應程式,將會收到 {@link java.lang.UnsupportedOperationException} 例外狀況。 +如果您試著更新列為「唯讀」的欄,則會略過此更新作業。 + +

+

+ 如果新增的原始聯絡人與現有的聯絡人都不相符,聯絡人供應程式就會建立新的聯絡人。 +如果現有原始聯絡人的資料在變更後,不再與之前附加的聯絡人相符,則供應程式也會建立新的聯絡人。 + +如果應用程式或同步配接器建立的新原始聯絡人「符合」現有的聯絡人,則新的原始聯絡人會彙總為現有的聯絡人。 + + +

+

+ 聯絡人供應程式使用 {@link android.provider.ContactsContract.Contacts Contacts} 表格中的聯絡人 + _ID 欄,將聯絡人列連結到其原始聯絡人列。 +原始聯絡人表格 + {@link android.provider.ContactsContract.RawContacts} 的 CONTACT_ID 欄,內含聯絡人列 (與每個原始聯絡人列相關聯) 的 _ID 值。 + +

+

+ {@link android.provider.ContactsContract.Contacts} 表格也有 +{@code android.provider.ContactsContract.ContactsColumns#LOOKUP_KEY} 欄,此為聯絡人列的「永久」連結。 +因為聯絡人供應程式會自動維護聯絡人,它會變更聯絡人列的 {@code android.provider.BaseColumns#_ID} 值,以回應彙總或同步操作。 + +即使發生這種情況,與聯絡人的 +{@code android.provider.ContactsContract.ContactsColumns#LOOKUP_KEY} 合併的內容 URI {@link android.provider.ContactsContract.Contacts#CONTENT_LOOKUP_URI} 仍會指向聯絡人列,因此,您可以使用 +{@code android.provider.ContactsContract.ContactsColumns#LOOKUP_KEY} + 來維護「常用聯絡人」等聯絡人的連結。 + +此欄有自己的格式,與 {@code android.provider.BaseColumns#_ID} 欄的格式無關。 + +

+

+ 圖 3 說明這三個主要表格彼此之間的關係。 +

+Contacts provider main tables +

+ 圖 3.聯絡人、原始聯絡人以及詳細資料表格的關係。 +

+

來自同步配接器的資料

+

+ 使用者將聯絡人資料直接輸入裝置,但資料也會過「同步配接器」從網路服務流入聯絡人供應程式 (同步配接器會自動將資料在裝置和服務之間傳輸)。 + +同步配接器受到系統的控制、在背景執行,並且會呼叫 {@link android.content.ContentResolver} 方法來管理資料。 + + +

+

+ 在 Android 中,與同步配接器搭配運作的網路服務,是透過帳戶類型加以識別。 + 每個同步配接器會與一種帳戶類型搭配,但可以支援該類型的多個帳戶名稱。 +帳戶類型和帳戶名稱在原始聯絡人資料的來源中會有更詳細的說明。 + 下列定義提供更詳細的資訊,說明帳戶類型和名稱與同步配接器和服務之間的關係。 + +

+
+
+ 帳戶類型 +
+
+ 識別使用者儲存資料的服務。在大部分情況下,使用者必須經過服務的驗證。 +例如,Google 聯絡人是一種帳戶類型,由程式碼 google.com 加以識別。 +此值會對應到 + {@link android.accounts.AccountManager} 所使用的帳戶類型。 +
+
+ 帳戶名稱 +
+
+ 識別特定帳戶或帳戶類型的登入。Google 聯絡人帳戶與 Google 帳戶相同,都是使用電子郵件地址做為帳戶名稱。 + + 其他服務可能是以單一文字使用者名稱或數值 ID 做為帳戶名稱。 +
+
+

+ 帳戶類型不必是唯一的。使用者可以設定多個 Google 聯絡人帳戶,並將其資料下載至聯絡人供應程式;如果使用者有一組個人帳戶名稱的個人聯絡人,還有另一組工作用的聯絡人,就可能發生此情形。 + +帳戶名稱通常是唯一的。 +兩者加起來,就可以識別聯絡人供應程式和外部服務之間的特定資料流程。 + +

+

+ 如果您要將服務的資料傳輸到聯絡人供應程式,則需要編寫您自己的同步配接器。 +如要進一步瞭解同步配接器,請參閱聯絡人供應程式同步配接器。 + +

+

+ 圖 4 顯示聯絡人供應程式在人員相關的資料流程中所扮演的角色。 +在標記為「同步配接器」的方塊中,每個配接器都以其帳戶類型做為標籤。 +

+Flow of data about people +

+ 圖 4.聯絡人供應程式資料流程。 +

+

必要權限

+

+ 要存取聯絡人供應程式的應用程式必須要求下列權限: + +

+
+
一或多份表格的讀取權限
+
+ {@link android.Manifest.permission#READ_CONTACTS},在 + AndroidManifest.xml 的 + + <uses-permission> 元素中,以 + <uses-permission android:name="android.permission.READ_CONTACTS"> 指定。 +
+
一或多份表格的寫入權限
+
+ {@link android.Manifest.permission#WRITE_CONTACTS},在 +AndroidManifest.xml 的 + + <uses-permission> 元素中,以 +<uses-permission android:name="android.permission.WRITE_CONTACTS"> 指定。 +
+
+

+ 這些權限不會延伸到使用者設定檔資料。如要瞭解使用者設定檔及其必要權限,請參閱使用者設定檔。 + + +

+

+ 請記住,使用者的聯絡人資料是個人的敏感資訊。使用者很關心隱私權相關的問題,因此不希望應用程式收集使用者本身或其聯絡人的相關資訊。 + + 如果沒有明確說明為何需要存取使用者的聯絡人資料,使用者可能會給您的應用程式低評分,或直接拒絕安裝。 + +

+

使用者設定檔

+

+ {@link android.provider.ContactsContract.Contacts} 表格中有一列內含裝置使用者的設定檔資料, +此資料描述裝置的 user,而不是其中一位使用者的聯絡人。 +針對使用設定檔的每個系統,設定檔聯絡人列會連結到原始聯絡人列。 + + 每個設定檔原始聯絡人列可以有多個資料列。{@link android.provider.ContactsContract.Profile} 類別中提供存取使用者設定檔的常數。 + +

+

+ 存取使用者設定檔需要特殊權限。除了 +{@link android.Manifest.permission#READ_CONTACTS} 和 +{@link android.Manifest.permission#WRITE_CONTACTS} 的讀取和寫入權限以外,存取使用者設定檔還分別需要 {@code android.Manifest.permission#READ_PROFILE} 讀取權限和 +{@code android.Manifest.permission#WRITE_PROFILE} 寫入權限。 + + +

+

+ 請務必將使用者的設定檔視為敏感資訊。 +{@code android.Manifest.permission#READ_PROFILE}權限可以讓您存取裝置上使用者的身分識別資料。 +請務必在應用程式的簡介中告訴使用者,為何需要使用者設定檔存取權限。 + +

+

+ 如要擷取內含使用者的設定檔的聯絡人列,請呼叫 {@link android.content.ContentResolver#query(Uri,String[], String, String[], String) +ContentResolver.query()}。 +將內容 URI 設為 +{@link android.provider.ContactsContract.Profile#CONTENT_URI},並且不要提供任何選取條件。 +您也可以使用此內容 URI 做為擷取原始聯絡人或設定檔資料的基礎 URI。 +例如,以下程式碼片段會擷取設定檔資料: +

+
+// Sets the columns to retrieve for the user profile
+mProjection = new String[]
+    {
+        Profile._ID,
+        Profile.DISPLAY_NAME_PRIMARY,
+        Profile.LOOKUP_KEY,
+        Profile.PHOTO_THUMBNAIL_URI
+    };
+
+// Retrieves the profile from the Contacts Provider
+mProfileCursor =
+        getContentResolver().query(
+                Profile.CONTENT_URI,
+                mProjection ,
+                null,
+                null,
+                null);
+
+

+ 注意:如果您擷取了多個聯絡人列,而想要判斷其中之一是否為使用者設定檔,請測試該列的 +{@link android.provider.ContactsContract.ContactsColumns#IS_USER_PROFILE} 欄。 +如果聯絡人為使用者設定檔,此欄會設為「1」。 + +

+

聯絡人供應程式中繼資料

+

+ 聯絡人供應程式管理的資料可以追蹤存放庫中聯絡人資料的 +狀態。存放庫相關的中繼資料儲存在不同的位置,包括 +「原始聯絡人」、「資料」以及「聯絡人」表格列, +{@link android.provider.ContactsContract.Settings} 表格以及 +{@link android.provider.ContactsContract.SyncState} 表格。以下表格說明 +這些中繼資料的作用: +

+

+ 表 3.聯絡人供應程式的中繼資料

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
表格意義
{@link android.provider.ContactsContract.RawContacts}{@link android.provider.ContactsContract.SyncColumns#DIRTY}「0」:上次同步後沒有變更。 + 標記裝置上經過變更,且必須同步回伺服器的原始聯絡人。 +Android 應用程式更新列時,聯絡人供應程式會自動設定此值。 + +

+ 修改原始聯絡人或資料表格的同步配接器一律會將字串{@link android.provider.ContactsContract#CALLER_IS_SYNCADAPTER} 附加到其使用的內容 URI, + +藉此防止供應程式將列標記為已變更 (dirty)。 + 否則,同步配接器修改會顯示為本機修改,因而傳送到伺服器,儘管伺服器才是修改的來源。 + +

+
「1」:上次同步後已變更,需要同步回伺服器。
{@link android.provider.ContactsContract.RawContacts}{@link android.provider.ContactsContract.SyncColumns#VERSION}此列的版本號碼。 + 每當列或其相關資料變更時,聯絡人供應程式都會自動增加此值。 + +
{@link android.provider.ContactsContract.Data}{@link android.provider.ContactsContract.DataColumns#DATA_VERSION}此列的版本號碼。 + 每當資料列變更時,聯絡人供應程式都會自動增加此值。 + +
{@link android.provider.ContactsContract.RawContacts}{@link android.provider.ContactsContract.SyncColumns#SOURCE_ID} + 可唯一識別帳戶原始聯絡人的字串值(此帳戶是以此字串值所建立)。 + + + 同步配接器建立新的原始聯絡人時,此欄應設為原始聯絡人的伺服器唯一 ID。 +Android 應用程式建立新的原始聯絡人時,應用程式應將此欄保留空白。 +這樣會提供訊號給同步配接器,要在伺服器上建立新的原始聯絡人,然後取得 +{@link android.provider.ContactsContract.SyncColumns#SOURCE_ID} 值。 + +

+ 尤其是來源 ID 對於每個帳戶類型必須具備唯一性,在同步時應該很穩定: + +

+
    +
  • + 唯一:帳戶的每個原始聯絡人都必須有自己的來源 ID。如果沒有強制執行此條件,則聯絡人應用程式會發生問題。 + + 請注意,同一個帳戶「類型」的兩個原始聯絡人可能會有相同的來源 ID。 +例如,{@code emily.dickinson@gmail.com} 帳戶的原始聯絡人「Thomas Higginson」與 + {@code emilyd@gmail.com} 帳戶的原始聯絡人「Thomas Higginson」的來源 ID相同。 + + +
  • +
  • + 穩定:來源 ID 是線上服務中原始聯絡人資料的永久部分。 +例如,如果使用者從應用程式設定中清除「聯絡人儲存空間」,然後重新同步,則還原的原始聯絡人的來源 ID 應該與先前相同。 + +如果沒有強制執行此條件,捷徑將停止運作。 + +
  • +
+
{@link android.provider.ContactsContract.Groups}{@link android.provider.ContactsContract.GroupsColumns#GROUP_VISIBLE}「0」:Android 應用程式 UI 中不應顯示此群組中的聯絡人。 + 有些伺服器可以讓使用者隱藏某些群組中的聯絡人,此欄的設計提供了與這類伺服器的相容性。 + +
「1」:應用程式 UI 中會顯示此群組中的聯絡人。
{@link android.provider.ContactsContract.Settings} + {@link android.provider.ContactsContract.SettingsColumns#UNGROUPED_VISIBLE} + 「0」:針對此帳戶和帳戶類型,Android 應用程式 UI 中不會顯示不屬於群組的聯絡人。 + + + 如果沒有任何原始聯絡人屬於某個群組,則聯絡人預設為不可見(原始聯絡人的群組成員資格是由{@link android.provider.ContactsContract.Data} 表格中的一或多個 +{@link android.provider.ContactsContract.CommonDataKinds.GroupMembership} 列所指出)。 + + + 在 {@link android.provider.ContactsContract.Settings} 表格列中為帳戶類型和帳戶設定此旗標,可以強制讓不屬於任何群組的聯絡人成為可見的。 + + 此旗標的其中一個用途是,顯示伺服器中不屬於任何群組的聯絡人。 +
+ 「1」:針對此帳戶和帳戶類型,應用程式 UI 中會顯示不屬於群組的聯絡人。 + +
{@link android.provider.ContactsContract.SyncState}(全部) + 使用此表格儲存同步配接器的中繼資料。 + + 使用此表格,您可以儲存同步狀態,以及其他與同步相關、會永久放在裝置上的資料。 + +
+

聯絡人供應程式存取

+

+ 本節說明從聯絡人供應程式存取資料的指導方針,著重於以下各項: + +

+
    +
  • + 實體查詢。 +
  • +
  • + 批次修改。 +
  • +
  • + 使用意圖進行擷取及修改。 +
  • +
  • + 資料完整性。 +
  • +
+

+ 從同步配接器進行修改,在聯絡人供應程式同步配接器中也提供更詳細的資訊。 + +

+

實體查詢

+

+ 因為聯絡人供應程式為階層式表格,擷取某一列及連結至此列的所有「子」列時非常實用。 +例如,如要顯示人員的所有資訊,您可能要擷取單一 + {@link android.provider.ContactsContract.Contacts} 列的 +所有 {@link android.provider.ContactsContract.RawContacts} 列,或單一 +{@link android.provider.ContactsContract.RawContacts} 列的所有 +{@link android.provider.ContactsContract.CommonDataKinds.Email} 列。 +為了協助此操作,聯絡人供應程式提供實體建構,其運作方式就像是資料庫結合各個表格一樣。 + + +

+

+ 實體就像是一份表格,由上層表格及其下層表格中的選取欄所組成。 + 查詢實體時,您會提供投影 (projection) 和搜尋條件根據該實體可用的欄。 +結果會是 {@link android.database.Cursor},擷取到的每個下層表格列在其中都會有一列。 +例如,如果您查詢 +{@link android.provider.ContactsContract.Contacts.Entity} 的聯絡人名稱,並且查詢所有 {@link android.provider.ContactsContract.CommonDataKinds.Email} 列中該名稱的所有原始聯絡人,則會取回 {@link android.database.Cursor},每個 {@link android.provider.ContactsContract.CommonDataKinds.Email} 列都會有一列。 + + + +

+

+ 實體簡化查詢。使用實體,您可以一次擷取聯絡人或原始聯絡人的所有聯絡人資料,而不用先查詢父項表格以取得 ID,再以此 ID 查詢子項表格。另外,聯絡人供應程式會在單一交易中處理針對實體的查詢,以確保所擷取的資料在內部的一致性。 + + + + +

+

+ 注意:實體通常不會包含上層表格和下層表格的所有欄。 +如果您嘗試使用的欄名稱未列在實體的欄名稱常數中,將會收到 {@link java.lang.Exception}。 + +

+

+ 以下程式碼片段展示如何擷取一位聯絡人的所有原始聯絡人列。此程式碼片段屬於大型應用程式的一部分,此應用程式有兩個 Activity:「主要」和「詳細」。 +主要 Activity 會顯示聯絡人列的清單,當使用者選取其中一項時,此 Activity 會將其 ID 傳送給詳細 Activity。 + +詳細 Activity 會使用 {@link android.provider.ContactsContract.Contacts.Entity},針對所選取的聯絡人,顯示與其關聯的所有原始聯絡人的所有資料列。 + + +

+

+ 此程式碼片段是取自「詳細」Activity: +

+
+...
+    /*
+     * Appends the entity path to the URI. In the case of the Contacts Provider, the
+     * expected URI is content://com.google.contacts/#/entity (# is the ID value).
+     */
+    mContactUri = Uri.withAppendedPath(
+            mContactUri,
+            ContactsContract.Contacts.Entity.CONTENT_DIRECTORY);
+
+    // Initializes the loader identified by LOADER_ID.
+    getLoaderManager().initLoader(
+            LOADER_ID,  // The identifier of the loader to initialize
+            null,       // Arguments for the loader (in this case, none)
+            this);      // The context of the activity
+
+    // Creates a new cursor adapter to attach to the list view
+    mCursorAdapter = new SimpleCursorAdapter(
+            this,                        // the context of the activity
+            R.layout.detail_list_item,   // the view item containing the detail widgets
+            mCursor,                     // the backing cursor
+            mFromColumns,                // the columns in the cursor that provide the data
+            mToViews,                    // the views in the view item that display the data
+            0);                          // flags
+
+    // Sets the ListView's backing adapter.
+    mRawContactList.setAdapter(mCursorAdapter);
+...
+@Override
+public Loader<Cursor> onCreateLoader(int id, Bundle args) {
+
+    /*
+     * Sets the columns to retrieve.
+     * RAW_CONTACT_ID is included to identify the raw contact associated with the data row.
+     * DATA1 contains the first column in the data row (usually the most important one).
+     * MIMETYPE indicates the type of data in the data row.
+     */
+    String[] projection =
+        {
+            ContactsContract.Contacts.Entity.RAW_CONTACT_ID,
+            ContactsContract.Contacts.Entity.DATA1,
+            ContactsContract.Contacts.Entity.MIMETYPE
+        };
+
+    /*
+     * Sorts the retrieved cursor by raw contact id, to keep all data rows for a single raw
+     * contact collated together.
+     */
+    String sortOrder =
+            ContactsContract.Contacts.Entity.RAW_CONTACT_ID +
+            " ASC";
+
+    /*
+     * Returns a new CursorLoader. The arguments are similar to
+     * ContentResolver.query(), except for the Context argument, which supplies the location of
+     * the ContentResolver to use.
+     */
+    return new CursorLoader(
+            getApplicationContext(),  // The activity's context
+            mContactUri,              // The entity content URI for a single contact
+            projection,               // The columns to retrieve
+            null,                     // Retrieve all the raw contacts and their data rows.
+            null,                     //
+            sortOrder);               // Sort by the raw contact ID.
+}
+
+

+ 載入完成時,{@link android.app.LoaderManager} 會呼叫 {@link android.app.LoaderManager.LoaderCallbacks#onLoadFinished(Loader, D) + onLoadFinished()} 的回呼。 +此方法的其中一個傳入引數是內含查詢結果的 + {@link android.database.Cursor}。在您的應用程式中,您可以從這個 {@link android.database.Cursor} 取得資料,然後加以顯示或進一步處理。 + +

+

批次修改

+

+ 您應該儘可能透過建立 + {@link android.content.ContentProviderOperation} 物件的{@link java.util.ArrayList},然後呼叫 + {@link android.content.ContentResolver#applyBatch(String, ArrayList) applyBatch()},以「批次模式」在聯絡人供應程式中進行資料的插入、更新以及刪除。 +因為聯絡人供應程式會在單一交易中執行 +{@link android.content.ContentResolver#applyBatch(String, ArrayList) applyBatch()} 的所有操作,所以您所做的修改不會讓聯絡人存放庫處於不一致的狀態。 + + +批次修改同時也有助於插入原始聯絡人及其詳細資料。 + +

+

+ 注意:如要修改「單一」原始聯絡人,請考慮將意圖傳送到裝置的聯絡人應用程式,而不要在您的應用程式中處理修改操作。這些動作在使用意圖擷取和修改中有更詳細的資料。 + + + +

+

降伏點

+

+ 包含大量操作的批次修改可能會封鎖其他處理程序,導致整體的使用者體驗不良。 +如要將您想要執行的所有修改,儘可能安排在較少的清單中執行,同時要避免這些修改讓系統無法進行其他操作,則應該要為一或多個操作設定「降伏點」。 + + + 降伏點是一個 {@link android.content.ContentProviderOperation} 物件,而且其 +{@link android.content.ContentProviderOperation#isYieldAllowed()} 值是設為 +true。當聯絡人供應程式遇到降伏點時,會暫停它的工作,以便讓其他處理程序執行,並關閉目前的交易。 +供應程式再次啟動時,會繼續 {@link java.util.ArrayList} 中的下一項操作,並啟動新的交易。 + + +

+

+ 降伏點會讓每次呼叫 +{@link android.content.ContentResolver#applyBatch(String, ArrayList) applyBatch()} 產生一個以上的交易。基於這項原因,您必須將上次操作的降伏點設為一組相關的列。 + + 例如,您應該為以下兩種上次操作設定降伏點:新增原始聯絡人列及其相關資料列的一組動作,或與單一聯絡人相關的一組列。 + + +

+

+ 降伏點也是微型操作的單位。兩個降伏點之間的所有存取會以單一單元來看待為成功或失敗。 +如果沒有設定任何降伏點,則最小的微型操作就是整批操作。 +如果使用降伏點,您可以防止操作降低系統效能,同時確保操作的子集是微型操作。 + + +

+

修改反向參考

+

+ 以一組 + {@link android.content.ContentProviderOperation} 物件插入新的原始聯絡人及其關聯的資料列時,您必須透過插入原始聯絡人的 + {@code android.provider.BaseColumns#_ID} 值做為 +{@link android.provider.ContactsContract.DataColumns#RAW_CONTACT_ID} 值,將資料列連結到原始聯絡人列。 +不過,此 +值在您為資料列建立 {@link android.content.ContentProviderOperation} +時並不存在,這是因為您尚未替原始聯絡人列 +套用 {@link android.content.ContentProviderOperation}。為解決此狀況,{@link android.content.ContentProviderOperation.Builder} 類別提供了 +{@link android.content.ContentProviderOperation.Builder#withValueBackReference(String, int) withValueBackReference()} 這個方法。 + + 此方法可以讓您使用之前操作的結果來插入或修改欄。 + +

+

+ {@link android.content.ContentProviderOperation.Builder#withValueBackReference(String, int) withValueBackReference()} +方法有兩個引數: +

+
+
+ key +
+
+ 鍵值對的鍵。此引數的值是您要修改表格中的欄名稱。 + +
+
+ previousResult +
+
+ +{@link android.content.ContentResolver#applyBatch(String, ArrayList) applyBatch()} 中的 +{@link android.content.ContentProviderResult} 物件以 0 開始的陣列索引值。套用批次操作時,每次操作結果都會儲存在結果的中繼陣列。 + +previousResult 值是這些結果的其中一個索引,這些結果是以 key值擷取並加以儲存。 + +這樣可以讓您插入新的原始聯絡人記錄,並取得其 +{@code android.provider.BaseColumns#_ID} 值,然後在您新增 {@link android.provider.ContactsContract.Data} 列時,做為此值的「反向參考」。 + +

+ 您首次呼叫 + {@link android.content.ContentResolver#applyBatch(String, ArrayList) applyBatch()} 時會建立整個結果陣列,此陣列的大小等同於您所提供 + {@link android.content.ContentProviderOperation} 物件的{@link java.util.ArrayList} 大小。 +不過,結果陣列中的所有元素會設為 null,如果您嘗試要針對尚未套用的操作結果製作反向參考, +{@link android.content.ContentProviderOperation.Builder#withValueBackReference(String, int) withValueBackReference()}則會擲回 {@link java.lang.Exception}。 + + + + +

+
+
+

+ 以下程式碼片段展示如何插入大量新的原始聯絡人和資料。其中包括建立降伏點和使用反向參考的程式碼。 +此程式碼片段是 createContacEntry() 方法的擴充版本。而這個方法是 + + Contact Manager 範例應用程式中 + ContactAdder 類別的一部分。 + +

+

+ 第一個程式碼片段會從 UI 擷取聯絡人資料。此時,使用者已經選好要加入的新原始聯絡人帳戶。 + +

+
+// Creates a contact entry from the current UI values, using the currently-selected account.
+protected void createContactEntry() {
+    /*
+     * Gets values from the UI
+     */
+    String name = mContactNameEditText.getText().toString();
+    String phone = mContactPhoneEditText.getText().toString();
+    String email = mContactEmailEditText.getText().toString();
+
+    int phoneType = mContactPhoneTypes.get(
+            mContactPhoneTypeSpinner.getSelectedItemPosition());
+
+    int emailType = mContactEmailTypes.get(
+            mContactEmailTypeSpinner.getSelectedItemPosition());
+
+

+ 下一個程式碼片段的操作會將原始聯絡人列插入 +{@link android.provider.ContactsContract.RawContacts} 表格: +

+
+    /*
+     * Prepares the batch operation for inserting a new raw contact and its data. Even if
+     * the Contacts Provider does not have any data for this person, you can't add a Contact,
+     * only a raw contact. The Contacts Provider will then add a Contact automatically.
+     */
+
+     // Creates a new array of ContentProviderOperation objects.
+    ArrayList<ContentProviderOperation> ops =
+            new ArrayList<ContentProviderOperation>();
+
+    /*
+     * Creates a new raw contact with its account type (server type) and account name
+     * (user's account). Remember that the display name is not stored in this row, but in a
+     * StructuredName data row. No other data is required.
+     */
+    ContentProviderOperation.Builder op =
+            ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI)
+            .withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, mSelectedAccount.getType())
+            .withValue(ContactsContract.RawContacts.ACCOUNT_NAME, mSelectedAccount.getName());
+
+    // Builds the operation and adds it to the array of operations
+    ops.add(op.build());
+
+

+ 接著,程式碼會建立顯示名稱、電話以及電子郵件列的資料列。 +

+

+ 每項操作建立器物件會使用 +{@link android.content.ContentProviderOperation.Builder#withValueBackReference(String, int) withValueBackReference()}來取得 +{@link android.provider.ContactsContract.DataColumns#RAW_CONTACT_ID}。 +參照會指向第一項操作中的 {@link android.content.ContentProviderResult} 物件 (第一項操作會新增原始聯絡人列,並傳回其新的 {@code android.provider.BaseColumns#_ID} 值)。 + + +因此,每個資料列會透過其 +{@link android.provider.ContactsContract.DataColumns#RAW_CONTACT_ID} 自動連結到它所屬的新 {@link android.provider.ContactsContract.RawContacts} 列。 + +

+

+ 新增電子郵件列的 {@link android.content.ContentProviderOperation.Builder} 物件會帶有 {@link android.content.ContentProviderOperation.Builder#withYieldAllowed(boolean) + withYieldAllowed()} 旗標,而這會設定降伏點: + +

+
+    // Creates the display name for the new raw contact, as a StructuredName data row.
+    op =
+            ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
+            /*
+             * withValueBackReference sets the value of the first argument to the value of
+             * the ContentProviderResult indexed by the second argument. In this particular
+             * call, the raw contact ID column of the StructuredName data row is set to the
+             * value of the result returned by the first operation, which is the one that
+             * actually adds the raw contact row.
+             */
+            .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
+
+            // Sets the data row's MIME type to StructuredName
+            .withValue(ContactsContract.Data.MIMETYPE,
+                    ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE)
+
+            // Sets the data row's display name to the name in the UI.
+            .withValue(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, name);
+
+    // Builds the operation and adds it to the array of operations
+    ops.add(op.build());
+
+    // Inserts the specified phone number and type as a Phone data row
+    op =
+            ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
+            /*
+             * Sets the value of the raw contact id column to the new raw contact ID returned
+             * by the first operation in the batch.
+             */
+            .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
+
+            // Sets the data row's MIME type to Phone
+            .withValue(ContactsContract.Data.MIMETYPE,
+                    ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE)
+
+            // Sets the phone number and type
+            .withValue(ContactsContract.CommonDataKinds.Phone.NUMBER, phone)
+            .withValue(ContactsContract.CommonDataKinds.Phone.TYPE, phoneType);
+
+    // Builds the operation and adds it to the array of operations
+    ops.add(op.build());
+
+    // Inserts the specified email and type as a Phone data row
+    op =
+            ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
+            /*
+             * Sets the value of the raw contact id column to the new raw contact ID returned
+             * by the first operation in the batch.
+             */
+            .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
+
+            // Sets the data row's MIME type to Email
+            .withValue(ContactsContract.Data.MIMETYPE,
+                    ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE)
+
+            // Sets the email address and type
+            .withValue(ContactsContract.CommonDataKinds.Email.ADDRESS, email)
+            .withValue(ContactsContract.CommonDataKinds.Email.TYPE, emailType);
+
+    /*
+     * Demonstrates a yield point. At the end of this insert, the batch operation's thread
+     * will yield priority to other threads. Use after every set of operations that affect a
+     * single contact, to avoid degrading performance.
+     */
+    op.withYieldAllowed(true);
+
+    // Builds the operation and adds it to the array of operations
+    ops.add(op.build());
+
+

+ 最後一個程式碼片段顯示 +{@link android.content.ContentResolver#applyBatch(String, ArrayList) applyBatch()} 的呼叫,以插入新的原始聯絡人和資料列。 + +

+
+    // Ask the Contacts Provider to create a new contact
+    Log.d(TAG,"Selected account: " + mSelectedAccount.getName() + " (" +
+            mSelectedAccount.getType() + ")");
+    Log.d(TAG,"Creating contact: " + name);
+
+    /*
+     * Applies the array of ContentProviderOperation objects in batch. The results are
+     * discarded.
+     */
+    try {
+
+            getContentResolver().applyBatch(ContactsContract.AUTHORITY, ops);
+    } catch (Exception e) {
+
+            // Display a warning
+            Context ctx = getApplicationContext();
+
+            CharSequence txt = getString(R.string.contactCreationFailure);
+            int duration = Toast.LENGTH_SHORT;
+            Toast toast = Toast.makeText(ctx, txt, duration);
+            toast.show();
+
+            // Log exception
+            Log.e(TAG, "Exception encountered while inserting contact: " + e);
+    }
+}
+
+

+ 批次操作也可以讓您實作開放式並行存取控制,此方法可以在套用修改交易時,不需要鎖定底層存放庫。 + + 如要使用此方法,您要套用交易,然後檢查同時發生的其他修改操作。 +如果您發現不一致的修改,請將交易復原並加以重試。 + +

+

+ 開放式並行存取控制很適合用在行動裝置,這是因為行動裝置一次只會有一位使用者,而且很少會發生同時存取資料存放庫的情形。 +由於不會使用到鎖定,因此您不必花時間在設定鎖定,或等待其他交易釋放鎖定。 + +

+

+ 如要在更新單一 + {@link android.provider.ContactsContract.RawContacts} 列時使用開放式並行存取控制,請遵循以下步驟: +

+
    +
  1. + 隨著您要擷取的其他資料,一起擷取原始聯絡人的 {@link android.provider.ContactsContract.SyncColumns#VERSION} 欄。 + +
  2. +
  3. + 使用 + {@link android.content.ContentProviderOperation#newAssertQuery(Uri)} 方法建立適合用於強制執行限制的{@link android.content.ContentProviderOperation.Builder} 物件。 +如果是內容 URI,請使用 {@link android.provider.ContactsContract.RawContacts#CONTENT_URI +RawContacts.CONTENT_URI} 並附加原始聯絡人的 {@code android.provider.BaseColumns#_ID}。 + + +
  4. +
  5. + 如果是 {@link android.content.ContentProviderOperation.Builder} 物件,請呼叫 {@link android.content.ContentProviderOperation.Builder#withValue(String, Object) + withValue()},以比較 {@link android.provider.ContactsContract.SyncColumns#VERSION} 欄和您剛才擷取的版本號碼。 + + +
  6. +
  7. + 如果是相同的 {@link android.content.ContentProviderOperation.Builder},請呼叫 {@link android.content.ContentProviderOperation.Builder#withExpectedCount(int) +withExpectedCount()} 以確保此判斷提示只測試一列。 + +
  8. +
  9. + 呼叫 {@link android.content.ContentProviderOperation.Builder#build()} 以建立 +{@link android.content.ContentProviderOperation} 物件,然後將此物件新增為您傳送到 + {@link android.content.ContentResolver#applyBatch(String, ArrayList) applyBatch()} 的第一個 {@link java.util.ArrayList} 物件。 + +
  10. +
  11. + 套用批次交易。 +
  12. +
+

+ 如果在您讀取原始聯絡人列和嘗試加以修改之間,有另一項操作要加以更新,則「判斷提示」{@link android.content.ContentProviderOperation} 將會失敗,而且整個批次的操作將會退出。 + +您之後可以選擇重試此批次作業或採取其他動作。 + +

+

+ 以下程式碼片段展示如何在使用 {@link android.content.CursorLoader} 查詢單一原始聯絡人後,建立「判斷提示」 +{@link android.content.ContentProviderOperation}: + +

+
+/*
+ * The application uses CursorLoader to query the raw contacts table. The system calls this method
+ * when the load is finished.
+ */
+public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
+
+    // Gets the raw contact's _ID and VERSION values
+    mRawContactID = cursor.getLong(cursor.getColumnIndex(BaseColumns._ID));
+    mVersion = cursor.getInt(cursor.getColumnIndex(SyncColumns.VERSION));
+}
+
+...
+
+// Sets up a Uri for the assert operation
+Uri rawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, mRawContactID);
+
+// Creates a builder for the assert operation
+ContentProviderOperation.Builder assertOp = ContentProviderOperation.netAssertQuery(rawContactUri);
+
+// Adds the assertions to the assert operation: checks the version and count of rows tested
+assertOp.withValue(SyncColumns.VERSION, mVersion);
+assertOp.withExpectedCount(1);
+
+// Creates an ArrayList to hold the ContentProviderOperation objects
+ArrayList ops = new ArrayList<ContentProviderOperationg>;
+
+ops.add(assertOp.build());
+
+// You would add the rest of your batch operations to "ops" here
+
+...
+
+// Applies the batch. If the assert fails, an Exception is thrown
+try
+    {
+        ContentProviderResult[] results =
+                getContentResolver().applyBatch(AUTHORITY, ops);
+
+    } catch (OperationApplicationException e) {
+
+        // Actions you want to take if the assert operation fails go here
+    }
+
+

使用意圖進行擷取及修改

+

+ 將意圖傳送給裝置的聯絡人應用程式,可讓您間接存取聯絡人供應程式。 +意圖會啟動裝置的聯絡人應用程式 UI,使用者可以在此執行與聯絡人相關的工作。 +透過此類型的存取方式,使用者可以: +

    +
  • 從清單挑選聯絡人,並將它傳送給應用程式以進行其他操作。
  • +
  • 編輯現有的聯絡人資料。
  • +
  • 為使用者的任何帳戶插入新的原始聯絡人。
  • +
  • 刪除聯絡人或聯絡人資料。
  • +
+

+ 如果使用者正在插入或更新資料,您可以先收集資料,然後讓它成為意圖的一部分加以傳送。 + +

+

+ 當您透過裝置的聯絡人應用程式使用意圖來存取聯絡人供應程式時,不需要自已撰寫存取供應程式的 UI 或程式碼。 +您也不需要要求供應程式的讀取或寫入權限。 +裝置的聯絡人應用程式可以將某個聯絡人的讀取權限委派給您,而且因為是透過另一個應用程式對供應程式進行修改,所以不需要具備寫入權限。 + + +

+

+ 傳送意圖以存取供應程式的一般流程在內容供應程式基本概念指南的「透過意圖存取資料」中有詳細的說明。 + +表 4 摘要說明您可以在工作中使用的動作、MIME 類型以及資料值,而您可以和 + {@link android.content.Intent#putExtra(String, String) putExtra()} 搭配使用的額外值則列於 {@link android.provider.ContactsContract.Intents.Insert} 的參考文件: + + + +

+

+ 表 4.聯絡人供應程式意圖。 +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
工作動作資料MIME 類型備註
從清單挑選聯絡人{@link android.content.Intent#ACTION_PICK} + 可以是以下其中一種: +
    +
  • +{@link android.provider.ContactsContract.Contacts#CONTENT_URI Contacts.CONTENT_URI},可顯示聯絡人的清單。 + +
  • +
  • +{@link android.provider.ContactsContract.CommonDataKinds.Phone#CONTENT_URI Phone.CONTENT_URI},可顯示原始聯絡人的電話號碼清單。 + +
  • +
  • +{@link android.provider.ContactsContract.CommonDataKinds.StructuredPostal#CONTENT_URI +StructuredPostal.CONTENT_URI},可顯示原始聯絡人的郵寄地址清單。 + +
  • +
  • +{@link android.provider.ContactsContract.CommonDataKinds.Email#CONTENT_URI Email.CONTENT_URI},可顯示原始聯絡人的電子郵件地址清單。 + +
  • +
+
+ 未使用 + + 顯示原始聯絡人清單或原始聯絡人的資料清單,視您提供的內容 URI 類型而定。 + +

+ 呼叫 +{@link android.app.Activity#startActivityForResult(Intent, int) startActivityForResult()}可傳回所選取列的內容 URI。 +URI 的格式是表格的內容 URI 附加列的 LOOKUP_ID。 + + 裝置的聯絡人應用程式會在 Activity 的生命週期內,將讀取和寫入權限委派給此內容 URI。 +詳情請參閱內容供應程式基本概念指南。 + + +

+
插入新的原始聯絡人{@link android.provider.ContactsContract.Intents.Insert#ACTION Insert.ACTION}不適用 + {@link android.provider.ContactsContract.RawContacts#CONTENT_TYPE +RawContacts.CONTENT_TYPE},一組原始聯絡人的 MIME 類型。 + + 顯示裝置聯絡人應用程式中的「新增聯絡人」畫面。會顯示您新增至意圖的額外值。 +如果隨著 +{@link android.app.Activity#startActivityForResult(Intent, int) startActivityForResult()} 一起傳送,則新增的原始聯絡人內容 URI 會在 {@link android.content.Intent} 引數的 [資料] 欄位中傳回給 Activity 的 +{@link android.app.Activity#onActivityResult(int, int, Intent) onActivityResult()}回呼方法。 + + +如要取得此值,請呼叫 {@link android.content.Intent#getData()}。 +
編輯聯絡人{@link android.content.Intent#ACTION_EDIT} + 聯絡人的 {@link android.provider.ContactsContract.Contacts#CONTENT_LOOKUP_URI}。 +編輯器 Activity 可讓使用者編輯與此聯絡人關聯的任何資料。 + + + {@link android.provider.ContactsContract.Contacts#CONTENT_ITEM_TYPE +Contacts.CONTENT_ITEM_TYPE},單一聯絡人。 + 顯示聯絡人應用程式中的「編輯聯絡人」畫面。顯示您新增至意圖的額外值。 +使用者按一下 [完成] 來儲存編輯內容時,您的 Activity 會回到前景。 + +
顯示也能夠新增資料的挑選器{@link android.content.Intent#ACTION_INSERT_OR_EDIT} + 不適用 + + {@link android.provider.ContactsContract.Contacts#CONTENT_ITEM_TYPE} + + 此意圖一律會顯示聯絡人應用程式的挑選器畫面。使用者可以挑選要編輯的聯絡人,或新增聯絡人。 +不論使用者選擇編輯或新增,所顯示的畫面中也會顯示您在意圖中傳送的額外資料。 + +如果您的應用程式顯示電子郵件或電話號碼之類的聯絡人資料,使用此意圖會讓使用者將資料新增至現有聯絡人。 + + +

+ 注意:不需要在意圖的額外值中傳送名稱值,這是因為使用者一定會挑選現有名稱或新增名稱。 +再者,如果您傳送名稱,而使用者選擇編輯,則聯絡人應用程式會覆寫之前的值,以顯示您傳送的名稱。 + +如果使用者沒有注意到此情形並儲存本次編輯,則會遺失舊的值。 + +

+
+

+ 裝置的聯絡人應用程式不會讓您刪除原始聯絡人或任何含有意圖的資料。 +如要刪除原始聯絡人,請改用 +{@link android.content.ContentResolver#delete(Uri, String, String[]) ContentResolver.delete()} 或 {@link android.content.ContentProviderOperation#newDelete(Uri) +ContentProviderOperation.newDelete()}。 + +

+

+ 以下程式碼片段展示如何建構及傳送可插入新原始聯絡人和資料,的意圖: + +

+
+// Gets values from the UI
+String name = mContactNameEditText.getText().toString();
+String phone = mContactPhoneEditText.getText().toString();
+String email = mContactEmailEditText.getText().toString();
+
+String company = mCompanyName.getText().toString();
+String jobtitle = mJobTitle.getText().toString();
+
+// Creates a new intent for sending to the device's contacts application
+Intent insertIntent = new Intent(ContactsContract.Intents.Insert.ACTION);
+
+// Sets the MIME type to the one expected by the insertion activity
+insertIntent.setType(ContactsContract.RawContacts.CONTENT_TYPE);
+
+// Sets the new contact name
+insertIntent.putExtra(ContactsContract.Intents.Insert.NAME, name);
+
+// Sets the new company and job title
+insertIntent.putExtra(ContactsContract.Intents.Insert.COMPANY, company);
+insertIntent.putExtra(ContactsContract.Intents.Insert.JOB_TITLE, jobtitle);
+
+/*
+ * Demonstrates adding data rows as an array list associated with the DATA key
+ */
+
+// Defines an array list to contain the ContentValues objects for each row
+ArrayList<ContentValues> contactData = new ArrayList<ContentValues>();
+
+
+/*
+ * Defines the raw contact row
+ */
+
+// Sets up the row as a ContentValues object
+ContentValues rawContactRow = new ContentValues();
+
+// Adds the account type and name to the row
+rawContactRow.put(ContactsContract.RawContacts.ACCOUNT_TYPE, mSelectedAccount.getType());
+rawContactRow.put(ContactsContract.RawContacts.ACCOUNT_NAME, mSelectedAccount.getName());
+
+// Adds the row to the array
+contactData.add(rawContactRow);
+
+/*
+ * Sets up the phone number data row
+ */
+
+// Sets up the row as a ContentValues object
+ContentValues phoneRow = new ContentValues();
+
+// Specifies the MIME type for this data row (all data rows must be marked by their type)
+phoneRow.put(
+        ContactsContract.Data.MIMETYPE,
+        ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE
+);
+
+// Adds the phone number and its type to the row
+phoneRow.put(ContactsContract.CommonDataKinds.Phone.NUMBER, phone);
+
+// Adds the row to the array
+contactData.add(phoneRow);
+
+/*
+ * Sets up the email data row
+ */
+
+// Sets up the row as a ContentValues object
+ContentValues emailRow = new ContentValues();
+
+// Specifies the MIME type for this data row (all data rows must be marked by their type)
+emailRow.put(
+        ContactsContract.Data.MIMETYPE,
+        ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE
+);
+
+// Adds the email address and its type to the row
+emailRow.put(ContactsContract.CommonDataKinds.Email.ADDRESS, email);
+
+// Adds the row to the array
+contactData.add(emailRow);
+
+/*
+ * Adds the array to the intent's extras. It must be a parcelable object in order to
+ * travel between processes. The device's contacts app expects its key to be
+ * Intents.Insert.DATA
+ */
+insertIntent.putParcelableArrayListExtra(ContactsContract.Intents.Insert.DATA, contactData);
+
+// Send out the intent to start the device's contacts app in its add contact activity.
+startActivity(insertIntent);
+
+

資料完整性

+

+ 因為聯絡人存放庫內含重要的敏感資料,使用者會期待這些資料為正確且為最新狀態,聯絡人供應程式對於資料完整性有定義良好的規則。 +因此,您在修改聯絡人資料時,必須符合這些規則。 +以下列出重要規則: + +

+
+
+ 務必為您新增的每個 {@link android.provider.ContactsContract.RawContacts} 列新增 {@link android.provider.ContactsContract.CommonDataKinds.StructuredName} 列。 + +
+
+ {@link android.provider.ContactsContract.Data} 表格中不含 + {@link android.provider.ContactsContract.CommonDataKinds.StructuredName} 列的 +{@link android.provider.ContactsContract.RawContacts} 列,在彙總時會造成問題。 + +
+
+ 務必將 {@link android.provider.ContactsContract.Data} 列連結到其上層的 +{@link android.provider.ContactsContract.RawContacts} 列。 +
+
+ 裝置的聯絡人應用程式中將看不到未連結到 + {@link android.provider.ContactsContract.RawContacts} 的 {@link android.provider.ContactsContract.Data} 列,而且與同步配接器搭配使用時可能會造成問題。 + +
+
+ 只針對您擁有的原始聯絡人變更資料。 +
+
+ 請記住,聯絡人供應程式通常用來管理來自不同帳戶類型或線上服務的資料。 +您必須確認應用程式只會修改或刪除屬於您的資料列,並且確認應用程式插入的資料只含有您可控制的帳戶類型和名稱。 + + +
+
+ 務必使用 {@link android.provider.ContactsContract} 及其子類別中定義的常數,做為授權、內容 URI、URI 路徑、欄名稱、MIME 類型以及 +{@link android.provider.ContactsContract.CommonDataKinds.CommonColumns#TYPE} 值。 + +
+
+ 使用這些常數可協助您避免發生錯誤。如果有任何常數已失效,則編譯器會發出通知。 + +
+
+

自訂資料列

+

+ 透過建立自訂的 MIME 類型,您可以插入、編輯、刪除以及擷取 {@link android.provider.ContactsContract.Data} 表格中您自己的資料列。 +儘管您可以將自己的類型特定欄名稱對應到預設的欄名稱,您的列仍受限於使用 + {@link android.provider.ContactsContract.DataColumns} 中所定義的欄。 + +在裝置的聯絡人應用程式中,可以顯示您的列中資料,但無法加以編輯或刪除,而且使用者無法新增其他資料。 + +如要讓使用者修改您自訂的資料列,您必須在自己的應用程式中提供編輯器 Activity。 + +

+

+ 如要顯示自己的資料,請提供 contacts.xml 檔案,其中要包含 +<ContactsAccountType> 元素以及一或多個其 +<ContactsDataKind> 子元素。詳情請參閱 <ContactsDataKind> element一節。 + +

+

+ 如要更瞭解自訂 MIME 類型的詳細資訊,請閱讀建立內容供應程式指南。 + + +

+

聯絡人供應程式同步配接器

+

+ 聯絡人供應程式的設計是專門用來處理裝置和線上服務之間聯絡人資料的「同步作業」。 +以便讓使用者將現有資料下載到新裝置,以及將現有資料上傳到新帳戶。 + + 同步作業也可以確保使用者手邊使用的是最新的資料,不論來源經過哪些新增和變更。 +同步作業的另一個好處是,即使裝置沒有連上網路,使用者仍然可以存取聯絡人資料。 + +

+

+ 您可以用各種方式實作同步作業,不過 Android 系統提供的外掛程式同步架構可以將以下工作自動化: + +

    + +
  • + 檢查網路可用性。 +
  • +
  • + 根據使用者偏好設定,安排並執行同步作業。 +
  • +
  • + 重新啟動已停止的同步作業。 +
  • +
+

+ 如要使用此架構,您要提供同步配接器外掛程式。每個同步配接器對於服務和內容供應程式來說是唯一的,但可以處理相同服務的多個帳戶名稱。 +此架構也可以讓相同服務和供應程式使用多個同步配接器。 + +

+

同步配接器類別和檔案

+

+ 您將同步配接器實做為 {@link android.content.AbstractThreadedSyncAdapter} 的子類別,並以 Android 應用程式的一部分加以安裝。 + +系統會從應用程式宣示說明中的元素,以及從宣示說明所指向的特殊 XML 檔案中瞭解同步配接器的相關資訊。 +此 XML 檔案定義線上服務的帳戶類型,以及內容供應程式的授權,這兩者可用來唯一識別此配接器。 + +同步配接器要在使用者新增同步配接器的帳戶類型, +並啟用要與同步配接器的內容供應程式同步後, +同步配接器才會變成使用中。此時,系統會開始管理配接器並視需要加以呼叫,以便在內容供應程式和伺服器之間進行同步。 + +

+

+ 注意:使用帳戶類型做為同步配接器識別的一部分,可以讓系統在偵測後,將存取不同服務、但來自相同組織的同步配接器群組在一起。 + +例如,Google 線上服務的同步配接器都有相同的帳戶類型 com.google。 +使用者將 Google 帳戶新增至其裝置後,所有已安裝的 Google 服務同步配接器會列在一起,每個列出的同步配接器會與裝置上不同的內容供應程式進行同步。 + + +

+

+ 由於大多數服務都需要在存取資料之前先驗證其身分,因此 Android 系統提供類似的驗證架構,而且通常會與同步配接器架構一起搭配使用。 + +驗證架構使用外掛程式驗證器,這是 + {@link android.accounts.AbstractAccountAuthenticator} 的子類別。 +驗證器會以下列步驟驗證使用者的身分: + +

    +
  1. + 收集使用者的名稱、密碼或類似資訊 (使用者的憑證)。 + +
  2. +
  3. + 將憑證傳送給服務 +
  4. +
  5. + 檢驗服務的回覆。 +
  6. +
+

+ 如果服務接受此憑證,則驗證器可以儲存憑證供以後使用。 +由於外掛程式驗證器架構的緣故, +{@link android.accounts.AccountManager} 可以存取驗證器支援且選擇顯示的任何 authtoken,例如 OAuth2 authtoken。 + +

+

+ 雖然驗證並非必要,大部分聯絡人服務仍會加以使用。 + 不過,您不一定要使用 Android 驗證架構來進行驗證動作。 +

+

同步配接器實作

+

+ 如要實作聯絡人供應程式的同步配接器,要從建立內含以下各項的 Android 應用程式開始: + +

+
+
+ 回應來自系統的要求,以繫結至同步配接器的 {@link android.app.Service} 元件。 + +
+
+ 系統要執行同步時,會呼叫服務的 + {@link android.app.Service#onBind(Intent) onBind()} 方法,以取得同步配接器的 + {@link android.os.IBinder}。這樣可以讓系統以跨處理程序的方式呼叫配接器的方法。 + +

+ 在範例同步配接器範例應用程式中,此服務的名稱為 + com.example.android.samplesync.syncadapter.SyncService。 + +

+
+
+ 實際的同步配接器是以 + {@link android.content.AbstractThreadedSyncAdapter} 的實體子類別加以實作。 +
+
+ 此類別會執行的工作包括:從伺服器下載資料、從裝置上傳資料以及解決衝突。 +配接器的主要工作會使用 {@link android.content.AbstractThreadedSyncAdapter#onPerformSync( +Account, Bundle, String, ContentProviderClient, SyncResult) +onPerformSync()} 方法完成。 +此類別必須以單一執行個體的方式加以具現化。 +

+ 在範例同步配接器範例應用程式中,同步配接器定義在 +com.example.android.samplesync.syncadapter.SyncAdapter 類別中。 + +

+
+
+ {@link android.app.Application} 的子類別。 +
+
+ 此類別就像是同步配接器單一執行個體的工廠。使用 +{@link android.app.Application#onCreate()} 方法具現化同步配接器,並提供靜態的「getter」方法將單一執行個體傳回給同步配接器服務的 +{@link android.app.Service#onBind(Intent) onBind()} 方法。 + + +
+
+ 選用:回應系統針對使用者發出的驗證要求的 {@link android.app.Service} 元件。 + +
+
+ {@link android.accounts.AccountManager} 會啟動此服以開始驗證程序。 +服務的 {@link android.app.Service#onCreate()} 方法會具現化為驗證器物件。 +系統需驗證應用程式同步配接器的使用者帳戶時,會呼叫服務的 +{@link android.app.Service#onBind(Intent) onBind()} 方法以取得驗證器的 + {@link android.os.IBinder}。 +這樣可以讓系統以跨處理程序的方式呼叫驗證器的方法。 + +

+ 在範例同步配接器範例應用程式中,此服務的類別名稱為 +com.example.android.samplesync.authenticator.AuthenticationService。 + +

+
+
+ 選用:處理驗證要求的 +{@link android.accounts.AbstractAccountAuthenticator} 實體子類別。 + +
+
+ {@link android.accounts.AccountManager} 會呼叫此類別提供的方法,透過伺服器驗證使用者的憑證。 +驗證程序的詳細方式會根據所使用的伺服器技術,而有很大的差異。 +建議您參閱伺服器軟體的說明文件,進一步瞭解驗證。 + +

+ 在範例同步配接器範例應用程式中,驗證器是在 +com.example.android.samplesync.authenticator.Authenticator 類別中完成定義。 + +

+
+
+ 定義系統同步配接器和驗證器的 XML 檔案。 +
+
+ 應用程式宣示說明中的 + <service> 元素會定義之前說明的同步配接器和驗證器服務元件。 + +這些元素包含的 +<meta-data> 子元素可提供系統的特定資料: + + + +
    +
  • + 同步配接器服務的 + <meta-data> 元素,此元素會指向 XML 檔案 res/xml/syncadapter.xml。 + +此檔案會按順序指出 +將與聯絡人供應程式同步的網路服務 URI, +以及網路服務的帳戶類型。 +
  • +
  • + 選用:驗證器服務的 + <meta-data> 元素,此元素會指向 XML 檔案 +res/xml/authenticator.xml。 +此檔案會按順序指出此驗證器支援的帳戶類型,以及驗證程序中會出現的 UI 資源。 + +此元素中指定的帳戶類型必須與同步配接器中指定的帳戶類型相同。 + + +
  • +
+
+
+

社交串流資料

+

+ {@code android.provider.ContactsContract.StreamItems} 和 +{@code android.provider.ContactsContract.StreamItemPhotos} 表格負責管理社交網路的傳入資料。 +您可以編寫一個同步配接器,將來自您自己網路的串流資料 +新增至這些表格,或是可以從這些表格讀取串流資料,然後 +顯示於您自己的應用程式中,或同時具備這兩種功能。有了這些功能,您的社交網路服務和應用程式就可以整合到 Android 的社交網路體驗。 + +

+

社交串流文字

+

+ 串流項目永遠會與原始聯絡人關聯。 +{@code android.provider.ContactsContract.StreamItemsColumns#RAW_CONTACT_ID} 會連結到原始聯絡人的 + _ID 值。原始聯絡人的帳戶類型和帳戶名稱也會儲存在串流項目列。 + +

+

+ 串流中的資料會儲存在下列各欄: +

+
+
+ {@code android.provider.ContactsContract.StreamItemsColumns#ACCOUNT_TYPE} +
+
+ 必要。與此串流項目相關聯的原始聯絡人使用者帳戶類型。 +請記得在插入串流項目時設定此值。 +
+
+ {@code android.provider.ContactsContract.StreamItemsColumns#ACCOUNT_NAME} +
+
+ 必要。與此串流項目相關聯的原始聯絡人使用者帳戶名稱。 +請記得在插入串流項目時設定此值。 +
+
+ 識別碼欄 +
+
+ 必要。您必須在插入串流項目時插入以下識別碼欄: + +
    +
  • + {@code android.provider.ContactsContract.StreamItemsColumns#CONTACT_ID}:與此串流項目相關聯的聯絡人 +{@code android.provider.BaseColumns#_ID} 值。 + +
  • +
  • + {@code android.provider.ContactsContract.StreamItemsColumns#CONTACT_LOOKUP_KEY}:與此串流項目相關聯的聯絡人 +{@code android.provider.ContactsContract.ContactsColumns#LOOKUP_KEY} 值。 + +
  • +
  • + {@code android.provider.ContactsContract.StreamItemsColumns#RAW_CONTACT_ID}:與此串流項目相關聯的原始聯絡人 +{@code android.provider.BaseColumns#_ID} 值。 + +
  • +
+
+
+ {@code android.provider.ContactsContract.StreamItemsColumns#COMMENTS} +
+
+ 選用。儲存您可以在串流項目開頭顯示的摘要資訊。 +
+
+ {@code android.provider.ContactsContract.StreamItemsColumns#TEXT} +
+
+ 串流項目的文字,可能是項目來源張貼的內容,或者會產生串流項目動作的描述。 +此欄可以包含 +{@link android.text.Html#fromHtml(String) fromHtml()} 能夠轉譯的任何格式內嵌資源影像。 +供應程式會截斷較長的內容,但會試著避免破壞標籤。 + +
+
+ {@code android.provider.ContactsContract.StreamItemsColumns#TIMESTAMP} +
+
+ 內含插入或更新串流項目時間的文字字串, +,以「毫秒」為單位。插入或更新串流項目的應用程式負責維護此欄,聯絡人供應程式不會自動加以維護。 + + +
+
+

+ 如要顯示串流項目的識別資訊,請使用 +{@code android.provider.ContactsContract.StreamItemsColumns#RES_ICON}、 +{@code android.provider.ContactsContract.StreamItemsColumns#RES_LABEL} 以及 +{@code android.provider.ContactsContract.StreamItemsColumns#RES_PACKAGE} 連結到應用程式中的資源。 + +

+

+ {@code android.provider.ContactsContract.StreamItems} 表格也包含 +{@code android.provider.ContactsContract.StreamItemsColumns#SYNC1} 到 +{@code android.provider.ContactsContract.StreamItemsColumns#SYNC4} 欄,專門供同步配接器使用。 + +

+

社交串流相片

+

+ {@code android.provider.ContactsContract.StreamItemPhotos} 表格會儲存與串流項目相關聯的相片。 +表格的 +{@code android.provider.ContactsContract.StreamItemPhotosColumns#STREAM_ITEM_ID} 欄會連結到 + {@code android.provider.ContactsContract.StreamItems} 表格中 {@code android.provider.BaseColumns#_ID} 欄的值。 +相片參照會儲存在表格中的以下各欄: + +

+
+
+ {@code android.provider.ContactsContract.StreamItemPhotos#PHOTO} 欄 (BLOB)。 +
+
+ 相片的二進位檔,由供應程式調整大小以進行儲存和顯示。 + 此欄是要提供與使用舊版聯絡人供應程式儲存相片的向下相容性。 +不過,在目前版本中,您不應使用此欄來儲存相片。 +請改用 {@code android.provider.ContactsContract.StreamItemPhotosColumns#PHOTO_FILE_ID} 或 +{@code android.provider.ContactsContract.StreamItemPhotosColumns#PHOTO_URI} (下文提供這兩者的相關說明) 在檔案中儲存相片。 + +此欄現在包含相片的縮圖可供讀取。 + +
+
+ {@code android.provider.ContactsContract.StreamItemPhotosColumns#PHOTO_FILE_ID} +
+
+ 原始聯絡人相片的數字識別碼。將此值附加到常數 +{@link android.provider.ContactsContract.DisplayPhoto#CONTENT_URI DisplayPhoto.CONTENT_URI} 可取得指向單一相片檔案的內容 URI,然後呼叫 {@link android.content.ContentResolver#openAssetFileDescriptor(Uri, String) +openAssetFileDescriptor()} 可取得此相片檔案的控制代碼。 + + +
+
+ {@code android.provider.ContactsContract.StreamItemPhotosColumns#PHOTO_URI} +
+
+ 直接指向此列所呈現相片的相片檔案內容 URI。 + 使用此 URI 呼叫 {@link android.content.ContentResolver#openAssetFileDescriptor(Uri, String) +openAssetFileDescriptor()} 可取得相片檔案的控制代碼。 +
+
+

使用社交串流表格

+

+ 這些表格的運作方式與聯絡人供應程式中的其他主要表格大致相同,以下各項除外: +

+
    +
  • + 這些表格需要額外的存取權限。如要從中讀取,您的應用程式必須具備 {@code android.Manifest.permission#READ_SOCIAL_STREAM} 權限。 +如要加以修改,您的應用程式必須具備 +{@code android.Manifest.permission#WRITE_SOCIAL_STREAM} 權限。 + +
  • +
  • + 針對 {@code android.provider.ContactsContract.StreamItems} 表格,每個原始聯絡人的儲存列數是有限制的。 +達到此限制後,聯絡人供應程式會自動刪除 +{@code android.provider.ContactsContract.StreamItemsColumns#TIMESTAMP} 最舊的列,為新的串流項目列騰出空間。 + +如要取得此限制,請發出查詢給內容 URI +{@code android.provider.ContactsContract.StreamItems#CONTENT_LIMIT_URI}。 +您只要將內容 URI 設為 null 即可,其餘引數不需要處理。 +此查詢會傳回內含單一列的 Cursor 與單一欄 +{@code android.provider.ContactsContract.StreamItems#MAX_ITEMS}。 + +
  • +
+ +

+ 類別 {@code android.provider.ContactsContract.StreamItems.StreamItemPhotos} 定義了單一串流項目,且內含相片列的 {@code android.provider.ContactsContract.StreamItemPhotos} 子表格。 + + +

+

社交串流互動

+

+ 社交串流資料受到聯絡人供應程式與裝置聯絡人應用程式的管理,提供強大的方式將您的社交網路系統與現有聯絡人連接起來。 + +提供下列功能: +

+
    +
  • + 使用同步配接器將您的社交網路服務同步到聯絡人供應程式後,您可以擷取某位使用者的聯絡人最近 Activity,並將它儲存在 {@code android.provider.ContactsContract.StreamItems} 和 +{@code android.provider.ContactsContract.StreamItemPhotos} 表格中,供後續使用。 + + +
  • +
  • + 除了一般同步之外,您可以在使用者選取要檢視的聯絡人時,觸發您的同步配接器,以擷取其他資料。 +此舉可讓您的同步配接器擷取聯絡人高解析度的相片,以及聯絡人最近的串流項目。 + +
  • +
  • + 藉由向裝置的聯絡人應用程式和聯絡人供應程式註冊通知,您可以在檢視聯絡人時「收到」意圖,並於此時更新您服務中的聯絡人狀態。 + +相較於與同步配接器執行完整同步, +此方式較快速且使用的頻寬較少。 +
  • +
  • + 使用者在裝置的聯絡人應用程式查看聯絡人時,可以將聯絡人新增至您的社交網路服務。 +您可以透過「邀請聯絡人」啟用上述功能。「邀請聯絡人」會啟用一連串的 Activity,將現有聯絡人新增至您的網路和 XML 檔案。此檔案會將您應用程式的詳細資訊提供給裝置的聯絡人應用程式和聯絡人供應程式。 + + + +
  • +
+

+ 串流項目與聯絡人供應程式的一般同步與其他同步相同。 +如要進一步瞭解同步,請參閱聯絡人供應程式同步配接器。 +以下兩節說明如何註冊通知和邀請聯絡人。 + +

+

註冊以處理社交網路檢視

+

+ 註冊您的同步配接器,使其在使用者查看由您的同步配接器所管理的聯絡人時收到通知: + +

+
    +
  1. + 在專案的 res/xml/ 目錄中建立名稱為 contacts.xml的檔案。 +如果已經有這個檔案,可略過此步驟。 +
  2. +
  3. + 在此檔案中,新增 +<ContactsAccountType xmlns:android="http://schemas.android.com/apk/res/android"> 元素。 + 如果這個元素已經存在,可略過此步驟。 +
  4. +
  5. + 為了註冊服務,讓使用者在裝置的聯絡人應用程式中開啟聯絡人的詳細資訊頁面時收到通知,請將 + viewContactNotifyService="serviceclass" 屬性新增至此元素,其中 + serviceclass 是該服務的完整類別名稱,而此服務會收到來自裝置聯絡人應用程式的意圖。 + +對於通知器服務而言,使用擴充 {@link android.app.IntentService} 的類別可以讓此服務接收意圖。 + +傳入意圖的資料中含有使用者所點擊該名原始聯絡人的內容 URI。 +您可以從通知器服務繫結,然後呼叫您的同步配接器,以更新原始聯絡人的資料。 + +
  6. +
+

+ 如何註冊使用者點擊串流項目或相片 (或兩者) 時所呼叫的 Activity: +

+
    +
  1. + 在專案的 res/xml/ 目錄中建立名稱為 contacts.xml的檔案。 +如果已經有這個檔案,可略過此步驟。 +
  2. +
  3. + 在此檔案中,新增 +<ContactsAccountType xmlns:android="http://schemas.android.com/apk/res/android"> 元素。 + 如果這個元素已經存在,可略過此步驟。 +
  4. +
  5. + 為了註冊其中一個 Activity,讓它處理使用者在裝置的聯絡人應用程式中點擊串流項目的 Activity,請將 + viewStreamItemActivity="activityclass" 屬性新增至此元素,其中 + activityclass 是該 Activity 的完整類別名稱,而此 Activity 會收到來自裝置聯絡人應用程式的意圖。 + + +
  6. +
  7. + 為了註冊其中一個 Activity,讓它處理使用者在裝置的聯絡人應用程式中點擊串流相片的活動,請將 + viewStreamItemPhotoActivity="activityclass" 屬性新增至此元素,其中 + activityclass 是該 Activity 的完整類別名稱,而此 Activity 會收到來自裝置聯絡人應用程式的意圖。 + + +
  8. +
+

+ 如要進一步瞭解 <ContactsAccountType> 元素,請參閱 <ContactsAccountType> 元素。 + +

+

+ 傳入意圖的資料中含有使用者所按下項目或相片的內容 URI。 + 如要針對文字項目和相片採取不同的 Activity,請在相同的檔案中同時使用兩個屬性。 +

+

與社交網路服務互動

+

+ 使用者不需要離開裝置的聯絡人應用程式,就可以邀請聯絡人到您的社交網路網站。 +您可以改為讓裝置的聯絡人應用程式傳送意圖,以邀請聯絡人前往您的 Activity。 +如要進行此設定: +

+
    +
  1. + 在專案的 res/xml/ 目錄中建立名稱為 contacts.xml的檔案。 +如果已經有這個檔案,可略過此步驟。 +
  2. +
  3. + 在此檔案中,新增 <ContactsAccountType xmlns:android="http://schemas.android.com/apk/res/android"> 元素。 + + 如果這個元素已經存在,可略過此步驟。 +
  4. +
  5. + 新增下列屬性: +
      +
    • inviteContactActivity="activityclass"
    • +
    • + inviteContactActionLabel="@string/invite_action_label" +
    • +
    + activityclass 值是 Activity 的完整類別名稱,以此 Activity 接收意圖。 +invite_action_label 值是顯示在裝置聯絡人應用程式內 [新增連線] 選單中的文字字串。 + + +
  6. +
+

+ 注意:ContactsSource 是 + ContactsAccountType 已淘汰的標籤名稱。 +

+

contacts.xml 參照

+

+ contacts.xml 檔案包含的 XML 元素,可控制您的同步配接器與應用程式 (聯絡人應用程式和聯絡人供應程式) 之間的互動。 +這些元素在以下各節有詳細的說明。 + +

+

<ContactsAccountType> 元素

+

+ <ContactsAccountType> 元素控制您的應用程式與聯絡人應用程式之間的互動。 +此元素的語法如下: +

+
+<ContactsAccountType
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        inviteContactActivity="activity_name"
+        inviteContactActionLabel="invite_command_text"
+        viewContactNotifyService="view_notify_service"
+        viewGroupActivity="group_view_activity"
+        viewGroupActionLabel="group_action_text"
+        viewStreamItemActivity="viewstream_activity_name"
+        viewStreamItemPhotoActivity="viewphotostream_activity_name">
+
+

+ 包含於: +

+

+ res/xml/contacts.xml +

+

+ 可以包含: +

+

+ <ContactsDataKind> +

+

+ 描述: +

+

+ 宣告 Android 元件和 UI 標籤,讓使用者可以邀請聯絡人加入社交網路、使用者的社交網路串流更新內容時通知使用者等等。 + + +

+

+ 請注意,<ContactsAccountType> 的屬性不需要使用屬性前置詞 android:。 + +

+

+ 屬性: +

+
+
{@code inviteContactActivity}
+
+ 使用者從裝置的聯絡人應用程式選取[新增連線]時,您希望在應用程式中啟動的 Activity 完整類別名稱。 + + +
+
{@code inviteContactActionLabel}
+
+ 在 [新增連線] 選單的 + {@code inviteContactActivity} 中所指定 Activity 的顯示文字字串。 + 例如,您可以使用「關注我的網路活動」字串。此標籤可以使用字串資源識別碼。 + +
+
{@code viewContactNotifyService}
+
+ 使用者檢視聯絡人時,要接收通知的應用程式中的服務完整類別名稱。 +此通知是由裝置的聯絡人應用程式所傳送,這樣可以讓您的應用程式延後要處理大量資料的操作,需要時再加以處理。 + +例如,您的應用程式可以藉由讀取並顯示聯絡人的高解析度相片,以及最近的社交串流項目,以回應此通知。 + +如要進一步瞭解此功能,請參閱社交串流互動。 +您可以在 + SampleSyncAdapter 範例應用程式的 NotifierService.java中查看通知服務的範例。 + + +
+
{@code viewGroupActivity}
+
+ 應用程式中可以顯示群組資訊的 Activity 完整類別名稱。 +使用者在裝置的聯絡人應用程式中按一下群組標籤時,會顯示此 Activity 的 UI。 + +
+
{@code viewGroupActionLabel}
+
+ 聯絡人應用程式顯示 UI 控制項的標籤,可以讓使用者在您的應用程式中查看群組。 + +

+ 例如,如果您在裝置上安裝 Google+ 應用程式,而您將Google+ 與聯絡人應用程式進行同步,您會看到 Google+ 社交圈已列為聯絡人應用程式 [群組] 標籤中的群組。 + +如果按一下 Google+ 社交圈,您會看到該社交圈中的人員已列為「群組」。 +系統會在畫面頂端顯示 Google+ 圖示,如果您按一下此圖示,則控制權會切換到 Google+ 應用程式。聯絡人應用程式使用 +{@code viewGroupActivity} 執行此動作,並使用 Google+ 圖示做為 +{@code viewGroupActionLabel} 的值。 + + +

+

+ 此屬性可以使用字串資源識別碼。 +

+
+
{@code viewStreamItemActivity}
+
+ 使用者按一下原始聯絡人的串流項目時,裝置的聯絡人應用程式所啟動應用程式中的 Activity 完整類別名稱。 + +
+
{@code viewStreamItemPhotoActivity}
+
+ 使用者按一下原始聯絡人串流項目中的相片時,裝置的聯絡人應用程式所啟動應用程式中的 Activity 完整類別名稱。 + + +
+
+

<ContactsDataKind> 元素

+

+ <ContactsDataKind> 元素控制聯絡人應用程式的 UI 中,您的應用程式自訂資料列所顯示的控制項。此元素的語法如下: + +

+
+<ContactsDataKind
+        android:mimeType="MIMEtype"
+        android:icon="icon_resources"
+        android:summaryColumn="column_name"
+        android:detailColumn="column_name">
+
+

+ 包含於: +

+<ContactsAccountType> +

+ 描述: +

+

+ 使用此元素讓聯絡人應用程式,將自訂資料列的內容顯示為原始聯絡人詳細資訊的一部分。 +<ContactsAccountType> 的每個 <ContactsDataKind> 子元素都代表同步配接器新增至 {@link android.provider.ContactsContract.Data} 表格的自訂資料列類型。 + +針對您使用的每個自訂 MIME 類型,新增一個 + <ContactsDataKind> 元素。如果您不要顯示某個自訂資料列的資料,就不用為該列新增元素。 + +

+

+ 屬性: +

+
+
{@code android:mimeType}
+
+ 您在 + {@link android.provider.ContactsContract.Data} 表格中已經為自訂資料列類型所定義的自訂 MIME 類型。例如, +vnd.android.cursor.item/vnd.example.locationstatus 值可能是 +記錄聯絡人最後已知位置資料列的自訂 MIME 類型。 +
+
{@code android:icon}
+
+ 聯絡人應用程式顯示在資料旁邊的 Android +可繪資源。 +使用此項向使用者指出資料是來自您的服務。 + +
+
{@code android:summaryColumn}
+
+ 從資料列擷取兩個值,其中第一個值的欄名稱。此資料列的值會顯示為該項目的第一行。 +第一行的用意是做為資料的摘要使用,但為選用。 +另請參閱 + android:detailColumn。 +
+
{@code android:detailColumn}
+
+ 從資料列擷取兩個值,其中第二個值的欄名稱。此資料列的值會顯示為該項目的第二行。 +另請參閱 +{@code android:summaryColumn}。 +
+
+

其他聯絡人供應程式功能

+

+ 除了上一節所描述的主要功能之外,聯絡人供應程式也提供以下實用功能來處理聯絡人資料: + +

+
    +
  • 聯絡人群組
  • +
  • 相片功能
  • +
+

聯絡人群組

+

+ 聯絡人供應程式可以選擇為群組資料相關的聯絡人集合貼上標籤。 +如果與使用者帳戶關聯的伺服器要維護群組,該帳戶的帳戶類型所屬的同步配接器,應該要在聯絡人供應程式和伺服器之間傳輸群組資料。 + +使用者將新的聯絡人新增至伺服器,然後將此聯絡人放置於新群組時,同步配接器必須將新群組新增至 {@link android.provider.ContactsContract.Groups} 表格。 + +原始聯絡人所屬的一或多個群組會使用 {@link android.provider.ContactsContract.CommonDataKinds.GroupMembership} MIME 類型儲存在 {@link android.provider.ContactsContract.Data} 表格。 + + +

+

+ 如果您設計的同步配接器,會將原始聯絡人資料從伺服器新增至聯絡人供應程式,表示您並未使用群組,那麼您需要告訴供應程式讓您的資料變成可見的。 + +在使用者將帳戶新增至裝置時,要執行的程式碼中,更新聯絡人供應程式為帳戶新增的 {@link android.provider.ContactsContract.Settings} 列。 + +在此列中,將 {@link android.provider.ContactsContract.SettingsColumns#UNGROUPED_VISIBLE +Settings.UNGROUPED_VISIBLE} 欄的值設為 1。 +這麼做之後,即使您沒有使用群組,聯絡人供應程式 +一律會讓您的聯絡人資料成為可見的。 +

+

聯絡人相片

+

+ {@link android.provider.ContactsContract.Data} 表格會使用 MIME 類型 {@link android.provider.ContactsContract.CommonDataKinds.Photo#CONTENT_ITEM_TYPE +Photo.CONTENT_ITEM_TYPE} 在列中儲存相片。 +列的 +{@link android.provider.ContactsContract.RawContactsColumns#CONTACT_ID} 欄是連結到其所屬原始聯絡人的 + {@code android.provider.BaseColumns#_ID} 欄。 + 類別 {@link android.provider.ContactsContract.Contacts.Photo} 定義了 + {@link android.provider.ContactsContract.Contacts} 的子表格,其中包含聯絡人主要相片的相片資訊,也就是聯絡人之主要原始聯絡人的主要相片。 +同樣地, +類別 {@link android.provider.ContactsContract.RawContacts.DisplayPhoto} 定義了 {@link android.provider.ContactsContract.RawContacts} 的子表格,其中包含原始聯絡人之主要相片的相片資訊。 + + +

+

+ {@link android.provider.ContactsContract.Contacts.Photo} 和 +{@link android.provider.ContactsContract.RawContacts.DisplayPhoto} 的參考文件含有擷取相片資訊的範例。 +擷取原始聯絡人的主要縮圖沒有方便使用的類別,不過您可以傳送查詢到 +{@link android.provider.ContactsContract.Data} 表格,然後選取原始聯絡人的 +{@code android.provider.BaseColumns#_ID}、{@link android.provider.ContactsContract.CommonDataKinds.Photo#CONTENT_ITEM_TYPE +Photo.CONTENT_ITEM_TYPE} 以及 {@link android.provider.ContactsContract.Data#IS_PRIMARY} 欄,以尋找原始聯絡人的主要相片列。 + + + +

+

+ 人員的社交串流資料可能也包括相片。這些資訊都儲存在 +{@code android.provider.ContactsContract.StreamItemPhotos} 表格。社交串流相片中針對此表格會有更詳細的說明。 + +

diff --git a/docs/html-intl/intl/zh-tw/guide/topics/providers/content-provider-basics.jd b/docs/html-intl/intl/zh-tw/guide/topics/providers/content-provider-basics.jd new file mode 100644 index 0000000000000000000000000000000000000000..78314784ba9e13ac8af69ae6e41cb9b986b24839 --- /dev/null +++ b/docs/html-intl/intl/zh-tw/guide/topics/providers/content-provider-basics.jd @@ -0,0 +1,1196 @@ +page.title=內容供應程式基本概念 +@jd:body +
+
+ +

本文件內容

+
    +
  1. + 總覽 +
      +
    1. + 存取供應程式 +
    2. +
    3. + 內容 URI +
    4. +
    +
  2. +
  3. + 從供應程式擷取資料 +
      +
    1. + 要求讀取權限 +
    2. +
    3. + 建構查詢 +
    4. +
    5. + 顯示查詢結果 +
    6. +
    7. + 從查詢結果取得資料 +
    8. +
    +
  4. +
  5. + 內容供應程式權限 +
  6. +
  7. + 插入、更新及刪除資料 +
      +
    1. + 插入資料 +
    2. +
    3. + 更新資料 +
    4. +
    5. + 刪除資料 +
    6. +
    +
  8. +
  9. + 供應程式資料類型 +
  10. +
  11. + 供應程式存取權的替代形式 +
      +
    1. + 批次存取 +
    2. +
    3. + 透過意圖存取資料 +
    4. +
    +
  12. +
  13. + 合約類別 +
  14. +
  15. + MIME 類型參考資料 +
  16. +
+ + +

重要類別

+
    +
  1. + {@link android.content.ContentProvider} +
  2. +
  3. + {@link android.content.ContentResolver} +
  4. +
  5. + {@link android.database.Cursor} +
  6. +
  7. + {@link android.net.Uri} +
  8. +
+ + +

相關範例

+
    +
  1. + + 游標 (使用者) +
  2. +
  3. + + 游標 (電話) +
  4. +
+ + +

另請參閱

+
    +
  1. + + 建立內容供應程式 +
  2. +
  3. + + 日曆供應程式 +
  4. +
+
+
+ + +

+ 內容供應程式可管理中央資料存放庫的存取權。供應程式是 Android 應用程式的一部分,通常可提供本身的 UI 方便使用者處理資料。 + +不過,內容供應程式主要是供其他應用程式使用 (透過供應程式用戶端物件進行存取)。 +供應程式與供應程式用戶端可提供一致的標準介面,除了可用於存取資料,還能用來處理程序間通訊以及保護資料存取的安全。 + + +

+

+ 本主題涵蓋以下基本概念: +

+
    +
  • 內容供應程式的運作方式。
  • +
  • 可用於從內容供應程式擷取資料的 API。
  • +
  • 可用於插入、更新或刪除內容供應程式資料的 API。
  • +
  • 有助於使用供應程式的其他 API 功能。
  • +
+ + +

總覽

+

+ 內容供應程式會向外部應用程式以一或多份表格顯示資料,這些表格的樣式類似於關聯式資料庫中的表格。 +每個資料列代表供應程式所收集部分資料類型的單一執行個體,而列中的每個資料欄則代表針對該執行個體收集的個別資料。 + + +

+

+ 例如,Android 平台內建的其中一個供應程式是使用者字典,其中儲存使用者想保留的非標準字詞的拼寫方式。 +表 1 說明此供應程式表格顯示這種資料的方式: + +

+

+ 表 1:使用者字典表格範例。 +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
字詞應用程式 ID頻率地區_ID
mapreduceuser1100en_US1
precompileruser14200fr_FR2
appletuser2225fr_CA3
constuser1255pt_BR4
intuser5100en_UK5
+

+ 在表 1 中,每個資料列代表標準字典未收錄的某個字詞執行個體。 +每個資料欄則代表該字詞的部分資料,例如初次出現該字詞的地區。 +欄標題為儲存在供應程式中的欄名稱。 +如果想得知某一列的地區,請查看相對應的 locale 欄。以這個供應程式為例,_ID 欄的用途與供應程式自動維護的「主索引鍵」欄相同。 + + +

+

+ 注意:供應程式不需具備主索引鍵,也不必採用 _ID 做為主索引鍵欄的名稱 (如果有此欄的話)。 +不過,如果您想將供應程式的資料繫結至 {@link android.widget.ListView},就必須將其中一個資料欄命名為 _ID。 + +如要進一步瞭解這項規定,請參閱顯示查詢結果。 + +

+

存取供應程式

+

+ 應用程式會透過 {@link android.content.ContentResolver} 用戶端物件存取內容供應程式的資料。 +此物件內含的方法可呼叫供應程式物件 ({@link android.content.ContentProvider} 子類別的其中一項執行個體) 中的同名方法。 + + +{@link android.content.ContentResolver} 方法可提供永久儲存空間的「CRUD」(建立、擷取、更新、刪除) 基本功能。 + +

+

+ 用戶端應用程式處理程序中的 {@link android.content.ContentResolver} 物件以及擁有供應程式的應用程式中的 {@link android.content.ContentProvider} 物件,會自動處理程序間通訊。 +{@link android.content.ContentProvider} 還可當作其資料存放庫與外部資料表格之間的抽象層。 + + + +

+

+ 注意:如要存取供應程式,您的應用程式通常必須透過本身的宣示說明檔案要求特定權限。 +如需詳細資料,請參閱內容供應程式權限。 + +

+

+ 例如,如要從使用者字典供應程式取得一份列出字詞及其地區的清單,請呼叫 {@link android.content.ContentResolver#query ContentResolver.query()}。 + + {@link android.content.ContentResolver#query query()} 方法會呼叫使用者字典供應程式所定義的 +{@link android.content.ContentProvider#query ContentProvider.query()} 方法。 +以下是 +{@link android.content.ContentResolver#query ContentResolver.query()} 呼叫的程式碼: +

+

+// Queries the user dictionary and returns results
+mCursor = getContentResolver().query(
+    UserDictionary.Words.CONTENT_URI,   // The content URI of the words table
+    mProjection,                        // The columns to return for each row
+    mSelectionClause                    // Selection criteria
+    mSelectionArgs,                     // Selection criteria
+    mSortOrder);                        // The sort order for the returned rows
+
+

+ 表 2 列出 +{@link android.content.ContentResolver#query +query(Uri,projection,selection,selectionArgs,sortOrder)} 的引數及相對應的 SQL SELECT 陳述式: +

+

+ 表 2:Query() 與 SQL 查詢的對照表。 +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
query() 引數SELECT 關鍵字/參數備註
UriFROM table_nameUri 會對應至供應程式中名為的「table_name」表格。
projectioncol,col,col,... + projection 代表需針對每個擷取的資料列 +納入的一系列資料欄。 +
selectionWHERE col = valueselection 會指定資料列選取條件。
selectionArgs + (沒有任何相對應的關鍵字/參數。選取引數會取代選取子句中的 +? 預留位置。) +
sortOrderORDER BY col,col,... + sortOrder 會指定資料列在傳回的 {@link android.database.Cursor} 中的顯示順序。 + +
+

內容 URI

+

+ 內容 URI 是指用於識別供應程式資料的 URI,內容 URI 包括整個供應程式的符號名稱 (亦即供應程式的授權),以及指向表格的名稱 (亦即路徑)。 + +當您呼叫用戶端方法來存取供應程式中的表格時,該表格的內容 URI 即為其中一個引數。 + + +

+

+ 在上方程式碼中, +{@link android.provider.UserDictionary.Words#CONTENT_URI} 常數包含使用者字典表格「字詞」的內容 URI。 +{@link android.content.ContentResolver} 物件會剖析該 URI 的授權,然後比較授權和已知供應程式的系統表格,藉此「解析」供應程式。 + +接著 {@link android.content.ContentResolver} 可以查詢引數分派給正確的供應程式。 + + +

+

+ {@link android.content.ContentProvider} 會使用內容 URI 的路徑部分選擇要存取的表格。 +供應程式通常包含用於公開每個表格的「路徑」。 +

+

+ 以上方程式碼為例,「字詞」的完整 URI 會如下所示: +

+
+content://user_dictionary/words
+
+

+ 其中的 user_dictionary 字串代表供應程式的授權,而 words 字串則是表格的路徑。 +字串 content:// (配置) 一律會顯示,而起會將此項目識別為內容 URI。 + + +

+

+ 許多供應程式都可讓您存取表格中的單一資料列,方法是在 URI 後方附加 ID 值。例如,如要從使用者字典擷取 _ID4 的資料列,請使用以下內容 URI: + + +

+
+Uri singleUri = ContentUris.withAppendedId(UserDictionary.Words.CONTENT_URI,4);
+
+

+ 在更新或刪除您擷取的一組資料列時,通常需要使用 ID 值。 + +

+

+ 注意:{@link android.net.Uri} 和 {@link android.net.Uri.Builder} 類別包含可用於從字串建構格式正確之 URI 物件的簡便方法。 +而 {@link android.content.ContentUris} 則包含可用於將 ID 值附加至 URI 的簡便方法。上方程式碼片段是使用 {@link android.content.ContentUris#withAppendedId +withAppendedId()} 將 ID 附加至 UserDictionary 內容 URI。 + + +

+ + + +

從供應程式擷取資料

+

+ 本節以使用者字典供應程式為例,說明如何從供應程式擷取資料。 + +

+

+ 為了避免造成混淆,本節中的程式碼片段是透過「UI 執行緒」呼叫 {@link android.content.ContentResolver#query ContentResolver.query()}。 +不過在實際程式碼中,您需要透過個別執行緒以非同步方式進行查詢。 +您可以使用 {@link android.content.CursorLoader} 類別 (詳情請參閱載入器指南) 來完成這項作業。 + + +此外,本節中的程式碼都只是程式碼片段,無法呈現完整的應用程式。 + +

+

+ 如要從供應程式擷取資料,請按照下列基本步驟操作: +

+
    +
  1. + 要求供應程式的讀取權限。 +
  2. +
  3. + 定義可將查詢傳送至供應程式的程式碼。 +
  4. +
+

要求讀取權限

+

+ 您的應用程式需取得供應程式的「讀取權限」,才能從供應程式擷取資料。 +您無法在執行階段要求此權限;您必須使用 <uses-permission> 元素和供應程式所定義的確切權限名稱,在宣示說明中指明您需要此權限。 + + + +在宣示說明中指定此元素後,即可為您的應用程式「要求」這項權限。 +當使用者安裝您應用程式時,他們會間接核准此要求。 + +

+

+ 如要找出所使用供應程式的確切讀取權限名稱,以及該供應程式使用的其他存取權限名稱,請查閱供應程式的說明文件。 + + +

+

+ 如要進一步瞭解用於存取供應程式的權限角色,請參閱內容供應程式權限。 + +

+

+ 使用者字典供應程式會在本身的宣示說明檔案中定義 android.permission.READ_USER_DICTIONARY 權限,因此想讀取該供應程式的應用程式都必須要求此權限。 + + +

+ +

建構查詢

+

+ 從供應程式擷取資料的下一個步驟是建構查詢。本節中的第一個程式碼片段可定義數個用於存取使用者字典供應程式的變數: + +

+
+
+// A "projection" defines the columns that will be returned for each row
+String[] mProjection =
+{
+    UserDictionary.Words._ID,    // Contract class constant for the _ID column name
+    UserDictionary.Words.WORD,   // Contract class constant for the word column name
+    UserDictionary.Words.LOCALE  // Contract class constant for the locale column name
+};
+
+// Defines a string to contain the selection clause
+String mSelectionClause = null;
+
+// Initializes an array to contain selection arguments
+String[] mSelectionArgs = {""};
+
+
+

+ 下一個程式碼片段以使用者字典供應程式為例,示範 {@link android.content.ContentResolver#query ContentResolver.query()} 的使用方式。 + +供應程式用戶端查詢類似於 SQL 查詢,包含一組要傳回的資料欄、一組選取條件以及一個排序順序。 + +

+

+ 查詢需傳回的一組資料欄稱為「投影」(即 mProjection 變數)。 + +

+

+ 指定要擷取的資料列的運算式會分為選取子句和選取引數。 +選取子句包含邏輯運算式和布林運算式、欄名稱和值 (即 mSelectionClause 變數)。 +如果您指定可替換的參數 ?,而不是某個值,則查詢方法會從選取引數陣列 (即 mSelectionArgs 變數) 擷取相關值。 + + +

+

+ 在下方程式碼片段中,如果使用者未輸入任何字詞,選取子句會設定為 null,此時查詢會傳回供應程式中的所有字詞。 +如果使用者輸入了某個字詞,則選取子句會設定為 UserDictionary.Words.WORD + " = ?",而選取引數陣列的第一個元素會設為使用者所輸入的字詞。 + + +

+
+/*
+ * This defines a one-element String array to contain the selection argument.
+ */
+String[] mSelectionArgs = {""};
+
+// Gets a word from the UI
+mSearchString = mSearchWord.getText().toString();
+
+// Remember to insert code here to check for invalid or malicious input.
+
+// If the word is the empty string, gets everything
+if (TextUtils.isEmpty(mSearchString)) {
+    // Setting the selection clause to null will return all words
+    mSelectionClause = null;
+    mSelectionArgs[0] = "";
+
+} else {
+    // Constructs a selection clause that matches the word that the user entered.
+    mSelectionClause = UserDictionary.Words.WORD + " = ?";
+
+    // Moves the user's input string to the selection arguments.
+    mSelectionArgs[0] = mSearchString;
+
+}
+
+// Does a query against the table and returns a Cursor object
+mCursor = getContentResolver().query(
+    UserDictionary.Words.CONTENT_URI,  // The content URI of the words table
+    mProjection,                       // The columns to return for each row
+    mSelectionClause                   // Either null, or the word the user entered
+    mSelectionArgs,                    // Either empty, or the string the user entered
+    mSortOrder);                       // The sort order for the returned rows
+
+// Some providers return null if an error occurs, others throw an exception
+if (null == mCursor) {
+    /*
+     * Insert code here to handle the error. Be sure not to use the cursor! You may want to
+     * call android.util.Log.e() to log this error.
+     *
+     */
+// If the Cursor is empty, the provider found no matches
+} else if (mCursor.getCount() < 1) {
+
+    /*
+     * Insert code here to notify the user that the search was unsuccessful. This isn't necessarily
+     * an error. You may want to offer the user the option to insert a new row, or re-type the
+     * search term.
+     */
+
+} else {
+    // Insert code here to do something with the results
+
+}
+
+

+ 這個查詢類似下方的 SQL 陳述式: +

+
+SELECT _ID, word, locale FROM words WHERE word = <userinput> ORDER BY word ASC;
+
+

+ 這個 SQL 陳述式會使用實際的欄名稱,而不是合約類別常數。 +

+

防範惡意輸入

+

+ 如果內容供應程式所管理的資料是儲存在 SQL 資料庫中,在原始 SQL 陳述式中納入不受信任的外部資料可能會導致 SQL 遭植入惡意程式碼。 + +

+

+ 以下提供選取子句範例: +

+
+// Constructs a selection clause by concatenating the user's input to the column name
+String mSelectionClause =  "var = " + mUserInput;
+
+

+ 使用這個選取子句可讓使用者能夠將惡意 SQL 串連至您的 SQL 陳述式。 + 例如,使用者可針對 mUserInput 輸入「nothing; DROP TABLE *;」,以產生 var = nothing; DROP TABLE *; 選取子句。 +由於系統會將選取子句視為 SQL 陳述式,因此這種選取子句可能會導致供應程式清除底層 SQLite 資料庫中的所有表格 (除非您設定供應程式捕捉 SQL 植入嘗試)。 + + + +

+

+ 為了避免發生這個問題,請使用包含 ? 可替換參數和一系列選取引數的選取子句。 +當您執行此動作時,使用者輸入會直接繫結到查詢,而不是被解譯為 SQL 陳述式的一部分。 + + 這樣一來,系統就不會將選取子句視為 SQL,進而防止使用者輸入植入惡意 SQL。您也可以使用以下選取子句,而不是透過串連方式納入使用者輸入: + +

+
+// Constructs a selection clause with a replaceable parameter
+String mSelectionClause =  "var = ?";
+
+

+ 設定如下所示的一系列選取引數: +

+
+// Defines an array to contain the selection arguments
+String[] selectionArgs = {""};
+
+

+ 如下所示在選取引數中加入一個值: +

+
+// Sets the selection argument to the user's input
+selectionArgs[0] = mUserInput;
+
+

+ 建議您使用包含 ? 可替換參數和一系列選取引數的選取子句,即使供應程式並非以 SQL 資料庫為基礎亦然。 + + +

+ +

顯示查詢結果

+

+ {@link android.content.ContentResolver#query ContentResolver.query()} 用戶端方法一律會針對符合查詢選取條件的資料列,傳回內含查詢的投影所指定資料欄的 {@link android.database.Cursor}。 + +{@link android.database.Cursor} 物件會針對本身包含的資料列和資料欄提供隨機讀取存取權。 + +您可以使用 {@link android.database.Cursor} 方法逐一查看結果中的資料列、決定每個資料欄的資料類型、匯出資料欄的資料,以及檢查結果的其他屬性。 + +實作某些 {@link android.database.Cursor} 方法可在供應程式的資料變更時自動更新相關物件,或在 {@link android.database.Cursor} 變更時觸發觀察器物件中的方法,或是同時進行以上兩者。 + + +

+

+ 注意:供應程式可能會根據建立查詢物件的屬性限制資料欄的存取權。 +例如,內容供應程式會限制同步配接器存取部分資料欄,避免將這些資料欄傳回 Activity 或服務。 + +

+

+ 如果沒有任何資料欄符合選取條件,則供應程式會傳回 {@link android.database.Cursor#getCount Cursor.getCount()} 為 0 的 +{@link android.database.Cursor} 物件 (即沒有任何內容的游標)。 + +

+

+ 如果發生內部錯誤,查詢結果將取決於供應程式的決定。供應程式可能會選擇傳回 null,或是擲回 {@link java.lang.Exception}。 + +

+

+ 由於 {@link android.database.Cursor} 是一份資料欄「清單」,因此要顯示 {@link android.database.Cursor} 的內容,最佳做法是透過 {@link android.widget.SimpleCursorAdapter} 將其連結至 {@link android.widget.ListView}。 + + +

+

+ 以下程式碼片段是上一個程式碼片段的延伸。它會建立內含查詢所擷取 {@link android.database.Cursor} 的 +{@link android.widget.SimpleCursorAdapter} 物件,並將該物件設定為 {@link android.widget.ListView} 的配接器: + + +

+
+// Defines a list of columns to retrieve from the Cursor and load into an output row
+String[] mWordListColumns =
+{
+    UserDictionary.Words.WORD,   // Contract class constant containing the word column name
+    UserDictionary.Words.LOCALE  // Contract class constant containing the locale column name
+};
+
+// Defines a list of View IDs that will receive the Cursor columns for each row
+int[] mWordListItems = { R.id.dictWord, R.id.locale};
+
+// Creates a new SimpleCursorAdapter
+mCursorAdapter = new SimpleCursorAdapter(
+    getApplicationContext(),               // The application's Context object
+    R.layout.wordlistrow,                  // A layout in XML for one row in the ListView
+    mCursor,                               // The result from the query
+    mWordListColumns,                      // A string array of column names in the cursor
+    mWordListItems,                        // An integer array of view IDs in the row layout
+    0);                                    // Flags (usually none are needed)
+
+// Sets the adapter for the ListView
+mWordList.setAdapter(mCursorAdapter);
+
+

+ 注意:如要返回內含 {@link android.database.Cursor} 的{@link android.widget.ListView},您必須為游標加入名為 _ID 的資料欄。 + + 因為這樣,上述查詢會擷取「字詞」表格的 _ID 欄,即使 {@link android.widget.ListView} 未顯示該資料欄亦然。 + + 這項限制也是為何大多數供應程式會針對本身的所有表格設定 _ID 欄的原因。 + +

+ + +

從查詢結果取得資料

+

+ 除了單純顯示查詢結果以外,您還可以將它們用於其他工作。例如,您可以從使用者字典擷取拼字,然後在其他供應程式中查閱這些拼字。 + +如要這麼做,請逐一查看 {@link android.database.Cursor} 中的資料列: +

+
+
+// Determine the column index of the column named "word"
+int index = mCursor.getColumnIndex(UserDictionary.Words.WORD);
+
+/*
+ * Only executes if the cursor is valid. The User Dictionary Provider returns null if
+ * an internal error occurs. Other providers may throw an Exception instead of returning null.
+ */
+
+if (mCursor != null) {
+    /*
+     * Moves to the next row in the cursor. Before the first movement in the cursor, the
+     * "row pointer" is -1, and if you try to retrieve data at that position you will get an
+     * exception.
+     */
+    while (mCursor.moveToNext()) {
+
+        // Gets the value from the column.
+        newWord = mCursor.getString(index);
+
+        // Insert code here to process the retrieved word.
+
+        ...
+
+        // end of while loop
+    }
+} else {
+
+    // Insert code here to report an error if the cursor is null or the provider threw an exception.
+}
+
+

+ {@link android.database.Cursor} 實作方法包含數個用於從物件擷取不同資料類型的「get」方法。 +例如,上方程式碼片段使用了 {@link android.database.Cursor#getString getString()}。 +此外,這種實作方法還包括 +{@link android.database.Cursor#getType getType()} 方法,可傳回指定資料欄資料類型的值。 + +

+ + + +

內容供應程式權限

+

+ 供應程式的應用程式可指定其他應用程式在存取供應程式的資料時所需的權限。 +這些權限可確保使用者瞭解應用程式嘗試存取的資料為何。 +根據供應程式需求,其他應用程式必須取得相關必要權限才能存取供應程式。 +使用者可在安裝應用程式時得知這些必要的權限。 + +

+

+ 如果供應程式的應用程式未指定任何權限,則其他應用程式就無法存取供應程式的資料。 +不過,供應程式的應用程式元件一律會具備完整的讀取及寫入存取權,無論供應程式指定的權限為何。 + +

+

+ 如上所述,使用者字典供應程式要求其他應用程式需取得 android.permission.READ_USER_DICTIONARY 權限,才能擷取其中的資料。 + + 而該供應程式還個別針對插入、更新或刪除資料用途,指定了不同的 android.permission.WRITE_USER_DICTIONARY 權限。 + +

+

+ 如要取得存取供應程式時所需的權限,應用程式可利用 <uses-permission> 元素在本身的宣示說明檔案中要求相關權限。 + +當 Android 套件管理員安裝應用程式時,使用者必須授予該應用程式要求的所有權限。 +使用者授予所有權限後,套件管理員才能繼續進行安裝作業;如果使用者不授予權限,則套件管理員就會取消安裝。 + + +

+

+ 以下的 +<uses-permission> + 元素會要求使用者字典供應程式的讀取存取權: +

+
+    <uses-permission android:name="android.permission.READ_USER_DICTIONARY">
+
+

+ 如要進一步瞭解權限對供應程式存取的影響,請參閱安全性和權限指南。 + +

+ + + +

插入、更新及刪除資料

+

+ 透過從供應程式擷取資料的相同方式,您也可以利用供應程式用戶端與供應程式的 {@link android.content.ContentProvider} 之間的互動來修改資料。 + + 如要這麼做,請呼叫其中引數已傳送到相對應 {@link android.content.ContentProvider} 方法的 {@link android.content.ContentResolver} 方法。 +供應程式和供應程式用戶端會自動處理安全性和處理程序間通訊。 + +

+

插入資料

+

+ 如要在供應程式中插入資料,請呼叫 +{@link android.content.ContentResolver#insert ContentResolver.insert()} 方法。 +這個方法會在供應程式中插入新的資料列,並傳回該列的內容 URI。 + 以下程式碼片段示範如何在使用者字典供應程式中插入新的字詞: +

+
+// Defines a new Uri object that receives the result of the insertion
+Uri mNewUri;
+
+...
+
+// Defines an object to contain the new values to insert
+ContentValues mNewValues = new ContentValues();
+
+/*
+ * Sets the values of each column and inserts the word. The arguments to the "put"
+ * method are "column name" and "value"
+ */
+mNewValues.put(UserDictionary.Words.APP_ID, "example.user");
+mNewValues.put(UserDictionary.Words.LOCALE, "en_US");
+mNewValues.put(UserDictionary.Words.WORD, "insert");
+mNewValues.put(UserDictionary.Words.FREQUENCY, "100");
+
+mNewUri = getContentResolver().insert(
+    UserDictionary.Word.CONTENT_URI,   // the user dictionary content URI
+    mNewValues                          // the values to insert
+);
+
+

+ 新資料列的資料會傳入單一 {@link android.content.ContentValues} 物件,該物件的格式與單列游標類似。 +您不必為這個物件中的資料欄指定相同的資料類型,而且如果您不想指定任何值,可以將資料欄設定為 null 以使用 {@link android.content.ContentValues#putNull ContentValues.putNull()}。 + + +

+

+ 該程式碼片段並不會新增 _ID 欄,這是因為系統自動會維護該欄。 +供應程式會將不重複值 _ID 指派給新增的所有資料列。 +供應程式通常會採用該值做為表格的主索引鍵。 +

+

+ newUri 以下方格式傳回的內容 URI 可用於識別新增的資料列: + +

+
+content://user_dictionary/words/<id_value>
+
+

+ <id_value> 是 ID 為 _ID 的資料列內容。 + 大多數供應程式可自動偵測這種格式的內容 URI,並據以針對指定的資料列執行特定作業。 + +

+

+ 如要從傳回的 {@link android.net.Uri} 取得 _ID 的值,請呼叫 +{@link android.content.ContentUris#parseId ContentUris.parseId()}。 +

+

更新資料

+

+ 如要更新資料列,請使用內含經過更新的值 (與您在插入資料時所使用的值相同) 以及選取條件 (與您在建立查詢時所使用的選取條件相同) 的 {@link android.content.ContentValues} 物件。 + + 您所使用的用戶端方法為 +{@link android.content.ContentResolver#update ContentResolver.update()}。您只需針對要更新的資料欄,將相關值加到 {@link android.content.ContentValues} 物件即可。 +如果您想清除資料欄的內容,請將值設定為 null。 + +

+

+ 以下程式碼片段會針對地區代碼含有「en」字詞的所有資料列,將其地區代碼變更為 null。 +系統會傳回已更新的資料列數量: +

+
+// Defines an object to contain the updated values
+ContentValues mUpdateValues = new ContentValues();
+
+// Defines selection criteria for the rows you want to update
+String mSelectionClause = UserDictionary.Words.LOCALE +  "LIKE ?";
+String[] mSelectionArgs = {"en_%"};
+
+// Defines a variable to contain the number of updated rows
+int mRowsUpdated = 0;
+
+...
+
+/*
+ * Sets the updated value and updates the selected words.
+ */
+mUpdateValues.putNull(UserDictionary.Words.LOCALE);
+
+mRowsUpdated = getContentResolver().update(
+    UserDictionary.Words.CONTENT_URI,   // the user dictionary content URI
+    mUpdateValues                       // the columns to update
+    mSelectionClause                    // the column to select on
+    mSelectionArgs                      // the value to compare to
+);
+
+

+ 建議您在呼叫 {@link android.content.ContentResolver#update ContentResolver.update()} 時對使用者輸入進行檢測。 +如需詳細資訊,請參閱防範惡意輸入。 + +

+

刪除資料

+

+ 刪除資料列的方法與擷取資料列資料類似:您必須為想刪除的資料列指定選取條件,用戶端方法最後會傳回已刪除的資料列數量。 + + 以下程式碼片段可刪除應用程式 ID 為「user」的資料列。此外,這個方法還會傳回已刪除的資料列數量。 + +

+
+
+// Defines selection criteria for the rows you want to delete
+String mSelectionClause = UserDictionary.Words.APP_ID + " LIKE ?";
+String[] mSelectionArgs = {"user"};
+
+// Defines a variable to contain the number of rows deleted
+int mRowsDeleted = 0;
+
+...
+
+// Deletes the words that match the selection criteria
+mRowsDeleted = getContentResolver().delete(
+    UserDictionary.Words.CONTENT_URI,   // the user dictionary content URI
+    mSelectionClause                    // the column to select on
+    mSelectionArgs                      // the value to compare to
+);
+
+

+ 建議您在呼叫 {@link android.content.ContentResolver#delete ContentResolver.delete()} 時對使用者輸入進行檢測。 +如需詳細資訊,請參閱防範惡意輸入。 + +

+ +

供應程式資料類型

+

+ 內容供應程式可提供多種資料類型。使用者字典供應程式只能提供文字,但供應程式還可提供下列格式: + +

+
    +
  • + 整數 +
  • +
  • + 長整數 (long) +
  • +
  • + 浮點數 +
  • +
  • + 長浮點數 (double) +
  • +
+

+ 供應程式慣用的其他資料類型為二進位大型物件 (BLOB),這種資料會實作成 64 KB 位元組陣列。 +如果想瞭解可用的資料類型,請查閱 {@link android.database.Cursor} 類別的「get」方法。 + +

+

+ 供應程式的說明文件通常會列出其中每個資料欄的資料類型。 + 使用者字典供應程式的資料類型列在其合約類別 {@link android.provider.UserDictionary.Words} 的參考文件 (如需合約類別的相關資訊,請參閱合約類別一節) 中。 + + + 您也可以呼叫 {@link android.database.Cursor#getType +Cursor.getType()} 來確認可用的資料類型。 +

+

+ 此外,供應程式也會保留任何所定義內容 URI 的 MIME 資料類型資訊。您可以利用 MIME 類型資訊確認您的應用程式是否可處理供應程式所提供的資料,或是根據 MIME 類型選擇處理方式類型。 + +當您使用內含複雜的資料結構或檔案的供應程式時,通常需要使用 MIME 類型。 + +例如,聯絡人供應程式中的 {@link android.provider.ContactsContract.Data} 表格會使用 MIME 類型為每個資料列中儲存的聯絡人資料加上標籤。 + +如要取得與內容 URI 相對應的 MIME 類型,請呼叫 +{@link android.content.ContentResolver#getType ContentResolver.getType()}。 +

+

+ 如要瞭解標準與自訂 MIME 類型的語法,請參閱 MIME 類型參考資料。 + +

+ + + +

供應程式存取權的替代形式

+

+ 開發應用程式時會使用到 3 種供應程式存取權的替代形式: +

+
    +
  • + 批次存取:您可以使用 {@link android.content.ContentProviderOperation} 類別中的方法建立批次存取權呼叫,然後利用 {@link android.content.ContentResolver#applyBatch ContentResolver.applyBatch()} 套用這些呼叫。 + + +
  • +
  • + 非同步查詢:建議您在個別執行緒中進行查詢。您可以使用 {@link android.content.CursorLoader} 類別來完成這項作業。 +如需相關示範說明,請參閱載入器指南中的範例。 + + +
  • +
  • + 透過意圖存取資料:您無法直接將意圖傳送到供應程式,但可以將意圖傳送到供應程式的應用程式;供應程式的應用程式通常是修改供應程式資料的最佳環境。 + + +
  • +
+

+ 如需透過意圖進行批次存取和修改作業的相關資訊,請參閱下文。 +

+

批次存取

+

+ 您可利用供應程式的批次存取功能插入大量資料列、透過相同方法呼叫在多個表格中插入資料列,或是透過單次交易 (微型作業) 在處理程序之間執行一組作業。 + + +

+

+ 如要以「批次模式」存取供應程式,請建立一系列 {@link android.content.ContentProviderOperation} 物件,然後使用 {@link android.content.ContentResolver#applyBatch ContentResolver.applyBatch()} 將這些物件分派給內容供應程式。 + + +請將內容供應程式的「授權」(而不是特定內容 URI) 傳入這個方法。這樣可讓陣列中的所有 {@link android.content.ContentProviderOperation} 物件能夠在不同表格中運作。 + + +如果您呼叫 {@link android.content.ContentResolver#applyBatch +ContentResolver.applyBatch()},則系統會傳回一系列結果。 +

+

+ {@link android.provider.ContactsContract.RawContacts} 合約類別的說明包含可展示批次插入作業的程式碼片段。 +聯絡人管理員範例應用程式的 ContactAdder.java 來源檔案包含批次存取範例供您參考。 + + + +

+ +

透過意圖存取資料

+

+ 意圖可提供內容供應程式的間接存取權。即使您的應用程式沒有存取權限,您仍可透過以下方式允許使用者存取供應程式的資料:從具備權限的應用程式取回結果意圖,或是啟用具備權限的應用程式並允許使用者存取該應用程式。 + + + +

+

透過臨時權限取得存取權

+

+ 即使沒有適當的存取權限,您仍可存取內容供應程式的資料,方法是傳送意圖到沒有權限的應用程式,然後接收內含「URI」權限的結果意圖。 + + + URI 權限是特定內容 URI 專用的權限;在接收權限的 Activity 結束之前,這類權限會維持有效狀態。 +具備永久權限的應用程式可授予臨時權限,只要在結果意圖中設定旗標即可: + +

+
    +
  • + 讀取權限: + {@link android.content.Intent#FLAG_GRANT_READ_URI_PERMISSION} +
  • +
  • + 寫入權限: + {@link android.content.Intent#FLAG_GRANT_WRITE_URI_PERMISSION} +
  • +
+

+ 注意:這些旗標並不會將讀取或寫入存取權授予系統在內容 URI 中提供授權的供應程式。存取權僅供 URI 使用。 + +

+

+ 供應程式會使用 +<provider> + 元素的 +android:grantUriPermission + 屬性以及 +<provider> + 元素的 +<grant-uri-permission> + 子元素在本身的宣示說明中為內容 URI 定義 URI 權限。如要進一步瞭解 URI 權限的運作機制,請參閱安全性和權限指南的「URI 權限」。 + + +

+

+ 例如,即使您沒有 {@link android.Manifest.permission#READ_CONTACTS} 權限,您仍可透過聯絡人供應程式擷取聯絡人資料。 +您可能會想透過可用來在聯絡人的生日當天傳送電子賀卡給對方的應用程式進行這項動作。 +您偏好讓使用者控管要讓您的應用程式使用的聯絡人資料,而不是要求 {@link android.Manifest.permission#READ_CONTACTS} 授權您存取使用者的所有聯絡人以及個人資訊。 + + +為了達到這個目的,您進行了下列程序: +

+
    +
  1. + 您的應用程式使用 {@link android.app.Activity#startActivityForResult +startActivityForResult()} 方法,傳送了內含 +{@link android.content.Intent#ACTION_PICK} 動作的意圖以及「聯絡人」MIME 類型 +{@link android.provider.ContactsContract.RawContacts#CONTENT_ITEM_TYPE}。 + +
  2. +
  3. + 由於該意圖符合聯絡人應用程式的「選取」Activity 的意圖篩選器,因此該 Activity 會在移動到前景。 + +
  4. +
  5. + 在選取 Activity 中,使用者選取了要更新的聯絡人。 +一旦使用者進行這項動作,選取 Activity 便會呼叫 +{@link android.app.Activity#setResult setResult(resultcode, intent)} 來設定要傳回您應用程式的意圖。 +該意圖包含使用者所選聯絡人的內容 URI,以及「額外」的旗標 +{@link android.content.Intent#FLAG_GRANT_READ_URI_PERMISSION}。 +這些旗標可將 URI 權限授予您的應用程式,以便其讀取內容 URI 指向的聯絡人資料。選取 Activity 隨後會呼叫 {@link android.app.Activity#finish()} 來傳回您應用程式的控制權。 + + + +
  6. +
  7. + 您的 Activity 返回前景,而系統呼叫您 Activity 的 +{@link android.app.Activity#onActivityResult onActivityResult()} 方法。 +這個方法可接收聯絡人應用程式中的選取 Activity 所建立的結果意圖。 + +
  8. +
  9. + 即使您未在宣示說明中要求供應程式的永久讀取權限,只要利用結果意圖的內容 URI 即可從聯絡人供應程式讀取聯絡人資料。 + +您之後可以取得聯絡人的出生日期資訊或聯絡人的電子郵件地址,以便傳送電子賀卡給對方。 + +
  10. +
+

使用其他應用程式

+

+ 允許使用者修改您無法存取的資料的簡單方式,是啟用具備相關權限的應用程式,並且讓使用者透過該應用程式進行修改作業。 + +

+

+ 例如,日曆應用程式接受可讓您啟用應用程式插入 UI 的 +{@link android.content.Intent#ACTION_INSERT} 意圖。您可以在該意圖中傳入「額外」的資料,供應用程式用於預先填入使用者介面。由於週期性活動的語法較為複雜,因此建議您利用 {@link android.content.Intent#ACTION_INSERT} 啟用日曆應用程式,然後讓使用者透過該應用程式將活動插入日曆供應程式。 + + + + +

+ +

合約類別

+

+ 合約類別可定義協助應用程式使用內容 URI、欄名稱、意圖動作,以及內容供應程式的其他功能的常數。 +合約類別並不會自動納入供應程式;供應程式的開發人員必須自行定義合約類別,然後將其提供給其他開發人員。 + +Android 平台內建的大多數供應程式都可在 {@link android.provider} 套件中取得對應的合約類別。 + +

+

+ 例如,使用者字典供應程式有一個內含內容 URI 和欄名稱常數的 {@link android.provider.UserDictionary} 合約類別。 +「字詞」表格的內容 URI 是在 +{@link android.provider.UserDictionary.Words#CONTENT_URI UserDictionary.Words.CONTENT_URI} 常數中定義。 + + 此外,{@link android.provider.UserDictionary.Words} 類別也包含欄名稱常數,可用於本指南中的程式碼片段範例。 +例如,您可以將查詢投影定義成如下所示: + +

+
+String[] mProjection =
+{
+    UserDictionary.Words._ID,
+    UserDictionary.Words.WORD,
+    UserDictionary.Words.LOCALE
+};
+
+

+ 聯絡人供應程式的另一個合約類別為 {@link android.provider.ContactsContract}。 + 此類別的參考文件附有程式碼片段範例。其中一個 +{@link android.provider.ContactsContract.Intents.Insert} 子類別為內含意圖常數和意圖資料的合約類別。 + +

+ + + +

MIME 類型參考資料

+

+ 內容供應程式可傳回標準 MIME 媒體類型或自訂媒體類型字串,或是以上兩者。 +

+

+ 以下是 MIME 類型的格式 +

+
+type/subtype
+
+

+ 例如,常見的 MIME 類型 text/html 包含 text 類型以及 html 子類型。 +如果供應程式傳回這種 URI 類型,代表採用該 URI 的查詢會傳回內含 HTML 標記的文字。 + +

+

+ 自訂 MIME 類型字串 (亦稱為「廠商專用」MIME 類型) 包含較為複雜的類型和子類型值。 +針對多個資料欄,類型值一律會如下所示 +

+
+vnd.android.cursor.dir
+
+

+ 或者,針對單一資料欄,類型值一律會如下所示 +

+
+vnd.android.cursor.item
+
+

+ 。 +

+

+ 子類型為供應程式專用。Android 內建的供應程式通常包含簡易的子類型。 +例如,當聯絡人應用程式建立電話號碼資料列時,會為該列設定下列 MIME 類型: + +

+
+vnd.android.cursor.item/phone_v2
+
+

+ 請注意,子類型值為 phone_v2。 +

+

+ 其他的供應程式開發人員可能會根據供應程式的授權和表格名稱,自行建立專屬的子類型模式。 +例如,考慮一個包含火車時刻表的供應程式。 + 供應程式的授權為 com.example.trains 且包括 Line1、Line2 和 Line3 表格。 +根據 Line1 表格的內容 URI +

+

+

+content://com.example.trains/Line1
+
+

+ 供應程式會傳回 MIME 類型 +

+
+vnd.android.cursor.dir/vnd.example.line1
+
+

+ 根據 Line1 表格的內容 URI +

+
+content://com.example.trains/Line2/5
+
+

+ 供應程式會傳回 MIME 類型 +

+
+vnd.android.cursor.item/vnd.example.line2
+
+

+ 大多數內容供應程式會針對其使用的 MIME 類型定義合約類別常數。例如,聯絡人供應程式的合約類別 {@link android.provider.ContactsContract.RawContacts} 會為某個 MIME 類型的原始聯絡人列定義 {@link android.provider.ContactsContract.RawContacts#CONTENT_ITEM_TYPE} 常數。 + + + + +

+

+ 如要進一步瞭解個別資料列的內容 URI,請參閱內容 URI。 + +

diff --git a/docs/html-intl/intl/zh-tw/guide/topics/providers/content-provider-creating.jd b/docs/html-intl/intl/zh-tw/guide/topics/providers/content-provider-creating.jd new file mode 100644 index 0000000000000000000000000000000000000000..3d46ee4b4c8092c176a3d31893b819034a94c2af --- /dev/null +++ b/docs/html-intl/intl/zh-tw/guide/topics/providers/content-provider-creating.jd @@ -0,0 +1,1214 @@ +page.title=建立內容供應程式 +@jd:body +
+
+ + +

本文件內容

+
    +
  1. + 設計資料儲存空間 +
  2. +
  3. + 設計內容 URI +
  4. +
  5. + 實作 ContentProvider 類別 +
      +
    1. + 必要方法 +
    2. +
    3. + 實作 query() 方法 +
    4. +
    5. + 實作 insert() 方法 +
    6. +
    7. + 實作 delete() 方法 +
    8. +
    9. + 實作 update() 方法 +
    10. +
    11. + 實作 onCreate() 方法 +
    12. +
    +
  6. +
  7. + 實作內容供應程式 MIME 類型 +
      +
    1. + 表格 MIME 類型 +
    2. +
    3. + 檔案 MIME 類型 +
    4. +
    +
  8. +
  9. + 實作合約類別 +
  10. +
  11. + 實作內容供應程式權限 +
  12. +
  13. + <provider> 元素 +
  14. +
  15. + 意圖和資料存取權 +
  16. +
+

重要類別

+
    +
  1. + {@link android.content.ContentProvider} +
  2. +
  3. + {@link android.database.Cursor} +
  4. +
  5. + {@link android.net.Uri} +
  6. +
+

相關範例

+
    +
  1. + Note Pad 範例應用程式 + + +
  2. +
+

另請參閱

+
    +
  1. + 內容供應程式基本存放庫概念 + +
  2. +
  3. + 日曆供應程式 + +
  4. +
+
+
+ + +

+ 內容供應程式可管理中央資料存放庫的存取權。您可以將供應程式實作成 Android 應用程式的一或多個類別,以及宣示說明檔案的元素。 + +您的其中一個類別會實作子類別 +{@link android.content.ContentProvider} (供應程式與其他應用程式之間的介面)。 +雖然內容供應程式的用途是將資料提供給其他應用程式,不過您也可以在應用程式中加入 Activity,讓使用者查詢及修改供應程式所管理的資料。 + + +

+

+ 本主題的其餘部分為一份說明建置內容供應程式的步驟清單,以及一份可供您使用的 API 清單。 + +

+ + + +

建置供應程式的前置作業

+

+ 請先完成下列事項,再開始建置供應程式: +

+
    +
  1. + 評估建置內容供應程式的必要性。如果您想提供下列功能,您就必須建置內容供應程式: + +
      +
    • 您想將複雜的資料或檔案提供給其他應用程式。
    • +
    • 您想讓使用者從您的應用程式將複雜的資料複製到其他應用程式。
    • +
    • 您想使用搜尋架構提供自訂搜尋建議。
    • +
    +

    + 如果您的應用程式內建所有您想提供的功能,您就「不需要」建置供應程式來使用 SQLite 資料庫。 + +

    +
  2. +
  3. + 詳閱內容供應程式基本概念進一步瞭解供應程式 (如果您尚未閱讀該文章的話)。 + + +
  4. +
+

+ 完成上述事項後,請按照下列步驟建置供應程式: +

+
    +
  1. + 為您的資料設計原始儲存空間。內容供應程式會以兩種方式提供資料: +
    +
    + 檔案資料 +
    +
    + 通常儲存在檔案中的資料,例如相片、音訊或影片。 +這種檔案會儲存在您應用程式的私人空間中。 +您的供應程式可針對此檔案提供處理支援,藉此回應其他應用程式發出的檔案要求。 + +
    +
    + 「結構化」資料 +
    +
    + 通常儲存在資料庫、陣列或類似結構中的資料。 + 這種資料會採用與內含資料列和資料欄的表格相容的格式儲存。資料列代表一個實體,例如某個人或庫存中的商品。 +而資料欄則代表實體的部分資料,例如某個人的名稱或商品的售價。 +這種資料類型一般會儲存在 SQLite 資料庫,但您也可以將它儲存在其他類型的永久儲存空間。 + +如要進一步瞭解 Android 系統提供的儲存空間類型,請參閱設計資料儲存空間。 + + +
    +
    +
  2. +
  3. + 定義 {@link android.content.ContentProvider} 類別及其所需方法的實作方式。 +這個類別是您的資料與 Android 系統其餘部分之間的介面。 +如要進一步瞭解這個類別,請參閱實作 ContentProvider 類別。 + +
  4. +
  5. + 定義供應程式的授權字串、內容 URI 和資料列名稱。如果您希望供應程式的應用程式能控制意圖,請同時定義意圖動作、額外資料和旗標。 + +此外,您還需要針對想存取您資料的應用程式,定義其所需的權限。 +建議您將這些值定義為個別合約類別中的常數;您之後可將這個類別提供給其他開發人員。 +如要進一步瞭解內容 URI,請參閱設計內容 URI。 + + + 如要進一步瞭解意圖,請參閱意圖和資料存取權。 + +
  6. +
  7. + 視需要進行其他動作,例如新增範例資料,或實作 {@link android.content.AbstractThreadedSyncAdapter} 讓應程式與雲端資料之間的資料保持同步。 + + +
  8. +
+ + + +

設計資料儲存空間

+

+ 內容供應程式是模組化格式資料的介面。建議這個介面之前,請先決定您儲存資料的方式。 +您可用任何偏好格式儲存資料,然後設計對應介面來讀取及寫入所需資料。 + +

+

+ 以下是 Android 提供的部分資料儲存技術: +

+
    +
  • + Android 系統內含 SQLite 資料庫 API,可讓 Android 本身的供應程式用於儲存以表格為基礎的資料。 + +{@link android.database.sqlite.SQLiteOpenHelper} 類別可協助您建立資料庫,而 {@link android.database.sqlite.SQLiteDatabase} 類別則是用於存取資料庫的基礎類別。 + + +

    + 請記住,您不需要使用資料庫實作存放庫。供應程式會以一組表格的形式對外顯示 (類似於相關的資料庫),不過這並非在內部實作供應程式的必要條件。 + + +

    +
  • +
  • + 如果您想儲存檔案資料,請使用 Android 提供的多種適用於檔案的 API。 + 如要進一步瞭解檔案儲存空間,請參閱資料儲存空間。 +如果您想設計可提供媒體相關資料 (例如音樂或影片) 的供應程式,建議您建立結合表格資料與檔案的供應程式。 + + +
  • +
  • + 如果您想存取以網路為基準的資料,請使用 {@link java.net} 和 +{@link android.net} 中的類別。您也可以將以網路為基準的資料同步到本機資料儲存空間 (例如資料庫),然後以表格或檔案的形式提供資料。 + + 範例同步配接器應用程式範例示範了如何進行這種同步處理作業。 + +
  • +
+

+ 資料設計注意事項 +

+

+ 以下提供一些有關設計供應程式資料結構的祕訣: +

+
    +
  • + 表格資料一律需包含「主索引鍵」欄,方便供應程式保存每個資料列的數值。 +您可以使用這些值將資料列連結至其他表格中的相關資料列 (也就是將這些值當作「外部索引鍵」使用)。 +事實上,您也可以使用此資料欄的任何名稱進行連結,但使用 {@link android.provider.BaseColumns#_ID BaseColumns._ID} 是最佳做法,這是因為將供應程式的查詢結果連結至 +{@link android.widget.ListView} 時需要將某個擷取出的資料列命名為 +_ID。 + + +
  • +
  • + 如果您想提供點陣圖或檔案資料的一大部分,請將資料儲存在檔案中,然後以間接方式提供該檔案,而不要直接將該檔案儲存在表格中。 + +如果您採用這種做法,請務必通知供應程式的使用者採用 +{@link android.content.ContentResolver} 檔案方法來存取資料。 +
  • +
  • + 使用二進位大型物件 (BLOB) 資料類型儲存大小或結構不同的資料。 +例如,您可以使用 BLOB 欄儲存通訊協定緩衝區 或 JSON 結構。 + + +

    + 此外,您也可以使用 BLOB 實作「按結構定義分門別類」的表格。在這種表格中,您需要定義主索引鍵欄、MIME 類型欄和一或多個 BLOB 一般資料欄。 + +MIME 類型欄中的值會決定 BLOB 欄的資料定義, +這可讓您在同一表格中儲存多種資料列類型。 +聯絡人供應程式的「資料」表格 +{@link android.provider.ContactsContract.Data} 即為按結構定義分門別類的表格範例。 + +

    +
  • +
+ +

設計內容 URI

+

+ 內容 URI 是指用於識別供應程式資料的 URI,其中包括整個供應程式的符號名稱 (亦即供應程式的授權),以及指向表格或檔案的名稱 (亦即路徑)。 + +選用的 ID 部分則會指向表格中的個別資料欄。 + +{@link android.content.ContentProvider} 的每個資料存取方法均包括一個內容 URI (引數);該內容 URI 可讓您決定要存取哪個表格、資料列或檔案。 + +

+

+ 如需內容 URI 的基本概念,請參閱內容供應程式基本概念。 + + +

+

設計授權

+

+ 供應程式通常會包含一個授權,可當做供應程式 Android 內部名稱。為了避免與其他供應程式發生衝突,請務必反向使用網際網路網域擁有權作為供應程式授權的基礎。 + +此外,也請針對 Android 套件名稱採取此建議做法;您可以將供應程式授權定義為內含供應程式的套件名稱的副檔名。 + +例如,假設您 Android 套件的名稱為 + com.example.<appname>,則請將供應程式的授權定義為 +com.example.<appname>.provider。 +

+

設計路徑結構

+

+ 開發人員通常只要附加指向個別表格的路徑,即可從授權建立內容 URI。 +例如,假設您有「table1」和「table2」這兩個表格,則您可以結合上述範例中的授權來產生內容 URI +com.example.<appname>.provider/table1 和 +com.example.<appname>.provider/table2。 + +路徑並不侷限於單一區隔,而您也不必為每個路徑層級產生表格。 + +

+

處理內容 URI ID

+

+ 一般來說,供應程式可利用 URI 尾端資料列的 ID 值接受內容 URI,藉此提供某個資料列的存取權。此外,供應程式通常也可比對 ID 值與表格的 _ID 欄,然後對相符的資料列執行要求的存取動作。 + + + +

+

+ 這種機制可協助採用一般設計模式應用程式存取供應程式,讓應用程式對供應程式執行查詢,並且利用 {@link android.widget.CursorAdapter} 在 {@link android.widget.ListView} 中顯示最終的 {@link android.database.Cursor}。 + + + 定義 {@link android.widget.CursorAdapter} 時需要將 +{@link android.database.Cursor} 的其中一個資料列設為 _ID。 +

+

+ 使用者之後挑選了顯示在 UI 中的某一資料列,以查看或修改資料。 +應用程式會從支援 + {@link android.widget.ListView} 的 {@link android.database.Cursor} 取得對應的資料列、取得該資料列的 _ID 值,然後將該值附加到內容 URI 並向供應程式發出存取要求。 +供應程式之後可對使用者挑選的資料列進行查詢或修改。 + +

+

內容 URI 模式

+

+ 為協助您決定要對傳入內容 URI 採取什麼動作,供應程式 API 提供了簡便類別 {@link android.content.UriMatcher},可將內容 URI「模式」對應至整數值。 + +您可以在 switch 陳述式中使用整數值,決定要對符合特定模式的內容 URI 採取的動作。 + +

+

+ 內容 URI 模式會比對採用萬用字元的內容 URI: +

+
    +
  • + *比對由任何有效字元組成的字串;長度不限。 +
  • +
  • + #比對由數字組成的字串;長度不限。 +
  • +
+

+ 以設計及編寫內容 URI 處理方式為例,假設供應程式內含 com.example.app.provider 授權,可識別下列指向表格的內容 URI: + + +

+
    +
  • + content://com.example.app.provider/table1:名稱為 table1 的表格。 +
  • +
  • + content://com.example.app.provider/table2/dataset1:名稱為 +dataset1 的表格。 +
  • +
  • + content://com.example.app.provider/table2/dataset2:名稱為 +dataset2 的表格。 +
  • +
  • + content://com.example.app.provider/table3:名稱為 table3 的表格。 +
  • +
+

+ 此外,供應程式也會識別附有資料列 ID 的內容 URI,例如 content://com.example.app.provider/table3/1 會指定 table3 中 ID 為 + 1 的資料列。 + +

+

+ 以下是可能的內容 URI 模式: +

+
+
+ content://com.example.app.provider/* +
+
+ 與供應程式的任何內容 URI 相符。 +
+
+ content://com.example.app.provider/table2/*: +
+
+ 與 dataset1dataset2 表格的內容 URI 相符,但與 table1 或 +table3 的內容 URI 不符。 + +
+
+ content://com.example.app.provider/table3/#:比對 table3 中單一資料列的內容 URI,例如 + content://com.example.app.provider/table3/6 會比對其中的 + 6 所指定的資料列。 + +
+
+

+ 以下程式碼片段說明各種方法在 {@link android.content.UriMatcher} 中的運作方式。 + 這個程式碼會以不同方式處理整個表格的 URI 以及單一資料列的 URI;針對表格使用內容 URI 模式 +content://<authority>/<path>,針對單一資料列則使用 +content://<authority>/<path>/<id>。 + +

+

+ {@link android.content.UriMatcher#addURI(String, String, int) addURI()} 方法會將授權和 +路徑對應至某個整數值,而 {@link android.content.UriMatcher#match(Uri) + match()} 方法會傳回 URI 的整數值。switch 陳述式則會要查詢整個表格,還是查詢單一記錄: + +

+
+public class ExampleProvider extends ContentProvider {
+...
+    // Creates a UriMatcher object.
+    private static final UriMatcher sUriMatcher;
+...
+    /*
+     * The calls to addURI() go here, for all of the content URI patterns that the provider
+     * should recognize. 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
+     */
+    sUriMatcher.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.
+     */
+    sUriMatcher.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 (sUriMatcher.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 is not recognized, you should do some error handling here.
+        }
+        // call the code to actually do the query
+    }
+
+

+ 另一個 {@link android.content.ContentUris} 類別可提供使用內容 URI 的 id 部分的簡便方法。 +{@link android.net.Uri} 和 +{@link android.net.Uri.Builder} 類別則可提供剖析現有 +{@link android.net.Uri} 物件及建置新物件的簡便方法。 +

+ + +

實作 ContentProvider 類別

+

+ {@link android.content.ContentProvider} 執行個體可透過處理其他應用程式所發出要求的方式,管理一組結構化資料的存取權。 +所有形式的存取權最終都會呼叫 {@link android.content.ContentResolver},而這個類別隨後會呼叫 {@link android.content.ContentProvider} 方法來取得存取權。 + + +

+

必要方法

+

+ 抽象類別 {@link android.content.ContentProvider} 會定義 6 種方法,而您必須將這些方法實作成您所擁有子類別的一部分。 +嘗試存取您內容供應程式的用戶端應用程式會呼叫以下所有方法 +({@link android.content.ContentProvider#onCreate() onCreate()} 除外): + +

+
+
+ {@link android.content.ContentProvider#query(Uri, String[], String, String[], String) + query()} +
+
+ 從您的供應程式擷取檔案。您可以使用引數來選取要查詢的表格、要傳回的資料列和資料欄,以及最終結果的排序順序。 + + 以 {@link android.database.Cursor} 物件的形式傳回資料。 +
+
+ {@link android.content.ContentProvider#insert(Uri, ContentValues) insert()} +
+
+ 在供應程式中插入新的資料列。您可以使用引數來選取目標表格,以及取得要使用的資料欄值。 +此外,這個方法還可傳回新插入資料欄的內容 URI。 + +
+
+ {@link android.content.ContentProvider#update(Uri, ContentValues, String, String[]) + update()} +
+
+ 更新供應程式中的現有資料欄。您可以使用引數來選取要更新的表格和資料列,以及取得更新過後的資料欄值。 +此外,這個方法還可傳回經過更新的資料列數量。 +
+
+ {@link android.content.ContentProvider#delete(Uri, String, String[]) delete()} +
+
+ 從您的供應程式中刪除資料列。您可以使用引數來選取要刪除的表格和資料列。 +此外,這個方法還可傳回遭刪除的資料列數量。 +
+
+ {@link android.content.ContentProvider#getType(Uri) getType()} +
+
+ 傳回與內容 URI 相對應的 MIME 類型。如要進一步瞭解這個方法,請參閱實作內容供應程式 MIME 類型。 + +
+
+ {@link android.content.ContentProvider#onCreate() onCreate()} +
+
+ 初始化您的供應程式。Android 系統會在建立供應程式後立即呼叫這個方法。 +請注意,一旦 + {@link android.content.ContentResolver} 嘗試存取您的供應程式,Android 系統便會建立供應程式。 +
+
+

+ 請注意,上述方法採用的簽名與同名的 +{@link android.content.ContentResolver} 方法相同。 +

+

+ 實作這些方法時請注意下列事項: +

+
    +
  • + 多重執行緒可能會同時呼叫 {@link android.content.ContentProvider#onCreate() onCreate()} 以外的所有方法,因此請務必確保這些方法不會對執行緒造成負面影響。 +如要進一步瞭解多重執行緒,請參閱處理程序和執行緒。 + + + +
  • +
  • + 避免透過 {@link android.content.ContentProvider#onCreate() + onCreate()} 進行需大量時間才可完成的作業。除非有實質上的需求,否則請延遲初始化工作。 + 如需相關資訊,請參閱實作 onCreate() 方法。 + +
  • +
  • + 雖然您必須實作這些方法,但您的程式碼只需傳回預期的資料類型即可,不必傳回任何其他資料。 +例如,您可能想避免其他應用程式將資料插入部分表格。 +在這種情況下,您可以略過呼叫 +{@link android.content.ContentProvider#insert(Uri, ContentValues) insert()} 並傳回 0。 + +
  • +
+

實作 query() 方法

+

+ {@link android.content.ContentProvider#query(Uri, String[], String, String[], String) + ContentProvider.query()} 方法如果未傳回 {@link android.database.Cursor} 物件,便會發生錯誤而擲回 {@link java.lang.Exception}。 + +如果您採用 SQLite 資料庫做為資料儲存空間,您可以直接傳回 +{@link android.database.sqlite.SQLiteDatabase} 類別的任一 query() 方法所傳回的 {@link android.database.Cursor}。 + + 如果查詢不符任何資料列,您就必須傳回其中的 {@link android.database.Cursor#getCount()} 方法傳回 0 的 + {@link android.database.Cursor} 執行個體。 + 請注意,只有在查詢程序發生內部錯誤時,您才需要傳回 null。 +

+

+ 如果您並非採用 SQLite 資料庫做為資料儲存空間,請使用 + {@link android.database.Cursor} 的子類別。例如,{@link android.database.MatrixCursor} 類別 +會一系列 {@link java.lang.Object} 的所有資料列中實作游標。您可以搭配這個類別使用 {@link android.database.MatrixCursor#addRow(Object[]) addRow()} 來新增資料列。 + +

+

+ 請記住,您必須確保 Android 系統能夠與 {@link java.lang.Exception} 通訊,而不會受到處理程序的限制。 +Android 可以針對下列例外狀況執行這項動作,藉此協助解決查詢錯誤: + +

+
    +
  • + {@link java.lang.IllegalArgumentException} (如果供應程式接收的內容 URI 無效,您可以選擇擲回這個類別) + +
  • +
  • + {@link java.lang.NullPointerException} +
  • +
+

實作 insert() 方法

+

+ {@link android.content.ContentProvider#insert(Uri, ContentValues) insert()} 方法會利用 {@link android.content.ContentValues} 引數中的值,在適當的表格中新增資料列。 + +如果 {@link android.content.ContentValues} 引數中沒有資料欄名稱,建議您在供應程式的程式碼或資料庫的結構定義中,提供預設的資料欄名稱。 + + +

+

+ 這個方法會傳回新資料列的內容 URI。如要建構這個方法,請使用 {@link android.content.ContentUris#withAppendedId(Uri, long) withAppendedId()},將新資料列的 _ID (或其他主索引鍵) 值附加到表格的內容 URI。 + + +

+

實作 delete() 方法

+

+ {@link android.content.ContentProvider#delete(Uri, String, String[]) delete()} 方法並不會從您的資料儲存空間中刪除資料列。 +如果您搭配供應程式使用同步配接器,建議您為已刪除的資料列加上「已刪除」標示,而不是徹底移除資料列。 + +同步配接器可檢查已刪除的資料列,並且將這些資料列從伺服器中移除,然後再從供應程式中將它們刪除。 + +

+

實作 update() 方法

+

+ {@link android.content.ContentProvider#update(Uri, ContentValues, String, String[]) + update()} 方法會採用 {@link android.content.ContentProvider#insert(Uri, ContentValues) insert()} 所使用的相同 {@link android.content.ContentValues} 引數,以及 + {@link android.content.ContentProvider#delete(Uri, String, String[]) delete()} 和 {@link android.content.ContentProvider#query(Uri, String[], String, String[], String) + ContentProvider.query()} 所使用的相同 selectionselectionArgs 引數, + + +以便讓您針對這些方法重複使用相同的程式碼。 +

+

實作 onCreate() 方法

+

+ Android 系統會在啟動供應程式時呼叫 {@link android.content.ContentProvider#onCreate() + onCreate()}。建議您只使用這個方法執行可快速完成的初始化工作,並且等到供應程式實際收到資料要求後,再建立資料庫以及載入資料。 + +如果您使用 {@link android.content.ContentProvider#onCreate() onCreate()} 進行需大量時間才能完成工作,啟動供應程式所需的時間就會延長。 + +此外,這樣也會延遲供應程式回應其他應用程式的時間。 + +

+

+ 例如,如果您採用 SQLite 資料庫,您可以透過 +{@link android.content.ContentProvider#onCreate() ContentProvider.onCreate()} 建立新的 {@link android.database.sqlite.SQLiteOpenHelper} 物件,然後在初次開啟資料庫時建立 SQL 表格。 + +為了加快這個程序,當您初次呼叫 {@link android.database.sqlite.SQLiteOpenHelper#getWritableDatabase +getWritableDatabase()} 時,該方法會自動呼叫 {@link android.database.sqlite.SQLiteOpenHelper#onCreate(SQLiteDatabase) +SQLiteOpenHelper.onCreate()} 方法。 + + +

+

+ 以下兩個程式碼片段展示了 +{@link android.content.ContentProvider#onCreate() ContentProvider.onCreate()} 與 {@link android.database.sqlite.SQLiteOpenHelper#onCreate(SQLiteDatabase) + SQLiteOpenHelper.onCreate()} 之間的互動過程。 +而第一個程式碼片段是用於實作 +{@link android.content.ContentProvider#onCreate() ContentProvider.onCreate()}: +

+
+public class ExampleProvider extends ContentProvider
+
+    /*
+     * Defines a handle to the database helper object. The MainDatabaseHelper class is defined
+     * in a following snippet.
+     */
+    private MainDatabaseHelper mOpenHelper;
+
+    // Defines the database name
+    private static final String DBNAME = "mydb";
+
+    // Holds the database object
+    private SQLiteDatabase db;
+
+    public boolean onCreate() {
+
+        /*
+         * Creates a new helper object. This method always returns quickly.
+         * Notice that the database itself isn't created or opened
+         * until SQLiteOpenHelper.getWritableDatabase is called
+         */
+        mOpenHelper = new MainDatabaseHelper(
+            getContext(),        // the application context
+            DBNAME,              // the name of the database)
+            null,                // uses the default SQLite cursor
+            1                    // the version number
+        );
+
+        return true;
+    }
+
+    ...
+
+    // Implements the provider's insert method
+    public Cursor insert(Uri uri, ContentValues values) {
+        // Insert code here to determine which table to open, handle error-checking, and so forth
+
+        ...
+
+        /*
+         * Gets a writeable database. This will trigger its creation if it doesn't already exist.
+         *
+         */
+        db = mOpenHelper.getWritableDatabase();
+    }
+}
+
+

+ 第二個程式碼片段則是用於實作 {@link android.database.sqlite.SQLiteOpenHelper#onCreate(SQLiteDatabase) +SQLiteOpenHelper.onCreate()},包括協助程式類別: + +

+
+...
+// A string that defines the SQL statement for creating a table
+private static final String SQL_CREATE_MAIN = "CREATE TABLE " +
+    "main " +                       // Table's name
+    "(" +                           // The columns in the table
+    " _ID INTEGER PRIMARY KEY, " +
+    " WORD TEXT"
+    " FREQUENCY INTEGER " +
+    " LOCALE TEXT )";
+...
+/**
+ * Helper class that actually creates and manages the provider's underlying data repository.
+ */
+protected static final class MainDatabaseHelper extends SQLiteOpenHelper {
+
+    /*
+     * Instantiates an open helper for the provider's SQLite data repository
+     * Do not do database creation and upgrade here.
+     */
+    MainDatabaseHelper(Context context) {
+        super(context, DBNAME, null, 1);
+    }
+
+    /*
+     * Creates the data repository. This is called when the provider attempts to open the
+     * repository and SQLite reports that it doesn't exist.
+     */
+    public void onCreate(SQLiteDatabase db) {
+
+        // Creates the main table
+        db.execSQL(SQL_CREATE_MAIN);
+    }
+}
+
+ + + +

實作 ContentProvider MIME 類型

+

+ {@link android.content.ContentProvider} 類別包含兩種用於傳回 MIME 類型的方法: +

+
+
+ {@link android.content.ContentProvider#getType(Uri) getType()} +
+
+ 您必須針對任何供應程式實作的其中一個必要方法。 +
+
+ {@link android.content.ContentProvider#getStreamTypes(Uri, String) getStreamTypes()} +
+
+ 如果您的供應程式會提供檔案,您就需要實作這個方法。 +
+
+

表格 MIME 類型

+

+ {@link android.content.ContentProvider#getType(Uri) getType()} 方法會採用 MIME 格式傳回 + {@link java.lang.String},以說明 URI 引數所傳回的資料類型。 +{@link android.net.Uri} 可以是一個模式 (而非特定 URI);在這種情況下,建議您傳回與符合模式的內容 URI 相關聯的資料類型。 + + +

+

+ 針對文字、HTML、JPEG 等常見資料類型, +{@link android.content.ContentProvider#getType(Uri) getType()} 會傳回該資料的標準 MIME 類型。 +如需這些標準模式的完整清單,請造訪 + IANA MIME 媒體類型網站。 + +

+

+ 針對指向表格資料的資料列的內容 URI, +{@link android.content.ContentProvider#getType(Uri) getType()} 會採用 Android 廠商專用的 MINE 格式傳回 MIME 類型: + +

+
    +
  • + 類型部分:vnd +
  • +
  • + 子類型部分: +
      +
    • + 如果 URI 模式適用於單一資料列:android.cursor.item/ +
    • +
    • + 如果 URI 模式適用於一個以上的資料列:android.cursor.dir/ +
    • +
    +
  • +
  • + 供應程式專用部分:vnd.<name>.<type> +

    + 您需要提供 <name><type>。 + <name> 必須是全域唯一值,而 <type> 必須為相對應 URI 模式的專屬值。 + +建議您使用貴公司的名稱或您應用程式的部分 Android 套件名稱做為 <name>。 +針對 +<type>,則建議您使用可識別與 URI 相關的表格的字串。 + +

    + +
  • +
+

+ 例如,假設供應程式的授權為 +com.example.app.provider,而該授權可提供 +table1 這個表格,則 table1 中多個資料列的 MIME 類型會如下所示: +

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

+ 而 table1 中單一資料列的 MIME 類型則會如下所示: +

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

檔案 MIME 類型

+

+ 如果您的供應程式會提供檔案,您就需要實作 +{@link android.content.ContentProvider#getStreamTypes(Uri, String) getStreamTypes()}。 + 該方法會針對您的供應程式可為特定 URI 傳回的檔案,傳回一系列 MIME 類型的 {@link java.lang.String}。建議您按 MIME 類型篩選器引數篩選您提供的 MIME 類型,方便您只傳回用戶端想處理的 MIME 類型。 + + +

+

+ 例如,假設您的供應程式會採用 .jpg、 +.png.gif 檔案格式提供相片。 + 當有應用程式使用篩選器字串 image/* (類型為「圖片」的任何檔案) 呼叫 {@link android.content.ContentResolver#getStreamTypes(Uri, String) +ContentResolver.getStreamTypes()} 時,{@link android.content.ContentProvider#getStreamTypes(Uri, String) +ContentProvider.getStreamTypes()} 方法就會傳回如下所示的陣列: + + +

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

+ 如果應用程式只想取得 .jpg 檔案,則可以使用篩選器字串 *\/jpeg 呼叫 {@link android.content.ContentResolver#getStreamTypes(Uri, String) +ContentResolver.getStreamTypes()},此時 {@link android.content.ContentProvider#getStreamTypes(Uri, String) +ContentProvider.getStreamTypes()} 會傳回如下所示的陣列: + + +

+{"image/jpeg"}
+
+

+ 如果您的供應程式並未提供任何篩選器字串所要求的 MIME 類型,則 + {@link android.content.ContentProvider#getStreamTypes(Uri, String) getStreamTypes()} 會傳回 null。 + +

+ + + +

實作合約類別

+

+ 合約類別是 public final 類別,內含以下項目的固定不變定義:URI、欄名稱、MIME 類型以及供應程式擁有的其他中繼資料。 +這個類別會在供應程式與其他應用程式之間建立合約,藉此確保使用者在 URI、欄名等值有變更的情況下,仍可正常存取供應程式。 + + + +

+

+ 此外,由於合約類別針對其常數採用了好記的名稱,因此可協助開發人員避免使用錯誤的欄名或 URI 值。 +與其他類別相同,合約類別也包含 Javadoc 說明文件。 +如 Eclipse 這類整合式開發環境可利用合約類別自動填入常數名稱,並針對常數顯示 Javadoc。 + + +

+

+ 開發人員無法存取您應用程式中合約類別的類別檔案,但可以靜態方式從您提供的 .jar 檔案中將類別檔案編入其應用程式。 + +

+

+ {@link android.provider.ContactsContract} 類別及其巢狀類別均為合約類別範例。 + +

+

實作內容供應程式權限

+

+ 如需 Android 系統提供的所有權限和存取權的詳細資訊,請參閱安全性和權限。 + + 您也可以參閱資料儲存空間,瞭解各種儲存空間實際提供的安全性和權限。 + + 簡言之,請注意下列重點: +

+
    +
  • + 在預設情況下,儲存在裝置內部儲存空間的資料檔案只有您的應用程式和供應程式可存取。 + +
  • +
  • + 您所建立的 {@link android.database.sqlite.SQLiteDatabase} 資料庫只有您的應用程式和供應程式可存取。 + +
  • +
  • + 在預設情況下,您儲存到外部儲存空間的資料檔案為「公開分享」,任何人均可讀取。 +您無法使用內容供應程式針對外部儲存空間中的檔案限制存取權,這是因為其他應用程式可使用其他 API 呼叫讀取及寫入這類檔案。 + +
  • +
  • + 呼叫用於開啟/建立檔案,或是用於在裝置內部儲存空間中開啟/建立 SQLite 儲存庫的方法,可能會同時將讀取及寫入存取權授予其他所有應用程式。 +如果您採用內部檔案或資料庫做為供應程式的存放庫,並且將「開放讀取」或「開放寫入」存取權授予該存放庫,則您在供應程式的宣示說明中設定的權限無助於保護您的資料安全。 + + +內部儲存空間中的檔案和儲存庫的預設存取權為「不公開」;請勿針對供應程式的存放庫變更此存取權。 + +
  • +
+

+ 如果您想使用內容供應程式權限控制資料的存取權,建議您將資料儲存在內部檔案、SQLite 資料庫或「雲端」(例如遠端伺服器),並確保只有您的應用程式可以存取這些檔案和資料庫。 + + +

+

實作權限

+

+ 在預設情況下,您的供應程式不會設定任何權限,因此即使底層資料的存取權限為「不公開」,任何應用程式都可透過您的供應程式讀取及寫入資料。 +如果想改變這種情況,請使用屬性或 + <provider> 元素的子元素,在供應程式的宣示說明檔案中設定權限。 + +您可以選擇讓設定好的權限套用至整個供應程式、特定表格或記錄,或是套用至以上三者。 + +

+

+ 您可以使用一或多項 + <permission> 元素,在供應程式的宣示說明檔案中定義權限。 +為了將權限設為僅適用於您的供應程式,請針對 + + android:name 屬性使用 Java 式範圍。 +例如,請將讀取權限命名為 +com.example.app.provider.permission.READ_PROVIDER。 + +

+

+ 以下列出供應程式權限範圍的詳細說明;這份清單將從套用至整個供應程式的權限開始說明,接著逐一說明套用範圍較小的權限。 + + 套用範圍較小權限的優先等級會比套用範圍較大的權限來得高: +

+
+
+ 供應程式層級的單一讀取寫入權限 +
+
+ 這項權限是由 + <provider> 元素的 + + android:permission 屬性所指定,可控制整個供應程式的讀取及寫入存取權。 + +
+
+ 供應程式層級的個別讀取及寫入權限 +
+
+ 整個供應程式的讀取權限及寫入權限。您可以使用 + <provider> 元素的 + + android:readPermission 和 + + android:writePermission 屬性指定這兩項權限。 +這些權限的優先等級比 + + android:permission 所需的權限來得高。 +
+
+ 路徑層級權限 +
+
+ 供應程式內容 URI 的讀取、寫入或讀取/寫入權限。您可以使用 + + <provider> 元素的 + + <path-permission> 子元素指定您想控制的所有 URI。 +針對您所指定的每個內容 URI,您可以指定讀取/寫入權限、讀取權限或寫入權限,或是以上三種權限。 +讀取及寫入權限的優先等級比讀取/寫入權限來得高。 +此外,路徑層級權限的優先等級比供應程式層級權限來得高。 + +
+
+ 臨時權限 +
+
+ 這種權限層級可將臨時存取權授予某個應用程式,即使該應用程式不具備一般的必要權限。 +臨時存取功能可減少應用程式的宣示說明所需的權限數量。 + +在啟用臨時權限的情況下,只有會繼續存取您資料的應用程式,需要供應程式的「永久」權限。 + + +

+ 如果想允許外部的圖片檢視器應用程式顯示您供應程式中的相片附加檔案,請將實作電子郵件供應程式和應用程式時所需的權限納入考量。 + +如要授予圖片檢視器必要的存取權,而不發出權限要求,請設定相片內容 URI 的臨時權限。 +並且將您的電子郵件應用程式設計成在使用者想顯示相片時,將內含相片內容 URI 和權限旗標的意圖傳送給圖片檢視器。 + +讓圖片檢視器在檢視器沒有供應程式的一般讀取權限的情況下,仍可查詢電子郵件供應程式來擷取相片。 + + +

+

+ 如要啟用臨時權限,請設定 + + <provider> 元素的 + + android:grantUriPermissions 元素,或是在 + + <provider> 元素中新增一或多項 + + <grant-uri-permission> 子元素。如果您有使用臨時權限,您就必須在從供應程式中移除內容 URI 支援,以及將內容 URI 與臨時權限建立關聯時呼叫 {@link android.content.Context#revokeUriPermission(Uri, int) +Context.revokeUriPermission()}。 + + +

+

+ 屬性的值會決定供應程式的可存取部分。 + 如果將屬性設為 true,則系統會將臨時權限授予整個供應程式,從而覆寫供應程式層級或路徑層級權限所需的任何其他權限。 + + +

+

+ 如果將這個旗標設為 false,您就必須在 + + <provider> 元素中加入 + + <grant-uri-permission> 子元素。每項子元素都會指定要授予臨時存取權的內容 URI。 + +

+

+ 如要將臨時存取權委派給某款應用程式,您就必須在意圖中加入 +{@link android.content.Intent#FLAG_GRANT_READ_URI_PERMISSION} 或 +{@link android.content.Intent#FLAG_GRANT_WRITE_URI_PERMISSION} 旗標,或是同時加入以上兩者。請使用 +{@link android.content.Intent#setFlags(int) setFlags()} 方法設定這些旗標。 +

+

+ 如果系統未顯示 + android:grantUriPermissions 屬性,代表該屬性是設為 + false。 +

+
+
+ + + + +

<provider> 元素

+

+ 與 {@link android.app.Activity} 和 {@link android.app.Service} 元件相同,您必須使用 + + <provider> 元素在應用程式的宣示說明檔案中定義 {@link android.content.ContentProvider} 子類別:Android 系統會從元素取得下列資訊: + + + +

+
+ 授權 + ({@code + android:authorities}) +
+
+ 用於識別系統內整個供應程式的符號名稱。如要進一步瞭解這項屬性,請參閱設定內容 URI。 + + +
+
+ 供應程式類別名稱 + ( +android:name + ) +
+
+ 實作 {@link android.content.ContentProvider} 的類別。如要進一步瞭解這個類別,請參閱實作 ContentProvider 。 + + +
+
+ 權限 +
+
+ 這些屬性可指定其他應用程式在存取供應程式的資料時所需的權限: + + +

+ 如要進一步瞭解權限及相對應的屬性,請參閱實作內容供應程式權限。 + + +

+
+
+ 啟動及控制屬性 +
+
+ 這些屬性可決定 Android 系統啟動供應程式的方式和時間、供應程式的處理程序特性,以及其他執行階段設定: + + +

+ 如需上述屬性的完整說明,請參閱開發人員指南的 + + <provider> 元素。 + +

+
+
+ 資訊屬性 +
+
+ 選用的供應程式圖示和標籤: +
    +
  • + + android:icon:內含供應程式圖示的可繪資源。 + 這個圖示會顯示在應用程式清單 ([設定] > [應用程式] > [全部]) 中,供應程式標籤的旁邊。 + +
  • +
  • + + android:label:附有供應程式或其資料或以上兩者的說明的資訊標籤。 +這個標籤會顯示在應用程式清單 ([設定] > [應用程式] > [全部]) 中。 + +
  • +
+

+ 如需上述屬性的完整說明,請參閱開發人員指南的 + + <provider> 元素。 +

+
+
+ + +

意圖和資料存取權

+

+ 應用程式可透過 {@link android.content.Intent} 以間接方式存取內容供應程式。 + 利用這種存取方式的應用程式不會呼叫 {@link android.content.ContentResolver} 或 +{@link android.content.ContentProvider} 的任何方法,而是會傳送可啟動 Activity (此 Activity 通常屬於供應程式本身的應用程式) 的意圖。 +目標 Activity 會負責擷取資料並在本身的 UI 中顯示該資料。視意圖中的動作而定,目標 Activity 也可能會提示使用者修改供應程式的資料。 + + + 此外,意圖還可能會包含目標 Activity 顯示在 UI 中的「額外」資料;使用者之後可選擇是否要先變更這些資料,然後再將其用於修改供應程式的資料。 + + +

+

+ +

+

+ 您可以使用意圖存取權來確保資料的完整性。您的供應程式可能會將根據詳細定義的業務邏輯插入、更新及刪除的資料做為運作依據。 +如果是這樣,允許其他應用程式直接修改您的資料可能會導致資料失效。 + +如果想讓開發人員使用意圖存取權,請務必保留相關的完整記錄。 + 並且向他們說明為何使用您應用程式 UI 的意圖存取權,比嘗試使用自己的程式碼修改資料來得好。 + +

+

+ 處理想修改供應程式資料的傳入意圖的方式與處理其他意圖完全相同。 +如要進一步瞭解如何使用意圖,請參閱意圖和意圖篩選器。 + +

diff --git a/docs/html-intl/intl/zh-tw/guide/topics/providers/content-providers.jd b/docs/html-intl/intl/zh-tw/guide/topics/providers/content-providers.jd new file mode 100644 index 0000000000000000000000000000000000000000..7f7fa34b9c0cfa98b10724adfeb2290c52243543 --- /dev/null +++ b/docs/html-intl/intl/zh-tw/guide/topics/providers/content-providers.jd @@ -0,0 +1,108 @@ +page.title=內容供應程式 +@jd:body +
+
+ + + +

本文主題

+
    +
  1. + 內容供應程式基本概念 + +
  2. +
  3. + 建立內容供應程式 + +
  4. +
  5. + 日曆供應程式 +
  6. +
  7. + 聯絡人供應程式 +
  8. +
+ + +

相關範例

+
    +
  1. + 聯絡人管理員應用程式 + +
  2. +
  3. + 「游標 (使用者)」 + + +
  4. +
  5. + 「游標 (電話)」 + +
  6. +
  7. + 範例同步配接器 + +
  8. +
+
+
+

+ 內容供應程式可管理一組結構化資料的存取權、壓縮資料以及提供用於定義資料安全性的機制。 +內容供應程式是一種標準介面,可將某個處理程序中的資料與另一個處理程序中執行的程式碼建立連結。 + +

+

+ 如果想透過內容供應程式存取資料,請使用您應用程式的 {@link android.content.Context} 中的{@link android.content.ContentResolver} 物件,以用戶端的身分與供應程式通訊。 + + + {@link android.content.ContentResolver} 物件會與供應程式物件 (實作 {@link android.content.ContentProvider} 的類別執行個體) 通訊。 +而供應程式物件則會接收用戶端發出的資料要求、執行要求的動作,以及傳回最終結果。 + + +

+

+ 如果您打算與其他應用程式分享您的資料,您不必自行開發供應程式。 +不過,您必須為自己的應用程式準備專屬供應程式,以提供自訂搜尋建議。 +此外,如果您想從自己的應用程式複製複雜的資料或檔案並貼到其他應用程式,也需要準備專屬的供應程式。 + +

+

+ Android 隨附內容供應程式,可用於管理音訊、影片、圖片、個人聯絡資訊等資料。 +如果想查看 Android 隨附的部分內容供應程式,請參閱 android.provider + 套件的參考文件。 + +這些供應程式可透過任何 Android 應用程式存取,只不過使用上有些許限制。 + +

+ 如果想進一步瞭解內容供應程式,請參閱下列主題: +

+
+
+ + 內容供應程式基本概念 +
+
+ 如何在資料已整理成表格的情況下,透過內容供應程式存取資料。 +
+
+ + 建立內容供應程式 +
+
+ 如何自行建立內容供應程式。 +
+
+ + 日曆供應程式 +
+
+ 如何存取屬於 Android 平台一部分的日曆供應程式。 +
+
+ + 聯絡人供應程式 +
+
+ 如何存取屬於 Android 平台一部分的聯絡人供應程式。 +
+
diff --git a/docs/html-intl/intl/zh-tw/guide/topics/providers/document-provider.jd b/docs/html-intl/intl/zh-tw/guide/topics/providers/document-provider.jd new file mode 100644 index 0000000000000000000000000000000000000000..1dc7c46f43873167e4c2be88a2116d2691d6d685 --- /dev/null +++ b/docs/html-intl/intl/zh-tw/guide/topics/providers/document-provider.jd @@ -0,0 +1,916 @@ +page.title=儲存空間存取架構 +@jd:body + + + +

Android 4.4 (API 級別 19) 導入了「儲存空間存取架構」(Storage Access Framework (SAF)),SAF 可方便使用者透過偏好的文件儲存空間供應程式開啟文件、圖片等其他檔案。 + +提供簡單易用的標準 UI 可讓使用者在各種應用程式和供應程式中,以相同的方式瀏覽檔案及存取近期開啟的檔案。 +

+ +

雲端或本機儲存服務可實作會封裝服務本身的 +{@link android.provider.DocumentsProvider},藉此加入這個生態系統。您只需編寫幾行程式碼,即可將需要存取供應程式文件的用戶端應用程式與 SAF 整合。 + +

+ +

SAF 內含下列項目:

+ +
    +
  • 文件供應程式 — 可讓儲存服務 (例如 Google 雲端硬碟) 顯示所管理檔案的內容供應程式。 +文件供應程式是當作 +{@link android.provider.DocumentsProvider} 類別的子類別使用。文件供應程式結構定義是以傳統檔案階層為依據,不論您為文件供應程式設定的資料儲存方式為何。Android 平台內建數種文件供應程式,例如「下載」、「圖片」和「影片」。 + + + +
  • + +
  • 用戶端應用程式 — 可呼叫 +{@link android.content.Intent#ACTION_OPEN_DOCUMENT} 和 (或) +{@link android.content.Intent#ACTION_CREATE_DOCUMENT} 意圖及接收文件供應程式所傳回檔案的自訂應用程式。 +
  • + +
  • 挑選器 — 可讓使用者透過所有符合用戶端應用程式搜尋條件的文件供應程式存取文件的系統 UI。 +
  • +
+ +

以下是 SAF 提供的部分功能:

+
    +
  • 可讓使用者透過所有文件供應程式 (而非單一應用程式) 瀏覽內容。
  • +
  • 可將文件供應程式所擁有文件的存取權永久授予您的應用程式, +方便使用者透過相關供應程式新增、編輯、儲存及刪除檔案。 +
  • +
  • 支援多個使用者帳戶和暫時性的根目錄,例如只在插入電腦時才會顯示的 USB 儲存空間供應程式。 +
  • +
+ +

總覽

+ +

SAF 是以內容供應程式 ({@link android.provider.DocumentsProvider} 類別的子類別) 為基礎。 +「文件供應程式」中的資料結構採用如下所示的傳統檔案階層: +

+

data model

+

圖 1.文件供應程式資料模型。「根目錄」會指向單一「文件」,接著該文件會展開成樹狀結構的分支。請注意下列事項: +

+ +

+
    + +
  • 每個文件供應程式都會回報一或多個「根目錄」(也就是文件樹狀結構的起始點)。每個根目錄都有專屬的 {@link android.provider.DocumentsContract.Root#COLUMN_ROOT_ID},可導向至代表該根目錄所含內容的某份文件 (某個目錄)。根目錄的設計架構是動態的,能夠支援多重帳戶、暫時性 USB 儲存裝置或使用者登入/登出等使用狀況。 + + + + + +
  • + +
  • 每個根目錄都內含一份文件,而該文件會指向 N 份文件的 1,每份文件又可指向另外 N 份文件的 1。 +
  • + +
  • 每個儲存空間後端都會透過唯一的 +{@link android.provider.DocumentsContract.Document#COLUMN_DOCUMENT_ID} 來參照個別檔案,藉此顯示這些檔案及目錄。文件 ID 不得重複而且一旦核發便不得更改,原因在於裝置重新啟動時會將這些 ID 用於永久 URI 授權。 + + +
  • + + +
  • 文件可以是可開啟的檔案 (類型為 MIME) 或內含其他文件的目錄 (類型為 +{@link android.provider.DocumentsContract.Document#MIME_TYPE_DIR} MIME )。 +
  • + +
  • 每份文件的功能會視 +{@link android.provider.DocumentsContract.Document#COLUMN_FLAGS COLUMN_FLAGS} 而有所不同,例如 {@link android.provider.DocumentsContract.Document#FLAG_SUPPORTS_WRITE}、 +{@link android.provider.DocumentsContract.Document#FLAG_SUPPORTS_DELETE} 和 +{@link android.provider.DocumentsContract.Document#FLAG_SUPPORTS_THUMBNAIL}。相同的 {@link android.provider.DocumentsContract.Document#COLUMN_DOCUMENT_ID} 可以納入多個目錄中。 + + +
  • +
+ +

控管流程

+

如上所述,文件供應程式資料模型是以傳統檔案階層為基礎。 +不過,您可以自己偏好的方式儲存您的資料,只要所儲存資料可透過 {@link android.provider.DocumentsProvider} API 存取即可。例如,您可以將資料存放在標籤式的雲端儲存空間。 + +

+ +

圖 2 是相片應用程式如何使用 SAF 存取已儲存資料的說明範例: +

+

app

+ +

圖 2.儲存空間存取架構

+ +

請注意下列事項:

+
    + +
  • 在 SAF 中,供應程式與用戶端無法直接進行互動。 +用戶端必須取得相關權限才能與檔案進行互動 (也就是讀取、編輯、建立或刪除檔案)。 +
  • + +
  • 應用程式 (在此範例中為相片應用程式) 觸發 +{@link android.content.Intent#ACTION_OPEN_DOCUMENT} 或 {@link android.content.Intent#ACTION_CREATE_DOCUMENT} 意圖後,互動程序便會開始。意圖可能包括用於縮小條件範圍的篩選器 — 例如「將所有內含 MIME 類型『圖片』的可開啟檔案提供給我」。 + +
  • + +
  • 一旦觸發意圖,系統挑選器就會前往所有已註冊的供應程式,並且向使用者顯示相符的內容根目錄。 +
  • + +
  • 即便底層文件供應程式可能不盡相同,挑選器仍會提供使用者可用於存取文件的標準介面。 +例如圖 2 中的 Google 雲端硬碟供應程式、USB 供應程式和雲端供應程式。 +
  • +
+ +

圖 3 顯示的是使用者搜尋指定 Google 雲端硬碟帳戶中的圖片時所用的挑選器: +

+ +

picker

+ +

圖 3.挑選器

+ +

使用者選取 Google 雲端硬碟後,系統就會顯示相關圖片 (如圖 4 所示)。 +此時,使用者即可與這些圖片進行供應程式和用戶端應用程式支援的互動。 + + +

picker

+ +

圖 4.相關圖片

+ +

編寫用戶端應用程式

+ +

如果您想讓應用程式在搭載 Android 4.3 以下版本的裝置上從其他應用程式擷取檔案,您的應用程式就必須呼叫 {@link android.content.Intent#ACTION_PICK}或 {@link android.content.Intent#ACTION_GET_CONTENT} 意圖。 + +接著,使用者必須選取某款應用程式來選取檔案,而且選定的應用程式必須提供使用者介面,讓使用者瀏覽及挑選可用的檔案。 + +

+ +

針對搭載 Android 4.4 以上版本的裝置,您的應用程式還可以呼叫 +{@link android.content.Intent#ACTION_OPEN_DOCUMENT} 意圖,以顯示系統所控管的挑選器 UI,方便使用者瀏覽其他應用程式提供的所有檔案。 + +透過這個單一 UI,使用者可以從任何受支援的應用程式挑選檔案。 +

+ +

{@link android.content.Intent#ACTION_OPEN_DOCUMENT} 並不是 {@link android.content.Intent#ACTION_GET_CONTENT} 的替代意圖,實際上應呼叫的意圖取決於您應用程式的需求。 + +

+ +
    +
  • 如果您只想讓應用程式讀取/匯入資料,請呼叫 {@link android.content.Intent#ACTION_GET_CONTENT}, +以便應用程式匯入資料 (例如圖片檔) 的複本。 +
  • + +
  • 如果您想將文件供應程式所擁有文件的存取權永久授予您的應用程式,請呼叫 {@link android.content.Intent#ACTION_OPEN_DOCUMENT}。 + +例如可讓使用者編輯文件供應程式中所儲存圖片的相片編輯應用程式。 +
  • + +
+ + +

本節說明如何根據 +{@link android.content.Intent#ACTION_OPEN_DOCUMENT} 和 +{@link android.content.Intent#ACTION_CREATE_DOCUMENT} 意圖編寫用戶端應用程式。

+ + + + +

+以下程式碼片段採用 {@link android.content.Intent#ACTION_OPEN_DOCUMENT},可搜尋內含圖片檔的文件供應程式: + +

+ +
private static final int READ_REQUEST_CODE = 42;
+...
+/**
+ * Fires an intent to spin up the "file chooser" UI and select an image.
+ */
+public void performFileSearch() {
+
+    // ACTION_OPEN_DOCUMENT is the intent to choose a file via the system's file
+    // browser.
+    Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
+
+    // Filter to only show results that can be "opened", such as a
+    // file (as opposed to a list of contacts or timezones)
+    intent.addCategory(Intent.CATEGORY_OPENABLE);
+
+    // Filter to show only images, using the image MIME data type.
+    // If one wanted to search for ogg vorbis files, the type would be "audio/ogg".
+    // To search for all documents available via installed storage providers,
+    // it would be "*/*".
+    intent.setType("image/*");
+
+    startActivityForResult(intent, READ_REQUEST_CODE);
+}
+ +

請注意下列事項:

+
    +
  • 當應用程式觸發 {@link android.content.Intent#ACTION_OPEN_DOCUMENT} 意圖時,挑選器便會啟動並顯示所有相符的文件供應程式。 +
  • + +
  • 將 {@link android.content.Intent#CATEGORY_OPENABLE} 這個類別加入意圖中可篩選搜尋結果,限定系統只顯示可開啟的文件 (例如圖片檔)。 +
  • + +
  • 使用 {@code intent.setType("image/*")} 陳述式可進一步篩選搜尋結果,顯示系統只顯示內含 MIME 類型圖片的文件。 +
  • +
+ +

處理結果

+ +

使用者在挑選器中選取某份文件後,便會呼叫 +{@link android.app.Activity#onActivityResult onActivityResult()}。指向所選文件的 URI 包含在 {@code resultData} 參數中。 + +請使用 {@link android.content.Intent#getData getData()} 擷取 URI,然後使用該 URI 擷取使用者所需的文件。 +例如: +

+ +
@Override
+public void onActivityResult(int requestCode, int resultCode,
+        Intent resultData) {
+
+    // The ACTION_OPEN_DOCUMENT intent was sent with the request code
+    // READ_REQUEST_CODE. If the request code seen here doesn't match, it's the
+    // response to some other intent, and the code below shouldn't run at all.
+
+    if (requestCode == READ_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
+        // The document selected by the user won't be returned in the intent.
+        // Instead, a URI to that document will be contained in the return intent
+        // provided to this method as a parameter.
+        // Pull that URI using resultData.getData().
+        Uri uri = null;
+        if (resultData != null) {
+            uri = resultData.getData();
+            Log.i(TAG, "Uri: " + uri.toString());
+            showImage(uri);
+        }
+    }
+}
+
+ +

檢查文件中繼資料

+ +

取得文件的 URI 後,您就可以存取該文件的中繼資料。以下程式碼片段會擷取 URI 所指定文件的中繼資料並且加以記錄: +

+ +
public void dumpImageMetaData(Uri uri) {
+
+    // The query, since it only applies to a single document, will only return
+    // one row. There's no need to filter, sort, or select fields, since we want
+    // all fields for one document.
+    Cursor cursor = getActivity().getContentResolver()
+            .query(uri, null, null, null, null, null);
+
+    try {
+    // moveToFirst() returns false if the cursor has 0 rows.  Very handy for
+    // "if there's anything to look at, look at it" conditionals.
+        if (cursor != null && cursor.moveToFirst()) {
+
+            // Note it's called "Display Name".  This is
+            // provider-specific, and might not necessarily be the file name.
+            String displayName = cursor.getString(
+                    cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME));
+            Log.i(TAG, "Display Name: " + displayName);
+
+            int sizeIndex = cursor.getColumnIndex(OpenableColumns.SIZE);
+            // If the size is unknown, the value stored is null.  But since an
+            // int can't be null in Java, the behavior is implementation-specific,
+            // which is just a fancy term for "unpredictable".  So as
+            // a rule, check if it's null before assigning to an int.  This will
+            // happen often:  The storage API allows for remote files, whose
+            // size might not be locally known.
+            String size = null;
+            if (!cursor.isNull(sizeIndex)) {
+                // Technically the column stores an int, but cursor.getString()
+                // will do the conversion automatically.
+                size = cursor.getString(sizeIndex);
+            } else {
+                size = "Unknown";
+            }
+            Log.i(TAG, "Size: " + size);
+        }
+    } finally {
+        cursor.close();
+    }
+}
+
+ +

開啟文件

+ +

取得文件的 URI 後,您就可以開啟該文件或是對該文件執行任何所需操作。 +

+ +

點陣圖

+ +

以下範例可開啟 {@link android.graphics.Bitmap}:

+ +
private Bitmap getBitmapFromUri(Uri uri) throws IOException {
+    ParcelFileDescriptor parcelFileDescriptor =
+            getContentResolver().openFileDescriptor(uri, "r");
+    FileDescriptor fileDescriptor = parcelFileDescriptor.getFileDescriptor();
+    Bitmap image = BitmapFactory.decodeFileDescriptor(fileDescriptor);
+    parcelFileDescriptor.close();
+    return image;
+}
+
+ +

請注意,請不要針對 UI 執行緒進行這項作業,請在背景中使用 {@link android.os.AsyncTask} 進行。 +開啟點陣圖後,您就可以在 {@link android.widget.ImageView} 中顯示該點陣圖。 + +

+ +

取得 InputStream

+ +

以下範例可從 URI 中取得 {@link java.io.InputStream}。在這個程式碼片段中,系統會將每行檔案解讀為單一字串: +

+ +
private String readTextFromUri(Uri uri) throws IOException {
+    InputStream inputStream = getContentResolver().openInputStream(uri);
+    BufferedReader reader = new BufferedReader(new InputStreamReader(
+            inputStream));
+    StringBuilder stringBuilder = new StringBuilder();
+    String line;
+    while ((line = reader.readLine()) != null) {
+        stringBuilder.append(line);
+    }
+    fileInputStream.close();
+    parcelFileDescriptor.close();
+    return stringBuilder.toString();
+}
+
+ +

建立新文件

+ +

應用程式可在文件供應程式中使用 +{@link android.content.Intent#ACTION_CREATE_DOCUMENT} 意圖建立新文件。 +如要建立新檔案,請將 MIME 類型和檔案名稱提供給意圖,然後使用專屬的要求程式碼執行該意圖。 +系統會為您完成其餘的作業:

+ + +
+// Here are some examples of how you might call this method.
+// The first parameter is the MIME type, and the second parameter is the name
+// of the file you are creating:
+//
+// createFile("text/plain", "foobar.txt");
+// createFile("image/png", "mypicture.png");
+
+// Unique request code.
+private static final int WRITE_REQUEST_CODE = 43;
+...
+private void createFile(String mimeType, String fileName) {
+    Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
+
+    // Filter to only show results that can be "opened", such as
+    // a file (as opposed to a list of contacts or timezones).
+    intent.addCategory(Intent.CATEGORY_OPENABLE);
+
+    // Create a file with the requested MIME type.
+    intent.setType(mimeType);
+    intent.putExtra(Intent.EXTRA_TITLE, fileName);
+    startActivityForResult(intent, WRITE_REQUEST_CODE);
+}
+
+ +

建立新文件後,您可在 +{@link android.app.Activity#onActivityResult onActivityResult()} 中取得該文件的 URI, +以便繼續在其中編寫程式碼。

+ +

刪除文件

+ +

如果您已取得文件的 URI,而且文件的 +{@link android.provider.DocumentsContract.Document#COLUMN_FLAGS Document.COLUMN_FLAGS} 含有 +{@link android.provider.DocumentsContract.Document#FLAG_SUPPORTS_DELETE SUPPORTS_DELETE},您就可以刪除該文件。 + +例如:

+ +
+DocumentsContract.deleteDocument(getContentResolver(), uri);
+
+ +

編輯文件

+ +

您可以使用 SAF 即時編輯文字文件。以下程式碼片段會觸發 {@link android.content.Intent#ACTION_OPEN_DOCUMENT} 意圖並使用 {@link android.content.Intent#CATEGORY_OPENABLE} 類別限制系統只顯示可開啟的文件。 + + + +此外,這個程式碼還會進一步篩選搜尋結果,讓系統只顯示文字檔:

+ +
+private static final int EDIT_REQUEST_CODE = 44;
+/**
+ * Open a file for writing and append some text to it.
+ */
+ private void editDocument() {
+    // ACTION_OPEN_DOCUMENT is the intent to choose a file via the system's
+    // file browser.
+    Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
+
+    // Filter to only show results that can be "opened", such as a
+    // file (as opposed to a list of contacts or timezones).
+    intent.addCategory(Intent.CATEGORY_OPENABLE);
+
+    // Filter to show only text files.
+    intent.setType("text/plain");
+
+    startActivityForResult(intent, EDIT_REQUEST_CODE);
+}
+
+ +

接著,您可以利用 {@link android.app.Activity#onActivityResult onActivityResult()} (詳情請參閱處理結果) 呼叫程式碼執行編輯動作。以下程式碼片段會利用 {@link android.content.ContentResolver} 取得 {@link java.io.FileOutputStream}。 + + +在預設情況下,這個程式碼片段會使用「寫入」模式。這種方法可索取最少量的所需存取權,因此如果您只需要寫入存取權,請勿要求讀取/寫入: + +

+ +
private void alterDocument(Uri uri) {
+    try {
+        ParcelFileDescriptor pfd = getActivity().getContentResolver().
+                openFileDescriptor(uri, "w");
+        FileOutputStream fileOutputStream =
+                new FileOutputStream(pfd.getFileDescriptor());
+        fileOutputStream.write(("Overwritten by MyCloud at " +
+                System.currentTimeMillis() + "\n").getBytes());
+        // Let the document provider know you're done by closing the stream.
+        fileOutputStream.close();
+        pfd.close();
+    } catch (FileNotFoundException e) {
+        e.printStackTrace();
+    } catch (IOException e) {
+        e.printStackTrace();
+    }
+}
+ +

保留權限

+ +

應用程式開啟要讀取或寫入的檔案後,系統會將該檔案的 URI 權限授予您的應用程式。 +除非使用者重新啟動裝置,否則這項權限會持續保持有效狀態。不過,假如您的應用程式為圖片編輯應用程式,而您希望使用者可直接透過您的應用程式存取他們最近編輯的 5 張圖片。如果使用者重新啟動的裝置,就您必須將使用者傳回系統挑選器來搜尋所需檔案,而這並非最佳做法。 + + + +

+ +

為了避免這種情況發生,您可以保留系統授予您應用程式的權限。實際上,您的應用程式會「取得」系統授予的永久性 URI 權限。 + +這種權限可讓使用者持續透過您的應用程式存取檔案,即使其裝置重新啟動也無妨: +

+ + +
final int takeFlags = intent.getFlags()
+            & (Intent.FLAG_GRANT_READ_URI_PERMISSION
+            | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+// Check for the freshest data.
+getContentResolver().takePersistableUriPermission(uri, takeFlags);
+ +

除了上述指示外,您還需要完成最後一個步驟。您儲存了您的應用程式最近存取的 URI,但這些 URI 有可能已失效 — 原因在於其他應用程式刪除或修改了文件。 + +因此,建議您一律呼叫 +{@code getContentResolver().takePersistableUriPermission()} 檢查最新資料。 +

+ +

編寫自訂文件供應程式

+ +

+如果您想開發可提供檔案儲存服務 (例如雲端儲存服務) 的應用程式,可以編寫自訂文件供應程式透過 SAF 提供您的檔案。 + +本節說明如何編寫這類程式。 +

+ + +

宣示說明

+ +

如要實作自訂文件供應程式,請將以下項目加入應用程式的宣示說明: +

+
    + +
  • 19 以上的 API 級別目標。
  • + +
  • 宣告自訂儲存空間供應程式的 +<provider> 元素。
  • + +
  • 供應程式的名稱 (也就是供應程式的類別名稱),包括套件名稱。範例:com.example.android.storageprovider.MyCloudProvider。 +
  • + +
  • 授權的名稱 (也就是套件的名稱;在此範例中為 +com.example.android.storageprovider) 以及內容供應程式的類型 +(documents)。範例:{@code com.example.android.storageprovider.documents}。
  • + +
  • 設為 "true"android:exported 屬性。您必須將供應程式匯出,方便其他應用程式加以偵測。 +
  • + +
  • 設為 "true" 的 +android:grantUriPermissions 屬性。這項設定可讓系統將供應程式內容的存取權授予其他應用程式。 +如果想瞭解如何保留特定文件的權限,請參閱保留權限。 +
  • + +
  • {@code MANAGE_DOCUMENTS} 權限。在預設情況下,所有人都可使用供應程式。 +加入這項權限可針對系統設定供應程式限制,藉此提高其安全性。 +
  • + +
  • 設定資源檔案所定義布林值的 {@code android:enabled} 屬性。 +這項屬性可用於針對搭載 Android 4.3 以下版本的裝置停用供應程式。範例:{@code android:enabled="@bool/atLeastKitKat"}。 +除了在宣示說明中加入這項屬性以外,您還必須執行下列操作: + +
      +
    • 在位於 {@code res/values/} 的 {@code bool.xml} 資源檔案中,新增以下程式碼: +
      <bool name="atLeastKitKat">false</bool>
    • + +
    • 在位於 {@code res/values-v19/} 的 {@code bool.xml} 資源檔案中,新增以下程式碼: +
      <bool name="atLeastKitKat">true</bool>
    • +
  • + +
  • 內含 +{@code android.content.action.DOCUMENTS_PROVIDER} 動作的意圖篩選器,讓您的供應程式能夠在系統搜尋供應程式時顯示在挑選器中。 +
  • + +
+

以下是內含供應程式的範例宣示說明例外狀況:

+ +
<manifest... >
+    ...
+    <uses-sdk
+        android:minSdkVersion="19"
+        android:targetSdkVersion="19" />
+        ....
+        <provider
+            android:name="com.example.android.storageprovider.MyCloudProvider"
+            android:authorities="com.example.android.storageprovider.documents"
+            android:grantUriPermissions="true"
+            android:exported="true"
+            android:permission="android.permission.MANAGE_DOCUMENTS"
+            android:enabled="@bool/atLeastKitKat">
+            <intent-filter>
+                <action android:name="android.content.action.DOCUMENTS_PROVIDER" />
+            </intent-filter>
+        </provider>
+    </application>
+
+</manifest>
+ +

支援搭載 Android 4.3 以下版本的裝置

+ +

只有搭載 Android 4.4 以上版本的裝置可使用 +{@link android.content.Intent#ACTION_OPEN_DOCUMENT} 意圖。如果您想讓應用程式支援 {@link android.content.Intent#ACTION_GET_CONTENT} 以便與搭載 Android 4.3 以下版本的裝置相容,請針對搭載 Android 4.4 以上版本的裝置停用宣示說明中的 {@link android.content.Intent#ACTION_GET_CONTENT} 意圖篩選器。 + + + + +文件供應器和 {@link android.content.Intent#ACTION_GET_CONTENT} 是完全不同的項目。 + +如果您同時支援這兩個項目,您的應用程式就會重複出現在系統挑選器 UI 中,讓使用者可透過兩種不同方式存取您儲存的資料, + +而這樣會造成混淆。

+ +

以下提供針對搭載 Android 4.4 以上版本的裝置停用 +{@link android.content.Intent#ACTION_GET_CONTENT} 意圖篩選器的建議做法: +

+ +
    +
  1. 在位於 {@code res/values/} 的 {@code bool.xml} 資源檔案中,新增以下程式碼: +
    <bool name="atMostJellyBeanMR2">true</bool>
  2. + +
  3. 在位於 {@code res/values-v19/} 的 {@code bool.xml} 資源檔案中,新增以下程式碼: +
    <bool name="atMostJellyBeanMR2">false</bool>
  4. + +
  5. 新增 +Activity 別名來針對搭載 Android 4.4 (API 級別 19) 以上版本的裝置停用 {@link android.content.Intent#ACTION_GET_CONTENT} 意圖篩選器。 + +例如: + +
    +<!-- This activity alias is added so that GET_CONTENT intent-filter
    +     can be disabled for builds on API level 19 and higher. -->
    +<activity-alias android:name="com.android.example.app.MyPicker"
    +        android:targetActivity="com.android.example.app.MyActivity"
    +        ...
    +        android:enabled="@bool/atMostJellyBeanMR2">
    +    <intent-filter>
    +        <action android:name="android.intent.action.GET_CONTENT" />
    +        <category android:name="android.intent.category.OPENABLE" />
    +        <category android:name="android.intent.category.DEFAULT" />
    +        <data android:mimeType="image/*" />
    +        <data android:mimeType="video/*" />
    +    </intent-filter>
    +</activity-alias>
    +
    +
  6. +
+

合約

+ +

一般來說,當您編寫自訂內容供應程式時,需要完成的其中一項工作為實作合約類別 (詳情請參閱內容供應程式開發人員指南)。 + + +合約類別是 {@code public final} 類別,內含以下項目的固定不變定義:URI、欄名稱、MIME 類型以及供應程式擁有的其他中繼資料。 + +SAF 可為您提供以下合約類別,因此您不必自行編 寫合約: + +

+ +
    +
  • {@link android.provider.DocumentsContract.Document}
  • +
  • {@link android.provider.DocumentsContract.Root}
  • +
+ +

例如,以下是在文件供應程式查詢文件或根目錄時可能會傳回的資料欄: +

+ +
private static final String[] DEFAULT_ROOT_PROJECTION =
+        new String[]{Root.COLUMN_ROOT_ID, Root.COLUMN_MIME_TYPES,
+        Root.COLUMN_FLAGS, Root.COLUMN_ICON, Root.COLUMN_TITLE,
+        Root.COLUMN_SUMMARY, Root.COLUMN_DOCUMENT_ID,
+        Root.COLUMN_AVAILABLE_BYTES,};
+private static final String[] DEFAULT_DOCUMENT_PROJECTION = new
+        String[]{Document.COLUMN_DOCUMENT_ID, Document.COLUMN_MIME_TYPE,
+        Document.COLUMN_DISPLAY_NAME, Document.COLUMN_LAST_MODIFIED,
+        Document.COLUMN_FLAGS, Document.COLUMN_SIZE,};
+
+ +

將 DocumentsProvider 設為子類別

+ +

編寫自動文件供應程式的下一個步驟是,將抽象類別 {@link android.provider.DocumentsProvider} 設為子類別。 +您至少必須實作下列方法: +

+ +
    +
  • {@link android.provider.DocumentsProvider#queryRoots queryRoots()}
  • + +
  • {@link android.provider.DocumentsProvider#queryChildDocuments queryChildDocuments()}
  • + +
  • {@link android.provider.DocumentsProvider#queryDocument queryDocument()}
  • + +
  • {@link android.provider.DocumentsProvider#openDocument openDocument()}
  • +
+ +

以上是您必須實作的方法,不過您可能會視需要實作其他方法。 +詳情請參閱 {@link android.provider.DocumentsProvider}。 +

+ +

實作 queryRoots

+ +

實作 {@link android.provider.DocumentsProvider#queryRoots +queryRoots()} 後系統會使用 {@link android.provider.DocumentsContract.Root} 中定義的資料欄,傳回指向文件供應程式所有根目錄的 {@link android.database.Cursor}。 + +

+ +

在以下程式碼片段中,{@code projection} 參數代表呼叫者想返回的特定欄位。 +這個程式碼片隊會建立新游標並在其中加入一列 — 也就是根目錄或頂層目錄 (例如「下載」或「圖片」)。 + +大多數供應程式只有一個根目錄。而您可以有多個根目錄,例如擁有多個使用者帳戶的情況下。 +在這種情況下,只要在游標中加入第二列即可。 +

+ +
+@Override
+public Cursor queryRoots(String[] projection) throws FileNotFoundException {
+
+    // Create a cursor with either the requested fields, or the default
+    // projection if "projection" is null.
+    final MatrixCursor result =
+            new MatrixCursor(resolveRootProjection(projection));
+
+    // If user is not logged in, return an empty root cursor.  This removes our
+    // provider from the list entirely.
+    if (!isUserLoggedIn()) {
+        return result;
+    }
+
+    // It's possible to have multiple roots (e.g. for multiple accounts in the
+    // same app) -- just add multiple cursor rows.
+    // Construct one row for a root called "MyCloud".
+    final MatrixCursor.RowBuilder row = result.newRow();
+    row.add(Root.COLUMN_ROOT_ID, ROOT);
+    row.add(Root.COLUMN_SUMMARY, getContext().getString(R.string.root_summary));
+
+    // FLAG_SUPPORTS_CREATE means at least one directory under the root supports
+    // creating documents. FLAG_SUPPORTS_RECENTS means your application's most
+    // recently used documents will show up in the "Recents" category.
+    // FLAG_SUPPORTS_SEARCH allows users to search all documents the application
+    // shares.
+    row.add(Root.COLUMN_FLAGS, Root.FLAG_SUPPORTS_CREATE |
+            Root.FLAG_SUPPORTS_RECENTS |
+            Root.FLAG_SUPPORTS_SEARCH);
+
+    // COLUMN_TITLE is the root title (e.g. Gallery, Drive).
+    row.add(Root.COLUMN_TITLE, getContext().getString(R.string.title));
+
+    // This document id cannot change once it's shared.
+    row.add(Root.COLUMN_DOCUMENT_ID, getDocIdForFile(mBaseDir));
+
+    // The child MIME types are used to filter the roots and only present to the
+    //  user roots that contain the desired type somewhere in their file hierarchy.
+    row.add(Root.COLUMN_MIME_TYPES, getChildMimeTypes(mBaseDir));
+    row.add(Root.COLUMN_AVAILABLE_BYTES, mBaseDir.getFreeSpace());
+    row.add(Root.COLUMN_ICON, R.drawable.ic_launcher);
+
+    return result;
+}
+ +

實作 queryChildDocuments

+ +

實作 +{@link android.provider.DocumentsProvider#queryChildDocuments queryChildDocuments()} 後系統會使用 +{@link android.provider.DocumentsContract.Document} 中定義的資料欄,傳回指向特定目錄中所有檔案的 {@link android.database.Cursor}。 + +

+ +

當您在挑選器 UI 中選擇應用程式的根目錄後,就會呼叫這個方法,藉此取得根目錄內某個目錄中的下層文件。 +您可以在檔案階層的任何層級中呼叫這個方法,而不單單只能在根目錄中呼叫。 +以下程式碼片段會使用要求的資料欄建立新游標,然後加入該游標中上層目錄的任何下層物件相關資訊。下層物件可以是圖片、其他目錄等任何檔案: + + +

+ +
@Override
+public Cursor queryChildDocuments(String parentDocumentId, String[] projection,
+                              String sortOrder) throws FileNotFoundException {
+
+    final MatrixCursor result = new
+            MatrixCursor(resolveDocumentProjection(projection));
+    final File parent = getFileForDocId(parentDocumentId);
+    for (File file : parent.listFiles()) {
+        // Adds the file's display name, MIME type, size, and so on.
+        includeFile(result, null, file);
+    }
+    return result;
+}
+
+ +

實作 queryDocument

+ +

實作 +{@link android.provider.DocumentsProvider#queryDocument queryDocument()} 後系統會使用 {@link android.provider.DocumentsContract.Document} 中定義的資料欄,傳回指向特定檔案的 {@link android.database.Cursor}。 + + +

+ +

{@link android.provider.DocumentsProvider#queryDocument queryDocument()}方法會針對特定檔案傳回 +{@link android.provider.DocumentsProvider#queryChildDocuments queryChildDocuments()} 所傳送的相同資訊: + +

+ + +
@Override
+public Cursor queryDocument(String documentId, String[] projection) throws
+        FileNotFoundException {
+
+    // Create a cursor with the requested projection, or the default projection.
+    final MatrixCursor result = new
+            MatrixCursor(resolveDocumentProjection(projection));
+    includeFile(result, documentId, null);
+    return result;
+}
+
+ +

實作 openDocument

+ +

您必須實作 {@link android.provider.DocumentsProvider#openDocument +openDocument()} 來傳回代表特定檔案的 +{@link android.os.ParcelFileDescriptor}。其他應用程式可利用傳回的 {@link android.os.ParcelFileDescriptor} 傳輸資料。 +使用者選取檔案而且用戶端應用程式呼叫 +{@link android.content.ContentResolver#openFileDescriptor openFileDescriptor()} 要求存取該檔案後,系統就會呼叫這個方法。範例: + +

+ +
@Override
+public ParcelFileDescriptor openDocument(final String documentId,
+                                         final String mode,
+                                         CancellationSignal signal) throws
+        FileNotFoundException {
+    Log.v(TAG, "openDocument, mode: " + mode);
+    // It's OK to do network operations in this method to download the document,
+    // as long as you periodically check the CancellationSignal. If you have an
+    // extremely large file to transfer from the network, a better solution may
+    // be pipes or sockets (see ParcelFileDescriptor for helper methods).
+
+    final File file = getFileForDocId(documentId);
+
+    final boolean isWrite = (mode.indexOf('w') != -1);
+    if(isWrite) {
+        // Attach a close listener if the document is opened in write mode.
+        try {
+            Handler handler = new Handler(getContext().getMainLooper());
+            return ParcelFileDescriptor.open(file, accessMode, handler,
+                        new ParcelFileDescriptor.OnCloseListener() {
+                @Override
+                public void onClose(IOException e) {
+
+                    // Update the file with the cloud server. The client is done
+                    // writing.
+                    Log.i(TAG, "A file with id " +
+                    documentId + " has been closed!
+                    Time to " +
+                    "update the server.");
+                }
+
+            });
+        } catch (IOException e) {
+            throw new FileNotFoundException("Failed to open document with id "
+            + documentId + " and mode " + mode);
+        }
+    } else {
+        return ParcelFileDescriptor.open(file, accessMode);
+    }
+}
+
+ +

安全性

+ +

假如您的文件供應程式為受密碼保護的雲端儲存服務,而您先想確認使用者都已登入,然後再開始分享其檔案。那麼在使用者未登入的情況下,您的應用程式應採取什麼行動? + +解決方案是不要讓文件供應程式在您實作 {@link android.provider.DocumentsProvider#queryRoots +queryRoots()} 後傳回任何根目錄。 +換句話說,就是讓供應程式傳回空的根目錄游標:

+ +
+public Cursor queryRoots(String[] projection) throws FileNotFoundException {
+...
+    // If user is not logged in, return an empty root cursor.  This removes our
+    // provider from the list entirely.
+    if (!isUserLoggedIn()) {
+        return result;
+}
+
+ +

另一個需採取的步驟是呼叫 {@code getContentResolver().notifyChange()}。還記得 {@link android.provider.DocumentsContract} 嗎? +我們會使用該類別建立 URI。以下程式碼片段會指示系統在使用者的登入狀態變更時,查詢文件供應程式的根目錄。 + +如果使用者未登入,呼叫 {@link android.provider.DocumentsProvider#queryRoots queryRoots()} 就會如上所述傳回空的游標。 + +這樣可確保只有登入供應程式的使用者可存取其中的文件。 +

+ +
private void onLoginButtonClick() {
+    loginOrLogout();
+    getContentResolver().notifyChange(DocumentsContract
+            .buildRootsUri(AUTHORITY), null);
+}
+
\ No newline at end of file diff --git a/docs/html-intl/intl/zh-tw/guide/topics/resources/accessing-resources.jd b/docs/html-intl/intl/zh-tw/guide/topics/resources/accessing-resources.jd new file mode 100644 index 0000000000000000000000000000000000000000..3a5a96121d942f5fc6f803327b4ec0bd22de8c0a --- /dev/null +++ b/docs/html-intl/intl/zh-tw/guide/topics/resources/accessing-resources.jd @@ -0,0 +1,337 @@ +page.title=存取資源 +parent.title=應用程式資源 +parent.link=index.html +@jd:body + +
+
+

快速檢視

+
    +
  • 使用 {@code R.java} 中的整數可以參照程式碼中的資源,例如 +{@code R.drawable.myimage}
  • +
  • 使用特殊 XML 語法可以參照資源中的資源,例如 {@code +@drawable/myimage}
  • +
  • 您也可以使用 +{@link android.content.res.Resources} 中的方法存取應用程式資源
  • +
+ +

重要類別

+
    +
  1. {@link android.content.res.Resources}
  2. +
+ +

本文件內容

+
    +
  1. 存取程式碼中的資源
  2. +
  3. 存取 XML 中的資源 +
      +
    1. 參照樣式屬性
    2. +
    +
  4. +
  5. 存取平台資源
  6. +
+ +

另請參閱

+
    +
  1. 提供資源
  2. +
  3. 資源類型
  4. +
+
+
+ + + + +

提供應用程式中的資源 (於提供資源中討論) 後,您可以參照其資源 ID 加以應用。所有資源 ID 會在專案的 {@code R} 類別中定義。此類別是由 {@code aapt} 工具自動產生的。 + +

+ +

編譯應用程式後,{@code aapt} 會產生 {@code R} 類別。此類別內含 {@code +res/} 目錄中所有資源的資源 ID。 +每一種資源類型都有 {@code R} 子類別 (例如,所有可繪項目資源的 +{@code R.drawable}),而這種類型的每一種資源都有靜態整數 (例如,{@code R.drawable.icon})。 +此整數就是資源 ID,您可以用於擷取資源。 +

+ +

儘管資源 ID 都指定於 {@code R} 類別,您不需要為了取得資源 ID 而加以查看。資源 ID 的組成如下: +

+
    +
  • 資源類型:每一種資源會以「類型」分類,例如 {@code +string}、{@code drawable} 以及 {@code layout}。如需有關不同類型的詳細資訊,請參閱資源類型。 +
  • +
  • 資源名稱可能是:檔案名稱,不含副檔名;或 XML {@code android:name} 屬性中的值,如果資源是簡單值 (例如字串)。 + +
  • +
+ +

您有兩種方式可存取資源:

+
    +
  • 在程式碼中:使用 {@code R} 類別中子類別的靜態整數,例如: + +
    R.string.hello
    +

    {@code string} 是資源類型,而 {@code hello} 是資源名稱。若您以此格式提供資源 ID 時,很多 Android API 都可以存取您的資源。 +請參閱在程式碼中存取資源。 +

    +
  • +
  • 在 XML 中:使用特殊 XML 語法,也可以對應到您在 {@code R} 類別中定義的資源 ID,例如: + +
    @string/hello
    +

    {@code string} 是資源類型,而 {@code hello} 是資源名稱。您可以在 XML 資源中的任何位置使用此語法,只要符合您在資源中所提供的預期值即可。 +請參閱存取 XML 中的資源

    +
  • +
+ + + +

在程式碼中存取資源

+ +

您可以將資源 ID 作為方法參數傳遞,在程式碼中使用資源。例如,您可以利用 {@link android.widget.ImageView#setImageResource(int) setImageResource()} 將 {@link android.widget.ImageView} 設定為使用 {@code res/drawable/myimage.png} 資源: + +

+
+ImageView imageView = (ImageView) findViewById(R.id.myimageview);
+imageView.setImageResource(R.drawable.myimage);
+
+ +

您也可以使用 {@link +android.content.res.Resources} (以 {@link android.content.Context#getResources()} 可取得執行個體) 中的方法擷取個別的資源。 +

+ + + + +

語法

+ +

在程式碼中參照資源的語法如下:

+ +
+[<package_name>.]R.<resource_type>.<resource_name>
+
+ +
    +
  • {@code <package_name>} 資源所在的封裝名稱 (從您自己的封裝中參照資源時,不需要此名稱)。 +
  • +
  • {@code <resource_type>} 是資源類型的 {@code R} 子類別。
  • +
  • {@code <resource_name>} 可以是不含副檔名的資源檔案名稱,或是 XML 元素中的 {@code android:name} 屬性值 (簡單值)。 + +
  • +
+

請參閱資源類型,以取得關於每個資源類型及其參照方式的詳細資訊。 +

+ + +

使用案例

+ +

很多方法都接受資源 ID 參數,您可以使用 +{@link android.content.res.Resources} 中的方法擷取資源。您可以透過以下方式取得 {@link +android.content.res.Resources} 的執行個體:{@link android.content.Context#getResources +Context.getResources()}。

+ + +

以下是在程式碼中存取資源的範例:

+ +
+// Load a background for the current screen from a drawable resource
+{@link android.app.Activity#getWindow()}.{@link
+android.view.Window#setBackgroundDrawableResource(int)
+setBackgroundDrawableResource}(R.drawable.my_background_image) ;
+
+// Set the Activity title by getting a string from the Resources object, because
+//  this method requires a CharSequence rather than a resource ID
+{@link android.app.Activity#getWindow()}.{@link android.view.Window#setTitle(CharSequence)
+setTitle}(getResources().{@link android.content.res.Resources#getText(int)
+getText}(R.string.main_title));
+
+// Load a custom layout for the current screen
+{@link android.app.Activity#setContentView(int)
+setContentView}(R.layout.main_screen);
+
+// Set a slide in animation by getting an Animation from the Resources object
+mFlipper.{@link android.widget.ViewAnimator#setInAnimation(Animation)
+setInAnimation}(AnimationUtils.loadAnimation(this,
+        R.anim.hyperspace_in));
+
+// Set the text on a TextView object using a resource ID
+TextView msgTextView = (TextView) findViewById(R.id.msg);
+msgTextView.{@link android.widget.TextView#setText(int)
+setText}(R.string.hello_message);
+
+ + +

注意:您不應手動修改 {@code +R.java} 檔案 — 此檔案是在編譯專案時由 {@code aapt} 工具所產生。 +下次編譯時會覆寫所有變更內容。

+ + + +

存取 XML 中的資源

+ +

您可以使用現有資源的參照,為某些 XML 屬性和元素定義值。 +您在建立版面配置檔案時,會經常以此方式提供字串和影像讓小工具使用。 +

+ +

例如,如果您要將 {@link android.widget.Button} 加入版面配置,應該使用按鈕文字的字串資源: +

+ +
+<Button
+    android:layout_width="fill_parent"
+    android:layout_height="wrap_content"
+    android:text="@string/submit" />
+
+ + +

語法

+ +

在 XML 資源中參照資源的語法如下:

+ +
+@[<package_name>:]<resource_type>/<resource_name>
+
+ +
    +
  • {@code <package_name>} 資源所在的封裝名稱 (從同一個封裝中參照資源時,不需要此名稱) +
  • +
  • {@code <resource_type>} 是資源類型的 +{@code R} 子類別。
  • +
  • {@code <resource_name>} 可以是不含副檔名的資源檔案名稱,或是 XML 元素中的 {@code android:name} 屬性值 (簡單值)。 + +
  • +
+ +

請參閱資源類型,以取得關於每個資源類型及其參照方式的詳細資訊。 +

+ + +

使用案例

+ +

有時候您必須在 XML 中使用資源,而不能使用值 (例如,將可繪項目影像套用至小工具)。不過,您也可以在 XML 中接受簡單值的任何位置使用資源。 +例如,如果您有下列資源檔案,其中內含色彩資源字串資源: +

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+   <color name="opaque_red">#f00</color>
+   <string name="hello">Hello!</string>
+</resources>
+
+ +

您可以在下列版面配置檔案中使用這些資源,以設定文字色彩和文字字串: +

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<EditText xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    android:textColor="@color/opaque_red"
+    android:text="@string/hello" />
+
+ +

在此情況下,您不用在資源參照中指定封裝名稱,因為資源是來自您自己的封裝。 +如要參照系統資源,則需要使用封裝名稱。 +範例:

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<EditText xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    android:textColor="@android:color/secondary_text_dark"
+    android:text="@string/hello" />
+
+ +

注意:您一律都要使用字串資源,便於應用程式當地語系化。 +如需關於建立替代資源 (例如當地語系化的字串) 的詳細資訊,請參閱提供替代資源。 + + +如需將應用程式當地語系化的詳細指南,請參閱當地語系化。 +

+ +

您甚至可以在 XML 中使用資源以建立別名。例如:您可以用其他可繪項目資源的別名,建立可繪項目資源: +

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
+    android:src="@drawable/other_drawable" />
+
+ +

聽起來多此一舉,但是,當您使用替代資源時,就非常實用。深入瞭解建立別名資源。 +

+ + + +

參照樣式屬性

+ +

樣式屬性資源可以讓您在目前套用的設計風格中參照屬性的值。 +參照樣式屬性可以讓您透過目前設計風格提供的標準變化自訂 UI 元素外觀,而不需要使用硬式編碼值。 + +參照樣式屬性基本上就是「使用目前設計風格中此屬性所定義的樣式」。 +

+ +

如要參照樣式屬性,名稱語法幾乎和一般資源格式相同,只要把小老鼠符號 ({@code @}) 換成問號 ({@code ?}) 即可,而且您可以選擇是否要使用資源類型。 + +範例:

+ +
+?[<package_name>:][<resource_type>/]<resource_name>
+
+ +

例如,您可以參照屬性,讓文字色彩符合系統設計風格的「主要」文字色彩,方式如下: +

+ +
+<EditText id="text"
+    android:layout_width="fill_parent"
+    android:layout_height="wrap_content"
+    android:textColor="?android:textColorSecondary"
+    android:text="@string/hello_world" />
+
+ +

這裡的 {@code android:textColor} 屬性指出目前設計風格中樣式屬性的名稱。 +Android 現在會將套用至 {@code android:textColorSecondary} 樣式屬性的值,當成此小工具中 {@code android:textColor} 的值。 +因為系統資源工具知道在此環境中會有一個屬性資源,您不需要明確陳述其類型 ( +?android:attr/textColorSecondary)— 可以不需要寫上出 {@code attr} 類型。 + +

+ + + + +

存取平台資源

+ +

Android 內含多種標準資源,例如樣式、設計風格以及版面配置。如要存取這些資源,則必須在參照資源時使用 +android 封裝名稱。 +例如,Android 在 {@link android.widget.ListAdapter} 中提供版面配置資源,您可以用於列出項目: +

+ +
+{@link android.app.ListActivity#setListAdapter(ListAdapter)
+setListAdapter}(new {@link
+android.widget.ArrayAdapter}<String>(this, android.R.layout.simple_list_item_1, myarray));
+
+ +

在此範例中,{@link android.R.layout#simple_list_item_1} 是一個版面配置資源,由平台為 {@link android.widget.ListView} 中的項目所定義。 +您可以加以使用,而不用自行建立清單項目的版面配置。 +如需詳細資訊,請參閱清單檢視開發人員指南。 +

+ diff --git a/docs/html-intl/intl/zh-tw/guide/topics/resources/overview.jd b/docs/html-intl/intl/zh-tw/guide/topics/resources/overview.jd new file mode 100644 index 0000000000000000000000000000000000000000..68197d17233a1d2a8eb97483c38d84cb5fa385f9 --- /dev/null +++ b/docs/html-intl/intl/zh-tw/guide/topics/resources/overview.jd @@ -0,0 +1,103 @@ +page.title=資源總覽 +@jd:body + +
+
+

本文主題

+
    +
  1. 提供資源
  2. +
  3. 存取資源
  4. +
  5. 處理執行階段變更
  6. +
  7. 本地化
  8. +
+ +

參考資料

+
    +
  1. 資源類型
  2. +
+
+
+ + +

建議您一律將應用程式程式碼當中像是圖片和字串的資源具體化,以便個別加以維護。 +將資源具體化也可讓您提供支援特定裝置設定的替代資源,例如不同語言或螢幕大小,隨著提供不同設定的 Android 裝置日漸增加,這點也變得日益重要。 + + +為了提供不同設定的相容性,您必須整理專案的 {@code res/} 目錄中的資源,使用各種子目錄按類型與設定將資源分門別類。 + + +

+ +
+ +

+圖 1.兩個不同的裝置,個別使用預設的版面配置 (應用程式未提供替代的版面配置)。 +

+
+ +
+ +

+圖 2.兩個不同的裝置,個別使用不同螢幕大小所提供的不同版面配置。 +

+
+ +

針對任何資源類型,您都能為應用程式指定「預設」與多項「替代」資源。 +

+
    +
  • 不論裝置設定為何,或在沒有符合目前設定的替代資源時,您都應該使用預設資源。 + +
  • +
  • 您設計用來與特定設定搭配使用的就是替代資源。 +如要為某個設定指定一組資源,請將適當的設定限定詞附加到目錄名稱。 +
  • +
+ +

例如,假設您的預設 UI 版面配置儲存在 {@code res/layout/} 目錄,您可能要指定一個不同的版面配置並儲存在 +{@code res/layout-land/} 目錄中,以在螢幕處於橫向時使用。 + +Android 會在比對裝置目前的設定與您的資源目錄名稱後,自動套用適當的資源。 +

+ +

圖 1 說明系統如何在沒有替代資源可用時,對兩個不同裝置套用相同的版面配置。 +圖 2 說明相同的應用程式為較大的螢幕新增替代版面配置資源的情況。 +

+ +

下文提供完整的說明,指引您如何整理應用程式資源、指定替代資源、在您的應用程式中存取這些資源等等: +

+ +
+
提供資源
+
您可在應用程式中加入哪些資源類型,儲存在哪裡以及如何針對不同的裝置設定建立替代資源。 +
+
存取資源
+
如何使用您提供的資源 (在應用程式的程式碼中或在其他 XML 資源中參考)。 +
+
處理執行階段變更
+
如何管理在 Activity 執行期間發生的設定變更。
+
本地化
+
從細節到整體的說明,指引您使用替代資源將應用程式本地化。雖然這只是替代資源的一種特殊用途,但為了觸及更多使用者,這樣做非常重要。 + +
+
資源類型
+
您能提供的各種資源類型參考資料,描述其 XML 元件、屬性及語法。 +例如,此參考資料說明如何建立應用程式選單、可繪項目、動畫等資源。 +
+
+ + diff --git a/docs/html-intl/intl/zh-tw/guide/topics/resources/providing-resources.jd b/docs/html-intl/intl/zh-tw/guide/topics/resources/providing-resources.jd new file mode 100644 index 0000000000000000000000000000000000000000..0938dc00e12db6aeb601ae646650da8408921ccc --- /dev/null +++ b/docs/html-intl/intl/zh-tw/guide/topics/resources/providing-resources.jd @@ -0,0 +1,1094 @@ +page.title=提供資源 +parent.title=應用程式資源 +parent.link=index.html +@jd:body + +
+
+

快速檢視

+
    +
  • 不同類型的資源屬於 {@code res/} 中不同的子目錄
  • +
  • 替代資源提供特定設定資源檔案
  • +
  • 始終要包含預設資源,如此應用程式才不會依賴特定裝置設定 +
  • +
+

本文件內容

+
    +
  1. 分組資源類型
  2. +
  3. 提供替代資源 +
      +
    1. 限定詞名稱規則
    2. +
    3. 建立別名資源
    4. +
    +
  4. +
  5. 使用資源提供最佳的裝置相容性
  6. +
  7. Android 如何尋找最相符的資源
  8. +
+ +

另請參閱

+
    +
  1. 存取資源
  2. +
  3. 資源類型
  4. +
  5. 支援多個螢幕 +
  6. +
+
+
+ +

您應該一律將應用程式資源具體化,例如影像和程式碼字串,以便單獨維護它們。 +同時,您還應該將替代資源分組到特殊命名的資源目錄中,為特定裝置設定提供替代資源。 +Android 會在執行階段根據目前的設定使用適當的資源。 +例如,您可能會想根據螢幕大小提供不同的 UI 版面配置,或者根據語言設定提供不同的字串。 + +

+ +

具體化您的應用程式資源後,可以使用在專案 {@code R} 類別中產生的資源 ID 來存取它們。 +在應用程式使用資源的方法在存取資源中有相關討論。 + +本文件說明如何在 Android 專案分組您的資源,並為特定裝置設定提供替代資源。 +

+ + +

分組資源類型

+ +

您應該將每個資源類型放在專案 +{@code res/} 目錄的特定子目錄中。例如,下列為簡單專案的檔案階層:

+ +
+MyProject/
+    src/  
+        MyActivity.java  
+    res/
+        drawable/  
+            graphic.png  
+        layout/  
+            main.xml
+            info.xml
+        mipmap/  
+            icon.png 
+        values/  
+            strings.xml  
+
+ +

如您在此範例所見,{@code res/} 目錄包含所有資源 (在子目錄中):一個影像資源、兩個版面配置資源、啟動器圖示的 {@code mipmap/} 目錄,以及字串資源檔案。 + +資源目錄名稱非常重要,在表 1 有相關說明。 +

+ +

注意:如需有關使用 mipmap 資料夾的詳細資訊,請參閱管理專案總覽。 +

+ +

表 1.專案 {@code res/} 目錄內部支援資源目錄。 +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
目錄資源類型
animator/定義屬性動畫的 XML 檔案。 +
anim/定義 tween 動畫的 XML 檔案。 +(屬性動畫也能儲存在這個目錄中,但建議將屬性動畫放在{@code animator/} 目錄中,以區分這兩個類型。) + +
color/定義色彩狀態清單的 XML 檔案。請參閱色彩狀態清單資源 +
drawable/

編譯成下列可繪項目資源子類型的點陣圖檔案 ({@code .png}、{@code .9.png}、{@code .jpg}、{@code .gif}) 或 XML 檔案: +

+
    +
  • 點陣圖檔案
  • +
  • 九宮格影像 (可重新調整大小的點陣圖)
  • +
  • 狀態清單
  • +
  • 形狀
  • +
  • 動畫可繪項目
  • +
  • 其他可繪項目
  • +
+

請參閱可繪項目資源

+
mipmap/適用於不同啟動器圖示密度的可繪項目檔案。如需有關使用 {@code mipmap/} 資料夾管理啟動器圖示的詳細資訊,請參閱管理專案總覽。 + +
layout/定義使用者介面版面配置的 XML 檔案。 + 請參閱版面配置資源
menu/定義應用程式選單 (例如,選項選單、操作選單或子選單) 的 XML 檔案。 +請參閱選單資源
raw/

以原始格式儲存的任意檔案。如要使用原始 +{@link java.io.InputStream} 開啟這些資源,使用資源 ID 呼叫 {@link android.content.res.Resources#openRawResource(int) +Resources.openRawResource()},資源 ID 為 {@code R.raw.filename}。

+

然而,如果您需要存取原始檔案名稱和檔案階層,可以考慮將一些資源儲存在 {@code +assets/} 目錄中 (而不是 {@code res/raw/})。 +位於 {@code assets/} 的檔案不會有資源 ID,因此您只能使用 {@link android.content.res.AssetManager} 讀取它們。 +

values/

包含簡單值 (例如,字串、整數和色彩) 的 XML 檔案。

+

位於其他 {@code res/} 子目錄的 XML 資源檔案會根據 XML 檔案名稱定義單一資源,而位於 {@code values/} 目錄的檔案則會描述多個資源。 +針對這個目錄中的檔案,{@code <resources>} 元素的每個子項都會定義單一資源。 + +例如,{@code <string>} 元素建立一個 +{@code R.string} 資源,{@code <color>} 元素建立一個 {@code R.color}資源。 +

+

由於每個資源都是以自己的 XML 元素定義,您可以依照自己的喜好命名檔案,並將不同的資源類型放在一個檔案中。 +然而,為了更加清楚,您可以將唯一資源類型放在不同的檔案中。 +例如,針對您可以在此目錄建立的資源,這裡提供一些檔案名稱慣例: +

+ +

請參閱字串資源、 +樣式資源和 +更多資源類型

+
xml/可以在執行階段讀取的任意 XML 檔案,方法是呼叫 {@link +android.content.res.Resources#getXml(int) Resources.getXML()}。各種 XML 設定檔案都必須儲存在這裡,例如可搜尋項目設定。 + +
+ +

注意:千萬不要將資源檔案直接儲存在 +{@code res/} 目錄內 — 會發生編譯錯誤。

+ +

如需特定資源類型的詳細資訊,請參閱資源類型文件。

+ +

您儲存在表 1 定義之子目錄的資源是您的「預設」資源。 +也就是說,這些資源會定義您應用程式的預設設計和內容。 +然而,不同類型的 Android 平台裝置可能需要不同類型的資源。 +例如,如果裝置的螢幕大於一般螢幕,則您需要提供不同的版面配置資源,以充分利用額外的螢幕空間。 +或者,如果裝置有不同的語言設定,則您需要提供不同的字串資源,以翻譯您使用者介面中的文字。 + +如要為不同裝置設定提供這些不同的資源,除了預設資源之外,您還需要提供替代資源。 + +

+ + +

提供替代資源

+ + +
+ +

+圖 1.兩個不同的裝置,個別使用不同的版面配置資源。

+
+ +

幾乎每個應用程式都應提供替代資源以支援特定裝置設定。 +例如,您應該為不同的螢幕密度包含替代可繪項目資源,並為不同語言提供替代字串資源。 +Android 會在執行階段偵測目前的裝置設定,並為您的應用程式載入適當的資源。 + +

+ +

如要為一組資源指定特定設定的替代項目:

+
    +
  1. 在 {@code res/} 建立一個新的目錄,命名的格式為 {@code +<resources_name>-<config_qualifier>}。 +
      +
    • {@code <resources_name>} 是對應預設資源 (定義在表 1) 的目錄名稱。 +
    • +
    • {@code <qualifier>} 是用來指定要使用這些資源之個別設定的名稱 (定義在表 2)。 +
    • +
    +

    您可以附加一個以上的 {@code <qualifier>}。使用破折號分隔每個項目。 +

    +

    注意:附加多個限定詞時,您必須以表 2 列出的相同順序放置它們。 +如果限定詞的順序錯誤,將會忽略該資源。 +

    +
  2. +
  3. 將各個替代資源儲存在這個新的目錄。資源檔案的名稱必須和預設資源檔案的名稱完全相同。 +
  4. +
+ +

例如,這裡有些預設和替代資源:

+ +
+res/
+    drawable/   
+        icon.png
+        background.png    
+    drawable-hdpi/  
+        icon.png
+        background.png  
+
+ +

{@code hdpi} 限定詞代表該目錄的資源適用於高密度螢幕裝置。 +這些可繪項目目錄中的影像大小已調整為符合特定螢幕密度,但是檔案名稱完全相同。 + +如此一來,您用來參照 {@code icon.png} 或 {@code +background.png} 影像的資源 ID 一律都會相同,但 Android 會透過比對裝置設定資訊和資源目錄名稱中的限定詞,選擇最符合目前裝置的每個資源版本。 + +

+ +

Android 支援多種設定限定詞,而且您可以將多個限定詞加到一個目錄名稱,並用破折號分隔每個限定詞。 +表 2 依優先等級列出有效的設定限定詞 — 如果您針對一個資源目錄使用多個限定詞,您必須依表格中列出的順序將它們新增到目錄名稱。 + + +

+ + +

表 2.設定限定詞名稱。 +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
設定限定詞值描述
MCC 和 MNC範例:
+ mcc310
+ mcc310-mnc004
+ mcc208-mnc00
+ 等等。 +
+

行動裝置國家/地區代碼 (MCC) 後面會選擇性加上裝置 SIM 卡上的行動裝置網路代碼 (MNC) +。例如,mcc310 代表美國地區的所有行動通訊業者、 + mcc310-mnc004 代表美國地區的 Verizon,以及 mcc208-mnc00 代表法國地區的 Orange。 +

+

如果裝置使用無線電連線 (GSM 手機),MCC 和 MNC 值都會來自SIM 卡。 +

+

您也可以單獨使用 MCC (例如,在應用程式包含國家特定法律資源)。 +如果您只需根據語言進行指定,則改為使用「語言和區域」限定詞 (稍後將會討論)。 +如果您決定使用 MCC 和 MNC 限定詞,應謹慎使用,並測試是否可按預期運作。 +

+

另請查看設定欄位 {@link +android.content.res.Configuration#mcc} 和 {@link +android.content.res.Configuration#mnc},分別提供目前的行動裝置國家代碼和行動裝置網路代碼。 +

+
語言和區域範例:
+ en
+ fr
+ en-rUS
+ fr-rFR
+ fr-rCA
+ 等等。 +

語言是以兩個字母的 ISO 639-1 語言代碼定義,後面可以視需要加上兩個字母的 ISO 3166-1-alpha-2 區域代碼 (前面加上小寫 "{@code r}")。 + + + +

+ 代碼「沒有」大小寫之分;{@code r} 首碼是用來區分區域部分。 + + 您不能只指定區域。

+

如果使用者在系統設定變更其語言,此設定就會在應用程式生命週期內發生變更。 +請參閱處理執行階段變更,以瞭解這會如何在執行階段期間影響您的應用程式。 +

+

請參閱當地語系化,以取得將您的應用程式當地語系化成其他語言的詳細指南。 +

+

另請查看 {@link android.content.res.Configuration#locale} 設定欄位,這會提供目前的地區設定。 +

+
版面配置方向ldrtl
+ ldltr
+

應用程式的版面配置方向。{@code ldrtl} 代表「版面配置方向右至左」。 + {@code ldltr} 代表「版面配置方向左至右」,而且是預設隱含值。 +

+

這適用於任何資源,例如版面配置、可繪項目或值。 +

+

例如,如果您想針對阿拉伯語言提供一些特定版面配置,以及為任何其他「右至左」語言 (像是波斯文或希伯來文) 提供幾個一般版面配置,則程式碼如下: + +

+
+res/
+    layout/   
+        main.xml  (Default layout)
+    layout-ar/  
+        main.xml  (Specific layout for Arabic)
+    layout-ldrtl/  
+        main.xml  (Any "right-to-left" language, except
+                  for Arabic, because the "ar" language qualifier
+                  has a higher precedence.)
+
+

注意:如要為您的應用程式啟用右至左版面配置功能,您必須將 {@code + supportsRtl} 設成 {@code "true"},以及將 {@code targetSdkVersion} 設成 17 或更高。 +

+

已新增至 API 級別 17。

+
smallestWidthsw<N>dp

+ 範例:
+ sw320dp
+ sw600dp
+ sw720dp
+ 等等。 +
+

螢幕的基本大小,也就是可用 +螢幕區域的最短維度。具體而言,裝置的 smallestWidth 是螢幕最短的可用高度和寬度 (您也可以把它當成螢幕的「最小寬度」)。 +您可以使用此限定詞確保無論螢幕目前的方向為何,您的應用程式至少有 {@code <N>} dps 的寬度可供 UI 使用。 + +

+

例如,如果您的版面配置需要隨時保持至少 600 dp 的最小螢幕區域維度,則您可以使用此限定詞建立版面配置資源 {@code +res/layout-sw600dp/}。 +系統只會在可用螢幕的最小維度至少是 600dp 時使用這些資源,無論 600dp 的長度對使用者而言是高度或寬度都一樣。 + +smallestWidth 是螢幕的固定螢幕大小特性;裝置的 smallestWidth 不會隨著螢幕方向的改變而變更。 +

+

裝置的 smallestWidth 會將螢幕裝飾和系統 UI 列入計算。例如,如果裝置的螢幕上有一些永久的 UI 元素,且這些元素佔用了 smallestWidth 座標軸上的空間,系統會宣告 smallestWidth 小於實際螢幕大小,因為這些是 UI 無法使用的螢幕像素。 + + +因此,您使用的值應該是版面配置所需的實際最小維度 (這個值通常是您版面配置支援的「最小寬度」,無論螢幕目前的方向為何都一樣)。 + +

+

您可以針對一般螢幕大小在這裡使用的一些值:

+
    +
  • 320 適用於採用以下螢幕設定的裝置: +
      +
    • 240x320 ldpi (QVGA 手機)
    • +
    • 320x480 mdpi (手機)
    • +
    • 480x800 hdpi (高密度手機)
    • +
    +
  • +
  • 480 適用於 480x800 mdpi 這類螢幕 (平板電腦/手機)。
  • +
  • 600 適用於 600x1024 mdpi 這類螢幕 (7" 平板電腦)。
  • +
  • 720 適用於 720x1280 mdpi 這類螢幕 (10" 平板電腦)。
  • +
+

當您的應用程式為 smallestWidth 限定詞提供多個值不相同的資源目錄時,系統會使用最接近 (不超過)裝置 smallestWidth 的值。 + +

+

已新增至 API 級別 13。

+

另請查看 {@code +android:requiresSmallestWidthDp} 屬性,該屬性會宣告與您應用程式相容的最小 smallestWidth,以及 {@link +android.content.res.Configuration#smallestScreenWidthDp} 設定欄位,此欄位保留裝置的 smallestWidth 值。 + +

+

如需設計不同螢幕及使用此限定詞的詳細資訊,請參閱支援多個螢幕開發人員指南。 + +

+
可用寬度w<N>dp

+ 範例:
+ w720dp
+ w1024dp
+ 等等。 +
+

指定資源應使用的最小可用螢幕寬度,以 {@code dp} 單位計算 — 由 <N> 值定義。 +這個設定值會隨著螢幕方向變更為橫向或直向而改變,以符合目前實際的寬度。 + +

+

當您的應用程式為這個設定提供多個值不相同的資源目錄時,系統會使用最接近 (不超過) 裝置目前螢幕寬度的值。 + +這個值會將螢幕裝飾列入計算,因此如果裝置顯示的左邊緣或右邊緣有一些永久的 UI 元素,則會使用比實際螢幕大小更小的寬度值,將這些 UI 元素列入計算並減少應用程式的可用空間。 + + + +

+

已新增至 API 級別 13。

+

另請查看 {@link android.content.res.Configuration#screenWidthDp} 設定欄位,該欄位保留目前的螢幕寬度。 +

+

如需設計不同螢幕及使用此限定詞的詳細資訊,請參閱支援多個螢幕開發人員指南。 + +

+
可用高度h<N>dp

+ 範例:
+ h720dp
+ h1024dp
+ 等等。 +
+

指定資源應使用的最小可用螢幕高度,以「dp」單位計算 — 由 <N> 值定義。 +這個設定值會隨著螢幕方向變更為橫向或直向而改變,以符合目前實際的高度。 + +

+

當您的應用程式為這個設定提供多個值不相同的資源目錄時,系統會使用最接近 (不超過) 裝置目前螢幕高度的值。 + +這個值會將螢幕裝飾列入計算,因此如果裝置顯示的上邊緣或下邊緣有一些永久的 UI 元素,則會使用比實際螢幕大小更小的高度值,將這些 UI 元素列入計算並減少應用程式的可用空間。 + + + +不固定的螢幕裝飾 (例如,可在全螢幕時隱藏的手機狀態列) 在這裡不列入計算,標題列或動作列這類視窗裝飾也不會列入計算,因此應用程式必須做好準備因應比其指定還小的空間。 + + + + +

已新增至 API 級別 13。

+

另請查看 {@link android.content.res.Configuration#screenHeightDp} 設定欄位,該欄位保留目前的螢幕寬度。 +

+

如需設計不同螢幕及使用此限定詞的詳細資訊,請參閱支援多個螢幕開發人員指南。 + +

+
螢幕大小 + small
+ normal
+ large
+ xlarge +
+
    +
  • {@code small}:與低密度 QVGA 螢幕大小相似的螢幕 +。小螢幕的最低版面配置大小約 320x426 dp 單位。 +範例包含 QVGA 低密度和 VGA 高密度。 +
  • +
  • {@code normal}:與中密度 HVGA 螢幕大小相似的螢幕。 +一般螢幕的最低版面配置大小約 320x470 dp 單位。 +這類螢幕的範例有 WQVGA 低密度、HVGA 中密度、WVGA高密度。 + +
  • +
  • {@code large}:與中密度 VGA 螢幕大小相似的螢幕。 + + 大螢幕的最低版面配置大小約 480x640 dp 單位。 + 範例包含 VGA 和 WVGA 中密度螢幕。
  • +
  • {@code xlarge}:比傳統中密度 HVGA 螢幕大很多的螢幕。 +超大螢幕的最低版面配置大小約 720x960 dp 單位。 +在大多數情況下,使用超大螢幕的裝置由於尺寸過大無法放入口袋,因此最有可能是平板電腦樣式的裝置。 + +已新增至 API 級別 9。
  • +
+

注意:使用大小限定詞不代表資源僅適用於該大小的螢幕。 +如果您提供的替代資源沒有更符合目前裝置設定的限定詞,系統將使用最符合的資源。 + +

+

注意:如果所有資源都使用比目前螢幕更大的大小限定詞,系統將不會使用這些資源,您的應用程式將會在執行階段當機 (例如,如果所有版面配置資源都標記為 {@code +xlarge} 限定詞,但裝置為一般大小螢幕)。 + +

+

已新增至 API 級別 4。

+ +

請參閱支援多個螢幕以取得詳細資訊。 +

+

另請查看 {@link android.content.res.Configuration#screenLayout} 設定欄位,該欄位會指出螢幕為小螢幕、一般螢幕或大螢幕。 + +

+
螢幕外觀 + long
+ notlong +
+
    +
  • {@code long}:長螢幕,例如 WQVGA、WVGA、FWVGA
  • +
  • {@code notlong}:非長螢幕,例如 QVGA、HVGA、VGA
  • +
+

已新增至 API 級別 4。

+

這純粹是依照螢幕的外觀比例來判定 (「長」螢幕是較寬的螢幕)。這與螢幕方向無關。 +

+

另請查看 {@link android.content.res.Configuration#screenLayout} 設定欄位,該欄位會指出螢幕是否為長螢幕。 +

+
螢幕方向 + port
+ land +
+
    +
  • {@code port}:裝置的方向為直向 (垂直)
  • +
  • {@code land}:裝置的方向為橫向 (水平)
  • + +
+

如果使用者旋轉螢幕,此設定就會在應用程式生命週期內發生變更。 +請參閱處理執行階段變更,以瞭解這會如何在執行階段期間影響您的應用程式。 +

+

另請查看 {@link android.content.res.Configuration#orientation} 設定欄位,該欄位會指出目前的裝置方向。 +

+
UI 模式 + car
+ desk
+ television
+ appliance + watch +
+
    +
  • {@code car}:在車用座架顯示的裝置
  • +
  • {@code desk}:在桌面座架顯示的裝置
  • +
  • {@code television}:在電視上顯示的裝置,提供在離使用者很遠的大螢幕上顯示 UI 的「十英呎」體驗,主要透過 DPAD 或其他非指標互動指定方向。 + + +
  • +
  • {@code appliance}:做為設備使用的裝置,沒有顯示器 +
  • +
  • {@code watch}:裝置有佩戴在手腕上的顯示器
  • +
+

已新增至 API 級別 8、電視已新增至 API 13、手錶已新增至 API 20。

+

如要瞭解從座架插入或移除裝置時,您的應用程式可以如何回應的相關資訊,請參閱判斷和監控座架狀態和類型。 + +

+

如果使用者將裝置放入座架,此設定就會在應用程式生命週期內發生變更。 +您可以使用 {@link +android.app.UiModeManager} 啟用或停用這些模式。請參閱處理執行階段變更,以瞭解這會如何在執行階段期間影響您的應用程式。 +

+
夜間模式 + night
+ notnight +
+
    +
  • {@code night}:夜間時間
  • +
  • {@code notnight}:日間模式
  • +
+

已新增至 API 級別 8。

+

如果從自動模式 (預設) 的夜間模式離開,在這種情況下模式會隨著日間時間變更,此設定就會在應用程式生命週期內發生變更。 +您可以使用 {@link android.app.UiModeManager} 啟用或停用這個模式。 +請參閱處理執行階段變更,以瞭解這會如何在執行階段期間影響您的應用程式。 +

+
螢幕像素密度 (dpi) + ldpi
+ mdpi
+ hdpi
+ xhdpi
+ xxhdpi
+ xxxhdpi
+ nodpi
+ tvdpi +
+
    +
  • {@code ldpi}:低密度螢幕;約 120dpi。
  • +
  • {@code mdpi}:中密度 (在傳統 HVGA) 螢幕;約 160dpi。 +
  • +
  • {@code hdpi}:高密度螢幕;約 240dpi。
  • +
  • {@code xhdpi}:特高密度螢幕;約 320dpi。已新增至 API 級別 8。 +
  • +
  • {@code xxhdpi}:特特高密度螢幕;約 480dpi。已新增至 API 級別 16。 +
  • +
  • {@code xxxhdpi}:特特特高密度使用 (僅限啟動器圖示,請參閱支援多個螢幕中的注意);約 640dpi。 + +已新增至 API 級別 18。 +
  • +
  • {@code nodpi}:如果您不想縮放點陣圖資源以符合裝置密度,可使用此設定。 +
  • +
  • {@code tvdpi}:介於 mdpi 和 hdpi 的螢幕;約 213dpi。這不屬於「主要」密度群組。 +這大部分用於電視,大多數應用程式應該不需要用到 —mdpi 和 hdpi 資源對大多數應用程式已經足夠,系統將視需要縮放它們。 + +這個限定詞由 API 級別 13 導入。
  • +
+

六個主要密度之間有一個 3:4:6:8:12:16 縮放比例 (忽略tvdpi 密度)。 +因此,ldpi 的 9x9 點陣圖等同於 mdpi 的 12x12 點陣圖、hdpi 的 18x18 點陣圖、xhdpi 的 24x24 點陣圖,以此類推。 +

+

如果您覺得影像資源在電視或其他特定裝置上看起來不夠美觀,而想嘗試 tvdpi 資源,比例因數為 1.33*mdpi。 +例如,mdpi 螢幕上的 100px x 100px 影像在 tvdpi 螢幕應該是 133px x 133px。 +

+

注意:使用密度限定詞不代表資源僅適用於該密度的螢幕。 +如果您提供的替代資源沒有更符合目前裝置設定的限定詞,系統將使用最符合的資源。 + +

+

請參閱支援多個螢幕,深入瞭解如何處理不同的螢幕密度,以及 Android會如何縮放點陣圖以符合目前的密度。 + +

+
觸控螢幕類型 + notouch
+ finger +
+
    +
  • {@code notouch}:沒有觸控螢幕的裝置。
  • +
  • {@code finger}:裝置含有觸控螢幕,其目的是透過使用者的手指進行方向互動。 +
  • +
+

另請查看 {@link android.content.res.Configuration#touchscreen} 設定欄位,該欄位會指出裝置的觸控螢幕類型。 +

+
鍵盤可用性 + keysexposed
+ keyshidden
+ keyssoft +
+
    +
  • {@code keysexposed}:裝置包含鍵盤。如果裝置啟用軟體鍵盤 (可能性很高),即使沒有將硬體鍵盤展示給使用者或甚至裝置沒有硬體鍵盤時,都可使用此設定。 + +如果沒有提供或已停用軟體鍵盤,則只能在展示硬體鍵盤時使用此設定。 + +
  • +
  • {@code keyshidden}:裝置有可用的硬體鍵盤但已隱藏起來,且裝置沒有啟用軟體鍵盤。 +
  • +
  • {@code keyssoft}:裝置已啟用軟體鍵盤,無論是可見或不可見。 +
  • +
+

如果您提供 keysexposed 資源但沒有 keyssoft資源,只要系統已啟用軟體鍵盤,無論鍵盤是否可見,系統都會使用 keysexposed 資源。 + +

+

如果使用者開啟硬體鍵盤,此設定就會在應用程式生命週期內發生變更。 +請參閱處理執行階段變更,以瞭解這會如何在執行階段期間影響您的應用程式。 +

+

另請查看設定欄位 {@link +android.content.res.Configuration#hardKeyboardHidden} 和 {@link +android.content.res.Configuration#keyboardHidden},這兩個欄位會分別指出硬體鍵盤的可見度,以及任何其他類型鍵盤 (包含軟體) 的可見度。 +

+
主要文字輸入方式 + nokeys
+ qwerty
+ 12key +
+
    +
  • {@code nokeys}:裝置沒有硬體按鍵可輸入文字。
  • +
  • {@code qwerty}:裝置有硬體 qwerty 軟體鍵盤,無論使用者是否可見。 + +
  • +
  • {@code 12key}:裝置有硬體 12 鍵鍵盤,無論使用者是否可見。 +
  • +
+

另請查看 {@link android.content.res.Configuration#keyboard} 設定欄位,該欄位會指出可用的主要文字輸入方式。 +

+
平台版本 (API 級別)範例:
+ v3
+ v4
+ v7
+ 等等。
+

裝置支援的 API 級別。例如,v1 代表 API 級別1 (裝置安裝 Android 1.0 以上版本),以及 v4 代表 API 級別 4 (裝置安裝 Android1.6 以上版本)。 + +請參閱 Android API 級別文件以取得有關這些值的詳細資訊。 +

+
+ + +

注意:有些設定限定詞從 Android1.0 就已新增,因此並非所有 Android 版本都支援所有限定詞。 +使用新的限定詞會以隱含的方式新增平台版本的限定詞,因此較舊的裝置一定會忽略它。 +例如,使用 + w600dp 限定詞最自動包含 v13 限定詞,因為可用寬度限定詞是 API 級別 13 的新項目。 +為了避免發生任何問題,永遠要包含一組預設資源 (一組沒有限定詞的資源)。 +如需詳細資訊,請參閱使用資源提供最佳的裝置相容性一節。 + +

+ + + +

限定詞名稱規則

+ +

這裡有一些使用設定限定詞名稱的規則:

+ +
    +
  • 您可以為一組資源指定多個限定詞,以破折號分隔。例如, +drawable-en-rUS-land 適用於橫向的美國英文裝置。 +
  • +
  • 限定詞的順序必須與表 2 所列的一樣。例如: + +
      +
    • 錯誤:drawable-hdpi-port/
    • +
    • 正確:drawable-port-hdpi/
    • +
    +
  • +
  • 替代資源目錄不可為巢狀。例如,您不能有 +res/drawable/drawable-en/
  • +
  • 值有大小寫之分。資源編譯器會將目錄名稱轉換為小寫再處理,以避免有大小寫之分的檔案系統發生問題。 + +名稱中的任何大寫只是為了方便閱讀。
  • +
  • 每個限定詞類型只支援一個值。例如,如果您想在西班牙文和法文使用相同的可繪項目檔案,則不能將目錄命名為 +drawable-rES-rFR/。 +您必須有兩個資源目錄,例如 +drawable-rES/drawable-rFR/,這兩個目錄要包含適當的檔案。 +但是,您不需要實際將相同的檔案複製到這兩位置。您可以為資源建立別名。 +請參閱下方的 +建立別名資源
  • +
+ +

當您將替代資源儲存到以這些限定詞命名的目錄後,Android 會根據目前的裝置設定自動將資源套用到您的應用程式。 + +每次要求資源時,Android 會檢查包含要求之資源檔案的替代資源目錄,然後尋找最符合的資源 + (下方有相關討論)。 +如果沒有符合特定裝置設定的替代資源,則 Android 會使用對應的預設資源 (特定資源類型的資源組,不含設定限定詞)。 + + +

+ + + +

建立別名資源

+ +

如果您要將資源用於一個以上的裝置設定 (但不想以預設資源的形式提供),您不需要將相同的資源放入一個以上的替代資源目錄。 + +您可以 (在某些情況下) 建立一個替代資源,將它做為儲存在您預設資源目錄的資源別名。 + +

+ +

注意:並非所有資源都提供為其他資源建立別名的機制。 +特別是動畫、選單、原始項目和其他在 {@code xml/} 目錄中未指定的資源都不提供這項功能。 +

+ +

例如,想像您有一個應用程式圖示 {@code icon.png},且必須為不同的地區設定提供唯一的版本。 +但是,加拿大英文和加拿大法文這兩個地區設定必須使用相同的版本。 +您可能會假設必須在加拿大英文和加拿大法文這兩個資源目錄中複製相同的影像,但並不需要這樣做。 + +您可以將這兩個地區設定的影像儲存成 {@code icon_ca.png} ({@code icon.png} 以外的任何名稱),然後將該影像放入預設的 {@code res/drawable/} 目錄。 + +之後,在 {@code +res/drawable-en-rCA/} 建立一個 {@code icon.xml} 檔案,以及建立 {@code res/drawable-fr-rCA/},用於參照使用 {@code <bitmap>} 元素的 {@code icon_ca.png} 資源。 +這樣可以讓您只儲存一個版本的 PNG 檔案,以及指向該檔案的兩個小 XML 檔案。 +(範例 XML 檔案如下所示。)

+ + +

可繪項目

+ +

如要為現有的可繪項目建立別名,請使用 {@code <bitmap>} 元素。 +例如:

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
+    android:src="@drawable/icon_ca" />
+
+ +

如果您將此檔案儲存成 {@code icon.xml} (在替代資源目錄中,像是 +{@code res/drawable-en-rCA/}),系統會將它編譯成可當作 {@code R.drawable.icon} 參照的資源,但它實際上是 {@code +R.drawable.icon_ca} 資源 (儲存在 {@code res/drawable/}) 的別名。 +

+ + +

版面配置

+ +

如要為現有的可繪項目建立別名,請使用以 {@code <merge>} 包裝的{@code <include>} 元素。 +例如:

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<merge>
+    <include layout="@layout/main_ltr"/>
+</merge>
+
+ +

如果您將此檔案儲存成 {@code main.xml},系統會將它編譯成可當作 {@code R.layout.main} 參照的資源,但它實際上是 {@code R.layout.main_ltr} 資源的別名。 + +

+ + +

字串和其他簡單值

+ +

如要為現有的字串建立別名,只要將所需字串的資源 ID 當作新字串的值即可。 +例如:

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <string name="hello">Hello</string>
+    <string name="hi">@string/hello</string>
+</resources>
+
+ +

{@code R.string.hi} 資源現在是 {@code R.string.hello} 的別名。

+ +

其他簡易值的運作方法也一樣。 +例如,一個顏色:

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <color name="yellow">#f00</color>
+    <color name="highlight">@color/red</color>
+</resources>
+
+ + + + +

使用資源提供最佳的裝置相容性

+ +

如要讓您的應用程式支援多個裝置設定,務必要為您應用程式使用的每個資源類型提供預設資源。 +

+ +

例如,如果您的應用程式支援多個語言,一律要包含一個 {@code +values/} 目錄 (儲存字串的位置),其中「不包含」語言和區域限定詞。如果您將所有字串檔案放在含有語言和區域限定詞的目錄中,當執行應用程式的裝置,其語言設定不受字串支援時,應用程式就會當機。 + +不過,只要您提供預設 {@code values/} 資源, +應用程式就能正常運作 (即便使用者不懂該語言 — 仍然比當機好)。 +

+ +

同樣地,如果您根據螢幕方向提供不同的版面配置資源,您應該選擇一個方向做為您的預設值。 +例如,不要在 {@code +layout-land/} 提供橫向版面配置資源及在 {@code layout-port/} 提供直向版面配置資源,而是留下一個做為預設值,例如 {@code layout/} 做為橫向和 {@code layout-port/} 做為直向。 +

+ +

提供預設資源很重要,這不只是因為您的應用程式可能執行您未預期到的設定,還因為新的 Android 版本有時候會新增舊版不支援的設定限定詞。 + +如果您使用新的資源限定詞,但與舊版 Android 保持程式碼相容,則舊版 Android 執行您的應用程式時,如果您沒有提供預設資源,應用程式就會當機,這是因為它無法使用以新限定詞命名的資源。 + + +例如,如果您的 {@code +minSdkVersion} 設為 4,而且您讓所有可繪項目資源具備使用夜間模式的資格 (已新增到 API 級別 8 的 {@code night} 或 {@code notnight}),則 API 級別 4 裝置將無法存取您的可繪項目資源且會當機。 +在這種情況下,您可能會想要將 {@code notnight} 當作預設資源,因此您應該要排除該限定詞,讓您的可繪項目資源成為 {@code drawable/} 或 {@code drawable-night/}。 + +

+ +

因此,如要提供最佳的裝置相容性,請務必針對應用程式正常執行所需的資源提供預設資源。 +然後,使用設定限定詞為特定裝置設定建立替代資源。 +

+ +

這個規則有一個例外狀況:如果您應用程式的 {@code minSdkVersion} 為 4 或高,當您提供含有螢幕密度限定詞的替代可繪項目資源時,則不需要預設可繪項目資源。 + +即使沒有預設可繪項目資源,Android 仍然可從替代螢幕密度中找到最符合的項目,並視需要縮放點陣圖。 + +不過,為了在所有裝置類型獲得最佳體驗,您應該為這三種密度類型提供替代可繪項目。 +

+ + + +

Android 如何尋找最相符的資源

+ +

當您要求已提供替代項目的資源時,Android 會根據目前的裝置設定,選擇執行階段要使用的替代資源。 +為了示範 Android 如何選取替代資源,假設下列可繪項目目錄分別包含相同影像的不同版本: + +

+ +
+drawable/
+drawable-en/
+drawable-fr-rCA/
+drawable-en-port/
+drawable-en-notouch-12key/
+drawable-port-ldpi/
+drawable-port-notouch-12key/
+
+ +

同時假設裝置設定如下:

+ +

+地區設定 = en-GB
+螢幕方向 = port
+螢幕像素密度 = hdpi
+觸控螢幕類型 = notouch
+主要文字輸入方式 = 12key +

+ +

Android 透過比對裝置設定和可用的替代資源,從 {@code drawable-en-port} 選取可繪項目。 +

+ +

系統使用下列邏輯決定要使用的資源: +

+ + +
+ +

圖 2.Android 如何尋找最相符資源的流程圖。 +

+
+ + +
    +
  1. 排除與裝置設定衝突的資源檔案。 +

    已排除 drawable-fr-rCA/ 目錄,因為它與 en-GB 地區設定衝突。 +

    +
    +drawable/
    +drawable-en/
    +drawable-fr-rCA/
    +drawable-en-port/
    +drawable-en-notouch-12key/
    +drawable-port-ldpi/
    +drawable-port-notouch-12key/
    +
    +

    例外狀況:螢幕像素密度是唯一沒有因為衝突而被排除的限定詞。 +即使裝置的螢幕密度是 hdpi 但仍然沒有排除 +drawable-port-ldpi/,因為此刻每個螢幕密度都視為相符。 +如需詳細資訊,請參閱支援多個螢幕文件。 +

  2. + +
  3. 在清單 (表 2) 中選擇 (下一個) 最高優先等級的限定詞(從 MCC 往下移動)。 +
  4. +
  5. 有任何資源目錄包含此限定詞嗎?
  6. +
      +
    • 如果沒有,回到步驟 2 查看下一個限定詞。(在此範例中,回答一直是「否」,直到語言限定詞為止。) +
    • +
    • 如果有,繼續到步驟 4。
    • +
    + + +
  7. 排除不包含此限定詞的來源目錄。在此範例中,系統排除所有不包含語言限定詞的目錄: +
  8. +
    +drawable/
    +drawable-en/
    +drawable-en-port/
    +drawable-en-notouch-12key/
    +drawable-port-ldpi/
    +drawable-port-notouch-12key/
    +
    +

    例外狀況:如果提到的限定詞是螢幕像素密度,Android 會選擇最符合裝置螢幕密度的選項。一般而言,Android 偏好縮小較大的原始影像以放大較小的原始影像。 + + +請參閱支援多個螢幕。 +

    + + +
  9. 重複步驟 2、3 和 4,直到剩下一個目錄為止。在此範例中,螢幕方向是下個有相符項目的限定詞。 +因此,將排除沒有指定螢幕方向的資源: + +
    +drawable-en/
    +drawable-en-port/
    +drawable-en-notouch-12key/
    +
    +

    剩下的目錄是 {@code drawable-en-port}。

    +
  10. +
+ +

雖然會針對每個要求的資源執行此程序,但系統還是會進一步最佳化某些方面。 +其中一個最佳化是一旦知道裝置設定,它將排除永遠無法符合的替代資源。 +例如,如果設定語言是英文 ("en"),則語言限定詞設定為不是英文的任何資源目錄永遠都不會包含在檢查的資源集區內 (但仍會包含不含語言限定詞的資源目錄)。 + + +

+ +

根據螢幕大小限定詞選取資源時,如果沒有最符合的資源,系統會使用適用於比目前螢幕更小之螢幕設計的資源 (例如,如有需要,大型螢幕會使用一般大小螢幕資源)。 + +然而,如果唯一可用的資源比目前螢幕更大,系統將不會使用這些資源,如果沒有其他資源符合裝置設定,則應用程式將會當機 (例如,如果所有版面配置資源都標記為 {@code xlarge} 限定詞,但裝置為一般大小螢幕)。 + + + +

+ +

注意:限定詞的優先等級 (在表 2) 比完全符合裝置的限定詞數量更重要。 +例如,在上述步驟 4,清單的最後一個選擇包含三個完全符合裝置的限定詞 (方向、觸控螢幕類型和輸入方法),而 drawable-en 只有一個相符的參數(語言)。 + + +然而,語言的優先等級高於這些其他限定詞,因此將排除 +drawable-port-notouch-12key

+ +

如需深入瞭解在應用程式使用資源的方法,請繼續閱讀存取資源

diff --git a/docs/html-intl/intl/zh-tw/guide/topics/resources/runtime-changes.jd b/docs/html-intl/intl/zh-tw/guide/topics/resources/runtime-changes.jd new file mode 100644 index 0000000000000000000000000000000000000000..7a8b3ae7fc3a08c74e5c1aa0d626686da570f25f --- /dev/null +++ b/docs/html-intl/intl/zh-tw/guide/topics/resources/runtime-changes.jd @@ -0,0 +1,281 @@ +page.title=處理執行階段變更 +page.tags=Activity、生命週期 +@jd:body + + + +

有些裝置設定可以在執行階段期間進行變更 (例如,螢幕方向、鍵盤可用性和語言)。 +進行這類變更時,Android 會重新啟動執行中的 +{@link android.app.Activity} (呼叫 {@link android.app.Activity#onDestroy()},後面加上 {@link +android.app.Activity#onCreate(Bundle) onCreate()})。 +重新啟動行為的設計是以符合新裝置設定的替代資源自動重新載入您的應用程式,以協助您的應用程式適應新的設定。 + +

+ +

如要正確處理重新啟動,務必透過一般 Activity 生命週期將您的 Activity 還原為之前的狀態,其中 Android 會先呼叫 +{@link android.app.Activity#onSaveInstanceState(Bundle) onSaveInstanceState()} 再終結您的 Activity,讓您能儲存應用程式狀態的相關資料。 + + +之後,您便能在 {@link android.app.Activity#onCreate(Bundle) onCreate()} 或 {@link +android.app.Activity#onRestoreInstanceState(Bundle) onRestoreInstanceState()} 期間還原狀態。 +

+ +

如要測試您的應用程式是否使用原本的應用程式狀態重新啟動,您應該在應用程式執行各種工作時呼叫設定變更 (例如變更螢幕方向)。 + +您的應用程式應該能隨時重新啟動而不會遺失使用者資料或狀態,以便處理設定變更這類事件,或是使用者接聽來電,然後很久後才在應用程式程序可能終結後才返回應用程式的這類情況。 + + +如要瞭解如何還原您的 Activity 狀態,請參閱 Activity 生命週期

+ +

不過,您可能遇到重新啟動應用程式以及還原大量資料的成本很昂貴,而且會產生使用者體驗不佳的情況。 +在這種情況下,您有兩種其他選擇: +

+ +
    +
  1. 變更設定期間保留物件 +

    允許您的 Activity 在設定變更時重新啟動,但將可設定狀態的物件帶到 Activity 的新執行個體中。 +

    + +
  2. +
  3. 自行處理設定變更 +

    避免系統在某些設定變更期間重新啟動您的 Activity,但在設定變更時接收回呼,您便能視需要手動更新您的 Activity。 + +

    +
  4. +
+ + +

變更設定期間保留物件

+ +

如果重新啟動您的 Activity 需要復原大量資料、重新建立網路連線或執行其他密集型操作,則由於設定變更造成的完整重新啟動可能會拖慢使用者體驗。 + +此外,您可能無法透過 {@link android.os.Bundle} 完全還原您的 Activity 狀態,這是系統透過 {@link android.app.Activity#onSaveInstanceState(Bundle) onSaveInstanceState()} 回呼所為您所儲存的—它的設計並不是用來傳送大型物件 (例如點陣圖),而且其中的資料必須先序列化再還原序列化,這會耗用大量記憶體並讓設定變更變慢。 + + + +在這種情況下,您可以在 Activity 因設定變更而重新啟動時,透過保留 {@link +android.app.Fragment} 的方式來減少重新初始化 Activity 的負擔。 +這個片段可包含您要保留可設定狀態物件的參考資料。 +

+ +

當 Android 系統因設定變更而關閉您的 Activity 時,您標示要保留的 Activity 片段不會被終結。 +您可以將這類片段新增至您的 Activity 以保留可設定狀態的物件。 +

+ +

如要在執行階段設定變更期間,在片段中保留可設定狀態的物件:

+ +
    +
  1. 延伸 {@link android.app.Fragment} 類別並宣告可設定狀態物件的參考資料。 +
  2. +
  3. 片段建立之後,呼叫 {@link android.app.Fragment#setRetainInstance(boolean)}。 +
  4. +
  5. 將片段新增至您的 Activity。
  6. +
  7. 當 Activity 重新啟動時,使用 {@link android.app.FragmentManager} 擷取片段。 +
  8. +
+ +

例如,將您的片段定義如下:

+ +
+public class RetainedFragment extends Fragment {
+
+    // data object we want to retain
+    private MyDataObject data;
+
+    // this method is only called once for this fragment
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        // retain this fragment
+        setRetainInstance(true);
+    }
+
+    public void setData(MyDataObject data) {
+        this.data = data;
+    }
+
+    public MyDataObject getData() {
+        return data;
+    }
+}
+
+ +

注意:雖然您可以儲存任何物件,但您不應該傳送與 {@link android.app.Activity} 相關的物件,例如 {@link +android.graphics.drawable.Drawable}、{@link android.widget.Adapter}、{@link android.view.View} 或任何與 {@link android.content.Context} 關聯的其他物件。 + +如果您這樣做,會流失原始 Activity 執行個體的所有檢視和資源。 +(資源流失表示您的應用程式會繼續保留資源但無法回收記憶體,因此會流失大量記憶體。) + +

+ +

然後使用 {@link android.app.FragmentManager} 將片段新增至您的 Activity。您可以在執行階段設定變更期間,於 Activity 再次啟動時,從片段取得資料物件。 + +例如,將您的 Activity 定義如下:

+ +
+public class MyActivity extends Activity {
+
+    private RetainedFragment dataFragment;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.main);
+
+        // find the retained fragment on activity restarts
+        FragmentManager fm = getFragmentManager();
+        dataFragment = (DataFragment) fm.findFragmentByTag(“data”);
+
+        // create the fragment and data the first time
+        if (dataFragment == null) {
+            // add the fragment
+            dataFragment = new DataFragment();
+            fm.beginTransaction().add(dataFragment, “data”).commit();
+            // load the data from the web
+            dataFragment.setData(loadMyData());
+        }
+
+        // the data is available in dataFragment.getData()
+        ...
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+        // store the data in the fragment
+        dataFragment.setData(collectMyLoadedData());
+    }
+}
+
+ +

在此範例中,{@link android.app.Activity#onCreate(Bundle) onCreate()} 會在 Activity 新增片段或還原參考資料。 +{@link android.app.Activity#onCreate(Bundle) onCreate()} 也會在片段執行個體內儲存可設定狀態的物件。 + +{@link android.app.Activity#onDestroy() onDestroy()} 會在保留的片段執行個體內更新可設定狀態的物件。 +

+ + + + + +

自行處理設定變更

+ +

如果您的應用程式在特定設定變更期間不需要更新資源,「且」您具有效能限制,要求您避免 Activity 重新啟動,則您可以宣告您的 Activity 自行處理設定變更,這樣可避免系統重新啟動您的 Activity。 + + +

+ +

注意:自行處理設定變更讓替代資源的使用變得更加困難,因為系統無法幫您自動套用。 + +當您必須避免因為設定變更而造成重新啟動時,應將這個方式視為最後手段,而且不建議對大部分的應用程式使用。 +

+ +

如要宣告您的 Activity 處理設定變更,可在宣示說明檔案中編輯適當的 {@code <activity>} 元素以包含 {@code +android:configChanges} 屬性和值,代表您要處理的設定。 + +{@code +android:configChanges} 屬性的可能值列於文件中 (最常用的值是 {@code "orientation"},可避免在螢幕方向變更時重新啟動,而 {@code "keyboardHidden"} 可避免鍵盤可用性變更時重新啟動)。 + +您可以使用直立線符號 {@code |} 字元來分隔,在屬性中宣告多個設定值。 +

+ +

例如,下列宣示說明程式碼宣告同時處理螢幕方向變更和鍵盤可用性變更的 Activity: +

+ +
+<activity android:name=".MyActivity"
+          android:configChanges="orientation|keyboardHidden"
+          android:label="@string/app_name">
+
+ +

現在,當其中一個設定變更時, {@code MyActivity} 便不會重新啟動。 +而是由 {@code MyActivity} 接收對 {@link android.app.Activity#onConfigurationChanged(Configuration) onConfigurationChanged()} 的呼叫。 +這個方法傳送一個 {@link android.content.res.Configuration} 物件,指定新裝置設定。 + +讀取 {@link android.content.res.Configuration} 中的欄位時,您可判斷新的設定,並且更新您介面中使用的資源來進行適當的變更。 + +此時,會呼叫這個方法,Activity 的 {@link android.content.res.Resources} 物件會根據新設定進行更新以傳回資源,因此您便能輕鬆地重新設定 UI 的元素,系統無需重新啟動您的 Activity。 + + +

+ +

注意:從 Android 3.2 (API 級別 13) 開始,裝置在橫向與直向之間進行切換時,「螢幕大小」也會跟著變更。 + +因此,在開發 API 級別 13 或更高級別時 (如 {@code minSdkVersion}{@code targetSdkVersion} 屬性所宣告),如果您要避免因為方向變更而造成執行階段重新啟動,除了{@code +"orientation"} 值之外,您還必須包含 {@code "screenSize"} 值。 + +也就是說,您必須宣告 {@code +android:configChanges="orientation|screenSize"}。不過,如果您的應用程式是針對 API 級別 12 或更低級別,則您的 Activity 一律要自行處理這個設定變更 (即使在 Android 3.2 或更高版本的裝置上執行時,這個設定變更也不會重新啟動您的 Activity)。 + +

+ +

例如,下列 {@link +android.app.Activity#onConfigurationChanged(Configuration) onConfigurationChanged()} 實作會檢查目前的裝置方向: +

+ +
+@Override
+public void onConfigurationChanged(Configuration newConfig) {
+    super.onConfigurationChanged(newConfig);
+
+    // Checks the orientation of the screen
+    if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
+        Toast.makeText(this, "landscape", Toast.LENGTH_SHORT).show();
+    } else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT){
+        Toast.makeText(this, "portrait", Toast.LENGTH_SHORT).show();
+    }
+}
+
+ +

{@link android.content.res.Configuration} 物件代表目前所有的設定,而不只是已變更的設定。 +大多數情況下,您不用特別留意設定如何變更,只要重新指派所有的資源,提供替代項目給您正在處理的設定。 + +例如,因為 {@link +android.content.res.Resources} 物件現在已更新,您可以使用 {@link android.widget.ImageView#setImageResource(int) +setImageResource()} 重新設定任何 {@link android.widget.ImageView}並且針對新設定,使用適當的資源 (如提供資源中所述)。 + +

+ +

請注意,{@link +android.content.res.Configuration} 欄位的值,是與 {@link android.content.res.Configuration} 類別的特定常數相符的整數。 +如需與每個欄位搭配使用之常數的相關文件,請參閱 {@link +android.content.res.Configuration} 參考資料中的適當欄位。 +

+ +

請記住:當您宣告您的 Activity 以處理設定變更時,您要負責為提供的替代項目重新設定所有元素。 +如果您宣告您的 Activity 以處理方向變更,而且具有應該在橫向與直向之間進行方向變更的影像,則您必須在 {@link +android.app.Activity#onConfigurationChanged(Configuration) onConfigurationChanged()} 期間對每個元素重新指派每個資源。 + +

+ +

如果您不需要根據這些設定變更來更新您的應用程式,可改為不必實作 {@link +android.app.Activity#onConfigurationChanged(Configuration) onConfigurationChanged()}。 +在此情況下,仍然可使用設定變更前使用的所有資源,而您只要避免重新啟動您的 Activity 即可。 + +不過,您的應用程式應該能夠關閉以及重新啟動成之前原本的狀態,因此在一般 Activity 生命週期期間無法保留您的狀態時,就不應考慮使用這個方法。 + +這不只是因為有其他設定變更讓您無法防止重新啟動應用程式,還因為有您應該處理事件,例如當使用者離開應用程式後,使用者返回應用程式之前應用程式已終結。 + + +

+ +

如需有關您在 Activity 中可以處理哪些設定變更的詳細資訊,請參閱 {@code +android:configChanges} 文件和 {@link android.content.res.Configuration} 類別。 +

diff --git a/docs/html-intl/intl/zh-tw/guide/topics/ui/controls.jd b/docs/html-intl/intl/zh-tw/guide/topics/ui/controls.jd new file mode 100644 index 0000000000000000000000000000000000000000..0f27ae4e8803f256d7abf642a8e64d314a5eda9f --- /dev/null +++ b/docs/html-intl/intl/zh-tw/guide/topics/ui/controls.jd @@ -0,0 +1,90 @@ +page.title=輸入控制項 +parent.title=使用者介面 +parent.link=index.html +@jd:body + +
+ +
+ +

輸入控制項是應用程式使用者介面中的互動元件。Android 提供了多種控制項讓您在 UI 中使用,例如按鈕、文字欄位、搜尋列、核取方塊、縮放按鈕、切換按鈕等。 + +

+ +

在使用者介面中加入輸入控制項,就如同將 XML 元素加到 XML 版面配置一樣簡單。例如,以下是包含文字欄位和按鈕的版面配置: +

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    android:orientation="horizontal">
+    <EditText android:id="@+id/edit_message"
+        android:layout_weight="1"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:hint="@string/edit_message" />
+    <Button android:id="@+id/button_send"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/button_send"
+        android:onClick="sendMessage" />
+</LinearLayout>
+
+ +

每個輸入控制項均支援一組特定輸入事件,方便您處理使用者輸入文字或輕觸按鈕等事件。 +

+ + +

一般控制項

+

下方列出您可在應用程式中使用的部分一般控制項。只要點進清單中的相關連結,即可進一步瞭解如何使用這些控制項。 +

+ +

注意:本文並未列出 Android 提供的部分控制項。 +如果想查看未列出的控制項,請瀏覽 {@link android.widget} 套件。如果您的應用程式需要特定類型的輸入控制項,您可以自行建置自訂元件。 +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
控制項類型說明相關類別
按鈕可供使用者按下或點擊來執行某項動作的按鈕。{@link android.widget.Button Button}
文字欄位可編輯的文字欄位。您可以使用 AutoCompleteTextView 小工具建立可提供自動完成建議的文字輸入小工具。{@link android.widget.EditText EditText}、{@link android.widget.AutoCompleteTextView}
核取方塊可供使用者切換的開啟/關閉開關。如果想為使用者提供一組互不相斥的可選取選項時,請使用核取方塊。{@link android.widget.CheckBox CheckBox}
圓形按鈕功用與核取方塊類似,但會限制使用者只能從一組選項中選取一個選項。{@link android.widget.RadioGroup RadioGroup} +
{@link android.widget.RadioButton RadioButton}
切換按鈕附有亮光指標的開啟/關閉按鈕。{@link android.widget.ToggleButton ToggleButton}
微調按鈕可供使用者從一組選項中選取單一值的下拉式清單。{@link android.widget.Spinner Spinner}
挑選器可供使用者透過向上/向下按鈕或滑動手勢選取單一值的對話方塊。此外,挑選器還會提供 DatePicker 程式碼小工具和 TimePicker 小工具,分別讓使用者輸入日期值 (年、月、日) 以及時間值 (小時、分鐘、AM/PM);系統會自動根據使用者所在的地區為這些值設定對應的格式。{@link android.widget.DatePicker}、{@link android.widget.TimePicker}
diff --git a/docs/html-intl/intl/zh-tw/guide/topics/ui/declaring-layout.jd b/docs/html-intl/intl/zh-tw/guide/topics/ui/declaring-layout.jd new file mode 100644 index 0000000000000000000000000000000000000000..72755715e3a217d13102ab1a1c244bd0fb4fde81 --- /dev/null +++ b/docs/html-intl/intl/zh-tw/guide/topics/ui/declaring-layout.jd @@ -0,0 +1,492 @@ +page.title=版面配置 +page.tags=view,viewgroup +@jd:body + +
+
+

本文件內容

+
    +
  1. 編寫 XML
  2. +
  3. 載入 XML 資源
  4. +
  5. 屬性 +
      +
    1. ID
    2. +
    3. 版面配置參數
    4. +
    +
  6. +
  7. 版面配置位置
  8. +
  9. 大小、邊距和邊界
  10. +
  11. 常見版面配置
  12. +
  13. 使用配接器建置版面配置 +
      +
    1. 將資料填入配接器檢視
    2. +
    3. 處理點擊事件
    4. +
    +
  14. +
+ +

重要類別

+
    +
  1. {@link android.view.View}
  2. +
  3. {@link android.view.ViewGroup}
  4. +
  5. {@link android.view.ViewGroup.LayoutParams}
  6. +
+ +

另請參閱

+
    +
  1. 建置簡易使用者 +介面
+
+ +

版面配置會定義使用者介面 (例如 Activity應用程式小工具的使用者介面) 的視覺結構。您可以兩種方式宣告版面配置: +

+
    +
  • 在 XML 中宣告 UI 元素。Android 提供的 XML 字彙不僅簡單明瞭,還可對應至 View 類別和子類別 (例如小工具和版面配置)。 +
  • +
  • 在執行階段啟動版面配置元素。您的應用程式可透過程式建立 View 和 ViewGroup 物件 (以及操控其屬性)。 +
  • +
+ +

Android 架構可讓您彈性使用上述任一或兩種方法來宣告及管理應用程式的 UI。例如,您可以在 XML 中宣告應用程式的預設版面配置,包括會顯示在應用程式中的元素及其屬性。接著,您可在執行階段為應用程式加入程式碼來修改螢幕物件的狀態,包括您在 XML 中宣告的物件。

+ + + +

在 XML 中宣告 UI 可讓您進一步將應用程式的顯示畫面與控制應用程式行為的程式碼區隔開來。這樣可將 UI 說明置於應用程式的程式碼外部,方便您修改或調整使用者介面說明,而不必修改並重新編譯原始碼。例如,您可以針對不同的螢幕方向、裝置螢幕大小和語言建立各種 XML 檔案。此外,在 XML 中宣告版面配置可協助以視覺效果呈現 UI 的結構,方便您進行除錯。針對上述優點,本文件著重於說明如何在 XML 中宣告版面配置。如果您想在執行階段啟動 View 物件,請參閱 {@link android.view.ViewGroup} 和 {@link android.view.View} 類別參考文件。 + +

+ +

一般來說,用於宣告 UI 元素的 XML 字彙會遵從類別與方法的結構和名稱,其中元素名稱對應至類別名稱,而屬性名稱則對應至方法。事實上,這些對應關係十分直接,您可以輕易猜出某個 XML 屬性對應的哪個類別方法,或某個類別對應的特定 XML 元素為何。不過請注意,並非所有字彙均為相同。在部分情況下,您會發現某些不同的名稱。例如,EditText 元素包含對應至 EditText.setText()text 屬性。 + +

+ +

提示:如要進一步瞭解各種版面配置類型,請參閱常見版面配置物件。 +您也可以參閱 Hello Views 教學指南,取得一系列說明如何建置各種版面配置的教學課程。 +

+ +

編寫 XML

+ +

您可以使用 Android 的 XML 字彙,快速設計 UI 版面配置和其中包含的螢幕元素,方法與採用 HTML 建立網頁相同 — 都需要建置一系列巢狀元素。

+ +

每個版面配置檔案均需包含 1 個根元素 (必須為 View 或 ViewGroup 物件)。定義根元素後,您就可以將額外的物件或小工具新增為子元素,逐步建置檢檢視階層視階層來定義您的版面配置。例如,以下是使用直向 {@link android.widget.LinearLayout} 以納入 +{@link android.widget.TextView} 和 {@link android.widget.Button} 的 XML 版面配置:

+
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:layout_width="match_parent"
+              android:layout_height="match_parent"
+              android:orientation="vertical" >
+    <TextView android:id="@+id/text"
+              android:layout_width="wrap_content"
+              android:layout_height="wrap_content"
+              android:text="Hello, I am a TextView" />
+    <Button android:id="@+id/button"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Hello, I am a Button" />
+</LinearLayout>
+
+ +

在 XML 中宣告版面配置後,請使用 .xml 副檔名將檔案儲存到 Android 專案的 res/layout/ 目錄中,藉此讓系統妥善加以編譯。 +

+ +

如要進一步瞭解 XML 版面配置檔案的語法,請參閱版面配置資源

+ +

載入 XML 資源

+ +

當您編譯應用程式時,系統會將所有 XML 版面配置檔案編入 +{@link android.view.View} 資源。在這種情況下,您必須透過 {@link android.app.Activity#onCreate(android.os.Bundle) Activity.onCreate()} 回呼,載入應用程式的程式碼中的版面配置資源,方法是呼叫 {@link android.app.Activity#setContentView(int) setContentView()},然後採用以下格式將其參考資源傳送到版面配置資源: +R.layout.layout_file_name。例如,假設您將 XML 版面配置儲存成 main_layout.xml,您就必須針對如下所示的 Activity 載入該 XML: + + + + +

+
+public void onCreate(Bundle savedInstanceState) {
+    super.onCreate(savedInstanceState);
+    setContentView(R.layout.main_layout);
+}
+
+ +

Android 架構會在 Activity 啟動時呼叫 Activity 中的 onCreate() 回呼方法 (如需生命週期相關資訊,請參閱 Activity)。 + + +

+ + +

屬性

+ +

所有 View 和 ViewGroup 物件均支援本身專用的各種 XML 屬性。部分屬性僅適用於 View 物件 (例如 TextView 可支援 textSize 屬性),不過可能延伸這個類別的 View 物件也會沿用這些屬性。由於某些屬性是沿用自 View 根類別,因此會成為所有 View 物件的常用屬性 (例如 id 屬性)。 + + + +而系統會將其他屬性視為「版面配置參數」,這些屬性可說明 View 物件的特定版面配置方向 (View 由物件的 ViewGroup 上層物件所定義)。 + +

+ +

ID

+ +

任何 View 物件都可能包含一個相關聯的整數 ID,可用於識別樹狀結構中的 View。在應用程式編寫期間,雖然這個 ID 通常是在 XML 版面配置檔案的 id 屬性中指派為字串,系統仍會將其解讀為整數。此為所有 View 物件常用的 XML 屬性 (由 {@link android.view.View} 類別所定義);您會經常使用的這項屬性。以下是 XML 標記中的 ID 語法: + + + + +

+
android:id="@+id/my_button"
+ +

字串開頭的 @ 符號可指示 XML 剖析器應剖析或展開 ID 字串的其餘部分,並且將其視為 ID 資源。 +加號符號 (+) 表示此為系統必須建立並加到資源 (R.java 檔案) 中的新資源名稱。 +Android 架構提供了多種其他 ID 資源。 +參照 Android 資源 ID 時,您不必加入 + 符號,但必須加入 android 套件命名空間,如下所示: +

+
android:id="@android:id/empty"
+

只要加入 android 套件命名空間,系統就會從 android.R 資源類別參照 ID,而不是從本機資源類別。 +

+ +

以下是透過應用程式建立檢視和參照的一般方法:

+
    +
  1. 在版面配置檔案中定義檢視/小工具,並為該檔案指派不重複 ID: +
    +<Button android:id="@+id/my_button"
    +        android:layout_width="wrap_content"
    +        android:layout_height="wrap_content"
    +        android:text="@string/my_button_text"/>
    +
    +
  2. +
  3. 接著,建立檢視物件執行個體,並從版面配置中擷取該執行個體 (通常使用 {@link android.app.Activity#onCreate(Bundle) onCreate()} 方法進行擷取): + +
    +Button myButton = (Button) findViewById(R.id.my_button);
    +
    +
  4. +
+

請務必在建立 {@link android.widget.RelativeLayout} 時為檢視物件定義 ID。在相關的版面配置中,同層級的檢視可將本身的版面配置與其他同層級的檢視建立關聯,而不重複 ID 正是這些版面配置的參照依據。 + +

+

整個樹狀結構的 ID 不必是不重複 ID,但您所搜尋樹狀結構部分的 ID 就必須是不重複 ID (由於您搜尋的部分通常是整個樹狀結構,因此建議您為其定義不重複 ID)。 + +

+ + +

版面配置參數

+ +

名為 layout_something 的 XML 版面配置屬性會為適用於所屬 ViewGroup 的 View 定義版面配置參數。 +

+ +

所有 ViewGroup 類別都會實作可延伸 {@link +android.view.ViewGroup.LayoutParams} 的巢狀類別。這個子類別包含定義下層檢視大小和位置的屬性類型,而這些屬性類型也適用於檢視群組。 + +如圖 1 所示,上層檢視群組為每個下層檢視 (包括下層檢視群組) 定義了版面配置參數。 +

+ + +

圖 1.檢視層級以及與每個檢視相關聯的版面配置參數。 +

+ +

請注意,所有 LayoutParams 子類別都包含用於設定值的專屬語法。 +而每個子元素都必須定義適用於其上層元素的 LayoutParams,即便子元素也會為其下層物件定義不同的 LayoutParams。 +

+ +

所有檢視群組均包含寬度和高度 (layout_width 和 +layout_height),而每個檢視都必須定義這些值。大多數 LayoutParams 還會包含選用的標界和邊框。 +

+ +

您可以依據測量結果指定寬度和高度 (您通常不必經常指定這些值)。 +在大多數情況下,您需使用以下任一常數設定寬度或高度: +

+ +
    +
  • wrap_content 可指示檢視自行將大小調整成其內容所需的尺寸。 +
  • +
  • match_parent (在 API 級別 8 之前稱為 fill_parent ) 可指示檢視自行將大小擴展成上層檢視群組允許的上限。 +
  • +
+ +

一般來說,我們不建議您使用絕對單位 (例如像素) 指定版面配置的寬度和高度。 +建議做法是使用相對測量單位 (例如依據密度的像素單位 ( +dp)、 wrap_content或 +match_parent指定這些值,這是因為這樣可協助確保應用程式在各種裝置螢幕大小中能保有最佳顯示效果。如需支援的測量類型的定義,請參閱可用資源。 + + + +

+ + +

版面配置位置

+

+ 檢視的形狀為矩形。檢視包含一個位置值 (以一組「水平」和「垂直」座標表示),以及兩個尺寸值 (以寬度和高度表示)。 + +位置和尺寸的單位均為像素。 + +

+ +

+ 您可以呼叫 {@link android.view.View#getLeft()} 和 {@link android.view.View#getTop()} 方法來擷取檢視的位置值。 +第一個方法會傳回顯示檢視的矩形區塊的水平座標 (X 座標); +第二個方法則會傳回顯示檢視的矩形區塊的垂直座標 (Y 座標)。 +這兩個方法都會傳回檢視相對於其上層項目的位置。 +例如,假設 getLeft() 傳回 20,表示該檢視位於其直屬上層項目左側邊緣向右平移 20 像素的位置。 + + +

+ +

+ 我們也提供了 {@link android.view.View#getRight()} 和 {@link android.view.View#getBottom()} 這兩個簡易方法,可用於避免不必要的運算程序。 + + 這些方法會傳回險是檢視的矩形區塊的右下角座標。 +例如,呼叫 {@link android.view.View#getRight()} 的用途與以下運算式類似:getLeft() + getWidth()。 + +

+ + +

大小、邊距和邊界

+

+ 檢視的大小是以寬度和高度表示。單一檢視會包含兩組寬度和高度值。 + +

+ +

+ 第一組值稱為「寬度測定值」和「高度測定值」, +可定義檢視在上層項目中的尺寸。 +您可以呼叫 {@link android.view.View#getMeasuredWidth()} 和 {@link android.view.View#getMeasuredHeight()} 來取得尺寸測定值。 + + +

+ +

+ 第二組值簡稱「寬度」和「高度」,或是「寬度描繪值」和「高度描繪值」, +可定義檢視在螢幕中的實際大小 (描繪期間以及版面配置之後)。 + +這些值可能 (但未必) 會與寬度和高度測量值不同。 +您可以呼叫 +{@link android.view.View#getWidth()} 和 {@link android.view.View#getHeight()} 來取得尺寸描繪值。 +

+ +

+ 測量檢視的尺寸時,系統會將檢視的邊距納入考量。邊距是指檢視的上、下、左、右部分 (單位為像素), + + 可用於將檢視內容偏移特定像素。 +例如,2 像素的左側邊距可將檢視內容由左側邊緣向右平移 2 像素。 +您可以使用 +{@link android.view.View#setPadding(int, int, int, int)} 方法來設定邊距,以及呼叫 +{@link android.view.View#getPaddingLeft()}、{@link android.view.View#getPaddingTop()}、 +{@link android.view.View#getPaddingRight()} 和 {@link android.view.View#getPaddingBottom()} 來查詢邊距。 +

+ +

+ 雖然檢視可定義邊距,但無法針對邊界提供任何支援。 +不過,檢視群組可提供這類支援。詳情請參閱 +{@link android.view.ViewGroup} 和 +{@link android.view.ViewGroup.MarginLayoutParams}。 +

+ +

如要進一步瞭解尺寸,請參閱尺寸值。 + +

+ + + + + + + + + + + +

常見版面配置

+ +

{@link android.view.ViewGroup} 類別的所有子類別均可以獨有方式顯示您在當中套疊的檢視。 +以下提供幾個 Android 平台內建的常見版面配置類型。 +

+ +

注意:雖然您可以將版面配置套疊在一起來符合您的 UI 設計,但建議您盡可能讓版面配置階層維持淺薄。 + +套疊的版面配置越少,版面配置的描繪速度就越快 (建議您使用寬的檢視階層,而不是深的檢視階層)。 +

+ + + + +
+

線性版面配置

+ +

這種版面配置可將其下層物件整合至單一水平或垂直列。如果視窗長度超過螢幕長度,線性版面配置就會建立捲軸方便使用者捲動畫面。 +

+
+ +
+

相對版面配置

+ +

這種版面配置可讓您指定下層物件之間的相對位置 (指定下層物件 A 位於下層物件 B 的左側),或指定下層物件與上層物件的相對位置 (指定下層物件緊貼上層物件的頂端)。 +

+
+ +
+

網頁檢視

+ +

這種版面配置可顯示網頁。

+
+ + + + +

使用配接器建置版面配置

+ +

如果您版面配置的內容為動態內容,而不是預先定義的內容,您可以在執行階段將子類別為 {@link android.widget.AdapterView} 的版面配置填入檢視中。 + +{@link android.widget.AdapterView} 類別的子類別會使用 {@link android.widget.Adapter} 將資料繫結至本身的版面配置。 +{@link android.widget.Adapter} 就如同是資料來源與 {@link android.widget.AdapterView} 版面配置的中間人 — {@link android.widget.Adapter} 會從陣列或資料庫查詢等來源擷取資料,然後將這些資料轉換成可加入 {@link android.widget.AdapterView} 版面配置的檢視。 + + +

+ +

以下是配接器支援的常見版面配置:

+ +
+

清單檢視

+ +

這種版面配置可顯示能夠捲動的單欄清單。

+
+ +
+

格狀檢視

+ +

這種版面配置可顯示能夠捲動的資料欄和資料列網格。

+
+ + + +

將資料填入配接器檢視

+ +

您可將 {@link android.widget.AdapterView} 執行個體繫結至 +{@link android.widget.Adapter} 以便從外部來源擷取資料,並建立可顯示所有資料的 {@link +android.view.View},藉此填入 {@link android.widget.AdapterView} (例如 {@link android.widget.ListView} 或 {@link android.widget.GridView}。 +

+ +

Android 提供數個 {@link android.widget.Adapter} 子類別可用於擷取不同資料類型以及為 {@link android.widget.AdapterView} 建置檢視。 +以下是兩種常見的配接器: +

+ +
+
{@link android.widget.ArrayAdapter}
+
如果您的資料來源是陣列,請使用這個配接器。在預設情況下,{@link +android.widget.ArrayAdapter} 會針對每個陣列項目呼叫 {@link +java.lang.Object#toString()} 並取代 {@link +android.widget.TextView} 中的內容,藉此為每個陣列項目建立檢視。 +

例如,如果您想在 {@link +android.widget.ListView} 中顯示一系列字串,請使用建構函式為每個字串和字串陣列指定版面配置,藉此初始化新的 {@link android.widget.ArrayAdapter}: +

+
+ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
+        android.R.layout.simple_list_item_1, myStringArray);
+
+

這個建構函式的引數包括:

+
    +
  • 您的應用程式 {@link android.content.Context}
  • +
  • 含有陣列中所有字串的 {@link android.widget.TextView} 的版面配置
  • +
  • 字串陣列
  • +
+

接著,針對您的 {@link android.widget.ListView} 呼叫 +{@link android.widget.ListView#setAdapter setAdapter()}:

+
+ListView listView = (ListView) findViewById(R.id.listview);
+listView.setAdapter(adapter);
+
+ +

如要自訂每個項目的外觀,您可以針對陣列中的物件覆寫 {@link +java.lang.Object#toString()} 方法。或者,如果想為 {@link android.widget.TextView} 以外的所有項目建立檢視 (例如,如果您想為所有陣列項目建立 {@link android.widget.ImageView}),請延伸 {@link +android.widget.ArrayAdapter} 類別並覆寫 {@link android.widget.ArrayAdapter#getView +getView()},以傳回您想為所有項目建立的檢視類型。 + +

+ +
+ +
{@link android.widget.SimpleCursorAdapter}
+
如果您的資料來自 {@link android.database.Cursor},請使用這個配接器。使用 +{@link android.widget.SimpleCursorAdapter} 時,您必須指定要用於 {@link android.database.Cursor} 中所有資料列的版面配置,以及指定要將 {@link android.database.Cursor} 中的哪些資料欄插入哪些版面配置檢視。 + +例如,如果您想建立一份列出使用者名稱和電話號碼的清單,請執行查詢來傳回 {@link +android.database.Cursor},其中包含一個列出每位使用者的資料列,以及多個列出名稱和電話號碼的資料欄。 + +接著,請建立一個字串陣列以便從 {@link +android.database.Cursor} 指定您想在版面配置中列出每項結果的資料欄,以及建立一個整數陣列為每個資料欄指定對應的值: +

+
+String[] fromColumns = {ContactsContract.Data.DISPLAY_NAME,
+                        ContactsContract.CommonDataKinds.Phone.NUMBER};
+int[] toViews = {R.id.display_name, R.id.phone_number};
+
+

當您啟動 {@link android.widget.SimpleCursorAdapter} 時,請傳送用於顯示所有結果的版面配置、包含結果的 {@link android.database.Cursor},以及下列兩個陣列: +

+
+SimpleCursorAdapter adapter = new SimpleCursorAdapter(this,
+        R.layout.person_name_and_number, cursor, fromColumns, toViews, 0);
+ListView listView = getListView();
+listView.setAdapter(adapter);
+
+

{@link android.widget.SimpleCursorAdapter} 隨即會將每個 {@code +fromColumns} 項目插入相對應的 {@code toViews} 檢視,藉此利用提供的版面配置為 +{@link android.database.Cursor} 中的所有資料列建立專屬檢視。

.
+
+ + +

如果您在應用程式的效期內更改配接器讀取的底層資料,您就必須呼叫 {@link android.widget.ArrayAdapter#notifyDataSetChanged()}。 +這樣會通知附加的檢視由於資料已變更,因此需進行重新整理。 +

+ + + +

處理點擊事件

+ +

只要實作 {@link android.widget.AdapterView.OnItemClickListener} 介面,即可回應 {@link android.widget.AdapterView} 中所有項目的點擊事件。 +例如:

+ +
+// Create a message handling object as an anonymous class.
+private OnItemClickListener mMessageClickedHandler = new OnItemClickListener() {
+    public void onItemClick(AdapterView parent, View v, int position, long id) {
+        // Do something in response to the click
+    }
+};
+
+listView.setOnItemClickListener(mMessageClickedHandler);
+
+ + + diff --git a/docs/html-intl/intl/zh-tw/guide/topics/ui/dialogs.jd b/docs/html-intl/intl/zh-tw/guide/topics/ui/dialogs.jd new file mode 100644 index 0000000000000000000000000000000000000000..b0ae12ea3a19874410df1a81a5e68faa4b82bd91 --- /dev/null +++ b/docs/html-intl/intl/zh-tw/guide/topics/ui/dialogs.jd @@ -0,0 +1,798 @@ +page.title=對話方塊 +page.tags=alertdialog,dialogfragment + +@jd:body + + + +
+
+

本文件內容

+
    +
  1. 建立對話方塊片段
  2. +
  3. 建置快訊對話方塊 +
      +
    1. 加入按鈕
    2. +
    3. 加入清單
    4. +
    5. 建立自訂版面配置
    6. +
    +
  4. +
  5. 將事件傳回對話方塊的主控件
  6. +
  7. 顯示對話方塊
  8. +
  9. 顯示全螢幕對話方塊或內嵌片段 +
      +
    1. 針對大型螢幕將 Activity 顯示為對話方塊
    2. +
    +
  10. +
  11. 關閉對話方塊
  12. +
+ +

重要類別

+
    +
  1. {@link android.app.DialogFragment}
  2. +
  3. {@link android.app.AlertDialog}
  4. +
+ +

另請參閱

+
    +
  1. 對話方塊設計指南
  2. +
  3. 挑選器 (日期/時間對話方塊)
  4. +
+
+
+ +

對話方塊是一種小型視窗,可提示使用者做出決定或輸入額外資訊。 +對話視窗並不會佔滿整個螢幕,而且通常是供強制回應事件使用,這種事件會要求使用者必須先完成特定動作,才能繼續下一步。 +

+ +
+

設計對話方塊

+

如要瞭解如何設計對話方塊,包括建議使用的設計語言,請參閱對話方塊設計指南。 +

+
+ + + +

{@link android.app.Dialog} 類別是對話方塊的基礎類別,但請避免直接啟動 {@link android.app.Dialog}。建議您改用下列其中一個子類別: + +

+
+
{@link android.app.AlertDialog}
+
這種對話方塊可以顯示一個標題、最多三個按鈕、一系列可選取項目或一個自訂版面配置。 +
+
{@link android.app.DatePickerDialog} 或 {@link android.app.TimePickerDialog}
+
這種對話方塊會提供預先定義的 UI,可讓使用者選取日期或時間。
+
+ + + +

上述類別可定義對話方塊的樣式和結構,但建議您使用 {@link android.support.v4.app.DialogFragment} 做為對話方塊的容器。 +{@link android.support.v4.app.DialogFragment} 類別可提供您所需的所有控制項,方便您建立對話方塊及管理對話方塊的外觀,如此您不必對 {@link android.app.Dialog} 物件呼叫方法。 + + +

+ +

使用 {@link android.support.v4.app.DialogFragment} 管理對話方塊可確保對話方塊能正確控制生命週期事件,例如使用者點擊 [返回] 按鈕或旋轉螢幕。 + +此外,{@link +android.support.v4.app.DialogFragment} 類別還能像傳統的 {@link +android.support.v4.app.Fragment} 一樣讓您重複使用對話方塊的 UI,做為大型 UI 中的可嵌入元件 (例如當您想讓對話方塊使用者介面在大型和小型螢幕上呈現不同外觀時)。 + +

+ +

本指南的以下各節說明如何搭配 {@link android.app.AlertDialog} 物件使用 {@link +android.support.v4.app.DialogFragment}。 +如果您是想建立日期或時間挑選器,請改為參閱挑選器指南。 +

+ +

注意:{@link android.app.DialogFragment} 類別最初是在 Android 3.0 (API 級別 11) 中導入,因此本文說明如何使用支援程式庫提供的 {@link +android.support.v4.app.DialogFragment} 類別。 + +只要將這個程式庫加到您的應用程式,即可在搭載 Android 1.6 以上版本的裝置上使用 {@link android.support.v4.app.DialogFragment} 以及多種其他 API。 + +如果您應用程式支援的最低版本為 API 級別 11 以上版本,您就可以使用架構版本的 {@link +android.app.DialogFragment},但請注意,本文中提供的連結是用於取得支援程式庫 API。 + +使用支援程式庫時,請務必匯入 android.support.v4.app.DialogFragment 類別,而「不是」android.app.DialogFragment。 + +

+ + +

建立對話方塊片段

+ +

如果想建立多種對話方塊設計 — 包括自訂版面配置和對話方塊設計指南中所述的對話方塊設計 — 只要延伸 +{@link android.support.v4.app.DialogFragment} 然後利用 +{@link android.support.v4.app.DialogFragment#onCreateDialog +onCreateDialog()} 回呼方法建立 {@link android.app.AlertDialog} 即可。 + +

+ +

例如,假設您利用 {@link android.support.v4.app.DialogFragment} 管理以下的基本 +{@link android.app.AlertDialog}:

+ +
+public class FireMissilesDialogFragment extends DialogFragment {
+    @Override
+    public Dialog onCreateDialog(Bundle savedInstanceState) {
+        // Use the Builder class for convenient dialog construction
+        AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+        builder.setMessage(R.string.dialog_fire_missiles)
+               .setPositiveButton(R.string.fire, new DialogInterface.OnClickListener() {
+                   public void onClick(DialogInterface dialog, int id) {
+                       // FIRE ZE MISSILES!
+                   }
+               })
+               .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
+                   public void onClick(DialogInterface dialog, int id) {
+                       // User cancelled the dialog
+                   }
+               });
+        // Create the AlertDialog object and return it
+        return builder.create();
+    }
+}
+
+ +
+ +

圖 1. +包含一段訊息和兩個動作按鈕的對話方塊。

+
+ +

在這種情況下,如果您建立這個類別執行個體,然後對該物件呼叫 {@link +android.support.v4.app.DialogFragment#show show()},就會顯示如圖 1 所示的對話方塊。 +

+ +

如要進一步瞭解如何使用 {@link android.app.AlertDialog.Builder} API 建立對話方塊,請參閱下一節。 +

+ +

視對話方塊的複雜程度而定,您可以在 {@link android.support.v4.app.DialogFragment} 中實作多種其他回呼方法,包括所有的基本片段生命週期方法。 + + + + + + + +

建置快訊對話方塊

+ + +

{@link android.app.AlertDialog} 通常是您需要使用的唯一對話方塊類別,可讓您建置多種對話方塊設計。如圖 2 所示,快訊對話方塊是由 3 個區塊組合而成: + +

+ +
+ +

圖 2.對話方塊的版面配置。

+
+ +
    +
  1. 標題 +

    此為選用區塊;只有在內容區域提供詳細訊息、一份清單或自訂版面配置時,您才需要使用標題。 +如果您想提供一段簡短訊息或一個簡易問題 (如圖 1 所示的對話方塊),您就不必使用標題。 +

  2. +
  3. 內容區塊 +

    這個區塊可以顯示一段訊息、一份清單或其他自訂版面配置。

  4. +
  5. 動作按鈕 +

    單一對話方塊最多只能包含 3 個動作按鈕。

  6. +
+ +

{@link android.app.AlertDialog.Builder} 類別提供的 API 可讓您建立包含這些內容類型 (包括自訂版面配置) 的 {@link android.app.AlertDialog}。 + +

+ +

如何建置 {@link android.app.AlertDialog}:

+ +
+// 1. Instantiate an {@link android.app.AlertDialog.Builder} with its constructor
+AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+
+// 2. Chain together various setter methods to set the dialog characteristics
+builder.setMessage(R.string.dialog_message)
+       .setTitle(R.string.dialog_title);
+
+// 3. Get the {@link android.app.AlertDialog} from {@link android.app.AlertDialog.Builder#create()}
+AlertDialog dialog = builder.create();
+
+ +

以下主題說明如何定義多種採用 {@link android.app.AlertDialog.Builder} 類別的對話方塊屬性。 +

+ + + + +

加入按鈕

+ +

想要加入如圖 2 所示的動作按鈕,請呼叫 {@link android.app.AlertDialog.Builder#setPositiveButton setPositiveButton()} 和 +{@link android.app.AlertDialog.Builder#setNegativeButton setNegativeButton()} 方法: +

+ +
+AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+// Add the buttons
+builder.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
+           public void onClick(DialogInterface dialog, int id) {
+               // User clicked OK button
+           }
+       });
+builder.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
+           public void onClick(DialogInterface dialog, int id) {
+               // User cancelled the dialog
+           }
+       });
+// Set other dialog properties
+...
+
+// Create the AlertDialog
+AlertDialog dialog = builder.create();
+
+ +

set...Button() 方法會要求字串資源提供的按鈕標題,以及可定義使用者點擊按鈕後需要完成的動作的{@link android.content.DialogInterface.OnClickListener}。 + + +

+ +

您可以加入的動作按鈕分為 3 種:

+
+
正面
+
這種按鈕的用途是接受及繼續進行特定動作 (「確定」按鈕)。
+
負面
+
這種按鈕的用途是取消動作。
+
中立
+
如果使用者不想繼續進行特定動作,但並非要取消動作,請使用這種按鈕。 +這種按鈕會顯示在正面和負面按鈕之間。 +範例:[稍後提醒我] 按鈕。
+
+ +

您可以將以上其中一種按鈕加入 {@link +android.app.AlertDialog};換句話說,您最多只能加入一個「正面」按鈕。

+ + + +
+ +

圖 3. +包含一個標題和一份清單的對話方塊。

+
+ +

加入清單

+ +

{@link android.app.AlertDialog} API 可提供以下 3 種清單類型:

+
    +
  • 傳統的單一選項清單
  • +
  • 永續性的單一選項清單 (圓形按鈕)
  • +
  • 永續性的多重選項清單 (核取方塊)
  • +
+ +

想要建立如圖 3 所示的單一選項清單,請使用 {@link android.app.AlertDialog.Builder#setItems setItems()} 方法: +

+ +
+@Override
+public Dialog onCreateDialog(Bundle savedInstanceState) {
+    AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+    builder.setTitle(R.string.pick_color)
+           .setItems(R.array.colors_array, new DialogInterface.OnClickListener() {
+               public void onClick(DialogInterface dialog, int which) {
+               // The 'which' argument contains the index position
+               // of the selected item
+           }
+    });
+    return builder.create();
+}
+
+ +

由於清單會出現在對話方塊的內容區塊中,因此對話方塊無法同時顯示訊息和清單,而且您必須使用 {@link android.app.AlertDialog.Builder#setTitle setTitle()} 為對話方塊設定標題。 + +如要指定清單列出的項目,請呼叫 {@link +android.app.AlertDialog.Builder#setItems setItems()} 來傳送陣列。或者,您也可以使用 {@link +android.app.AlertDialog.Builder#setAdapter setAdapter()} 指定清單。 + +如此一來,您就可以使用 {@link android.widget.ListAdapter} 為清單加入動態資料 (例如資料庫中的動態資料)。 +

+ +

如果您選擇讓清單採用 {@link android.widget.ListAdapter},請一律使用 {@link android.support.v4.content.Loader} 以非同步方式載入內容。 + +詳情請參閱使用配接器建置版面配置載入器指南。 + + +

+ +

注意:在預設情況下,輕觸某個清單項目會關閉對話方塊,除非您使用以下的永續性選項清單。 +

+ +
+ +

圖 4. +多重選項清單。

+
+ + +

加入永續性的多重選項或單一選項清單

+ +

如要加入多重選項 (核取方塊) 或單一選項 (圓形按鈕),請使用 +{@link android.app.AlertDialog.Builder#setMultiChoiceItems(Cursor,String,String, +DialogInterface.OnMultiChoiceClickListener) setMultiChoiceItems()} 或 +{@link android.app.AlertDialog.Builder#setSingleChoiceItems(int,int,DialogInterface.OnClickListener) +setSingleChoiceItems()} 方法。 +

+ +

例如,以下說明如何建立如圖 4 所示能夠在 {@link java.util.ArrayList} 中儲存所選項目的多重選項清單: + +

+ +
+@Override
+public Dialog onCreateDialog(Bundle savedInstanceState) {
+    mSelectedItems = new ArrayList();  // Where we track the selected items
+    AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+    // Set the dialog title
+    builder.setTitle(R.string.pick_toppings)
+    // Specify the list array, the items to be selected by default (null for none),
+    // and the listener through which to receive callbacks when items are selected
+           .setMultiChoiceItems(R.array.toppings, null,
+                      new DialogInterface.OnMultiChoiceClickListener() {
+               @Override
+               public void onClick(DialogInterface dialog, int which,
+                       boolean isChecked) {
+                   if (isChecked) {
+                       // If the user checked the item, add it to the selected items
+                       mSelectedItems.add(which);
+                   } else if (mSelectedItems.contains(which)) {
+                       // Else, if the item is already in the array, remove it 
+                       mSelectedItems.remove(Integer.valueOf(which));
+                   }
+               }
+           })
+    // Set the action buttons
+           .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
+               @Override
+               public void onClick(DialogInterface dialog, int id) {
+                   // User clicked OK, so save the mSelectedItems results somewhere
+                   // or return them to the component that opened the dialog
+                   ...
+               }
+           })
+           .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
+               @Override
+               public void onClick(DialogInterface dialog, int id) {
+                   ...
+               }
+           });
+
+    return builder.create();
+}
+
+ +

雖然傳統清單和包含圓形按鈕的清單都可提供「單選」動作,但如果您想保留使用者的選擇,請使用 {@link +android.app.AlertDialog.Builder#setSingleChoiceItems(int,int,DialogInterface.OnClickListener) +setSingleChoiceItems()}。也就是說,如果您想讓對話方塊再次開啟時顯示使用者目前所選的選項,請建立包含圓形按鈕的清單。 + + +

+ + + + + +

建立自訂版面配置

+ +
+ +

圖 5.自訂的對話方塊版面配置。

+
+ +

如果您想自訂對話方塊的版面配置,請建立所需的版面配置,然後對 {@link +android.app.AlertDialog.Builder} 物件呼叫 {@link +android.app.AlertDialog.Builder#setView setView()},將新建的版面配置加到 {@link android.app.AlertDialog}。 +

+ +

自訂版面配置預設會佔滿整個對話方塊視窗,但您仍可使用 {@link android.app.AlertDialog.Builder} 方法在其中加入按鈕和標題。 +

+ +

例如,以下是圖 5 所示的對話方塊版面配置檔案:

+ +

res/layout/dialog_signin.xml

+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content">
+    <ImageView
+        android:src="@drawable/header_logo"
+        android:layout_width="match_parent"
+        android:layout_height="64dp"
+        android:scaleType="center"
+        android:background="#FFFFBB33"
+        android:contentDescription="@string/app_name" />
+    <EditText
+        android:id="@+id/username"
+        android:inputType="textEmailAddress"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="16dp"
+        android:layout_marginLeft="4dp"
+        android:layout_marginRight="4dp"
+        android:layout_marginBottom="4dp"
+        android:hint="@string/username" />
+    <EditText
+        android:id="@+id/password"
+        android:inputType="textPassword"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="4dp"
+        android:layout_marginLeft="4dp"
+        android:layout_marginRight="4dp"
+        android:layout_marginBottom="16dp"
+        android:fontFamily="sans-serif"
+        android:hint="@string/password"/>
+</LinearLayout>
+
+ +

提示:在預設情況下,如果您設定 {@link android.widget.EditText} 元素使用 {@code "textPassword"} 輸入類型,字型系列就會設為等寬字型,因此您必須將該元素的字型系列變更為 {@code "sans-serif"},讓文字欄位採用對應的字型。 + + +

+ +

如要擴大 {@link android.support.v4.app.DialogFragment} 中的版面配置,請透過 {@link android.app.Activity#getLayoutInflater()} 取得 {@link android.view.LayoutInflater},然後呼叫{@link android.view.LayoutInflater#inflate inflate()} (其中的第一個參數為版面配置資源 ID,第二個參數則是版面配置的上層檢視)。接著,您可以呼叫 {@link android.app.AlertDialog#setView setView()} 來取代對話方塊中的版面配置。 + + + + + +

+ +
+@Override
+public Dialog onCreateDialog(Bundle savedInstanceState) {
+    AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+    // Get the layout inflater
+    LayoutInflater inflater = getActivity().getLayoutInflater();
+
+    // Inflate and set the layout for the dialog
+    // Pass null as the parent view because its going in the dialog layout
+    builder.setView(inflater.inflate(R.layout.dialog_signin, null))
+    // Add action buttons
+           .setPositiveButton(R.string.signin, new DialogInterface.OnClickListener() {
+               @Override
+               public void onClick(DialogInterface dialog, int id) {
+                   // sign in the user ...
+               }
+           })
+           .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
+               public void onClick(DialogInterface dialog, int id) {
+                   LoginDialogFragment.this.getDialog().cancel();
+               }
+           });      
+    return builder.create();
+}
+
+ +
+

提示:如果您想自訂對話方塊,請改為將 {@link android.app.Activity} 顯示為對話方塊,而不是使用 {@link android.app.Dialog} API。 + +方法很簡單,只要建立 Activity 然後在 {@code +<activity>} 宣示說明元素中將其主題設為 +{@link android.R.style#Theme_Holo_Dialog Theme.Holo.Dialog} 即可: +

+ +
+<activity android:theme="@android:style/Theme.Holo.Dialog" >
+
+

這樣一來,Activity 就會顯示在對話方塊視窗,而不是以全螢幕模式顯示。

+
+ + + +

將事件傳回對話方塊的主控件

+ +

使用者在對話方塊中輕觸任一動作按鈕或從清單中選取一個項目後,您的 {@link android.support.v4.app.DialogFragment} 可能會自行執行必要動作,不過,您通常會想將事件傳送到 Activity 或對話方塊開啟的片段。 + + +如要這麼做,請透過每個點擊事件類型適用的方法定義介面,然後在會接收對話方塊的動作事件的主機元件中實作該介面。 + +

+ +

例如,以下是定義用於將事件傳回主機 Activity 的介面的 {@link android.support.v4.app.DialogFragment}: +

+ +
+public class NoticeDialogFragment extends DialogFragment {
+    
+    /* The activity that creates an instance of this dialog fragment must
+     * implement this interface in order to receive event callbacks.
+     * Each method passes the DialogFragment in case the host needs to query it. */
+    public interface NoticeDialogListener {
+        public void onDialogPositiveClick(DialogFragment dialog);
+        public void onDialogNegativeClick(DialogFragment dialog);
+    }
+    
+    // Use this instance of the interface to deliver action events
+    NoticeDialogListener mListener;
+    
+    // Override the Fragment.onAttach() method to instantiate the NoticeDialogListener
+    @Override
+    public void onAttach(Activity activity) {
+        super.onAttach(activity);
+        // Verify that the host activity implements the callback interface
+        try {
+            // Instantiate the NoticeDialogListener so we can send events to the host
+            mListener = (NoticeDialogListener) activity;
+        } catch (ClassCastException e) {
+            // The activity doesn't implement the interface, throw exception
+            throw new ClassCastException(activity.toString()
+                    + " must implement NoticeDialogListener");
+        }
+    }
+    ...
+}
+
+ +

代管對話方塊的 Activity 會建立包含對話方塊片段的建構函式的對話方塊執行個體,以及透過您實作的 {@code NoticeDialogListener} 介面接收對話方塊的事件: + +

+ +
+public class MainActivity extends FragmentActivity
+                          implements NoticeDialogFragment.NoticeDialogListener{
+    ...
+    
+    public void showNoticeDialog() {
+        // Create an instance of the dialog fragment and show it
+        DialogFragment dialog = new NoticeDialogFragment();
+        dialog.show(getSupportFragmentManager(), "NoticeDialogFragment");
+    }
+
+    // The dialog fragment receives a reference to this Activity through the
+    // Fragment.onAttach() callback, which it uses to call the following methods
+    // defined by the NoticeDialogFragment.NoticeDialogListener interface
+    @Override
+    public void onDialogPositiveClick(DialogFragment dialog) {
+        // User touched the dialog's positive button
+        ...
+    }
+
+    @Override
+    public void onDialogNegativeClick(DialogFragment dialog) {
+        // User touched the dialog's negative button
+        ...
+    }
+}
+
+ +

由於主機 Activity 會實作 {@code NoticeDialogListener} — 如上所述的 {@link android.support.v4.app.Fragment#onAttach onAttach()} 回呼方法會強制執行這個 Activity — 因此對話方塊片段可以使用介面回呼方法將點擊事件傳送到 Activity: + + +

+ +
+public class NoticeDialogFragment extends DialogFragment {
+    ...
+
+    @Override
+    public Dialog onCreateDialog(Bundle savedInstanceState) {
+        // Build the dialog and set up the button click handlers
+        AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+        builder.setMessage(R.string.dialog_fire_missiles)
+               .setPositiveButton(R.string.fire, new DialogInterface.OnClickListener() {
+                   public void onClick(DialogInterface dialog, int id) {
+                       // Send the positive button event back to the host activity
+                       mListener.onDialogPositiveClick(NoticeDialogFragment.this);
+                   }
+               })
+               .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
+                   public void onClick(DialogInterface dialog, int id) {
+                       // Send the negative button event back to the host activity
+                       mListener.onDialogNegativeClick(NoticeDialogFragment.this);
+                   }
+               });
+        return builder.create();
+    }
+}
+
+ + + +

顯示對話方塊

+ +

如果想顯示對話方塊,請建立 {@link +android.support.v4.app.DialogFragment} 執行個體並呼叫 {@link android.support.v4.app.DialogFragment#show +show()} 來傳送對話方塊片段的 {@link android.support.v4.app.FragmentManager} 和標籤名稱。 +

+ +

如要取得 {@link android.support.v4.app.FragmentManager},請呼叫 +{@link android.support.v4.app.FragmentActivity} 中的 +{@link android.support.v4.app.FragmentActivity#getSupportFragmentManager()} 或 {@link +android.support.v4.app.Fragment} 中的 {@link +android.support.v4.app.Fragment#getFragmentManager()}。例如:

+ +
+public void confirmFireMissiles() {
+    DialogFragment newFragment = new FireMissilesDialogFragment();
+    newFragment.show(getSupportFragmentManager(), "missiles");
+}
+
+ +

第二個引數 {@code "missiles"} 是不重複的標籤名稱,可供系統視需要用於儲存及還原片段狀態。 +此外,該標籤還可讓您呼叫 + {@link android.support.v4.app.FragmentManager#findFragmentByTag +findFragmentByTag()} 來取得片段的控點。

+ + + + +

顯示全螢幕對話方塊或內嵌片段

+ +

您可能會想建立 UI 設計,讓 UI 片段在某些情況下於該設計中顯示為對話方塊,而不是其他設計中的全螢幕或內嵌片段 (例如視裝置採用螢幕大小而定)。 + +{@link android.support.v4.app.DialogFragment} 類別能為您提供這樣的彈性,這是因為該類別仍可做為可嵌入的 {@link +android.support.v4.app.Fragment} 使用。 +

+ +

不過,您無法使用 {@link android.app.AlertDialog.Builder AlertDialog.Builder} 或其他 {@link android.app.Dialog} 物件來建置這種對話方塊。 +如果您想建立可嵌入的 {@link android.support.v4.app.DialogFragment},請務必在版面配置中定義對話方塊的 UI,然後透過 {@link android.support.v4.app.DialogFragment#onCreateView +onCreateView()} 回呼載入版面配置。 + + +

+ +

以下的 {@link android.support.v4.app.DialogFragment} 範例可顯示為對話方塊或可嵌入片段 (使用名為 purchase_items.xml 的版面配置): +

+ +
+public class CustomDialogFragment extends DialogFragment {
+    /** The system calls this to get the DialogFragment's layout, regardless
+        of whether it's being displayed as a dialog or an embedded fragment. */
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+            Bundle savedInstanceState) {
+        // Inflate the layout to use as dialog or embedded fragment
+        return inflater.inflate(R.layout.purchase_items, container, false);
+    }
+  
+    /** The system calls this only when creating the layout in a dialog. */
+    @Override
+    public Dialog onCreateDialog(Bundle savedInstanceState) {
+        // The only reason you might override this method when using onCreateView() is
+        // to modify any dialog characteristics. For example, the dialog includes a
+        // title by default, but your custom layout might not need it. So here you can
+        // remove the dialog title, but you must call the superclass to get the Dialog.
+        Dialog dialog = super.onCreateDialog(savedInstanceState);
+        dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
+        return dialog;
+    }
+}
+
+ +

以下的程式碼可根據螢幕大小,決定要將片段顯示為對話方塊或全螢幕 UI: +

+ +
+public void showDialog() {
+    FragmentManager fragmentManager = getSupportFragmentManager();
+    CustomDialogFragment newFragment = new CustomDialogFragment();
+    
+    if (mIsLargeLayout) {
+        // The device is using a large layout, so show the fragment as a dialog
+        newFragment.show(fragmentManager, "dialog");
+    } else {
+        // The device is smaller, so show the fragment fullscreen
+        FragmentTransaction transaction = fragmentManager.beginTransaction();
+        // For a little polish, specify a transition animation
+        transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
+        // To make it fullscreen, use the 'content' root view as the container
+        // for the fragment, which is always the root view for the activity
+        transaction.add(android.R.id.content, newFragment)
+                   .addToBackStack(null).commit();
+    }
+}
+
+ +

如要進一步瞭解如何進行片段交易,請參閱片段指南。 +

+ +

在本範例中,mIsLargeLayout 布林值可指定是否要讓目前的裝置採用應用程式的大型版面配置設計 (藉此將這個片段顯示為對話方塊,而不是全螢幕)。 + +設定這種布林值的最佳做法,是使用替代資源值為不同裝置大小宣告布林資源值。 + +例如,以下是適用於不同螢幕大小的 2 種布林資源版本: +

+ +

res/values/bools.xml

+
+<!-- Default boolean values -->
+<resources>
+    <bool name="large_layout">false</bool>
+</resources>
+
+ +

res/values-large/bools.xml

+
+<!-- Large screen boolean values -->
+<resources>
+    <bool name="large_layout">true</bool>
+</resources>
+
+ +

接著,您可以在呼叫 Activity 的 {@link android.app.Activity#onCreate onCreate()} 方法時初始化 {@code mIsLargeLayout} 值: +

+ +
+boolean mIsLargeLayout;
+
+@Override
+public void onCreate(Bundle savedInstanceState) {
+    super.onCreate(savedInstanceState);
+    setContentView(R.layout.activity_main);
+
+    mIsLargeLayout = getResources().getBoolean(R.bool.large_layout);
+}
+
+ + + +

針對大型螢幕將 Activity 顯示為對話方塊

+ +

您可以針對大型螢幕將 {@link android.app.Activity} 顯示為對話方塊,而不是將對話方塊顯示為全螢幕 UI,藉此達到相同結果。 + +您要採用的方法取決於您的應用程式設計,但如果您的應用程式是針對小型螢幕進行設計,將 Activity 顯示為對話方塊通常就能獲得良好效果,而如果您想針對平板電腦改善使用者體驗,請將生命週期較短的 Activity 顯示為對話方塊。 + + +

+ +

如果只想針對大型螢幕將 Activity 顯示為對話方塊,請為 {@code +<activity>} 宣示元素套用 {@link android.R.style#Theme_Holo_DialogWhenLarge Theme.Holo.DialogWhenLarge} 主題: + +

+ +
+<activity android:theme="@android:style/Theme.Holo.DialogWhenLarge" >
+
+ +

如要進一步瞭解如何運用主題設定 Activity 的樣式,請參閱樣式和主題指南。

+ + + +

關閉對話方塊

+ +

只要使用者輕觸任何用 {@link android.app.AlertDialog.Builder} 建立的動作按鈕,系統就會為您關閉對話方塊。 +

+ +

此外,系統也會在使用者輕觸對話方塊清單中的項目時,關閉對話方塊,但如果清單採用圓形按鈕或核取方塊,系統就不會關閉對話方塊。 +不過,您可以對 {@link +android.support.v4.app.DialogFragment} 呼叫 {@link android.support.v4.app.DialogFragment#dismiss()},藉此手動關閉對話方塊。 +

+ +

如果您需要在對話方塊關閉時執行特定動作,請在 {@link +android.support.v4.app.DialogFragment} 中實作 {@link +android.support.v4.app.DialogFragment#onDismiss onDismiss()} 方法。 +

+ +

此外,您還可以「取消」對話方塊。這種特殊事件可用於指明使用者尚未完成工作便關閉對話方塊。 +如果使用者按下 [返回] 按鈕、輕觸對話方塊以外的螢幕畫面,或如果您對 {@link +android.app.Dialog} 呼叫 {@link android.app.Dialog#cancel()} (例如藉此回應對話方塊中的 [取消] 按鈕),就會發生這個事件。 + +

+ +

如上方範例所示,您可以在 {@link +android.support.v4.app.DialogFragment} 類別中實作 {@link android.support.v4.app.DialogFragment#onCancel onCancel()} 來回應取消事件。 +

+ +

注意:系統會在發生會呼叫 {@link android.support.v4.app.DialogFragment#onCancel onCancel()} 回呼的事件時呼叫 +{@link android.support.v4.app.DialogFragment#onDismiss onDismiss()}。 +不過,如果您呼叫 {@link android.app.Dialog#dismiss Dialog.dismiss()} 或 {@link +android.support.v4.app.DialogFragment#dismiss DialogFragment.dismiss()},則系統會呼叫 {@link android.support.v4.app.DialogFragment#onDismiss onDismiss()}「而不是」{@link android.support.v4.app.DialogFragment#onCancel onCancel()}。 + + +因此,我們通常建議您在使用者點擊您對話方塊中的「正面」按鈕以便將對話方塊從檢視移除時,呼叫 {@link android.support.v4.app.DialogFragment#dismiss dismiss()}。 + +

+ + diff --git a/docs/html-intl/intl/zh-tw/guide/topics/ui/menus.jd b/docs/html-intl/intl/zh-tw/guide/topics/ui/menus.jd new file mode 100644 index 0000000000000000000000000000000000000000..be1fa7f0dfdab6f9e4efb9881d424524a4f5be57 --- /dev/null +++ b/docs/html-intl/intl/zh-tw/guide/topics/ui/menus.jd @@ -0,0 +1,1031 @@ +page.title=選單 +parent.title=使用者介面 +parent.link=index.html +@jd:body + +
+
+

本文件內容

+
    +
  1. 在 XML 中定義選單
  2. +
  3. 建立選項選單 +
      +
    1. 處理點擊事件
    2. +
    3. 在執行階段變更選單項目
    4. +
    +
  4. +
  5. 建立內容關聯選單 +
      +
    1. 建立浮動內容選單
    2. +
    3. 使用內容關聯動作模式
    4. +
    +
  6. +
  7. 建立彈出式選單 +
      +
    1. 處理點擊事件
    2. +
    +
  8. +
  9. 建立選單群組 +
      +
    1. 使用可核取的選單項目
    2. +
    +
  10. +
  11. 根據意圖新增選單項目 +
      +
    1. 允許將 Activity 新增至其他選單
    2. +
    +
  12. +
+ +

重要類別

+
    +
  1. {@link android.view.Menu}
  2. +
  3. {@link android.view.MenuItem}
  4. +
  5. {@link android.view.ContextMenu}
  6. +
  7. {@link android.view.ActionMode}
  8. +
+ +

另請參閱

+
    +
  1. 動作列
  2. +
  3. 選單資源
  4. +
  5. 和選單按鈕說再見 +
  6. +
+
+
+ +

選單在許多類型的應用程式中都是常見的使用者介面元件。為提供熟悉且一致的使用者體驗,您應該使用 +{@link android.view.Menu} API 來呈現 Activity 中的使用者動作與其他選項。 +

+ +

從 Android 3.0 (API 級別 11) 開始,提供 Android 的裝置不再需要提供專屬的「選單」按鈕。 +有此變更之後,Android 應用程式應可脫離對傳統有 6 個項目的選單面板的依賴,改為提供動作列來呈現一般的使用者動作。 + +

+ +

雖然有些選單項目的設計與使用者體驗有所變更,但依然是根據 +{@link android.view.Menu} API 來定義一組動作與選項的語意。本指南說明如何在所有版本的 Android 上,建立三個基本類型的選單或動作呈現方式: + +

+ +
+
選項選單和動作列
+
選項選單是 Activity 的主要選單項目集合。 +您應該將對應用程式有全域影響的動作放置在此,例如「搜尋」、「撰寫電子郵件」及「設定」。 + +

如果您是為 Android 2.3 以下版本進行開發,使用者可按下「選單」按鈕來顯示選項選單面板。 +

+

在 Android 3.0 以上版本,選項選單的項目是當成螢幕上的動作項目與溢出選項組合,以動作列呈現。 +從 Android 3.0 開始,「選單」按鈕已淘汰 (有些裝置甚至沒有),因此您應轉向使用動作列來存取動作和其他選項。 + + +

+

請參閱建立選項選單

+
+ +
內容選單和內容關聯動作模式
+ +
內容選單是會在使用者長按某元素時顯示的浮動選單。 +它提供的動作會影響所選取內容或內容畫面。 + +

針對 Android 3.0 以上版本進行開發時,您應該改為使用關聯比對動作模式,以啟用所選取內容的動作。此模式顯示的動作項目會影響畫面頂端列中選取的內容,並允許使用者選取多個項目。 + +

+

請參閱建立內容關聯選單

+
+ +
彈出式選單
+
彈出式選單顯示的項目清單會以垂直清單的方式,錨定在呼叫該選單的檢視。 +它很適合用來提供與特定內容有關的動作溢出,或針對第二部分的命令提供選項。 +彈出式選單中的動作「不」應直接影響對應內容,應由內容關聯動作直接影響。 + +彈出式選單主要用於您 Activity 中與內容區域相關的延伸動作。 + +

請參閱建立彈出式選單

+
+
+ + + +

在 XML 中定義選單

+ +

對於所有選單類型,Android 提供標準 XML 格式來定義選單項目。 +您應該在 XML 選單資源中定義選單與其相關項目,而不是在您 Activity 的程式碼中建置選單。 +接著,您可以擴大 Activity 或片段中的選單資源 (當成 +{@link android.view.Menu} 物件載入)。 +

+ +

建議使用選單資源的幾個理由如下:

+
    +
  • 較容易視覺化 XML 中的選單結構。
  • +
  • 可將選單內容和您應用程式的行為程式碼分開。
  • +
  • 可讓您運用應用程式資源架構,為不同的平台版本、螢幕大小及其他設定,建立替代選單設定。 +
  • +
+ +

如要定義選單,可在專案的 res/menu/ 目錄內建立 XML 檔案,然後利用下列元素建置選單: +

+
+
<menu>
+
定義選單項目的容器 {@link android.view.Menu}。<menu> 元素必須是檔案的根節點,並可保留一或多個 +<item><group> 元素。 +
+ +
<item>
+
建立代表選單中單一項目的 {@link android.view.MenuItem}。此元素可以包含巢狀 +<menu> 元素以建立子選單。
+ +
<group>
+
可供 {@code <item>} 元素選用的不可見容器。它可讓您將選單項目分類,以便分享屬性,例如有效狀態與可見度。 +如需詳細資訊,請參閱建立選單群組。 +
+
+ + +

稱為 game_menu.xml 的範例選單如下:

+
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:id="@+id/new_game"
+          android:icon="@drawable/ic_new_game"
+          android:title="@string/new_game"
+          android:showAsAction="ifRoom"/>
+    <item android:id="@+id/help"
+          android:icon="@drawable/ic_help"
+          android:title="@string/help" />
+</menu>
+
+ +

<item> 元素支援數個屬性,您可以用來定義項目的外觀與行為。 +上述選單中的項目包括下列屬性:

+ +
+
{@code android:id}
+
項目獨有的資源 ID,當使用者選取該項目時可讓應用程式辨識出來。 +
+
{@code android:icon}
+
要當成項目圖示使用的可繪項目參考資料。
+
{@code android:title}
+
要當成項目標題使用的字串參考資料。
+
{@code android:showAsAction}
+
指定此項目應何時且如何顯示為動作列中的動作項目。
+
+ +

這些都是您應該使用的最重要屬性,但不侷限於上述這幾個。 +如需所有支援屬性的詳細資訊,請參閱選單資源

+ +

您可以藉由將 {@code <menu>} 元素新增為 {@code <item>} 的子項,將子選單新增至任何選單 (子選單除外) 的項目。 +當您的應用程式有很多可按主題分類的功能,例如 PC 應用程式選單列中的項目 (檔案、編輯、檢視等等) 時,子選單相當有用。 + +例如:

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:id="@+id/file"
+          android:title="@string/file" >
+        <!-- "file" submenu -->
+        <menu>
+            <item android:id="@+id/create_new"
+                  android:title="@string/create_new" />
+            <item android:id="@+id/open"
+                  android:title="@string/open" />
+        </menu>
+    </item>
+</menu>
+
+ +

如要在 Activity 中使用選單,您必須使用 {@link android.view.MenuInflater#inflate(int,Menu) +MenuInflater.inflate()} 擴大選單資源 (將 XML 轉換成可程式化的物件)。 +下列各節將說明如何擴大各種 +選單類型的選單。

+ + + +

建立選項選單

+ +
+ +

圖 1.Android 2.3 裝置上瀏覽器中的選項選單。 +

+
+ +

您應該在選項選單中包括與目前 Activity 內容關聯動作和其他選項,例如「搜尋」、「撰寫電子郵件」及「設定」。 +

+ +

選項選單中的項目會顯示在螢幕上的哪個位置,取決於您為其開發應用程式的版本。 +

+ +
    +
  • 如果您為「Android 2.3.x (API 級別 10) 以下版本」開發應用程式,當使用者按下[選單] 按鈕,選項選單的內容會顯示在螢幕底部,如圖 1 所示。 + +開啟時,第一個可見部分就是最多可保留六個選單項目的圖示選單。 + +如果您的選單包含六個以上的項目,Android 會將第六個與其餘的項目放入溢出選單,使用者可透過選取[更多] 來開啟。 + +
  • + +
  • 如果您為「Android 3.0 (API 級別 11) 以上版本」開發應用程式,選項選單中的項目會顯示在動作列。 +在預設情況下,系統會將所有項目放入動作溢出,使用者可以利用動作列右側的動作溢出圖示來顯示 (或按裝置的「選單」按鈕,如果有的話)。 + +為了能夠快速存取重要的動作,您可以將 {@code android:showAsAction="ifRoom"} 新增至對應的 {@code <item>} 元素,將要顯示在動作列的幾個項目升階 (請參閱圖 2)。 + + + +

    如需動作項目與其他動作列行為的詳細資訊,請參閱動作列指南。

    +

    注意:即使您「並非」為 Android 3.0 以上版本進行開發,您仍可建置自己的動作列版面配置,達到類似的效果。 +如需如何支援舊版Android 提供動作列的範例,請參閱動作列相容性範例。 + +

    +
  • +
+ + +

圖 2.Honeycomb 圖片庫應用程式的動作列,顯示導覽索引標籤與相機動作項目 (再加上動作溢出按鈕)。 +

+ +

您可以從 {@link android.app.Activity} 子類別或 {@link android.app.Fragment} 子類別宣告選項選單的項目。 +如果您的 Activity 與片段都宣告選項選單的項目,兩者會在 UI 中結合。 + +先顯示 Activity 的項目,接著會依片段新增至 Activity 的順序顯示各片段的項目。 +您可以視需要在您想移動的 {@code <item>} 中,利用其 {@code android:orderInCategory}屬性重新排列選單項目的順序。 +

+ +

如要指定 Activity 的選項選單,可覆寫 {@link +android.app.Activity#onCreateOptionsMenu(Menu) onCreateOptionsMenu()} (片段會提供自己的 +{@link android.app.Fragment#onCreateOptionsMenu onCreateOptionsMenu()} 回呼)。在這種方法中,您可以將選單資源(在 XML 中完成定義) 擴大回呼中提供的 {@link +android.view.Menu}。 +例如:

+ +
+@Override
+public boolean onCreateOptionsMenu(Menu menu) {
+    MenuInflater inflater = {@link android.app.Activity#getMenuInflater()};
+    inflater.inflate(R.menu.game_menu, menu);
+    return true;
+}
+
+ +

透過 {@link android.view.MenuItem} API,您也可以使用 {@link android.view.Menu#add(int,int,int,int) +add()} 來新增選單項目,以及利用 {@link android.view.Menu#findItem findItem()} 擷取項目來修訂它們的屬性。 +

+ +

如果您是為 Android 2.3.x 以下版本開發應用程式,當使用者初次開啟選單時,系統會呼叫 {@link +android.app.Activity#onCreateOptionsMenu(Menu) onCreateOptionsMenu()},以建立選項選單。 +如果您是為 Android 3.0 以上版本開發應用程式,啟動 Activity 時,系統會呼叫 +{@link android.app.Activity#onCreateOptionsMenu(Menu) onCreateOptionsMenu()},以在動作列顯示項目。 +

+ + + +

處理點擊事件

+ +

使用者從選項選單 (包括動作列中的動作項目) 中選取項目時,系統會呼叫您 Activity 的 +{@link android.app.Activity#onOptionsItemSelected(MenuItem) +onOptionsItemSelected()} 方法。此方法會傳送所選取的 {@link android.view.MenuItem}。您可以呼叫 +{@link android.view.MenuItem#getItemId()} 傳回選單項目的唯一 ID (由選單資源中的 +{@code android:id} 屬性或透過指定給 +{@link android.view.Menu#add(int,int,int,int) add()} 方法的整數來定義) 來識別該項目。您可以將此 ID 和已知選單項目比對,以執行適當動作。 +例如:

+ +
+@Override
+public boolean onOptionsItemSelected(MenuItem item) {
+    // Handle item selection
+    switch (item.getItemId()) {
+        case R.id.new_game:
+            newGame();
+            return true;
+        case R.id.help:
+            showHelp();
+            return true;
+        default:
+            return super.onOptionsItemSelected(item);
+    }
+}
+
+ +

當您成功處理選單項目時會傳回 {@code true}。如果您不處理選單項目,應該呼叫 +{@link +android.app.Activity#onOptionsItemSelected(MenuItem) onOptionsItemSelected()} 的超級類別實作 (預設實作會傳回 false)。 +

+ +

如果您的 Activity 包含片段,系統會先呼叫 Activity 的 {@link +android.app.Activity#onOptionsItemSelected(MenuItem) onOptionsItemSelected()},再依片段的新增順序呼叫各片段,直到其中之一傳回 +{@code true} 或所有片段均已呼叫。 +

+ +

提示:Android 3.0 新增的功能可讓您在 XML 中使用 +{@code android:onClick} 屬性定義選單項目的點擊行為。該屬性值必須是使用選單的 Activity 所定義的方法名稱。 +它必須是公用方法且接受單一 +{@link android.view.MenuItem} 參數,當系統呼叫此方法時,它會傳送選取的選單項目。 +如需詳細資訊與範例,請參閱選單資源文件。

+ +

提示:如果您的應用程式包含多個 Activity 且有一些提供相同的選項選單,可考慮建立只實作 +{@link android.app.Activity#onCreateOptionsMenu(Menu) +onCreateOptionsMenu()} 與 {@link android.app.Activity#onOptionsItemSelected(MenuItem) +onOptionsItemSelected()} 方法的 Activity。 +接著,針對應共用相同選項選單的 Activity 擴充此類別。 +如此一來,您可以管理一組處理選單動作的程式碼,以及每一個繼承選單行為的子系類別。 + + +如果您想要將選單項目新增至其中一個子系 Activity,請覆寫該 Activity 中的 {@link android.app.Activity#onCreateOptionsMenu(Menu) +onCreateOptionsMenu()}。呼叫 {@code super.onCreateOptionsMenu(menu)} 以建立原始的選單項目,然後利用 +{@link +android.view.Menu#add(int,int,int,int) menu.add()} 來新增選單項目。您也可以覆寫個別選單項目的超級類別行為。 +

+ + +

在執行階段變更選單項目

+ +

系統呼叫 {@link android.app.Activity#onCreateOptionsMenu(Menu) +onCreateOptionsMenu()} 之後,會保留您填入的 {@link android.view.Menu} 執行個體,除非該選單因某種理由而變無效,否則不會再次呼叫 +{@link android.app.Activity#onCreateOptionsMenu(Menu) onCreateOptionsMenu()}。 +不過,{@link +android.app.Activity#onCreateOptionsMenu(Menu) onCreateOptionsMenu()} 應該只用來建立初始選單狀態,您不要在 Activity 生命週期間用來進行變更。 +

+ +

如果您想要根據 Activity 生命週期當中發生的事件來修改選項選單,您可以使用 +{@link android.app.Activity#onPrepareOptionsMenu(Menu) onPrepareOptionsMenu()} 方法這麼做。 +這個方法會將目前存在的 +{@link android.view.Menu} 物件傳送給您加以修改,例如新增、移除或停用項目 +(片段也會提供 {@link +android.app.Fragment#onPrepareOptionsMenu onPrepareOptionsMenu()} 回呼)。

+ +

在 Android 2.3.x 以下版本,每當使用者開啟選項選單 (按下 [選單] +按鈕) 時,系統都會呼叫 {@link +android.app.Activity#onPrepareOptionsMenu(Menu) +onPrepareOptionsMenu()}。

+ +

在 Android 3.0 以上版本,當選單項目顯示在動作列時會一律將選項選單視為開啟。 +當事件發生且您想要執行選單更新時,您必須呼叫 +{@link android.app.Activity#invalidateOptionsMenu invalidateOptionsMenu()},以要求系統呼叫 +{@link android.app.Activity#onPrepareOptionsMenu(Menu) onPrepareOptionsMenu()}。

+ +

注意:您不應該根據目前處於焦點狀態的 +{@link android.view.View},變更選項選單中的項目。 +處於輕觸模式 (使用者未使用軌跡球或導覽用的方向鍵) 時,檢視無法成為焦點,因此您不應該使用焦點當成修改選項選單中項目時的依據。 + +如果您想要在 {@link +android.view.View} 提供具內容相關性的選單項目,請使用內容選單

+ + + + +

建立內容關聯選單

+ +
+ +

圖 3.浮動內容選單的螢幕擷取畫面 (左) 與內容關聯動作列 (右)。 +

+
+ +

內容關聯選單提供的動作會影響 UI 中的特定項或內容畫面。 + +您可以為任何檢視提供內容選單,但它們通常用於使用者能直接對各項目執行動作的 {@link +android.widget.ListView}、{@link android.widget.GridView} 或其他檢視集合中的項目。

+ +

您有兩種方式可提供內容關聯動作:

+
    +
  • 浮動內容選單中。當使用者長按 (按住不放) 某個宣告支援內容選單的檢視時,選單會顯示為浮動的選單項目清單 (類似於對話方塊)。 + +使用者一次可對一個項目執行內容關聯動作。 +
  • + +
  • 內容關聯動作模式中。此模式是 +{@link android.view.ActionMode} 的系統實作,在畫面頂端將會影響將所選項目的動作項目顯示在「內容關聯動作列」。 +此模式處於使用中時,使用者能一次對多個項目執行某一動作 (如果您的應用程式允許的話)。 +
  • +
+ +

注意:Android 3.0 (API 級別 11) 以上版本可以使用內容關聯動作模式,此模式同時是可以顯示內容關聯動作時的偏好技術。 + +如果您的應用程式支援 3.0 以下版本,您應該在這類裝置上切換為浮動內容選單。 +

+ + +

建立浮動內容選單

+ +

如何提供浮動內容選單:

+
    +
  1. 呼叫 {@link android.app.Activity#registerForContextMenu(View) registerForContextMenu()} 並將 {@link android.view.View} 傳送給它,向應該建立關聯的內容選單註冊 {@link android.view.View}。 + + +

    如果您的 Activity 使用 {@link android.widget.ListView} 或 {@link android.widget.GridView},且您希望每個項目都提供相同的內容選單,可將 {@link android.widget.ListView} 或 {@link android.widget.GridView} 傳送至 {@link +android.app.Activity#registerForContextMenu(View) registerForContextMenu()} 來註冊內容選單的所有項目。 + +

    +
  2. + +
  3. 在您的 {@link android.app.Activity} 或 {@link android.app.Fragment} 中實作 {@link +android.view.View.OnCreateContextMenuListener#onCreateContextMenu onCreateContextMenu()} 方法。 + +

    當註冊的檢視收到長按事件時,系統會呼叫 {@link +android.view.View.OnCreateContextMenuListener#onCreateContextMenu onCreateContextMenu()} 方法。 +您可以在這裡定義選單項目,方法通常是擴大選單資源。例如: +

    +
    +@Override
    +public void onCreateContextMenu(ContextMenu menu, View v,
    +                                ContextMenuInfo menuInfo) {
    +    super.onCreateContextMenu(menu, v, menuInfo);
    +    MenuInflater inflater = getMenuInflater();
    +    inflater.inflate(R.menu.context_menu, menu);
    +}
    +
    + +

    {@link android.view.MenuInflater} 可讓您從選單資源擴大內容選單。回呼方法參數包括使用者選取的 +{@link android.view.View},以及提供其他所選取項目相關資訊的 {@link android.view.ContextMenu.ContextMenuInfo} 物件。 + +如果 Activity 有數個分別提供不同內容選單的檢視,您可以使用這些參數來決定要擴大的內容選單。 + +

    +
  4. + +
  5. 實作 {@link android.app.Activity#onContextItemSelected(MenuItem) +onContextItemSelected()}。 +

    當使用者選取選單項目時,系統會呼叫此方法,以便您執行適當的動作。 +例如:

    + +
    +@Override
    +public boolean onContextItemSelected(MenuItem item) {
    +    AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo();
    +    switch (item.getItemId()) {
    +        case R.id.edit:
    +            editNote(info.id);
    +            return true;
    +        case R.id.delete:
    +            deleteNote(info.id);
    +            return true;
    +        default:
    +            return super.onContextItemSelected(item);
    +    }
    +}
    +
    + +

    {@link android.view.MenuItem#getItemId()} 方法會查詢選定選單項目的 ID,使用 +{@code +android:id} 屬性在 XML 中指派給各個選單項目,如在 XML 中定義選單一節所述。 +

    + +

    當您成功處理選單項目時會傳回 {@code true}。如果您不處理選單項目,請將選單項目傳送至超級類別實作。 +如果您的 Activity 包括片段,該 Activity 會先收到此回呼。 +在不處理時呼叫超級類別,系統就可以一次一個 (依片段的新增順序) 將事件傳送至各片段中的個別回呼方法,直到傳回 +{@code true} 或 {@code false}。 +( +{@link android.app.Activity} 與 {@code android.app.Fragment} 的預設實作會傳回 {@code +false},因此當您不處理時,務必要呼叫超級類別。)

    +
  6. +
+ + +

使用內容關聯動作模式

+ +

內容關聯動作模式是 {@link android.view.ActionMode} 的系統實作,執行內容關聯動作時著重於與使用者互動。 +當使用者選取項目而啟用此模式時,「內容關聯動作列」會出現在畫面頂端,呈現使用者可在目前所選項目上執行的動作。 + +此模式啟用時,使用者能選取多個項目 (如果您允許的話)、取消選取項目,還可以在 Activity 內繼續瀏覽 (在您允許的範圍內)。 + +當使用者取消選取所有項目、按下 [後退] 按鈕,或選取該列左側的[完成] 按鈕時,動作模式會停用且內容 關聯 動作列也會消失。 + +

+ +

注意:內容關聯動作列不一定要與動作列關聯。 +即使內容相關動作列在視覺上覆蓋動作列的位置,兩者依然獨立運作。 + +

+ +

如果您在為 Android 3.0 (API 級別 11) 以上版本進行開發,您應使用內容關聯動作模式來呈現內容動作,而不是浮動內容選單。 +

+ +

針對提供內容相關動作的檢視,您應該就下列兩種事件之一 (或兩者) 呼叫內容關聯動作模式: +

+
    +
  • 使用者長按檢視。
  • +
  • 使用者選取檢視內的核取方塊或類似 UI 元件。
  • +
+ +

應用程式如何呼叫內容關聯動作模式以及如何定義每個動作的行為,視您的設計而定。 +基本上可分為兩種設計:

+
    +
  • 在個別的任意檢視上執行內容關聯動作。
  • +
  • 針對 {@link +android.widget.ListView} 或 {@link android.widget.GridView} (允許使用者選取多個項目並對全部執行同一動作) 中的項目群組批次執行內容關聯動作。 +
  • +
+ +

下列各節說明每種情況所需的設定。

+ + +

為個別的檢視啟用內容關離動作模式

+ +

如果您只有在使用者選取特定檢視時,才想要呼叫內容關聯動作模式,建議您採取以下動作: +

+
    +
  1. 實作 {@link android.view.ActionMode.Callback} 介面。在它的回呼方法中,您可以指定內容相關動作列的動作、回應動作項目的點擊事件,以及處理動作模式的其他生命週期事件。 + +
  2. +
  3. 當您想要顯示該列時 (例如當使用者長按檢視時),可呼叫 {@link android.app.Activity#startActionMode startActionMode()}。 +
  4. +
+ +

例如:

+ +
    +
  1. 實作 {@link android.view.ActionMode.Callback ActionMode.Callback} 介面: +
    +private ActionMode.Callback mActionModeCallback = new ActionMode.Callback() {
    +
    +    // Called when the action mode is created; startActionMode() was called
    +    @Override
    +    public boolean onCreateActionMode(ActionMode mode, Menu menu) {
    +        // Inflate a menu resource providing context menu items
    +        MenuInflater inflater = mode.getMenuInflater();
    +        inflater.inflate(R.menu.context_menu, menu);
    +        return true;
    +    }
    +
    +    // Called each time the action mode is shown. Always called after onCreateActionMode, but
    +    // may be called multiple times if the mode is invalidated.
    +    @Override
    +    public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
    +        return false; // Return false if nothing is done
    +    }
    +
    +    // Called when the user selects a contextual menu item
    +    @Override
    +    public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
    +        switch (item.getItemId()) {
    +            case R.id.menu_share:
    +                shareCurrentItem();
    +                mode.finish(); // Action picked, so close the CAB
    +                return true;
    +            default:
    +                return false;
    +        }
    +    }
    +
    +    // Called when the user exits the action mode
    +    @Override
    +    public void onDestroyActionMode(ActionMode mode) {
    +        mActionMode = null;
    +    }
    +};
    +
    + +

    請注意,這些事件回呼幾乎和選項選單的回呼一模一樣,只是每一個都會傳送與事件關聯的 {@link +android.view.ActionMode} 物件。您可以使用 {@link +android.view.ActionMode} API 對 CAB 進行各種變更,例如使用 +{@link android.view.ActionMode#setTitle setTitle()} 與 {@link +android.view.ActionMode#setSubtitle setSubtitle()} 修改標題與子標題 (指出已選取的項目數量相當有用)。 +

    + +

    同時請注意,當動作模式終結時,上述範例會將 {@code mActionMode} 變數設為 null。 +在下個步驟中,您將看到它是如何初始化,以及如何儲存 Activity 或片段中的成員變數,非常實用。 +

    +
  2. + +
  3. 呼叫 {@link android.app.Activity#startActionMode startActionMode()} (可以的話) 以啟用內容關聯動作模式,例如回應長按 {@link +android.view.View}。 +

    + +
    +someView.setOnLongClickListener(new View.OnLongClickListener() {
    +    // Called when the user long-clicks on someView
    +    public boolean onLongClick(View view) {
    +        if (mActionMode != null) {
    +            return false;
    +        }
    +
    +        // Start the CAB using the ActionMode.Callback defined above
    +        mActionMode = getActivity().startActionMode(mActionModeCallback);
    +        view.setSelected(true);
    +        return true;
    +    }
    +});
    +
    + +

    當您呼叫 {@link android.app.Activity#startActionMode startActionMode()} 時,系統會傳回建立的 +{@link android.view.ActionMode}。只要將此儲存在成員變數中,您就可以變更內容關聯動作列以回應其他事件。 +在上述範例中, +{@link android.view.ActionMode} 是用來確保不會重新建立已在使用中的 {@link android.view.ActionMode} 執行個體,方法是在將動作模式啟動之前,檢查成員是否為 null。 + +

    +
  4. +
+ + + +

啟用 ListView 或 GridView 中的批次內容相關動作

+ +

如果您在 {@link android.widget.ListView} 或 {@link +android.widget.GridView} (或 {@link android.widget.AbsListView} 的另一個延伸) 中有一系列項目,且想要允許使用者執行批次動作,請進行以下動作: +

+ +
    +
  • 實作 {@link android.widget.AbsListView.MultiChoiceModeListener} 介面並使用 +{@link android.widget.AbsListView#setMultiChoiceModeListener +setMultiChoiceModeListener()} 加以設定以供檢視群組使用。在接聽器的回呼方法中,您可以指定內容相關動作列的動作、回應動作項目的點擊事件,以及處理從 {@link android.view.ActionMode.Callback} 介面繼承的其他回呼。 + +
  • + +
  • 使用 {@link +android.widget.AbsListView#CHOICE_MODE_MULTIPLE_MODAL} 引數呼叫 {@link android.widget.AbsListView#setChoiceMode setChoiceMode()}。
  • +
+ +

例如:

+ +
+ListView listView = getListView();
+listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL);
+listView.setMultiChoiceModeListener(new MultiChoiceModeListener() {
+
+    @Override
+    public void onItemCheckedStateChanged(ActionMode mode, int position,
+                                          long id, boolean checked) {
+        // Here you can do something when items are selected/de-selected,
+        // such as update the title in the CAB
+    }
+
+    @Override
+    public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
+        // Respond to clicks on the actions in the CAB
+        switch (item.getItemId()) {
+            case R.id.menu_delete:
+                deleteSelectedItems();
+                mode.finish(); // Action picked, so close the CAB
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    @Override
+    public boolean onCreateActionMode(ActionMode mode, Menu menu) {
+        // Inflate the menu for the CAB
+        MenuInflater inflater = mode.getMenuInflater();
+        inflater.inflate(R.menu.context, menu);
+        return true;
+    }
+
+    @Override
+    public void onDestroyActionMode(ActionMode mode) {
+        // Here you can make any necessary updates to the activity when
+        // the CAB is removed. By default, selected items are deselected/unchecked.
+    }
+
+    @Override
+    public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
+        // Here you can perform updates to the CAB due to
+        // an {@link android.view.ActionMode#invalidate} request
+        return false;
+    }
+});
+
+ +

這樣一來,當使用者以長按的方式選取項目時,系統就會呼叫 {@link +android.widget.AbsListView.MultiChoiceModeListener#onCreateActionMode onCreateActionMode()} +方法,並顯示包含指定動作的內容相關動作列。可以看見內容關聯動作列時,使用者即可選取其他項目。 +

+ +

在某些情況下,內容關聯動作提供一般動作項目,因為使用者可能未發現有長按行為,所以您可能希望新增核取方塊或類似的 UI 元素,讓他們選取項目。 + +當使用者選取核取方塊時,您可以使用 {@link android.widget.AbsListView#setItemChecked setItemChecked()} 將個別的清單項目設定成已核取狀態,以呼叫內容關聯動作模式。 + +

+ + + + +

建立彈出式選單

+ +
+ +

圖 4.Gmail 應用程式中的彈出式選單錨定於右上角的溢出按鈕。 +

+
+ +

{@link android.widget.PopupMenu} 是錨定於 {@link android.view.View} 的強制回應選單。 +有空間的畫會顯示在錨定檢視下方或檢視上方。適合用途:

+
    +
  • 為與特定內容關聯動作提供溢出樣式選單 (例如 Gmail 的電子郵件標頭,如圖 4 所示)。 + +

    注意:內容選單一般用於會「影響」所選取內容的動作,與這並不相同。 +對於會影響所選取內容的動作,請使用內容關聯動作模式浮動內容選單。 +

  • +
  • 提供命令句的第二部分 (例如,標示為「新增」的按鈕會產生包含不同「新增」選項的彈出式選單)。 +
  • +
  • 提供類似於 {@link android.widget.Spinner} (不保留永續性選擇) 的下拉式清單。 +
  • +
+ + +

注意: API 級別 11 以上版本才可以使用 {@link android.widget.PopupMenu}。 +

+ +

如果您在 XML 中定義選單,以下說明如何顯示彈出式選單:

+
    +
  1. 透過其建構函式將 {@link android.widget.PopupMenu} 具現化,使用應錨定選單的目前應用程式 +{@link android.content.Context} 與 {@link android.view.View}。 +
  2. +
  3. 使用 {@link android.view.MenuInflater} 將您的選單資源擴大成 +{@link +android.widget.PopupMenu#getMenu() PopupMenu.getMenu()} 所傳回的 {@link android.view.Menu} 物件。針對 14 以上的 API 級別,您可以改用 +{@link android.widget.PopupMenu#inflate PopupMenu.inflate()}。
  4. +
  5. 呼叫 {@link android.widget.PopupMenu#show() PopupMenu.show()}。
  6. +
+ +

例如,以下是含有 {@link android.R.attr#onClick android:onClick} 屬性可顯示彈出式選單的按鈕。 +

+ +
+<ImageButton
+    android:layout_width="wrap_content" 
+    android:layout_height="wrap_content" 
+    android:src="@drawable/ic_overflow_holo_dark"
+    android:contentDescription="@string/descr_overflow_button"
+    android:onClick="showPopup" />
+
+ +

接著,該 Activity 可以顯示彈出式選單,如下所示:

+ +
+public void showPopup(View v) {
+    PopupMenu popup = new PopupMenu(this, v);
+    MenuInflater inflater = popup.getMenuInflater();
+    inflater.inflate(R.menu.actions, popup.getMenu());
+    popup.show();
+}
+
+ +

在 14 以上的 API 級別中,您可以利用 {@link +android.widget.PopupMenu#inflate PopupMenu.inflate()} 將兩行結合來擴大選單。

+ +

當使用者選取某項目或輕觸選單區域外時會關閉選單。 +您可以使用 {@link +android.widget.PopupMenu.OnDismissListener} 來接聽關閉事件。

+ +

處理點擊事件

+ +

如要在使用者選取選單項目時執行動作,您必須呼叫 {@link android.widget.PopupMenu#setOnMenuItemClickListener +setOnMenuItemclickListener()} 來實作 {@link +android.widget.PopupMenu.OnMenuItemClickListener} 介面並向您的 {@link +android.widget.PopupMenu} 註冊。 +當使用者選取項目時,系統會在您的介面中呼叫 {@link +android.widget.PopupMenu.OnMenuItemClickListener#onMenuItemClick onMenuItemClick()} 回呼。 +

+ +

例如:

+ +
+public void showMenu(View v) {
+    PopupMenu popup = new PopupMenu(this, v);
+
+    // This activity implements OnMenuItemClickListener
+    popup.setOnMenuItemClickListener(this);
+    popup.inflate(R.menu.actions);
+    popup.show();
+}
+
+@Override
+public boolean onMenuItemClick(MenuItem item) {
+    switch (item.getItemId()) {
+        case R.id.archive:
+            archive(item);
+            return true;
+        case R.id.delete:
+            delete(item);
+            return true;
+        default:
+            return false;
+    }
+}
+
+ + +

建立選單群組

+ +

選單群組是指某些特性相同的選單項目集合。您可以利用群組: +

+
    +
  • 透過 {@link android.view.Menu#setGroupVisible(int,boolean) +setGroupVisible()} 來顯示或隱藏所有項目
  • +
  • 透過 {@link android.view.Menu#setGroupEnabled(int,boolean) +setGroupEnabled()} 來啟用或停用所有項目
  • +
  • 透過 {@link +android.view.Menu#setGroupCheckable(int,boolean,boolean) setGroupCheckable()} 指定是否可核取所有項目
  • +
+ +

您可以將 {@code <item>} 元素堆疊在選單資源中的 {@code <group>}元素內,或使用 {@link +android.view.Menu#add(int,int,int,int) add()} 方法指定群組 ID 來建立群組。 +

+ +

以下是包含群組的範例選單資源:

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:id="@+id/menu_save"
+          android:icon="@drawable/menu_save"
+          android:title="@string/menu_save" />
+    <!-- menu group -->
+    <group android:id="@+id/group_delete">
+        <item android:id="@+id/menu_archive"
+              android:title="@string/menu_archive" />
+        <item android:id="@+id/menu_delete"
+              android:title="@string/menu_delete" />
+    </group>
+</menu>
+
+ +

群組中的項目和第一個項目都會顯示在同一層,選單中的這三個項目都屬於同層級。 +不過,您可以參考群組 ID 並使用上方所述的方法,修改群組當中兩個項目的特性。 +系統也絕不會將群組的項目分離。 +例如,如果您針對每個項目宣告 {@code +android:showAsAction="ifRoom"},它們都會顯示在動作列中或顯示在動作溢出中。 +

+ + +

使用可勾選的選單項目

+ +
+ +

圖 5.包含可勾選項目的子選單螢幕擷取畫面。 +

+
+ +

對於獨立選項使用核取方塊,或對互斥選項群組使用選項按鈕,將選單當成開啟或關閉選項的介面,相當實用。 + +圖 5 顯示的子選單包含能以圓形按鈕勾選的項目。 +

+ +

注意:圖示選單 (出自選項選單) 中的選單項目無法顯示核取方塊或選項按鈕。 +如果您選擇要將圖示選單中的項目設為可勾選,每當狀態變更時,您都必須切換圖示和/或文字,手動指出勾選狀態。 + +

+ +

您可以使用 {@code <item>} 元素中的 {@code +android:checkable} 屬性,為個別的選單項目定義可勾選行為,或利用 {@code <group>} 元素中的 {@code android:checkableBehavior} 屬性來為整個群組定義。 +例如,此選單群組中的所有項目都可以利用選項按鈕勾選。 +

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+    <group android:checkableBehavior="single">
+        <item android:id="@+id/red"
+              android:title="@string/red" />
+        <item android:id="@+id/blue"
+              android:title="@string/blue" />
+    </group>
+</menu>
+
+ +

{@code android:checkableBehavior} 屬性可接受其中一項: +

+
{@code single}
+
只能勾選群組中的一個項目 (圓形按鈕)
+
{@code all}
+
可勾選所有項目 (核取方塊)
+
{@code none}
+
沒有任何項目可供勾選
+
+ +

您可以使用 +{@code <item>} 元素中的 {@code android:checked} 屬性將預設的勾選狀態套用至項目,並使用 {@link +android.view.MenuItem#setChecked(boolean) setChecked()} 方法在程式碼中變更它。

+ +

已選取可勾選項目時,系統會呼叫所選取個別項目的回呼方法 (例如 {@link android.app.Activity#onOptionsItemSelected(MenuItem) onOptionsItemSelected()})。 +您必須在這裡設定核取方塊的狀態,原因是核取方塊或選項按鈕並不會自動變更其狀態。 + +您可以利用 +{@link android.view.MenuItem#isChecked()} 來查詢項目的目前狀態 (使用者選取它之前的狀態),然後利用 +{@link android.view.MenuItem#setChecked(boolean) setChecked()} 來設定勾選狀態。例如:

+ +
+@Override
+public boolean onOptionsItemSelected(MenuItem item) {
+    switch (item.getItemId()) {
+        case R.id.vibrate:
+        case R.id.dont_vibrate:
+            if (item.isChecked()) item.setChecked(false);
+            else item.setChecked(true);
+            return true;
+        default:
+            return super.onOptionsItemSelected(item);
+    }
+}
+
+ +

如果您不以這種方式設定已勾選狀態,將無法在使用者選取時變更項目 (核取方塊或選項按鈕) 的可見狀態。 + +當您確實設定狀態,Activity 會保留項目的已勾選狀態,當使用者稍後開啟選單時,即可看見您設定的已勾選狀態。 + +

+ +

注意:可勾選的選單項目只能在各工作階段上使用,並不會在應用程式終結後儲存。 + +如果想為使用者儲存特定應用程式設定,請使用共用偏好設定。 +

+ + + +

根據意圖新增選單項目

+ +

有時候您會希望選單項目使用 {@link android.content.Intent}來啟動 Activity (不論是您的應用程式或另一應用程式中的 Activity)。 +當您知道想要使用的意圖,同時有應繼承該意圖的特定選單項目時,您可在使用者選取項目時才回呼的適當方法實作期間使用 +{@link android.app.Activity#startActivity(Intent) startActivity()} 執行該意圖 (例如 {@link +android.app.Activity#onOptionsItemSelected(MenuItem) onOptionsItemSelected()} callback) 回呼)。 + +

+ +

不過,如果您不確定使用者的裝置是否包含可處理該意圖的應用程式,而新增可呼叫它的選單項目會導致選單項目無法運作,原因可能是該意圖不會解析成 Activity。 + + +為解決這個問題,當 Android 在處理您意圖的裝置上找到 Activity 時,Android 會讓您將選單項目動態新增至選單。 +

+ +

如何根據接受意圖的可用 Activity 來新增選單項目:

+
    +
  1. 定義類別為 +{@link android.content.Intent#CATEGORY_ALTERNATIVE} 和/或 +{@link android.content.Intent#CATEGORY_SELECTED_ALTERNATIVE} 的意圖,再加上其他需求。
  2. +
  3. 呼叫 {@link +android.view.Menu#addIntentOptions(int,int,int,ComponentName,Intent[],Intent,int,MenuItem[]) +Menu.addIntentOptions()}。Android 接著會搜尋可執行該意圖的任何應用程式,並將其新增至您的選單。 +
  4. +
+ +

如果未安裝可滿足該意圖的應用程式,則不會新增任何選單項目。 +

+ +

注意: +{@link android.content.Intent#CATEGORY_SELECTED_ALTERNATIVE} 是用來處理畫面上目前選取的元素。 +因此,請只在 {@link +android.app.Activity#onCreateContextMenu(ContextMenu,View,ContextMenuInfo) +onCreateContextMenu()} 中建立選單的情況下才使用。

+ +

例如:

+ +
+@Override
+public boolean onCreateOptionsMenu(Menu menu){
+    super.onCreateOptionsMenu(menu);
+
+    // Create an Intent that describes the requirements to fulfill, to be included
+    // in our menu. The offering app must include a category value of Intent.CATEGORY_ALTERNATIVE.
+    Intent intent = new Intent(null, dataUri);
+    intent.addCategory(Intent.CATEGORY_ALTERNATIVE);
+
+    // Search and populate the menu with acceptable offering applications.
+    menu.addIntentOptions(
+         R.id.intent_group,  // Menu group to which new items will be added
+         0,      // Unique item ID (none)
+         0,      // Order for the items (none)
+         this.getComponentName(),   // The current activity name
+         null,   // Specific items to place first (none)
+         intent, // Intent created above that describes our requirements
+         0,      // Additional flags to control items (none)
+         null);  // Array of MenuItems that correlate to specific items (none)
+
+    return true;
+}
+ +

找到的各個 Activity 提供的意圖篩選器如果與定義的意圖相符,就會新增選單項目,使用意圖篩選器的 android:label 中的值當成選單項目標題,並將應用程式圖示當成選單項目圖示。 + +此外, +{@link android.view.Menu#addIntentOptions(int,int,int,ComponentName,Intent[],Intent,int,MenuItem[]) +addIntentOptions()} 方法還會傳回新增的選單項目數量。

+ +

注意:當您呼叫 {@link +android.view.Menu#addIntentOptions(int,int,int,ComponentName,Intent[],Intent,int,MenuItem[]) +addIntentOptions()} 時,它會覆寫第一個引數中所指定選單群組中的任何或所有選單項目。 +

+ + +

允許將 Activity 新增至其他選單

+ +

您也能向其他應用程式提供 Activity 的服務,這樣即可在其他應用程式的選單中包含您的應用程式 (與上述的角色顛倒)。 +

+ +

如要包含在其他應用程式選單中,您必須照常定義意圖篩選器,但務必為意圖篩選器類別納入 +{@link android.content.Intent#CATEGORY_ALTERNATIVE} 和/或 {@link android.content.Intent#CATEGORY_SELECTED_ALTERNATIVE} 值。 + +例如:

+
+<intent-filter label="@string/resize_image">
+    ...
+    <category android:name="android.intent.category.ALTERNATIVE" />
+    <category android:name="android.intent.category.SELECTED_ALTERNATIVE" />
+    ...
+</intent-filter>
+
+ +

如要進一步瞭解如何編寫意圖篩選器,請參閱意圖和意圖篩選器。 +

+ +

如需採用此技術的範例應用程式,請參閱 NotePad 範例程式碼。 + +

diff --git a/docs/html-intl/intl/zh-tw/guide/topics/ui/notifiers/notifications.jd b/docs/html-intl/intl/zh-tw/guide/topics/ui/notifiers/notifications.jd new file mode 100644 index 0000000000000000000000000000000000000000..b8537445cff01b27877887ac5823a84a2c01e9ab --- /dev/null +++ b/docs/html-intl/intl/zh-tw/guide/topics/ui/notifiers/notifications.jd @@ -0,0 +1,979 @@ +page.title=通知 +@jd:body + + +

+ 通知是您應用程式的一般 UI 以外,可以向使用者顯示的訊息。當您告訴系統發出通知時,它會先在「通知區域」顯示為圖示。 + +使用者可開啟「通知匣」來查看通知的詳細資料。 +通知區域與通知匣都是使用者可隨時查看的系統控制區域。 + +

+ +

+ 圖 1.通知區域中的通知。 +

+ +

+ 圖 2.通知匣中的通知。 +

+ +

注意:除非另外註明,否則本指南參照支援程式庫 4 版中的 +{@link android.support.v4.app.NotificationCompat.Builder NotificationCompat.Builder} 類別。類別 {@link android.app.Notification.Builder Notification.Builder} 是在 Android 3.0 (API 級別 11) 新增。 + + +

+ +

設計注意事項

+ +

做為 Android 使用者介面重要部分,通知有自己的設計指導方針。 +在 Android 5.0 (API 級別 21) 導入的質感設計變更,更是特別重要。 +如需詳情,請參閱質感設計。 +如要瞭解如何設計通知與其互動,請參閱通知設計指南。 +

+ +

建立通知

+ +

您會為 +{@link android.support.v4.app.NotificationCompat.Builder NotificationCompat.Builder} 物件中的通知指定 UI 資訊與動作。 +如要建立通知本身,您可以呼叫 +{@link android.support.v4.app.NotificationCompat.Builder#build NotificationCompat.Builder.build()},其傳回的 +{@link android.app.Notification} 物件會包含您的規格。如要發出通知,您可以呼叫 {@link android.app.NotificationManager#notify NotificationManager.notify()} 將 {@link android.app.Notification} 物件傳送至系統。 + +

+ +

必要的通知內容

+

+ {@link android.app.Notification} 物件「必須」包含下列項目: +

+
    +
  • + 小圖示,由 +{@link android.support.v4.app.NotificationCompat.Builder#setSmallIcon setSmallIcon()} 設定 +
  • +
  • + 標題,由 +{@link android.support.v4.app.NotificationCompat.Builder#setContentTitle setContentTitle()} 設定 +
  • +
  • + 詳情文字,由 +{@link android.support.v4.app.NotificationCompat.Builder#setContentText setContentText()} 設定 +
  • +
+

選用的通知內容和設定

+

+ 所有其他的通知設定與內容均為選用。如需詳情,請參閱 +{@link android.support.v4.app.NotificationCompat.Builder} 的參考文件。 +

+ +

通知動作

+

+ 動作雖屬選用性質,但您至少必須將一個動作新增至通知。 + 動作可讓使用者從通知直接移至您應用程式中的 +{@link android.app.Activity},他們能在那裡查看一或多個事件或執行進一步工作。 + +

+

+ 通知可以提供多個動作。您應該一律定義會在使用者按一下通知時觸發的動作。 +這個動作通常會開啟您應用程式中的 +{@link android.app.Activity}。您也可以將按鈕新增至通知來執行其他動作,例如延遲鬧鐘或立即回應文字訊息。自 Android 4.1 起才提供此功能。 + +如果您使用其他動作按鈕,也務必要在您應用程式的 {@link android.app.Activity} 中提供此功能。 + +如需詳情,請參閱處理相容性。 +

+

+ 在 {@link android.app.Notification} 內,動作本身是由 +{@link android.app.PendingIntent} 完成定義,其中包含的 +{@link android.content.Intent} 會啟動您應用程式中的 +{@link android.app.Activity}。如要將 +{@link android.app.PendingIntent} 與手勢關聯,可呼叫 +{@link android.support.v4.app.NotificationCompat.Builder} 的適當方法。例如,如果當使用者按一下通知匣中的通知文字時,您希望啟動 +{@link android.app.Activity},可呼叫 +{@link android.support.v4.app.NotificationCompat.Builder#setContentIntent setContentIntent()} 來新增 {@link android.app.PendingIntent}。 + +

+

+ 最常見的動作情況就是在使用者按一下通知時啟動 {@link android.app.Activity}。 +您也可以在使用者關閉通知時啟動 {@link android.app.Activity}。 +在 Android 4.1 以上版本中,您可以透過動作按鈕啟動 +{@link android.app.Activity}。如需詳情,請參閱 +{@link android.support.v4.app.NotificationCompat.Builder} 的參考指南。 +

+ +

通知優先順序

+

+ 如有需要,您可以設定通知的優先順序。優先順序就像是提示一樣,可讓裝置 UI 知道該如何顯示通知。 + + 如要設定通知的優先順序,可呼叫 {@link +android.support.v4.app.NotificationCompat.Builder#setPriority(int) +NotificationCompat.Builder.setPriority()},然後傳入其中一個 {@link +android.support.v4.app.NotificationCompat} 優先順序常數。共有五個優先順序級別,範圍從 +{@link +android.support.v4.app.NotificationCompat#PRIORITY_MIN} (-2) 到 {@link +android.support.v4.app.NotificationCompat#PRIORITY_MAX} (2)。如未加以設定,優先順序會預設為 +{@link +android.support.v4.app.NotificationCompat#PRIORITY_DEFAULT} (0)。 +

+

如要瞭解如何設定適當的優先順序級別,請參閱通知設計指南中的「正確地設定及管理通知優先順序」。 + + +

+ +

建立簡易通知

+

+ 下列程式碼片段說明簡易的通知,指定要在使用者按一下通知時開啟的 Activity。 +請注意,以下程式碼會建立 +{@link android.support.v4.app.TaskStackBuilder} 物件,並使用此物件建立動作的 +{@link android.app.PendingIntent}。如要進一步瞭解這種模式,請參閱啟動 Activity 時保留導覽: + + +

+
+NotificationCompat.Builder mBuilder =
+        new NotificationCompat.Builder(this)
+        .setSmallIcon(R.drawable.notification_icon)
+        .setContentTitle("My notification")
+        .setContentText("Hello World!");
+// Creates an explicit intent for an Activity in your app
+Intent resultIntent = new Intent(this, ResultActivity.class);
+
+// The stack builder object will contain an artificial back stack for the
+// started Activity.
+// This ensures that navigating backward from the Activity leads out of
+// your application to the Home screen.
+TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);
+// Adds the back stack for the Intent (but not the Intent itself)
+stackBuilder.addParentStack(ResultActivity.class);
+// Adds the Intent that starts the Activity to the top of the stack
+stackBuilder.addNextIntent(resultIntent);
+PendingIntent resultPendingIntent =
+        stackBuilder.getPendingIntent(
+            0,
+            PendingIntent.FLAG_UPDATE_CURRENT
+        );
+mBuilder.setContentIntent(resultPendingIntent);
+NotificationManager mNotificationManager =
+    (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+// mId allows you to update the notification later on.
+mNotificationManager.notify(mId, mBuilder.build());
+
+

這樣一來,您的使用者就會收到通知。

+ +

將擴充版面配置套用至通知

+

+ 如要讓通知顯示在擴充的檢視中,請先建立含有您所需一般檢視選項的 +{@link android.support.v4.app.NotificationCompat.Builder} 物件。 +接著,將擴充版面配置物件當成引數,呼叫 {@link android.support.v4.app.NotificationCompat.Builder#setStyle +Builder.setStyle()}。 +

+

+ 請記住,Android 4.1 以下版本平台不支援擴充通知。如要進一步瞭解如何處理 Android 4.1 以下版本平台的通知,請參閱處理相容性。 + + +

+

+ 例如,下列程式碼片段示範如何更改上述程式碼片段中建立的通知,以使用擴充版面配置: + +

+
+NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(this)
+    .setSmallIcon(R.drawable.notification_icon)
+    .setContentTitle("Event tracker")
+    .setContentText("Events received")
+NotificationCompat.InboxStyle inboxStyle =
+        new NotificationCompat.InboxStyle();
+String[] events = new String[6];
+// Sets a title for the Inbox in expanded layout
+inboxStyle.setBigContentTitle("Event tracker details:");
+...
+// Moves events into the expanded layout
+for (int i=0; i < events.length; i++) {
+
+    inboxStyle.addLine(events[i]);
+}
+// Moves the expanded layout object into the notification object.
+mBuilder.setStyle(inBoxStyle);
+...
+// Issue the notification here.
+
+ +

處理相容性

+ +

+ 即使支援程式庫類別 {@link android.support.v4.app.NotificationCompat.Builder NotificationCompat.Builder} 中有設定這些功能的方法,特定版本也不一定能使用所有通知功能。 + + + 例如,取決於擴充通知的動作按鈕,只能在 Android 4.1 以上版本中顯示,原因是擴充通知本身只能在 Android 4.1 以上版本中使用。 + + +

+

+ 為確保能有最佳相容性,可利用 +{@link android.support.v4.app.NotificationCompat NotificationCompat} 與其子類別來建立通知,特別是 +{@link android.support.v4.app.NotificationCompat.Builder +NotificationCompat.Builder}。此外,實作通知時,請按照下列程序操作: +

+
    +
  1. + 不論使用者所用的版本為何,請向所有使用者提供所有所有通知功能。 +如要這麼做,請驗證可從您應用程式的 +{@link android.app.Activity} 使用所有功能。您可能會想要新增 +{@link android.app.Activity} 來執行此動作。 +

    + 例如,如果您想要使用 +{@link android.support.v4.app.NotificationCompat.Builder#addAction addAction()} + 以提供可停止和開始媒體播放的控制項,可先在您應用程式的 +{@link android.app.Activity} 中實作此控制項。 +

    +
  2. +
  3. + 藉由在使用者按一下通知時啟動功能,可確保所有使用者都能使用 {@link android.app.Activity} 中的功能。 +如要這樣做,請為 {@link android.app.Activity} 建立 {@link android.app.PendingIntent}。 + +呼叫 +{@link android.support.v4.app.NotificationCompat.Builder#setContentIntent +setContentIntent()} 以將 {@link android.app.PendingIntent} 新增至通知。 +
  4. +
  5. + 現在,可以將您想要使用的擴充通知功能新增至通知。請記住,您新增的任何功能也必須能在使用者點選通知時所啟動的 +{@link android.app.Activity} 中使用。 + +
  6. +
+ + + + +

管理通知

+

+ 當您必須對相同類型的事件發出多次通知時,請避免建立全新的通知。 +您應該考慮變更或新增 (或兩者) 就有通知的某些值來加以更新。 + +

+

+ 例如,Gmail 藉由新增其未讀訊息計數並將每封電子郵件的摘要新增至通知,藉此通知使用者新電子郵件已送達。 +而這稱為「堆疊」通知。詳情請參閱通知設計指南。 + + +

+

+ 注意:此 Gmail 功能需要「收件匣」擴充版面配置,也屬於 Android 4.1 以上版本才能使用的擴充通知功能。 + +

+

+ 以下各節說明如何更新及移除通知。 +

+

更新通知

+

+ 藉由呼叫 +{@link android.app.NotificationManager#notify(int, android.app.Notification) NotificationManager.notify()} 連同通知 ID 一起發出,即可將通知設定成可以更新。 + 如要在發出此通知後加以更新,可以更新或建立 +{@link android.support.v4.app.NotificationCompat.Builder} 物件,從中建置 +{@link android.app.Notification} 物件,並將 +{@link android.app.Notification} 連同您先前使用的相同 ID 一起發出。如果仍可看見先前的通知,系統會更新 {@link android.app.Notification} 物件內容中的通知。 + +如果已關閉先前的通知,會改為建立新的通知。 + +

+

+ 以下程式碼片段示範如何更新通知以反映發生的事件數目。 +以堆疊通知的方式顯示摘要: +

+
+mNotificationManager =
+        (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+// Sets an ID for the notification, so it can be updated
+int notifyID = 1;
+mNotifyBuilder = new NotificationCompat.Builder(this)
+    .setContentTitle("New Message")
+    .setContentText("You've received new messages.")
+    .setSmallIcon(R.drawable.ic_notify_status)
+numMessages = 0;
+// Start of a loop that processes data and then notifies the user
+...
+    mNotifyBuilder.setContentText(currentText)
+        .setNumber(++numMessages);
+    // Because the ID remains unchanged, the existing notification is
+    // updated.
+    mNotificationManager.notify(
+            notifyID,
+            mNotifyBuilder.build());
+...
+
+ + +

移除通知

+

+ 下列其中一個情況發生之前,都會看見通知: +

+
    +
  • + 使用者個別關閉通知,或透過「全部清除」的方式關閉通知 (如果可以清除通知的話)。 + +
  • +
  • + 使用者按一下通知,而您在建立通知時呼叫 +{@link android.support.v4.app.NotificationCompat.Builder#setAutoCancel setAutoCancel()}。 + +
  • +
  • + 您對特定通知 ID 呼叫 {@link android.app.NotificationManager#cancel(int) cancel()}。這種方法也會刪除進行中的通知。 + +
  • +
  • + 您呼叫 {@link android.app.NotificationManager#cancelAll() cancelAll()},將先前發出的所有通知移除。 + +
  • +
+ + +

啟動 Activity 時保留導覽

+

+ 當您從通知啟動 {@link android.app.Activity} 時,您必須保留使用者預期的導覽體驗。 +按一下 [返回] 應可將使用者從應用程式的一般工作流程帶回主螢幕,而按一下 + [近期記錄] 應會將 +{@link android.app.Activity} 顯示為個別的工作。如要保留導覽體驗,請以全新的工作啟動 +{@link android.app.Activity}。您該如何設定 +{@link android.app.PendingIntent} 以取得全新工作,取決於即將啟動的 +{@link android.app.Activity} 屬性。一般有兩種情況: +

+
+
+ 一般 Activity +
+
+ 您即將啟動的 {@link android.app.Activity} 屬於應用程式的一般工作流程。 +在這種情況下,設定 {@link android.app.PendingIntent}以啟動全新工作,再連同返回堆疊一起提供 {@link android.app.PendingIntent},可再次產生應用程式的一般 + + 返回 行為。 +

+ Gmail 應用程式的通知可示範這種情況。當您點選某一封電子郵件訊息的通知後,您會看到訊息本身。 +輕觸 [返回]可將您從 Gmail 帶回主螢幕,就像您剛才從主螢幕進入 Gmail,而不是從通知進入。 + + +

+

+ 不論您所在的應用程式為何,當您輕觸通知時,都會發生這種情況。 +例如,如果您在 Gmail 撰寫訊息,而您點選某一封電子郵件的通知,就會立即前往該封電子郵件。 +輕觸 [返回] + 會將您帶往收件匣,然後再到主螢幕,而不是到您正在撰寫的訊息。 + +

+
+
+ 特殊 Activity +
+
+ 只有從通知啟動這個 {@link android.app.Activity} 時,使用者才會看到它。 + 在某種意義上,{@link android.app.Activity}藉由提供難以在通知本身顯示的資訊來擴充通知。 +針對這種情況,請設定 +{@link android.app.PendingIntent} 以全新工作啟動。您不必建立返回堆疊,原因是啟動的 +{@link android.app.Activity} 不屬於應用程式的 Activity 流程。 +按一下 [返回] 還是會將使用者帶回主螢幕。 + +
+
+ +

設定一般 Activity PendingIntent

+

+ 如要設定會啟動直接項目 +{@link android.app.Activity} 的 {@link android.app.PendingIntent},請按照下列步驟操作: +

+
    +
  1. + 在宣示說明中定義您應用程式的 {@link android.app.Activity} 階層。 +
      +
    1. + 新增 Android 4.0.3 以下版本的支援。如要這樣做,請將 +<meta-data> 元素新增為 +<activity> 的下層物件,以指定您要啟動 +{@link android.app.Activity} 的上層物件。 + +

      + 針對此元素設定 +android:name="android.support.PARENT_ACTIVITY"。 + 設定 +android:value="<parent_activity_name>",其中 <parent_activity_name> 是上層 +<activity> 元素的 +android:name 值。 + + +如需範例,請參閱下列 XML。 +

      +
    2. +
    3. + 此外您還需要新增 Android 4.1 以上版本的支援。為此,請將 +android:parentActivityName 屬性新增到您要啟動 {@link android.app.Activity} 的 +<activity> 元素。 + + +
    4. +
    +

    + 最後的 XML 看起來會如下所示: +

    +
    +<activity
    +    android:name=".MainActivity"
    +    android:label="@string/app_name" >
    +    <intent-filter>
    +        <action android:name="android.intent.action.MAIN" />
    +        <category android:name="android.intent.category.LAUNCHER" />
    +    </intent-filter>
    +</activity>
    +<activity
    +    android:name=".ResultActivity"
    +    android:parentActivityName=".MainActivity">
    +    <meta-data
    +        android:name="android.support.PARENT_ACTIVITY"
    +        android:value=".MainActivity"/>
    +</activity>
    +
    +
  2. +
  3. + 根據啟動 +{@link android.app.Activity} 的 {@link android.content.Intent} 建立返回堆疊: +
      +
    1. + 建立 {@link android.content.Intent} 以啟動 {@link android.app.Activity}。 +
    2. +
    3. + 呼叫 {@link android.app.TaskStackBuilder#create +TaskStackBuilder.create()} 來建立堆疊建立器。 +
    4. +
    5. + 呼叫 +{@link android.support.v4.app.TaskStackBuilder#addParentStack addParentStack()} 將返回堆疊新增至堆疊建立器。 + 對於您在宣示說明中所定義階層中的每個 {@link android.app.Activity} +而言,返回堆疊包含會啟動 +{@link android.app.Activity} 的 {@link android.content.Intent} 物件。此方法也會新增以全新工作啟動堆疊的旗標。 + +

      + 注意:雖然 +{@link android.support.v4.app.TaskStackBuilder#addParentStack addParentStack()}的引數是參考啟動的 {@link android.app.Activity},但呼叫的方法不會新增會啟動 +{@link android.app.Activity} 的 +{@link android.content.Intent}, +這項工作是在下個步驟中完成。 +

      +
    6. +
    7. + 呼叫 +{@link android.support.v4.app.TaskStackBuilder#addNextIntent addNextIntent()} 來新增會從通知啟動 {@link android.app.Activity} 的 {@link android.content.Intent}。 + + 將您在第一個步驟中建立的 {@link android.content.Intent} 當成 +{@link android.support.v4.app.TaskStackBuilder#addNextIntent addNextIntent()} 的引數傳送。 + +
    8. +
    9. + 如有必要,呼叫 +{@link android.support.v4.app.TaskStackBuilder#editIntentAt +TaskStackBuilder.editIntentAt()} 在堆疊上將引數新增至 {@link android.content.Intent} 物件。當使用者使用以下方式導覽時,有必要確保目標 +{@link android.app.Activity} 可顯示有意義的資料: + 返回。 +
    10. +
    11. + 呼叫 +{@link android.support.v4.app.TaskStackBuilder#getPendingIntent getPendingIntent()} 來取得 {@link android.app.PendingIntent} 以用於此返回堆疊。 + 接著,您可以使用這個 {@link android.app.PendingIntent} 當成 +{@link android.support.v4.app.NotificationCompat.Builder#setContentIntent +setContentIntent()} 的引數。 +
    12. +
    +
  4. +
+

+ 以下程式碼片段會示範程序: +

+
+...
+Intent resultIntent = new Intent(this, ResultActivity.class);
+TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);
+// Adds the back stack
+stackBuilder.addParentStack(ResultActivity.class);
+// Adds the Intent to the top of the stack
+stackBuilder.addNextIntent(resultIntent);
+// Gets a PendingIntent containing the entire back stack
+PendingIntent resultPendingIntent =
+        stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
+...
+NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
+builder.setContentIntent(resultPendingIntent);
+NotificationManager mNotificationManager =
+    (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+mNotificationManager.notify(id, builder.build());
+
+ +

設定特殊 Activity PendingIntent

+

+ 下一節說明如何設定特殊 Activity +{@link android.app.PendingIntent}。 +

+

+ 特殊 {@link android.app.Activity} 不需要返回堆疊,因此您不必在宣示說明中定義其 +{@link android.app.Activity} 階層,也不用呼叫 +{@link android.support.v4.app.TaskStackBuilder#addParentStack addParentStack()}來建置返回堆疊。 + +而是使用宣示說明設定 {@link android.app.Activity} 工作選項,並呼叫 +{@link android.app.PendingIntent#getActivity getActivity()} 來建立 {@link android.app.PendingIntent}: + +

+
    +
  1. + 在您的宣示說明中,將下列屬性新增至 {@link android.app.Activity} 的 +<activity> 元素 + +
    +
    +android:name="activityclass" +
    +
    + Activity 的完整類別名稱。 +
    +
    +android:taskAffinity="" +
    +
    + 結合您在程式碼中設定的 +{@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK FLAG_ACTIVITY_NEW_TASK} 旗標,可確保此 {@link android.app.Activity} 不會前往應用程式的預設工作。 + +具有應用程式預設親和性的任何現有工作均不受影響。 + +
    +
    +android:excludeFromRecents="true" +
    +
    + 從 [近期記錄] 中排除新的工作,,避免使用者意外返回瀏覽。 + +
    +
    +

    + 以下程式碼片段顯示該元素: +

    +
    +<activity
    +    android:name=".ResultActivity"
    +...
    +    android:launchMode="singleTask"
    +    android:taskAffinity=""
    +    android:excludeFromRecents="true">
    +</activity>
    +...
    +
    +
  2. +
  3. + 建置並發出通知: +
      +
    1. + 建立會啟動 {@link android.app.Activity} 的{@link android.content.Intent}。 + +
    2. +
    3. + 使用旗標 +{@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK FLAG_ACTIVITY_NEW_TASK} 與 +{@link android.content.Intent#FLAG_ACTIVITY_CLEAR_TASK FLAG_ACTIVITY_CLEAR_TASK} 呼叫 +{@link android.content.Intent#setFlags setFlags()} 來設定 {@link android.app.Activity} 以全新的空白工作啟動。 + +
    4. +
    5. + 為 {@link android.content.Intent} 設定您需要的任何其他選項。 +
    6. +
    7. + 呼叫 {@link android.app.PendingIntent#getActivity getActivity()} 從{@link android.content.Intent} 中建立 {@link android.app.PendingIntent}。 + + 接著,您可以使用這個 {@link android.app.PendingIntent} 當成 +{@link android.support.v4.app.NotificationCompat.Builder#setContentIntent +setContentIntent()} 的引數。 +
    8. +
    +

    + 以下程式碼片段會示範程序: +

    +
    +// Instantiate a Builder object.
    +NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
    +// Creates an Intent for the Activity
    +Intent notifyIntent =
    +        new Intent(this, ResultActivity.class);
    +// Sets the Activity to start in a new, empty task
    +notifyIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
    +                        | Intent.FLAG_ACTIVITY_CLEAR_TASK);
    +// Creates the PendingIntent
    +PendingIntent notifyPendingIntent =
    +        PendingIntent.getActivity(
    +        this,
    +        0,
    +        notifyIntent,
    +        PendingIntent.FLAG_UPDATE_CURRENT
    +);
    +
    +// Puts the PendingIntent into the notification builder
    +builder.setContentIntent(notifyPendingIntent);
    +// Notifications are issued by sending them to the
    +// NotificationManager system service.
    +NotificationManager mNotificationManager =
    +    (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
    +// Builds an anonymous Notification object from the builder, and
    +// passes it to the NotificationManager
    +mNotificationManager.notify(id, builder.build());
    +
    +
  4. +
+ + +

在通知中顯示進度

+

+ 通知可包含動畫進度指示器,向使用者顯示進行中操作的狀態。 +如果您隨時可預估操作花費的時間以及完成程度,可以使用形式「明確」的指示器 (進度列)。 + +如果您無法預估操作的時間長度,請使用形式「不明確」的指示器 (Activity 指示器)。 + +

+

+ 進度指示器是使用平台的 +{@link android.widget.ProgressBar} 類別實作來顯示。 +

+

+ 從 Android 4.0 開始,只要呼叫 +{@link android.support.v4.app.NotificationCompat.Builder#setProgress setProgress()} 即可在平台上使用進度指示器。對於先前的版本,您必須建立自己的自訂通知版面配置,當中還要包含 {@link android.widget.ProgressBar} 檢視。 + + +

+

+ 下列各節說明如何使用 +{@link android.support.v4.app.NotificationCompat.Builder#setProgress setProgress()} 在通知中顯示進度。 +

+ +

顯示時間長度固定的進度指示器

+

+ 如要顯示明確的進度列,請呼叫 +{@link android.support.v4.app.NotificationCompat.Builder#setProgress +setProgress(max, progress, false)} 將該列新增至您的通知,然後發出通知。隨著您的操作進行, +progress 會增加並更新通知。操作結束時, +progress 應該等於 max。呼叫 +{@link android.support.v4.app.NotificationCompat.Builder#setProgress setProgress()} 的常見方法是將 max 設定為 100,然後新增 progress 做為操作的「完成百分比」值。 + + +

+

+ 當操作完成時,您可以繼續顯示進度列或將其移除。不論是任何一種情況,都務必更新通知文字來指出操作已完成。 + + 如要移除進度列,請呼叫 +{@link android.support.v4.app.NotificationCompat.Builder#setProgress +setProgress(0, 0, false)}。例如: +

+
+...
+mNotifyManager =
+        (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+mBuilder = new NotificationCompat.Builder(this);
+mBuilder.setContentTitle("Picture Download")
+    .setContentText("Download in progress")
+    .setSmallIcon(R.drawable.ic_notification);
+// Start a lengthy operation in a background thread
+new Thread(
+    new Runnable() {
+        @Override
+        public void run() {
+            int incr;
+            // Do the "lengthy" operation 20 times
+            for (incr = 0; incr <= 100; incr+=5) {
+                    // Sets the progress indicator to a max value, the
+                    // current completion percentage, and "determinate"
+                    // state
+                    mBuilder.setProgress(100, incr, false);
+                    // Displays the progress bar for the first time.
+                    mNotifyManager.notify(0, mBuilder.build());
+                        // Sleeps the thread, simulating an operation
+                        // that takes time
+                        try {
+                            // Sleep for 5 seconds
+                            Thread.sleep(5*1000);
+                        } catch (InterruptedException e) {
+                            Log.d(TAG, "sleep failure");
+                        }
+            }
+            // When the loop is finished, updates the notification
+            mBuilder.setContentText("Download complete")
+            // Removes the progress bar
+                    .setProgress(0,0,false);
+            mNotifyManager.notify(ID, mBuilder.build());
+        }
+    }
+// Starts the thread by calling the run() method in its Runnable
+).start();
+
+ + +

顯示持續性 Activity 指示器

+

+ 如要顯示不明確的 Activity 指示器,可利用 +{@link android.support.v4.app.NotificationCompat.Builder#setProgress setProgress(0, 0, true)}(可忽略前兩個引數) 將這類指示器新增至通知,然後發出通知。 +結果是指示器會有和進度列相同的樣式,只不過其中的動畫會持續播放。 + +

+

+ 在操作一開始發出通知。動畫會持續播放,直到您修改通知為止。 +操作完成時,可呼叫 +{@link android.support.v4.app.NotificationCompat.Builder#setProgress setProgress(0, 0, false)},然後更新通知以移除 Activity 指示器。 + + 請務必這樣做,不然即使操作完成後,動畫還是會繼續播放。此外,請務必變更通知文字來指出操作已完成。 + +

+

+ 如要查看 Activity 指示器如何運作,請參考上方的程式碼片段。找出下列幾行程式碼: +

+
+// Sets the progress indicator to a max value, the current completion
+// percentage, and "determinate" state
+mBuilder.setProgress(100, incr, false);
+// Issues the notification
+mNotifyManager.notify(0, mBuilder.build());
+
+

+ 用下列幾行取代您找到的程式碼: +

+
+ // Sets an activity indicator for an operation of indeterminate length
+mBuilder.setProgress(0, 0, true);
+// Issues the notification
+mNotifyManager.notify(0, mBuilder.build());
+
+ +

通知中繼資料

+ +

通知會按照您以下列 +{@link android.support.v4.app.NotificationCompat.Builder} 方法指派的中繼資料加以排序:

+ +
    +
  • 當裝置處於「優先順序」模式時 (例如,如果您的通知代表來電、即時訊息或鬧鐘),{@link android.support.v4.app.NotificationCompat.Builder#setCategory(java.lang.String) setCategory()} 會指示系統如何處理您的應用程式通知。 + +
  • +
  • {@link android.support.v4.app.NotificationCompat.Builder#setPriority(int) setPriority()} 會將具有優先順序欄位的通知設定為 {@code PRIORITY_MAX} 或 {@code PRIORITY_HIGH},而如果通知也有聲音或振動,就會以小型浮動視窗顯示。 + +
  • +
  • {@link android.support.v4.app.NotificationCompat.Builder#addPerson(java.lang.String) addPerson()} 可讓您將人員名單新增至通知。 +您的應用程式可用這種方式通知系統,應該將來自指定人員的通知歸在同一組,或是將較重要人員發出的通知排序。 + +
  • +
+ +
+ +

+ 圖 3.顯示抬頭通知的全螢幕 Activity +

+
+ +

抬頭通知

+ +

針對 Android 5.0 (API 級別 21),當裝置處於使用中 (也就是裝置已解鎖且螢幕處於開啟) 時,通知能以小型浮動視窗顯示 (也稱為「抬頭」通知)。 + +這些通知類似於精簡版的通知,只不過抬頭通知也會顯示動作按鈕。 + +使用者不需要離開目前的應用程式,就可以執行動作或關閉抬頭通知。 +

+ +

可能觸發抬頭通知的範例情況包括:

+ +
    +
  • 使用者的 Activity 處於全螢幕模式 (應用程式使用 +{@link android.app.Notification#fullScreenIntent});或者
  • +
  • 通知擁有高優先順序,並使用鈴聲或振動 +
  • +
+ +

鎖定螢幕通知

+ +

隨著 Android 5.0 (API 級別 21) 版發行,通知現在可以顯示在鎖定螢幕上。 +您的應用程式可使用此功能,提供媒體播放控制項和其他常見動作。 +使用者可以選擇是否透過 [設定] 在鎖定螢幕上顯示通知,您還可以指定是否能在鎖定螢幕上看見來自您應用程式的通知。 +

+ +

設定可見度

+ +

您的應用程式可控制安全鎖定螢幕上所顯示通知的詳細資料可見程度。 +您可以呼叫 {@link android.support.v4.app.NotificationCompat.Builder#setVisibility(int) setVisibility()},然後指定下列其中一個值: +

+ +
    +
  • {@link android.support.v4.app.NotificationCompat#VISIBILITY_PUBLIC}:顯示通知的完整內容。 +
  • +
  • {@link android.support.v4.app.NotificationCompat#VISIBILITY_SECRET}:不在鎖定螢幕上顯示此通知的任何部分。 +
  • +
  • {@link android.support.v4.app.NotificationCompat#VISIBILITY_PRIVATE}:顯示基本資訊,例如通知的圖示與內容標題,但隱藏通知的完整內容。 +
  • +
+ +

已設定 {@link android.support.v4.app.NotificationCompat#VISIBILITY_PRIVATE} 時,您還可提供可隱藏特定詳細資料的通知內容替代版本。 +例如,SMS 應用程式可能會顯示「您有 3 則簡訊」,但隱藏訊息內容與寄件者。 + +如要提供此替代通知,可先使用 {@link android.support.v4.app.NotificationCompat.Builder} 來建立替代通知。 +建立私人通知物件時,透過 {@link android.support.v4.app.NotificationCompat.Builder#setPublicVersion(android.app.Notification) setPublicVersion()} 方法附加上替代通知。 + + +

+ +

在鎖定螢幕上控制媒體播放

+ +

在 Android 5.0 (API 級別 21) 中,鎖定螢幕不會再依據 {@link android.media.RemoteControlClient} (現已淘汰) 顯示媒體控制項。 +利用 {@link android.app.Notification.Builder#addAction(android.app.Notification.Action) addAction()} 方法使用 {@link android.app.Notification.MediaStyle} 範本,將動作轉換成可點擊的圖示。 + + +

+ +

注意:支援程式庫不包含範本與 {@link android.app.Notification.Builder#addAction(android.app.Notification.Action) addAction()} 方法,因此這些功能只能在 Android 5.0 以上版本中執行。 + +

+ +

如要在 Android 5.0 的鎖定螢幕上顯示媒體播放控制項,請將可見度設定為 {@link android.support.v4.app.NotificationCompat#VISIBILITY_PUBLIC},如上所述。 +接著,新增動作並設定 {@link android.app.Notification.MediaStyle} 範本,如以下程式碼範例所示: + +

+ +
+Notification notification = new Notification.Builder(context)
+    // Show controls on lock screen even when user hides sensitive content.
+    .setVisibility(Notification.VISIBILITY_PUBLIC)
+    .setSmallIcon(R.drawable.ic_stat_player)
+    // Add media control buttons that invoke intents in your media service
+    .addAction(R.drawable.ic_prev, "Previous", prevPendingIntent) // #0
+    .addAction(R.drawable.ic_pause, "Pause", pausePendingIntent)  // #1
+    .addAction(R.drawable.ic_next, "Next", nextPendingIntent)     // #2
+    // Apply the media style template
+    .setStyle(new Notification.MediaStyle()
+    .setShowActionsInCompactView(1 /* #1: pause button */)
+    .setMediaSession(mMediaSession.getSessionToken())
+    .setContentTitle("Wonderful music")
+    .setContentText("My Awesome Band")
+    .setLargeIcon(albumArtBitmap)
+    .build();
+
+ +

注意:進一步實作控制媒體的 {@link android.media.RemoteControlClient} 已失效。 +如要進一步瞭解管理媒體工作階段與控制播放的新 API,請參閱媒體播放控制項。 + +

+ + + +

自訂通知版面配置

+

+ 通知架構可讓您定義自訂通知版面配置,藉此定義 {@link android.widget.RemoteViews} 物件中的通知外觀。 + + 自訂配置通知類似於一般通知,只不過這類通知是以 XML 版面配置檔案中定義的 {@link android.widget.RemoteViews} 為基礎。 + +

+

+ 自訂通知版面配置的可用高度取決於通知檢視。一般檢視的版面配置以 64 dp 為限,而擴充檢視的版面配置以 256 dp 為限。 + +

+

+ 如要定義自訂通知版面配置,請從具現化可擴大 XML 配置檔案的 +{@link android.widget.RemoteViews} 物件著手。接著,改為呼叫 {@link android.support.v4.app.NotificationCompat.Builder#setContent setContent()},而不是呼叫像是 {@link android.support.v4.app.NotificationCompat.Builder#setContentTitle setContentTitle()} 的方法。 + + +如要設定自訂通知中的內容詳細資料,可使用 {@link android.widget.RemoteViews} 中的方法來設定檢視下層物件的值: + + +

+
    +
  1. + 以個別檔案建立通知的 XML 版面配置。您可以使用任何檔案名稱,但副檔名必須為 .xml。 + +
  2. +
  3. + 在您的應用程式中,使用 {@link android.widget.RemoteViews} 方法來定義通知的圖示與文字。 +藉由呼叫 {@link android.support.v4.app.NotificationCompat.Builder#setContent setContent()} 將這個 {@link android.widget.RemoteViews} 物件放入您的 {@link android.support.v4.app.NotificationCompat.Builder}。 + +避免在您的 {@link android.widget.RemoteViews} 物件上設定背景 {@link android.graphics.drawable.Drawable},這是因為這樣做可能會使您的文字色彩變得無法閱讀。 + + +
  4. +
+

+ {@link android.widget.RemoteViews} 類別還包含您可用來將 {@link android.widget.Chronometer} 或 {@link android.widget.ProgressBar} 輕鬆新增至通知版面配置的方法。 + +如要進一步瞭解如何為您的通知建立自訂版面配置,請參閱 {@link android.widget.RemoteViews} 參考文件。 + +

+

+ 注意:當您使用自訂通知版面配置時,請特別注意,確保您的自訂版面配置適用於不同的裝置方向或解析度。 +雖然此建議適用於所有檢視版面配置,但對於通知而言特別重要,原因是通知匣的空間十分有限。 + +不要建立過於複雜的自訂版面配置,而且一定要在不同的設定中加以測試。 + +

+ +

針對自訂通知文字使用樣式資源

+

+ 自訂通知文字一律使用樣式資源。在不同的裝置與版本間可能會有不同的通知背景色彩,因此使用樣式資源可協助您將這點列入考量。 + +從 Android 2.3 開始,系統已定義了標準通知版面配置資料的樣式。 +如果您在適用於 Android 2.3 以上版本的應用程式中使用相同的樣式,將可確保能在顯示背景上看見您的文字。 + +

diff --git a/docs/html-intl/intl/zh-tw/guide/topics/ui/overview.jd b/docs/html-intl/intl/zh-tw/guide/topics/ui/overview.jd new file mode 100644 index 0000000000000000000000000000000000000000..44d05a82ebc2ea7ba8feaadc97a631f72c5878df --- /dev/null +++ b/docs/html-intl/intl/zh-tw/guide/topics/ui/overview.jd @@ -0,0 +1,71 @@ +page.title=UI 總覽 +@jd:body + + +

Android 應用程式中的所有使用者介面元素都是使用 {@link android.view.View} 與 +{@link android.view.ViewGroup} 物件。{@link android.view.View} 物件可在畫面上繪製使用者能與之互動的項目。 +{@link android.view.ViewGroup} 物件可保留其他 +{@link android.view.View} (與 {@link android.view.ViewGroup}) 物件,以定義介面的版面配置。 +

+ +

Android 提供 {@link android.view.View} 與{@link +android.view.ViewGroup} 子類別集合,提供一般輸入控制項 (例如按鈕與文字欄位) 及各種版面配置模型 (例如線性或相對)。 +

+ + +

使用者介面版面配置

+ +

應用程式每個元件的使用者介面是使用 {@link +android.view.View} 與 {@link android.view.ViewGroup} 物件的階層來定義,如圖 1 所示。每個檢視群組都是不可見容器,用以組織子檢視,而子檢視可能是輸入控制項或其他繪製部分 UI 的小工具。這個階層樹狀結構可依您的需求簡單或複雜化 (但簡化才會有最佳效能)。 + + + +

+ + +

圖 1.定義 UI 版面配置的檢視階層圖例 +

+ +

如要宣告版面配置,您可以在程式碼中將 {@link android.view.View} 物件具現化,然後開始建置樹狀結構,但最簡單也最有效的方法是使用 XML 檔案來定義您的版面配置。 + +XML 提供類似於 HTML 且人類看得懂的版面配置結構。

+ +

檢視的 XML 元素名稱相當於它個別代表的 Android 類別。因此, +<TextView> 元素可在您的 UI 中建立 {@link android.widget.TextView} 小工具,而 +<LinearLayout> 元素可以建立 {@link android.widget.LinearLayout} 檢視群組。 +

+ +

例如,上面有一個文字檢視與按鈕的簡單垂直版面配置,看起來像這樣:

+
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:layout_width="fill_parent" 
+              android:layout_height="fill_parent"
+              android:orientation="vertical" >
+    <TextView android:id="@+id/text"
+              android:layout_width="wrap_content"
+              android:layout_height="wrap_content"
+              android:text="I am a TextView" />
+    <Button android:id="@+id/button"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="I am a Button" />
+</LinearLayout>
+
+ +

當您載入應用程式中的版面配置資源時,Android 會將每個版面配置節點初始化成執行階段物件,您再用來定義其他行為、查詢物件狀態或修改版面配置。 + +

+ +

如需建立 UI 版面配置的完整指南,請參閱 XML 版面配置。 + + + +

使用者介面元件

+ +

您不必使用 {@link android.view.View} 與 {@link +android.view.ViewGroup} 物件來建置您的所有 UI。Android 提供的數個應用程式元件會提供標準 UI 版面配置,您只需要定義內容即可。 +這些 UI 元件各自有一組獨特的 API,如其各自的文件中所述,例如動作列對話方塊狀態通知。 +

+ + diff --git a/docs/html-intl/intl/zh-tw/guide/topics/ui/settings.jd b/docs/html-intl/intl/zh-tw/guide/topics/ui/settings.jd new file mode 100644 index 0000000000000000000000000000000000000000..91ac929e0fa12b8f4ae4bf27b19a9ae70d2209ea --- /dev/null +++ b/docs/html-intl/intl/zh-tw/guide/topics/ui/settings.jd @@ -0,0 +1,1202 @@ +page.title=設定 +page.tags=偏好設定、偏好設定 Activity、偏好設定片段 + +@jd:body + + +
+
+ +

本文件內容

+
    +
  1. 總覽 +
      +
    1. 偏好設定
    2. +
    +
  2. +
  3. 在 XML 中定義偏好設定 +
      +
    1. 建立設定群組
    2. +
    3. 使用意圖
    4. +
    +
  4. +
  5. 建立偏好設定 Activity
  6. +
  7. 使用偏好設定片段
  8. +
  9. 設定預設值
  10. +
  11. 使用偏好設定標頭 +
      +
    1. 建立標頭檔案
    2. +
    3. 顯示標頭
    4. +
    5. 使用偏好設定標頭支援舊版
    6. +
    +
  12. +
  13. 讀取偏好設定 +
      +
    1. 接聽偏好設定變更
    2. +
    +
  14. +
  15. 管理網路使用量
  16. +
  17. 建置自訂偏好設定 +
      +
    1. 指定使用者介面
    2. +
    3. 儲存設定值
    4. +
    5. 初始化目前值
    6. +
    7. 提供預設值
    8. +
    9. 儲存和還原偏好設定狀態
    10. +
    +
  18. +
+ +

重要類別

+
    +
  1. {@link android.preference.Preference}
  2. +
  3. {@link android.preference.PreferenceActivity}
  4. +
  5. {@link android.preference.PreferenceFragment}
  6. +
+ + +

另請參閱

+
    +
  1. 設定設計指南
  2. +
+
+
+ + + + +

應用程式通常會包含可讓使用者修改應用程式功能和行為的設定。例如,有些應用程式可讓使用者指定是否啟用通知,或指定應用程式與雲端同步資料的頻率。 + +

+ +

如果您想要為應用程式提供設定,應該使用 Android {@link android.preference.Preference} API 建置與其他 Android 應用程式使用者體驗一致的介面 (包含系統設定)。 + +本文件說明如何使用 {@link android.preference.Preference} API 建置應用程式設定。 +

+ +
+

設定設計

+

如要瞭解如何設計設定,請參閱設定設計指南。

+
+ + + +

圖 1.Android 簡訊應用程式設定的螢幕擷取畫面。 +選取一個 {@link android.preference.Preference} 定義的項目可以開啟介面變更設定。 +

+ + + + +

總覽

+ +

現在不使用 {@link android.view.View} 物件建置使用者介面,而是使用您在 XML 檔案中所宣告 {@link android.preference.Preference} 類別的各種子類別來建置設定。 + +

+ +

一個 {@link android.preference.Preference} 物件是單一設定的建置區塊。 +每個 {@link android.preference.Preference} 都以項目的形式在清單中顯示,並為使用者提供適當的 UI 以修改設定。 +例如,{@link +android.preference.CheckBoxPreference} 可以建立顯示核取方塊的清單項目,而 {@link +android.preference.ListPreference} 可以建立開啟對話方塊 (其中包含選擇清單) 的項目。

+ +

每個您新增的 {@link android.preference.Preference} 都會有一個對應的鍵值配對,系統可使用此鍵值對將設定儲存到您應用程式設定的預設 {@link android.content.SharedPreferences} 檔案。 + +當使用者變更設定,系統會為您更新 {@link android.content.SharedPreferences} 檔案中的對應值。 +您唯一需要與關聯的 {@link android.content.SharedPreferences} 檔案直接互動的時候,是當您需要讀取值才能根據使用者設定判斷應用程式行為時。 + +

+ +

儲存在 {@link android.content.SharedPreferences} 的每個設定值可以是下列其中一個資料類型: +

+ +
    +
  • 布林值
  • +
  • 浮動
  • +
  • 整數
  • +
  • 長整數
  • +
  • 字串
  • +
  • 字串 {@link java.util.Set}
  • +
+ +

由於您的應用程式設定 UI 是使用 {@link android.preference.Preference} 物件,而不是 {@link android.view.View} 物件建置,因此您需要使用專門的 {@link android.app.Activity} 或 {@link android.app.Fragment} 子類別來顯示清單設定: + + +

+ +
    +
  • 如果應用程式支援的 Android 版本早於 3.0 (API 級別 10 及較早版本),您必須以 {@link android.preference.PreferenceActivity} 類別延伸的形式建置 Activity。 +
  • +
  • 在 Android 3.0 及更新版本上,您應該使用傳統 {@link android.app.Activity},它託管了顯示應用程式設定的 {@link android.preference.PreferenceFragment}。 + +然而,當您有多個設定群組時,還可以使用 {@link android.preference.PreferenceActivity} 在大螢幕建立兩個面板的版面配置。 +
  • +
+ +

如何設定 {@link android.preference.PreferenceActivity} 和 {@link +android.preference.PreferenceFragment} 執行個體在建立偏好設定 Activity使用偏好設定片段小節中有相關說明。 +

+ + +

偏好設定

+ +

應用程式的每個設定都會以 {@link +android.preference.Preference} 類別的特定子類別代表。每個子類別包含一組核心屬性,可讓您為設定指定標題等項目和預設值。 +每個子類別還提供自己專屬的屬性和使用者介面。 +例如,圖 1 顯示簡訊應用程式設定的螢幕擷取畫面。 +設定畫面中的每個清單項目都由不同的 {@link +android.preference.Preference} 物件支援。

+ +

下列為幾個最常見的偏好設定:

+ +
+
{@link android.preference.CheckBoxPreference}
+
為啟用或停用的設定顯示含有核取方塊的項目。儲存的值是布林值 (如果選取 true)。 +
+ +
{@link android.preference.ListPreference}
+
開啟含有選項按鈕清單的對話方塊。儲存的值可以是任何支援的值類型 (如上所列)。 +
+ +
{@link android.preference.EditTextPreference}
+
開啟含有 {@link android.widget.EditText} 小工具的對話方塊。儲存的值是 {@link +java.lang.String}。
+
+ +

請參閱 {@link android.preference.Preference} 類別,以取得所有其他子類別及其對應屬性的清單。 +

+ +

當然,內建類別無法滿足所有需要,您的應用程式可能需要更專門的功能。 +例如,平台目前無法提供 {@link +android.preference.Preference} 類別以選取數字或日期。因此,您可能需要定義自己的 {@link android.preference.Preference} 子類別。 +如需執行此操作的協助,請參閱建置自訂偏好設定

+ + + +

在 XML 中定義偏好設定

+ +

雖然您可以在執行階段將新的 {@link android.preference.Preference} 物件具現化,但您仍應該在 XML 中定義設定清單,並在其中包含 {@link android.preference.Preference} 物件的階層。 + +使用 XML 檔案定義設定集合是較建議的做法,因為該檔案提供的易讀結構很容易更新。 +而且,雖然您仍然可以在執行階段修改集合,但您的應用程式設定通常是預先定義好的。 +

+ +

每個 {@link android.preference.Preference} 子類別都可透過與類別名稱相符的 XML 元素進行宣告,例如 {@code <CheckBoxPreference>}。 +

+ +

您必須將 XML 檔案儲存在 {@code res/xml/} 目錄中。雖然您可以自由命名檔案,但在傳統上會命名為 {@code preferences.xml}。 +您通常只需要一個檔案,因為階層 (會開啟自己的設定清單) 中的子目錄是透過 {@link android.preference.PreferenceScreen} 的巢狀執行個體進行宣告。 + +

+ +

注意:如果您想要為設定建立多個面板的版面配置,則您需要為每個片段準備個別的 XML 檔案。 +

+ +

XML 檔案的根節點必須是 {@link android.preference.PreferenceScreen +<PreferenceScreen>} 元素。您要將每個 {@link +android.preference.Preference} 新增到此元素內。您在 {@link android.preference.PreferenceScreen <PreferenceScreen>} 元素內新增的每個子項會在設定清單中顯示為單一項目。 + +

+ +

例如:

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <CheckBoxPreference
+        android:key="pref_sync"
+        android:title="@string/pref_sync"
+        android:summary="@string/pref_sync_summ"
+        android:defaultValue="true" />
+    <ListPreference
+        android:dependency="pref_sync"
+        android:key="pref_syncConnectionType"
+        android:title="@string/pref_syncConnectionType"
+        android:dialogTitle="@string/pref_syncConnectionType"
+        android:entries="@array/pref_syncConnectionTypes_entries"
+        android:entryValues="@array/pref_syncConnectionTypes_values"
+        android:defaultValue="@string/pref_syncConnectionTypes_default" />
+</PreferenceScreen>
+
+ +

此範例中有一個 {@link android.preference.CheckBoxPreference} 和一個 {@link +android.preference.ListPreference}。這兩個項目都包含下列三個屬性:

+ +
+
{@code android:key}
+
需要這個屬性才能保留資料值的偏好設定。它會指定當系統將此設定值儲存於 {@link +android.content.SharedPreferences} 時要使用的唯一索引鍵 (字串)。 + +

只有在下列情況下不需要此屬性:偏好設定為 {@link android.preference.PreferenceCategory} 或 {@link android.preference.PreferenceScreen},或者偏好設定指定 {@link android.content.Intent} 進行呼叫 (搭配 {@code <intent>} 元素) 或 {@link android.app.Fragment} 進行顯示 (搭配 {@code +android:fragment} 屬性)。 + +

+
+
{@code android:title}
+
這可為設定提供使用者可見的名稱。
+
{@code android:defaultValue}
+
這可指定系統在 {@link +android.content.SharedPreferences} 檔案中應設定的初始值。您應該為所有設定提供預設值。 +
+
+ +

如需所有其他支援的屬性相關資訊,請參閱 {@link +android.preference.Preference} (及個別子類別) 文件。

+ + +
+ +

圖 2.設定含有標題的類別。 +
1.類別以 {@link +android.preference.PreferenceCategory <PreferenceCategory>} 元素指定。
2.標題以 {@code android:title} 屬性指定。 +

+
+ + +

如果您的設定清單超過約 10 個項目,您可以新增標題來定義設定群組,或在分開的畫面顯示這些群組。 + +這些選項在下列幾節有詳細的描述。

+ + +

建立設定群組

+ +

如果您的清單有 10 個以上的項目,掃描、理解和處理這些項目對使用者而言可能會很困難。 +如要解決這個問題,可將部分或所有設定分成不同的群組,有效地將一長串清單分成多個簡短的清單。 + +一組包含相關設定的群組可以下列其中一種方式顯示:

+ + + +

您可以使用上述一種或兩種分組技巧來整理您的應用程式設定。決定要使用哪種方法或如何分割設定時,您應該要依照 Android 設計設定指南中的指導方針進行。 + +

+ + +

使用標題

+ +

如果您想要在設定群組間使用標題區分 (如圖 2 所示),將 {@link android.preference.Preference} 物件的每個群組放入 {@link +android.preference.PreferenceCategory}。 +

+ +

例如:

+ +
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <PreferenceCategory 
+        android:title="@string/pref_sms_storage_title"
+        android:key="pref_key_storage_settings">
+        <CheckBoxPreference
+            android:key="pref_key_auto_delete"
+            android:summary="@string/pref_summary_auto_delete"
+            android:title="@string/pref_title_auto_delete"
+            android:defaultValue="false"... />
+        <Preference 
+            android:key="pref_key_sms_delete_limit"
+            android:dependency="pref_key_auto_delete"
+            android:summary="@string/pref_summary_delete_limit"
+            android:title="@string/pref_title_sms_delete"... />
+        <Preference 
+            android:key="pref_key_mms_delete_limit"
+            android:dependency="pref_key_auto_delete"
+            android:summary="@string/pref_summary_delete_limit"
+            android:title="@string/pref_title_mms_delete" ... />
+    </PreferenceCategory>
+    ...
+</PreferenceScreen>
+
+ + +

使用子畫面

+ +

如果您想將設定群組放入子畫面 (如圖 3 所示),將 {@link android.preference.Preference} 物件的群組放入 {@link +android.preference.PreferenceScreen}。 +

+ + +

圖 3.設定子畫面。{@code +<PreferenceScreen>} 元素建立的項目會在選取時開啟獨立的清單以顯示巢狀設定。 +

+ +

例如:

+ +
+<PreferenceScreen  xmlns:android="http://schemas.android.com/apk/res/android">
+    <!-- opens a subscreen of settings -->
+    <PreferenceScreen
+        android:key="button_voicemail_category_key"
+        android:title="@string/voicemail"
+        android:persistent="false">
+        <ListPreference
+            android:key="button_voicemail_provider_key"
+            android:title="@string/voicemail_provider" ... />
+        <!-- opens another nested subscreen -->
+        <PreferenceScreen
+            android:key="button_voicemail_setting_key"
+            android:title="@string/voicemail_settings"
+            android:persistent="false">
+            ...
+        </PreferenceScreen>
+        <RingtonePreference
+            android:key="button_voicemail_ringtone_key"
+            android:title="@string/voicemail_ringtone_title"
+            android:ringtoneType="notification" ... />
+        ...
+    </PreferenceScreen>
+    ...
+</PreferenceScreen>
+
+ + +

使用意圖

+ +

在某些情況下,您可能會希望偏好設定項目開啟不同的 Activity 而不是設定畫面,例如,開啟網路瀏覽器以檢視網頁。 +如要在使用者選取偏好設定項目時呼叫 {@link +android.content.Intent},請新增 {@code <intent>} 元素做為對應 {@code <Preference>} 元素的子項。 +

+ +

例如,您可以使用下列方法透過偏好設定項目開啟網頁:

+ +
+<Preference android:title="@string/prefs_web_page" >
+    <intent android:action="android.intent.action.VIEW"
+            android:data="http://www.example.com" />
+</Preference>
+
+ +

您可以使用下列屬性建立隱含和明確意圖:

+ +
+
{@code android:action}
+
根據 {@link android.content.Intent#setAction setAction()} 方法指派的動作。 +
+
{@code android:data}
+
根據 {@link android.content.Intent#setData setData()} 方法指派的資料。
+
{@code android:mimeType}
+
根據 {@link android.content.Intent#setType setType()} 方法指派的 MIME 類型。 +
+
{@code android:targetClass}
+
根據 {@link android.content.Intent#setComponent +setComponent()} 方法指派的元件名稱類別部分。
+
{@code android:targetPackage}
+
根據 {@link +android.content.Intent#setComponent setComponent()} 方法指派的元件名稱封裝部分。
+
+ + + +

建立偏好設定 Activity

+ +

如要在 Activity 中顯示設定,延伸 {@link +android.preference.PreferenceActivity} 類別。這是傳統 {@link +android.app.Activity} 類別的延伸,可根據 {@link +android.preference.Preference} 物件的階層顯示設定清單。當使用者進行變更時,{@link android.preference.PreferenceActivity} 會自動保留與每個 {@link +android.preference.Preference} 關聯的設定。 +

+ +

注意:如果您針對 Android 3.0 及更新版本開發應用程式,您應該改為使用 {@link android.preference.PreferenceFragment}。 +前往下一個使用偏好設定片段章節。 +

+ +

最需要注意的一件事就是,不要在 {@link +android.preference.PreferenceActivity#onCreate onCreate()} 呼叫期間載入檢視的版面配置。您應該要呼叫 {@link +android.preference.PreferenceActivity#addPreferencesFromResource addPreferencesFromResource()},將您在 XML 檔案中宣告的偏好設定新增到 Activity 中。 +例如,下列為功能 {@link android.preference.PreferenceActivity} 所需的基本程式碼: +

+ +
+public class SettingsActivity extends PreferenceActivity {
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        addPreferencesFromResource(R.xml.preferences);
+    }
+}
+
+ +

事實上,這個程式碼對某些應用程式而言已經足夠,因為使用者修改偏好設定後,系統會將變更儲存到預設 {@link android.content.SharedPreferences} 檔案,當您需要檢查使用者設定時,您的其他應用程式元件就能進行讀取。 + +但是,許多應用程式需要更多一點程式碼,以接聽對偏好設定進行的變更。 +如需在 {@link android.content.SharedPreferences} 檔案接聽變更的相關資訊,請參閱讀取偏好設定。 + +

+ + + + +

使用偏好設定片段

+ +

如果您針對 Android 3.0 (API 級別 11) 及更新版本進行開發,您應該使用 {@link +android.preference.PreferenceFragment} 顯示 {@link android.preference.Preference} 物件清單。 +您可以將 {@link android.preference.PreferenceFragment} 新增到任何 Activity — 您不需要使用 {@link android.preference.PreferenceActivity}。 +

+ +

片段單就 Activity 而言,可為您的應用程式提供更有彈性的架構,無論您建置哪一種 Activity 都一樣。 + +因此,我們建議您盡可能使用 {@link +android.preference.PreferenceFragment} 來控制設定的顯示,而不要使用 {@link +android.preference.PreferenceActivity}。

+ +

實作 {@link android.preference.PreferenceFragment} 可以很簡單,只要定義 {@link android.preference.PreferenceFragment#onCreate onCreate()} 方法,透過 {@link android.preference.PreferenceFragment#addPreferencesFromResource +addPreferencesFromResource()} 載入偏好設定檔案。 + +例如:

+ +
+public static class SettingsFragment extends PreferenceFragment {
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        // Load the preferences from an XML resource
+        addPreferencesFromResource(R.xml.preferences);
+    }
+    ...
+}
+
+ +

之後,您可以將此片段新增至 {@link android.app.Activity},做法與任何其他 +{@link android.app.Fragment} 一樣。例如:

+ +
+public class SettingsActivity extends Activity {
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        // Display the fragment as the main content.
+        getFragmentManager().beginTransaction()
+                .replace(android.R.id.content, new SettingsFragment())
+                .commit();
+    }
+}
+
+ +

注意:{@link android.preference.PreferenceFragment} 沒有自己的 {@link android.content.Context} 物件。 +如果您需要 {@link android.content.Context} 物件,可以呼叫 {@link android.app.Fragment#getActivity()}。 +不過,必須小心,只能在片段附加到 Activity 時才能呼叫 {@link android.app.Fragment#getActivity()}。 +如果未附加片段,或是在生命週期期間中斷連結,{@link +android.app.Fragment#getActivity()} 將傳回 null。 +

+ + +

設定預設值

+ +

您建立的偏好設定可能為應用程式定義了一些重要的行為,因此當使用者第一次開啟您的應用程式時,務必使用每個 {@link android.preference.Preference} 預設值來初始化關聯的 {@link android.content.SharedPreferences} 檔案。 + + +

+ +

您必須要做的第一件事,就是使用 {@code android:defaultValue} 屬性指定 XML 檔案中每個 {@link +android.preference.Preference} 物件的預設值。 +值可以是適用於對應 {@link android.preference.Preference} 物件的任何資料類型。 +例如: +

+ +
+<!-- default value is a boolean -->
+<CheckBoxPreference
+    android:defaultValue="true"
+    ... />
+
+<!-- default value is a string -->
+<ListPreference
+    android:defaultValue="@string/pref_syncConnectionTypes_default"
+    ... />
+
+ +

之後,從您應用程式主 Activity — 以及使用者第一次進入您應用程式所使用的任何其他 Activity — 的 {@link android.app.Activity#onCreate onCreate()} 方法呼叫 {@link android.preference.PreferenceManager#setDefaultValues +setDefaultValues()}: + +

+ +
+PreferenceManager.setDefaultValues(this, R.xml.advanced_preferences, false);
+
+ +

在 {@link android.app.Activity#onCreate onCreate()} 期間呼叫它,可確保您的應用程式正確地以預設值初始化,您的應用程式可能需要讀取這些預設值,才能判斷一些行為 (例如,使用行動網路時是否可下載資料)。 + + +

+ +

這個方法採用三種引數:

+
    +
  • 您的應用程式 {@link android.content.Context}。
  • +
  • 您要設定預設值之偏好設定 XML 檔案的資源 ID。
  • +
  • 布林值指出預設值是否要設定一次以上。 +

    如果為 false,系統只會在過去從未呼叫此方法時設定預設值 (或者預設值共用偏好設定檔案的 {@link android.preference.PreferenceManager#KEY_HAS_SET_DEFAULT_VALUES} 為 false)。 + +

  • +
+ +

只要將第三個引數設為 false,您可以在每次 Activity 啟動時很安全地呼叫此方法,而不會將使用者儲存的偏好設定重設為預設值。 + +不過,如果您將它設為 true,會將任何之前的值覆寫為預設值。 +

+ + + +

使用偏好設定標頭

+ +

在少數情況下,您可能會想將設定設計為只在第一個畫面顯示子畫面清單(與系統設定應用程式一樣,如圖 4 和 5 所示)。 + +當您為 Android 3.0 及更新版本開發這類設計時,您應該使用 Android 3.0 的新「標頭」功能,而不是使用巢狀 {@link android.preference.PreferenceScreen} 元素建置子畫面。 + +

+ +

如要使用標頭建置設定,您必須:

+
    +
  1. 將每個設定群組分成不同的 {@link +android.preference.PreferenceFragment} 執行個體。也就是說,每個設定群組需要一個獨立的 XML 檔案。 +
  2. +
  3. 建立一個 XML 標頭檔案,其中列出每個設定群組以及宣告哪些片段包含對應的設定清單。 +
  4. +
  5. 延伸 {@link android.preference.PreferenceActivity} 類別以託管您的設定。
  6. +
  7. 實作 {@link +android.preference.PreferenceActivity#onBuildHeaders onBuildHeaders()} 回呼以指定標頭檔案。 +
  8. +
+ +

使用這個設計最大的好處在於在大螢幕執行時, +{@link android.preference.PreferenceActivity} 會自動顯示兩個面板的版面配置,如圖 4 所示。

+ +

即使應用程式支援的 Android 版本較 3.0 舊,您還是可以建置應用程式在新版的裝置上使用 {@link android.preference.PreferenceFragment} 顯示兩個面板,同時仍然支援舊版裝置上的傳統多畫面階層 (請參閱使用偏好設定標頭支援舊版)。 + + + +

+ + +

圖 4.含標頭的兩個面板版面配置。
1.標頭以 XML 標頭檔案定義。 +
2.每個設定群組是由標頭檔案 {@code <header>} 元素指定的{@link android.preference.PreferenceFragment} 所定義。 + +

+ + +

圖 5.含設定標頭的手機裝置。選取項目後,關聯的 {@link android.preference.PreferenceFragment} 會取代標頭。 + +

+ + +

建立標頭檔案

+ +

您標頭清單中的每個設定群組是由根 {@code <preference-headers>} 元素中的單一 {@code <header>} 元素指定。 +例如:

+ +
+<?xml version="1.0" encoding="utf-8"?>
+<preference-headers xmlns:android="http://schemas.android.com/apk/res/android">
+    <header 
+        android:fragment="com.example.prefs.SettingsActivity$SettingsFragmentOne"
+        android:title="@string/prefs_category_one"
+        android:summary="@string/prefs_summ_category_one" />
+    <header 
+        android:fragment="com.example.prefs.SettingsActivity$SettingsFragmentTwo"
+        android:title="@string/prefs_category_two"
+        android:summary="@string/prefs_summ_category_two" >
+        <!-- key/value pairs can be included as arguments for the fragment. -->
+        <extra android:name="someKey" android:value="someHeaderValue" />
+    </header>
+</preference-headers>
+
+ +

有了 {@code android:fragment} 屬性,每個標頭會宣告當使用者選取標頭時,應該開啟的 {@link +android.preference.PreferenceFragment} 執行個體。

+ +

{@code <extras>} 元素可讓您將鍵值配對傳送到 {@link +android.os.Bundle} 中的片段。片段可以透過呼叫 {@link +android.app.Fragment#getArguments()} 擷取引數。您可以因各種理由將引數傳送到片段,但其中一個最好的理由是針對每個群組重複使用 {@link +android.preference.PreferenceFragment} 中相同的子類別,並使用引數指定應載入片段的偏好設定 XML 檔案。 + +

+ +

例如,當每個標頭使用 {@code "settings"} 索引鍵定義 {@code <extra>} 引數時,下列片段可在多個設定群組重複使用: +

+ +
+public static class SettingsFragment extends PreferenceFragment {
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        String settings = getArguments().getString("settings");
+        if ("notifications".equals(settings)) {
+            addPreferencesFromResource(R.xml.settings_wifi);
+        } else if ("sync".equals(settings)) {
+            addPreferencesFromResource(R.xml.settings_sync);
+        }
+    }
+}
+
+ + + +

顯示標頭

+ +

如要顯示偏好設定標頭,必須實作 {@link +android.preference.PreferenceActivity#onBuildHeaders onBuildHeaders()} 回呼方法,並呼叫 {@link android.preference.PreferenceActivity#loadHeadersFromResource +loadHeadersFromResource()}。 +例如:

+ +
+public class SettingsActivity extends PreferenceActivity {
+    @Override
+    public void onBuildHeaders(List<Header> target) {
+        loadHeadersFromResource(R.xml.preference_headers, target);
+    }
+}
+
+ +

當使用者從標頭清單選取項目時,系統會開啟關聯的 {@link +android.preference.PreferenceFragment}。

+ +

注意:使用偏好設定標頭時,{@link +android.preference.PreferenceActivity} 的子類別不需要實作 {@link +android.preference.PreferenceActivity#onCreate onCreate()} 方法,這是因為 Activity 唯一需要做的工作就是載入標頭。 +

+ + +

使用偏好設定標頭支援舊版

+ +

如果您應用程式支援的 Android 版本比 3.0 舊,您仍然可以在 Android 3.0 及更新版本執行時,使用標頭提供兩個面板的版面配置。 +您只需要建立一個額外的偏好設定 XML 檔案,該檔案要使用行為與標頭項目 (供舊版 Android 使用) 一樣的基本 {@link android.preference.Preference +<Preference>} 元素。 + +

+ +

但是,不會開啟新的 {@link android.preference.PreferenceScreen},每個 {@link +android.preference.Preference <Preference>} 元素會傳送一個 {@link android.content.Intent} 到 {@link android.preference.PreferenceActivity},以指定要載入的偏好設定 XML 檔案。 + +

+ +

例如,下列為使用 Android 3.0及更新版本的偏好設定標頭 XML 檔案 ({@code res/xml/preference_headers.xml}): +

+ +
+<preference-headers xmlns:android="http://schemas.android.com/apk/res/android">
+    <header 
+        android:fragment="com.example.prefs.SettingsFragmentOne"
+        android:title="@string/prefs_category_one"
+        android:summary="@string/prefs_summ_category_one" />
+    <header 
+        android:fragment="com.example.prefs.SettingsFragmentTwo"
+        android:title="@string/prefs_category_two"
+        android:summary="@string/prefs_summ_category_two" />
+</preference-headers>
+
+ +

而,這裡有個偏好設定檔案為 Android 3.0 以下版本提供相同的標頭 ({@code res/xml/preference_headers_legacy.xml}): +

+ +
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <Preference 
+        android:title="@string/prefs_category_one"
+        android:summary="@string/prefs_summ_category_one"  >
+        <intent 
+            android:targetPackage="com.example.prefs"
+            android:targetClass="com.example.prefs.SettingsActivity"
+            android:action="com.example.prefs.PREFS_ONE" />
+    </Preference>
+    <Preference 
+        android:title="@string/prefs_category_two"
+        android:summary="@string/prefs_summ_category_two" >
+        <intent 
+            android:targetPackage="com.example.prefs"
+            android:targetClass="com.example.prefs.SettingsActivity"
+            android:action="com.example.prefs.PREFS_TWO" />
+    </Preference>
+</PreferenceScreen>
+
+ +

因為 Android 3.0 已加入對 {@code <preference-headers>} 的支援,系統只會在 Androd 3.0 或更新版本執行時,才會呼叫 {@link +android.preference.PreferenceActivity} 中的 {@link android.preference.PreferenceActivity#onBuildHeaders onBuildHeaders()}。 +如要載入「舊版」標頭檔案 ({@code preference_headers_legacy.xml}),您必須檢查 Android 版本,如果版本比 Android 3.0 更舊 ({@link +android.os.Build.VERSION_CODES#HONEYCOMB}),呼叫 {@link +android.preference.PreferenceActivity#addPreferencesFromResource addPreferencesFromResource()} 以載入舊版標頭檔案。 + + +例如:

+ +
+@Override
+public void onCreate(Bundle savedInstanceState) {
+    super.onCreate(savedInstanceState);
+    ...
+
+    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
+        // Load the legacy preferences headers
+        addPreferencesFromResource(R.xml.preference_headers_legacy);
+    }
+}
+
+// Called only on Honeycomb and later
+@Override
+public void onBuildHeaders(List<Header> target) {
+   loadHeadersFromResource(R.xml.preference_headers, target);
+}
+
+ +

最後一件要做的事,是處理傳送到 Activity 的 {@link android.content.Intent},以識別要載入的偏好設定檔案。 +擷取意圖的動作,並將它與偏好設定 XML {@code <intent>} 標籤中使用的已知動作字串進行比對: +

+ +
+final static String ACTION_PREFS_ONE = "com.example.prefs.PREFS_ONE";
+...
+
+@Override
+public void onCreate(Bundle savedInstanceState) {
+    super.onCreate(savedInstanceState);
+
+    String action = getIntent().getAction();
+    if (action != null && action.equals(ACTION_PREFS_ONE)) {
+        addPreferencesFromResource(R.xml.preferences);
+    }
+    ...
+
+    else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
+        // Load the legacy preferences headers
+        addPreferencesFromResource(R.xml.preference_headers_legacy);
+    }
+}
+
+ +

請注意,{@link +android.preference.PreferenceActivity#addPreferencesFromResource addPreferencesFromResource()} 的連續呼叫會將所有偏好設定堆疊在單一清單中,因此請確定將條件鏈結到 else-if 陳述式時只會呼叫它一次。 + +

+ + + + + +

讀取偏好設定

+ +

根據預設,透過呼叫靜態方法 {@link +android.preference.PreferenceManager#getDefaultSharedPreferences +PreferenceManager.getDefaultSharedPreferences()},您應用程式中的所有偏好設定會儲存可從應用程式內任何地方存取的檔案中。 +這會傳回 {@link +android.content.SharedPreferences} 物件,其中包含與您 {@link +android.preference.PreferenceActivity} 使用之 {@link android.preference.Preference} 物件關聯的所有鍵值配對。 +

+ +

例如,下列說明如何從應用程式中的任何其他 Activity 讀取其中一個偏好設定值: +

+ +
+SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this);
+String syncConnPref = sharedPref.getString(SettingsActivity.KEY_PREF_SYNC_CONN, "");
+
+ + + +

接聽偏好設定變更

+ +

使用者變更其中一個偏好設定後,您想要立即收到通知的原因有好幾個。 +如要在任何偏好設定發生變更時收到回呼,實作 {@link android.content.SharedPreferences.OnSharedPreferenceChangeListener +SharedPreference.OnSharedPreferenceChangeListener} 介面,並呼叫 {@link +android.content.SharedPreferences#registerOnSharedPreferenceChangeListener +registerOnSharedPreferenceChangeListener()} 為 {@link android.content.SharedPreferences} 物件註冊接聽器。 + +

+ +

介面只有一個回呼方式 {@link +android.content.SharedPreferences.OnSharedPreferenceChangeListener#onSharedPreferenceChanged +onSharedPreferenceChanged()},而且在 Activity 中實作介面可能對您來說會更為容易。 +例如:

+ +
+public class SettingsActivity extends PreferenceActivity
+                              implements OnSharedPreferenceChangeListener {
+    public static final String KEY_PREF_SYNC_CONN = "pref_syncConnectionType";
+    ...
+
+    public void onSharedPreferenceChanged(SharedPreferences sharedPreferences,
+        String key) {
+        if (key.equals(KEY_PREF_SYNC_CONN)) {
+            Preference connectionPref = findPreference(key);
+            // Set summary to be the user-description for the selected value
+            connectionPref.setSummary(sharedPreferences.getString(key, ""));
+        }
+    }
+}
+
+ +

在此範例中,方法會檢查是否是針對已知的偏好設定索引鍵設定進行變更。它會呼叫 {@link android.preference.PreferenceActivity#findPreference findPreference()} 以取得變更的 {@link android.preference.Preference} 物件,以便將項目摘要修改為使用者選取的描述。 + + +也就是說,當設定為 {@link +android.preference.ListPreference} 或其他多選擇設定時,如果設定變更為顯示目前狀態 (如圖 5 顯示的休眠設定),您應該呼叫 {@link +android.preference.Preference#setSummary setSummary()}。 +

+ +

注意:如 Android 設計文件中有關設定的說明所述,我們建議您在每次使用者變更偏好設定時更新 {@link android.preference.ListPreference} 的摘要,以描述目前的設定。 + +

+ +

為了在 Activity 中正確管理生命週期,我們建議您分別在 {@link +android.app.Activity#onResume} 和 {@link android.app.Activity#onPause} 回呼期間,註冊和解決註冊您的 {@link android.content.SharedPreferences.OnSharedPreferenceChangeListener}: +

+ +
+@Override
+protected void onResume() {
+    super.onResume();
+    getPreferenceScreen().getSharedPreferences()
+            .registerOnSharedPreferenceChangeListener(this);
+}
+
+@Override
+protected void onPause() {
+    super.onPause();
+    getPreferenceScreen().getSharedPreferences()
+            .unregisterOnSharedPreferenceChangeListener(this);
+}
+
+ +

注意:當您呼叫 {@link +android.content.SharedPreferences#registerOnSharedPreferenceChangeListener +registerOnSharedPreferenceChangeListener()} 時,偏好設定管理員目前不會在接聽器儲存強式參照。 +您必須將強式參照儲存到接聽器,否則它會很容易受記憶體回收的影響。 +我們建議您在物件的執行個體資料中保留接聽器參照,如此一來您可以在需要接聽器時隨時使用。 + +

+ +

例如,在下列程式碼中,呼叫器沒有保留接聽器參照。 +因此,接聽器將受到記憶體回收的支配,而且會在未來不明確的時間發生失敗: +

+ +
+prefs.registerOnSharedPreferenceChangeListener(
+  // Bad! The listener is subject to garbage collection!
+  new SharedPreferences.OnSharedPreferenceChangeListener() {
+  public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
+    // listener implementation
+  }
+});
+
+ +

將接聽器參照儲存在物件的執行個體資料欄位中,可以在需要接聽器時隨時使用: +

+ +
+SharedPreferences.OnSharedPreferenceChangeListener listener =
+    new SharedPreferences.OnSharedPreferenceChangeListener() {
+  public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
+    // listener implementation
+  }
+};
+prefs.registerOnSharedPreferenceChangeListener(listener);
+
+ +

管理網路使用量

+ + +

從 Android 4.0 開始,系統的設定應用程式允許使用者查看應用程式在前景和背景時使用了多少網路資料。 +然後,使用者可以停用個別應用程式的背景資料。 +如要避免使用者停用您的應用程式從背景存取資料,您應該有效率地使用資料連線,並允許使用者透過應用程式設定精簡您的應用程式資料使用量。 + +

+ +

例如,您可以讓使用者控制同步資料的頻率,無論您的應用程式只在 Wi-Fi 上執行上傳/下載、應用程式在漫遊使用資料等。 +使用者有了這些控制項,就比較不會在快要達到在系統設定中設定的限制時停用您應用程式的資料存取,因為他們可以準確地控制您應用程式使用的資料量。 + + +

+ +

您在 {@link android.preference.PreferenceActivity} 新增所需的偏好設定以控制應用程式的資料習慣後,您應該在宣示說明檔新增 {@link +android.content.Intent#ACTION_MANAGE_NETWORK_USAGE} 的意圖篩選器。 +例如:

+ +
+<activity android:name="SettingsActivity" ... >
+    <intent-filter>
+       <action android:name="android.intent.action.MANAGE_NETWORK_USAGE" />
+       <category android:name="android.intent.category.DEFAULT" />
+    </intent-filter>
+</activity>
+
+ +

這個意圖篩選器向系統指出,是 Activity 在控制您的應用程式資料使用量。 +因此,當使用者從系統設定應用程式檢查您的應用程式使用了多少資料量時,可以使用 [檢視應用程式設定] 按鈕啟動您的 {@link android.preference.PreferenceActivity},讓使用者精簡您應用程式使用的資料量。 + + +

+ + + + + + + +

建置自訂偏好設定

+ +

Android 架構包含各式各樣的 {@link android.preference.Preference} 子類別,可讓您建置所種不同設定類型的 UI。不過,您可能會發現內建解決方案中可能沒有您所需的設定,例如數字挑選器或日期挑選器。 + + +在這類情況下,您需要延伸 {@link android.preference.Preference} 類別或其中一個其他子類別,以建立自訂的偏好設定。 +

+ +

當您延伸 {@link android.preference.Preference} 類別時,需要執行幾個重要工作: +

+ +
    +
  • 指定使用者選取設定時要顯示的使用者介面。
  • +
  • 視需要儲存設定值。
  • +
  • 顯示 {@link android.preference.Preference} 時,使用目前 (或預設) 值加以初始化。 +
  • +
  • 在系統要求時提供預設值。
  • +
  • 如果 {@link android.preference.Preference} 提供自己的 UI (例如對話方塊),儲存並還原狀態以處理生命週期變更 (例如,當使用者旋轉螢幕時)。 +
  • +
+ +

以下各節說明如何完成這些工作。

+ + + +

指定使用者介面

+ +

如果您直接延伸 {@link android.preference.Preference} 類別,必須實作 {@link android.preference.Preference#onClick()} 以定義使用者選取項目時的動作。 + +不過,大多數自訂設定會延伸 {@link android.preference.DialogPreference} 來顯示對話方塊,以簡化程序。 +延伸 {@link +android.preference.DialogPreference} 時,您必須在類別建構函式期間呼叫 {@link +android.preference.DialogPreference#setDialogLayoutResource setDialogLayoutResourcs()},以指定對話方塊的版面配置。 +

+ +

例如,下列為自訂 {@link +android.preference.DialogPreference} 的建構函式,該偏好設定宣告版面配置並指定預設正值和負值對話方塊按鈕的文字: +

+ +
+public class NumberPickerPreference extends DialogPreference {
+    public NumberPickerPreference(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        
+        setDialogLayoutResource(R.layout.numberpicker_dialog);
+        setPositiveButtonText(android.R.string.ok);
+        setNegativeButtonText(android.R.string.cancel);
+        
+        setDialogIcon(null);
+    }
+    ...
+}
+
+ + + +

儲存設定值

+ +

您可以呼叫其中一個 {@link +android.preference.Preference} 類別的 {@code persist*()} 方法隨時儲存設定值,例如,如果設定值是整數,使用 {@link +android.preference.Preference#persistInt persistInt()},或者使用 {@link android.preference.Preference#persistBoolean persistBoolean()} 儲存布林值。 +

+ +

注意:每個 {@link android.preference.Preference} 只能儲存一個資料類型,因此您必須使用適合您自訂 {@link android.preference.Preference} 使用之資料類型的 {@code persist*()} 方法。 + +

+ +

當您選擇保留設定時,可以依據您延伸的 {@link +android.preference.Preference} 類別加以設定。如果您延伸 {@link +android.preference.DialogPreference},則應該只在對話方塊因正值結果 (使用者選取 [確定] 按鈕) 關閉時保留該值。 +

+ +

當 {@link android.preference.DialogPreference} 關閉時,系統會呼叫 {@link +android.preference.DialogPreference#onDialogClosed onDialogClosed()} 方法。這個方法包括一個布林值引數,指定使用者結果是否為「正值」— 如果值為 +true,則表示使用者選取正值按鈕,而您應該儲存新值。 +例如: +

+ +
+@Override
+protected void onDialogClosed(boolean positiveResult) {
+    // When the user selects "OK", persist the new value
+    if (positiveResult) {
+        persistInt(mNewValue);
+    }
+}
+
+ +

在此範例中,mNewValue 是保留設定目前值的類別成員。 +呼叫 {@link android.preference.Preference#persistInt persistInt()} 可將值儲存到 {@link android.content.SharedPreferences} 檔案 (自動使用 XML 檔案中為此 {@link android.preference.Preference} 指定的索引鍵)。 + +

+ + +

初始化目前值

+ +

當系統將您的 {@link android.preference.Preference} 新增到畫面時,會呼叫 {@link android.preference.Preference#onSetInitialValue onSetInitialValue()} 以通知您設定是否有持續值。 + +如果沒有持續值,這個呼叫會提供您預設值。 +

+ +

{@link android.preference.Preference#onSetInitialValue onSetInitialValue()} 方法會傳送布林值 restorePersistedValue,以指出該設定的值是否已經持續。 + +如果是 true,則您應該呼叫其中一個 {@link +android.preference.Preference} 類別的 {@code getPersisted*()} 方法以擷取持續值,例如,如果為整數值,可使用 {@link +android.preference.Preference#getPersistedInt getPersistedInt()}。 +您通常都會想要擷取持續值,如此您可以正確的更新 UI 以反映之前儲存的值。 + +

+ +

如果 restorePersistedValuefalse,則您應該使用在第二引數中傳送的預設值。 +

+ +
+@Override
+protected void onSetInitialValue(boolean restorePersistedValue, Object defaultValue) {
+    if (restorePersistedValue) {
+        // Restore existing state
+        mCurrentValue = this.getPersistedInt(DEFAULT_VALUE);
+    } else {
+        // Set default state from the XML attribute
+        mCurrentValue = (Integer) defaultValue;
+        persistInt(mCurrentValue);
+    }
+}
+
+ +

在實際上沒有持續值或沒有索引鍵的情況下,每個 {@code getPersisted*()} 方法會採用指定要使用之預設值的引數。 +在上述範例中,會使用本機常數來指定預設值,以避免 {@link +android.preference.Preference#getPersistedInt getPersistedInt()} 無法傳回持續值。 +

+ +

注意:無法使用 defaultValue 作為 {@code getPersisted*()} 方法中的預設值,因為當 restorePersistedValuetrue 時,其值永遠是 null。 + +

+ + +

提供預設值

+ +

如果 {@link android.preference.Preference} 類別的執行個體指定預設值 (包含 {@code android:defaultValue} 屬性),則系統會在將物件具現化時呼叫 {@link android.preference.Preference#onGetDefaultValue +onGetDefaultValue()},以擷取值。 + +您必須實作此方法,才能讓系統將預設值儲存在 {@link +android.content.SharedPreferences} 中。 +例如:

+ +
+@Override
+protected Object onGetDefaultValue(TypedArray a, int index) {
+    return a.getInteger(index, DEFAULT_VALUE);
+}
+
+ +

方法引數可以提供您所需的所有物件:屬性陣列,以及您必須擷取之 {@code android:defaultValue} 的索引位置。 +您必須實作此方法以便從屬性擷取預設值的原因,是因為您必須在未定義值的情況為屬性指定一個本機預設值。 + +

+ + + +

儲存和還原偏好設定狀態

+ +

如同版面配置中的 {@link android.view.View},您的 {@link android.preference.Preference} 子類別負責在 Activity 或片段重新啟動 (例如,當使用者旋轉螢幕時) 時,儲存和還原其狀態。 + +如要正確的儲存和還原 {@link android.preference.Preference} 類別的狀態,您必須實作生命週期回呼方法 {@link android.preference.Preference#onSaveInstanceState +onSaveInstanceState()} 和 {@link +android.preference.Preference#onRestoreInstanceState onRestoreInstanceState()}。 + +

+ +

您 {@link android.preference.Preference} 的狀態由實作 {@link android.os.Parcelable} 介面的物件定義。 +Android 架構為您提供這類物件作為定義狀態物件的起始點:{@link +android.preference.Preference.BaseSavedState} 類別。 +

+ +

如要定義 {@link android.preference.Preference} 類別儲存狀態的方法,您應該延伸 {@link android.preference.Preference.BaseSavedState} 類別。 +您只需要覆寫幾個方法,然後定義 {@link android.preference.Preference.BaseSavedState#CREATOR}物件。 + +

+ +

對於大多數應用程式而言,如果您的 {@link android.preference.Preference} 子類別儲存整數以外的資料類型,您可以複製下列實作,然後變更處理 {@code value} 的行即可。 + +

+ +
+private static class SavedState extends BaseSavedState {
+    // Member that holds the setting's value
+    // Change this data type to match the type saved by your Preference
+    int value;
+
+    public SavedState(Parcelable superState) {
+        super(superState);
+    }
+
+    public SavedState(Parcel source) {
+        super(source);
+        // Get the current preference's value
+        value = source.readInt();  // Change this to read the appropriate data type
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        super.writeToParcel(dest, flags);
+        // Write the preference's value
+        dest.writeInt(value);  // Change this to write the appropriate data type
+    }
+
+    // Standard creator object using an instance of this class
+    public static final Parcelable.Creator<SavedState> CREATOR =
+            new Parcelable.Creator<SavedState>() {
+
+        public SavedState createFromParcel(Parcel in) {
+            return new SavedState(in);
+        }
+
+        public SavedState[] newArray(int size) {
+            return new SavedState[size];
+        }
+    };
+}
+
+ +

將上述 {@link android.preference.Preference.BaseSavedState} 實作新增到您的應用程式 (通常做為 {@link android.preference.Preference} 子類別的子類別) 之後,您需要針對 {@link android.preference.Preference} 子類別實作 {@link android.preference.Preference#onSaveInstanceState +onSaveInstanceState()} 和 {@link +android.preference.Preference#onRestoreInstanceState onRestoreInstanceState()} 方法。 + + +

+ +

例如:

+ +
+@Override
+protected Parcelable onSaveInstanceState() {
+    final Parcelable superState = super.onSaveInstanceState();
+    // Check whether this Preference is persistent (continually saved)
+    if (isPersistent()) {
+        // No need to save instance state since it's persistent,
+        // use superclass state
+        return superState;
+    }
+
+    // Create instance of custom BaseSavedState
+    final SavedState myState = new SavedState(superState);
+    // Set the state's value with the class member that holds current
+    // setting value
+    myState.value = mNewValue;
+    return myState;
+}
+
+@Override
+protected void onRestoreInstanceState(Parcelable state) {
+    // Check whether we saved the state in onSaveInstanceState
+    if (state == null || !state.getClass().equals(SavedState.class)) {
+        // Didn't save the state, so call superclass
+        super.onRestoreInstanceState(state);
+        return;
+    }
+
+    // Cast state to custom BaseSavedState and pass to superclass
+    SavedState myState = (SavedState) state;
+    super.onRestoreInstanceState(myState.getSuperState());
+    
+    // Set this Preference's widget to reflect the restored state
+    mNumberPicker.setValue(myState.value);
+}
+
+ diff --git a/docs/html-intl/intl/zh-tw/guide/topics/ui/ui-events.jd b/docs/html-intl/intl/zh-tw/guide/topics/ui/ui-events.jd new file mode 100644 index 0000000000000000000000000000000000000000..68714e8b2b5918e649d50e443bf7bb48fe4d9900 --- /dev/null +++ b/docs/html-intl/intl/zh-tw/guide/topics/ui/ui-events.jd @@ -0,0 +1,291 @@ +page.title=輸入事件 +parent.title=使用者介面 +parent.link=index.html +@jd:body + +
+
+

本文件內容

+
    +
  1. 事件接聽器
  2. +
  3. 事件處理常式
  4. +
  5. 輕觸模式
  6. +
  7. 處理焦點
  8. +
+ +
+
+ +

在 Android,有多種方法可以攔截使用者與應用程式互動的事件。 +如果考慮的是使用者介面內的事件,方法就是從與使用者互動的特定檢視物件中擷取事件。 +檢視類別提供執行此動作的方法。

+ +

在您用來撰寫版面配置的各種檢視類別中,您會發現多種公用回呼方法,對 UI 事件似乎相當實用。 +當該物件發生個別動作時,Android 架構會呼叫這些方法。 +例如,輕觸檢視 (例如按鈕) 時,會在該物件上呼叫 onTouchEvent() 方法。 +不過,為了攔截這個事件,您必須延伸類別並覆寫方法。 +然而,延伸每個檢視物件以便處理這類事件並不實際。 +這就是檢視類別也包含巢狀介面與回呼的集合的原因,這樣您就能更輕鬆地定義。 +這些介面稱為事件接聽器,就是用來擷取使用者與您 UI 互動的票券。 +

+ +

雖然您更常使用事件接聽器來接聽使用者互動,未來您可能會想要延伸檢視類別以建置自訂元件。 +或許您想要延伸 {@link android.widget.Button} 類別讓一些項目更為出色。 + +在這種情況下,您能夠使用類別事件處理常式來為您的類別定義預設事件行為。 +

+ + +

事件接聽器

+ +

事件接聽器是 {@link android.view.View} 類別中的一個介面,該類別包含單一回呼方法。 +當使用者與 UI 中的項目互動並觸發接聽器所註冊的檢視時,Android 架構就會呼叫這些方法。 +

+ +

事件接聽器介面包含下列回呼方法:

+ +
+
onClick()
+
從 {@link android.view.View.OnClickListener}。使用者輕觸項目 (處於輕觸模式時),或將焦點放在具有導覽鍵或軌跡球的項目上,然後按適當的 "enter" 鍵或按下軌跡球時,就會呼叫。 + + +
+
onLongClick()
+
從 {@link android.view.View.OnLongClickListener}。使用者輕觸並按住項目 (處於輕觸模式時),或將焦點放在具有導覽鍵或軌跡球的項目上,然後按住適當的 "enter" 鍵或按住軌跡球 (一秒) 時,就會呼叫。 + + +
+
onFocusChange()
+
從 {@link android.view.View.OnFocusChangeListener}。使用者使用導覽鍵或軌跡球導覽至項目或離開項目時,就會呼叫。 +
+
onKey()
+
從 {@link android.view.View.OnKeyListener}。使用者將焦點放在項目上,然後按下或放開裝置上的硬體鍵時,就會呼叫。 +
+
onTouch()
+
從 {@link android.view.View.OnTouchListener}。當使用者執行符合輕觸事件資格的動作時,包括按下、放開或在螢幕上的任何移動手勢 (在項目邊界內),就會呼叫。 + +
+
onCreateContextMenu()
+
從 {@link android.view.View.OnCreateContextMenuListener}。建置操作選單時 (產生持續「長按」的效果),就會呼叫。 +請參閱選單開發人員指南的操作選單相關討論。 + +
+
+ +

這些方法是其個別介面的唯一要素。如要定義其中一種方法並處理您的事件,可在您的 Activity 中實作巢狀介面或將它定義為匿名類別。然後,將您的實作執行個體傳送至個別的 View.set...Listener() 方法。 + + +(例如,呼叫 +{@link android.view.View#setOnClickListener(View.OnClickListener) setOnClickListener()},將您的 {@link android.view.View.OnClickListener OnClickListener} 實作傳送給它。) +

+ +

下列範例說明如何為某個按鈕註冊 on-click 接聽器。

+ +
+// Create an anonymous implementation of OnClickListener
+private OnClickListener mCorkyListener = new OnClickListener() {
+    public void onClick(View v) {
+      // do something when the button is clicked
+    }
+};
+
+protected void onCreate(Bundle savedValues) {
+    ...
+    // Capture our button from layout
+    Button button = (Button)findViewById(R.id.corky);
+    // Register the onClick listener with the implementation above
+    button.setOnClickListener(mCorkyListener);
+    ...
+}
+
+ +

您也會發現在 Activity 中實作 OnClickListener 會更為方便。這樣可避免額外的類別載入和物件配置。 +例如:

+
+public class ExampleActivity extends Activity implements OnClickListener {
+    protected void onCreate(Bundle savedValues) {
+        ...
+        Button button = (Button)findViewById(R.id.corky);
+        button.setOnClickListener(this);
+    }
+
+    // Implement the OnClickListener callback
+    public void onClick(View v) {
+      // do something when the button is clicked
+    }
+    ...
+}
+
+ +

請注意,上述範例的 onClick() 回呼不會傳回任何值,但某些其他事件接聽器方法必須傳回布林值。 +這個原因取決於事件。 +少數執行此事件的原因如下:

+
    +
  • {@link android.view.View.OnLongClickListener#onLongClick(View) onLongClick()} - 這會傳回一個布林值,指出您是否已使用此事件,未來不應該繼續執行。 +亦即,傳回 true 指出您已處理事件,應該在這裡停止;如果您未處理事件和/或事件應該繼續在任何其他 on-click 接聽器上執行,則會傳回 false。 + + +
  • +
  • {@link android.view.View.OnKeyListener#onKey(View,int,KeyEvent) onKey()} - 這會傳回一個布林值,指出您是否已使用此事件,未來不應該繼續執行。 + + 亦即,傳回 true 指出您已處理事件,應該在這裡停止;如果您未處理事件和/或事件應該繼續在任何其他 on-key 接聽器上執行,則會傳回 false。 + +
  • +
  • {@link android.view.View.OnTouchListener#onTouch(View,MotionEvent) onTouch()} - 這會傳回一個布林值,指出您的接聽器是否已使用此事件。 +最重要的是,這個事件可以處理彼此接續的多個動作。 +因此,收到向下動作事件時如果您傳回 false,就表示您尚未使用事件,而且對於這個事件的後續動作也不感興趣。 + +因此,您不需要在事件內執行任何其他動作,例如手指手勢或最終動作事件。 +
  • +
+ +

請記住,硬體按鍵事件一律會傳送至目前焦點中的檢視。它們會從檢視階層的最上層開始發送,然後往下直到到達適當的目的地為止。 +如果焦點現在位於您的檢視 (或檢視的子項),則您可以透過 {@link android.view.View#dispatchKeyEvent(KeyEvent) +dispatchKeyEvent()} 方法查看事件過程。 +作為透過您的檢視擷取按鍵事件的替代方法,您也可以使用 {@link android.app.Activity#onKeyDown(int,KeyEvent) onKeyDown()}{@link android.app.Activity#onKeyUp(int,KeyEvent) onKeyUp()} 接收您 Activity 內的所有事件。 + +

+ +

此外,當您思考應用程式的文字輸入時,請記住,許多裝置只有軟體輸入方法。 +這類方法不需要以按鍵為基礎;有些可能會使用語音輸入、手寫等方式。即使輸入方法出現類似鍵盤的介面,也通常不會觸發事件的 +{@link android.app.Activity#onKeyDown(int,KeyEvent) onKeyDown()} 系列。 +您不應該建置需要按下特定按鍵才能控制的 UI,除非您想將應用程式限制為使用硬體鍵盤的裝置。 + +尤其是當使用者按 Return 鍵時,不要藉助這些方法來驗證輸入;改為使用像 {@link android.view.inputmethod.EditorInfo#IME_ACTION_DONE} 的動作向輸入方法示意您應用程式預期的反應方式,讓它能夠以有意義的方式變更其 UI。 + +避免假設軟體輸入方法應該會如何運作,只要相信它能為您的應用程式提供既有的格式化文字。 +

+ +

注意:Android 會先呼叫事件處理常式,然後再從類別定義呼叫適當的預設處理常式。 +因此,從這些事件接聽器傳回 true 將會停止將事件傳播到其他事件接聽器,也會封鎖對檢視中預設事件處理常式的回呼。 + +因此,要確定您傳回 true 時要終止事件。

+ + +

事件處理常式

+ +

如果您正從檢視建置自訂元件,則您就能定義多種回呼方法做為預設事件處理常式。在自訂元件的相關文件中,您將瞭解事件處理常式的一些常見回呼,包括: + + + +

+
    +
  • {@link android.view.View#onKeyDown} - 當發生新的按鍵事件時就會進行呼叫。
  • +
  • {@link android.view.View#onKeyUp} - 當發生向上鍵事件時就會進行呼叫。
  • +
  • {@link android.view.View#onTrackballEvent} - 當發生軌跡球動作事件時就會進行呼叫。
  • +
  • {@link android.view.View#onTouchEvent} - 當發生觸控螢幕動作事件時就會進行呼叫。
  • +
  • {@link android.view.View#onFocusChanged} - 當檢視獲得或失去焦點時就會進行呼叫。
  • +
+

您還必須注意其他一些方法,這些方法不屬於檢視類別,但會直接影響您能夠處理事件的方式。 +因此,在版面配置中管理更複雜的事件時,請考量下列其他方法: +

+
    +
  • {@link android.app.Activity#dispatchTouchEvent(MotionEvent) + Activity.dispatchTouchEvent(MotionEvent)} - 這讓您的 {@link +android.app.Activity} 能在發送至視窗之前攔截所有輕觸事件。
  • +
  • {@link android.view.ViewGroup#onInterceptTouchEvent(MotionEvent) + ViewGroup.onInterceptTouchEvent(MotionEvent)} - 這讓 {@link +android.view.ViewGroup} 在發送至子檢視時能監控事件。
  • +
  • {@link android.view.ViewParent#requestDisallowInterceptTouchEvent(boolean) + ViewParent.requestDisallowInterceptTouchEvent(boolean)} - 呼叫這個父檢視以指出不應該使用 {@link + android.view.ViewGroup#onInterceptTouchEvent(MotionEvent)} 攔截輕觸事件。 +
  • +
+ +

輕觸模式

+

+使用者以方向鍵或軌跡球瀏覽使用者介面時,必須對可動作的項目 (例如按鈕) 提供焦點,這樣使用者就能看到什麼項目將接受輸入。 + +不過,如果裝置具有輕觸功能,而且使用者透過輕觸方式開始與介面互動,就不需要再將項目反白顯示,或是對特定檢視提供焦點。 + +因此,這就是名稱為「輕觸模式」的互動模式。 + +

+

+如果是具備輕觸功能的裝置,使用者輕觸螢幕之後,裝置就會進入輕觸模式。 +從這點以此類推,只有 +{@link android.view.View#isFocusableInTouchMode} 為 true 的檢視才可設定焦點,例如文字編輯小工具。 +其他可輕觸的檢視,例如按鈕,在輕觸時不會成為焦點;按下時,只會觸發 on-click 接聽器。 + +

+

+使用者在任何時候點擊方向鍵或使用軌跡球捲動時,裝置會結束輕觸模式,然後找尋要成為焦點的檢視。 +現在,使用者可繼續與使用者介面互動,而不必輕觸螢幕。 + +

+

+整個系統 (所有視窗和 Activity) 都會保留輕觸模式的狀態。如要查詢目前的狀態,您可以呼叫 +{@link android.view.View#isInTouchMode} 以查看裝置目前是否處於輕觸模式。 + +

+ + +

處理焦點

+ +

架構會處理慣例焦點移動以回應使用者輸入。 +這包括在移除或隱藏檢視時變更焦點,或是新檢視可用時。 +檢視可透過 {@link android.view.View#isFocusable()} 方法指出它們成為焦點的意願。 +如要變更檢視是否可成為焦點,可呼叫 {@link android.view.View#setFocusable(boolean) setFocusable()}。 +處於輕觸模式時,您可使用 {@link android.view.View#isFocusableInTouchMode()} 查詢檢視是否允許成為焦點。 + +您可以使用 {@link android.view.View#setFocusableInTouchMode(boolean) setFocusableInTouchMode()} 進行變更。 +

+ +

焦點移動是以演算法為依據,在指定的方向尋找最接近的項目。 +在少數情況下,預設的演算法與開發人員預期的行為可能不符。 +在這些情況下,您可以在版面配置檔案使用下列 XML 屬性進行明確的覆寫: + +nextFocusDownnextFocusLeftnextFocusRight和 +nextFocusUp。在移出焦點的檢視中,新增下列其中一個屬性。 +將屬性的值定義為應該成為焦點之檢視的 ID。 +例如:

+
+<LinearLayout
+    android:orientation="vertical"
+    ... >
+  <Button android:id="@+id/top"
+          android:nextFocusUp="@+id/bottom"
+          ... />
+  <Button android:id="@+id/bottom"
+          android:nextFocusDown="@+id/top"
+          ... />
+</LinearLayout>
+
+ +

一般來說,在這個直式版面配置中,從第一個按鈕往上瀏覽或從第二個按鈕往下瀏覽都不會到任何地方。 +現在,上方按鈕已將下方按鈕定義為 + nextFocusUp (反之亦然),導覽焦點會從上到下以及從下到上循環。 +

+ +

如果您想在 UI 中將檢視宣告為可設定焦點 (傳統上不行),可在您的版面配置宣告中將 android:focusable XML 屬性新增至檢視。 + +將值設定為 true。您也可以使用 android:focusableInTouchMode 在輕觸模式中將檢視宣告為可設定焦點。 +

+

如要要求特定檢視成為焦點,可呼叫 {@link android.view.View#requestFocus()}

+

如要接聽焦點事件 (在檢視獲得或失去焦點時收到通知),可使用 +{@link android.view.View.OnFocusChangeListener#onFocusChange(View,boolean) onFocusChange()} (如上述事件接聽器中所討論)。 +

+ + + + diff --git a/docs/html-intl/intl/zh-tw/sdk/index.jd b/docs/html-intl/intl/zh-tw/sdk/index.jd new file mode 100644 index 0000000000000000000000000000000000000000..7114c99e65530b2c5a9767c3f15aceba0a9e71b0 --- /dev/null +++ b/docs/html-intl/intl/zh-tw/sdk/index.jd @@ -0,0 +1,487 @@ +page.title=下載 Android Studio 和 SDK 工具 +page.tags=sdk, android studio +page.template=sdk +header.hide=1 +page.metaDescription=下載官方 Android IDE 和開發人員工具以建置適用於 Android 手機、平板電腦、穿戴式裝置、電視等裝置的應用程式。 + +studio.version=1.1.0 + +studio.linux_bundle_download=android-studio-ide-135.1740770-linux.zip +studio.linux_bundle_bytes=259336386 +studio.linux_bundle_checksum=e8d166559c50a484f83ebfec6731cc0e3f259208 + +studio.mac_bundle_download=android-studio-ide-135.1740770-mac.dmg +studio.mac_bundle_bytes=261303345 +studio.mac_bundle_checksum=f9745d0fec1eefd498f6160a2d6a1b5247d4cda3 + +studio.win_bundle_exe_download=android-studio-bundle-135.1740770-windows.exe +studio.win_bundle_exe_bytes=856233768 +studio.win_bundle_exe_checksum=7484b9989d2914e1de30995fbaa97a271a514b3f + +studio.win_notools_exe_download=android-studio-ide-135.1740770-windows.exe +studio.win_notools_exe_bytes=242135128 +studio.win_notools_exe_checksum=5ea77661cd2300cea09e8e34f4a2219a0813911f + +studio.win_bundle_download=android-studio-ide-135.1740770-windows.zip +studio.win_bundle_bytes=261667054 +studio.win_bundle_checksum=e903f17cc6a57c7e3d460c4555386e3e65c6b4eb + + + +sdk.linux_download=android-sdk_r24.1.2-linux.tgz +sdk.linux_bytes=168121693 +sdk.linux_checksum=68980e4a26cca0182abb1032abffbb72a1240c51 + +sdk.mac_download=android-sdk_r24.1.2-macosx.zip +sdk.mac_bytes=89151287 +sdk.mac_checksum=00e43ff1557e8cba7da53e4f64f3a34498048256 + +sdk.win_download=android-sdk_r24.1.2-windows.zip +sdk.win_bytes=159778618 +sdk.win_checksum=704f6c874373b98e061fe2e7eb34f9fcb907a341 + +sdk.win_installer=installer_r24.1.2-windows.exe +sdk.win_installer_bytes=111364285 +sdk.win_installer_checksum=e0ec864efa0e7449db2d7ed069c03b1f4d36f0cd + + + + + + +@jd:body + + + + + + + +
+ + + + + + + + + +
+ +
 
+ + + +
+ +

Android Studio

+ +

官方 Android IDE

+ +
    +
  • Android Studio IDE
  • +
  • Android SDK 工具
  • +
  • Android 5.0 (Lollipop) 平台
  • +
  • 含 Google API 的 Android 5.0 模擬器系統映像檔
  • +
+ + +下載 Android Studio + + +

+想要取得 Android Studio 或獨立 SDK 工具,請造訪 developer.android.com/sdk/ +

+ + + +
+ + + + +

智慧型程式碼編輯器

+ +
+ +
+ +
+

Android Studio 的核心是一個智慧型程式碼編輯器,能夠進行進階的程式碼自動完成、重構及程式碼分析作業。 +

+

這項功能強大的程式碼編輯器可協助您成為更具生產力的 Android 應用程式開發人員。

+
+ + + + + +

程式碼範本與 GitHub 整合

+ +
+ +
+ +
+

全新的專案精靈可協助您及早發起新專案。

+ +

您可以使用模式 (例如導覽匣和資料檢視巡覽區) 範本程式碼,甚至是從 GitHub 匯入 Google 程式碼來發起專案。 +

+
+ + + + +

多螢幕應用程式開發

+ +
+ +
+ +
+

建置適用於 Android 手機、平板電腦、Android Wear、Android TV、Android Auto 和 Google Glass 的應用程式。 +

+

透過 Android Studio 中新的「Android 專案檢視」和模組支援功能,管理應用程式專案和相關資源變得更加容易。 + +

+ + + + +

適用於所有形狀和大小的虛擬裝置

+ +
+ +
+ +
+

Android Studio 隨附預先設定、經過最佳化的模擬器映像檔。

+

經過更新及簡化的 Virtual Device Manager (虛擬裝置管理員) 可為常見的 Android 裝置提供預先定義的裝置設定檔。 +

+
+ + + + +

+Android 版本已隨 Gradle 更新

+ +
+ +
+ +
+

可使用同一項專案為您的 Android 應用程式建立多個具有不同功能的 APK。

+

可使用 Maven 管理應用程式依附功能。

+

可使用 Android Studio 或命令列建置 APK。

+
+ + + + +

瞭解 Android Studio

+
+ +下載 Android Studio + +
    +
  • 以 IntelliJ IDEA Community Edition (JetBrains 所推出的常用 Java IDE) 為基礎。
  • +
  • 具有彈性的 Gradle 型建置系統。
  • +
  • 可產生建置變體和多個 APK。
  • +
  • 可為 Google 各項服務和各種裝置類型提供額外的範本支援。
  • +
  • 具有支援主題編輯功能的版面配置編輯器。
  • +
  • 具有可取得效能、可用性、版本相容性資料及偵測其他問題的 Lint 工具。
  • +
  • 具有 ProGuard 和應用程式簽署功能。
  • +
  • 內建 Google Cloud Platform 支援,可讓您輕鬆整合 Google Cloud Messaging 與 App Engine。 +
  • +
+ +

+如要進一步瞭解 Android Studio 提供的功能,請參閱《Android Studio 基本概念》指南。 +

+
+ + +

如果您搭配 ADT 使用 Eclipse,請注意,Android Studio 現已成為 Android 的官方 IDE,因此請遷移至 Android Studio 以接收 IDE 的所有更新資訊。 + +如果想瞭解如何遷移專案,請參閱遷移至 Android Studio。 + +

+ + + + + + + +

系統需求

+ +

Windows

+ +
    +
  • Microsoft® Windows® 8/7/Vista/2003 (32 或 64 位元)
  • +
  • 2 GB 以上的 RAM (建議準備 4 GB 的 RAM)
  • +
  • 400 MB 的硬碟空間
  • +
  • 至少 1 GB (供 Android SDK、模擬器系統映像檔及快取使用)
  • +
  • 1280 x 800 以上的螢幕解析度
  • +
  • Java Development Kit (JDK) 7
  • +
  • 提升模擬器效能的選用配件:支援 Intel® VT-x、Intel® EM64T (Intel® 64) 及 Execute Disable (XD) Bit 功能的 Intel® 處理器 +
  • +
+ + +

Mac OS X

+ +
    +
  • Mac® OS X® 10.8.5 以上版本;最高可支援 10.9 (Mavericks)
  • +
  • 2 GB 以上的 RAM (建議準備 4 GB 的 RAM)
  • +
  • 400 MB 的硬碟空間
  • +
  • 至少 1 GB (供 Android SDK、模擬器系統映像檔及快取使用)
  • +
  • 1280 x 800 以上的螢幕解析度
  • +
  • Java Runtime Environment (JRE) 6
  • +
  • Java Development Kit (JDK) 7
  • +
  • 提升模擬器效能的選用配件:支援 Intel® VT-x、Intel® EM64T (Intel® 64) 及 Execute Disable (XD) Bit 功能的 Intel® 處理器 +
  • +
+ +

如果您使用 Mac OS,可搭配 Java Runtime Environment (JRE) 6 執行 Android Studio 以獲得最佳字型顯示效果。 +此外,您還可以設定專案使用 Java Development Kit (JDK) 6 或 JDK 7。

+ + + +

Linux

+ +
    +
  • GNOME 或 KDE 桌面
  • +
  • GNU C 程式庫 (glibc) 2.15 以上版本
  • +
  • 2 GB 以上的 RAM (建議準備 4 GB 的 RAM)
  • +
  • 400 MB 的硬碟空間
  • +
  • 至少 1 GB (供 Android SDK、模擬器系統映像檔及快取使用)
  • +
  • 1280 x 800 以上的螢幕解析度
  • +
  • Oracle® Java Development Kit (JDK) 7
  • +
+

已在 Ubuntu® 14.04 Trusty Tahr (能夠執行 32 位元應用程式的 64 位元發行版本) 上經過測試。 +

+ + + + +

其他下載選項

+ + diff --git a/docs/html-intl/intl/zh-tw/sdk/installing/adding-packages.jd b/docs/html-intl/intl/zh-tw/sdk/installing/adding-packages.jd new file mode 100644 index 0000000000000000000000000000000000000000..7b4ad0b37aa33c4581ed02ac7f93e3d811383408 --- /dev/null +++ b/docs/html-intl/intl/zh-tw/sdk/installing/adding-packages.jd @@ -0,0 +1,227 @@ +page.title=新增 SDK 封裝 + +page.tags=sdk manager +helpoutsWidget=true + +@jd:body + + + + +

+Android SDK 預設不會包括開發所需的所有內容。SDK 將工具、平台及其他元件劃分為不同的封裝。您可以使用 + Android SDK Manager 視需要下載。因此,您要先將封裝加入 Android SDK 後,才可以開始進行。 + + +

+ +

如要新增封裝,請透過下列其中一種方式啟動 Android SDK Manager:

+
    +
  • 在 Android Studio 中,按一下工具列的 [SDK Manager] +
  • +
  • 如果您不是使用 Android Studio: +
      +
    • Windows:按兩下位於 Android SDK 目錄之根目錄的 SDK Manager.exe 檔案。 +
    • +
    • Mac/Linux:開啟終端機並瀏覽到安裝 Android SDK 的 tools/ 目錄,然後執行 android sdk。 +
    • +
    +
  • +
+ +

第一次開啟 SDK Manager 時,預設會選取多個封裝。 +保留預設選取的封裝,但務必按照以下步驟確認您已經取得開發所需的所有內容: +

+ + +
    +
  1. +

    取得最新的 SDK 工具

    + + + +

    設定 Android SDK 時,您一定要下載最新的工具和平台: +

    +
      +
    1. 開啟 Tools 目錄並選取: +
        +
      • Android SDK 工具
      • +
      • Android SDK 平台工具
      • +
      • Android SDK 建置工具 (最新版本)
      • +
      +
    2. +
    3. 開啟第一個 Android X.X 資料夾 (最新版本) 並選取: +
        +
      • SDK 平台
      • +
      • 模擬器的系統映像,例如
        + ARM EABI v7a 系統映像
      • +
      +
    4. +
    +
  2. + +
  3. +

    取得額外 API 的支援程式庫

    + + + +

    Android 支援程式庫提供的 API 延伸集合,可以與大部分的 Android 版本相容。 +

    + +

    開啟 Extras 目錄並選取:

    +
      +
    • Android 支援存放庫
    • +
    • Android 支援程式庫
    • +
    + +

     

    +

     

    + +
  4. + + +
  5. +

    從 Google Play 服務取得更多 API

    + + + +

    如要使用 Google API 進行開發,您需要 Google Play 服務封裝:

    +

    開啟 Extras 目錄並選取:

    +
      +
    • Google 存放庫
    • +
    • Google Play 服務
    • +
    + +

    注意:並非所有 Android 裝置都提供 Google Play 服務,但內含 Google Play 市集的所有裝置都可以使用 Google Play 服務。 +如要在 Android 模擬器中使用這些 API,您必須從 SDK Manager 最新的 Android X.X 目錄中安裝 Google API 系統映像。 + +

    +
  6. + + +
  7. +

    安裝封裝

    +

    您選好需要的所有封裝後,就可以繼續安裝:

    +
      +
    1. 按一下 [安裝 X 封裝]。
    2. +
    3. 在下一個視窗中,按兩下左側的每個封裝名稱,以接受每項的授權合約。 +
    4. +
    5. 按一下 [安裝]。
    6. +
    +

    SDK Manager 視窗的下方會顯示下載進度。 + 請勿結束 SDK Manager,下載會因而取消。

    +
  8. + +
  9. +

    開始建置!

    + +

    Android SDK 現在已內含上述封裝,您可以開始建置 Android 應用程式了。 +若有新的工具和其他 API,只要啟動 SDK Manager 即可將新的封裝下載到 SDK。 +

    + +

    以下是繼續進行的選項:

    + +
    +
    +

    開始使用

    +

    如果您是 Android 開發新手,可參考建置第一個應用程式指南瞭解 Android 應用程式的基本概念。 +

    + +
    +
    +

    建置穿戴式應用程式

    +

    如果您準備好開始建置 Android 穿戴式裝置的應用程式,請參閱建置 Android Wear 的應用程式指南。 +

    + +
    +
    +

    使用 Google API

    +

    如要開始使用 Google API (例如「地圖」或 Play Game 服務),請參閱設定 Google Play 服務指南。 + + +

    + +
    +
    + + +
  10. + +
+ +