Halaman ini dimaksudkan sebagai panduan bagi developer untuk memahami prinsip umum yang diterapkan Dewan API dalam peninjauan API.
Selain mengikuti panduan ini saat menulis API, developer harus menjalankan alat API Lint, yang mengenkode banyak aturan ini dalam pemeriksaan yang dijalankannya terhadap API.
Anggap ini sebagai panduan untuk aturan yang dipatuhi oleh alat Lint tersebut, ditambah saran umum tentang aturan yang tidak dapat dikodifikasi ke dalam alat tersebut dengan akurasi tinggi.
Alat API Lint
API Lint
diintegrasikan ke dalam alat analisis statis Metalava dan berjalan secara otomatis
selama validasi di CI. Anda dapat menjalankannya secara manual dari
checkout platform lokal menggunakan m
checkapi
atau checkout AndroidX lokal menggunakan
./gradlew :path:to:project:checkApi
.
Aturan API
Platform Android dan banyak library Jetpack sudah ada sebelum serangkaian pedoman ini dibuat, dan kebijakan yang ditetapkan di halaman ini terus berkembang untuk memenuhi kebutuhan ekosistem Android.
Akibatnya, beberapa API yang ada mungkin tidak mengikuti panduan. Dalam kasus lain, developer aplikasi mungkin mendapatkan pengalaman pengguna yang lebih baik jika API baru tetap konsisten dengan API yang ada, daripada mematuhi pedoman secara ketat.
Gunakan penilaian Anda dan hubungi Dewan API jika ada pertanyaan sulit tentang API yang perlu diselesaikan atau pedoman yang perlu diperbarui.
Dasar-dasar API
Kategori ini berkaitan dengan aspek inti Android API.
Semua API harus diterapkan
Terlepas dari audiens API (misalnya, publik atau @SystemApi
), semua platform API harus diterapkan saat digabungkan atau diekspos sebagai API. Jangan gabungkan stub API
dengan implementasi yang akan dilakukan di lain waktu.
Platform API tanpa implementasi memiliki beberapa masalah:
- Tidak ada jaminan bahwa permukaan yang tepat atau lengkap telah terekspos. Sebelum API diuji atau digunakan oleh klien, tidak ada cara untuk memverifikasi bahwa klien memiliki API yang sesuai untuk dapat menggunakan fitur tersebut.
- API tanpa penerapan tidak dapat diuji di Pratinjau Developer.
- API tanpa implementasi tidak dapat diuji di CTS.
Semua API harus diuji
Hal ini sejalan dengan persyaratan CTS platform, kebijakan AndroidX, dan secara umum gagasan bahwa API harus diimplementasikan.
Pengujian platform API memberikan jaminan dasar bahwa platform API dapat digunakan dan kita telah menangani kasus penggunaan yang diharapkan. Pengujian keberadaan tidaklah cukup; perilaku API itu sendiri harus diuji.
Perubahan yang menambahkan API baru harus menyertakan pengujian yang sesuai dalam CL atau topik Gerrit yang sama.
API juga harus dapat diuji. Anda harus dapat menjawab pertanyaan, "Bagaimana cara developer aplikasi menguji kode yang menggunakan API Anda?"
Semua API harus didokumentasikan
Dokumentasi adalah bagian penting dari kegunaan API. Meskipun sintaksis permukaan API mungkin tampak jelas, klien baru tidak akan memahami semantik, perilaku, atau konteks di balik API.
Semua API yang dihasilkan harus mematuhi pedoman
API yang dihasilkan oleh alat harus mengikuti panduan API yang sama dengan kode yang ditulis secara manual.
Alat yang tidak disarankan untuk membuat API:
AutoValue
: melanggar pedoman dalam berbagai cara, misalnya, tidak ada cara untuk menerapkan class nilai akhir maupun builder akhir dengan cara kerja AutoValue.
Gaya kode
Kategori ini berkaitan dengan gaya kode umum yang harus digunakan developer, terutama saat menulis API publik.
Ikuti konvensi coding standar, kecuali jika dinyatakan lain
Konvensi coding Android didokumentasikan untuk kontributor eksternal di sini:
https://source.android.com/source/code-style.html
Secara keseluruhan, kami cenderung mengikuti konvensi coding Java dan Kotlin standar.
Akronim tidak boleh ditulis dengan huruf kapital dalam nama metode
Misalnya: nama metode harus runCtsTests
, bukan runCTSTests
.
Nama tidak boleh diakhiri dengan Impl
Hal ini akan mengekspos detail implementasi, jadi hindari hal tersebut.
Class
Bagian ini menjelaskan aturan tentang class, antarmuka, dan pewarisan.
Mewarisi class publik baru dari class dasar yang sesuai
Pewarisan mengekspos elemen API di subclass Anda yang mungkin tidak sesuai.
Misalnya, subclass publik baru dari FrameLayout
terlihat seperti FrameLayout
ditambah perilaku dan elemen API baru. Jika API yang diwariskan tersebut tidak sesuai
untuk kasus penggunaan Anda, wariskan dari class yang lebih tinggi dalam hierarki, misalnya,
ViewGroup
atau View
.
Jika Anda tergoda untuk mengganti metode dari class dasar untuk memunculkan
UnsupportedOperationException
, pertimbangkan kembali class dasar yang Anda gunakan.
Menggunakan class koleksi dasar
Baik mengambil koleksi sebagai argumen atau menampilkannya sebagai nilai, selalu
pilih class dasar daripada implementasi spesifik (seperti menampilkan
List<Foo>
, bukan ArrayList<Foo>
).
Gunakan class dasar yang menyatakan batasan yang sesuai untuk API. Misalnya, gunakan List
untuk API yang pengumpulannya harus diurutkan, dan gunakan Set
untuk API yang pengumpulannya harus terdiri dari elemen unik.
Di Kotlin, sebaiknya gunakan koleksi yang tidak dapat diubah. Lihat Mutabilitas koleksi untuk mengetahui detail selengkapnya.
Class abstrak versus antarmuka
Java 8 menambahkan dukungan untuk metode antarmuka default, yang memungkinkan desainer API menambahkan metode ke antarmuka sambil mempertahankan kompatibilitas biner. Kode platform dan semua library Jetpack harus menargetkan Java 8 atau yang lebih baru.
Dalam kasus ketika implementasi default tidak memiliki status, desainer API harus lebih memilih antarmuka daripada class abstrak -- yaitu, metode antarmuka default dapat diimplementasikan sebagai panggilan ke metode antarmuka lainnya.
Jika konstruktor atau status internal diperlukan oleh implementasi default, class abstrak harus digunakan.
Dalam kedua kasus tersebut, desainer API dapat memilih untuk membuat satu metode abstrak agar penggunaannya sebagai lambda lebih sederhana:
public interface AnimationEndCallback {
// Always called, must be implemented.
public void onFinished(Animation anim);
// Optional callbacks.
public default void onStopped(Animation anim) { }
public default void onCanceled(Animation anim) { }
}
Nama class harus mencerminkan apa yang diperluasnya
Misalnya, class yang memperluas Service
harus diberi nama FooService
agar
lebih jelas:
public class IntentHelper extends Service {}
public class IntentService extends Service {}
Akhiran umum
Hindari penggunaan sufiks nama class generik seperti Helper
dan Util
untuk kumpulan
metode utilitas. Sebagai gantinya, letakkan metode langsung di class terkait
atau ke dalam fungsi ekstensi Kotlin.
Jika metode menjembatani beberapa class, beri class yang berisi nama yang bermakna yang menjelaskan fungsinya.
Dalam kasus yang sangat terbatas, penggunaan akhiran Helper
mungkin sesuai:
- Digunakan untuk komposisi perilaku default
- Mungkin melibatkan delegasi perilaku yang ada ke class baru
- Mungkin memerlukan status yang dipertahankan
- Biasanya melibatkan
View
Misalnya, jika tooltip backport memerlukan persistensi status yang terkait
dengan View
dan memanggil beberapa metode pada View
untuk menginstal backport,
TooltipHelper
akan menjadi nama class yang dapat diterima.
Jangan mengekspos kode yang dihasilkan IDL sebagai API publik secara langsung
Simpan kode yang dihasilkan IDL sebagai detail implementasi. Hal ini mencakup protobuf, soket, FlatBuffers, atau platform API non-Java, non-NDK lainnya. Namun, sebagian besar IDL di Android menggunakan AIDL, jadi halaman ini berfokus pada AIDL.
Class AIDL yang dihasilkan tidak memenuhi persyaratan panduan gaya API (misalnya, class tersebut tidak dapat menggunakan overloading) dan alat AIDL tidak secara eksplisit dirancang untuk mempertahankan kompatibilitas API bahasa, sehingga Anda tidak dapat menyematkannya dalam API publik.
Sebagai gantinya, tambahkan lapisan API publik di atas antarmuka AIDL, meskipun awalnya berupa wrapper dangkal.
Antarmuka Binder
Jika antarmuka Binder
adalah detail penerapan, antarmuka tersebut dapat diubah dengan bebas di masa mendatang, dengan lapisan publik yang memungkinkan kompatibilitas mundur yang diperlukan dipertahankan. Misalnya, Anda mungkin perlu menambahkan argumen baru ke panggilan internal, atau mengoptimalkan traffic IPC dengan menggunakan batching atau streaming, menggunakan memori bersama, atau yang serupa. Semua hal ini tidak dapat dilakukan jika antarmuka AIDL Anda juga merupakan API publik.
Misalnya, jangan mengekspos FooService
sebagai API publik secara langsung:
// BAD: Public API generated from IFooService.aidl
public class IFooService {
public void doFoo(String foo);
}
Sebagai gantinya, bungkus antarmuka Binder
di dalam pengelola atau class lain:
/**
* @hide
*/
public class IFooService {
public void doFoo(String foo);
}
public IFooManager {
public void doFoo(String foo) {
mFooService.doFoo(foo);
}
}
Jika nanti diperlukan argumen baru untuk panggilan ini, overload antarmuka internal yang minimal dan praktis dapat ditambahkan ke API publik. Anda dapat menggunakan lapisan pembungkus untuk menangani masalah kompatibilitas mundur lainnya saat implementasi berkembang:
/**
* @hide
*/
public class IFooService {
public void doFoo(String foo, int flags);
}
public IFooManager {
public void doFoo(String foo) {
if (mAppTargetSdkLevel < 26) {
useOldFooLogic(); // Apps targeting API before 26 are broken otherwise
mFooService.doFoo(foo, FLAG_THAT_ONE_WEIRD_HACK);
} else {
mFooService.doFoo(foo, 0);
}
}
public void doFoo(String foo, int flags) {
mFooService.doFoo(foo, flags);
}
}
Untuk antarmuka Binder
yang bukan bagian dari platform Android (misalnya,
antarmuka layanan yang diekspor oleh layanan Google Play untuk digunakan aplikasi), persyaratan untuk antarmuka IPC yang stabil, dipublikasikan, dan diberi versi berarti
jauh lebih sulit untuk mengembangkan antarmuka itu sendiri. Namun, tetap bermanfaat untuk memiliki lapisan wrapper di sekitarnya, agar sesuai dengan panduan API lainnya dan mempermudah penggunaan API publik yang sama untuk versi baru antarmuka IPC, jika hal itu diperlukan.
Jangan gunakan objek Binder mentah di API publik
Objek Binder
tidak memiliki arti dengan sendirinya dan oleh karena itu tidak boleh
digunakan di API publik. Salah satu kasus penggunaan umum adalah menggunakan Binder
atau IBinder
sebagai
token karena memiliki semantik identitas. Daripada menggunakan objek Binder
mentah,
gunakan class token wrapper.
public final class IdentifiableObject {
public Binder getToken() {...}
}
public final class IdentifiableObjectToken {
/**
* @hide
*/
public Binder getRawValue() {...}
/**
* @hide
*/
public static IdentifiableObjectToken wrapToken(Binder rawValue) {...}
}
public final class IdentifiableObject {
public IdentifiableObjectToken getToken() {...}
}
Class pengelola harus bersifat final
Class pengelola harus dideklarasikan sebagai final
. Class pengelola berkomunikasi dengan layanan sistem dan merupakan satu-satunya titik interaksi. Tidak perlu penyesuaian, jadi deklarasikan sebagai final
.
Jangan gunakan CompletableFuture atau Future
java.util.concurrent.CompletableFuture
memiliki platform API besar yang memungkinkan mutasi arbitrer nilai masa depan dan memiliki default yang rentan error.
Sebaliknya, java.util.concurrent.Future
tidak memiliki kemampuan untuk memproses permintaan secara non-blocking,
sehingga sulit digunakan dengan kode asinkron.
Dalam kode platform dan API library level rendah yang digunakan oleh Kotlin dan
Java, sebaiknya gunakan kombinasi callback penyelesaian, Executor
, dan jika API mendukung pembatalan CancellationSignal
.
public void asyncLoadFoo(android.os.CancellationSignal cancellationSignal,
Executor callbackExecutor,
android.os.OutcomeReceiver<FooResult, Throwable> callback);
Jika Anda menargetkan Kotlin, sebaiknya gunakan fungsi suspend
.
suspend fun asyncLoadFoo(): Foo
Di library integrasi khusus Java, Anda dapat menggunakan
ListenableFuture
Guava.
public com.google.common.util.concurrent.ListenableFuture<Foo> asyncLoadFoo();
Jangan gunakan Opsional
Meskipun Optional
dapat memiliki keunggulan di beberapa permukaan API, Optional
tidak konsisten dengan area permukaan API Android yang ada. @Nullable
dan @NonNull
memberikan
bantuan alat untuk keamanan null
dan Kotlin menerapkan kontrak nullability
di tingkat compiler, sehingga Optional
tidak diperlukan.
Untuk primitif opsional, gunakan metode has
dan get
yang dipasangkan. Jika nilai tidak
ditetapkan (has
menampilkan false
), metode get
akan memunculkan
IllegalStateException
.
public boolean hasAzimuth() { ... }
public int getAzimuth() {
if (!hasAzimuth()) {
throw new IllegalStateException("azimuth is not set");
}
return azimuth;
}
Menggunakan konstruktor pribadi untuk class yang tidak dapat diinstansiasi
Class yang hanya dapat dibuat oleh Builder
, class yang hanya berisi
konstanta atau metode statis, atau class yang tidak dapat di-instantiate harus
mencakup setidaknya satu konstruktor pribadi untuk mencegah pembuatan instance menggunakan
konstruktor no-arg default.
public final class Log {
// Not instantiable.
private Log() {}
}
Singleton
Singleton tidak disarankan karena memiliki kekurangan terkait pengujian berikut:
- Konstruksi dikelola oleh class, sehingga mencegah penggunaan tiruan
- Pengujian tidak dapat bersifat hermetik karena sifat statis singleton
- Untuk mengatasi masalah ini, developer harus mengetahui detail internal singleton atau membuat wrapper di sekitarnya
Lebih memilih pola instance tunggal, yang mengandalkan class dasar abstrak untuk mengatasi masalah ini.
Instance tunggal
Class instance tunggal menggunakan class dasar abstrak dengan konstruktor private
atau
internal
dan menyediakan metode getInstance()
statis untuk mendapatkan
instance. Metode getInstance()
harus menampilkan objek yang sama pada
panggilan berikutnya.
Objek yang ditampilkan oleh getInstance()
harus berupa implementasi pribadi dari
class dasar abstrak.
class Singleton private constructor(...) {
companion object {
private val _instance: Singleton by lazy { Singleton(...) }
fun getInstance(): Singleton {
return _instance
}
}
}
abstract class SingleInstance private constructor(...) {
companion object {
private val _instance: SingleInstance by lazy { SingleInstanceImp(...) }
fun getInstance(): SingleInstance {
return _instance
}
}
}
Instance tunggal berbeda dari singleton karena developer
dapat membuat versi palsu SingleInstance
dan menggunakan framework Injeksi
Dependensi sendiri untuk mengelola penerapan tanpa harus membuat
wrapper, atau library dapat menyediakan versi palsunya sendiri dalam artefak -testing
.
Class yang melepaskan resource harus menerapkan AutoCloseable
Class yang melepaskan resource melalui close
, release
, destroy
, atau metode serupa
harus menerapkan java.lang.AutoCloseable
untuk memungkinkan developer
membersihkan resource ini secara otomatis saat menggunakan blok try-with-resources
.
Hindari pengenalan subclass View baru di android.*
Jangan memperkenalkan class baru yang mewarisi secara langsung atau tidak langsung dari
android.view.View
di API publik platform (yaitu, di android.*
).
Toolkit UI Android kini mengutamakan Compose. Fitur UI baru yang diekspos oleh platform harus diekspos sebagai API tingkat rendah yang dapat digunakan untuk mengimplementasikan Jetpack Compose dan komponen UI berbasis View secara opsional untuk developer di library Jetpack. Menawarkan komponen ini di library memberikan peluang untuk implementasi yang di-backport saat fitur platform tidak tersedia.
Kolom
Aturan ini berkaitan dengan kolom publik pada class.
Jangan mengekspos kolom mentah
Class Java tidak boleh mengekspos kolom secara langsung. Kolom harus bersifat pribadi dan dapat diakses hanya menggunakan getter dan setter publik, terlepas dari apakah kolom ini bersifat final atau tidak.
Pengecualian langka mencakup struktur data dasar yang tidak perlu meningkatkan
perilaku dalam menentukan atau mengambil kolom. Dalam kasus tersebut, kolom harus
diberi nama menggunakan konvensi penamaan variabel standar, misalnya, Point.x
dan
Point.y
.
Class Kotlin dapat mengekspos properti.
Kolom yang ditampilkan harus ditandai sebagai final
Kolom mentah sangat tidak disarankan (@see
Don't expose raw fields). Namun, dalam situasi langka saat kolom diekspos sebagai kolom publik, tandai kolom tersebut dengan final
.
Kolom internal tidak boleh diekspos
Jangan merujuk nama kolom internal di API publik.
public int mFlags;
Gunakan public, bukan protected
@see Gunakan publik, bukan terlindungi
Konstanta
Ini adalah aturan tentang konstanta publik.
Konstanta tanda tidak boleh tumpang-tindih dengan nilai int atau panjang
Flag menyiratkan bit yang dapat digabungkan ke dalam beberapa nilai gabungan. Jika tidak demikian, jangan panggil variabel atau konstanta flag
.
public static final int FLAG_SOMETHING = 2;
public static final int FLAG_SOMETHING = 3;
public static final int FLAG_PRIVATE = 1 << 2;
public static final int FLAG_PRESENTATION = 1 << 3;
Lihat @IntDef
untuk flag bitmask untuk mengetahui informasi selengkapnya tentang cara menentukan konstanta flag publik.
Konstanta final statis harus menggunakan konvensi penamaan yang dipisahkan dengan garis bawah dan semua huruf kapital
Semua kata dalam konstanta harus menggunakan huruf kapital dan beberapa kata harus dipisahkan dengan _
. Contoh:
public static final int fooThing = 5
public static final int FOO_THING = 5
Menggunakan awalan standar untuk konstanta
Banyak konstanta yang digunakan di Android adalah untuk hal-hal standar, seperti tanda, kunci, dan tindakan. Konstanta ini harus memiliki awalan standar agar lebih mudah diidentifikasi sebagai hal-hal tersebut.
Misalnya, ekstra intent harus dimulai dengan EXTRA_
. Tindakan intent harus
dimulai dengan ACTION_
. Konstanta yang digunakan dengan Context.bindService()
harus dimulai
dengan BIND_
.
Nama dan cakupan konstanta utama
Nilai konstanta string harus konsisten dengan nama konstanta itu sendiri, dan umumnya dicakup ke paket atau domain. Contoh:
public static final String FOO_THING = "foo"
tidak diberi nama secara konsisten atau memiliki cakupan yang sesuai. Sebagai gantinya, pertimbangkan:
public static final String FOO_THING = "android.fooservice.FOO_THING"
Awalan android
dalam konstanta string yang tercakup dicadangkan untuk Project Open Source Android.
Tindakan dan tambahan intent, serta entri Bundle, harus diberi namespace menggunakan nama paket yang ditentukan di dalamnya.
package android.foo.bar {
public static final String ACTION_BAZ = "android.foo.bar.action.BAZ"
public static final String EXTRA_BAZ = "android.foo.bar.extra.BAZ"
}
Gunakan public, bukan protected
@see Gunakan publik, bukan terlindungi
Menggunakan awalan yang konsisten
Semua konstanta terkait harus dimulai dengan awalan yang sama. Misalnya, untuk sekumpulan konstanta yang akan digunakan dengan nilai flag:
public static final int SOME_VALUE = 0x01;
public static final int SOME_OTHER_VALUE = 0x10;
public static final int SOME_THIRD_VALUE = 0x100;
public static final int FLAG_SOME_VALUE = 0x01;
public static final int FLAG_SOME_OTHER_VALUE = 0x10;
public static final int FLAG_SOME_THIRD_VALUE = 0x100;
@see Menggunakan awalan standar untuk konstanta
Menggunakan nama resource yang konsisten
ID, atribut, dan nilai publik harus diberi nama menggunakan konvensi penamaan camelCase, misalnya @id/accessibilityActionPageUp
atau @attr/textAppearance
, mirip dengan kolom publik di Java.
Dalam beberapa kasus, ID atau atribut publik menyertakan awalan umum yang dipisahkan oleh garis bawah:
- Nilai konfigurasi platform seperti
@string/config_recentsComponentName
di config.xml - Atribut tampilan khusus tata letak seperti
@attr/layout_marginStart
di attrs.xml
Tema dan gaya publik harus mengikuti konvensi penamaan PascalCase hierarkis, misalnya @style/Theme.Material.Light.DarkActionBar
atau
@style/Widget.Material.SearchView.ActionBar
, mirip dengan class bertingkat di
Java.
Tata letak dan resource drawable sebaiknya tidak diekspos sebagai API publik. Namun, jika harus diekspos, tata letak dan drawable publik harus diberi nama menggunakan konvensi penamaan under_score, misalnya layout/simple_list_item_1.xml
atau drawable/title_bar_tall.xml
.
Jika konstanta dapat berubah, buat konstanta tersebut menjadi dinamis
Compiler dapat menyisipkan nilai konstanta, sehingga menjaga nilai tetap sama dianggap sebagai bagian dari kontrak API. Jika nilai konstanta MIN_FOO
atau MAX_FOO
dapat berubah di masa mendatang, sebaiknya jadikan konstanta tersebut sebagai metode dinamis.
CameraManager.MAX_CAMERAS
CameraManager.getMaxCameras()
Mempertimbangkan kompatibilitas dengan versi selanjutnya untuk callback
Konstanta yang ditentukan dalam versi API mendatang tidak diketahui oleh aplikasi yang menargetkan API lama. Oleh karena itu, konstanta yang dikirim ke aplikasi harus mempertimbangkan versi API target aplikasi tersebut dan memetakan konstanta yang lebih baru ke nilai yang konsisten. Pertimbangkan skenario berikut:
Sumber SDK hipotetis:
// Added in API level 22
public static final int STATUS_SUCCESS = 1;
public static final int STATUS_FAILURE = 2;
// Added in API level 23
public static final int STATUS_FAILURE_RETRY = 3;
// Added in API level 26
public static final int STATUS_FAILURE_ABORT = 4;
Aplikasi hipotetis dengan targetSdkVersion="22"
:
if (result == STATUS_FAILURE) {
// Oh no!
} else {
// Success!
}
Dalam hal ini, aplikasi dirancang dalam batasan API level 22 dan membuat asumsi (agak) wajar bahwa hanya ada dua kemungkinan status. Namun, jika aplikasi menerima STATUS_FAILURE_RETRY
yang baru ditambahkan, aplikasi akan menafsirkannya sebagai keberhasilan.
Metode yang menampilkan konstanta dapat menangani kasus seperti ini dengan aman dengan membatasi outputnya agar sesuai dengan level API yang ditargetkan oleh aplikasi:
private int mapResultForTargetSdk(Context context, int result) {
int targetSdkVersion = context.getApplicationInfo().targetSdkVersion;
if (targetSdkVersion < 26) {
if (result == STATUS_FAILURE_ABORT) {
return STATUS_FAILURE;
}
if (targetSdkVersion < 23) {
if (result == STATUS_FAILURE_RETRY) {
return STATUS_FAILURE;
}
}
}
return result;
}
Developer tidak dapat memprediksi apakah daftar konstanta dapat berubah di masa mendatang. Jika Anda menentukan API dengan konstanta UNKNOWN
atau UNSPECIFIED
yang
terlihat seperti mencakup semua, developer menganggap bahwa konstanta yang dipublikasikan saat mereka
menulis aplikasi mereka sudah lengkap. Jika Anda tidak bersedia menetapkan ekspektasi ini,
pertimbangkan kembali apakah konstanta catch-all merupakan ide yang baik untuk API Anda.
Selain itu, library tidak dapat menentukan targetSdkVersion
-nya sendiri secara terpisah dari
aplikasi dan penanganan perubahan perilaku targetSdkVersion
dari kode library menjadi
rumit dan rentan terhadap error.
Konstanta bilangan bulat atau string
Gunakan konstanta bilangan bulat dan @IntDef
jika namespace untuk nilai tidak dapat diperluas di luar paket Anda. Gunakan konstanta string jika namespace
dibagikan atau dapat diperluas oleh kode di luar paket Anda.
Class data
Class data merepresentasikan sekumpulan properti yang tidak dapat diubah dan menyediakan sekumpulan kecil fungsi utilitas yang terdefinisi dengan baik untuk berinteraksi dengan data tersebut.
Jangan gunakan data class
di API Kotlin publik, karena compiler Kotlin tidak
menjamin kompatibilitas API bahasa atau biner untuk kode yang dihasilkan. Sebagai gantinya,
terapkan fungsi yang diperlukan secara manual.
Pembuatan instance
Di Java, class data harus menyediakan konstruktor jika ada beberapa properti
atau menggunakan pola Builder
jika ada banyak properti.
Di Kotlin, class data harus menyediakan konstruktor dengan argumen default terlepas dari jumlah properti. Class data yang ditentukan di Kotlin juga dapat memanfaatkan penyediaan builder saat menargetkan klien Java.
Memodifikasi dan menyalin
Jika data perlu diubah, berikan class
Builder
dengan konstruktor salinan (Java) atau fungsi
anggota copy()
(Kotlin) yang menampilkan objek baru.
Saat memberikan fungsi copy()
di Kotlin, argumen harus cocok dengan konstruktor
class dan default harus diisi menggunakan nilai objek saat ini:
class Typography(
val labelMedium: TextStyle = TypographyTokens.LabelMedium,
val labelSmall: TextStyle = TypographyTokens.LabelSmall
) {
fun copy(
labelMedium: TextStyle = this.labelMedium,
labelSmall: TextStyle = this.labelSmall
): Typography = Typography(
labelMedium = labelMedium,
labelSmall = labelSmall
)
}
Perilaku tambahan
Class data harus mengimplementasikan
equals()
dan hashCode()
, dan setiap properti harus
diperhitungkan dalam implementasi metode ini.
Class data dapat menerapkan toString()
dengan format yang direkomendasikan yang cocok dengan penerapan class data Kotlin, misalnya User(var1=Alex, var2=42)
.
Metode
Ini adalah aturan tentang berbagai hal spesifik dalam metode, seputar parameter, nama metode, jenis nilai yang ditampilkan, dan penentu akses.
Waktu
Aturan ini mencakup cara konsep waktu seperti tanggal dan durasi harus dinyatakan dalam API.
Lebih memilih jenis java.time.* jika memungkinkan
java.time.Duration
, java.time.Instant
, dan banyak jenis java.time.*
lainnya tersedia di semua versi platform melalui desugaring dan sebaiknya digunakan saat menyatakan waktu dalam parameter atau nilai yang ditampilkan API.
Lebih baik hanya mengekspos varian API yang menerima atau menampilkan
java.time.Duration
atau java.time.Instant
dan menghilangkan varian primitif dengan
kasus penggunaan yang sama, kecuali jika domain API adalah tempat alokasi objek dalam pola penggunaan yang dimaksudkan akan memiliki dampak performa yang sangat besar.
Metode yang menyatakan durasi harus diberi nama durasi
Jika nilai waktu menyatakan durasi waktu yang terlibat, beri nama parameter "duration", bukan "time".
ValueAnimator.setTime(java.time.Duration);
ValueAnimator.setDuration(java.time.Duration);
Pengecualian:
"timeout" cocok jika durasi secara khusus berlaku untuk nilai waktu tunggu.
"time" dengan jenis java.time.Instant
cocok saat merujuk ke titik waktu tertentu, bukan durasi.
Metode yang menyatakan durasi atau waktu sebagai primitif harus diberi nama dengan satuan waktu, dan menggunakan long
Metode yang menerima atau menampilkan durasi sebagai primitif harus menyertakan akhiran nama metode dengan unit waktu terkait (seperti Millis
, Nanos
, Seconds
) untuk mencadangkan nama yang tidak dihiasi untuk digunakan dengan java.time.Duration
. Lihat
Waktu.
Metode juga harus dianotasi dengan tepat menggunakan unit dan basis waktunya:
@CurrentTimeMillisLong
: Nilai adalah stempel waktu non-negatif yang diukur sebagai jumlah milidetik sejak 1970-01-01T00:00:00Z.@CurrentTimeSecondsLong
: Nilai adalah stempel waktu non-negatif yang diukur sebagai jumlah detik sejak 1970-01-01T00.00.00Z.@DurationMillisLong
: Nilai adalah durasi non-negatif dalam milidetik.@ElapsedRealtimeLong
: Nilai adalah stempel waktu non-negatif dalam basis waktuSystemClock.elapsedRealtime()
.@UptimeMillisLong
: Nilai adalah stempel waktu non-negatif dalam basis waktuSystemClock.uptimeMillis()
.
Parameter waktu atau nilai yang ditampilkan primitif harus menggunakan long
, bukan int
.
ValueAnimator.setDuration(@DurationMillisLong long);
ValueAnimator.setDurationNanos(long);
Metode yang menyatakan satuan waktu harus lebih memilih singkatan yang tidak disingkat untuk nama satuan
public void setIntervalNs(long intervalNs);
public void setTimeoutUs(long timeoutUs);
public void setIntervalNanos(long intervalNanos);
public void setTimeoutMicros(long timeoutMicros);
Membuat anotasi argumen waktu yang panjang
Platform ini mencakup beberapa anotasi untuk memberikan pengetikan yang lebih kuat untuk unit waktu jenis long
:
@CurrentTimeMillisLong
: Nilai adalah stempel waktu non-negatif yang diukur sebagai jumlah milidetik sejak1970-01-01T00:00:00Z
, sehingga dalam basis waktuSystem.currentTimeMillis()
.@CurrentTimeSecondsLong
: Nilai adalah stempel waktu non-negatif yang diukur sebagai jumlah detik sejak1970-01-01T00:00:00Z
.@DurationMillisLong
: Nilai adalah durasi non-negatif dalam milidetik.@ElapsedRealtimeLong
: Nilai adalah stempel waktu non-negatif dalam basis waktuSystemClock#elapsedRealtime()
.@UptimeMillisLong
: Nilai adalah stempel waktu non-negatif dalam basis waktuSystemClock#uptimeMillis()
.
Satuan ukur
Untuk semua metode yang menyatakan satuan pengukuran selain waktu, sebaiknya gunakan CamelCased awalan satuan SI.
public long[] getFrequenciesKhz();
public float getStreamVolumeDb();
Menempatkan parameter opsional di akhir overload
Jika Anda memiliki kelebihan beban metode dengan parameter opsional, pertahankan parameter tersebut di akhir dan pertahankan pengurutan yang konsisten dengan parameter lainnya:
public int doFoo(boolean flag);
public int doFoo(int id, boolean flag);
public int doFoo(boolean flag);
public int doFoo(boolean flag, int id);
Saat menambahkan kelebihan beban untuk argumen opsional, perilaku metode yang lebih sederhana harus sama persis dengan jika argumen default telah diberikan ke metode yang lebih rumit.
Akibat: Jangan membebani metode selain untuk menambahkan argumen opsional atau untuk menerima berbagai jenis argumen jika metode bersifat polimorfik. Jika metode yang kelebihan beban melakukan sesuatu yang pada dasarnya berbeda, maka beri nama baru.
Metode dengan parameter default harus dianotasi dengan @JvmOverloads (khusus Kotlin)
Metode dan konstruktor dengan parameter default harus dianotasi dengan
@JvmOverloads
untuk mempertahankan kompatibilitas biner.
Lihat Overload fungsi untuk default dalam panduan interoperabilitas Kotlin-Java resmi untuk mengetahui detail selengkapnya.
class Greeting @JvmOverloads constructor(
loudness: Int = 5
) {
@JvmOverloads
fun sayHello(prefix: String = "Dr.", name: String) = // ...
}
Jangan hapus nilai parameter default (khusus Kotlin)
Jika metode telah dikirimkan dengan parameter yang memiliki nilai default, penghapusan nilai default akan menyebabkan perubahan yang merusak sumber.
Parameter metode yang paling khas dan mengidentifikasi harus berada di urutan pertama
Jika Anda memiliki metode dengan beberapa parameter, masukkan parameter yang paling relevan terlebih dahulu. Parameter yang menentukan flag dan opsi lainnya kurang penting daripada parameter yang menjelaskan objek yang sedang ditindaklanjuti. Jika ada callback penyelesaian, letakkan di akhir.
public void openFile(int flags, String name);
public void openFileAsync(OnFileOpenedListener listener, String name, int flags);
public void setFlags(int mask, int flags);
public void openFile(String name, int flags);
public void openFileAsync(String name, int flags, OnFileOpenedListener listener);
public void setFlags(int flags, int mask);
Lihat juga: Menempatkan parameter opsional di akhir dalam kelebihan beban
Builder
Pola Builder direkomendasikan untuk membuat objek Java yang kompleks, dan biasanya digunakan di Android untuk kasus ketika:
- Properti objek yang dihasilkan tidak boleh diubah
- Ada banyak properti wajib, misalnya banyak argumen konstruktor
- Ada hubungan yang kompleks antara properti pada waktu pembuatan, misalnya, langkah verifikasi diperlukan. Perhatikan bahwa tingkat kompleksitas ini sering kali menunjukkan masalah kegunaan API.
Pertimbangkan apakah Anda memerlukan alat pembuat. Builder berguna dalam antarmuka API jika digunakan untuk:
- Mengonfigurasi hanya beberapa parameter pembuatan opsional dari sekumpulan parameter yang berpotensi besar
- Mengonfigurasi banyak parameter pembuatan opsional atau wajib diisi yang berbeda, terkadang dengan jenis yang serupa atau cocok, yang dapat membuat situs panggilan menjadi membingungkan untuk dibaca atau rentan terhadap kesalahan saat ditulis
- Mengonfigurasi pembuatan objek secara inkremental, dengan beberapa potongan kode konfigurasi yang berbeda dapat melakukan panggilan pada builder sebagai detail implementasi
- Mengizinkan jenis berkembang dengan menambahkan parameter pembuatan opsional tambahan di versi API mendatang
Jika memiliki jenis dengan tiga atau kurang parameter wajib dan tidak ada parameter opsional, Anda hampir selalu dapat melewati builder dan menggunakan konstruktor biasa.
Class yang bersumber dari Kotlin harus lebih memilih konstruktor beranotasi @JvmOverloads
dengan
argumen default daripada Builder, tetapi dapat memilih untuk meningkatkan kegunaan bagi klien Java dengan juga menyediakan Builder dalam kasus yang diuraikan sebelumnya.
class Tone @JvmOverloads constructor(
val duration: Long = 1000,
val frequency: Int = 2600,
val dtmfConfigs: List<DtmfConfig> = emptyList()
) {
class Builder {
// ...
}
}
Class builder harus menampilkan builder
Class builder harus mengaktifkan rangkaian metode dengan menampilkan objek Builder
(seperti this
) dari setiap metode kecuali build()
. Objek yang dibuat tambahan
harus diteruskan sebagai argumen -- jangan menampilkan builder objek lain.
Contoh:
public static class Builder {
public void setDuration(long);
public void setFrequency(int);
public DtmfConfigBuilder addDtmfConfig();
public Tone build();
}
public class Tone {
public static class Builder {
public Builder setDuration(long);
public Builder setFrequency(int);
public Builder addDtmfConfig(DtmfConfig);
public Tone build();
}
}
Dalam kasus yang jarang terjadi saat class builder dasar harus mendukung ekstensi, gunakan jenis nilai yang ditampilkan generik:
public abstract class Builder<T extends Builder<T>> {
abstract T setValue(int);
}
public class TypeBuilder<T extends TypeBuilder<T>> extends Builder<T> {
T setValue(int);
T setTypeSpecificValue(long);
}
Class builder harus dibuat melalui konstruktor
Untuk mempertahankan pembuatan builder yang konsisten melalui platform API Android, semua
builder harus dibuat melalui konstruktor, bukan metode
pembuat statis. Untuk API berbasis Kotlin, Builder
harus bersifat publik meskipun pengguna Kotlin
diharapkan mengandalkan builder secara implisit melalui mekanisme pembuatan
gaya DSL/metode factory. Library tidak boleh menggunakan @PublishedApi internal
untuk menyembunyikan konstruktor class Builder
secara selektif dari klien Kotlin.
public class Tone {
public static Builder builder();
public static class Builder {
}
}
public class Tone {
public static class Builder {
public Builder();
}
}
Semua argumen untuk konstruktor builder harus wajib (seperti @NonNull)
Argumen opsional, misalnya @Nullable
, harus dipindahkan ke metode setter.
Konstruktor builder harus menampilkan NullPointerException
(pertimbangkan untuk menggunakan
Objects.requireNonNull
) jika ada argumen wajib yang tidak ditentukan.
Class builder harus berupa class dalam statis akhir dari jenis yang dibangunnya
Demi organisasi logis dalam paket, class builder biasanya diekspos sebagai class dalam akhir dari jenis yang dibangun, misalnya Tone.Builder
, bukan ToneBuilder
.
Builder dapat menyertakan konstruktor untuk membuat instance baru dari instance yang ada
Builder dapat menyertakan konstruktor salinan untuk membuat instance builder baru dari builder yang ada atau objek yang dibuat. Mereka tidak boleh menyediakan metode alternatif untuk membuat instance builder dari builder atau objek build yang ada.
public class Tone {
public static class Builder {
public Builder clone();
}
public Builder toBuilder();
}
public class Tone {
public static class Builder {
public Builder(Builder original);
public Builder(Tone original);
}
}
Penyetel builder harus menggunakan argumen @Nullable jika builder memiliki konstruktor salinan
Penyetelan ulang sangat penting jika instance baru builder dapat dibuat dari instance yang ada. Jika tidak ada konstruktor salinan, maka builder dapat memiliki argumen @Nullable
atau @NonNullable
.
public static class Builder {
public Builder(Builder original);
public Builder setObjectValue(@Nullable Object value);
}
Setter builder dapat menggunakan argumen @Nullable untuk properti opsional
Sering kali lebih mudah menggunakan nilai yang dapat bernilai null untuk input derajat kedua, terutama di Kotlin, yang menggunakan argumen default, bukan builder dan kelebihan beban.
Selain itu, setter @Nullable
akan mencocokkannya dengan getter, yang harus
berupa @Nullable
untuk properti opsional.
Value createValue(@Nullable OptionalValue optionalValue) {
Value.Builder builder = new Value.Builder();
if (optionalValue != null) {
builder.setOptionalValue(optionalValue);
}
return builder.build();
}
Value createValue(@Nullable OptionalValue optionalValue) {
return new Value.Builder()
.setOptionalValue(optionalValue);
.build();
}
// Or in other cases:
Value createValue() {
return new Value.Builder()
.setOptionalValue(condition ? new OptionalValue() : null);
.build();
}
Penggunaan umum di Kotlin:
fun createValue(optionalValue: OptionalValue? = null) =
Value.Builder()
.apply { optionalValue?.let { setOptionalValue(it) } }
.build()
fun createValue(optionalValue: OptionalValue? = null) =
Value.Builder()
.setOptionalValue(optionalValue)
.build()
Nilai default (jika setter tidak dipanggil), dan arti null
, harus
didokumentasikan dengan benar di setter dan getter.
/**
* ...
*
* <p>Defaults to {@code null}, which means the optional value won't be used.
*/
Penyetel builder dapat disediakan untuk properti yang dapat diubah yang penyetelnya tersedia di class yang dibuat
Jika class Anda memiliki properti yang dapat diubah dan memerlukan class Builder
, pertama-tama tanyakan
pada diri Anda apakah class Anda sebenarnya harus memiliki properti yang dapat diubah.
Selanjutnya, jika Anda yakin bahwa Anda memerlukan properti yang dapat diubah, putuskan skenario berikut mana yang lebih cocok untuk kasus penggunaan yang Anda harapkan:
Objek yang dibuat harus dapat langsung digunakan, sehingga setter harus disediakan untuk semua properti yang relevan, baik yang dapat diubah maupun tidak.
map.put(key, new Value.Builder(requiredValue) .setImmutableProperty(immutableValue) .setUsefulMutableProperty(usefulValue) .build());
Beberapa panggilan tambahan mungkin perlu dilakukan sebelum objek yang dibuat dapat berguna, sehingga setter tidak boleh disediakan untuk properti yang dapat diubah.
Value v = new Value.Builder(requiredValue) .setImmutableProperty(immutableValue) .build(); v.setUsefulMutableProperty(usefulValue) Result r = v.performSomeAction(); Key k = callSomeMethod(r); map.put(k, v);
Jangan mencampur kedua skenario.
Value v = new Value.Builder(requiredValue)
.setImmutableProperty(immutableValue)
.setUsefulMutableProperty(usefulValue)
.build();
Result r = v.performSomeAction();
Key k = callSomeMethod(r);
map.put(k, v);
Builder tidak boleh memiliki pengambil
Getter harus ada di objek yang dibuat, bukan di builder.
Penyetel builder harus memiliki pengambil yang sesuai pada class yang dibuat
public class Tone {
public static class Builder {
public Builder setDuration(long);
public Builder setFrequency(int);
public Builder addDtmfConfig(DtmfConfig);
public Tone build();
}
}
public class Tone {
public static class Builder {
public Builder setDuration(long);
public Builder setFrequency(int);
public Builder addDtmfConfig(DtmfConfig);
public Tone build();
}
public long getDuration();
public int getFrequency();
public @NonNull List<DtmfConfig> getDtmfConfigs();
}
Penamaan metode builder
Nama metode builder harus menggunakan gaya setFoo()
, addFoo()
, atau clearFoo()
.
Class builder diharapkan mendeklarasikan metode build()
Class builder harus mendeklarasikan metode build()
yang menampilkan instance
objek yang dibuat.
Metode build() builder harus menampilkan objek @NonNull
Metode build()
builder diharapkan menampilkan instance objek yang dibuat yang tidak null. Jika objek tidak dapat dibuat karena parameter tidak valid, validasi dapat ditunda ke metode build dan IllegalStateException
harus ditampilkan.
Jangan mengekspos kunci internal
Metode di API publik tidak boleh menggunakan kata kunci synchronized
. Kata kunci
ini menyebabkan objek atau class Anda digunakan sebagai kunci, dan karena
diekspos ke orang lain, Anda mungkin mengalami efek samping yang tidak terduga jika kode lain
di luar class Anda mulai menggunakannya untuk tujuan penguncian.
Sebagai gantinya, lakukan penguncian yang diperlukan terhadap objek internal dan pribadi.
public synchronized void doThing() { ... }
private final Object mThingLock = new Object();
public void doThing() {
synchronized (mThingLock) {
...
}
}
Metode bergaya aksesor harus mengikuti panduan properti Kotlin
Saat dilihat dari sumber Kotlin, metode bergaya pengakses -- yang menggunakan awalan
get
, set
, atau is
-- juga akan tersedia sebagai properti Kotlin.
Misalnya, int getField()
yang ditentukan di Java tersedia di Kotlin sebagai
properti val field: Int
.
Oleh karena itu, dan untuk memenuhi ekspektasi developer secara umum terkait perilaku metode aksesor, metode yang menggunakan awalan metode aksesor harus berperilaku serupa dengan kolom Java. Hindari penggunaan awalan gaya aksesor saat:
- Metode memiliki efek samping -- lebih memilih nama metode yang lebih deskriptif
- Metode ini melibatkan pekerjaan yang memerlukan biaya komputasi yang tinggi -- sebaiknya gunakan
compute
- Metode ini melibatkan pemblokiran atau pekerjaan yang berjalan lama untuk menampilkan nilai, seperti IPC atau I/O lainnya -- sebaiknya gunakan
fetch
- Metode memblokir thread hingga dapat menampilkan nilai -- lebih memilih
await
- Metode ini menampilkan instance objek baru pada setiap panggilan -- lebih baik menggunakan
create
- Metode mungkin tidak berhasil menampilkan nilai -- sebaiknya gunakan
request
Perhatikan bahwa melakukan tugas yang mahal secara komputasi satu kali dan menyimpan nilai dalam cache untuk panggilan berikutnya masih dihitung sebagai melakukan tugas yang mahal secara komputasi. Jank tidak diamortisasi di seluruh frame.
Menggunakan awalan is untuk metode pengakses boolean
Ini adalah konvensi penamaan standar untuk metode dan kolom boolean di Java. Umumnya, nama metode dan variabel boolean harus ditulis sebagai pertanyaan yang dijawab oleh nilai yang ditampilkan.
Metode pengakses boolean Java harus mengikuti skema penamaan set
/is
dan
kolom harus lebih memilih is
, seperti:
// Visibility is a direct property. The object "is" visible:
void setVisible(boolean visible);
boolean isVisible();
// Factory reset protection is an indirect property.
void setFactoryResetProtectionEnabled(boolean enabled);
boolean isFactoryResetProtectionEnabled();
final boolean isAvailable;
Menggunakan set
/is
untuk metode pengakses Java atau is
untuk kolom Java akan memungkinkan metode dan kolom tersebut digunakan sebagai properti dari Kotlin:
obj.isVisible = true
obj.isFactoryResetProtectionEnabled = false
if (!obj.isAvailable) return
Properti dan metode pengakses umumnya harus menggunakan penamaan positif, misalnya Enabled
, bukan Disabled
. Penggunaan terminologi negatif membalikkan
makna true
dan false
serta membuat perilaku lebih sulit dipahami.
// Passing false here is a double-negative.
void setFactoryResetProtectionDisabled(boolean disabled);
Dalam kasus ketika boolean menjelaskan penyertaan atau kepemilikan properti, Anda dapat menggunakan has, bukan is; namun, hal ini tidak akan berfungsi dengan sintaksis properti Kotlin:
// Transient state is an indirect property used to track state
// related to the object. The object is not transient; rather,
// the object "has" transient state associated with it:
void setHasTransientState(boolean hasTransientState);
boolean hasTransientState();
Beberapa awalan alternatif yang mungkin lebih sesuai mencakup dapat dan sebaiknya:
// "Can" describes a behavior that the object may provide,
// and here is more concise than setRecordingEnabled or
// setRecordingAllowed. The object "can" record:
void setCanRecord(boolean canRecord);
boolean canRecord();
// "Should" describes a hint or property that is not strictly
// enforced, and here is more explicit than setFitWidthEnabled.
// The object "should" fit width:
void setShouldFitWidth(boolean shouldFitWidth);
boolean shouldFitWidth();
Metode yang mengalihkan perilaku atau fitur dapat menggunakan awalan is dan sufiks Enabled:
// "Enabled" describes the availability of a property, and is
// more appropriate here than "can use" or "should use" the
// property:
void setWiFiRoamingSettingEnabled(boolean enabled)
boolean isWiFiRoamingSettingEnabled()
Demikian pula, metode yang menunjukkan dependensi pada perilaku atau fitur lain dapat menggunakan awalan is dan akhiran Supported atau Required:
// "Supported" describes whether this API would work on devices that support
// multiple users. The API "supports" multi-user:
void setMultiUserSupported(boolean supported)
boolean isMultiUserSupported()
// "Required" describes whether this API depends on devices that support
// multiple users. The API "requires" multi-user:
void setMultiUserRequired(boolean required)
boolean isMultiUserRequired()
Umumnya, nama metode harus ditulis sebagai pertanyaan yang dijawab oleh nilai yang ditampilkan.
Metode properti Kotlin
Untuk properti class var foo: Foo
, Kotlin akan menghasilkan metode get
/set
menggunakan aturan yang konsisten: tambahkan get
dan ubah huruf pertama menjadi huruf besar untuk
pengambil, serta tambahkan set
dan ubah huruf pertama menjadi huruf besar untuk penyetel. Deklarasi
properti akan menghasilkan metode bernama public Foo getFoo()
dan
public void setFoo(Foo foo)
.
Jika properti berjenis Boolean
, aturan tambahan berlaku dalam pembuatan nama: jika nama properti diawali dengan is
, maka get
tidak ditambahkan untuk nama metode pengambil, nama properti itu sendiri digunakan sebagai pengambil.
Oleh karena itu, sebaiknya beri nama properti Boolean
dengan awalan is
agar
mengikuti panduan penamaan:
var isVisible: Boolean
Jika properti Anda adalah salah satu pengecualian yang disebutkan di atas dan diawali dengan
awalan yang sesuai, gunakan anotasi @get:JvmName
pada properti untuk
menentukan nama yang sesuai secara manual:
@get:JvmName("hasTransientState")
var hasTransientState: Boolean
@get:JvmName("canRecord")
var canRecord: Boolean
@get:JvmName("shouldFitWidth")
var shouldFitWidth: Boolean
Pengakses bitmask
Lihat Gunakan @IntDef
untuk flag bitmask untuk pedoman API terkait cara menentukan flag bitmask.
Penyetel
Dua metode setter harus disediakan: satu yang mengambil bitstring lengkap dan menggantikan semua flag yang ada, dan yang lainnya mengambil bitmask kustom untuk memberikan fleksibilitas yang lebih besar.
/**
* Sets the state of all scroll indicators.
* <p>
* See {@link #setScrollIndicators(int, int)} for usage information.
*
* @param indicators a bitmask of indicators that should be enabled, or
* {@code 0} to disable all indicators
* @see #setScrollIndicators(int, int)
* @see #getScrollIndicators()
*/
public void setScrollIndicators(@ScrollIndicators int indicators);
/**
* Sets the state of the scroll indicators specified by the mask. To change
* all scroll indicators at once, see {@link #setScrollIndicators(int)}.
* <p>
* When a scroll indicator is enabled, it will be displayed if the view
* can scroll in the direction of the indicator.
* <p>
* Multiple indicator types may be enabled or disabled by passing the
* logical OR of the specified types. If multiple types are specified, they
* will all be set to the same enabled state.
* <p>
* For example, to enable the top scroll indicator:
* {@code setScrollIndicators(SCROLL_INDICATOR_TOP, SCROLL_INDICATOR_TOP)}
* <p>
* To disable the top scroll indicator:
* {@code setScrollIndicators(0, SCROLL_INDICATOR_TOP)}
*
* @param indicators a bitmask of values to set; may be a single flag,
* the logical OR of multiple flags, or 0 to clear
* @param mask a bitmask indicating which indicator flags to modify
* @see #setScrollIndicators(int)
* @see #getScrollIndicators()
*/
public void setScrollIndicators(@ScrollIndicators int indicators, @ScrollIndicators int mask);
Pengambil
Satu pengambil harus disediakan untuk mendapatkan bitmask lengkap.
/**
* Returns a bitmask representing the enabled scroll indicators.
* <p>
* For example, if the top and left scroll indicators are enabled and all
* other indicators are disabled, the return value will be
* {@code View.SCROLL_INDICATOR_TOP | View.SCROLL_INDICATOR_LEFT}.
* <p>
* To check whether the bottom scroll indicator is enabled, use the value
* of {@code (getScrollIndicators() & View.SCROLL_INDICATOR_BOTTOM) != 0}.
*
* @return a bitmask representing the enabled scroll indicators
*/
@ScrollIndicators
public int getScrollIndicators();
Gunakan public, bukan protected
Selalu pilih public
daripada protected
di API publik. Akses terlindungi pada akhirnya
akan menyulitkan dalam jangka panjang, karena pelaksana harus mengganti untuk menyediakan
pengakses publik jika akses eksternal secara default akan sama
baiknya.
Ingatlah bahwa visibilitas protected
tidak mencegah developer memanggil
API -- visibilitas ini hanya membuatnya sedikit lebih mengganggu.
Menerapkan equals() dan hashCode() atau tidak menerapkan keduanya
Jika Anda mengganti salah satunya, Anda harus mengganti yang lainnya.
Menerapkan toString() untuk class data
Class data dianjurkan untuk mengganti toString()
, untuk membantu developer melakukan debug kode mereka.
Mendokumentasikan apakah output ditujukan untuk perilaku program atau proses debug
Tentukan apakah Anda ingin perilaku program bergantung pada penerapan Anda atau tidak. Misalnya, UUID.toString() dan File.toString() mendokumentasikan format spesifiknya untuk digunakan oleh program. Jika Anda hanya mengekspos informasi untuk proses debug, seperti Intent, maka sertakan dokumen yang diwarisi dari superclass.
Jangan sertakan informasi tambahan
Semua informasi yang tersedia dari toString()
juga harus tersedia melalui
API publik objek. Jika tidak, Anda mendorong developer untuk mengurai dan mengandalkan output toString()
, yang akan mencegah perubahan di masa mendatang. Praktik
yang baik adalah menerapkan toString()
hanya menggunakan API publik objek.
Mencegah ketergantungan pada output debug
Meskipun tidak mungkin mencegah developer bergantung pada output debug, termasuk System.identityHashCode
objek Anda dalam output toString()
-nya, akan membuat dua objek yang berbeda sangat tidak mungkin memiliki output toString()
yang sama.
@Override
public String toString() {
return getClass().getSimpleName() + "@" + Integer.toHexString(System.identityHashCode(this)) + " {mFoo=" + mFoo + "}";
}
Hal ini dapat secara efektif mencegah developer menulis pernyataan pengujian seperti
assertThat(a.toString()).isEqualTo(b.toString())
pada objek Anda.
Gunakan createFoo saat menampilkan objek yang baru dibuat
Gunakan awalan create
, bukan get
atau new
, untuk metode yang akan membuat nilai yang ditampilkan, misalnya dengan membuat objek baru.
Jika metode akan membuat objek untuk ditampilkan, jelaskan hal tersebut dalam nama metode.
public FooThing getFooThing() {
return new FooThing();
}
public FooThing createFooThing() {
return new FooThing();
}
Metode yang menerima objek File juga harus menerima stream
Lokasi penyimpanan data di Android tidak selalu berupa file di disk. Misalnya, konten yang diteruskan di seluruh batas pengguna ditampilkan sebagai content://
Uri
. Untuk mengaktifkan pemrosesan berbagai sumber data, API yang menerima objek File
juga harus menerima InputStream
, OutputStream
, atau keduanya.
public void setDataSource(File file)
public void setDataSource(InputStream stream)
Mengambil dan menampilkan primitif mentah, bukan versi box
Jika Anda perlu mengomunikasikan nilai yang tidak ada atau null, pertimbangkan untuk menggunakan -1
, Integer.MAX_VALUE
, atau Integer.MIN_VALUE
.
public java.lang.Integer getLength()
public void setLength(java.lang.Integer)
public int getLength()
public void setLength(int value)
Menghindari class yang setara dengan jenis primitif akan menghindari overhead memori dari class ini, akses metode ke nilai, dan yang lebih penting, autoboxing yang berasal dari casting antara jenis primitif dan objek. Menghindari perilaku ini akan menghemat memori dan alokasi sementara yang dapat menyebabkan pembersihan sampah memori yang mahal dan lebih sering.
Menggunakan anotasi untuk memperjelas parameter dan nilai yang ditampilkan yang valid
Anotasi developer ditambahkan untuk membantu mengklarifikasi nilai yang diizinkan dalam berbagai
situasi. Hal ini mempermudah alat membantu developer saat mereka memberikan nilai yang salah (misalnya, meneruskan int
arbitrer saat framework memerlukan salah satu dari serangkaian nilai konstanta tertentu). Gunakan semua anotasi berikut jika sesuai:
Nullability
Anotasi nullability eksplisit diperlukan untuk API Java, tetapi konsep nullability adalah bagian dari bahasa Kotlin dan anotasi nullability tidak boleh digunakan dalam API Kotlin.
@Nullable
: Menunjukkan bahwa nilai yang ditampilkan, parameter, atau kolom tertentu dapat bernilai null:
@Nullable
public String getName()
public void setName(@Nullable String name)
@NonNull
: Menunjukkan bahwa nilai yang ditampilkan, parameter, atau kolom tertentu tidak boleh
bernilai null. Menandai sesuatu sebagai @Nullable
relatif baru di Android, sehingga sebagian besar metode API Android tidak didokumentasikan secara konsisten. Oleh karena itu, kita memiliki
tiga status "tidak diketahui, @Nullable
, @NonNull
". Itulah sebabnya @NonNull
menjadi bagian
dari panduan API:
@NonNull
public String getName()
public void setName(@NonNull String name)
Untuk dokumentasi platform Android, anotasi parameter metode Anda akan otomatis membuat dokumentasi dalam bentuk "Nilai ini mungkin null", kecuali jika "null" secara eksplisit digunakan di tempat lain dalam dokumentasi parameter.
Metode "tidak benar-benar nullable" yang ada: Metode yang ada di API tanpa anotasi @Nullable
yang dideklarasikan dapat diberi anotasi @Nullable
jika metode dapat menampilkan null
dalam keadaan tertentu yang jelas (seperti findViewById()
). Metode @NotNull requireFoo()
pendamping yang menampilkan IllegalArgumentException
harus ditambahkan untuk developer yang tidak ingin melakukan pemeriksaan null.
Metode antarmuka: API baru harus menambahkan anotasi yang tepat saat
menerapkan metode antarmuka, seperti Parcelable.writeToParcel()
(yaitu,
metode tersebut dalam class penerapan harus berupa writeToParcel(@NonNull Parcel,
int)
, bukan writeToParcel(Parcel, int)
); API yang ada yang tidak memiliki
anotasi tidak perlu "diperbaiki".
Penerapan nullability
Di Java, metode direkomendasikan untuk melakukan validasi input untuk parameter @NonNull
menggunakan
Objects.requireNonNull()
dan menampilkan NullPointerException
saat parameter null. Hal ini dilakukan secara otomatis di Kotlin.
Referensi
ID resource: Parameter bilangan bulat yang menunjukkan ID untuk resource
tertentu harus diberi anotasi dengan definisi jenis resource yang sesuai.
Ada anotasi untuk setiap jenis resource, seperti @StringRes
,
@ColorRes
, dan @AnimRes
, selain @AnyRes
yang mencakup semua jenis resource. Contoh:
public void setTitle(@StringRes int resId)
@IntDef untuk kumpulan konstanta
Konstanta ajaib: Parameter String
dan int
yang dimaksudkan untuk menerima salah satu dari serangkaian nilai terbatas yang mungkin ditunjukkan oleh konstanta publik harus diberi anotasi dengan tepat menggunakan @StringDef
atau @IntDef
. Anotasi ini memungkinkan Anda membuat anotasi baru yang dapat Anda gunakan dan berfungsi seperti typedef untuk parameter yang diizinkan. Contoh:
/** @hide */
@IntDef(prefix = {"NAVIGATION_MODE_"}, value = {
NAVIGATION_MODE_STANDARD,
NAVIGATION_MODE_LIST,
NAVIGATION_MODE_TABS
})
@Retention(RetentionPolicy.SOURCE)
public @interface NavigationMode {}
public static final int NAVIGATION_MODE_STANDARD = 0;
public static final int NAVIGATION_MODE_LIST = 1;
public static final int NAVIGATION_MODE_TABS = 2;
@NavigationMode
public int getNavigationMode();
public void setNavigationMode(@NavigationMode int mode);
Metode direkomendasikan untuk memeriksa validitas parameter yang diberi anotasi
dan menampilkan IllegalArgumentException
jika parameter bukan bagian dari
@IntDef
@IntDef untuk tanda bitmask
Anotasi juga dapat menentukan bahwa konstanta adalah tanda, dan dapat digabungkan dengan & dan I:
/** @hide */
@IntDef(flag = true, prefix = { "FLAG_" }, value = {
FLAG_USE_LOGO,
FLAG_SHOW_HOME,
FLAG_HOME_AS_UP,
})
@Retention(RetentionPolicy.SOURCE)
public @interface DisplayOptions {}
@StringDef untuk kumpulan konstanta string
Ada juga anotasi @StringDef
, yang persis seperti @IntDef
di bagian sebelumnya, tetapi untuk konstanta String
. Anda dapat menyertakan beberapa nilai "prefix" yang digunakan untuk memancarkan dokumentasi secara otomatis untuk semua nilai.
@SdkConstant untuk konstanta SDK
@SdkConstant Anotasi kolom publik jika kolom tersebut adalah salah satu nilai SdkConstant
berikut: ACTIVITY_INTENT_ACTION
, BROADCAST_INTENT_ACTION
, SERVICE_ACTION
,
INTENT_CATEGORY
, FEATURE
.
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
public static final String ACTION_CALL = "android.intent.action.CALL";
Menyediakan nullabilitas yang kompatibel untuk penggantian
Untuk kompatibilitas API, kemampuan nilai null penggantian harus kompatibel dengan kemampuan nilai null induk saat ini. Tabel berikut menunjukkan ekspektasi kompatibilitas. Jelasnya, penggantian hanya boleh sama ketat atau lebih ketat daripada elemen yang diganti.
Jenis | Orang tua | Anak |
---|---|---|
Jenis nilai yang ditampilkan | Tidak diberi anotasi | Tidak dianotasi atau non-null |
Jenis nilai yang ditampilkan | Nullable | Nullable atau non-null |
Jenis nilai yang ditampilkan | Nonnull | Nonnull |
Argumen menyenangkan | Tidak diberi anotasi | Tidak dianotasi atau dapat bernilai null |
Argumen menyenangkan | Nullable | Nullable |
Argumen menyenangkan | Nonnull | Nullable atau non-null |
Pilih argumen yang tidak dapat bernilai null (seperti @NonNull) jika memungkinkan
Saat metode di-overload, sebaiknya semua argumen tidak boleh null.
public void startActivity(@NonNull Component component) { ... }
public void startActivity(@NonNull Component component, @NonNull Bundle options) { ... }
Aturan ini juga berlaku untuk setter properti yang kelebihan beban. Argumen utama tidak boleh null dan penghapusan properti harus diterapkan sebagai metode terpisah. Hal ini mencegah panggilan "tidak masuk akal" saat developer harus menetapkan parameter di akhir meskipun tidak diperlukan.
public void setTitleItem(@Nullable IconCompat icon, @ImageMode mode)
public void setTitleItem(@Nullable IconCompat icon, @ImageMode mode, boolean isLoading)
// Nonsense call to clear property
setTitleItem(null, MODE_RAW, false);
public void setTitleItem(@NonNull IconCompat icon, @ImageMode mode)
public void setTitleItem(@NonNull IconCompat icon, @ImageMode mode, boolean isLoading)
public void clearTitleItem()
Pilih jenis nilai yang ditampilkan non-nullable (seperti @NonNull) untuk penampung
Untuk jenis penampung seperti Bundle
atau Collection
, tampilkan penampung kosong -- dan
tidak dapat diubah, jika berlaku. Dalam kasus saat null
akan digunakan untuk
membedakan ketersediaan penampung, pertimbangkan untuk menyediakan metode boolean
terpisah.
@NonNull
public Bundle getExtras() { ... }
Anotasi nullabilitas untuk pasangan get dan set harus sama
Pasangan metode get dan set untuk satu properti logis harus selalu sesuai dalam anotasi nullabilitasnya. Jika panduan ini tidak diikuti, sintaksis properti Kotlin akan gagal, dan menambahkan anotasi nullability yang tidak sesuai ke metode properti yang ada akan menjadi perubahan yang merusak sumber bagi pengguna Kotlin.
@NonNull
public Bundle getExtras() { ... }
public void setExtras(@NonNull Bundle bundle) { ... }
Nilai yang ditampilkan dalam kondisi kegagalan atau error
Semua API harus mengizinkan aplikasi bereaksi terhadap error. Menampilkan false
, -1
, null
,
atau nilai segala jenis lainnya "terjadi kesalahan" tidak memberi tahu developer
cukup banyak tentang kegagalan untuk menetapkan ekspektasi pengguna atau melacak
keandalan aplikasi mereka secara akurat di lapangan. Saat mendesain API, bayangkan Anda sedang membangun aplikasi. Jika Anda mengalami error, apakah API memberikan informasi yang cukup untuk menampilkannya kepada pengguna atau bereaksi dengan tepat?
- Tidak masalah (dan dianjurkan) untuk menyertakan informasi mendetail dalam pesan pengecualian, tetapi developer tidak perlu menguraikannya untuk menangani error dengan tepat. Kode error verbose atau informasi lainnya harus diekspos sebagai metode.
- Pastikan opsi penanganan error yang Anda pilih memberi Anda fleksibilitas untuk
memperkenalkan jenis error baru di masa mendatang. Untuk
@IntDef
, itu berarti menyertakan nilaiOTHER
atauUNKNOWN
- saat menampilkan kode baru, Anda dapat memeriksatargetSdkVersion
pemanggil untuk menghindari menampilkan kode error yang tidak diketahui aplikasi. Untuk pengecualian, miliki superclass umum yang diimplementasikan oleh pengecualian Anda, sehingga kode apa pun yang menangani jenis tersebut juga akan menangkap dan menangani subjenis. - Developer seharusnya sulit atau tidak mungkin mengabaikan error secara tidak sengaja -- jika error Anda dikomunikasikan dengan menampilkan nilai, anotasikan metode Anda dengan
@CheckResult
.
Lebih baik memunculkan ? extends RuntimeException
saat kondisi kegagalan atau error
tercapai karena sesuatu yang salah dilakukan oleh developer, misalnya mengabaikan
batasan pada parameter input atau gagal memeriksa status yang dapat diamati.
Metode setter atau tindakan (misalnya, perform
) dapat menampilkan kode status bilangan bulat jika tindakan dapat gagal sebagai akibat dari kondisi atau status yang diperbarui secara asinkron di luar kendali developer.
Kode status harus ditentukan pada class yang berisi sebagai kolom public static final
, diberi awalan dengan ERROR_
, dan di-enum dalam anotasi @hide
@IntDef
.
Nama metode harus selalu diawali dengan kata kerja, bukan subjek
Nama metode harus selalu diawali dengan kata kerja (seperti get
,
create
, reload
, dll.), bukan objek yang Anda tindaki.
public void tableReload() {
mTable.reload();
}
public void reloadTable() {
mTable.reload();
}
Lebih memilih jenis Collection daripada array sebagai jenis nilai yang ditampilkan atau parameter
Antarmuka koleksi yang diketik secara generik memberikan beberapa keuntungan dibandingkan array, termasuk kontrak API yang lebih kuat terkait keunikan dan pengurutan, dukungan untuk generik, dan sejumlah metode praktis yang mudah digunakan developer.
Pengecualian untuk primitif
Jika elemennya adalah primitif, do lebih memilih array untuk menghindari biaya boxing otomatis. Lihat Mengambil dan menampilkan primitif mentah, bukan versi yang dikemas
Pengecualian untuk kode yang sensitif terhadap performa
Dalam skenario tertentu, saat API digunakan dalam kode yang sensitif terhadap performa (seperti grafis atau API pengukuran/tata letak/penggambaran lainnya), Anda dapat menggunakan array alih-alih koleksi untuk mengurangi alokasi dan churn memori.
Pengecualian untuk Kotlin
Array Kotlin invarian dan bahasa Kotlin menyediakan banyak API utilitas di sekitar array, sehingga array setara dengan List
dan Collection
untuk API Kotlin yang dimaksudkan untuk diakses dari Kotlin.
Lebih memilih koleksi @NonNull
Selalu pilih @NonNull
untuk objek pengumpulan. Saat menampilkan koleksi kosong, gunakan metode Collections.empty
yang sesuai untuk menampilkan objek koleksi yang berbiaya rendah, berjenis yang benar, dan tidak dapat diubah.
Jika anotasi jenis didukung, selalu pilih @NonNull
untuk elemen
koleksi.
Anda juga sebaiknya menggunakan @NonNull
saat menggunakan array, bukan koleksi (lihat
item sebelumnya). Jika alokasi objek menjadi masalah, buat konstanta dan teruskan - bagaimanapun, array kosong bersifat tetap. Contoh:
private static final int[] EMPTY_USER_IDS = new int[0];
@NonNull
public int[] getUserIds() {
int [] userIds = mService.getUserIds();
return userIds != null ? userIds : EMPTY_USER_IDS;
}
Kemampuan koleksi untuk diubah
Kotlin API harus memilih jenis nilai yang hanya dapat dibaca (bukan Mutable
) untuk koleksi secara default kecuali kontrak API secara khusus memerlukan jenis nilai yang dapat diubah.
Namun, Java API harus memilih jenis nilai yang ditampilkan dapat diubah secara default karena implementasi Java API di platform Android belum menyediakan implementasi koleksi yang tidak dapat diubah dengan mudah. Pengecualian untuk aturan ini adalah jenis nilai yang ditampilkan Collections.empty
, yang tidak dapat diubah. Jika mutabilitas
dapat dieksploitasi oleh klien -- dengan sengaja atau karena kesalahan -- untuk merusak pola penggunaan yang dimaksudkan API, API Java harus mempertimbangkan dengan cermat untuk menampilkan salinan dangkal koleksi.
@Nullable
public PermissionInfo[] getGrantedPermissions() {
return mPermissions;
}
@NonNull
public Set<PermissionInfo> getGrantedPermissions() {
if (mPermissions == null) {
return Collections.emptySet();
}
return new ArraySet<>(mPermissions);
}
Jenis nilai yang ditampilkan yang dapat diubah secara eksplisit
API yang menampilkan koleksi idealnya tidak boleh mengubah objek koleksi yang ditampilkan setelah ditampilkan. Jika kumpulan yang ditampilkan harus diubah atau digunakan kembali dengan cara tertentu -- misalnya, tampilan yang disesuaikan dari set data yang dapat diubah -- perilaku kapan konten dapat berubah harus didokumentasikan secara eksplisit atau mengikuti konvensi penamaan API yang sudah ditetapkan.
/**
* Returns a view of this object as a list of [Item]s.
*/
fun MyObject.asList(): List<Item> = MyObjectListWrapper(this)
Konvensi Kotlin .asFoo()
dijelaskan
di bawah dan memungkinkan kumpulan yang ditampilkan oleh
.asList()
berubah jika kumpulan asli berubah.
Mutabilitas objek jenis data yang ditampilkan
Mirip dengan API yang menampilkan koleksi, API yang menampilkan objek jenis data idealnya tidak boleh mengubah properti objek yang ditampilkan setelah ditampilkan.
val tempResult = DataContainer()
fun add(other: DataContainer): DataContainer {
tempResult.innerValue = innerValue + other.innerValue
return tempResult
}
fun add(other: DataContainer): DataContainer {
return DataContainer(innerValue + other.innerValue)
}
Dalam kasus yang sangat terbatas, beberapa kode yang sensitif terhadap performa dapat memperoleh manfaat dari penggabungan atau penggunaan ulang objek. Jangan tulis struktur data kumpulan objek Anda sendiri dan jangan mengekspos objek yang digunakan kembali di API publik. Dalam kedua kasus tersebut, Anda harus sangat berhati-hati dalam mengelola akses serentak.
Penggunaan jenis parameter vararg
API Kotlin dan Java dianjurkan untuk menggunakan vararg
jika
developer cenderung membuat array di situs panggilan hanya untuk
tujuan meneruskan beberapa parameter terkait dengan jenis yang sama.
public void setFeatures(Feature[] features) { ... }
// Developer code
setFeatures(new Feature[]{Features.A, Features.B, Features.C});
public void setFeatures(Feature... features) { ... }
// Developer code
setFeatures(Features.A, Features.B, Features.C);
Salinan defensif
Implementasi parameter vararg
Java dan Kotlin dikompilasi ke bytecode yang didukung array yang sama dan oleh karena itu dapat dipanggil dari kode Java dengan array yang dapat diubah. Desainer API sangat dianjurkan untuk membuat salinan dangkal defensif dari parameter array jika parameter tersebut akan dipertahankan ke kolom atau class dalam anonim.
public void setValues(SomeObject... values) {
this.values = Arrays.copyOf(values, values.length);
}
Perhatikan bahwa membuat salinan defensif tidak memberikan perlindungan apa pun terhadap modifikasi serentak antara panggilan metode awal dan pembuatan salinan, juga tidak melindungi terhadap mutasi objek yang terdapat dalam array.
Memberikan semantik yang benar dengan parameter jenis koleksi atau jenis yang ditampilkan
List<Foo>
adalah opsi default, tetapi pertimbangkan jenis lainnya untuk memberikan makna tambahan:
Gunakan
Set<Foo>
, jika API Anda tidak terpengaruh oleh urutan elemen dan tidak mengizinkan duplikat atau duplikat tidak memiliki arti.Collection<Foo>,
jika API Anda tidak terpengaruh oleh urutan dan mengizinkan duplikat.
Fungsi konversi Kotlin
Kotlin sering menggunakan .toFoo()
dan .asFoo()
untuk mendapatkan objek dengan jenis yang berbeda dari objek yang ada dengan Foo
adalah nama jenis nilai yang ditampilkan konversi. Hal ini konsisten dengan JDK
Object.toString()
yang sudah dikenal. Kotlin melangkah lebih jauh dengan menggunakannya untuk konversi primitif seperti 25.toFloat()
.
Perbedaan antara konversi yang diberi nama .toFoo()
dan .asFoo()
sangat signifikan:
Gunakan .toFoo() saat membuat objek baru yang independen
Seperti .toString()
, konversi "ke" menampilkan objek baru yang independen. Jika objek asli diubah nanti, objek baru tidak akan mencerminkan perubahan tersebut.
Demikian pula, jika objek baru diubah nanti, objek lama tidak akan mencerminkan
perubahan tersebut.
fun Foo.toBundle(): Bundle = Bundle().apply {
putInt(FOO_VALUE_KEY, value)
}
Gunakan .asFoo() saat membuat wrapper dependen, objek yang didekorasi, atau cast
Pengecoran di Kotlin dilakukan menggunakan kata kunci as
. Hal ini mencerminkan perubahan pada antarmuka, tetapi bukan perubahan pada identitas. Jika digunakan sebagai awalan dalam
fungsi ekstensi, .asFoo()
akan mendekorasi penerima. Mutasi pada
objek penerima asli akan tercermin dalam objek yang ditampilkan oleh asFoo()
.
Mutasi pada objek Foo
baru dapat tercermin dalam objek asli.
fun <T> Flow<T>.asLiveData(): LiveData<T> = liveData {
collect {
emit(it)
}
}
Fungsi konversi harus ditulis sebagai fungsi ekstensi
Menulis fungsi konversi di luar definisi class penerima dan hasil mengurangi coupling antar-jenis. Konversi yang ideal hanya memerlukan akses API publik ke objek asli. Hal ini membuktikan dengan contoh bahwa developer dapat menulis konversi analog ke jenis pilihan mereka sendiri.
Tampilkan pengecualian spesifik yang sesuai
Metode tidak boleh menampilkan pengecualian generik seperti java.lang.Exception
atau
java.lang.Throwable
, tetapi pengecualian spesifik yang sesuai harus digunakan
seperti java.lang.NullPointerException
agar developer dapat menangani pengecualian
tanpa terlalu luas.
Error yang tidak terkait dengan argumen yang diberikan langsung ke metode yang dipanggil secara publik harus menampilkan java.lang.IllegalStateException
, bukan
java.lang.IllegalArgumentException
atau java.lang.NullPointerException
.
Listener dan callback
Berikut adalah aturan seputar class dan metode yang digunakan untuk mekanisme pemroses dan callback.
Nama class callback harus tunggal
Gunakan MyObjectCallback
, bukan MyObjectCallbacks
.
Nama metode callback harus dalam format on
onFooEvent
menandakan bahwa FooEvent
sedang terjadi dan callback harus
bertindak sebagai respons.
Tense lampau versus present tense harus menjelaskan perilaku pengaturan waktu
Metode callback terkait peristiwa harus diberi nama untuk menunjukkan apakah peristiwa telah terjadi atau sedang dalam proses terjadi.
Misalnya, jika metode dipanggil setelah tindakan klik dilakukan:
public void onClicked()
Namun, jika metode bertanggung jawab untuk melakukan tindakan klik:
public boolean onClick()
Pendaftaran callback
Jika pemroses atau callback dapat ditambahkan atau dihapus dari objek, metode terkait harus diberi nama add dan remove atau register dan unregister. Konsisten dengan konvensi yang ada yang digunakan oleh class atau oleh class lain dalam paket yang sama. Jika tidak ada preseden seperti itu, pilih tambahkan dan hapus.
Metode yang melibatkan pendaftaran atau pembatalan pendaftaran callback harus menentukan seluruh nama jenis callback.
public void addFooCallback(@NonNull FooCallback callback);
public void removeFooCallback(@NonNull FooCallback callback);
public void registerFooCallback(@NonNull FooCallback callback);
public void unregisterFooCallback(@NonNull FooCallback callback);
Hindari getter untuk callback
Jangan menambahkan metode getFooCallback()
. Ini adalah jalan keluar yang menarik untuk kasus ketika developer mungkin ingin menggabungkan callback yang ada dengan penggantinya sendiri, tetapi hal ini tidak stabil dan membuat status saat ini sulit dipahami oleh developer komponen. Misalnya,
- Developer A memanggil
setFooCallback(a)
- Developer B memanggil
setFooCallback(new B(getFooCallback()))
- Developer A ingin menghapus callback
a
-nya dan tidak dapat melakukannya tanpa mengetahui jenisB
, danB
telah dibuat untuk memungkinkan modifikasi callback yang di-wrap.
Menerima Eksekutor untuk mengontrol pengiriman callback
Saat mendaftarkan callback yang tidak memiliki ekspektasi threading eksplisit (hampir
di mana saja di luar toolkit UI), sangat disarankan untuk menyertakan parameter
Executor
sebagai bagian dari pendaftaran untuk memungkinkan developer menentukan
thread tempat callback akan dipanggil.
public void registerFooCallback(
@NonNull @CallbackExecutor Executor executor,
@NonNull FooCallback callback)
Sebagai pengecualian terhadap
pedoman kami tentang parameter opsional, dapat diterima
untuk memberikan kelebihan beban yang menghilangkan Executor
meskipun bukan argumen
terakhir dalam daftar parameter. Jika Executor
tidak diberikan, callback
harus dipanggil di thread utama menggunakan Looper.getMainLooper()
dan hal ini
harus didokumentasikan pada metode yang kelebihan beban terkait.
/**
* ...
* Note that the callback will be executed on the main thread using
* {@link Looper.getMainLooper()}. To specify the execution thread, use
* {@link registerFooCallback(Executor, FooCallback)}.
* ...
*/
public void registerFooCallback(
@NonNull FooCallback callback)
public void registerFooCallback(
@NonNull @CallbackExecutor Executor executor,
@NonNull FooCallback callback)
Kesalahan penerapan Executor
: Perhatikan bahwa berikut ini adalah eksekutor yang valid.
public class SynchronousExecutor implements Executor {
@Override
public void execute(Runnable r) {
r.run();
}
}
Artinya, saat menerapkan API yang berbentuk ini, implementasi objek binder masuk Anda di sisi proses aplikasi harus memanggil
Binder.clearCallingIdentity()
sebelum memanggil callback aplikasi di
Executor
yang disediakan aplikasi. Dengan cara ini, kode aplikasi apa pun yang menggunakan identitas binder (seperti Binder.getCallingUid()
) untuk pemeriksaan izin akan mengatribusikan kode yang berjalan dengan benar ke aplikasi, bukan ke proses sistem yang memanggil ke aplikasi. Jika pengguna API Anda menginginkan informasi UID atau PID pemanggil, hal ini harus menjadi bagian eksplisit dari permukaan API Anda, bukan implisit berdasarkan tempat Executor
yang mereka berikan berjalan.
Menentukan Executor
harus didukung oleh API Anda. Dalam kasus yang penting untuk performa, aplikasi mungkin perlu menjalankan kode secara langsung atau serentak dengan masukan dari API Anda. Menerima Executor
mengizinkan hal ini.
Membuat HandlerThread
tambahan atau yang serupa dengan trampolin secara defensif dari
menggagalkan kasus penggunaan yang diinginkan ini.
Jika aplikasi akan menjalankan kode yang mahal di suatu tempat dalam prosesnya sendiri, izinkan mereka. Solusi yang akan ditemukan developer aplikasi untuk mengatasi batasan Anda akan jauh lebih sulit didukung dalam jangka panjang.
Pengecualian untuk callback tunggal: jika sifat peristiwa yang dilaporkan hanya memerlukan dukungan untuk satu instance callback, gunakan gaya berikut:
public void setFooCallback(
@NonNull @CallbackExecutor Executor executor,
@NonNull FooCallback callback)
public void clearFooCallback()
Gunakan Executor, bukan Handler
Handler
Android digunakan sebagai standar untuk mengalihkan eksekusi callback ke
thread Looper
tertentu pada masa lalu. Standar ini diubah untuk lebih memilih
Executor
karena sebagian besar developer aplikasi mengelola kumpulan thread mereka sendiri, sehingga thread utama
atau UI menjadi satu-satunya thread Looper
yang tersedia untuk aplikasi. Gunakan Executor
untuk
memberi developer kontrol yang mereka butuhkan untuk menggunakan kembali konteks eksekusi
yang ada/disukai.
Library konkurensi modern seperti kotlinx.coroutines atau RxJava menyediakan mekanisme penjadwalan sendiri yang melakukan pengiriman sendiri jika diperlukan, sehingga penting untuk menyediakan kemampuan menggunakan eksekutor langsung (seperti Runnable::run
) untuk menghindari latensi dari hop thread ganda. Misalnya, satu hop
untuk memposting ke thread Looper
menggunakan Handler
, diikuti dengan hop lain dari
framework konkurensi aplikasi.
Pengecualian terhadap pedoman ini jarang terjadi. Alasan umum pengajuan banding untuk mendapatkan pengecualian meliputi:
Saya harus menggunakan Looper
karena saya memerlukan Looper
untuk epoll
acara.
Permintaan pengecualian ini disetujui karena manfaat Executor
tidak dapat direalisasikan dalam situasi ini.
Saya tidak ingin kode aplikasi memblokir thread saya yang memublikasikan peristiwa. Permintaan pengecualian ini biasanya tidak diberikan untuk kode yang berjalan dalam proses aplikasi. Aplikasi yang salah dalam hal ini hanya merugikan diri mereka sendiri, tidak memengaruhi kesehatan sistem secara keseluruhan. Aplikasi yang melakukannya dengan benar atau menggunakan framework konkurensi umum tidak akan dikenai penalti latensi tambahan.
Handler
konsisten secara lokal dengan API serupa lainnya dalam class yang sama.
Permintaan pengecualian ini diberikan secara situasional. Lebih memilih penambahan overload berbasis Executor
, memigrasikan penerapan Handler
untuk menggunakan penerapan Executor
baru. (myHandler::post
adalah
Executor
yang valid!) Bergantung pada ukuran class, jumlah metode Handler
yang ada, dan kemungkinan developer perlu menggunakan metode berbasis Handler
yang ada bersama dengan metode baru, pengecualian dapat diberikan untuk menambahkan metode berbasis Handler
baru.
Simetri dalam pendaftaran
Jika ada cara untuk menambahkan atau mendaftarkan sesuatu, seharusnya ada juga cara untuk menghapus/membatalkan pendaftarannya. Metode
registerThing(Thing)
harus memiliki
unregisterThing(Thing)
Memberikan ID permintaan
Jika developer dapat menggunakan kembali callback, berikan objek ID untuk mengikat callback ke permintaan.
class RequestParameters {
public int getId() { ... }
}
class RequestExecutor {
public void executeRequest(
RequestParameters parameters,
Consumer<RequestParameters> onRequestCompletedListener) { ... }
}
Objek callback multi-metode
Callback multi-metode harus lebih memilih interface
dan menggunakan metode default
saat menambahkan ke antarmuka yang dirilis sebelumnya. Sebelumnya, panduan ini merekomendasikan abstract class
karena tidak adanya metode default
di Java 7.
public interface MostlyOptionalCallback {
void onImportantAction();
default void onOptionalInformation() {
// Empty stub, this method is optional.
}
}
Menggunakan android.os.OutcomeReceiver saat membuat model panggilan fungsi non-pemblokiran
OutcomeReceiver<R,E>
melaporkan nilai hasil R
jika berhasil atau E : Throwable
jika tidak - hal yang sama dapat dilakukan oleh panggilan metode biasa. Gunakan OutcomeReceiver
sebagai jenis callback saat mengonversi metode pemblokiran yang menampilkan hasil atau menampilkan pengecualian ke metode asinkron non-pemblokiran:
interface FooType {
// Before:
public FooResult requestFoo(FooRequest request);
// After:
public void requestFooAsync(FooRequest request, Executor executor,
OutcomeReceiver<FooResult, Throwable> callback);
}
Metode asinkron yang dikonversi dengan cara ini selalu menampilkan void
. Setiap hasil yang akan ditampilkan oleh requestFoo
dilaporkan ke OutcomeReceiver.onResult
parameter callback
requestFooAsync
dengan memanggilnya di executor
yang diberikan.
Pengecualian apa pun yang akan diterapkan oleh requestFoo
akan dilaporkan ke metode
OutcomeReceiver.onError
dengan cara yang sama.
Penggunaan OutcomeReceiver
untuk melaporkan hasil metode asinkron juga menyediakan wrapper
suspend fun
Kotlin untuk metode asinkron menggunakan ekstensi
Continuation.asOutcomeReceiver
dari androidx.core:core-ktx
:
suspend fun FooType.requestFoo(request: FooRequest): FooResult =
suspendCancellableCoroutine { continuation ->
requestFooAsync(request, Runnable::run, continuation.asOutcomeReceiver())
}
Ekstensi seperti ini memungkinkan klien Kotlin memanggil metode asinkron non-pemblokiran
dengan kemudahan panggilan fungsi biasa tanpa memblokir
thread panggilan. Ekstensi 1-1 untuk API platform ini dapat ditawarkan sebagai bagian dari artefak
androidx.core:core-ktx
di Jetpack jika digabungkan dengan pemeriksaan dan pertimbangan
kompatibilitas versi standar. Lihat dokumentasi untuk
asOutcomeReceiver
untuk mengetahui informasi selengkapnya, pertimbangan pembatalan, dan contoh.
Metode asinkron yang tidak cocok dengan semantik metode yang menampilkan hasil atau
mengecualikan pengecualian saat tugasnya selesai tidak boleh menggunakan
OutcomeReceiver
sebagai jenis callback. Sebagai gantinya, pertimbangkan salah satu opsi lain yang tercantum di bagian berikut.
Lebih memilih antarmuka fungsional daripada membuat jenis metode abstrak tunggal (SAM) baru
Level API 24 menambahkan jenis java.util.function.*
(dokumen referensi)
, yang menawarkan antarmuka SAM generik seperti Consumer<T>
yang
cocok untuk digunakan sebagai lambda callback. Dalam banyak kasus, membuat antarmuka SAM baru
kurang memberikan nilai dalam hal keamanan jenis atau menyampaikan maksud, sekaligus
memperluas area permukaan API Android secara tidak perlu.
Pertimbangkan untuk menggunakan antarmuka generik ini, daripada membuat yang baru:
Runnable
:() -> Unit
Supplier<R>
:() -> R
Consumer<T>
:(T) -> Unit
Function<T,R>
:(T) -> R
Predicate<T>
:(T) -> Boolean
- banyak lagi yang tersedia di dokumen referensi
Penempatan parameter SAM
Parameter SAM harus ditempatkan terakhir untuk memungkinkan penggunaan idiomatis dari Kotlin, meskipun metode tersebut kelebihan beban dengan parameter tambahan.
public void schedule(Runnable runnable)
public void schedule(int delay, Runnable runnable)
Dokumen
Berikut adalah aturan tentang dokumen publik (Javadoc) untuk API.
Semua API publik harus didokumentasikan
Semua API publik harus memiliki dokumentasi yang memadai untuk menjelaskan cara developer menggunakan API. Asumsikan developer menemukan metode menggunakan pelengkapan otomatis atau saat menjelajahi dokumen referensi API dan memiliki sedikit konteks dari permukaan API yang berdekatan (misalnya, class yang sama).
Metode
Parameter metode dan nilai yang ditampilkan harus didokumentasikan menggunakan anotasi dokumen @param
dan @return
. Format isi Javadoc seolah-olah diawali dengan "Metode ini...".
Jika metode tidak menggunakan parameter, tidak memiliki pertimbangan khusus, dan
menampilkan apa yang dikatakan oleh nama metode, Anda dapat menghapus @return
dan
menulis dokumen yang mirip dengan:
/**
* Returns the priority of the thread.
*/
@IntRange(from = 1, to = 10)
public int getPriority() { ... }
Selalu gunakan link di Javadoc
Dokumen harus ditautkan ke dokumen lain untuk konstanta, metode, dan elemen
terkait lainnya. Gunakan tag Javadoc (misalnya, @see
dan {@link foo}
), bukan hanya
kata-kata teks biasa.
Untuk contoh sumber berikut:
public static final int FOO = 0;
public static final int BAR = 1;
Jangan menggunakan teks mentah atau font kode:
/**
* Sets value to one of FOO or <code>BAR</code>.
*
* @param value the value being set, one of FOO or BAR
*/
public void setValue(int value) { ... }
Sebagai gantinya, gunakan link:
/**
* Sets value to one of {@link #FOO} or {@link #BAR}.
*
* @param value the value being set
*/
public void setValue(@ValueType int value) { ... }
Perhatikan bahwa penggunaan anotasi IntDef
seperti @ValueType
pada parameter akan otomatis menghasilkan dokumentasi yang menentukan jenis yang diizinkan. Lihat
panduan tentang anotasi untuk mengetahui informasi selengkapnya tentang IntDef
.
Jalankan target update-api atau docs saat menambahkan Javadoc
Aturan ini sangat penting saat menambahkan tag @link
atau @see
, dan pastikan output terlihat seperti yang diharapkan. Output ERROR di Javadoc sering kali disebabkan oleh link yang salah. Target update-api
atau docs
Make melakukan pemeriksaan ini, tetapi
target docs
mungkin lebih cepat jika Anda hanya mengubah Javadoc dan tidak
perlu menjalankan target update-api
.
Gunakan {@code foo} untuk membedakan nilai Java
Bungkus nilai Java seperti true
, false
, dan null
dengan {@code...}
untuk
membedakannya dari teks dokumentasi.
Saat menulis dokumentasi di sumber Kotlin, Anda dapat membungkus kode dengan tanda petik terbalik seperti yang Anda lakukan untuk Markdown.
Ringkasan @param dan @return harus berupa fragmen kalimat tunggal
Ringkasan parameter dan nilai yang ditampilkan harus diawali dengan karakter huruf kecil dan hanya berisi satu fragmen kalimat. Jika Anda memiliki informasi tambahan yang melampaui satu kalimat, pindahkan ke isi Javadoc metode:
/**
* @param e The element to be appended to the list. This must not be
* null. If the list contains no entries, this element will
* be added at the beginning.
* @return This method returns true on success.
*/
Harus diubah menjadi:
/**
* @param e element to be appended to this list, must be non-{@code null}
* @return {@code true} on success, {@code false} otherwise
*/
Anotasi Dokumen memerlukan penjelasan
Mendokumentasikan alasan anotasi @hide
dan @removed
disembunyikan dari API publik.
Sertakan petunjuk tentang cara mengganti elemen API yang ditandai dengan anotasi @deprecated
.
Menggunakan @throws untuk mendokumentasikan pengecualian
Jika metode menampilkan pengecualian yang diperiksa, misalnya IOException
, dokumentasikan
pengecualian dengan @throws
. Untuk API yang bersumber dari Kotlin yang ditujukan untuk digunakan oleh
klien Java, anotasi fungsi dengan
@Throws
.
Jika metode menampilkan pengecualian yang tidak diperiksa yang menunjukkan error yang dapat dicegah, misalnya IllegalArgumentException
atau IllegalStateException
, dokumentasikan pengecualian dengan penjelasan mengapa pengecualian ditampilkan. Pengecualian
yang dilempar juga harus menunjukkan alasan pengecualian tersebut dilempar.
Kasus pengecualian yang tidak diperiksa tertentu dianggap implisit dan tidak perlu didokumentasikan, seperti NullPointerException
atau IllegalArgumentException
jika argumen tidak cocok dengan @IntDef
atau anotasi serupa yang menyematkan
kontrak API ke dalam tanda tangan metode:
/**
* ...
* @throws IOException If it cannot find the schema for {@code toVersion}
* @throws IllegalStateException If the schema validation fails
*/
public SupportSQLiteDatabase runMigrationsAndValidate(String name, int version,
boolean validateDroppedTables, Migration... migrations) throws IOException {
// ...
if (!dbPath.exists()) {
throw new IllegalStateException("Cannot find the database file for " + name
+ ". Before calling runMigrations, you must first create the database "
+ "using createDatabase.");
}
// ...
Atau, di Kotlin:
/**
* ...
* @throws IOException If something goes wrong reading the file, such as a bad
* database header or missing permissions
*/
@Throws(IOException::class)
fun readVersion(databaseFile: File): Int {
// ...
val read = input.read(buffer)
if (read != 4) {
throw IOException("Bad database header, unable to read 4 bytes at " +
"offset 60")
}
}
// ...
Jika metode memanggil kode asinkron yang dapat memunculkan pengecualian, pertimbangkan
cara developer mengetahui dan merespons pengecualian tersebut. Biasanya, hal ini melibatkan penerusan pengecualian ke callback dan mendokumentasikan pengecualian yang ditampilkan pada metode yang menerimanya. Pengecualian asinkron
tidak boleh didokumentasikan dengan @throws
kecuali jika benar-benar ditampilkan ulang dari
metode yang diberi anotasi.
Akhiri kalimat pertama dokumen dengan titik
Alat Doclava mengurai dokumen secara sederhana, mengakhiri dokumen sinopsis (kalimat pertama, yang digunakan dalam deskripsi singkat di bagian atas dokumen class) segera setelah melihat titik (.) diikuti dengan spasi. Hal ini menyebabkan dua masalah:
- Jika dokumen singkat tidak diakhiri dengan titik, dan jika anggota tersebut telah mewarisi
dokumen yang diambil oleh alat, maka sinopsis juga akan mengambil dokumen yang
diwarisi tersebut. Misalnya, lihat
actionBarTabStyle
di dokumenR.attr
, yang memiliki deskripsi dimensi yang ditambahkan ke sinopsis. - Hindari "misalnya" dalam kalimat pertama karena alasan yang sama, karena Doclava mengakhiri dokumen sinopsis setelah "misalnya". Misalnya, lihat
TEXT_ALIGNMENT_CENTER
diView.java
. Perhatikan bahwa Metalava otomatis mengoreksi error ini dengan menyisipkan spasi tanpa putus setelah titik; namun, jangan membuat kesalahan ini sejak awal.
Memformat dokumen agar dirender dalam HTML
Javadoc dirender dalam HTML, jadi format dokumen ini dengan tepat:
Baris baru harus menggunakan tag
<p>
eksplisit. Jangan menambahkan tag penutup</p>
.Jangan gunakan ASCII untuk merender daftar atau tabel.
Daftar harus menggunakan
<ul>
atau<ol>
untuk daftar tidak berurutan dan berurutan. Setiap item harus diawali dengan tag<li>
, tetapi tidak memerlukan tag penutup</li>
. Tag penutup</ul>
atau</ol>
diperlukan setelah item terakhir.Tabel harus menggunakan
<table>
,<tr>
untuk baris,<th>
untuk header, dan<td>
untuk sel. Semua tag tabel memerlukan tag penutup yang cocok. Anda dapat menggunakanclass="deprecated"
pada tag apa pun untuk menunjukkan penghentian penggunaan.Untuk membuat font kode inline, gunakan
{@code foo}
.Untuk membuat blok kode, gunakan
<pre>
.Semua teks di dalam blok
<pre>
diuraikan oleh browser, jadi berhati-hatilah dengan tanda kurung<>
. Anda dapat mengganti karakter tersebut dengan entity HTML<
dan>
.Atau, Anda dapat membiarkan tanda kurung mentah
<>
dalam cuplikan kode jika Anda membungkus bagian yang bermasalah dalam{@code foo}
. Contoh:<pre>{@code <manifest>}</pre>
Mengikuti panduan gaya referensi API
Untuk memberikan konsistensi dalam gaya untuk ringkasan class, deskripsi metode, deskripsi parameter, dan item lainnya, ikuti rekomendasi dalam panduan bahasa Java resmi di Cara Menulis Komentar Dokumen untuk Alat Javadoc.
Aturan khusus Framework Android
Aturan ini berkaitan dengan API, pola, dan struktur data yang khusus untuk
API dan perilaku yang dibuat ke dalam framework Android (misalnya, Bundle
atau
Parcelable
).
Pembuat intent harus menggunakan pola create*Intent()
Pembuat untuk intent harus menggunakan metode yang bernama createFooIntent()
.
Gunakan Bundle, bukan membuat struktur data serbaguna baru
Hindari pembuatan struktur data serbaguna baru untuk merepresentasikan pemetaan kunci ke nilai yang diketik secara arbitrer. Sebagai gantinya, pertimbangkan untuk menggunakan Bundle
.
Hal ini biasanya muncul saat menulis API platform yang berfungsi sebagai saluran komunikasi antara aplikasi dan layanan non-platform, di mana platform tidak membaca data yang dikirim melalui saluran tersebut dan kontrak API mungkin sebagian ditentukan di luar platform (misalnya, di library Jetpack).
Jika platform membaca data, hindari penggunaan Bundle
dan
pilih class data dengan jenis yang dikenali.
Implementasi Parcelable harus memiliki kolom CREATOR publik
Inflasi Parcelable diekspos melalui CREATOR
, bukan konstruktor mentah. Jika class mengimplementasikan Parcelable
, maka kolom CREATOR
-nya juga harus berupa API publik dan konstruktor class yang mengambil argumen Parcel
harus bersifat pribadi.
Menggunakan CharSequence untuk string UI
Saat string ditampilkan di antarmuka pengguna, gunakan CharSequence
untuk memungkinkan instance Spannable
.
Jika hanya berupa kunci atau label atau nilai lain yang tidak terlihat oleh pengguna,
String
tidak masalah.
Hindari penggunaan Enum
IntDef
harus digunakan di atas enum di semua API platform, dan harus dipertimbangkan secara matang
dalam API library yang tidak digabungkan. Gunakan enum hanya jika Anda yakin bahwa nilai baru tidak akan ditambahkan.
ManfaatIntDef
:
- Memungkinkan penambahan nilai dari waktu ke waktu
- Pernyataan
when
Kotlin dapat gagal saat runtime jika tidak lagi menyeluruh karena nilai enum yang ditambahkan di platform.
- Pernyataan
- Tidak ada class atau objek yang digunakan saat runtime, hanya primitif
- Meskipun R8 atau minifikasi dapat menghindari biaya ini untuk API library yang tidak digabungkan, pengoptimalan ini tidak dapat memengaruhi class API platform.
Manfaat Enum
- Fitur bahasa idiomatik Java, Kotlin
- Mengaktifkan penggunaan pernyataan
when
switch- yang lengkap
- Catatan - nilai tidak boleh berubah dari waktu ke waktu, lihat daftar sebelumnya
- Penamaan yang tercakup dengan jelas dan dapat ditemukan
- Memungkinkan verifikasi waktu kompilasi
- Misalnya, pernyataan
when
di Kotlin yang menampilkan nilai
- Misalnya, pernyataan
- Adalah class berfungsi yang dapat mengimplementasikan antarmuka, memiliki helper statis, mengekspos metode anggota atau ekstensi, dan mengekspos kolom.
Mengikuti hierarki pelapisan paket Android
Hierarki paket android.*
memiliki pengurutan implisit, di mana paket tingkat yang lebih rendah tidak dapat bergantung pada paket tingkat yang lebih tinggi.
Hindari merujuk ke Google, perusahaan lain, dan produknya
Platform Android adalah project open source dan bertujuan untuk tidak terikat dengan vendor. API harus bersifat umum dan dapat digunakan secara setara oleh integrator sistem atau aplikasi dengan izin yang diperlukan.
Implementasi Parcelable harus final
Class Parcelable yang ditentukan oleh platform selalu dimuat dari
framework.jar
, sehingga tidak valid bagi aplikasi untuk mencoba mengganti implementasi
Parcelable
.
Jika aplikasi pengirim memperluas Parcelable
, aplikasi penerima tidak akan memiliki
implementasi kustom pengirim untuk diekstrak. Catatan tentang kompatibilitas
mundur: jika kelas Anda secara historis bukan final, tetapi tidak memiliki
konstruktor yang tersedia secara publik, Anda tetap dapat menandainya sebagai final
.
Metode yang memanggil ke dalam proses sistem harus melempar ulang RemoteException sebagai RuntimeException
RemoteException
biasanya ditampilkan oleh AIDL internal, dan menunjukkan bahwa proses sistem telah berhenti, atau aplikasi mencoba mengirim terlalu banyak data. Dalam kedua kasus tersebut, API publik harus melempar ulang sebagai RuntimeException
untuk mencegah aplikasi mempertahankan keputusan keamanan atau kebijakan.
Jika Anda tahu bahwa sisi lain panggilan Binder
adalah proses sistem, kode standar ini adalah praktik terbaik:
try {
...
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
Menampilkan pengecualian tertentu untuk perubahan API
Perilaku API publik dapat berubah di berbagai level API dan menyebabkan aplikasi error (misalnya untuk menerapkan kebijakan keamanan baru).
Jika API perlu menampilkan pengecualian untuk permintaan yang sebelumnya valid, tampilkan pengecualian spesifik baru, bukan pengecualian umum. Misalnya, ExportedFlagRequired
bukan SecurityException
(dan ExportedFlagRequired
dapat memperluas
SecurityException
).
Tindakan ini akan membantu developer dan alat mendeteksi perubahan perilaku API.
Mengimplementasikan konstruktor salinan, bukan clone
Penggunaan metode clone()
Java sangat tidak dianjurkan karena kurangnya kontrak API yang disediakan oleh class Object
dan kesulitan yang melekat dalam memperluas class yang menggunakan clone()
. Sebagai gantinya, gunakan konstruktor salinan yang mengambil objek
dengan jenis yang sama.
/**
* Constructs a shallow copy of {@code other}.
*/
public Foo(Foo other)
Class yang mengandalkan Builder untuk konstruksi harus mempertimbangkan untuk menambahkan konstruktor salinan Builder untuk memungkinkan modifikasi pada salinan.
public class Foo {
public static final class Builder {
/**
* Constructs a Foo builder using data from {@code other}.
*/
public Builder(Foo other)
Menggunakan ParcelFileDescriptor melalui FileDescriptor
Objek java.io.FileDescriptor
memiliki definisi kepemilikan yang buruk, yang dapat menyebabkan bug use-after-close yang tidak jelas. Sebagai gantinya, API harus menampilkan atau
menerima instance ParcelFileDescriptor
. Kode lama dapat mengonversi antara PFD dan
FD jika diperlukan menggunakan
dup()
atau
getFileDescriptor().
Hindari penggunaan nilai numerik berukuran ganjil
Hindari penggunaan nilai short
atau byte
secara langsung, karena nilai tersebut sering kali membatasi cara Anda mengembangkan API pada masa mendatang.
Hindari penggunaan BitSet
java.util.BitSet
sangat bagus untuk implementasi, tetapi tidak untuk API publik. Mutable, memerlukan alokasi untuk panggilan metode frekuensi tinggi, dan tidak memberikan makna semantik untuk setiap bit yang diwakilinya.
Untuk skenario berperforma tinggi, gunakan int
atau long
dengan @IntDef
. Untuk
skenario performa rendah, pertimbangkan Set<EnumType>
. Untuk data biner mentah, gunakan
byte[]
.
Lebih memilih android.net.Uri
android.net.Uri
adalah enkapsulasi pilihan untuk URI di Android API.
Hindari java.net.URI
, karena terlalu ketat dalam mengurai URI, dan jangan pernah menggunakan
java.net.URL
, karena definisi kesetaraannya sangat rusak.
Menyembunyikan anotasi yang ditandai sebagai @IntDef, @LongDef, atau @StringDef
Anotasi yang ditandai sebagai @IntDef
, @LongDef
, atau @StringDef
menunjukkan serangkaian
konstanta valid yang dapat diteruskan ke API. Namun, saat diekspor sebagai
API itu sendiri, compiler akan menyisipkan konstanta dan hanya nilai (yang sekarang tidak berguna)
yang tetap ada di stub API anotasi (untuk platform) atau JAR (untuk
library).
Oleh karena itu, penggunaan anotasi ini harus ditandai dengan anotasi @hide
docs di platform atau anotasi kode @RestrictTo.Scope.LIBRARY)
di library. Mereka harus ditandai @Retention(RetentionPolicy.SOURCE)
dalam kedua kasus untuk mencegahnya muncul di stub API atau JAR.
@RestrictTo(RestrictTo.Scope.LIBRARY)
@Retention(RetentionPolicy.SOURCE)
@IntDef({
STREAM_TYPE_FULL_IMAGE_DATA,
STREAM_TYPE_EXIF_DATA_ONLY,
})
public @interface ExifStreamType {}
Saat membangun SDK platform dan AAR library, alat akan mengekstrak anotasi dan menggabungkannya secara terpisah dari sumber yang dikompilasi. Android Studio membaca format gabungan ini dan menerapkan definisi jenis.
Jangan menambahkan kunci penyedia setelan baru
Jangan mengekspos kunci baru dari
Settings.Global
,
Settings.System
,
atau
Settings.Secure
.
Sebagai gantinya, tambahkan Java API getter dan setter yang tepat di class yang relevan, yang biasanya merupakan class "pengelola". Tambahkan mekanisme pemroses atau siaran untuk memberi tahu klien tentang perubahan sesuai kebutuhan.
Setelan SettingsProvider
memiliki sejumlah masalah dibandingkan dengan getter/setter:
- Tidak ada keamanan jenis.
- Tidak ada cara terpadu untuk memberikan nilai default.
- Tidak ada cara yang tepat untuk menyesuaikan izin.
- Misalnya, Anda tidak dapat melindungi setelan Anda dengan izin khusus.
- Tidak ada cara yang tepat untuk menambahkan logika kustom dengan benar.
- Misalnya, Anda tidak dapat mengubah nilai setelan A bergantung pada nilai setelan B.
Contoh:
Settings.Secure.LOCATION_MODE
telah ada sejak lama, tetapi tim lokasi telah menghentikan penggunaannya untuk
API Java yang tepat
LocationManager.isLocationEnabled()
dan siaran
MODE_CHANGED_ACTION
, yang memberi tim lebih banyak fleksibilitas, dan semantik
API kini jauh lebih jelas.
Jangan memperluas Activity dan AsyncTask
AsyncTask
adalah detail implementasi. Sebagai gantinya, tampilkan pendengar atau, di
androidx, API ListenableFuture
.
Subkelas Activity
tidak dapat disusun. Memperluas aktivitas untuk fitur Anda membuatnya tidak kompatibel dengan fitur lain yang mengharuskan pengguna melakukan hal yang sama. Sebagai gantinya, andalkan komposisi dengan menggunakan alat seperti
LifecycleObserver.
Menggunakan getUser() Konteks
Class yang terikat ke Context
, seperti apa pun yang ditampilkan dari
Context.getSystemService()
harus menggunakan pengguna yang terikat ke Context
, bukan
mengekspos anggota yang menargetkan pengguna tertentu.
class FooManager {
Context mContext;
void fooBar() {
mIFooBar.fooBarForUser(mContext.getUser());
}
}
class FooManager {
Context mContext;
Foobar getFoobar() {
// Bad: doesn't appy mContext.getUserId().
mIFooBar.fooBarForUser(Process.myUserHandle());
}
Foobar getFoobar() {
// Also bad: doesn't appy mContext.getUserId().
mIFooBar.fooBar();
}
Foobar getFoobarForUser(UserHandle user) {
mIFooBar.fooBarForUser(user);
}
}
Pengecualian: Metode dapat menerima argumen pengguna jika menerima nilai yang tidak
mewakili satu pengguna, seperti UserHandle.ALL
.
Gunakan UserHandle, bukan int biasa
UserHandle
lebih disukai untuk memberikan keamanan jenis dan menghindari penggabungan ID pengguna dengan uid.
Foobar getFoobarForUser(UserHandle user);
Foobar getFoobarForUser(int userId);
Jika tidak dapat dihindari, int
yang merepresentasikan ID pengguna harus diberi anotasi dengan
@UserIdInt
.
Foobar getFoobarForUser(@UserIdInt int user);
Lebih memilih pemroses atau callback untuk menyiarkan intent
Intent siaran sangat berguna, tetapi telah menghasilkan perilaku yang muncul yang dapat berdampak negatif pada kesehatan sistem, sehingga intent siaran baru harus ditambahkan dengan bijak.
Berikut beberapa masalah spesifik yang membuat kami tidak menganjurkan pengenalan maksud siaran baru:
Saat mengirim siaran tanpa tanda
FLAG_RECEIVER_REGISTERED_ONLY
, aplikasi akan dipaksa dimulai jika belum berjalan. Meskipun terkadang hal ini merupakan hasil yang diinginkan, hal ini dapat menyebabkan puluhan aplikasi mengalami efek berlebihan, yang berdampak negatif pada kesehatan sistem. Sebaiknya gunakan strategi alternatif, sepertiJobScheduler
, untuk mengoordinasikan dengan lebih baik kapan berbagai prasyarat terpenuhi.Saat mengirim siaran, ada sedikit kemampuan untuk memfilter atau menyesuaikan konten yang dikirim ke aplikasi. Hal ini membuat respons terhadap masalah privasi di masa mendatang menjadi sulit atau tidak mungkin, atau memperkenalkan perubahan perilaku berdasarkan SDK target aplikasi penerima.
Karena antrean siaran adalah resource bersama, antrean tersebut dapat kelebihan beban dan mungkin tidak menghasilkan pengiriman acara Anda tepat waktu. Kami telah mengamati beberapa antrean siaran di luar sana yang memiliki latensi end-to-end 10 menit atau lebih.
Oleh karena itu, kami mendorong fitur baru untuk mempertimbangkan penggunaan pemroses atau callback atau fasilitas lain seperti JobScheduler
, bukan maksud siaran.
Dalam kasus ketika maksud siaran masih menjadi desain yang ideal, berikut beberapa praktik terbaik yang harus dipertimbangkan:
- Jika memungkinkan, gunakan
Intent.FLAG_RECEIVER_REGISTERED_ONLY
untuk membatasi siaran ke aplikasi yang sudah berjalan. Misalnya,ACTION_SCREEN_ON
menggunakan desain ini untuk menghindari mengaktifkan aplikasi. - Jika memungkinkan, gunakan
Intent.setPackage()
atauIntent.setComponent()
untuk menargetkan siaran ke aplikasi tertentu yang diminati. Misalnya,ACTION_MEDIA_BUTTON
menggunakan desain ini untuk berfokus pada penanganan aplikasi saat ini kontrol pemutaran. - Jika memungkinkan, tentukan siaran Anda sebagai
<protected-broadcast>
untuk mencegah aplikasi berbahaya meniru OS.
Intent dalam layanan developer yang terikat sistem
Layanan yang dimaksudkan untuk diperluas oleh developer dan diikat oleh sistem, misalnya layanan abstrak seperti NotificationListenerService
, dapat merespons tindakan Intent
dari sistem. Layanan tersebut harus memenuhi kriteria berikut:
- Tentukan konstanta string
SERVICE_INTERFACE
pada class yang berisi nama class layanan yang memenuhi syarat sepenuhnya. Konstanta ini harus diberi anotasi dengan@SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
. - Dokumen di kelas yang harus ditambahkan
<intent-filter>
oleh developer keAndroidManifest.xml
mereka untuk menerima Maksud dari platform. - Sangat disarankan untuk menambahkan izin tingkat sistem guna mencegah aplikasi berbahaya
mengirim
Intent
ke layanan developer.
Interop Kotlin-Java
Lihat panduan interoperabilitas Kotlin-Java Android resmi untuk mengetahui daftar lengkap pedoman. Pedoman yang dipilih telah disalin ke panduan ini untuk meningkatkan visibilitas.
Visibilitas API
Beberapa API Kotlin, seperti suspend fun
, tidak dimaksudkan untuk digunakan oleh developer Java; namun, jangan mencoba mengontrol visibilitas khusus bahasa menggunakan @JvmSynthetic
karena memiliki efek samping pada cara API ditampilkan di debugger yang membuat proses debug menjadi lebih sulit.
Lihat Panduan interop Kotlin-Java atau Panduan asinkron untuk mendapatkan panduan khusus.
Objek pendamping
Kotlin menggunakan companion object
untuk mengekspos anggota statis. Dalam beberapa kasus, ini akan muncul dari Java di class dalam bernama Companion
, bukan di class yang berisi. Class Companion
dapat ditampilkan sebagai class kosong dalam file teks API -- hal ini berfungsi sebagaimana mestinya.
Untuk memaksimalkan kompatibilitas dengan Java, anotasi kolom non-konstan objek pendamping dengan @JvmField
dan fungsi publik dengan @JvmStatic
untuk mengeksposnya secara langsung pada class yang memuatnya.
companion object {
@JvmField val BIG_INTEGER_ONE = BigInteger.ONE
@JvmStatic fun fromPointF(pointf: PointF) {
/* ... */
}
}
Perkembangan API platform Android
Bagian ini menjelaskan kebijakan terkait jenis perubahan yang dapat Anda lakukan pada API Android yang ada dan cara menerapkan perubahan tersebut untuk memaksimalkan kompatibilitas dengan aplikasi dan codebase yang ada.
Perubahan yang dapat menyebabkan gangguan biner
Hindari perubahan yang dapat menyebabkan gangguan pada biner di permukaan API publik yang telah difinalisasi. Jenis perubahan ini umumnya menimbulkan error saat menjalankan make update-api
, tetapi mungkin ada kasus ekstrem yang tidak terdeteksi oleh pemeriksaan API Metalava. Jika ragu, lihat panduan Evolving Java-based APIs dari Eclipse Foundation untuk mengetahui penjelasan mendetail tentang jenis perubahan API yang kompatibel di Java. Perubahan yang merusak biner dalam API tersembunyi (misalnya, sistem) harus mengikuti siklus penghentian penggunaan/penggantian.
Perubahan yang dapat menyebabkan gangguan pada sumber
Kami tidak menganjurkan perubahan yang merusak sumber meskipun tidak merusak biner. Salah satu contoh perubahan yang kompatibel biner tetapi merusak sumber adalah menambahkan generik ke class yang ada, yang kompatibel biner tetapi dapat menimbulkan error kompilasi karena pewarisan atau referensi yang ambigu.
Perubahan yang menyebabkan gangguan pada sumber tidak akan menimbulkan error saat menjalankan make update-api
, jadi Anda harus berhati-hati untuk memahami dampak perubahan pada tanda tangan API yang ada.
Dalam beberapa kasus, perubahan yang merusak sumber menjadi diperlukan untuk meningkatkan pengalaman developer atau kebenaran kode. Misalnya, menambahkan anotasi nullability ke sumber Java meningkatkan interoperabilitas dengan kode Kotlin dan mengurangi kemungkinan terjadinya error, tetapi sering kali memerlukan perubahan -- terkadang perubahan signifikan -- pada kode sumber.
Perubahan pada API pribadi
Anda dapat mengubah API yang diberi anotasi dengan @TestApi
kapan saja.
Anda harus mempertahankan API yang dianotasi dengan @SystemApi
selama tiga tahun. Anda harus
menghapus atau memfaktorkan ulang API sistem sesuai jadwal berikut:
- API y - Ditambahkan
- API y+1 - Penghentian penggunaan
- Tandai kode dengan
@Deprecated
. - Tambahkan penggantian, dan tautkan ke penggantian di Javadoc untuk kode yang tidak digunakan lagi menggunakan anotasi dokumen
@deprecated
. - Selama siklus pengembangan, ajukan bug file terhadap pengguna internal yang memberi tahu mereka bahwa API tidak digunakan lagi. Hal ini membantu memvalidasi bahwa API pengganti sudah memadai.
- Tandai kode dengan
- API y+2 - Penghapusan ringan
- Tandai kode dengan
@removed
. - Secara opsional, lempar atau no-op untuk aplikasi yang menargetkan level SDK saat ini untuk rilis.
- Tandai kode dengan
- API y+3 - Penghapusan paksa
- Hapus kode sepenuhnya dari pohon sumber.
Penghentian penggunaan
Kami menganggap penghentian penggunaan sebagai perubahan API, dan hal ini dapat terjadi dalam rilis utama (seperti
huruf). Gunakan anotasi sumber @Deprecated
dan anotasi dokumen @deprecated
<summary>
bersama-sama saat menghentikan penggunaan API. Ringkasan Anda harus
mencakup strategi migrasi. Strategi ini dapat ditautkan ke API pengganti atau
menjelaskan alasan Anda tidak boleh menggunakan API:
/**
* Simple version of ...
*
* @deprecated Use the {@link androidx.fragment.app.DialogFragment}
* class with {@link androidx.fragment.app.FragmentManager}
* instead.
*/
@Deprecated
public final void showDialog(int id)
Anda juga harus menghentikan penggunaan API yang ditentukan dalam XML dan diekspos di Java, termasuk atribut dan properti yang dapat distilisasi yang diekspos di class android.R
, dengan ringkasan:
<!-- Attribute whether the accessibility service ...
{@deprecated Not used by the framework}
-->
<attr name="canRequestEnhancedWebAccessibility" format="boolean" />
Kapan API dihentikan penggunaannya
Penghentian penggunaan paling berguna untuk mencegah penggunaan API dalam kode baru.
Kami juga mewajibkan Anda menandai API sebagai @deprecated
sebelum API tersebut
@removed
, tetapi hal ini tidak memberikan motivasi yang kuat bagi
developer untuk bermigrasi dari API yang sudah mereka gunakan.
Sebelum menghentikan penggunaan API, pertimbangkan dampaknya terhadap developer. Dampak penghentian penggunaan API meliputi:
javac
akan memunculkan peringatan selama kompilasi.- Peringatan penghentian penggunaan tidak dapat dihentikan secara global atau ditetapkan sebagai dasar, sehingga
developer yang menggunakan
-Werror
harus memperbaiki atau menghentikan penggunaan setiap penggunaan API yang tidak digunakan lagi satu per satu sebelum mereka dapat memperbarui versi SDK kompilasi. - Peringatan penghentian penggunaan pada impor class yang tidak digunakan lagi tidak dapat dihilangkan. Akibatnya, developer harus menyisipkan nama class yang sepenuhnya memenuhi syarat untuk setiap penggunaan class yang tidak digunakan lagi sebelum mereka dapat mengupdate versi SDK kompilasi.
- Peringatan penghentian penggunaan tidak dapat dihentikan secara global atau ditetapkan sebagai dasar, sehingga
developer yang menggunakan
- Dokumentasi tentang
d.android.com
menampilkan pemberitahuan penghentian. - IDE seperti Android Studio menampilkan peringatan di situs penggunaan API.
- IDE dapat menurunkan peringkat atau menyembunyikan API dari pelengkapan otomatis.
Akibatnya, penghentian penggunaan API dapat membuat developer yang paling
memperhatikan kualitas kode (yang menggunakan -Werror
) enggan mengadopsi SDK baru.
Developer yang tidak khawatir dengan peringatan dalam kode yang ada cenderung mengabaikan penghentian penggunaan sama sekali.
SDK yang memperkenalkan sejumlah besar penghentian penggunaan akan memperburuk kedua kasus ini.
Oleh karena itu, sebaiknya hentikan penggunaan API hanya dalam kasus berikut:
- Kami berencana untuk
@remove
API ini dalam rilis mendatang. - Penggunaan API menyebabkan perilaku yang salah atau tidak terdefinisi yang tidak dapat kami perbaiki tanpa merusak kompatibilitas.
Saat Anda menghentikan penggunaan API dan menggantinya dengan API baru, kami sangat merekomendasikan
penambahan API kompatibilitas yang sesuai ke library Jetpack seperti
androidx.core
untuk menyederhanakan dukungan bagi perangkat lama dan baru.
Kami tidak merekomendasikan penghentian penggunaan API yang berfungsi sebagaimana mestinya dalam rilis saat ini dan mendatang:
/**
* ...
* @deprecated Use {@link #doThing(int, Bundle)} instead.
*/
@Deprecated
public void doThing(int action) {
...
}
public void doThing(int action, @Nullable Bundle extras) {
...
}
Penghentian penggunaan sesuai dalam kasus saat API tidak dapat lagi mempertahankan perilaku yang didokumentasikan:
/**
* ...
* @deprecated No longer displayed in the status bar as of API 21.
*/
@Deprecated
public RemoteViews tickerView;
Perubahan pada API yang tidak digunakan lagi
Anda harus mempertahankan perilaku API yang tidak digunakan lagi. Artinya, penerapan pengujian harus tetap sama, dan pengujian harus terus berhasil setelah Anda menghentikan penggunaan API. Jika API tidak memiliki pengujian, Anda harus menambahkan pengujian.
Jangan memperluas platform API yang tidak digunakan lagi dalam rilis mendatang. Anda dapat menambahkan anotasi kebenaran lint (misalnya, @Nullable
) ke API yang sudah tidak digunakan lagi, tetapi tidak boleh menambahkan API baru.
Jangan menambahkan API baru sebagai API yang tidak digunakan lagi. Jika ada API yang ditambahkan dan kemudian dihentikan penggunaannya dalam siklus pra-rilis (sehingga awalnya akan masuk ke permukaan API publik sebagai tidak digunakan lagi), Anda harus menghapusnya sebelum menyelesaikan API.
Penghapusan sementara
Penghapusan sementara adalah perubahan yang dapat menyebabkan gangguan pada sumber, dan Anda harus menghindarinya di API publik kecuali jika Dewan API menyetujuinya secara eksplisit.
Untuk API sistem, Anda harus menghentikan penggunaan API selama durasi rilis utama sebelum penghapusan sementara. Hapus semua referensi dokumen ke
API dan gunakan anotasi dokumen @removed <summary>
saat menghapus
API secara halus. Ringkasan Anda harus menyertakan alasan penghapusan dan dapat menyertakan
strategi migrasi, seperti yang kami jelaskan dalam Penghentian Penggunaan.
Perilaku API yang dihapus sementara dapat dipertahankan seperti apa adanya, tetapi yang lebih penting harus dipertahankan agar pemanggil yang ada tidak mengalami error saat memanggil API. Dalam beberapa kasus, hal itu mungkin berarti mempertahankan perilaku.
Cakupan pengujian harus dipertahankan, tetapi konten pengujian mungkin perlu diubah untuk mengakomodasi perubahan perilaku. Pengujian tetap harus memvalidasi bahwa penelepon yang sudah ada tidak mengalami error saat runtime. Anda dapat mempertahankan perilaku API yang dihapus sementara seperti apa adanya, tetapi yang lebih penting, Anda harus mempertahankannya agar pemanggil yang ada tidak mengalami error saat memanggil API. Dalam beberapa kasus, hal itu mungkin berarti mempertahankan perilaku.
Anda harus mempertahankan cakupan pengujian, tetapi konten pengujian mungkin perlu diubah untuk mengakomodasi perubahan perilaku. Pengujian tetap harus memvalidasi bahwa penelepon yang sudah ada tidak mengalami error saat runtime.
Di tingkat teknis, kita menghapus API dari JAR stub SDK dan classpath waktu kompilasi menggunakan anotasi Javadoc @remove
, tetapi API tersebut masih ada di classpath waktu proses -- mirip dengan API @hide
:
/**
* Ringer volume. This is ...
*
* @removed Not functional since API 2.
*/
public static final String VOLUME_RING = ...
Dari perspektif developer aplikasi, API tidak lagi muncul di pelengkapan otomatis dan kode sumber yang mereferensikan API tidak akan dikompilasi jika compileSdk
sama dengan atau lebih baru dari SDK tempat API dihapus; namun, kode sumber akan terus dikompilasi dengan berhasil terhadap SDK dan biner sebelumnya yang mereferensikan API akan terus berfungsi.
Kategori API tertentu tidak boleh dihapus sementara. Anda tidak boleh menghapus kategori API tertentu secara sementara.
Metode abstrak
Anda tidak boleh menghapus secara halus metode abstrak pada class yang mungkin diperluas oleh developer. Dengan demikian, developer tidak dapat berhasil memperluas kelas di semua tingkat SDK.
Dalam kasus yang jarang terjadi ketika developer tidak pernah dan tidak akan dapat memperluas class, Anda tetap dapat menghapus metode abstrak secara halus.
Penghapusan permanen
Penghapusan paksa adalah perubahan yang merusak biner dan tidak boleh terjadi di API publik.
Anotasi yang tidak direkomendasikan
Kami menggunakan anotasi @Discouraged
untuk menunjukkan bahwa kami tidak merekomendasikan API dalam sebagian besar (>95%) kasus. API yang tidak disarankan berbeda dengan API yang tidak digunakan lagi karena ada kasus penggunaan penting yang sempit yang mencegah penghentian penggunaan. Saat menandai API sebagai tidak disarankan, Anda harus memberikan penjelasan dan solusi alternatif:
@Discouraged(message = "Use of this function is discouraged because resource
reflection makes it harder to perform build
optimizations and compile-time verification of code. It
is much more efficient to retrieve resources by
identifier (such as `R.foo.bar`) than by name (such as
`getIdentifier()`)")
public int getIdentifier(String name, String defType, String defPackage) {
return mResourcesImpl.getIdentifier(name, defType, defPackage);
}
Anda tidak boleh menambahkan API baru karena tidak disarankan.
Perubahan pada perilaku API yang ada
Dalam beberapa kasus, Anda mungkin ingin mengubah perilaku penerapan API yang ada. Misalnya, di Android 7.0, kami meningkatkan DropBoxManager
untuk
mengomunikasikan dengan jelas saat developer mencoba memposting peristiwa yang terlalu besar untuk
dikirim melalui Binder
.
Namun, untuk menghindari masalah pada aplikasi yang sudah ada, sebaiknya pertahankan perilaku yang aman untuk aplikasi lama. Sebelumnya, kami melindungi perubahan perilaku ini berdasarkan ApplicationInfo.targetSdkVersion
aplikasi, tetapi baru-baru ini kami bermigrasi untuk mewajibkan penggunaan Framework Kompatibilitas Aplikasi. Berikut
contoh cara menerapkan perubahan perilaku menggunakan framework baru ini:
import android.app.compat.CompatChanges;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledSince;
public class MyClass {
@ChangeId
// This means the change will be enabled for target SDK R and higher.
@EnabledSince(targetSdkVersion=android.os.Build.VERSION_CODES.R)
// Use a bug number as the value, provide extra detail in the bug.
// FOO_NOW_DOES_X will be the change name, and 123456789 the change ID.
static final long FOO_NOW_DOES_X = 123456789L;
public void doFoo() {
if (CompatChanges.isChangeEnabled(FOO_NOW_DOES_X)) {
// do the new thing
} else {
// do the old thing
}
}
}
Dengan desain Framework Kompatibilitas Aplikasi ini, developer dapat menonaktifkan sementara perubahan perilaku tertentu selama rilis pratinjau dan beta sebagai bagian dari proses debug aplikasi mereka, alih-alih memaksa mereka menyesuaikan diri dengan puluhan perubahan perilaku secara bersamaan.
Kompatibilitas dengan versi baru
Kompatibilitas ke depan adalah karakteristik desain yang memungkinkan sistem menerima input yang ditujukan untuk versi yang lebih baru. Dalam kasus desain API, Anda harus memberikan perhatian khusus pada desain awal serta perubahan di masa mendatang karena developer berharap untuk menulis kode sekali, mengujinya sekali, dan menjalankannya di mana saja tanpa masalah.
Berikut adalah penyebab masalah kompatibilitas ke depan yang paling umum di Android:
- Menambahkan konstanta baru ke set (seperti
@IntDef
atauenum
) yang sebelumnya dianggap lengkap (misalnya, jikaswitch
memilikidefault
yang memunculkan pengecualian). - Menambahkan dukungan untuk fitur yang tidak tercakup secara langsung di permukaan API
(misalnya, dukungan untuk menetapkan resource jenis
ColorStateList
di XML yang sebelumnya hanya mendukung resource<color>
). - Melonggarkan batasan pada pemeriksaan run-time, misalnya menghapus pemeriksaan
requireNotNull()
yang ada pada versi yang lebih rendah.
Dalam semua kasus ini, developer baru mengetahui bahwa ada yang salah saat runtime. Lebih buruk lagi, mereka mungkin mengetahuinya sebagai akibat dari laporan error dari perangkat lama di lapangan.
Selain itu, semua kasus ini adalah perubahan API yang secara teknis valid. Perubahan ini tidak merusak kompatibilitas biner atau sumber, dan lint API tidak akan mendeteksi masalah ini.
Oleh karena itu, desainer API harus berhati-hati saat mengubah class yang ada. Ajukan pertanyaan, "Apakah perubahan ini akan menyebabkan kode yang ditulis dan diuji hanya terhadap platform versi terbaru gagal pada versi yang lebih rendah?"
Skema XML
Jika skema XML berfungsi sebagai antarmuka yang stabil antar-komponen, skema tersebut harus ditentukan secara eksplisit dan harus berkembang dengan cara yang kompatibel mundur, mirip dengan API Android lainnya. Misalnya, struktur elemen dan atribut XML harus dipertahankan seperti cara metode dan variabel dipertahankan di platform Android API lainnya.
Penghentian penggunaan XML
Jika ingin menghentikan penggunaan elemen atau atribut XML, Anda dapat menambahkan penanda xs:annotation
, tetapi Anda harus terus mendukung file XML yang ada dengan mengikuti siklus proses evolusi @SystemApi
yang umum.
<xs:element name="foo">
<xs:complexType>
<xs:sequence>
<xs:element name="name" type="xs:string">
<xs:annotation name="Deprecated"/>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
Jenis elemen harus dipertahankan
Skema mendukung elemen sequence
, elemen choice
, dan elemen all
sebagai
elemen turunan dari elemen complexType
. Namun, elemen turunan ini berbeda dalam jumlah dan urutan elemen turunannya, sehingga mengubah jenis yang ada akan menjadi perubahan yang tidak kompatibel.
Jika Anda ingin mengubah jenis yang ada, praktik terbaiknya adalah menghentikan penggunaan jenis lama dan memperkenalkan jenis baru untuk menggantikannya.
<!-- Original "sequence" value -->
<xs:element name="foo">
<xs:complexType>
<xs:sequence>
<xs:element name="name" type="xs:string">
<xs:annotation name="Deprecated"/>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
<!-- New "choice" value -->
<xs:element name="fooChoice">
<xs:complexType>
<xs:choice>
<xs:element name="name" type="xs:string"/>
</xs:choice>
</xs:complexType>
</xs:element>
Pola khusus mainline
Mainline adalah project untuk mengizinkan update subsistem ("modul mainline") Android OS satu per satu, bukan mengupdate seluruh image sistem.
Modul utama harus "dibatalkan penggabungannya" dari platform inti, yang berarti semua interaksi antara setiap modul dan seluruh dunia harus dilakukan menggunakan API formal (publik atau sistem).
Ada pola desain tertentu yang harus diikuti oleh modul utama. Bagian ini menjelaskannya.
Pola <Module>FrameworkInitializer
Jika modul utama perlu mengekspos class @SystemService
(misalnya,
JobScheduler
), gunakan pola berikut:
Ekspos class
<YourModule>FrameworkInitializer
dari modul Anda. Class ini harus berada di$BOOTCLASSPATH
. Contoh: StatsFrameworkInitializerTandai dengan
@SystemApi(client = MODULE_LIBRARIES)
.Tambahkan metode
public static void registerServiceWrappers()
ke dalamnya.Gunakan
SystemServiceRegistry.registerContextAwareService()
untuk mendaftarkan class pengelola layanan saat memerlukan referensi keContext
.Gunakan
SystemServiceRegistry.registerStaticService()
untuk mendaftarkan class pengelola layanan jika tidak memerlukan referensi keContext
.Panggil metode
registerServiceWrappers()
dari penginisialisasi statisSystemServiceRegistry
.
Pola <Module>ServiceManager
Biasanya, untuk mendaftarkan objek binder layanan sistem atau mendapatkan referensi
ke objek tersebut, orang akan menggunakan
ServiceManager
,
tetapi modul utama tidak dapat menggunakannya karena tersembunyi. Class ini disembunyikan karena modul utama tidak boleh mendaftarkan atau merujuk ke objek binder layanan sistem yang diekspos oleh platform statis atau oleh modul lain.
Modul utama dapat menggunakan pola berikut untuk dapat mendaftarkan dan mendapatkan referensi ke layanan binder yang diimplementasikan di dalam modul.
Buat class
<YourModule>ServiceManager
, dengan mengikuti desain TelephonyServiceManagerTampilkan class sebagai
@SystemApi
. Jika Anda hanya perlu mengaksesnya dari class$BOOTCLASSPATH
atau class server sistem, Anda dapat menggunakan@SystemApi(client = MODULE_LIBRARIES)
; jika tidak,@SystemApi(client = PRIVILEGED_APPS)
akan berfungsi.Class ini akan terdiri dari:
- Konstruktor tersembunyi, sehingga hanya kode platform statis yang dapat membuat instance-nya.
- Metode getter publik yang menampilkan instance
ServiceRegisterer
untuk nama tertentu. Jika Anda memiliki satu objek binder, Anda memerlukan satu metode pengambilan. Jika Anda memiliki dua, Anda memerlukan dua getter. - Di
ActivityThread.initializeMainlineModules()
, buat instance class ini, dan teruskan ke metode statis yang diekspos oleh modul Anda. Biasanya, Anda menambahkan API@SystemApi(client = MODULE_LIBRARIES)
statis di classFrameworkInitializer
yang menerimanya.
Pola ini akan mencegah modul utama lainnya mengakses API ini karena tidak ada cara bagi modul lain untuk mendapatkan instance <YourModule>ServiceManager
, meskipun API get()
dan register()
terlihat olehnya.
Berikut cara telepon mendapatkan referensi ke layanan telepon: link penelusuran kode.
Jika Anda menerapkan objek binder layanan dalam kode native, Anda menggunakan
API native AServiceManager
.
API ini sesuai dengan ServiceManager
Java API, tetapi API native-nya
diekspos langsung ke modul utama. Jangan menggunakannya untuk mendaftarkan atau merujuk ke objek binder yang tidak dimiliki oleh modul Anda. Jika Anda mengekspos objek binder
dari native, <YourModule>ServiceManager.ServiceRegisterer
Anda tidak memerlukan
metode register()
.
Definisi izin dalam modul Mainline
Modul utama yang berisi APK dapat menentukan izin (kustom) dalam AndroidManifest.xml
APK-nya dengan cara yang sama seperti APK biasa.
Jika izin yang ditentukan hanya digunakan secara internal dalam modul, nama izinnya harus diberi awalan dengan nama paket APK, misalnya:
<permission
android:name="com.android.permissioncontroller.permission.MANAGE_ROLES_FROM_CONTROLLER"
android:protectionLevel="signature" />
Jika izin yang ditentukan akan diberikan sebagai bagian dari API platform yang dapat diupdate ke aplikasi lain, nama izinnya harus diawali dengan "android.permission." (seperti izin platform statis) ditambah nama paket modul, untuk menandakan bahwa API tersebut adalah API platform dari modul sekaligus menghindari konflik penamaan, misalnya:
<permission
android:name="android.permission.health.READ_ACTIVE_CALORIES_BURNED"
android:label="@string/active_calories_burned_read_content_description"
android:protectionLevel="dangerous"
android:permissionGroup="android.permission-group.HEALTH" />
Kemudian, modul dapat mengekspos nama izin ini sebagai konstanta API di permukaan API-nya, misalnya
HealthPermissions.READ_ACTIVE_CALORIES_BURNED
.