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 dijalankan 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
terintegrasi 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 kumpulan panduan ini dibuat, dan kebijakan yang ditetapkan nanti di halaman ini terus berkembang untuk memenuhi kebutuhan ekosistem Android.
Akibatnya, beberapa API yang ada mungkin tidak mengikuti panduan ini. Dalam kasus lain, API baru mungkin memberikan pengalaman pengguna yang lebih baik bagi developer aplikasi jika API baru tetap konsisten dengan API yang ada, bukan mengikuti 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 datang di lain waktu.
Platform API tanpa implementasi memiliki beberapa masalah:
- Tidak ada jaminan bahwa permukaan yang tepat atau lengkap telah diekspos. Sebelum API diuji atau digunakan oleh klien, tidak ada cara untuk memverifikasi bahwa klien memiliki API yang sesuai agar dapat menggunakan fitur tersebut.
- API tanpa implementasi 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 ide bahwa API harus diimplementasikan.
Menguji platform API memberikan jaminan dasar bahwa platform API dapat digunakan dan kami telah mengatasi kasus penggunaan yang diharapkan. Pengujian keberadaan saja tidak cukup; perilaku API itu sendiri harus diuji.
Perubahan yang menambahkan API baru harus menyertakan pengujian yang sesuai dalam topik CL atau Gerrit yang sama.
API juga harus dapat diuji. Anda harus dapat menjawab pertanyaan, "Bagaimana developer aplikasi akan menguji kode yang menggunakan API Anda?"
Semua API harus didokumentasikan
Dokumentasi adalah bagian penting dari kegunaan API. Meskipun sintaks platform 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 tulis tangan.
Alat yang tidak direkomendasikan untuk membuat API:
AutoValue
: melanggar pedoman dengan berbagai cara, misalnya, tidak ada cara untuk menerapkan class nilai akhir atau builder akhir dengan cara AutoValue berfungsi.
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, kita cenderung mengikuti konvensi coding Java dan Kotlin standar.
Akronim tidak boleh menggunakan huruf besar dalam nama metode
Misalnya: nama metode harus runCtsTests
, bukan runCTSTests
.
Nama tidak boleh diakhiri dengan Impl
Hal ini akan mengekspos detail implementasi, 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 diwarisi tersebut tidak sesuai
untuk kasus penggunaan Anda, warisi dari class yang lebih tinggi di hierarki, misalnya,
ViewGroup
atau View
.
Jika Anda tergoda untuk mengganti metode dari class dasar untuk menampilkan
UnsupportedOperationException
, pertimbangkan kembali class dasar yang Anda gunakan.
Menggunakan class koleksi dasar
Baik mengambil koleksi sebagai argumen maupun menampilkannya sebagai nilai, selalu
pilih class dasar daripada implementasi tertentu (seperti menampilkan
List<Foo>
, bukan ArrayList<Foo>
).
Gunakan class dasar yang menyatakan batasan yang sesuai untuk API. Misalnya, gunakan List
untuk API yang koleksinya harus diurutkan, dan gunakan Set
untuk API yang koleksinya harus terdiri dari elemen unik.
Di Kotlin, pilih koleksi yang tidak dapat diubah. Lihat Perubahan koleksi untuk detail selengkapnya.
Class abstrak versus antarmuka
Java 8 menambahkan dukungan untuk metode antarmuka default, yang memungkinkan desainer API menambahkan metode ke antarmuka sekaligus mempertahankan kompatibilitas biner. Kode platform dan semua library Jetpack harus menargetkan Java 8 atau yang lebih baru.
Jika implementasi default bersifat stateless, desainer API harus 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 membiarkan satu metode abstrak untuk menyederhanakan penggunaan sebagai lambda:
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 diperluas
Misalnya, class yang memperluas Service
harus diberi nama FooService
untuk
kejelasan:
public class IntentHelper extends Service {}
public class IntentService extends Service {}
Akhiran umum
Hindari penggunaan akhiran nama class generik seperti Helper
dan Util
untuk kumpulan
metode utilitas. Sebagai gantinya, tempatkan 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 backporting memerlukan mempertahankan status yang terkait
dengan View
dan memanggil beberapa metode di 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, socket, FlatBuffers, atau platform API non-Java, non-NDK lainnya. Namun, sebagian besar IDL di Android berada dalam AIDL, sehingga halaman ini berfokus pada AIDL.
Class AIDL yang dihasilkan tidak memenuhi persyaratan panduan gaya API (misalnya, class tersebut tidak dapat menggunakan overload) dan alat AIDL tidak dirancang secara eksplisit 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 merupakan wrapper dangkal.
Antarmuka Binder
Jika antarmuka Binder
adalah detail implementasi, antarmuka tersebut dapat diubah secara bebas
di masa mendatang, dengan lapisan publik yang memungkinkan kompatibilitas
mundur yang diperlukan untuk 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. Tidak satu pun dari hal ini yang 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, gabungkan 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 argumen baru diperlukan untuk panggilan ini, antarmuka internal dapat menjadi overload yang minimal dan praktis yang ditambahkan ke API publik. Anda dapat menggunakan lapisan penggabungan untuk menangani masalah kompatibilitas mundur lainnya seiring penerapan 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 bahwa
sangat sulit untuk mengembangkan antarmuka itu sendiri. Namun, sebaiknya tetap
memiliki lapisan wrapper di sekitarnya, agar sesuai dengan panduan API lainnya dan memudahkan
penggunaan API publik yang sama untuk antarmuka IPC versi baru, jika
hal itu diperlukan.
Jangan gunakan objek Binder mentah di API publik
Objek Binder
tidak memiliki arti apa pun 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. Sebagai ganti 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 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 mendatang dan memiliki default yang rentan error
.
Sebaliknya, java.util.concurrent.Future
tidak memiliki pemrosesan non-pemblokiran,
sehingga sulit digunakan dengan kode asinkron.
Dalam kode platform dan API library level rendah yang digunakan oleh Kotlin dan
Java, pilih 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, pilih 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 platform API, Optional
tidak konsisten
dengan area platform 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 disambungkan. Jika nilai tidak
ditetapkan (has
menampilkan false
), metode get
akan menampilkan
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 dibuat instance-nya
Class yang hanya dapat dibuat oleh Builder
, class yang hanya berisi
konstanta atau metode statis, atau class yang tidak dapat dibuat instance-nya harus
menyertakan minimal satu konstruktor pribadi untuk mencegah pembuatan instance menggunakan
konstruktor no-arg default.
public final class Log {
// Not instantiable.
private Log() {}
}
Singleton
Singleton tidak dianjurkan karena memiliki kelemahan terkait pengujian berikut:
- Konstruksi dikelola oleh class, sehingga mencegah penggunaan palsu
- Pengujian tidak dapat bersifat hermetis karena sifat statis singleton
- Untuk mengatasi masalah ini, developer harus mengetahui detail internal singleton atau membuat wrapper di sekitarnya
Pilih 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 dengan singleton karena developer
dapat membuat versi palsu SingleInstance
dan menggunakan framework Dependency
Injection mereka sendiri untuk mengelola implementasi tanpa harus membuat
wrapper, atau library dapat menyediakan palsunya sendiri dalam artefak -testing
.
Class yang merilis resource harus mengimplementasikan AutoCloseable
Class yang merilis resource melalui metode close
, release
, destroy
, atau
yang serupa harus menerapkan java.lang.AutoCloseable
agar developer dapat
melakukan pembersihan resource ini secara otomatis saat menggunakan blok try-with-resources
.
Hindari memperkenalkan subclass View baru di android.*
Jangan perkenalkan class baru yang mewarisi secara langsung atau tidak langsung dari
android.view.View
di API publik platform (yaitu, di android.*
).
Toolkit UI Android kini Compose-first. Fitur UI baru yang ditampilkan oleh platform harus ditampilkan sebagai API tingkat rendah yang dapat digunakan untuk mengimplementasikan Jetpack Compose dan secara opsional komponen UI berbasis View untuk developer di library Jetpack. Menawarkan komponen ini dalam library memberikan peluang untuk implementasi yang di-backport saat fitur platform tidak tersedia.
Kolom
Aturan ini membahas kolom publik di class.
Jangan mengekspos kolom mentah
Class Java tidak boleh mengekspos kolom secara langsung. Kolom harus bersifat pribadi dan hanya dapat diakses menggunakan pengambil dan penyetel publik, terlepas dari apakah kolom ini bersifat final atau tidak.
Pengecualian yang jarang terjadi mencakup struktur data dasar yang tidak perlu meningkatkan
perilaku menentukan atau mengambil kolom. Dalam kasus tersebut, kolom harus
dinamai 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 dianjurkan (@lihat
Jangan mengekspos kolom mentah). Namun, dalam situasi yang jarang terjadi saat
kolom diekspos sebagai kolom publik, tandai kolom tersebut final
.
Kolom internal tidak boleh ditampilkan
Jangan mereferensikan nama kolom internal di API publik.
public int mFlags;
Gunakan publik, bukan dilindungi
@lihat Menggunakan publik, bukan dilindungi
Konstanta
Ini adalah aturan tentang konstanta publik.
Konstanta tanda tidak boleh tumpang-tindih dengan nilai int atau long
Flag menyiratkan bit yang dapat digabungkan ke dalam beberapa nilai gabungan. Jika tidak
begitu, 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 guna mengetahui informasi
selengkapnya tentang cara menentukan konstanta flag publik.
konstanta final statis harus menggunakan konvensi penamaan huruf besar semua, yang dipisahkan garis bawah
Semua kata dalam konstanta harus menggunakan huruf besar dan beberapa kata harus
dipisahkan oleh _
. 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 ditujukan untuk hal-hal standar, seperti tanda, kunci, dan tindakan. Konstanta ini harus memiliki awalan standar agar lebih mudah dikenali sebagai hal-hal tersebut.
Misalnya, tambahan intent harus diawali 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 harus dicakupkan ke paket atau domain. Contoh:
public static final String FOO_THING = "foo"
tidak diberi nama secara konsisten atau cakupannya tidak sesuai. Sebagai gantinya, pertimbangkan:
public static final String FOO_THING = "android.fooservice.FOO_THING"
Awalan android
dalam konstanta string cakupan dicadangkan untuk Project Open Source
Android.
Tindakan dan tambahan intent, serta entri Paket, harus diberi namespace menggunakan nama paket tempatnya ditentukan.
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 publik, bukan dilindungi
@lihat Menggunakan publik, bukan dilindungi
Gunakan awalan yang konsisten
Semua konstanta terkait harus diawali dengan awalan yang sama. Misalnya, untuk kumpulan 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;
@lihat 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 dengan 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.
Resource tata letak dan drawable tidak boleh 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 mungkin menyisipkan nilai konstanta, sehingga mempertahankan nilai yang sama dianggap sebagai bagian dari kontrak API. Jika nilai konstanta MIN_FOO
atau MAX_FOO
dapat berubah di masa mendatang, sebaiknya buat metode dinamis
sebagai gantinya.
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 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 berhasil.
Metode yang menampilkan konstanta dapat menangani kasus seperti ini dengan aman dengan membatasi outputnya agar cocok dengan API level 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 mengantisipasi apakah daftar konstanta dapat berubah di
masa mendatang. Jika Anda menentukan API dengan konstanta UNKNOWN
atau UNSPECIFIED
yang
terlihat seperti generik, developer mengasumsikan bahwa konstanta yang dipublikasikan saat mereka
menulis aplikasi sudah lengkap. Jika Anda tidak ingin menetapkan ekspektasi ini,
pertimbangkan kembali apakah konstanta generik adalah ide yang baik untuk API Anda.
Selain itu, library tidak dapat menentukan targetSdkVersion
-nya sendiri secara terpisah dari
aplikasi dan menangani perubahan perilaku targetSdkVersion
dari kode library
rumit dan rentan 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 mewakili sekumpulan properti yang tidak dapat diubah dan menyediakan sekumpulan fungsi utilitas yang kecil dan ditentukan dengan baik untuk berinteraksi dengan data tersebut.
Jangan gunakan data class
di API Kotlin publik, karena compiler Kotlin tidak
menjamin API bahasa atau kompatibilitas biner untuk kode yang dihasilkan. Sebagai gantinya,
terapkan fungsi yang diperlukan secara manual.
Pembuatan instance
Di Java, class data harus menyediakan konstruktor jika ada sedikit 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 mungkin juga mendapatkan manfaat dari penyediaan builder saat menargetkan klien Java.
Modifikasi dan penyalinan
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 mengimplementasikan toString()
dengan format yang direkomendasikan
yang cocok dengan implementasi
class data Kotlin, misalnya User(var1=Alex, var2=42)
.
Metode
Ini adalah aturan tentang berbagai detail dalam metode, seputar parameter, nama metode, jenis nilai yang ditampilkan, dan pengonfigurasi akses.
Waktu
Aturan ini mencakup cara konsep waktu seperti tanggal dan durasi harus dinyatakan di API.
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
harus lebih disukai saat menyatakan waktu dalam parameter API atau nilai yang ditampilkan.
Sebaiknya hanya mengekspos varian API yang menerima atau menampilkan
java.time.Duration
atau java.time.Instant
dan menghapus varian primitif dengan
kasus penggunaan yang sama, kecuali jika domain API adalah tempat alokasi objek dalam
pola penggunaan yang diinginkan akan memiliki dampak performa yang tidak dapat diterima.
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" sesuai jika durasi secara khusus berlaku untuk nilai waktu tunggu.
"time" dengan jenis java.time.Instant
sesuai jika merujuk pada
titik waktu tertentu, bukan durasi.
Metode yang menyatakan durasi atau waktu sebagai primitif harus diberi nama dengan satuan waktunya, dan menggunakan long
Metode yang menerima atau menampilkan durasi sebagai primitif harus menambahkan akhiran pada nama
metode dengan unit waktu terkait (seperti Millis
, Nanos
, Seconds
) untuk
menyimpan nama yang tidak didekorasi untuk digunakan dengan java.time.Duration
. Lihat
Waktu.
Metode juga harus dianotasi dengan benar dengan unit dan dasar 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 primitif atau nilai yang ditampilkan 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 lama
Platform ini menyertakan 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 pengukuran
Untuk semua metode yang menyatakan satuan pengukuran selain waktu, pilih awalan unit SI CamelCase.
public long[] getFrequenciesKhz();
public float getStreamVolumeDb();
Menempatkan parameter opsional di akhir overload
Jika Anda memiliki overload metode dengan parameter opsional, pertahankan parameter tersebut di bagian 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 overload untuk argumen opsional, perilaku metode yang lebih sederhana harus berperilaku persis sama seperti argumen default telah disediakan ke metode yang lebih rumit.
Korolar: Jangan overload metode selain untuk menambahkan argumen opsional atau untuk menerima berbagai jenis argumen jika metode bersifat polimorfik. Jika metode yang dioverload melakukan sesuatu yang berbeda secara mendasar, 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 Penambahan beban fungsi untuk default dalam panduan interop 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 dikirim dengan parameter dengan nilai default, penghapusan nilai default adalah perubahan yang merusak sumber.
Parameter metode yang paling khas dan dapat diidentifikasi harus didahulukan
Jika Anda memiliki metode dengan beberapa parameter, tempatkan 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 bagian terakhir.
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 overload
Builder
Pola Builder direkomendasikan untuk membuat objek Java yang kompleks, dan umumnya digunakan di Android untuk kasus saat:
- Properti objek yang dihasilkan tidak boleh diubah
- Ada banyak properti yang diperlukan, 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 pada kegunaan API.
Pertimbangkan apakah Anda memerlukan builder. Builder berguna di platform API jika digunakan untuk:
- Mengonfigurasi hanya beberapa dari kumpulan parameter pembuatan opsional yang berpotensi besar
- Mengonfigurasi berbagai parameter pembuatan opsional atau wajib, terkadang dari jenis yang serupa atau cocok, sehingga situs panggilan menjadi membingungkan untuk dibaca atau rentan error saat ditulis
- Konfigurasikan pembuatan objek secara bertahap, dengan beberapa bagian kode konfigurasi yang berbeda-beda, yang masing-masing dapat melakukan panggilan pada builder sebagai detail implementasi
- Mengizinkan jenis berkembang dengan menambahkan parameter pembuatan opsional tambahan dalam versi API mendatang
Jika memiliki jenis dengan tiga parameter wajib atau kurang dan tidak ada parameter opsional, Anda hampir selalu dapat melewati builder dan menggunakan konstruktor biasa.
Class yang bersumber dari Kotlin harus lebih memilih konstruktor yang dianotasi @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 pembuatan rantai metode dengan menampilkan objek Builder
(seperti this
) dari setiap metode kecuali build()
. Objek tambahan yang dibuat
harus diteruskan sebagai argumen -- jangan menampilkan builder objek yang berbeda.
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 return 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 Android API, semua
builder harus dibuat melalui konstruktor, bukan metode
pencipta statis. Untuk API berbasis Kotlin, Builder
harus bersifat publik meskipun pengguna Kotlin
diharapkan secara implisit mengandalkan builder melalui metode factory/mekanisme pembuatan
gaya DSL. 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 diwajibkan (seperti @NonNull)
Opsional, misalnya @Nullable
, argumen harus dipindahkan ke metode penyetel.
Konstruktor builder harus menampilkan NullPointerException
(pertimbangkan untuk menggunakan
Objects.requireNonNull
) jika argumen yang diperlukan tidak ditentukan.
Class Builder harus berupa class dalam statis akhir dari jenis yang di-build
Untuk organisasi logis dalam paket, class builder biasanya
harus diekspos sebagai class dalam akhir dari jenis yang di-build, 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. Builder tidak boleh menyediakan metode alternatif untuk membuat instance builder dari builder yang ada atau membuat objek.
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
Mereset sangat penting jika instance baru builder dapat dibuat dari
instance yang ada. Jika tidak ada konstruktor salinan yang tersedia, builder dapat
memiliki argumen @Nullable
atau @NonNullable
.
public static class Builder {
public Builder(Builder original);
public Builder setObjectValue(@Nullable Object value);
}
Penyetel Builder dapat menggunakan argumen @Nullable untuk properti opsional
Sering kali lebih mudah menggunakan nilai nullable untuk input tingkat kedua, terutama di Kotlin, yang menggunakan argumen default, bukan builder dan overload.
Selain itu, penyetel @Nullable
akan mencocokkannya dengan pengambilnya, yang harus
@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 penyetel tidak dipanggil), dan arti null
, harus
didokumentasikan dengan benar di penyetel dan pengambil.
/**
* ...
*
* <p>Defaults to {@code null}, which means the optional value won't be used.
*/
Penyetel builder dapat disediakan untuk properti yang dapat diubah dengan penyetel yang tersedia di class yang di-build
Jika class Anda memiliki properti yang dapat diubah dan memerlukan class Builder
, tanyakan diri Anda terlebih dahulu
apakah class Anda sebenarnya harus memiliki properti yang dapat diubah.
Selanjutnya, jika Anda yakin bahwa Anda memerlukan properti yang dapat diubah, tentukan skenario mana di antara skenario berikut yang lebih cocok untuk kasus penggunaan yang diharapkan:
Objek yang dibuat harus langsung dapat digunakan, sehingga penyetel 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 penyetel 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 menggabungkan kedua skenario tersebut.
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
Pengambil harus berada di objek yang dibuat, bukan 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 non-null dari
objek yang dibuat. Jika objek tidak dapat dibuat karena parameter
tidak valid, validasi dapat ditangguhkan 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 pribadi internal.
public synchronized void doThing() { ... }
private final Object mThingLock = new Object();
public void doThing() {
synchronized (mThingLock) {
...
}
}
Metode bergaya aksesor harus mengikuti panduan properti Kotlin
Jika 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
.
Karena alasan ini, dan untuk secara umum memenuhi ekspektasi developer seputar perilaku metode pengakses, metode yang menggunakan awalan metode pengakses harus berperilaku serupa dengan kolom Java. Hindari penggunaan awalan gaya aksesor saat:
- Metode memiliki efek samping -- pilih nama metode yang lebih deskriptif
- Metode ini melibatkan pekerjaan yang mahal secara komputasi -- pilih
compute
- Metode ini melibatkan pemblokiran atau pekerjaan yang berjalan lama untuk menampilkan
nilai, seperti IPC atau I/O lainnya -- pilih
fetch
- Metode ini memblokir thread hingga dapat menampilkan nilai -- pilih
await
- Metode ini menampilkan instance objek baru pada setiap panggilan -- pilih
create
- Metode mungkin tidak berhasil menampilkan nilai -- pilih
request
Perhatikan bahwa melakukan pekerjaan yang mahal secara komputasi satu kali dan meng-cache nilai untuk panggilan berikutnya masih dihitung sebagai melakukan pekerjaan 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 variabel dan metode 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 dalam:
// 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 biasanya harus menggunakan penamaan positif, misalnya
Enabled
, bukan Disabled
. Menggunakan terminologi negatif akan membalikkan
makna true
dan false
serta mempersulit alasan
perilaku.
// Passing false here is a double-negative.
void setFactoryResetProtectionDisabled(boolean disabled);
Jika 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 harus:
// "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 akhiran 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 karakter pertama menjadi huruf besar untuk
pengambil, dan tambahkan set
dan ubah karakter 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 akan berlaku dalam pembuatan
nama: jika nama properti diawali dengan is
, get
tidak akan ditambahkan di awal
untuk nama metode pengambil, nama properti itu sendiri akan digunakan sebagai pengambil.
Oleh karena itu, pilih untuk memberi nama properti Boolean
dengan awalan is
agar
dapat 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 Menggunakan @IntDef
untuk flag bitmask untuk mengetahui panduan API
terkait penentuan flag bitmask.
Penyetel
Dua metode penyetel harus disediakan: satu yang menggunakan bitstring penuh dan menulis ulang semua flag yang ada, dan satu lagi yang menggunakan bitmask kustom untuk memungkinkan 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 publik, bukan dilindungi
Selalu pilih public
daripada protected
di API publik. Akses yang dilindungi pada akhirnya
menjadi merepotkan dalam jangka panjang, karena implementator harus mengganti untuk menyediakan
pengakses publik jika akses eksternal secara default akan
sama baiknya.
Perlu diingat bahwa visibilitas protected
tidak mencegah developer memanggil
API -- hanya membuatnya sedikit lebih menjengkelkan.
Tidak menerapkan salah satu atau kedua equals() dan hashCode()
Jika mengganti salah satunya, Anda harus mengganti yang lainnya.
Mengimplementasikan toString() untuk class data
Class data disarankan untuk mengganti toString()
, untuk membantu developer men-debug
kode mereka.
Mendokumentasikan apakah output 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 program. Jika Anda mengekspos informasi hanya untuk proses debug, seperti Intent, maka implisitkan dokumen turunan 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 pada masa mendatang. Praktik
yang baik adalah menerapkan toString()
hanya menggunakan API publik objek.
Tidak menyarankan untuk mengandalkan output debug
Meskipun tidak mungkin untuk mencegah developer bergantung pada output debug,
menyertakan System.identityHashCode
objek Anda dalam output toString()
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.
Menggunakan 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.
Saat 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 streaming
Lokasi penyimpanan data di Android tidak selalu berupa file di disk. Misalnya,
konten yang diteruskan di seluruh batas pengguna direpresentasikan sebagai content://
Uri
. Untuk
memungkinkan 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 berkotak
Jika Anda perlu menyampaikan 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 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 lebih sering dan lebih mahal.
Menggunakan anotasi untuk memperjelas parameter dan nilai yang valid
Anotasi developer ditambahkan untuk membantu menjelaskan nilai yang diizinkan dalam berbagai situasi. Hal ini memudahkan alat untuk membantu developer saat mereka memberikan
nilai yang salah (misalnya, meneruskan int
arbitrer saat framework
memerlukan salah satu kumpulan nilai konstan tertentu). Gunakan salah satu atau 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 di 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 untuk Android, sehingga sebagian besar
metode API Android tidak didokumentasikan secara konsisten. Oleh karena itu, kita memiliki
tiga status "unknown, @Nullable
, @NonNull
", yang menjadi alasan @NonNull
menjadi bagian
dari panduan API:
@NonNull
public String getName()
public void setName(@NonNull String name)
Untuk dokumen platform Android, menganotasi parameter metode akan otomatis membuat dokumentasi dalam bentuk "Nilai ini mungkin null". kecuali jika "null" digunakan secara eksplisit di tempat lain dalam dokumen parameter.
Metode "tidak benar-benar nullable" yang ada: Metode yang ada di API tanpa
anotasi @Nullable
yang dideklarasikan dapat dianotasi @Nullable
jika metode dapat
menampilkan null
dalam keadaan tertentu dan 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
di class penerapan harus 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 parameternya null. Hal ini
dilakukan secara otomatis di Kotlin.
Referensi
ID resource: Parameter bilangan bulat yang menunjukkan ID untuk resource
tertentu harus dianotasi dengan definisi jenis resource yang sesuai.
Ada anotasi untuk setiap jenis resource, seperti @StringRes
,
@ColorRes
, dan @AnimRes
, selain @AnyRes
generik. Contoh:
public void setTitle(@StringRes int resId)
@IntDef untuk kumpulan konstanta
Konstanta ajaib: Parameter String
dan int
yang dimaksudkan untuk menerima salah satu dari sekumpulan kemungkinan nilai yang terbatas yang dilambangkan dengan konstanta publik harus dianotasi dengan benar menggunakan @StringDef
atau @IntDef
. Anotasi ini memungkinkan
Anda membuat anotasi baru yang dapat Anda gunakan yang 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 dianotasi
dan menampilkan IllegalArgumentException
jika parameter bukan bagian dari
@IntDef
@IntDef untuk flag bitmask
Anotasi juga dapat menentukan bahwa konstanta adalah flag, 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 sama persis dengan @IntDef
di
bagian sebelumnya, tetapi untuk konstanta String
. Anda dapat menyertakan beberapa nilai "awalan" yang digunakan untuk otomatis menampilkan dokumentasi untuk semua nilai.
@SdkConstant untuk konstanta SDK
@SdkConstant Menambahkan anotasi pada kolom publik jika merupakan 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";
Memberikan nullability yang kompatibel untuk penggantian
Untuk kompatibilitas API, nullability penggantian harus kompatibel dengan nullability induk saat ini. Tabel berikut menunjukkan ekspektasi kompatibilitas. Secara jelas, penggantian hanya boleh sama ketatnya atau lebih ketat daripada elemen yang diganti.
Jenis | Orang tua | Anak |
---|---|---|
Jenis nilai yang ditampilkan | Tidak dianotasi | Tidak dianotasi atau non-null |
Jenis nilai yang ditampilkan | Nullable | Nullable atau nonnull |
Jenis nilai yang ditampilkan | Nonnull | Nonnull |
Argumen yang menyenangkan | Tidak dianotasi | Tidak dianotasi atau nullable |
Argumen yang menyenangkan | Nullable | Nullable |
Argumen yang menyenangkan | Nonnull | Nullable atau nonnull |
Pilih argumen non-nullable (seperti @NonNull) jika memungkinkan
Saat metode di-overload, sebaiknya semua argumen non-null.
public void startActivity(@NonNull Component component) { ... }
public void startActivity(@NonNull Component component, @NonNull Bundle options) { ... }
Aturan ini juga berlaku untuk penyetel properti yang kelebihan beban. Argumen utama harus non-null dan menghapus properti harus diterapkan sebagai metode terpisah. Hal ini mencegah panggilan "nonsense" saat developer harus menetapkan parameter 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()
Memilih 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 --. Jika null
akan digunakan untuk
membedakan ketersediaan penampung, pertimbangkan untuk menyediakan metode
boolean terpisah.
@NonNull
public Bundle getExtras() { ... }
Anotasi nullability untuk pasangan get dan set harus sesuai
Pasangan metode get dan set untuk satu properti logis harus selalu setuju dalam anotasi nullability-nya. Gagal mengikuti panduan ini akan mengacaukan sintaksis properti Kotlin, dan menambahkan anotasi nullability yang tidak setuju ke metode properti yang ada adalah perubahan yang merusak sumber bagi pengguna Kotlin.
@NonNull
public Bundle getExtras() { ... }
public void setExtras(@NonNull Bundle bundle) { ... }
Menampilkan nilai dalam kondisi kegagalan atau error
Semua API harus mengizinkan aplikasi bereaksi terhadap error. Menampilkan false
, -1
, null
,
atau nilai generik lainnya "terjadi error" tidak memberi tahu developer
secara memadai tentang kegagalan untuk menetapkan ekspektasi pengguna atau melacak
keandalan aplikasi mereka secara akurat di lapangan. Saat mendesain API, bayangkan Anda
mem-build aplikasi. Jika Anda mengalami error, apakah API memberi Anda 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 mengurainya untuk menangani error dengan tepat. Kode error panjang atau informasi lainnya harus ditampilkan sebagai metode.
- Pastikan opsi penanganan error yang Anda pilih memberi Anda fleksibilitas untuk
memperkenalkan jenis error baru di masa mendatang. Untuk
@IntDef
, hal ini 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 diterapkan pengecualian Anda, sehingga kode apa pun yang menangani jenis tersebut juga akan menangkap dan menangani subjenis. - Developer akan kesulitan atau tidak mungkin mengabaikan error secara tidak sengaja. Jika error Anda dikomunikasikan dengan menampilkan nilai, anotasikan metode Anda dengan
@CheckResult
.
Sebaiknya tampilkan ? extends RuntimeException
saat kondisi kegagalan atau error
dicapai karena kesalahan yang dilakukan developer, misalnya mengabaikan
batasan pada parameter input atau gagal memeriksa status yang dapat diamati.
Metode penyetel atau tindakan (misalnya, perform
) dapat menampilkan kode status bilangan bulat
jika tindakan dapat gagal akibat status atau
kondisi yang diperbarui secara asinkron di luar kontrol developer.
Kode status harus ditentukan di class penampung sebagai kolom public static final
, diawali dengan ERROR_
, dan dihitung 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 gunakan.
public void tableReload() {
mTable.reload();
}
public void reloadTable() {
mTable.reload();
}
Memilih jenis Koleksi daripada array sebagai jenis nilai yang ditampilkan atau parameter
Antarmuka koleksi dengan jenis generik memberikan beberapa keunggulan dibandingkan array, termasuk kontrak API yang lebih kuat seputar keunikan dan pengurutan, dukungan untuk generik, dan sejumlah metode praktis yang mudah digunakan developer.
Pengecualian untuk primitif
Jika elemennya adalah primitif, pilih array, untuk menghindari biaya auto-boxing. Lihat Mengambil dan menampilkan primitif mentah, bukan versi dengan kotak
Pengecualian untuk kode yang sensitif terhadap performa
Dalam skenario tertentu, saat API digunakan dalam kode yang sensitif terhadap performa (seperti grafik atau API pengukuran/tata letak/gambar lainnya), Anda dapat menggunakan array bukan koleksi untuk mengurangi alokasi dan perubahan memori.
Pengecualian untuk Kotlin
Array Kotlin bersifat 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 koleksi. Saat menampilkan koleksi
kosong, gunakan metode Collections.empty
yang sesuai untuk menampilkan objek koleksi yang tidak dapat diubah,
berjenis benar, dan berbiaya rendah.
Jika anotasi jenis didukung, selalu pilih @NonNull
untuk elemen
koleksi.
Anda juga harus memilih @NonNull
saat menggunakan array, bukan koleksi (lihat
item sebelumnya). Jika alokasi objek
menjadi masalah, buat konstanta dan teruskan - bagaimanapun juga, array kosong
tidak dapat diubah. 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;
}
Mutabilitas koleksi
API Kotlin harus lebih 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 lebih memilih jenis nilai yang dapat diubah secara default karena
implementasi platform Android untuk Java API belum memberikan implementasi
koleksi yang tidak dapat diubah yang praktis. Pengecualian untuk aturan ini adalah
jenis nilai yang ditampilkan Collections.empty
, yang tidak dapat diubah. Jika mutabilitas
dapat dieksploitasi oleh klien -- dengan sengaja atau tidak sengaja -- untuk merusak pola penggunaan
yang diinginkan API, Java API harus mempertimbangkan dengan cermat untuk menampilkan salinan
kumpulan yang dangkal.
@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 secara eksplisit
API yang menampilkan koleksi idealnya tidak boleh mengubah objek koleksi yang ditampilkan setelah ditampilkan. Jika koleksi yang ditampilkan harus berubah atau digunakan kembali dengan cara tertentu -- misalnya, tampilan yang diadaptasi dari set data yang dapat diubah -- perilaku yang tepat dari kapan konten dapat berubah harus didokumentasikan secara eksplisit atau mengikuti konvensi penamaan API yang telah ditetapkan.
/**
* Returns a view of this object as a list of [Item]s.
*/
fun MyObject.asList(): List<Item> = MyObjectListWrapper(this)
Konvensi .asFoo()
Kotlin dijelaskan
di bawah dan memungkinkan koleksi yang ditampilkan oleh
.asList()
berubah jika koleksi asli berubah.
Perubahan objek jenis data yang ditampilkan
Serupa dengan API yang menampilkan koleksi, API yang menampilkan objek jenis data idealnya tidak akan 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 memanfaatkan penggabungan atau penggunaan kembali objek. Jangan menulis struktur data kumpulan objek Anda sendiri dan jangan mengekspos objek yang digunakan kembali di API publik. Dalam kasus apa pun, berhati-hatilah saat mengelola akses serentak.
Penggunaan jenis parameter vararg
API Kotlin dan Java disarankan untuk menggunakan vararg
jika
developer cenderung membuat array di lokasi panggilan hanya
untuk meneruskan beberapa parameter terkait dari 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 hasilnya 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, atau tidak melindungi dari 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 lain untuk memberikan makna tambahan:
Gunakan
Set<Foo>
, jika API Anda tidak peduli dengan urutan elemen dan tidak mengizinkan duplikat atau duplikat tidak berarti.Collection<Foo>,
jika API Anda tidak peduli dengan urutan dan mengizinkan duplikat.
Fungsi konversi Kotlin
Kotlin sering menggunakan .toFoo()
dan .asFoo()
untuk mendapatkan objek dari
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 mengembangkannya lebih lanjut dengan menggunakannya untuk konversi primitif
seperti 25.toFloat()
.
Perbedaan antara konversi bernama .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)
}
Menggunakan .asFoo() saat membuat wrapper dependen, objek yang didekorasi, atau transmisi
Pemrosesan di Kotlin dilakukan menggunakan kata kunci as
. Hal ini mencerminkan perubahan
antarmuka, tetapi bukan perubahan identitas. Jika digunakan sebagai awalan dalam
fungsi ekstensi, .asFoo()
akan mendekorasi penerima. Mutasi dalam
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 penerima dan definisi class hasil akan mengurangi pengaitan antar-jenis. Konversi yang ideal hanya memerlukan akses API publik ke objek asli. Hal ini membuktikan dengan contoh bahwa developer juga dapat menulis konversi analog ke jenis pilihan mereka sendiri.
Menampilkan pengecualian tertentu yang sesuai
Metode tidak boleh menampilkan pengecualian generik seperti java.lang.Exception
atau
java.lang.Throwable
. Sebagai gantinya, pengecualian spesifik yang sesuai harus digunakan
seperti java.lang.NullPointerException
untuk memungkinkan developer 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
.
Pemroses 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.
Past tense 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 tambahkan dan hapus atau daftarkan dan batalkan pendaftaran. 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);
Menghindari pengambil untuk callback
Jangan tambahkan metode getFooCallback()
. Ini adalah jalan keluar yang menarik untuk
kasus saat developer mungkin ingin merantai callback yang ada bersama dengan
penggantian mereka sendiri, tetapi ini rapuh dan membuat status saat ini sulit
untuk dipikirkan 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 di-build untuk mengizinkan modifikasi callback yang digabungkan.
Menerima Eksekutor untuk mengontrol pengiriman callback
Saat mendaftarkan callback yang tidak memiliki ekspektasi threading eksplisit (hampir
di mana saja di luar toolkit UI), sebaiknya sertakan
parameter Executor
sebagai bagian dari pendaftaran agar developer dapat menentukan
thread tempat callback akan dipanggil.
public void registerFooCallback(
@NonNull @CallbackExecutor Executor executor,
@NonNull FooCallback callback)
Sebagai pengecualian terhadap
pedoman tentang parameter opsional biasa kami, Anda dapat
memberikan overload yang menghilangkan Executor
meskipun bukan argumen
terakhir dalam daftar parameter. Jika Executor
tidak disediakan, callback
harus dipanggil di thread utama menggunakan Looper.getMainLooper()
dan 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 adalah eksekutor
yang valid.
public class SynchronousExecutor implements Executor {
@Override
public void execute(Runnable r) {
r.run();
}
}
Artinya, saat menerapkan API yang menggunakan formulir 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 ke aplikasi dengan benar, bukan ke proses sistem yang memanggil aplikasi. Jika pengguna
API Anda menginginkan informasi UID atau PID pemanggil, ini harus menjadi
bagian eksplisit dari platform API Anda, bukan implisit berdasarkan tempat
Executor
yang mereka berikan dijalankan.
Penentuan Executor
harus didukung oleh API Anda. Dalam
kasus yang penting untuk performa, aplikasi mungkin perlu menjalankan kode segera atau
secara sinkron dengan masukan dari API Anda. Menerima Executor
akan mengizinkan hal ini.
Membuat HandlerThread
tambahan secara defensif atau mirip dengan trampolin dari
mengalahkan kasus penggunaan yang diinginkan ini.
Jika aplikasi akan menjalankan kode yang mahal di suatu tempat dalam prosesnya sendiri, biarkan aplikasi tersebut. Solusi yang akan ditemukan developer aplikasi untuk mengatasi batasan Anda akan jauh lebih sulit untuk didukung dalam jangka panjang.
Pengecualian untuk satu callback: 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()
Menggunakan Eksekutor, bukan Pengendali
Handler
Android digunakan sebagai standar untuk mengalihkan eksekusi callback ke
thread Looper
tertentu sebelumnya. 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/diinginkan.
Library konkurensi modern seperti kotlinx.coroutines atau RxJava menyediakan
mekanisme penjadwalan mereka sendiri yang melakukan pengirimannya sendiri saat diperlukan, sehingga
penting untuk memberikan kemampuan untuk 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 untuk pedoman ini jarang terjadi. Banding umum untuk pengecualian meliputi:
Saya harus menggunakan Looper
karena saya memerlukan Looper
ke epoll
untuk peristiwa tersebut.
Permintaan pengecualian ini diberikan karena manfaat Executor
tidak dapat
diwujudkan 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 melakukannya hanya akan merugikan diri sendiri, bukan memengaruhi kesehatan sistem secara keseluruhan. Aplikasi yang melakukannya dengan benar atau menggunakan framework serentak umum tidak akan dikenai penalti latensi tambahan.
Handler
secara lokal konsisten dengan API serupa lainnya di class yang sama.
Permintaan pengecualian ini diberikan secara situasional. Preferensinya adalah
overload berbasis Executor
yang akan ditambahkan, memigrasikan implementasi Handler
untuk
menggunakan implementasi Executor
baru. (myHandler::post
adalah
Executor
yang valid!) Bergantung pada ukuran class, jumlah metode Handler
yang ada, dan kemungkinan bahwa developer perlu menggunakan metode berbasis
Handler
yang ada bersama metode baru, pengecualian dapat diberikan untuk menambahkan metode
berbasis Handler
yang baru.
Simetri dalam pendaftaran
Jika ada cara untuk menambahkan atau mendaftarkan sesuatu, juga harus ada 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 mengaitkan callback ke permintaan.
class RequestParameters {
public int getId() { ... }
}
class RequestExecutor {
public void executeRequest(
RequestParameters parameters,
Consumer<RequestParameters> onRequestCompletedListener) { ... }
}
Objek callback beberapa metode
Callback beberapa metode harus lebih memilih interface
dan menggunakan metode default
saat menambahkan ke antarmuka yang dirilis sebelumnya. Sebelumnya, panduan ini
merekomendasikan abstract class
karena kurangnya 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 dengan yang dapat dilakukan 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
akan dilaporkan ke OutcomeReceiver.onResult
parameter
callback
requestFooAsync
dengan memanggilnya di executor
yang disediakan.
Setiap pengecualian yang akan ditampilkan oleh requestFoo
akan dilaporkan ke
metode OutcomeReceiver.onError
dengan cara yang sama.
Menggunakan OutcomeReceiver
untuk melaporkan hasil metode asinkron juga memberikan 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 pertimbangan dan pemeriksaan
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
menampilkan pengecualian saat tugasnya selesai tidak boleh menggunakan
OutcomeReceiver
sebagai jenis callback. Sebagai gantinya, pertimbangkan salah satu opsi lain
yang tercantum di bagian berikut.
Memilih antarmuka fungsional daripada membuat jenis single abstract method (SAM) baru
API level 24 menambahkan jenis java.util.function.*
(dokumen referensi),
yang menawarkan antarmuka SAM generik seperti Consumer<T>
yang
cocok untuk digunakan sebagai lambda callback. Sering kali, membuat antarmuka SAM baru
memberikan sedikit nilai dalam hal keamanan jenis atau menyampaikan intent sekaligus
memperluas area platform Android yang tidak perlu.
Pertimbangkan untuk menggunakan antarmuka umum ini, bukan membuat antarmuka 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 mengaktifkan 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 akan menggunakan API. Asumsikan developer menemukan metode menggunakan pelengkapan otomatis atau saat menjelajahi dokumen referensi API dan memiliki jumlah konteks minimal dari platform 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
didahului dengan "Metode ini...".
Jika metode tidak menggunakan parameter, tidak memiliki pertimbangan khusus, dan
menampilkan apa yang dilakukan 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
lain yang terkait. Gunakan tag Javadoc (misalnya, @see
dan {@link foo}
), bukan hanya
kata teks biasa.
Untuk contoh sumber berikut:
public static final int FOO = 0;
public static final int BAR = 1;
Jangan gunakan 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
.
Menjalankan target update-api atau dokumen saat menambahkan Javadoc
Aturan ini sangat penting saat menambahkan tag @link
atau @see
, dan memastikan
output terlihat seperti yang diharapkan. Output ERROR di Javadoc sering kali disebabkan oleh link
yang buruk. Target Make update-api
atau docs
akan melakukan pemeriksaan ini, tetapi
target docs
mungkin lebih cepat jika Anda hanya mengubah Javadoc dan tidak
perlu menjalankan target update-api
.
Menggunakan {@code foo} untuk membedakan nilai Java
Gabungkan nilai Java seperti true
, false
, dan null
dengan {@code...}
untuk
membedakan nilai tersebut dari teks dokumentasi.
Saat menulis dokumentasi di sumber Kotlin, Anda dapat menggabungkan 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 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
Dokumentasikan alasan anotasi @hide
dan @removed
disembunyikan dari API publik.
Sertakan petunjuk 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 bersumber Kotlin yang ditujukan untuk digunakan oleh
klien Java, anotasikan fungsi dengan
@Throws
.
Jika metode menampilkan pengecualian yang tidak dicentang yang menunjukkan error yang dapat dicegah, misalnya
IllegalArgumentException
atau IllegalStateException
, dokumentasikan
pengecualian dengan penjelasan alasan pengecualian ditampilkan. Pengecualian
yang ditampilkan juga harus menunjukkan alasannya.
Kasus pengecualian tertentu yang tidak dicentang dianggap implisit dan tidak perlu
didokumentasikan, seperti NullPointerException
atau IllegalArgumentException
saat 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 mungkin menampilkan 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 kembali dari
metode yang dianotasi.
Mengakhiri 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 kelas) segera setelah melihat titik (.) diikuti dengan spasi. Hal ini menyebabkan dua masalah:
- Jika dokumen singkat tidak diakhiri dengan titik, dan jika anggota tersebut memiliki dokumen turunan
yang diambil oleh alat, sinopsis juga akan mengambil dokumen
turunan 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 non-pemisah setelah titik; namun, jangan membuat kesalahan ini di awal.
Memformat dokumen untuk dirender dalam HTML
Javadoc dirender dalam HTML, jadi format dokumen ini dengan benar:
Baris baru harus menggunakan tag
<p>
eksplisit. Jangan tambahkan tag</p>
penutup.Jangan gunakan ASCII untuk merender daftar atau tabel.
Daftar harus menggunakan
<ul>
atau<ol>
untuk daftar tidak urut dan urut. 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"
di tag mana 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 meng-escape-nya dengan entity HTML<
dan>
.Atau, Anda dapat membiarkan tanda kurung mentah
<>
dalam cuplikan kode jika Anda menggabungkan bagian yang melanggar di{@code foo}
. Contoh:<pre>{@code <manifest>}</pre>
Ikuti 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 di-build ke dalam framework Android (misalnya, Bundle
atau
Parcelable
).
Builder intent harus menggunakan pola create*Intent()
Kreator untuk intent harus menggunakan metode bernama createFooIntent()
.
Menggunakan Paket, bukan membuat struktur data tujuan umum baru
Hindari membuat struktur data tujuan umum baru untuk merepresentasikan pemetaan nilai berjenis
kunci arbitrer. Sebagai gantinya, pertimbangkan untuk menggunakan Bundle
.
Hal ini biasanya muncul saat menulis API platform yang berfungsi sebagai saluran komunikasi antara aplikasi dan layanan nonplatform, dengan platform tidak membaca data yang dikirim melalui saluran 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 menerapkan Parcelable
, kolom CREATOR
-nya juga harus berupa API
publik dan konstruktor class yang menggunakan argumen Parcel
harus bersifat pribadi.
Menggunakan CharSequence untuk string UI
Saat string ditampilkan di antarmuka pengguna, gunakan CharSequence
untuk mengizinkan
instance Spannable
.
Jika hanya kunci atau beberapa label atau nilai lain yang tidak terlihat oleh pengguna,
String
tidak masalah.
Menghindari penggunaan Enums
IntDef
harus digunakan melalui enum di semua API platform, dan harus dipertimbangkan dengan cermat
dalam API library yang tidak dipaketkan. 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 dipaketkan, 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 jelas cakupannya dan dapat ditemukan
- Mengaktifkan verifikasi waktu kompilasi
- Misalnya, pernyataan
when
di Kotlin yang menampilkan nilai
- Misalnya, pernyataan
- Adalah class yang berfungsi dan 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, dengan 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 menjadi netral vendor. API harus bersifat umum dan dapat digunakan secara setara oleh integrator sistem atau aplikasi dengan izin yang diperlukan.
Implementasi parcelable harus bersifat final
Class parcelable yang ditentukan oleh platform selalu dimuat dari
framework.jar
, sehingga aplikasi tidak valid untuk mencoba mengganti implementasi
Parcelable
.
Jika aplikasi pengirim memperluas Parcelable
, aplikasi penerima tidak akan memiliki
implementasi kustom pengirim untuk di-unzip. Catatan tentang kompatibilitas
mundur: jika class Anda secara historis bukan final, tetapi tidak memiliki
konstruktor yang tersedia secara publik, Anda masih dapat menandainya final
.
Metode yang memanggil proses sistem harus menampilkan kembali 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 ditampilkan kembali sebagai RuntimeException
untuk mencegah aplikasi
mempertahankan keputusan keamanan atau kebijakan.
Jika Anda mengetahui sisi lain dari panggilan Binder
adalah proses sistem, kode boilerplate
ini adalah praktik terbaik:
try {
...
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
Menampilkan pengecualian tertentu untuk perubahan API
Perilaku API publik dapat berubah di seluruh API level dan menyebabkan error aplikasi (misalnya untuk menerapkan kebijakan keamanan baru).
Jika API perlu ditampilkan untuk permintaan yang sebelumnya valid, tampilkan pengecualian khusus
baru, bukan pengecualian umum. Misalnya, ExportedFlagRequired
,
bukan SecurityException
(dan ExportedFlagRequired
dapat memperluas
SecurityException
).
Hal ini akan membantu developer dan alat aplikasi 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
dari 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
meningkatkan API di masa mendatang.
Menghindari penggunaan BitSet
java.util.BitSet
sangat cocok untuk implementasi, tetapi tidak untuk API publik. Nilai ini
dapat diubah, memerlukan alokasi untuk panggilan metode frekuensi tinggi, dan tidak
memberikan makna semantik untuk apa yang diwakili setiap bit.
Untuk skenario berperforma tinggi, gunakan int
atau long
dengan @IntDef
. Untuk
skenario berperforma rendah, pertimbangkan Set<EnumType>
. Untuk data biner mentah, gunakan
byte[]
.
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 gunakan
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 kumpulan
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).
Dengan demikian, penggunaan anotasi ini harus ditandai dengan anotasi dokumen
@hide
di platform atau anotasi kode @RestrictTo.Scope.LIBRARY)
di
library. Keduanya harus ditandai @Retention(RetentionPolicy.SOURCE)
dalam
kedua kasus tersebut untuk mencegahnya muncul di stub atau JAR API.
@RestrictTo(RestrictTo.Scope.LIBRARY)
@Retention(RetentionPolicy.SOURCE)
@IntDef({
STREAM_TYPE_FULL_IMAGE_DATA,
STREAM_TYPE_EXIF_DATA_ONLY,
})
public @interface ExifStreamType {}
Saat mem-build SDK platform dan AAR library, alat akan mengekstrak anotasi dan memaketkannya secara terpisah dari sumber yang dikompilasi. Android Studio membaca format yang dipaketkan ini dan menerapkan definisi jenis.
Jangan tambahkan kunci penyedia setelan baru
Jangan mengekspos kunci baru dari
Settings.Global
,
Settings.System
,
atau
Settings.Secure
.
Sebagai gantinya, tambahkan API Java pengambil dan penyetel 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
pengambil/penyemat:
- 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 dengan izin kustom.
- 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
LocationManager.isLocationEnabled()
yang tepat 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 pemroses atau, di
androidx, ListenableFuture
API.
Subclass Activity
tidak dapat disusun. Memperluas aktivitas untuk
fitur Anda akan 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 dengan Context
, seperti apa pun yang ditampilkan dari
Context.getSystemService()
harus menggunakan pengguna yang terikat dengan 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 mewakili ID pengguna harus dianotasi dengan
@UserIdInt
.
Foobar getFoobarForUser(@UserIdInt int user);
Memilih pemroses atau callback untuk menyiarkan intent
Intent siaran sangat canggih, tetapi menghasilkan perilaku baru yang dapat berdampak negatif pada kesehatan sistem, sehingga intent siaran baru harus ditambahkan dengan cermat.
Berikut beberapa masalah spesifik yang menyebabkan kami tidak menyarankan pengenalan intent siaran baru:
Saat mengirim siaran tanpa tanda
FLAG_RECEIVER_REGISTERED_ONLY
, siaran akan memaksa memulai aplikasi yang belum berjalan. Meskipun terkadang merupakan hasil yang diinginkan, hal ini dapat menyebabkan puluhan aplikasi mengalami stampede, yang berdampak negatif pada kesehatan sistem. Sebaiknya gunakan strategi alternatif, sepertiJobScheduler
, untuk berkoordinasi dengan lebih baik saat berbagai prasyarat terpenuhi.Saat mengirim siaran, ada sedikit kemampuan untuk memfilter atau menyesuaikan konten yang dikirim ke aplikasi. Hal ini menyulitkan atau bahkan tidak memungkinkan untuk merespons masalah privasi di masa mendatang, 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 peristiwa Anda secara tepat waktu. Kami telah mengamati beberapa antrean siaran di lapangan yang memiliki latensi menyeluruh 10 menit atau lebih.
Karena alasan ini, sebaiknya fitur baru mempertimbangkan penggunaan pemroses atau
callback atau fasilitas lain seperti JobScheduler
, bukan intent
siaran.
Jika intent 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 pengaktifan aplikasi. - Jika memungkinkan, gunakan
Intent.setPackage()
atauIntent.setComponent()
untuk menargetkan siaran ke aplikasi tertentu yang diinginkan. Misalnya,ACTION_MEDIA_BUTTON
menggunakan desain ini untuk berfokus pada aplikasi saat ini yang menangani 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 terikat 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 sepenuhnya memenuhi syarat. Konstanta ini harus dianotasi dengan@SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
. - Dokumentasikan di class bahwa developer harus menambahkan
<intent-filter>
keAndroidManifest.xml
untuk menerima Intent dari platform. - Sebaiknya pertimbangkan untuk menambahkan izin tingkat sistem guna mencegah aplikasi nakal
mengirim
Intent
ke layanan developer.
Interop Kotlin-Java
Lihat panduan interop Kotlin-Java Android resmi untuk daftar lengkap panduan. Pedoman tertentu 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 mempersulit proses debug.
Lihat Panduan interop Kotlin-Java atau Panduan asinkron untuk mendapatkan panduan tertentu.
Objek pendamping
Kotlin menggunakan companion object
untuk mengekspos anggota statis. Dalam beberapa kasus, ini
akan muncul dari Java pada class dalam bernama Companion
, bukan pada
class yang berisi. Class Companion
dapat ditampilkan sebagai class kosong dalam file teks API, yang berfungsi sebagaimana mestinya.
Untuk memaksimalkan kompatibilitas dengan Java, anotasikan
kolom non-konstanta
objek pendamping dengan @JvmField
dan
fungsi publik
dengan @JvmStatic
untuk mengeksposnya secara langsung di class penampung.
companion object {
@JvmField val BIG_INTEGER_ONE = BigInteger.ONE
@JvmStatic fun fromPointF(pointf: PointF) {
/* ... */
}
}
Evolusi API platform Android
Bagian ini menjelaskan kebijakan terkait jenis perubahan yang dapat Anda buat 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 biner di platform API publik yang telah selesai. Jenis perubahan
ini biasanya 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
Eclipse Foundation untuk mendapatkan penjelasan mendetail tentang jenis perubahan API yang kompatibel di
Java. Perubahan yang dapat menyebabkan gangguan biner di API tersembunyi (misalnya, sistem) harus mengikuti
siklus penghentian/penggantian.
Perubahan yang dapat menyebabkan gangguan pada sumber
Sebaiknya jangan lakukan perubahan yang dapat menyebabkan gangguan pada sumber meskipun tidak menyebabkan gangguan biner. Salah satu
contoh perubahan yang kompatibel dengan biner tetapi merusak sumber adalah menambahkan generik ke
class yang ada, yang
kompatibel dengan biner
tetapi dapat menyebabkan error kompilasi karena pewarisan atau referensi ambigu.
Perubahan yang merusak 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 ketepatan kode. Misalnya, menambahkan anotasi nullability ke sumber Java akan meningkatkan interoperabilitas dengan kode Kotlin dan mengurangi kemungkinan error, tetapi sering kali memerlukan perubahan -- terkadang perubahan yang signifikan -- pada kode sumber.
Perubahan pada API pribadi
Anda dapat mengubah API yang dianotasi 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
- 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, laporkan bug kepada pengguna internal yang memberi tahu mereka bahwa API tidak digunakan lagi. Hal ini membantu memvalidasi bahwa API pengganti memadai.
- Tandai kode dengan
- API y+2 - Penghapusan lembut
- Tandai kode dengan
@removed
. - Secara opsional, tampilkan atau tidak lakukan operasi untuk aplikasi yang menargetkan level SDK saat ini untuk rilis.
- Tandai kode dengan
- API y+3 - Penghapusan permanen
- Hapus kode sepenuhnya dari hierarki sumber.
Penghentian penggunaan
Kami menganggap penghentian sebagai perubahan API, dan hal ini dapat terjadi dalam rilis utama (seperti
huruf). Gunakan anotasi sumber @Deprecated
dan anotasi dokumen @deprecated
<summary>
secara bersamaan saat menghentikan penggunaan API. Ringkasan Anda harus
menyertakan strategi migrasi. Strategi ini mungkin 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 harus juga menghentikan penggunaan API yang ditentukan dalam XML dan ditampilkan di Java, termasuk
atribut dan properti yang dapat ditata gaya yang ditampilkan di class android.R
, dengan
ringkasan:
<!-- Attribute whether the accessibility service ...
{@deprecated Not used by the framework}
-->
<attr name="canRequestEnhancedWebAccessibility" format="boolean" />
Kapan harus menghentikan penggunaan API
Penghentian penggunaan paling berguna untuk mencegah adopsi API dalam kode baru.
Kami juga mewajibkan Anda menandai API sebagai @deprecated
sebelum
@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 API mencakup:
javac
memunculkan peringatan selama kompilasi.- Peringatan penghentian penggunaan tidak dapat disembunyikan secara global atau dijadikan dasar pengukuran, sehingga
developer yang menggunakan
-Werror
harus memperbaiki atau menyembunyikan setiap penggunaan API yang tidak digunakan lagi secara terpisah sebelum mereka dapat mengupdate versi SDK kompilasi. - Peringatan penghentian penggunaan pada impor class yang tidak digunakan lagi tidak dapat disembunyikan. Akibatnya, developer harus menyisipkan nama class yang sepenuhnya memenuhi syarat untuk setiap penggunaan class yang tidak digunakan lagi sebelum mereka dapat memperbarui versi SDK kompilasi.
- Peringatan penghentian penggunaan tidak dapat disembunyikan secara global atau dijadikan dasar pengukuran, sehingga
developer yang menggunakan
- Dokumentasi di
d.android.com
menampilkan pemberitahuan penghentian. - IDE seperti Android Studio menampilkan peringatan di situs penggunaan API.
- IDE mungkin menurunkan peringkat atau menyembunyikan API dari pelengkapan otomatis.
Akibatnya, penghentian API dapat membuat developer yang paling
memperhatikan kesehatan kode (yang menggunakan -Werror
) tidak mau mengadopsi SDK baru.
Developer yang tidak peduli dengan peringatan dalam kode yang ada kemungkinan
akan mengabaikan penghentian penggunaan sepenuhnya.
SDK yang memperkenalkan banyak penghentian penggunaan akan memperburuk kedua kasus ini.
Oleh karena itu, sebaiknya hentikan penggunaan API hanya jika:
- Kami berencana untuk
@remove
API dalam rilis mendatang. - Penggunaan API menyebabkan perilaku yang salah atau tidak ditentukan yang tidak dapat kami perbaiki tanpa mengganggu kompatibilitas.
Saat Anda menghentikan penggunaan API dan menggantinya dengan API baru, sebaiknya tambahkan API kompatibilitas yang sesuai ke library Jetpack seperti
androidx.core
untuk menyederhanakan dukungan perangkat lama dan baru.
Kami tidak merekomendasikan penghentian 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 API sesuai jika 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, implementasi pengujian harus tetap sama, dan pengujian harus terus lulus 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
ketepatan lint (misalnya, @Nullable
) ke API
yang tidak digunakan lagi yang ada, tetapi tidak boleh menambahkan API baru.
Jangan tambahkan API baru sebagai tidak digunakan lagi. Jika ada API yang ditambahkan dan kemudian tidak digunakan lagi dalam siklus pra-rilis (sehingga awalnya akan memasuki platform API publik sebagai tidak digunakan lagi), Anda harus menghapusnya sebelum menyelesaikan API.
Penghapusan sementara
Penghapusan lembut adalah perubahan yang dapat menyebabkan gangguan 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 soft. Ringkasan Anda harus menyertakan alasan penghapusan dan dapat menyertakan strategi migrasi, seperti yang kami jelaskan di Penghentian.
Perilaku API yang dihapus secara soft dapat dipertahankan apa adanya, tetapi yang lebih penting, harus dipertahankan sehingga pemanggil yang ada tidak akan error saat memanggil API. Dalam beberapa kasus, hal itu mungkin berarti mempertahankan perilaku.
Cakupan pengujian harus dipertahankan, tetapi konten pengujian mungkin perlu berubah untuk mengakomodasi perubahan perilaku. Pengujian masih harus memvalidasi bahwa pemanggil yang ada tidak mengalami error pada waktu proses. Anda dapat mempertahankan perilaku API yang dihapus secara soft seperti apa adanya, tetapi yang lebih penting, Anda harus mempertahankannya sehingga pemanggil yang ada tidak akan mengalami error saat memanggil API. Dalam beberapa kasus, hal itu mungkin berarti mempertahankan perilaku.
Anda harus mempertahankan cakupan pengujian, tetapi konten pengujian mungkin perlu berubah untuk mengakomodasi perubahan perilaku. Pengujian masih harus memvalidasi bahwa pemanggil yang ada tidak mengalami error pada waktu proses.
Pada 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 @hide
API:
/**
* 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 terus berhasil dikompilasi terhadap SDK dan biner sebelumnya yang
mereferensikan API akan terus berfungsi.
Kategori API tertentu tidak boleh dihapus secara soft. Anda tidak boleh menghapus secara soft kategori API tertentu.
Metode abstrak
Anda tidak boleh menghapus metode abstrak secara soft di class yang mungkin diperluas oleh developer. Tindakan ini membuat developer tidak dapat berhasil memperluas class di semua tingkat SDK.
Dalam kasus yang jarang terjadi, jika developer tidak pernah dan tidak akan dapat memperluas class, Anda masih dapat menghapus metode abstrak secara soft.
Penghapusan permanen
Penghapusan total adalah perubahan yang menyebabkan gangguan 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 kasus (>95%). API yang tidak direkomendasikan berbeda dengan API yang tidak digunakan lagi karena
ada kasus penggunaan penting yang sempit yang mencegah penghentian penggunaan. Saat menandai
API sebagai tidak direkomendasikan, 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 dianjurkan.
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
berkomunikasi dengan jelas saat developer mencoba memposting peristiwa yang terlalu besar untuk
dikirim melalui Binder
.
Namun, untuk menghindari masalah pada aplikasi yang ada, sebaiknya
pertahankan perilaku yang aman untuk aplikasi lama. Kami telah menjaga 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 menggunakan desain Framework Kompatibilitas Aplikasi ini, developer dapat sementara menonaktifkan perubahan perilaku tertentu selama rilis pratinjau dan beta sebagai bagian dari proses debug aplikasi, bukan memaksa mereka untuk menyesuaikan dengan puluhan perubahan perilaku secara bersamaan.
Kompatibilitas dengan versi baru
Kompatibilitas maju adalah karakteristik desain yang memungkinkan sistem menerima input yang ditujukan untuk versi yang lebih baru. Dalam hal desain API, Anda harus memberikan perhatian khusus pada desain awal serta perubahan mendatang karena developer berharap dapat menulis kode sekali, mengujinya sekali, dan menjalankannya di mana saja tanpa masalah.
Berikut adalah penyebab masalah kompatibilitas maju yang paling umum di Android:
- Menambahkan konstanta baru ke set (seperti
@IntDef
atauenum
) yang sebelumnya diasumsikan sudah selesai (misalnya, jikaswitch
memilikidefault
yang menampilkan pengecualian). - Menambahkan dukungan untuk fitur yang tidak diambil langsung di platform API
(misalnya, dukungan untuk menetapkan resource jenis
ColorStateList
dalam XML yang sebelumnya hanya mendukung resource<color>
). - Memperlonggar pembatasan pada pemeriksaan waktu proses, misalnya menghapus
pemeriksaan
requireNotNull()
yang ada pada versi yang lebih rendah.
Dalam semua kasus ini, developer mengetahui bahwa ada yang salah hanya pada waktu berjalan. 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 mengganggu kompatibilitas biner atau sumber dan lint API tidak akan mendeteksi masalah ini.
Akibatnya, desainer API harus memperhatikan dengan cermat saat mengubah class yang ada. Ajukan pertanyaan, "Apakah perubahan ini akan menyebabkan kode yang ditulis dan diuji hanya terhadap versi terbaru platform gagal pada versi yang lebih rendah?"
Skema XML
Jika skema XML berfungsi sebagai antarmuka yang stabil di antara komponen, skema tersebut harus ditentukan secara eksplisit dan harus berkembang dengan cara yang kompatibel dengan versi sebelumnya, serupa dengan API Android lainnya. Misalnya, struktur elemen dan atribut XML harus dipertahankan mirip dengan cara metode dan variabel dikelola 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 mengupdate subsistem ("modul utama") Android OS satu per satu, bukan mengupdate seluruh image sistem.
Modul utama harus "di-unbundle" 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 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, seseorang akan menggunakan
ServiceManager
,
tetapi modul utama tidak dapat menggunakannya karena tersembunyi. Class ini disembunyikan
karena modul utama tidak boleh mendaftar atau merujuk ke objek binder
layanan sistem yang diekspos oleh platform statis atau oleh modul lain.
Sebagai gantinya, modul utama dapat menggunakan pola berikut agar dapat mendaftar dan mendapatkan referensi ke layanan binder yang diterapkan 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 darinya.
- Metode pengambil publik yang menampilkan instance
ServiceRegisterer
untuk nama tertentu. Jika memiliki satu objek binder, Anda memerlukan satu metode pengambil. Jika Anda memiliki dua, Anda memerlukan dua pengambil. - Di
ActivityThread.initializeMainlineModules()
, buat instance class ini, lalu teruskan ke metode statis yang ditampilkan oleh modul Anda. Biasanya, Anda menambahkan@SystemApi(client = MODULE_LIBRARIES)
API statis di classFrameworkInitializer
yang menggunakannya.
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 oleh mereka.
Berikut cara telepon mendapatkan referensi ke layanan telepon: link penelusuran kode.
Jika menerapkan objek binder layanan dalam kode native, Anda menggunakan
API native AServiceManager
.
API ini sesuai dengan ServiceManager
Java API, tetapi API native
ditampilkan 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 reguler.
Jika izin yang ditentukan hanya digunakan secara internal dalam modul, nama izinnya harus diawali 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 ini 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 platform
API-nya, misalnya
HealthPermissions.READ_ACTIVE_CALORIES_BURNED
.