Pedoman Android API

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:

  1. Konstruksi dikelola oleh class, sehingga mencegah penggunaan palsu
  2. Pengujian tidak dapat bersifat hermetis karena sifat statis singleton
  3. 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 waktu SystemClock.elapsedRealtime().
  • @UptimeMillisLong: Nilai adalah stempel waktu non-negatif dalam basis waktu SystemClock.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 sejak 1970-01-01T00:00:00Z, sehingga dalam basis waktu System.currentTimeMillis().
  • @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 waktu SystemClock#elapsedRealtime().
  • @UptimeMillisLong: Nilai adalah stempel waktu non-negatif dalam basis waktu SystemClock#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:

  1. 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());
    
  2. 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?

  1. 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.
  2. Pastikan opsi penanganan error yang Anda pilih memberi Anda fleksibilitas untuk memperkenalkan jenis error baru di masa mendatang. Untuk @IntDef, hal ini berarti menyertakan nilai OTHER atau UNKNOWN - saat menampilkan kode baru, Anda dapat memeriksa targetSdkVersion 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.
  3. 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 jenis B, dan B 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:

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() { ... }

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 dokumen R.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 di View.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 menggunakan class="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 &lt; dan &gt;.

  • 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.
  • 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
  • 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, seperti JobScheduler, 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() atau Intent.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:

  1. 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).
  2. Dokumentasikan di class bahwa developer harus menambahkan <intent-filter> ke AndroidManifest.xml untuk menerima Intent dari platform.
  3. 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.
  • 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.
  • 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.
  • 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 atau enum) yang sebelumnya diasumsikan sudah selesai (misalnya, jika switch memiliki default 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: StatsFrameworkInitializer

  • Tandai dengan @SystemApi(client = MODULE_LIBRARIES).

  • Tambahkan metode public static void registerServiceWrappers() ke dalamnya.

  • Gunakan SystemServiceRegistry.registerContextAwareService() untuk mendaftarkan class pengelola layanan saat memerlukan referensi ke Context.

  • Gunakan SystemServiceRegistry.registerStaticService() untuk mendaftarkan class pengelola layanan jika tidak memerlukan referensi ke Context.

  • Panggil metode registerServiceWrappers() dari penginisialisasi statis SystemServiceRegistry.

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 TelephonyServiceManager

  • Tampilkan 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 class FrameworkInitializer 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.