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 dijalankannya terhadap API.

Anggap ini sebagai panduan untuk aturan yang dipatuhi oleh alat Lint tersebut, ditambah saran umum tentang aturan yang tidak dapat dikodifikasi ke dalam alat tersebut dengan akurasi tinggi.

Alat API Lint

API Lint diintegrasikan ke dalam alat analisis statis Metalava dan berjalan secara otomatis selama validasi di CI. Anda dapat menjalankannya secara manual dari checkout platform lokal menggunakan m checkapi atau checkout AndroidX lokal menggunakan ./gradlew :path:to:project:checkApi.

Aturan API

Platform Android dan banyak library Jetpack sudah ada sebelum serangkaian pedoman ini dibuat, dan kebijakan yang ditetapkan di halaman ini terus berkembang untuk memenuhi kebutuhan ekosistem Android.

Akibatnya, beberapa API yang ada mungkin tidak mengikuti panduan. Dalam kasus lain, developer aplikasi mungkin mendapatkan pengalaman pengguna yang lebih baik jika API baru tetap konsisten dengan API yang ada, daripada mematuhi pedoman secara ketat.

Gunakan penilaian Anda dan hubungi Dewan API jika ada pertanyaan sulit tentang API yang perlu diselesaikan atau pedoman yang perlu diperbarui.

Dasar-dasar API

Kategori ini berkaitan dengan aspek inti Android API.

Semua API harus diterapkan

Terlepas dari audiens API (misalnya, publik atau @SystemApi), semua platform API harus diterapkan saat digabungkan atau diekspos sebagai API. Jangan gabungkan stub API dengan implementasi yang akan dilakukan di lain waktu.

Platform API tanpa implementasi memiliki beberapa masalah:

  • Tidak ada jaminan bahwa permukaan yang tepat atau lengkap telah terekspos. Sebelum API diuji atau digunakan oleh klien, tidak ada cara untuk memverifikasi bahwa klien memiliki API yang sesuai untuk dapat menggunakan fitur tersebut.
  • API tanpa penerapan tidak dapat diuji di Pratinjau Developer.
  • API tanpa implementasi tidak dapat diuji di CTS.

Semua API harus diuji

Hal ini sejalan dengan persyaratan CTS platform, kebijakan AndroidX, dan secara umum gagasan bahwa API harus diimplementasikan.

Pengujian platform API memberikan jaminan dasar bahwa platform API dapat digunakan dan kita telah menangani kasus penggunaan yang diharapkan. Pengujian keberadaan tidaklah cukup; perilaku API itu sendiri harus diuji.

Perubahan yang menambahkan API baru harus menyertakan pengujian yang sesuai dalam CL atau topik Gerrit yang sama.

API juga harus dapat diuji. Anda harus dapat menjawab pertanyaan, "Bagaimana cara developer aplikasi menguji kode yang menggunakan API Anda?"

Semua API harus didokumentasikan

Dokumentasi adalah bagian penting dari kegunaan API. Meskipun sintaksis permukaan API mungkin tampak jelas, klien baru tidak akan memahami semantik, perilaku, atau konteks di balik API.

Semua API yang dihasilkan harus mematuhi pedoman

API yang dihasilkan oleh alat harus mengikuti panduan API yang sama dengan kode yang ditulis secara manual.

Alat yang tidak disarankan untuk membuat API:

  • AutoValue: melanggar pedoman dalam berbagai cara, misalnya, tidak ada cara untuk menerapkan class nilai akhir maupun builder akhir dengan cara kerja AutoValue.

Gaya kode

Kategori ini berkaitan dengan gaya kode umum yang harus digunakan developer, terutama saat menulis API publik.

Ikuti konvensi coding standar, kecuali jika dinyatakan lain

Konvensi coding Android didokumentasikan untuk kontributor eksternal di sini:

https://source.android.com/source/code-style.html

Secara keseluruhan, kami cenderung mengikuti konvensi coding Java dan Kotlin standar.

Akronim tidak boleh ditulis dengan huruf kapital dalam nama metode

Misalnya: nama metode harus runCtsTests, bukan runCTSTests.

Nama tidak boleh diakhiri dengan Impl

Hal ini akan mengekspos detail implementasi, jadi hindari hal tersebut.

Class

Bagian ini menjelaskan aturan tentang class, antarmuka, dan pewarisan.

Mewarisi class publik baru dari class dasar yang sesuai

Pewarisan mengekspos elemen API di subclass Anda yang mungkin tidak sesuai. Misalnya, subclass publik baru dari FrameLayout terlihat seperti FrameLayout ditambah perilaku dan elemen API baru. Jika API yang diwariskan tersebut tidak sesuai untuk kasus penggunaan Anda, wariskan dari class yang lebih tinggi dalam hierarki, misalnya, ViewGroup atau View.

Jika Anda tergoda untuk mengganti metode dari class dasar untuk memunculkan UnsupportedOperationException, pertimbangkan kembali class dasar yang Anda gunakan.

Menggunakan class koleksi dasar

Baik mengambil koleksi sebagai argumen atau menampilkannya sebagai nilai, selalu pilih class dasar daripada implementasi spesifik (seperti menampilkan List<Foo>, bukan ArrayList<Foo>).

Gunakan class dasar yang menyatakan batasan yang sesuai untuk API. Misalnya, gunakan List untuk API yang pengumpulannya harus diurutkan, dan gunakan Set untuk API yang pengumpulannya harus terdiri dari elemen unik.

Di Kotlin, sebaiknya gunakan koleksi yang tidak dapat diubah. Lihat Mutabilitas koleksi untuk mengetahui detail selengkapnya.

Class abstrak versus antarmuka

Java 8 menambahkan dukungan untuk metode antarmuka default, yang memungkinkan desainer API menambahkan metode ke antarmuka sambil mempertahankan kompatibilitas biner. Kode platform dan semua library Jetpack harus menargetkan Java 8 atau yang lebih baru.

Dalam kasus ketika implementasi default tidak memiliki status, desainer API harus lebih memilih antarmuka daripada class abstrak -- yaitu, metode antarmuka default dapat diimplementasikan sebagai panggilan ke metode antarmuka lainnya.

Jika konstruktor atau status internal diperlukan oleh implementasi default, class abstrak harus digunakan.

Dalam kedua kasus tersebut, desainer API dapat memilih untuk membuat satu metode abstrak agar penggunaannya sebagai lambda lebih sederhana:

public interface AnimationEndCallback {
  // Always called, must be implemented.
  public void onFinished(Animation anim);
  // Optional callbacks.
  public default void onStopped(Animation anim) { }
  public default void onCanceled(Animation anim) { }
}

Nama class harus mencerminkan apa yang diperluasnya

Misalnya, class yang memperluas Service harus diberi nama FooService agar lebih jelas:

public class IntentHelper extends Service {}
public class IntentService extends Service {}

Akhiran umum

Hindari penggunaan sufiks nama class generik seperti Helper dan Util untuk kumpulan metode utilitas. Sebagai gantinya, letakkan metode langsung di class terkait atau ke dalam fungsi ekstensi Kotlin.

Jika metode menjembatani beberapa class, beri class yang berisi nama yang bermakna yang menjelaskan fungsinya.

Dalam kasus yang sangat terbatas, penggunaan akhiran Helper mungkin sesuai:

  • Digunakan untuk komposisi perilaku default
  • Mungkin melibatkan delegasi perilaku yang ada ke class baru
  • Mungkin memerlukan status yang dipertahankan
  • Biasanya melibatkan View

Misalnya, jika tooltip backport memerlukan persistensi status yang terkait dengan View dan memanggil beberapa metode pada View untuk menginstal backport, TooltipHelper akan menjadi nama class yang dapat diterima.

Jangan mengekspos kode yang dihasilkan IDL sebagai API publik secara langsung

Simpan kode yang dihasilkan IDL sebagai detail implementasi. Hal ini mencakup protobuf, soket, FlatBuffers, atau platform API non-Java, non-NDK lainnya. Namun, sebagian besar IDL di Android menggunakan AIDL, jadi halaman ini berfokus pada AIDL.

Class AIDL yang dihasilkan tidak memenuhi persyaratan panduan gaya API (misalnya, class tersebut tidak dapat menggunakan overloading) dan alat AIDL tidak secara eksplisit dirancang untuk mempertahankan kompatibilitas API bahasa, sehingga Anda tidak dapat menyematkannya dalam API publik.

Sebagai gantinya, tambahkan lapisan API publik di atas antarmuka AIDL, meskipun awalnya berupa wrapper dangkal.

Antarmuka Binder

Jika antarmuka Binder adalah detail penerapan, antarmuka tersebut dapat diubah dengan bebas di masa mendatang, dengan lapisan publik yang memungkinkan kompatibilitas mundur yang diperlukan dipertahankan. Misalnya, Anda mungkin perlu menambahkan argumen baru ke panggilan internal, atau mengoptimalkan traffic IPC dengan menggunakan batching atau streaming, menggunakan memori bersama, atau yang serupa. Semua hal ini tidak dapat dilakukan jika antarmuka AIDL Anda juga merupakan API publik.

Misalnya, jangan mengekspos FooService sebagai API publik secara langsung:

// BAD: Public API generated from IFooService.aidl
public class IFooService {
   public void doFoo(String foo);
}

Sebagai gantinya, bungkus antarmuka Binder di dalam pengelola atau class lain:

/**
 * @hide
 */
public class IFooService {
   public void doFoo(String foo);
}

public IFooManager {
   public void doFoo(String foo) {
      mFooService.doFoo(foo);
   }
}

Jika nanti diperlukan argumen baru untuk panggilan ini, overload antarmuka internal yang minimal dan praktis dapat ditambahkan ke API publik. Anda dapat menggunakan lapisan pembungkus untuk menangani masalah kompatibilitas mundur lainnya saat implementasi berkembang:

/**
 * @hide
 */
public class IFooService {
   public void doFoo(String foo, int flags);
}

public IFooManager {
   public void doFoo(String foo) {
      if (mAppTargetSdkLevel < 26) {
         useOldFooLogic(); // Apps targeting API before 26 are broken otherwise
         mFooService.doFoo(foo, FLAG_THAT_ONE_WEIRD_HACK);
      } else {
         mFooService.doFoo(foo, 0);
      }
   }

   public void doFoo(String foo, int flags) {
      mFooService.doFoo(foo, flags);
   }
}

Untuk antarmuka Binder yang bukan bagian dari platform Android (misalnya, antarmuka layanan yang diekspor oleh layanan Google Play untuk digunakan aplikasi), persyaratan untuk antarmuka IPC yang stabil, dipublikasikan, dan diberi versi berarti jauh lebih sulit untuk mengembangkan antarmuka itu sendiri. Namun, tetap bermanfaat untuk memiliki lapisan wrapper di sekitarnya, agar sesuai dengan panduan API lainnya dan mempermudah penggunaan API publik yang sama untuk versi baru antarmuka IPC, jika hal itu diperlukan.

Jangan gunakan objek Binder mentah di API publik

Objek Binder tidak memiliki arti dengan sendirinya dan oleh karena itu tidak boleh digunakan di API publik. Salah satu kasus penggunaan umum adalah menggunakan Binder atau IBinder sebagai token karena memiliki semantik identitas. Daripada menggunakan objek Binder mentah, gunakan class token wrapper.

public final class IdentifiableObject {
  public Binder getToken() {...}
}
public final class IdentifiableObjectToken {
  /**
   * @hide
   */
  public Binder getRawValue() {...}

  /**
   * @hide
   */
  public static IdentifiableObjectToken wrapToken(Binder rawValue) {...}
}

public final class IdentifiableObject {
  public IdentifiableObjectToken getToken() {...}
}

Class pengelola harus bersifat final

Class pengelola harus dideklarasikan sebagai final. Class pengelola berkomunikasi dengan layanan sistem dan merupakan satu-satunya titik interaksi. Tidak perlu penyesuaian, jadi deklarasikan sebagai final.

Jangan gunakan CompletableFuture atau Future

java.util.concurrent.CompletableFuture memiliki platform API besar yang memungkinkan mutasi arbitrer nilai masa depan dan memiliki default yang rentan error.

Sebaliknya, java.util.concurrent.Future tidak memiliki kemampuan untuk memproses permintaan secara non-blocking, sehingga sulit digunakan dengan kode asinkron.

Dalam kode platform dan API library level rendah yang digunakan oleh Kotlin dan Java, sebaiknya gunakan kombinasi callback penyelesaian, Executor, dan jika API mendukung pembatalan CancellationSignal.

public void asyncLoadFoo(android.os.CancellationSignal cancellationSignal,
    Executor callbackExecutor,
    android.os.OutcomeReceiver<FooResult, Throwable> callback);

Jika Anda menargetkan Kotlin, sebaiknya gunakan fungsi suspend.

suspend fun asyncLoadFoo(): Foo

Di library integrasi khusus Java, Anda dapat menggunakan ListenableFuture Guava.

public com.google.common.util.concurrent.ListenableFuture<Foo> asyncLoadFoo();

Jangan gunakan Opsional

Meskipun Optional dapat memiliki keunggulan di beberapa permukaan API, Optional tidak konsisten dengan area permukaan API Android yang ada. @Nullable dan @NonNull memberikan bantuan alat untuk keamanan null dan Kotlin menerapkan kontrak nullability di tingkat compiler, sehingga Optional tidak diperlukan.

Untuk primitif opsional, gunakan metode has dan get yang dipasangkan. Jika nilai tidak ditetapkan (has menampilkan false), metode get akan memunculkan IllegalStateException.

public boolean hasAzimuth() { ... }
public int getAzimuth() {
  if (!hasAzimuth()) {
    throw new IllegalStateException("azimuth is not set");
  }
  return azimuth;
}

Menggunakan konstruktor pribadi untuk class yang tidak dapat diinstansiasi

Class yang hanya dapat dibuat oleh Builder, class yang hanya berisi konstanta atau metode statis, atau class yang tidak dapat di-instantiate harus mencakup setidaknya satu konstruktor pribadi untuk mencegah pembuatan instance menggunakan konstruktor no-arg default.

public final class Log {
  // Not instantiable.
  private Log() {}
}

Singleton

Singleton tidak disarankan karena memiliki kekurangan terkait pengujian berikut:

  1. Konstruksi dikelola oleh class, sehingga mencegah penggunaan tiruan
  2. Pengujian tidak dapat bersifat hermetik karena sifat statis singleton
  3. Untuk mengatasi masalah ini, developer harus mengetahui detail internal singleton atau membuat wrapper di sekitarnya

Lebih memilih pola instance tunggal, yang mengandalkan class dasar abstrak untuk mengatasi masalah ini.

Instance tunggal

Class instance tunggal menggunakan class dasar abstrak dengan konstruktor private atau internal dan menyediakan metode getInstance() statis untuk mendapatkan instance. Metode getInstance() harus menampilkan objek yang sama pada panggilan berikutnya.

Objek yang ditampilkan oleh getInstance() harus berupa implementasi pribadi dari class dasar abstrak.

class Singleton private constructor(...) {
  companion object {
    private val _instance: Singleton by lazy { Singleton(...) }

    fun getInstance(): Singleton {
      return _instance
    }
  }
}
abstract class SingleInstance private constructor(...) {
  companion object {
    private val _instance: SingleInstance by lazy { SingleInstanceImp(...) }
    fun getInstance(): SingleInstance {
      return _instance
    }
  }
}

Instance tunggal berbeda dari singleton karena developer dapat membuat versi palsu SingleInstance dan menggunakan framework Injeksi Dependensi sendiri untuk mengelola penerapan tanpa harus membuat wrapper, atau library dapat menyediakan versi palsunya sendiri dalam artefak -testing.

Class yang melepaskan resource harus menerapkan AutoCloseable

Class yang melepaskan resource melalui close, release, destroy, atau metode serupa harus menerapkan java.lang.AutoCloseable untuk memungkinkan developer membersihkan resource ini secara otomatis saat menggunakan blok try-with-resources.

Hindari pengenalan subclass View baru di android.*

Jangan memperkenalkan class baru yang mewarisi secara langsung atau tidak langsung dari android.view.View di API publik platform (yaitu, di android.*).

Toolkit UI Android kini mengutamakan Compose. Fitur UI baru yang diekspos oleh platform harus diekspos sebagai API tingkat rendah yang dapat digunakan untuk mengimplementasikan Jetpack Compose dan komponen UI berbasis View secara opsional untuk developer di library Jetpack. Menawarkan komponen ini di library memberikan peluang untuk implementasi yang di-backport saat fitur platform tidak tersedia.

Kolom

Aturan ini berkaitan dengan kolom publik pada class.

Jangan mengekspos kolom mentah

Class Java tidak boleh mengekspos kolom secara langsung. Kolom harus bersifat pribadi dan dapat diakses hanya menggunakan getter dan setter publik, terlepas dari apakah kolom ini bersifat final atau tidak.

Pengecualian langka mencakup struktur data dasar yang tidak perlu meningkatkan perilaku dalam menentukan atau mengambil kolom. Dalam kasus tersebut, kolom harus diberi nama menggunakan konvensi penamaan variabel standar, misalnya, Point.x dan Point.y.

Class Kotlin dapat mengekspos properti.

Kolom yang ditampilkan harus ditandai sebagai final

Kolom mentah sangat tidak disarankan (@see Don't expose raw fields). Namun, dalam situasi langka saat kolom diekspos sebagai kolom publik, tandai kolom tersebut dengan final.

Kolom internal tidak boleh diekspos

Jangan merujuk nama kolom internal di API publik.

public int mFlags;

Gunakan public, bukan protected

@see Gunakan publik, bukan terlindungi

Konstanta

Ini adalah aturan tentang konstanta publik.

Konstanta tanda tidak boleh tumpang-tindih dengan nilai int atau panjang

Flag menyiratkan bit yang dapat digabungkan ke dalam beberapa nilai gabungan. Jika tidak demikian, jangan panggil variabel atau konstanta flag.

public static final int FLAG_SOMETHING = 2;
public static final int FLAG_SOMETHING = 3;
public static final int FLAG_PRIVATE = 1 << 2;
public static final int FLAG_PRESENTATION = 1 << 3;

Lihat @IntDef untuk flag bitmask untuk mengetahui informasi selengkapnya tentang cara menentukan konstanta flag publik.

Konstanta final statis harus menggunakan konvensi penamaan yang dipisahkan dengan garis bawah dan semua huruf kapital

Semua kata dalam konstanta harus menggunakan huruf kapital dan beberapa kata harus dipisahkan dengan _. Contoh:

public static final int fooThing = 5
public static final int FOO_THING = 5

Menggunakan awalan standar untuk konstanta

Banyak konstanta yang digunakan di Android adalah untuk hal-hal standar, seperti tanda, kunci, dan tindakan. Konstanta ini harus memiliki awalan standar agar lebih mudah diidentifikasi sebagai hal-hal tersebut.

Misalnya, ekstra intent harus dimulai dengan EXTRA_. Tindakan intent harus dimulai dengan ACTION_. Konstanta yang digunakan dengan Context.bindService() harus dimulai dengan BIND_.

Nama dan cakupan konstanta utama

Nilai konstanta string harus konsisten dengan nama konstanta itu sendiri, dan umumnya dicakup ke paket atau domain. Contoh:

public static final String FOO_THING = "foo"

tidak diberi nama secara konsisten atau memiliki cakupan yang sesuai. Sebagai gantinya, pertimbangkan:

public static final String FOO_THING = "android.fooservice.FOO_THING"

Awalan android dalam konstanta string yang tercakup dicadangkan untuk Project Open Source Android.

Tindakan dan tambahan intent, serta entri Bundle, harus diberi namespace menggunakan nama paket yang ditentukan di dalamnya.

package android.foo.bar {
  public static final String ACTION_BAZ = "android.foo.bar.action.BAZ"
  public static final String EXTRA_BAZ = "android.foo.bar.extra.BAZ"
}

Gunakan public, bukan protected

@see Gunakan publik, bukan terlindungi

Menggunakan awalan yang konsisten

Semua konstanta terkait harus dimulai dengan awalan yang sama. Misalnya, untuk sekumpulan konstanta yang akan digunakan dengan nilai flag:

public static final int SOME_VALUE = 0x01;

public static final int SOME_OTHER_VALUE = 0x10;

public static final int SOME_THIRD_VALUE = 0x100;
public static final int FLAG_SOME_VALUE = 0x01;

public static final int FLAG_SOME_OTHER_VALUE = 0x10;

public static final int FLAG_SOME_THIRD_VALUE = 0x100;

@see Menggunakan awalan standar untuk konstanta

Menggunakan nama resource yang konsisten

ID, atribut, dan nilai publik harus diberi nama menggunakan konvensi penamaan camelCase, misalnya @id/accessibilityActionPageUp atau @attr/textAppearance, mirip dengan kolom publik di Java.

Dalam beberapa kasus, ID atau atribut publik menyertakan awalan umum yang dipisahkan oleh garis bawah:

  • Nilai konfigurasi platform seperti @string/config_recentsComponentName di config.xml
  • Atribut tampilan khusus tata letak seperti @attr/layout_marginStart di attrs.xml

Tema dan gaya publik harus mengikuti konvensi penamaan PascalCase hierarkis, misalnya @style/Theme.Material.Light.DarkActionBar atau @style/Widget.Material.SearchView.ActionBar, mirip dengan class bertingkat di Java.

Tata letak dan resource drawable sebaiknya tidak diekspos sebagai API publik. Namun, jika harus diekspos, tata letak dan drawable publik harus diberi nama menggunakan konvensi penamaan under_score, misalnya layout/simple_list_item_1.xml atau drawable/title_bar_tall.xml.

Jika konstanta dapat berubah, buat konstanta tersebut menjadi dinamis

Compiler dapat menyisipkan nilai konstanta, sehingga menjaga nilai tetap sama dianggap sebagai bagian dari kontrak API. Jika nilai konstanta MIN_FOO atau MAX_FOO dapat berubah di masa mendatang, sebaiknya jadikan konstanta tersebut sebagai metode dinamis.

CameraManager.MAX_CAMERAS
CameraManager.getMaxCameras()

Mempertimbangkan kompatibilitas dengan versi selanjutnya untuk callback

Konstanta yang ditentukan dalam versi API mendatang tidak diketahui oleh aplikasi yang menargetkan API lama. Oleh karena itu, konstanta yang dikirim ke aplikasi harus mempertimbangkan versi API target aplikasi tersebut dan memetakan konstanta yang lebih baru ke nilai yang konsisten. Pertimbangkan skenario berikut:

Sumber SDK hipotetis:

// Added in API level 22
public static final int STATUS_SUCCESS = 1;
public static final int STATUS_FAILURE = 2;
// Added in API level 23
public static final int STATUS_FAILURE_RETRY = 3;
// Added in API level 26
public static final int STATUS_FAILURE_ABORT = 4;

Aplikasi hipotetis dengan targetSdkVersion="22":

if (result == STATUS_FAILURE) {
  // Oh no!
} else {
  // Success!
}

Dalam hal ini, aplikasi dirancang dalam batasan API level 22 dan membuat asumsi (agak) wajar bahwa hanya ada dua kemungkinan status. Namun, jika aplikasi menerima STATUS_FAILURE_RETRY yang baru ditambahkan, aplikasi akan menafsirkannya sebagai keberhasilan.

Metode yang menampilkan konstanta dapat menangani kasus seperti ini dengan aman dengan membatasi outputnya agar sesuai dengan level API yang ditargetkan oleh aplikasi:

private int mapResultForTargetSdk(Context context, int result) {
  int targetSdkVersion = context.getApplicationInfo().targetSdkVersion;
  if (targetSdkVersion < 26) {
    if (result == STATUS_FAILURE_ABORT) {
      return STATUS_FAILURE;
    }
    if (targetSdkVersion < 23) {
      if (result == STATUS_FAILURE_RETRY) {
        return STATUS_FAILURE;
      }
    }
  }
  return result;
}

Developer tidak dapat memprediksi apakah daftar konstanta dapat berubah di masa mendatang. Jika Anda menentukan API dengan konstanta UNKNOWN atau UNSPECIFIED yang terlihat seperti mencakup semua, developer menganggap bahwa konstanta yang dipublikasikan saat mereka menulis aplikasi mereka sudah lengkap. Jika Anda tidak bersedia menetapkan ekspektasi ini, pertimbangkan kembali apakah konstanta catch-all merupakan ide yang baik untuk API Anda.

Selain itu, library tidak dapat menentukan targetSdkVersion-nya sendiri secara terpisah dari aplikasi dan penanganan perubahan perilaku targetSdkVersion dari kode library menjadi rumit dan rentan terhadap error.

Konstanta bilangan bulat atau string

Gunakan konstanta bilangan bulat dan @IntDef jika namespace untuk nilai tidak dapat diperluas di luar paket Anda. Gunakan konstanta string jika namespace dibagikan atau dapat diperluas oleh kode di luar paket Anda.

Class data

Class data merepresentasikan sekumpulan properti yang tidak dapat diubah dan menyediakan sekumpulan kecil fungsi utilitas yang terdefinisi dengan baik untuk berinteraksi dengan data tersebut.

Jangan gunakan data class di API Kotlin publik, karena compiler Kotlin tidak menjamin kompatibilitas API bahasa atau biner untuk kode yang dihasilkan. Sebagai gantinya, terapkan fungsi yang diperlukan secara manual.

Pembuatan instance

Di Java, class data harus menyediakan konstruktor jika ada beberapa properti atau menggunakan pola Builder jika ada banyak properti.

Di Kotlin, class data harus menyediakan konstruktor dengan argumen default terlepas dari jumlah properti. Class data yang ditentukan di Kotlin juga dapat memanfaatkan penyediaan builder saat menargetkan klien Java.

Memodifikasi dan menyalin

Jika data perlu diubah, berikan class Builder dengan konstruktor salinan (Java) atau fungsi anggota copy() (Kotlin) yang menampilkan objek baru.

Saat memberikan fungsi copy() di Kotlin, argumen harus cocok dengan konstruktor class dan default harus diisi menggunakan nilai objek saat ini:

class Typography(
  val labelMedium: TextStyle = TypographyTokens.LabelMedium,
  val labelSmall: TextStyle = TypographyTokens.LabelSmall
) {
    fun copy(
      labelMedium: TextStyle = this.labelMedium,
      labelSmall: TextStyle = this.labelSmall
    ): Typography = Typography(
      labelMedium = labelMedium,
      labelSmall = labelSmall
    )
}

Perilaku tambahan

Class data harus mengimplementasikan equals() dan hashCode(), dan setiap properti harus diperhitungkan dalam implementasi metode ini.

Class data dapat menerapkan toString() dengan format yang direkomendasikan yang cocok dengan penerapan class data Kotlin, misalnya User(var1=Alex, var2=42).

Metode

Ini adalah aturan tentang berbagai hal spesifik dalam metode, seputar parameter, nama metode, jenis nilai yang ditampilkan, dan penentu akses.

Waktu

Aturan ini mencakup cara konsep waktu seperti tanggal dan durasi harus dinyatakan dalam API.

Lebih memilih jenis java.time.* jika memungkinkan

java.time.Duration, java.time.Instant, dan banyak jenis java.time.* lainnya tersedia di semua versi platform melalui desugaring dan sebaiknya digunakan saat menyatakan waktu dalam parameter atau nilai yang ditampilkan API.

Lebih baik hanya mengekspos varian API yang menerima atau menampilkan java.time.Duration atau java.time.Instant dan menghilangkan varian primitif dengan kasus penggunaan yang sama, kecuali jika domain API adalah tempat alokasi objek dalam pola penggunaan yang dimaksudkan akan memiliki dampak performa yang sangat besar.

Metode yang menyatakan durasi harus diberi nama durasi

Jika nilai waktu menyatakan durasi waktu yang terlibat, beri nama parameter "duration", bukan "time".

ValueAnimator.setTime(java.time.Duration);
ValueAnimator.setDuration(java.time.Duration);

Pengecualian:

"timeout" cocok jika durasi secara khusus berlaku untuk nilai waktu tunggu.

"time" dengan jenis java.time.Instant cocok saat merujuk ke titik waktu tertentu, bukan durasi.

Metode yang menyatakan durasi atau waktu sebagai primitif harus diberi nama dengan satuan waktu, dan menggunakan long

Metode yang menerima atau menampilkan durasi sebagai primitif harus menyertakan akhiran nama metode dengan unit waktu terkait (seperti Millis, Nanos, Seconds) untuk mencadangkan nama yang tidak dihiasi untuk digunakan dengan java.time.Duration. Lihat Waktu.

Metode juga harus dianotasi dengan tepat menggunakan unit dan basis waktunya:

  • @CurrentTimeMillisLong: Nilai adalah stempel waktu non-negatif yang diukur sebagai jumlah milidetik sejak 1970-01-01T00:00:00Z.
  • @CurrentTimeSecondsLong: Nilai adalah stempel waktu non-negatif yang diukur sebagai jumlah detik sejak 1970-01-01T00.00.00Z.
  • @DurationMillisLong: Nilai adalah durasi non-negatif dalam milidetik.
  • @ElapsedRealtimeLong: Nilai adalah stempel waktu non-negatif dalam basis waktu SystemClock.elapsedRealtime().
  • @UptimeMillisLong: Nilai adalah stempel waktu non-negatif dalam basis waktuSystemClock.uptimeMillis().

Parameter waktu atau nilai yang ditampilkan primitif harus menggunakan long, bukan int.

ValueAnimator.setDuration(@DurationMillisLong long);
ValueAnimator.setDurationNanos(long);

Metode yang menyatakan satuan waktu harus lebih memilih singkatan yang tidak disingkat untuk nama satuan

public void setIntervalNs(long intervalNs);

public void setTimeoutUs(long timeoutUs);
public void setIntervalNanos(long intervalNanos);

public void setTimeoutMicros(long timeoutMicros);

Membuat anotasi argumen waktu yang panjang

Platform ini mencakup beberapa anotasi untuk memberikan pengetikan yang lebih kuat untuk unit waktu jenis long:

  • @CurrentTimeMillisLong: Nilai adalah stempel waktu non-negatif yang diukur sebagai jumlah milidetik 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 ukur

Untuk semua metode yang menyatakan satuan pengukuran selain waktu, sebaiknya gunakan CamelCased awalan satuan SI.

public  long[] getFrequenciesKhz();

public  float getStreamVolumeDb();

Menempatkan parameter opsional di akhir overload

Jika Anda memiliki kelebihan beban metode dengan parameter opsional, pertahankan parameter tersebut di akhir dan pertahankan pengurutan yang konsisten dengan parameter lainnya:

public int doFoo(boolean flag);

public int doFoo(int id, boolean flag);
public int doFoo(boolean flag);

public int doFoo(boolean flag, int id);

Saat menambahkan kelebihan beban untuk argumen opsional, perilaku metode yang lebih sederhana harus sama persis dengan jika argumen default telah diberikan ke metode yang lebih rumit.

Akibat: Jangan membebani metode selain untuk menambahkan argumen opsional atau untuk menerima berbagai jenis argumen jika metode bersifat polimorfik. Jika metode yang kelebihan beban melakukan sesuatu yang pada dasarnya berbeda, maka beri nama baru.

Metode dengan parameter default harus dianotasi dengan @JvmOverloads (khusus Kotlin)

Metode dan konstruktor dengan parameter default harus dianotasi dengan @JvmOverloads untuk mempertahankan kompatibilitas biner.

Lihat Overload fungsi untuk default dalam panduan interoperabilitas Kotlin-Java resmi untuk mengetahui detail selengkapnya.

class Greeting @JvmOverloads constructor(
  loudness: Int = 5
) {
  @JvmOverloads
  fun sayHello(prefix: String = "Dr.", name: String) = // ...
}

Jangan hapus nilai parameter default (khusus Kotlin)

Jika metode telah dikirimkan dengan parameter yang memiliki nilai default, penghapusan nilai default akan menyebabkan perubahan yang merusak sumber.

Parameter metode yang paling khas dan mengidentifikasi harus berada di urutan pertama

Jika Anda memiliki metode dengan beberapa parameter, masukkan parameter yang paling relevan terlebih dahulu. Parameter yang menentukan flag dan opsi lainnya kurang penting daripada parameter yang menjelaskan objek yang sedang ditindaklanjuti. Jika ada callback penyelesaian, letakkan di akhir.

public void openFile(int flags, String name);

public void openFileAsync(OnFileOpenedListener listener, String name, int flags);

public void setFlags(int mask, int flags);
public void openFile(String name, int flags);

public void openFileAsync(String name, int flags, OnFileOpenedListener listener);

public void setFlags(int flags, int mask);

Lihat juga: Menempatkan parameter opsional di akhir dalam kelebihan beban

Builder

Pola Builder direkomendasikan untuk membuat objek Java yang kompleks, dan biasanya digunakan di Android untuk kasus ketika:

  • Properti objek yang dihasilkan tidak boleh diubah
  • Ada banyak properti wajib, misalnya banyak argumen konstruktor
  • Ada hubungan yang kompleks antara properti pada waktu pembuatan, misalnya, langkah verifikasi diperlukan. Perhatikan bahwa tingkat kompleksitas ini sering kali menunjukkan masalah kegunaan API.

Pertimbangkan apakah Anda memerlukan alat pembuat. Builder berguna dalam antarmuka API jika digunakan untuk:

  • Mengonfigurasi hanya beberapa parameter pembuatan opsional dari sekumpulan parameter yang berpotensi besar
  • Mengonfigurasi banyak parameter pembuatan opsional atau wajib diisi yang berbeda, terkadang dengan jenis yang serupa atau cocok, yang dapat membuat situs panggilan menjadi membingungkan untuk dibaca atau rentan terhadap kesalahan saat ditulis
  • Mengonfigurasi pembuatan objek secara inkremental, dengan beberapa potongan kode konfigurasi yang berbeda dapat melakukan panggilan pada builder sebagai detail implementasi
  • Mengizinkan jenis berkembang dengan menambahkan parameter pembuatan opsional tambahan di versi API mendatang

Jika memiliki jenis dengan tiga atau kurang parameter wajib dan tidak ada parameter opsional, Anda hampir selalu dapat melewati builder dan menggunakan konstruktor biasa.

Class yang bersumber dari Kotlin harus lebih memilih konstruktor beranotasi @JvmOverloads dengan argumen default daripada Builder, tetapi dapat memilih untuk meningkatkan kegunaan bagi klien Java dengan juga menyediakan Builder dalam kasus yang diuraikan sebelumnya.

class Tone @JvmOverloads constructor(
  val duration: Long = 1000,
  val frequency: Int = 2600,
  val dtmfConfigs: List<DtmfConfig> = emptyList()
) {
  class Builder {
    // ...
  }
}

Class builder harus menampilkan builder

Class builder harus mengaktifkan rangkaian metode dengan menampilkan objek Builder (seperti this) dari setiap metode kecuali build(). Objek yang dibuat tambahan harus diteruskan sebagai argumen -- jangan menampilkan builder objek lain. Contoh:

public static class Builder {
  public void setDuration(long);
  public void setFrequency(int);
  public DtmfConfigBuilder addDtmfConfig();
  public Tone build();
}
public class Tone {
  public static class Builder {
    public Builder setDuration(long);
    public Builder setFrequency(int);
    public Builder addDtmfConfig(DtmfConfig);
    public Tone build();
  }
}

Dalam kasus yang jarang terjadi saat class builder dasar harus mendukung ekstensi, gunakan jenis nilai yang ditampilkan generik:

public abstract class Builder<T extends Builder<T>> {
  abstract T setValue(int);
}

public class TypeBuilder<T extends TypeBuilder<T>> extends Builder<T> {
  T setValue(int);
  T setTypeSpecificValue(long);
}

Class builder harus dibuat melalui konstruktor

Untuk mempertahankan pembuatan builder yang konsisten melalui platform API Android, semua builder harus dibuat melalui konstruktor, bukan metode pembuat statis. Untuk API berbasis Kotlin, Builder harus bersifat publik meskipun pengguna Kotlin diharapkan mengandalkan builder secara implisit melalui mekanisme pembuatan gaya DSL/metode factory. Library tidak boleh menggunakan @PublishedApi internal untuk menyembunyikan konstruktor class Builder secara selektif dari klien Kotlin.

public class Tone {
  public static Builder builder();
  public static class Builder {
  }
}
public class Tone {
  public static class Builder {
    public Builder();
  }
}

Semua argumen untuk konstruktor builder harus wajib (seperti @NonNull)

Argumen opsional, misalnya @Nullable, harus dipindahkan ke metode setter. Konstruktor builder harus menampilkan NullPointerException (pertimbangkan untuk menggunakan Objects.requireNonNull) jika ada argumen wajib yang tidak ditentukan.

Class builder harus berupa class dalam statis akhir dari jenis yang dibangunnya

Demi organisasi logis dalam paket, class builder biasanya diekspos sebagai class dalam akhir dari jenis yang dibangun, misalnya Tone.Builder, bukan ToneBuilder.

Builder dapat menyertakan konstruktor untuk membuat instance baru dari instance yang ada

Builder dapat menyertakan konstruktor salinan untuk membuat instance builder baru dari builder yang ada atau objek yang dibuat. Mereka tidak boleh menyediakan metode alternatif untuk membuat instance builder dari builder atau objek build yang ada.

public class Tone {
  public static class Builder {
    public Builder clone();
  }

  public Builder toBuilder();
}
public class Tone {
  public static class Builder {
    public Builder(Builder original);
    public Builder(Tone original);
  }
}
Penyetel builder harus menggunakan argumen @Nullable jika builder memiliki konstruktor salinan

Penyetelan ulang sangat penting jika instance baru builder dapat dibuat dari instance yang ada. Jika tidak ada konstruktor salinan, maka builder dapat memiliki argumen @Nullable atau @NonNullable.

public static class Builder {
  public Builder(Builder original);
  public Builder setObjectValue(@Nullable Object value);
}
Setter builder dapat menggunakan argumen @Nullable untuk properti opsional

Sering kali lebih mudah menggunakan nilai yang dapat bernilai null untuk input derajat kedua, terutama di Kotlin, yang menggunakan argumen default, bukan builder dan kelebihan beban.

Selain itu, setter @Nullable akan mencocokkannya dengan getter, yang harus berupa @Nullable untuk properti opsional.

Value createValue(@Nullable OptionalValue optionalValue) {
  Value.Builder builder = new Value.Builder();
  if (optionalValue != null) {
    builder.setOptionalValue(optionalValue);
  }
  return builder.build();
}
Value createValue(@Nullable OptionalValue optionalValue) {
  return new Value.Builder()
    .setOptionalValue(optionalValue);
    .build();
}

// Or in other cases:

Value createValue() {
  return new Value.Builder()
    .setOptionalValue(condition ? new OptionalValue() : null);
    .build();
}

Penggunaan umum di Kotlin:

fun createValue(optionalValue: OptionalValue? = null) =
  Value.Builder()
    .apply { optionalValue?.let { setOptionalValue(it) } }
    .build()
fun createValue(optionalValue: OptionalValue? = null) =
  Value.Builder()
    .setOptionalValue(optionalValue)
    .build()

Nilai default (jika setter tidak dipanggil), dan arti null, harus didokumentasikan dengan benar di setter dan getter.

/**
 * ...
 *
 * <p>Defaults to {@code null}, which means the optional value won't be used.
 */

Penyetel builder dapat disediakan untuk properti yang dapat diubah yang penyetelnya tersedia di class yang dibuat

Jika class Anda memiliki properti yang dapat diubah dan memerlukan class Builder, pertama-tama tanyakan pada diri Anda apakah class Anda sebenarnya harus memiliki properti yang dapat diubah.

Selanjutnya, jika Anda yakin bahwa Anda memerlukan properti yang dapat diubah, putuskan skenario berikut mana yang lebih cocok untuk kasus penggunaan yang Anda harapkan:

  1. Objek yang dibuat harus dapat langsung digunakan, sehingga setter harus disediakan untuk semua properti yang relevan, baik yang dapat diubah maupun tidak.

    map.put(key, new Value.Builder(requiredValue)
        .setImmutableProperty(immutableValue)
        .setUsefulMutableProperty(usefulValue)
        .build());
    
  2. Beberapa panggilan tambahan mungkin perlu dilakukan sebelum objek yang dibuat dapat berguna, sehingga setter tidak boleh disediakan untuk properti yang dapat diubah.

    Value v = new Value.Builder(requiredValue)
        .setImmutableProperty(immutableValue)
        .build();
    v.setUsefulMutableProperty(usefulValue)
    Result r = v.performSomeAction();
    Key k = callSomeMethod(r);
    map.put(k, v);
    

Jangan mencampur kedua skenario.

Value v = new Value.Builder(requiredValue)
    .setImmutableProperty(immutableValue)
    .setUsefulMutableProperty(usefulValue)
    .build();
Result r = v.performSomeAction();
Key k = callSomeMethod(r);
map.put(k, v);

Builder tidak boleh memiliki pengambil

Getter harus ada di objek yang dibuat, bukan di builder.

Penyetel builder harus memiliki pengambil yang sesuai pada class yang dibuat

public class Tone {
  public static class Builder {
    public Builder setDuration(long);
    public Builder setFrequency(int);
    public Builder addDtmfConfig(DtmfConfig);
    public Tone build();
  }
}
public class Tone {
  public static class Builder {
    public Builder setDuration(long);
    public Builder setFrequency(int);
    public Builder addDtmfConfig(DtmfConfig);
    public Tone build();
  }

  public long getDuration();
  public int getFrequency();
  public @NonNull List<DtmfConfig> getDtmfConfigs();
}

Penamaan metode builder

Nama metode builder harus menggunakan gaya setFoo(), addFoo(), atau clearFoo().

Class builder diharapkan mendeklarasikan metode build()

Class builder harus mendeklarasikan metode build() yang menampilkan instance objek yang dibuat.

Metode build() builder harus menampilkan objek @NonNull

Metode build() builder diharapkan menampilkan instance objek yang dibuat yang tidak null. Jika objek tidak dapat dibuat karena parameter tidak valid, validasi dapat ditunda ke metode build dan IllegalStateException harus ditampilkan.

Jangan mengekspos kunci internal

Metode di API publik tidak boleh menggunakan kata kunci synchronized. Kata kunci ini menyebabkan objek atau class Anda digunakan sebagai kunci, dan karena diekspos ke orang lain, Anda mungkin mengalami efek samping yang tidak terduga jika kode lain di luar class Anda mulai menggunakannya untuk tujuan penguncian.

Sebagai gantinya, lakukan penguncian yang diperlukan terhadap objek internal dan pribadi.

public synchronized void doThing() { ... }
private final Object mThingLock = new Object();

public void doThing() {
  synchronized (mThingLock) {
    ...
  }
}

Metode bergaya aksesor harus mengikuti panduan properti Kotlin

Saat dilihat dari sumber Kotlin, metode bergaya pengakses -- yang menggunakan awalan get, set, atau is -- juga akan tersedia sebagai properti Kotlin. Misalnya, int getField() yang ditentukan di Java tersedia di Kotlin sebagai properti val field: Int.

Oleh karena itu, dan untuk memenuhi ekspektasi developer secara umum terkait perilaku metode aksesor, metode yang menggunakan awalan metode aksesor harus berperilaku serupa dengan kolom Java. Hindari penggunaan awalan gaya aksesor saat:

  • Metode memiliki efek samping -- lebih memilih nama metode yang lebih deskriptif
  • Metode ini melibatkan pekerjaan yang memerlukan biaya komputasi yang tinggi -- sebaiknya gunakan compute
  • Metode ini melibatkan pemblokiran atau pekerjaan yang berjalan lama untuk menampilkan nilai, seperti IPC atau I/O lainnya -- sebaiknya gunakan fetch
  • Metode memblokir thread hingga dapat menampilkan nilai -- lebih memilih await
  • Metode ini menampilkan instance objek baru pada setiap panggilan -- lebih baik menggunakan create
  • Metode mungkin tidak berhasil menampilkan nilai -- sebaiknya gunakan request

Perhatikan bahwa melakukan tugas yang mahal secara komputasi satu kali dan menyimpan nilai dalam cache untuk panggilan berikutnya masih dihitung sebagai melakukan tugas yang mahal secara komputasi. Jank tidak diamortisasi di seluruh frame.

Menggunakan awalan is untuk metode pengakses boolean

Ini adalah konvensi penamaan standar untuk metode dan kolom boolean di Java. Umumnya, nama metode dan variabel boolean harus ditulis sebagai pertanyaan yang dijawab oleh nilai yang ditampilkan.

Metode pengakses boolean Java harus mengikuti skema penamaan set/is dan kolom harus lebih memilih is, seperti:

// Visibility is a direct property. The object "is" visible:
void setVisible(boolean visible);
boolean isVisible();

// Factory reset protection is an indirect property.
void setFactoryResetProtectionEnabled(boolean enabled);
boolean isFactoryResetProtectionEnabled();

final boolean isAvailable;

Menggunakan set/is untuk metode pengakses Java atau is untuk kolom Java akan memungkinkan metode dan kolom tersebut digunakan sebagai properti dari Kotlin:

obj.isVisible = true
obj.isFactoryResetProtectionEnabled = false
if (!obj.isAvailable) return

Properti dan metode pengakses umumnya harus menggunakan penamaan positif, misalnya Enabled, bukan Disabled. Penggunaan terminologi negatif membalikkan makna true dan false serta membuat perilaku lebih sulit dipahami.

// Passing false here is a double-negative.
void setFactoryResetProtectionDisabled(boolean disabled);

Dalam kasus ketika boolean menjelaskan penyertaan atau kepemilikan properti, Anda dapat menggunakan has, bukan is; namun, hal ini tidak akan berfungsi dengan sintaksis properti Kotlin:

// Transient state is an indirect property used to track state
// related to the object. The object is not transient; rather,
// the object "has" transient state associated with it:
void setHasTransientState(boolean hasTransientState);
boolean hasTransientState();

Beberapa awalan alternatif yang mungkin lebih sesuai mencakup dapat dan sebaiknya:

// "Can" describes a behavior that the object may provide,
// and here is more concise than setRecordingEnabled or
// setRecordingAllowed. The object "can" record:
void setCanRecord(boolean canRecord);
boolean canRecord();

// "Should" describes a hint or property that is not strictly
// enforced, and here is more explicit than setFitWidthEnabled.
// The object "should" fit width:
void setShouldFitWidth(boolean shouldFitWidth);
boolean shouldFitWidth();

Metode yang mengalihkan perilaku atau fitur dapat menggunakan awalan is dan sufiks Enabled:

// "Enabled" describes the availability of a property, and is
// more appropriate here than "can use" or "should use" the
// property:
void setWiFiRoamingSettingEnabled(boolean enabled)
boolean isWiFiRoamingSettingEnabled()

Demikian pula, metode yang menunjukkan dependensi pada perilaku atau fitur lain dapat menggunakan awalan is dan akhiran Supported atau Required:

// "Supported" describes whether this API would work on devices that support
// multiple users. The API "supports" multi-user:
void setMultiUserSupported(boolean supported)
boolean isMultiUserSupported()
// "Required" describes whether this API depends on devices that support
// multiple users. The API "requires" multi-user:
void setMultiUserRequired(boolean required)
boolean isMultiUserRequired()

Umumnya, nama metode harus ditulis sebagai pertanyaan yang dijawab oleh nilai yang ditampilkan.

Metode properti Kotlin

Untuk properti class var foo: Foo, Kotlin akan menghasilkan metode get/set menggunakan aturan yang konsisten: tambahkan get dan ubah huruf pertama menjadi huruf besar untuk pengambil, serta tambahkan set dan ubah huruf pertama menjadi huruf besar untuk penyetel. Deklarasi properti akan menghasilkan metode bernama public Foo getFoo() dan public void setFoo(Foo foo).

Jika properti berjenis Boolean, aturan tambahan berlaku dalam pembuatan nama: jika nama properti diawali dengan is, maka get tidak ditambahkan untuk nama metode pengambil, nama properti itu sendiri digunakan sebagai pengambil. Oleh karena itu, sebaiknya beri nama properti Boolean dengan awalan is agar mengikuti panduan penamaan:

var isVisible: Boolean

Jika properti Anda adalah salah satu pengecualian yang disebutkan di atas dan diawali dengan awalan yang sesuai, gunakan anotasi @get:JvmName pada properti untuk menentukan nama yang sesuai secara manual:

@get:JvmName("hasTransientState")
var hasTransientState: Boolean

@get:JvmName("canRecord")
var canRecord: Boolean

@get:JvmName("shouldFitWidth")
var shouldFitWidth: Boolean

Pengakses bitmask

Lihat Gunakan @IntDef untuk flag bitmask untuk pedoman API terkait cara menentukan flag bitmask.

Penyetel

Dua metode setter harus disediakan: satu yang mengambil bitstring lengkap dan menggantikan semua flag yang ada, dan yang lainnya mengambil bitmask kustom untuk memberikan fleksibilitas yang lebih besar.

/**
 * Sets the state of all scroll indicators.
 * <p>
 * See {@link #setScrollIndicators(int, int)} for usage information.
 *
 * @param indicators a bitmask of indicators that should be enabled, or
 *                   {@code 0} to disable all indicators
 * @see #setScrollIndicators(int, int)
 * @see #getScrollIndicators()
 */
public void setScrollIndicators(@ScrollIndicators int indicators);

/**
 * Sets the state of the scroll indicators specified by the mask. To change
 * all scroll indicators at once, see {@link #setScrollIndicators(int)}.
 * <p>
 * When a scroll indicator is enabled, it will be displayed if the view
 * can scroll in the direction of the indicator.
 * <p>
 * Multiple indicator types may be enabled or disabled by passing the
 * logical OR of the specified types. If multiple types are specified, they
 * will all be set to the same enabled state.
 * <p>
 * For example, to enable the top scroll indicator:
 * {@code setScrollIndicators(SCROLL_INDICATOR_TOP, SCROLL_INDICATOR_TOP)}
 * <p>
 * To disable the top scroll indicator:
 * {@code setScrollIndicators(0, SCROLL_INDICATOR_TOP)}
 *
 * @param indicators a bitmask of values to set; may be a single flag,
 *                   the logical OR of multiple flags, or 0 to clear
 * @param mask a bitmask indicating which indicator flags to modify
 * @see #setScrollIndicators(int)
 * @see #getScrollIndicators()
 */
public void setScrollIndicators(@ScrollIndicators int indicators, @ScrollIndicators int mask);

Pengambil

Satu pengambil harus disediakan untuk mendapatkan bitmask lengkap.

/**
 * Returns a bitmask representing the enabled scroll indicators.
 * <p>
 * For example, if the top and left scroll indicators are enabled and all
 * other indicators are disabled, the return value will be
 * {@code View.SCROLL_INDICATOR_TOP | View.SCROLL_INDICATOR_LEFT}.
 * <p>
 * To check whether the bottom scroll indicator is enabled, use the value
 * of {@code (getScrollIndicators() & View.SCROLL_INDICATOR_BOTTOM) != 0}.
 *
 * @return a bitmask representing the enabled scroll indicators
 */
@ScrollIndicators
public int getScrollIndicators();

Gunakan public, bukan protected

Selalu pilih public daripada protected di API publik. Akses terlindungi pada akhirnya akan menyulitkan dalam jangka panjang, karena pelaksana harus mengganti untuk menyediakan pengakses publik jika akses eksternal secara default akan sama baiknya.

Ingatlah bahwa visibilitas protected tidak mencegah developer memanggil API -- visibilitas ini hanya membuatnya sedikit lebih mengganggu.

Menerapkan equals() dan hashCode() atau tidak menerapkan keduanya

Jika Anda mengganti salah satunya, Anda harus mengganti yang lainnya.

Menerapkan toString() untuk class data

Class data dianjurkan untuk mengganti toString(), untuk membantu developer melakukan debug kode mereka.

Mendokumentasikan apakah output ditujukan untuk perilaku program atau proses debug

Tentukan apakah Anda ingin perilaku program bergantung pada penerapan Anda atau tidak. Misalnya, UUID.toString() dan File.toString() mendokumentasikan format spesifiknya untuk digunakan oleh program. Jika Anda hanya mengekspos informasi untuk proses debug, seperti Intent, maka sertakan dokumen yang diwarisi dari superclass.

Jangan sertakan informasi tambahan

Semua informasi yang tersedia dari toString() juga harus tersedia melalui API publik objek. Jika tidak, Anda mendorong developer untuk mengurai dan mengandalkan output toString(), yang akan mencegah perubahan di masa mendatang. Praktik yang baik adalah menerapkan toString() hanya menggunakan API publik objek.

Mencegah ketergantungan pada output debug

Meskipun tidak mungkin mencegah developer bergantung pada output debug, termasuk System.identityHashCode objek Anda dalam output toString()-nya, akan membuat dua objek yang berbeda sangat tidak mungkin memiliki output toString() yang sama.

@Override
public String toString() {
  return getClass().getSimpleName() + "@" + Integer.toHexString(System.identityHashCode(this)) + " {mFoo=" + mFoo + "}";
}

Hal ini dapat secara efektif mencegah developer menulis pernyataan pengujian seperti assertThat(a.toString()).isEqualTo(b.toString()) pada objek Anda.

Gunakan createFoo saat menampilkan objek yang baru dibuat

Gunakan awalan create, bukan get atau new, untuk metode yang akan membuat nilai yang ditampilkan, misalnya dengan membuat objek baru.

Jika metode akan membuat objek untuk ditampilkan, jelaskan hal tersebut dalam nama metode.

public FooThing getFooThing() {
  return new FooThing();
}
public FooThing createFooThing() {
  return new FooThing();
}

Metode yang menerima objek File juga harus menerima stream

Lokasi penyimpanan data di Android tidak selalu berupa file di disk. Misalnya, konten yang diteruskan di seluruh batas pengguna ditampilkan sebagai content:// Uri. Untuk mengaktifkan pemrosesan berbagai sumber data, API yang menerima objek File juga harus menerima InputStream, OutputStream, atau keduanya.

public void setDataSource(File file)
public void setDataSource(InputStream stream)

Mengambil dan menampilkan primitif mentah, bukan versi box

Jika Anda perlu mengomunikasikan nilai yang tidak ada atau null, pertimbangkan untuk menggunakan -1, Integer.MAX_VALUE, atau Integer.MIN_VALUE.

public java.lang.Integer getLength()
public void setLength(java.lang.Integer)
public int getLength()
public void setLength(int value)

Menghindari class yang setara dengan jenis primitif akan menghindari overhead memori dari class ini, akses metode ke nilai, dan yang lebih penting, autoboxing yang berasal dari casting antara jenis primitif dan objek. Menghindari perilaku ini akan menghemat memori dan alokasi sementara yang dapat menyebabkan pembersihan sampah memori yang mahal dan lebih sering.

Menggunakan anotasi untuk memperjelas parameter dan nilai yang ditampilkan yang valid

Anotasi developer ditambahkan untuk membantu mengklarifikasi nilai yang diizinkan dalam berbagai situasi. Hal ini mempermudah alat membantu developer saat mereka memberikan nilai yang salah (misalnya, meneruskan int arbitrer saat framework memerlukan salah satu dari serangkaian nilai konstanta tertentu). Gunakan semua anotasi berikut jika sesuai:

Nullability

Anotasi nullability eksplisit diperlukan untuk API Java, tetapi konsep nullability adalah bagian dari bahasa Kotlin dan anotasi nullability tidak boleh digunakan dalam API Kotlin.

@Nullable: Menunjukkan bahwa nilai yang ditampilkan, parameter, atau kolom tertentu dapat bernilai null:

@Nullable
public String getName()

public void setName(@Nullable String name)

@NonNull: Menunjukkan bahwa nilai yang ditampilkan, parameter, atau kolom tertentu tidak boleh bernilai null. Menandai sesuatu sebagai @Nullable relatif baru di Android, sehingga sebagian besar metode API Android tidak didokumentasikan secara konsisten. Oleh karena itu, kita memiliki tiga status "tidak diketahui, @Nullable, @NonNull". Itulah sebabnya @NonNull menjadi bagian dari panduan API:

@NonNull
public String getName()

public void setName(@NonNull String name)

Untuk dokumentasi platform Android, anotasi parameter metode Anda akan otomatis membuat dokumentasi dalam bentuk "Nilai ini mungkin null", kecuali jika "null" secara eksplisit digunakan di tempat lain dalam dokumentasi parameter.

Metode "tidak benar-benar nullable" yang ada: Metode yang ada di API tanpa anotasi @Nullable yang dideklarasikan dapat diberi anotasi @Nullable jika metode dapat menampilkan null dalam keadaan tertentu yang jelas (seperti findViewById()). Metode @NotNull requireFoo() pendamping yang menampilkan IllegalArgumentException harus ditambahkan untuk developer yang tidak ingin melakukan pemeriksaan null.

Metode antarmuka: API baru harus menambahkan anotasi yang tepat saat menerapkan metode antarmuka, seperti Parcelable.writeToParcel() (yaitu, metode tersebut dalam class penerapan harus berupa writeToParcel(@NonNull Parcel, int), bukan writeToParcel(Parcel, int)); API yang ada yang tidak memiliki anotasi tidak perlu "diperbaiki".

Penerapan nullability

Di Java, metode direkomendasikan untuk melakukan validasi input untuk parameter @NonNull menggunakan Objects.requireNonNull() dan menampilkan NullPointerException saat parameter null. Hal ini dilakukan secara otomatis di Kotlin.

Referensi

ID resource: Parameter bilangan bulat yang menunjukkan ID untuk resource tertentu harus diberi anotasi dengan definisi jenis resource yang sesuai. Ada anotasi untuk setiap jenis resource, seperti @StringRes, @ColorRes, dan @AnimRes, selain @AnyRes yang mencakup semua jenis resource. Contoh:

public void setTitle(@StringRes int resId)

@IntDef untuk kumpulan konstanta

Konstanta ajaib: Parameter String dan int yang dimaksudkan untuk menerima salah satu dari serangkaian nilai terbatas yang mungkin ditunjukkan oleh konstanta publik harus diberi anotasi dengan tepat menggunakan @StringDef atau @IntDef. Anotasi ini memungkinkan Anda membuat anotasi baru yang dapat Anda gunakan dan berfungsi seperti typedef untuk parameter yang diizinkan. Contoh:

/** @hide */
@IntDef(prefix = {"NAVIGATION_MODE_"}, value = {
  NAVIGATION_MODE_STANDARD,
  NAVIGATION_MODE_LIST,
  NAVIGATION_MODE_TABS
})
@Retention(RetentionPolicy.SOURCE)
public @interface NavigationMode {}

public static final int NAVIGATION_MODE_STANDARD = 0;
public static final int NAVIGATION_MODE_LIST = 1;
public static final int NAVIGATION_MODE_TABS = 2;

@NavigationMode
public int getNavigationMode();
public void setNavigationMode(@NavigationMode int mode);

Metode direkomendasikan untuk memeriksa validitas parameter yang diberi anotasi dan menampilkan IllegalArgumentException jika parameter bukan bagian dari @IntDef

@IntDef untuk tanda bitmask

Anotasi juga dapat menentukan bahwa konstanta adalah tanda, dan dapat digabungkan dengan & dan I:

/** @hide */
@IntDef(flag = true, prefix = { "FLAG_" }, value = {
  FLAG_USE_LOGO,
  FLAG_SHOW_HOME,
  FLAG_HOME_AS_UP,
})
@Retention(RetentionPolicy.SOURCE)
public @interface DisplayOptions {}

@StringDef untuk kumpulan konstanta string

Ada juga anotasi @StringDef, yang persis seperti @IntDef di bagian sebelumnya, tetapi untuk konstanta String. Anda dapat menyertakan beberapa nilai "prefix" yang digunakan untuk memancarkan dokumentasi secara otomatis untuk semua nilai.

@SdkConstant untuk konstanta SDK

@SdkConstant Anotasi kolom publik jika kolom tersebut adalah salah satu nilai SdkConstant berikut: ACTIVITY_INTENT_ACTION, BROADCAST_INTENT_ACTION, SERVICE_ACTION, INTENT_CATEGORY, FEATURE.

@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
public static final String ACTION_CALL = "android.intent.action.CALL";

Menyediakan nullabilitas yang kompatibel untuk penggantian

Untuk kompatibilitas API, kemampuan nilai null penggantian harus kompatibel dengan kemampuan nilai null induk saat ini. Tabel berikut menunjukkan ekspektasi kompatibilitas. Jelasnya, penggantian hanya boleh sama ketat atau lebih ketat daripada elemen yang diganti.

Jenis Orang tua Anak
Jenis nilai yang ditampilkan Tidak diberi anotasi Tidak dianotasi atau non-null
Jenis nilai yang ditampilkan Nullable Nullable atau non-null
Jenis nilai yang ditampilkan Nonnull Nonnull
Argumen menyenangkan Tidak diberi anotasi Tidak dianotasi atau dapat bernilai null
Argumen menyenangkan Nullable Nullable
Argumen menyenangkan Nonnull Nullable atau non-null

Pilih argumen yang tidak dapat bernilai null (seperti @NonNull) jika memungkinkan

Saat metode di-overload, sebaiknya semua argumen tidak boleh null.

public void startActivity(@NonNull Component component) { ... }
public void startActivity(@NonNull Component component, @NonNull Bundle options) { ... }

Aturan ini juga berlaku untuk setter properti yang kelebihan beban. Argumen utama tidak boleh null dan penghapusan properti harus diterapkan sebagai metode terpisah. Hal ini mencegah panggilan "tidak masuk akal" saat developer harus menetapkan parameter di akhir meskipun tidak diperlukan.

public void setTitleItem(@Nullable IconCompat icon, @ImageMode mode)
public void setTitleItem(@Nullable IconCompat icon, @ImageMode mode, boolean isLoading)

// Nonsense call to clear property
setTitleItem(null, MODE_RAW, false);
public void setTitleItem(@NonNull IconCompat icon, @ImageMode mode)
public void setTitleItem(@NonNull IconCompat icon, @ImageMode mode, boolean isLoading)
public void clearTitleItem()

Pilih jenis nilai yang ditampilkan non-nullable (seperti @NonNull) untuk penampung

Untuk jenis penampung seperti Bundle atau Collection, tampilkan penampung kosong -- dan tidak dapat diubah, jika berlaku. Dalam kasus saat null akan digunakan untuk membedakan ketersediaan penampung, pertimbangkan untuk menyediakan metode boolean terpisah.

@NonNull
public Bundle getExtras() { ... }

Anotasi nullabilitas untuk pasangan get dan set harus sama

Pasangan metode get dan set untuk satu properti logis harus selalu sesuai dalam anotasi nullabilitasnya. Jika panduan ini tidak diikuti, sintaksis properti Kotlin akan gagal, dan menambahkan anotasi nullability yang tidak sesuai ke metode properti yang ada akan menjadi perubahan yang merusak sumber bagi pengguna Kotlin.

@NonNull
public Bundle getExtras() { ... }
public void setExtras(@NonNull Bundle bundle) { ... }

Nilai yang ditampilkan dalam kondisi kegagalan atau error

Semua API harus mengizinkan aplikasi bereaksi terhadap error. Menampilkan false, -1, null, atau nilai segala jenis lainnya "terjadi kesalahan" tidak memberi tahu developer cukup banyak tentang kegagalan untuk menetapkan ekspektasi pengguna atau melacak keandalan aplikasi mereka secara akurat di lapangan. Saat mendesain API, bayangkan Anda sedang membangun aplikasi. Jika Anda mengalami error, apakah API memberikan informasi yang cukup untuk menampilkannya kepada pengguna atau bereaksi dengan tepat?

  1. Tidak masalah (dan dianjurkan) untuk menyertakan informasi mendetail dalam pesan pengecualian, tetapi developer tidak perlu menguraikannya untuk menangani error dengan tepat. Kode error verbose atau informasi lainnya harus diekspos sebagai metode.
  2. Pastikan opsi penanganan error yang Anda pilih memberi Anda fleksibilitas untuk memperkenalkan jenis error baru di masa mendatang. Untuk @IntDef, itu 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 diimplementasikan oleh pengecualian Anda, sehingga kode apa pun yang menangani jenis tersebut juga akan menangkap dan menangani subjenis.
  3. Developer seharusnya sulit atau tidak mungkin mengabaikan error secara tidak sengaja -- jika error Anda dikomunikasikan dengan menampilkan nilai, anotasikan metode Anda dengan @CheckResult.

Lebih baik memunculkan ? extends RuntimeException saat kondisi kegagalan atau error tercapai karena sesuatu yang salah dilakukan oleh developer, misalnya mengabaikan batasan pada parameter input atau gagal memeriksa status yang dapat diamati.

Metode setter atau tindakan (misalnya, perform) dapat menampilkan kode status bilangan bulat jika tindakan dapat gagal sebagai akibat dari kondisi atau status yang diperbarui secara asinkron di luar kendali developer.

Kode status harus ditentukan pada class yang berisi sebagai kolom public static final, diberi awalan dengan ERROR_, dan di-enum dalam anotasi @hide @IntDef.

Nama metode harus selalu diawali dengan kata kerja, bukan subjek

Nama metode harus selalu diawali dengan kata kerja (seperti get, create, reload, dll.), bukan objek yang Anda tindaki.

public void tableReload() {
  mTable.reload();
}
public void reloadTable() {
  mTable.reload();
}

Lebih memilih jenis Collection daripada array sebagai jenis nilai yang ditampilkan atau parameter

Antarmuka koleksi yang diketik secara generik memberikan beberapa keuntungan dibandingkan array, termasuk kontrak API yang lebih kuat terkait keunikan dan pengurutan, dukungan untuk generik, dan sejumlah metode praktis yang mudah digunakan developer.

Pengecualian untuk primitif

Jika elemennya adalah primitif, do lebih memilih array untuk menghindari biaya boxing otomatis. Lihat Mengambil dan menampilkan primitif mentah, bukan versi yang dikemas

Pengecualian untuk kode yang sensitif terhadap performa

Dalam skenario tertentu, saat API digunakan dalam kode yang sensitif terhadap performa (seperti grafis atau API pengukuran/tata letak/penggambaran lainnya), Anda dapat menggunakan array alih-alih koleksi untuk mengurangi alokasi dan churn memori.

Pengecualian untuk Kotlin

Array Kotlin invarian dan bahasa Kotlin menyediakan banyak API utilitas di sekitar array, sehingga array setara dengan List dan Collection untuk API Kotlin yang dimaksudkan untuk diakses dari Kotlin.

Lebih memilih koleksi @NonNull

Selalu pilih @NonNull untuk objek pengumpulan. Saat menampilkan koleksi kosong, gunakan metode Collections.empty yang sesuai untuk menampilkan objek koleksi yang berbiaya rendah, berjenis yang benar, dan tidak dapat diubah.

Jika anotasi jenis didukung, selalu pilih @NonNull untuk elemen koleksi.

Anda juga sebaiknya menggunakan @NonNull saat menggunakan array, bukan koleksi (lihat item sebelumnya). Jika alokasi objek menjadi masalah, buat konstanta dan teruskan - bagaimanapun, array kosong bersifat tetap. Contoh:

private static final int[] EMPTY_USER_IDS = new int[0];

@NonNull
public int[] getUserIds() {
  int [] userIds = mService.getUserIds();
  return userIds != null ? userIds : EMPTY_USER_IDS;
}

Kemampuan koleksi untuk diubah

Kotlin API harus memilih jenis nilai yang hanya dapat dibaca (bukan Mutable) untuk koleksi secara default kecuali kontrak API secara khusus memerlukan jenis nilai yang dapat diubah.

Namun, Java API harus memilih jenis nilai yang ditampilkan dapat diubah secara default karena implementasi Java API di platform Android belum menyediakan implementasi koleksi yang tidak dapat diubah dengan mudah. Pengecualian untuk aturan ini adalah jenis nilai yang ditampilkan Collections.empty, yang tidak dapat diubah. Jika mutabilitas dapat dieksploitasi oleh klien -- dengan sengaja atau karena kesalahan -- untuk merusak pola penggunaan yang dimaksudkan API, API Java harus mempertimbangkan dengan cermat untuk menampilkan salinan dangkal koleksi.

@Nullable
public PermissionInfo[] getGrantedPermissions() {
  return mPermissions;
}
@NonNull
public Set<PermissionInfo> getGrantedPermissions() {
  if (mPermissions == null) {
    return Collections.emptySet();
  }
  return new ArraySet<>(mPermissions);
}

Jenis nilai yang ditampilkan yang dapat diubah secara eksplisit

API yang menampilkan koleksi idealnya tidak boleh mengubah objek koleksi yang ditampilkan setelah ditampilkan. Jika kumpulan yang ditampilkan harus diubah atau digunakan kembali dengan cara tertentu -- misalnya, tampilan yang disesuaikan dari set data yang dapat diubah -- perilaku kapan konten dapat berubah harus didokumentasikan secara eksplisit atau mengikuti konvensi penamaan API yang sudah ditetapkan.

/**
 * Returns a view of this object as a list of [Item]s.
 */
fun MyObject.asList(): List<Item> = MyObjectListWrapper(this)

Konvensi Kotlin .asFoo() dijelaskan di bawah dan memungkinkan kumpulan yang ditampilkan oleh .asList() berubah jika kumpulan asli berubah.

Mutabilitas objek jenis data yang ditampilkan

Mirip dengan API yang menampilkan koleksi, API yang menampilkan objek jenis data idealnya tidak boleh mengubah properti objek yang ditampilkan setelah ditampilkan.

val tempResult = DataContainer()

fun add(other: DataContainer): DataContainer {
  tempResult.innerValue = innerValue + other.innerValue
  return tempResult
}
fun add(other: DataContainer): DataContainer {
  return DataContainer(innerValue + other.innerValue)
}

Dalam kasus yang sangat terbatas, beberapa kode yang sensitif terhadap performa dapat memperoleh manfaat dari penggabungan atau penggunaan ulang objek. Jangan tulis struktur data kumpulan objek Anda sendiri dan jangan mengekspos objek yang digunakan kembali di API publik. Dalam kedua kasus tersebut, Anda harus sangat berhati-hati dalam mengelola akses serentak.

Penggunaan jenis parameter vararg

API Kotlin dan Java dianjurkan untuk menggunakan vararg jika developer cenderung membuat array di situs panggilan hanya untuk tujuan meneruskan beberapa parameter terkait dengan jenis yang sama.

public void setFeatures(Feature[] features) { ... }

// Developer code
setFeatures(new Feature[]{Features.A, Features.B, Features.C});
public void setFeatures(Feature... features) { ... }

// Developer code
setFeatures(Features.A, Features.B, Features.C);

Salinan defensif

Implementasi parameter vararg Java dan Kotlin dikompilasi ke bytecode yang didukung array yang sama dan oleh karena itu dapat dipanggil dari kode Java dengan array yang dapat diubah. Desainer API sangat dianjurkan untuk membuat salinan dangkal defensif dari parameter array jika parameter tersebut akan dipertahankan ke kolom atau class dalam anonim.

public void setValues(SomeObject... values) {
   this.values = Arrays.copyOf(values, values.length);
}

Perhatikan bahwa membuat salinan defensif tidak memberikan perlindungan apa pun terhadap modifikasi serentak antara panggilan metode awal dan pembuatan salinan, juga tidak melindungi terhadap mutasi objek yang terdapat dalam array.

Memberikan semantik yang benar dengan parameter jenis koleksi atau jenis yang ditampilkan

List<Foo> adalah opsi default, tetapi pertimbangkan jenis lainnya untuk memberikan makna tambahan:

  • Gunakan Set<Foo>, jika API Anda tidak terpengaruh oleh urutan elemen dan tidak mengizinkan duplikat atau duplikat tidak memiliki arti.

  • Collection<Foo>, jika API Anda tidak terpengaruh oleh urutan dan mengizinkan duplikat.

Fungsi konversi Kotlin

Kotlin sering menggunakan .toFoo() dan .asFoo() untuk mendapatkan objek dengan jenis yang berbeda dari objek yang ada dengan Foo adalah nama jenis nilai yang ditampilkan konversi. Hal ini konsisten dengan JDK Object.toString() yang sudah dikenal. Kotlin melangkah lebih jauh dengan menggunakannya untuk konversi primitif seperti 25.toFloat().

Perbedaan antara konversi yang diberi nama .toFoo() dan .asFoo() sangat signifikan:

Gunakan .toFoo() saat membuat objek baru yang independen

Seperti .toString(), konversi "ke" menampilkan objek baru yang independen. Jika objek asli diubah nanti, objek baru tidak akan mencerminkan perubahan tersebut. Demikian pula, jika objek baru diubah nanti, objek lama tidak akan mencerminkan perubahan tersebut.

fun Foo.toBundle(): Bundle = Bundle().apply {
    putInt(FOO_VALUE_KEY, value)
}

Gunakan .asFoo() saat membuat wrapper dependen, objek yang didekorasi, atau cast

Pengecoran di Kotlin dilakukan menggunakan kata kunci as. Hal ini mencerminkan perubahan pada antarmuka, tetapi bukan perubahan pada identitas. Jika digunakan sebagai awalan dalam fungsi ekstensi, .asFoo() akan mendekorasi penerima. Mutasi pada objek penerima asli akan tercermin dalam objek yang ditampilkan oleh asFoo(). Mutasi pada objek Foo baru dapat tercermin dalam objek asli.

fun <T> Flow<T>.asLiveData(): LiveData<T> = liveData {
    collect {
        emit(it)
    }
}

Fungsi konversi harus ditulis sebagai fungsi ekstensi

Menulis fungsi konversi di luar definisi class penerima dan hasil mengurangi coupling antar-jenis. Konversi yang ideal hanya memerlukan akses API publik ke objek asli. Hal ini membuktikan dengan contoh bahwa developer dapat menulis konversi analog ke jenis pilihan mereka sendiri.

Tampilkan pengecualian spesifik yang sesuai

Metode tidak boleh menampilkan pengecualian generik seperti java.lang.Exception atau java.lang.Throwable, tetapi pengecualian spesifik yang sesuai harus digunakan seperti java.lang.NullPointerException agar developer dapat menangani pengecualian tanpa terlalu luas.

Error yang tidak terkait dengan argumen yang diberikan langsung ke metode yang dipanggil secara publik harus menampilkan java.lang.IllegalStateException, bukan java.lang.IllegalArgumentException atau java.lang.NullPointerException.

Listener dan callback

Berikut adalah aturan seputar class dan metode yang digunakan untuk mekanisme pemroses dan callback.

Nama class callback harus tunggal

Gunakan MyObjectCallback, bukan MyObjectCallbacks.

Nama metode callback harus dalam format on

onFooEvent menandakan bahwa FooEvent sedang terjadi dan callback harus bertindak sebagai respons.

Tense lampau versus present tense harus menjelaskan perilaku pengaturan waktu

Metode callback terkait peristiwa harus diberi nama untuk menunjukkan apakah peristiwa telah terjadi atau sedang dalam proses terjadi.

Misalnya, jika metode dipanggil setelah tindakan klik dilakukan:

public void onClicked()

Namun, jika metode bertanggung jawab untuk melakukan tindakan klik:

public boolean onClick()

Pendaftaran callback

Jika pemroses atau callback dapat ditambahkan atau dihapus dari objek, metode terkait harus diberi nama add dan remove atau register dan unregister. Konsisten dengan konvensi yang ada yang digunakan oleh class atau oleh class lain dalam paket yang sama. Jika tidak ada preseden seperti itu, pilih tambahkan dan hapus.

Metode yang melibatkan pendaftaran atau pembatalan pendaftaran callback harus menentukan seluruh nama jenis callback.

public void addFooCallback(@NonNull FooCallback callback);
public void removeFooCallback(@NonNull FooCallback callback);
public void registerFooCallback(@NonNull FooCallback callback);
public void unregisterFooCallback(@NonNull FooCallback callback);

Hindari getter untuk callback

Jangan menambahkan metode getFooCallback(). Ini adalah jalan keluar yang menarik untuk kasus ketika developer mungkin ingin menggabungkan callback yang ada dengan penggantinya sendiri, tetapi hal ini tidak stabil dan membuat status saat ini sulit dipahami oleh developer komponen. Misalnya,

  • Developer A memanggil setFooCallback(a)
  • Developer B memanggil setFooCallback(new B(getFooCallback()))
  • Developer A ingin menghapus callback a-nya dan tidak dapat melakukannya tanpa mengetahui jenis B, dan B telah dibuat untuk memungkinkan modifikasi callback yang di-wrap.

Menerima Eksekutor untuk mengontrol pengiriman callback

Saat mendaftarkan callback yang tidak memiliki ekspektasi threading eksplisit (hampir di mana saja di luar toolkit UI), sangat disarankan untuk menyertakan parameter Executor sebagai bagian dari pendaftaran untuk memungkinkan developer menentukan thread tempat callback akan dipanggil.

public void registerFooCallback(
    @NonNull @CallbackExecutor Executor executor,
    @NonNull FooCallback callback)

Sebagai pengecualian terhadap pedoman kami tentang parameter opsional, dapat diterima untuk memberikan kelebihan beban yang menghilangkan Executor meskipun bukan argumen terakhir dalam daftar parameter. Jika Executor tidak diberikan, callback harus dipanggil di thread utama menggunakan Looper.getMainLooper() dan hal ini harus didokumentasikan pada metode yang kelebihan beban terkait.

/**
 * ...
 * Note that the callback will be executed on the main thread using
 * {@link Looper.getMainLooper()}. To specify the execution thread, use
 * {@link registerFooCallback(Executor, FooCallback)}.
 * ...
 */
public void registerFooCallback(
    @NonNull FooCallback callback)

public void registerFooCallback(
    @NonNull @CallbackExecutor Executor executor,
    @NonNull FooCallback callback)

Kesalahan penerapan Executor: Perhatikan bahwa berikut ini adalah eksekutor yang valid.

public class SynchronousExecutor implements Executor {
    @Override
    public void execute(Runnable r) {
        r.run();
    }
}

Artinya, saat menerapkan API yang berbentuk ini, implementasi objek binder masuk Anda di sisi proses aplikasi harus memanggil Binder.clearCallingIdentity() sebelum memanggil callback aplikasi di Executor yang disediakan aplikasi. Dengan cara ini, kode aplikasi apa pun yang menggunakan identitas binder (seperti Binder.getCallingUid()) untuk pemeriksaan izin akan mengatribusikan kode yang berjalan dengan benar ke aplikasi, bukan ke proses sistem yang memanggil ke aplikasi. Jika pengguna API Anda menginginkan informasi UID atau PID pemanggil, hal ini harus menjadi bagian eksplisit dari permukaan API Anda, bukan implisit berdasarkan tempat Executor yang mereka berikan berjalan.

Menentukan Executor harus didukung oleh API Anda. Dalam kasus yang penting untuk performa, aplikasi mungkin perlu menjalankan kode secara langsung atau serentak dengan masukan dari API Anda. Menerima Executor mengizinkan hal ini. Membuat HandlerThread tambahan atau yang serupa dengan trampolin secara defensif dari menggagalkan kasus penggunaan yang diinginkan ini.

Jika aplikasi akan menjalankan kode yang mahal di suatu tempat dalam prosesnya sendiri, izinkan mereka. Solusi yang akan ditemukan developer aplikasi untuk mengatasi batasan Anda akan jauh lebih sulit didukung dalam jangka panjang.

Pengecualian untuk callback tunggal: jika sifat peristiwa yang dilaporkan hanya memerlukan dukungan untuk satu instance callback, gunakan gaya berikut:

public void setFooCallback(
    @NonNull @CallbackExecutor Executor executor,
    @NonNull FooCallback callback)

public void clearFooCallback()

Gunakan Executor, bukan Handler

Handler Android digunakan sebagai standar untuk mengalihkan eksekusi callback ke thread Looper tertentu pada masa lalu. Standar ini diubah untuk lebih memilih Executor karena sebagian besar developer aplikasi mengelola kumpulan thread mereka sendiri, sehingga thread utama atau UI menjadi satu-satunya thread Looper yang tersedia untuk aplikasi. Gunakan Executor untuk memberi developer kontrol yang mereka butuhkan untuk menggunakan kembali konteks eksekusi yang ada/disukai.

Library konkurensi modern seperti kotlinx.coroutines atau RxJava menyediakan mekanisme penjadwalan sendiri yang melakukan pengiriman sendiri jika diperlukan, sehingga penting untuk menyediakan kemampuan menggunakan eksekutor langsung (seperti Runnable::run) untuk menghindari latensi dari hop thread ganda. Misalnya, satu hop untuk memposting ke thread Looper menggunakan Handler, diikuti dengan hop lain dari framework konkurensi aplikasi.

Pengecualian terhadap pedoman ini jarang terjadi. Alasan umum pengajuan banding untuk mendapatkan pengecualian meliputi:

Saya harus menggunakan Looper karena saya memerlukan Looper untuk epoll acara. Permintaan pengecualian ini disetujui karena manfaat Executor tidak dapat direalisasikan dalam situasi ini.

Saya tidak ingin kode aplikasi memblokir thread saya yang memublikasikan peristiwa. Permintaan pengecualian ini biasanya tidak diberikan untuk kode yang berjalan dalam proses aplikasi. Aplikasi yang salah dalam hal ini hanya merugikan diri mereka sendiri, tidak memengaruhi kesehatan sistem secara keseluruhan. Aplikasi yang melakukannya dengan benar atau menggunakan framework konkurensi umum tidak akan dikenai penalti latensi tambahan.

Handler konsisten secara lokal dengan API serupa lainnya dalam class yang sama. Permintaan pengecualian ini diberikan secara situasional. Lebih memilih penambahan overload berbasis Executor, memigrasikan penerapan Handler untuk menggunakan penerapan Executor baru. (myHandler::post adalah Executor yang valid!) Bergantung pada ukuran class, jumlah metode Handler yang ada, dan kemungkinan developer perlu menggunakan metode berbasis Handler yang ada bersama dengan metode baru, pengecualian dapat diberikan untuk menambahkan metode berbasis Handler baru.

Simetri dalam pendaftaran

Jika ada cara untuk menambahkan atau mendaftarkan sesuatu, seharusnya ada juga cara untuk menghapus/membatalkan pendaftarannya. Metode

registerThing(Thing)

harus memiliki

unregisterThing(Thing)

Memberikan ID permintaan

Jika developer dapat menggunakan kembali callback, berikan objek ID untuk mengikat callback ke permintaan.

class RequestParameters {
  public int getId() { ... }
}

class RequestExecutor {
  public void executeRequest(
    RequestParameters parameters,
    Consumer<RequestParameters> onRequestCompletedListener) { ... }
}

Objek callback multi-metode

Callback multi-metode harus lebih memilih interface dan menggunakan metode default saat menambahkan ke antarmuka yang dirilis sebelumnya. Sebelumnya, panduan ini merekomendasikan abstract class karena tidak adanya metode default di Java 7.

public interface MostlyOptionalCallback {
  void onImportantAction();
  default void onOptionalInformation() {
    // Empty stub, this method is optional.
  }
}

Menggunakan android.os.OutcomeReceiver saat membuat model panggilan fungsi non-pemblokiran

OutcomeReceiver<R,E> melaporkan nilai hasil R jika berhasil atau E : Throwable jika tidak - hal yang sama dapat dilakukan oleh panggilan metode biasa. Gunakan OutcomeReceiver sebagai jenis callback saat mengonversi metode pemblokiran yang menampilkan hasil atau menampilkan pengecualian ke metode asinkron non-pemblokiran:

interface FooType {
  // Before:
  public FooResult requestFoo(FooRequest request);

  // After:
  public void requestFooAsync(FooRequest request, Executor executor,
      OutcomeReceiver<FooResult, Throwable> callback);
}

Metode asinkron yang dikonversi dengan cara ini selalu menampilkan void. Setiap hasil yang akan ditampilkan oleh requestFoo dilaporkan ke OutcomeReceiver.onResult parameter callback requestFooAsync dengan memanggilnya di executor yang diberikan. Pengecualian apa pun yang akan diterapkan oleh requestFoo akan dilaporkan ke metode OutcomeReceiver.onError dengan cara yang sama.

Penggunaan OutcomeReceiver untuk melaporkan hasil metode asinkron juga menyediakan wrapper suspend fun Kotlin untuk metode asinkron menggunakan ekstensi Continuation.asOutcomeReceiver dari androidx.core:core-ktx:

suspend fun FooType.requestFoo(request: FooRequest): FooResult =
  suspendCancellableCoroutine { continuation ->
    requestFooAsync(request, Runnable::run, continuation.asOutcomeReceiver())
  }

Ekstensi seperti ini memungkinkan klien Kotlin memanggil metode asinkron non-pemblokiran dengan kemudahan panggilan fungsi biasa tanpa memblokir thread panggilan. Ekstensi 1-1 untuk API platform ini dapat ditawarkan sebagai bagian dari artefak androidx.core:core-ktx di Jetpack jika digabungkan dengan pemeriksaan dan pertimbangan kompatibilitas versi standar. Lihat dokumentasi untuk asOutcomeReceiver untuk mengetahui informasi selengkapnya, pertimbangan pembatalan, dan contoh.

Metode asinkron yang tidak cocok dengan semantik metode yang menampilkan hasil atau mengecualikan pengecualian saat tugasnya selesai tidak boleh menggunakan OutcomeReceiver sebagai jenis callback. Sebagai gantinya, pertimbangkan salah satu opsi lain yang tercantum di bagian berikut.

Lebih memilih antarmuka fungsional daripada membuat jenis metode abstrak tunggal (SAM) baru

Level API 24 menambahkan jenis java.util.function.* (dokumen referensi) , yang menawarkan antarmuka SAM generik seperti Consumer<T> yang cocok untuk digunakan sebagai lambda callback. Dalam banyak kasus, membuat antarmuka SAM baru kurang memberikan nilai dalam hal keamanan jenis atau menyampaikan maksud, sekaligus memperluas area permukaan API Android secara tidak perlu.

Pertimbangkan untuk menggunakan antarmuka generik ini, daripada membuat yang baru:

Penempatan parameter SAM

Parameter SAM harus ditempatkan terakhir untuk memungkinkan penggunaan idiomatis dari Kotlin, meskipun metode tersebut kelebihan beban dengan parameter tambahan.

public void schedule(Runnable runnable)

public void schedule(int delay, Runnable runnable)

Dokumen

Berikut adalah aturan tentang dokumen publik (Javadoc) untuk API.

Semua API publik harus didokumentasikan

Semua API publik harus memiliki dokumentasi yang memadai untuk menjelaskan cara developer menggunakan API. Asumsikan developer menemukan metode menggunakan pelengkapan otomatis atau saat menjelajahi dokumen referensi API dan memiliki sedikit konteks dari permukaan API yang berdekatan (misalnya, class yang sama).

Metode

Parameter metode dan nilai yang ditampilkan harus didokumentasikan menggunakan anotasi dokumen @param dan @return. Format isi Javadoc seolah-olah diawali dengan "Metode ini...".

Jika metode tidak menggunakan parameter, tidak memiliki pertimbangan khusus, dan menampilkan apa yang dikatakan oleh nama metode, Anda dapat menghapus @return dan menulis dokumen yang mirip dengan:

/**
 * Returns the priority of the thread.
 */
@IntRange(from = 1, to = 10)
public int getPriority() { ... }

Dokumen harus ditautkan ke dokumen lain untuk konstanta, metode, dan elemen terkait lainnya. Gunakan tag Javadoc (misalnya, @see dan {@link foo}), bukan hanya kata-kata teks biasa.

Untuk contoh sumber berikut:

public static final int FOO = 0;
public static final int BAR = 1;

Jangan menggunakan teks mentah atau font kode:

/**
 * Sets value to one of FOO or <code>BAR</code>.
 *
 * @param value the value being set, one of FOO or BAR
 */
public void setValue(int value) { ... }

Sebagai gantinya, gunakan link:

/**
 * Sets value to one of {@link #FOO} or {@link #BAR}.
 *
 * @param value the value being set
 */
public void setValue(@ValueType int value) { ... }

Perhatikan bahwa penggunaan anotasi IntDef seperti @ValueType pada parameter akan otomatis menghasilkan dokumentasi yang menentukan jenis yang diizinkan. Lihat panduan tentang anotasi untuk mengetahui informasi selengkapnya tentang IntDef.

Jalankan target update-api atau docs saat menambahkan Javadoc

Aturan ini sangat penting saat menambahkan tag @link atau @see, dan pastikan output terlihat seperti yang diharapkan. Output ERROR di Javadoc sering kali disebabkan oleh link yang salah. Target update-api atau docs Make melakukan pemeriksaan ini, tetapi target docs mungkin lebih cepat jika Anda hanya mengubah Javadoc dan tidak perlu menjalankan target update-api.

Gunakan {@code foo} untuk membedakan nilai Java

Bungkus nilai Java seperti true, false, dan null dengan {@code...} untuk membedakannya dari teks dokumentasi.

Saat menulis dokumentasi di sumber Kotlin, Anda dapat membungkus kode dengan tanda petik terbalik seperti yang Anda lakukan untuk Markdown.

Ringkasan @param dan @return harus berupa fragmen kalimat tunggal

Ringkasan parameter dan nilai yang ditampilkan harus diawali dengan karakter huruf kecil dan hanya berisi satu fragmen kalimat. Jika Anda memiliki informasi tambahan yang melampaui satu kalimat, pindahkan ke isi Javadoc metode:

/**
 * @param e The element to be appended to the list. This must not be
 *       null. If the list contains no entries, this element will
 *       be added at the beginning.
 * @return This method returns true on success.
 */

Harus diubah menjadi:

/**
 * @param e element to be appended to this list, must be non-{@code null}
 * @return {@code true} on success, {@code false} otherwise
 */

Anotasi Dokumen memerlukan penjelasan

Mendokumentasikan alasan anotasi @hide dan @removed disembunyikan dari API publik. Sertakan petunjuk tentang cara mengganti elemen API yang ditandai dengan anotasi @deprecated.

Menggunakan @throws untuk mendokumentasikan pengecualian

Jika metode menampilkan pengecualian yang diperiksa, misalnya IOException, dokumentasikan pengecualian dengan @throws. Untuk API yang bersumber dari Kotlin yang ditujukan untuk digunakan oleh klien Java, anotasi fungsi dengan @Throws.

Jika metode menampilkan pengecualian yang tidak diperiksa yang menunjukkan error yang dapat dicegah, misalnya IllegalArgumentException atau IllegalStateException, dokumentasikan pengecualian dengan penjelasan mengapa pengecualian ditampilkan. Pengecualian yang dilempar juga harus menunjukkan alasan pengecualian tersebut dilempar.

Kasus pengecualian yang tidak diperiksa tertentu dianggap implisit dan tidak perlu didokumentasikan, seperti NullPointerException atau IllegalArgumentException jika argumen tidak cocok dengan @IntDef atau anotasi serupa yang menyematkan kontrak API ke dalam tanda tangan metode:

/**
 * ...
 * @throws IOException If it cannot find the schema for {@code toVersion}
 * @throws IllegalStateException If the schema validation fails
 */
public SupportSQLiteDatabase runMigrationsAndValidate(String name, int version,
    boolean validateDroppedTables, Migration... migrations) throws IOException {
  // ...
  if (!dbPath.exists()) {
    throw new IllegalStateException("Cannot find the database file for " + name
        + ". Before calling runMigrations, you must first create the database "
        + "using createDatabase.");
  }
  // ...

Atau, di Kotlin:

/**
 * ...
 * @throws IOException If something goes wrong reading the file, such as a bad
 *                     database header or missing permissions
 */
@Throws(IOException::class)
fun readVersion(databaseFile: File): Int {
  // ...
  val read = input.read(buffer)
    if (read != 4) {
      throw IOException("Bad database header, unable to read 4 bytes at " +
          "offset 60")
    }
  }
  // ...

Jika metode memanggil kode asinkron yang dapat memunculkan pengecualian, pertimbangkan cara developer mengetahui dan merespons pengecualian tersebut. Biasanya, hal ini melibatkan penerusan pengecualian ke callback dan mendokumentasikan pengecualian yang ditampilkan pada metode yang menerimanya. Pengecualian asinkron tidak boleh didokumentasikan dengan @throws kecuali jika benar-benar ditampilkan ulang dari metode yang diberi anotasi.

Akhiri kalimat pertama dokumen dengan titik

Alat Doclava mengurai dokumen secara sederhana, mengakhiri dokumen sinopsis (kalimat pertama, yang digunakan dalam deskripsi singkat di bagian atas dokumen class) segera setelah melihat titik (.) diikuti dengan spasi. Hal ini menyebabkan dua masalah:

  • Jika dokumen singkat tidak diakhiri dengan titik, dan jika anggota tersebut telah mewarisi dokumen yang diambil oleh alat, maka sinopsis juga akan mengambil dokumen yang diwarisi tersebut. Misalnya, lihat actionBarTabStyle di 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 tanpa putus setelah titik; namun, jangan membuat kesalahan ini sejak awal.

Memformat dokumen agar dirender dalam HTML

Javadoc dirender dalam HTML, jadi format dokumen ini dengan tepat:

  • Baris baru harus menggunakan tag <p> eksplisit. Jangan menambahkan tag penutup </p>.

  • Jangan gunakan ASCII untuk merender daftar atau tabel.

  • Daftar harus menggunakan <ul> atau <ol> untuk daftar tidak berurutan dan berurutan. Setiap item harus diawali dengan tag <li>, tetapi tidak memerlukan tag penutup </li>. Tag penutup </ul> atau </ol> diperlukan setelah item terakhir.

  • Tabel harus menggunakan <table>, <tr> untuk baris, <th> untuk header, dan <td> untuk sel. Semua tag tabel memerlukan tag penutup yang cocok. Anda dapat menggunakan class="deprecated" pada tag apa pun untuk menunjukkan penghentian penggunaan.

  • Untuk membuat font kode inline, gunakan {@code foo}.

  • Untuk membuat blok kode, gunakan <pre>.

  • Semua teks di dalam blok <pre> diuraikan oleh browser, jadi berhati-hatilah dengan tanda kurung <>. Anda dapat mengganti karakter tersebut dengan entity HTML &lt; dan &gt;.

  • Atau, Anda dapat membiarkan tanda kurung mentah <> dalam cuplikan kode jika Anda membungkus bagian yang bermasalah dalam {@code foo}. Contoh:

    <pre>{@code <manifest>}</pre>
    

Mengikuti panduan gaya referensi API

Untuk memberikan konsistensi dalam gaya untuk ringkasan class, deskripsi metode, deskripsi parameter, dan item lainnya, ikuti rekomendasi dalam panduan bahasa Java resmi di Cara Menulis Komentar Dokumen untuk Alat Javadoc.

Aturan khusus Framework Android

Aturan ini berkaitan dengan API, pola, dan struktur data yang khusus untuk API dan perilaku yang dibuat ke dalam framework Android (misalnya, Bundle atau Parcelable).

Pembuat intent harus menggunakan pola create*Intent()

Pembuat untuk intent harus menggunakan metode yang bernama createFooIntent().

Gunakan Bundle, bukan membuat struktur data serbaguna baru

Hindari pembuatan struktur data serbaguna baru untuk merepresentasikan pemetaan kunci ke nilai yang diketik secara arbitrer. Sebagai gantinya, pertimbangkan untuk menggunakan Bundle.

Hal ini biasanya muncul saat menulis API platform yang berfungsi sebagai saluran komunikasi antara aplikasi dan layanan non-platform, di mana platform tidak membaca data yang dikirim melalui saluran tersebut dan kontrak API mungkin sebagian ditentukan di luar platform (misalnya, di library Jetpack).

Jika platform membaca data, hindari penggunaan Bundle dan pilih class data dengan jenis yang dikenali.

Implementasi Parcelable harus memiliki kolom CREATOR publik

Inflasi Parcelable diekspos melalui CREATOR, bukan konstruktor mentah. Jika class mengimplementasikan Parcelable, maka kolom CREATOR-nya juga harus berupa API publik dan konstruktor class yang mengambil argumen Parcel harus bersifat pribadi.

Menggunakan CharSequence untuk string UI

Saat string ditampilkan di antarmuka pengguna, gunakan CharSequence untuk memungkinkan instance Spannable.

Jika hanya berupa kunci atau label atau nilai lain yang tidak terlihat oleh pengguna, String tidak masalah.

Hindari penggunaan Enum

IntDef harus digunakan di atas enum di semua API platform, dan harus dipertimbangkan secara matang dalam API library yang tidak digabungkan. Gunakan enum hanya jika Anda yakin bahwa nilai baru tidak akan ditambahkan.

ManfaatIntDef:

  • Memungkinkan penambahan nilai dari waktu ke waktu
    • Pernyataan when Kotlin dapat gagal saat runtime jika tidak lagi menyeluruh karena nilai enum yang ditambahkan di platform.
  • Tidak ada class atau objek yang digunakan saat runtime, hanya primitif
    • Meskipun R8 atau minifikasi dapat menghindari biaya ini untuk API library yang tidak digabungkan, pengoptimalan ini tidak dapat memengaruhi class API platform.

Manfaat Enum

  • Fitur bahasa idiomatik Java, Kotlin
  • Mengaktifkan penggunaan pernyataan when switch
      yang lengkap
    • Catatan - nilai tidak boleh berubah dari waktu ke waktu, lihat daftar sebelumnya
  • Penamaan yang tercakup dengan jelas dan dapat ditemukan
  • Memungkinkan verifikasi waktu kompilasi
    • Misalnya, pernyataan when di Kotlin yang menampilkan nilai
  • Adalah class berfungsi yang dapat mengimplementasikan antarmuka, memiliki helper statis, mengekspos metode anggota atau ekstensi, dan mengekspos kolom.

Mengikuti hierarki pelapisan paket Android

Hierarki paket android.* memiliki pengurutan implisit, di mana paket tingkat yang lebih rendah tidak dapat bergantung pada paket tingkat yang lebih tinggi.

Hindari merujuk ke Google, perusahaan lain, dan produknya

Platform Android adalah project open source dan bertujuan untuk tidak terikat dengan vendor. API harus bersifat umum dan dapat digunakan secara setara oleh integrator sistem atau aplikasi dengan izin yang diperlukan.

Implementasi Parcelable harus final

Class Parcelable yang ditentukan oleh platform selalu dimuat dari framework.jar, sehingga tidak valid bagi aplikasi untuk mencoba mengganti implementasi Parcelable.

Jika aplikasi pengirim memperluas Parcelable, aplikasi penerima tidak akan memiliki implementasi kustom pengirim untuk diekstrak. Catatan tentang kompatibilitas mundur: jika kelas Anda secara historis bukan final, tetapi tidak memiliki konstruktor yang tersedia secara publik, Anda tetap dapat menandainya sebagai final.

Metode yang memanggil ke dalam proses sistem harus melempar ulang RemoteException sebagai RuntimeException

RemoteException biasanya ditampilkan oleh AIDL internal, dan menunjukkan bahwa proses sistem telah berhenti, atau aplikasi mencoba mengirim terlalu banyak data. Dalam kedua kasus tersebut, API publik harus melempar ulang sebagai RuntimeException untuk mencegah aplikasi mempertahankan keputusan keamanan atau kebijakan.

Jika Anda tahu bahwa sisi lain panggilan Binder adalah proses sistem, kode standar ini adalah praktik terbaik:

try {
    ...
} catch (RemoteException e) {
    throw e.rethrowFromSystemServer();
}

Menampilkan pengecualian tertentu untuk perubahan API

Perilaku API publik dapat berubah di berbagai level API dan menyebabkan aplikasi error (misalnya untuk menerapkan kebijakan keamanan baru).

Jika API perlu menampilkan pengecualian untuk permintaan yang sebelumnya valid, tampilkan pengecualian spesifik baru, bukan pengecualian umum. Misalnya, ExportedFlagRequired bukan SecurityException (dan ExportedFlagRequired dapat memperluas SecurityException).

Tindakan ini akan membantu developer dan alat mendeteksi perubahan perilaku API.

Mengimplementasikan konstruktor salinan, bukan clone

Penggunaan metode clone() Java sangat tidak dianjurkan karena kurangnya kontrak API yang disediakan oleh class Object dan kesulitan yang melekat dalam memperluas class yang menggunakan clone(). Sebagai gantinya, gunakan konstruktor salinan yang mengambil objek dengan jenis yang sama.

/**
 * Constructs a shallow copy of {@code other}.
 */
public Foo(Foo other)

Class yang mengandalkan Builder untuk konstruksi harus mempertimbangkan untuk menambahkan konstruktor salinan Builder untuk memungkinkan modifikasi pada salinan.

public class Foo {
    public static final class Builder {
        /**
         * Constructs a Foo builder using data from {@code other}.
         */
        public Builder(Foo other)

Menggunakan ParcelFileDescriptor melalui FileDescriptor

Objek java.io.FileDescriptor memiliki definisi kepemilikan yang buruk, yang dapat menyebabkan bug use-after-close yang tidak jelas. Sebagai gantinya, API harus menampilkan atau menerima instance ParcelFileDescriptor. Kode lama dapat mengonversi antara PFD dan FD jika diperlukan menggunakan dup() atau getFileDescriptor().

Hindari penggunaan nilai numerik berukuran ganjil

Hindari penggunaan nilai short atau byte secara langsung, karena nilai tersebut sering kali membatasi cara Anda mengembangkan API pada masa mendatang.

Hindari penggunaan BitSet

java.util.BitSet sangat bagus untuk implementasi, tetapi tidak untuk API publik. Mutable, memerlukan alokasi untuk panggilan metode frekuensi tinggi, dan tidak memberikan makna semantik untuk setiap bit yang diwakilinya.

Untuk skenario berperforma tinggi, gunakan int atau long dengan @IntDef. Untuk skenario performa rendah, pertimbangkan Set<EnumType>. Untuk data biner mentah, gunakan byte[].

Lebih memilih android.net.Uri

android.net.Uri adalah enkapsulasi pilihan untuk URI di Android API.

Hindari java.net.URI, karena terlalu ketat dalam mengurai URI, dan jangan pernah menggunakan java.net.URL, karena definisi kesetaraannya sangat rusak.

Menyembunyikan anotasi yang ditandai sebagai @IntDef, @LongDef, atau @StringDef

Anotasi yang ditandai sebagai @IntDef, @LongDef, atau @StringDef menunjukkan serangkaian konstanta valid yang dapat diteruskan ke API. Namun, saat diekspor sebagai API itu sendiri, compiler akan menyisipkan konstanta dan hanya nilai (yang sekarang tidak berguna) yang tetap ada di stub API anotasi (untuk platform) atau JAR (untuk library).

Oleh karena itu, penggunaan anotasi ini harus ditandai dengan anotasi @hide docs di platform atau anotasi kode @RestrictTo.Scope.LIBRARY) di library. Mereka harus ditandai @Retention(RetentionPolicy.SOURCE) dalam kedua kasus untuk mencegahnya muncul di stub API atau JAR.

@RestrictTo(RestrictTo.Scope.LIBRARY)
@Retention(RetentionPolicy.SOURCE)
@IntDef({
  STREAM_TYPE_FULL_IMAGE_DATA,
  STREAM_TYPE_EXIF_DATA_ONLY,
})
public @interface ExifStreamType {}

Saat membangun SDK platform dan AAR library, alat akan mengekstrak anotasi dan menggabungkannya secara terpisah dari sumber yang dikompilasi. Android Studio membaca format gabungan ini dan menerapkan definisi jenis.

Jangan menambahkan kunci penyedia setelan baru

Jangan mengekspos kunci baru dari Settings.Global, Settings.System, atau Settings.Secure.

Sebagai gantinya, tambahkan Java API getter dan setter yang tepat di class yang relevan, yang biasanya merupakan class "pengelola". Tambahkan mekanisme pemroses atau siaran untuk memberi tahu klien tentang perubahan sesuai kebutuhan.

Setelan SettingsProvider memiliki sejumlah masalah dibandingkan dengan getter/setter:

  • Tidak ada keamanan jenis.
  • Tidak ada cara terpadu untuk memberikan nilai default.
  • Tidak ada cara yang tepat untuk menyesuaikan izin.
    • Misalnya, Anda tidak dapat melindungi setelan Anda dengan izin khusus.
  • Tidak ada cara yang tepat untuk menambahkan logika kustom dengan benar.
    • Misalnya, Anda tidak dapat mengubah nilai setelan A bergantung pada nilai setelan B.

Contoh: Settings.Secure.LOCATION_MODE telah ada sejak lama, tetapi tim lokasi telah menghentikan penggunaannya untuk API Java yang tepat LocationManager.isLocationEnabled() dan siaran MODE_CHANGED_ACTION, yang memberi tim lebih banyak fleksibilitas, dan semantik API kini jauh lebih jelas.

Jangan memperluas Activity dan AsyncTask

AsyncTask adalah detail implementasi. Sebagai gantinya, tampilkan pendengar atau, di androidx, API ListenableFuture.

Subkelas Activity tidak dapat disusun. Memperluas aktivitas untuk fitur Anda membuatnya tidak kompatibel dengan fitur lain yang mengharuskan pengguna melakukan hal yang sama. Sebagai gantinya, andalkan komposisi dengan menggunakan alat seperti LifecycleObserver.

Menggunakan getUser() Konteks

Class yang terikat ke Context, seperti apa pun yang ditampilkan dari Context.getSystemService() harus menggunakan pengguna yang terikat ke Context, bukan mengekspos anggota yang menargetkan pengguna tertentu.

class FooManager {
  Context mContext;

  void fooBar() {
    mIFooBar.fooBarForUser(mContext.getUser());
  }
}
class FooManager {
  Context mContext;

  Foobar getFoobar() {
    // Bad: doesn't appy mContext.getUserId().
    mIFooBar.fooBarForUser(Process.myUserHandle());
  }

  Foobar getFoobar() {
    // Also bad: doesn't appy mContext.getUserId().
    mIFooBar.fooBar();
  }

  Foobar getFoobarForUser(UserHandle user) {
    mIFooBar.fooBarForUser(user);
  }
}

Pengecualian: Metode dapat menerima argumen pengguna jika menerima nilai yang tidak mewakili satu pengguna, seperti UserHandle.ALL.

Gunakan UserHandle, bukan int biasa

UserHandle lebih disukai untuk memberikan keamanan jenis dan menghindari penggabungan ID pengguna dengan uid.

Foobar getFoobarForUser(UserHandle user);
Foobar getFoobarForUser(int userId);

Jika tidak dapat dihindari, int yang merepresentasikan ID pengguna harus diberi anotasi dengan @UserIdInt.

Foobar getFoobarForUser(@UserIdInt int user);

Lebih memilih pemroses atau callback untuk menyiarkan intent

Intent siaran sangat berguna, tetapi telah menghasilkan perilaku yang muncul yang dapat berdampak negatif pada kesehatan sistem, sehingga intent siaran baru harus ditambahkan dengan bijak.

Berikut beberapa masalah spesifik yang membuat kami tidak menganjurkan pengenalan maksud siaran baru:

  • Saat mengirim siaran tanpa tanda FLAG_RECEIVER_REGISTERED_ONLY, aplikasi akan dipaksa dimulai jika belum berjalan. Meskipun terkadang hal ini merupakan hasil yang diinginkan, hal ini dapat menyebabkan puluhan aplikasi mengalami efek berlebihan, yang berdampak negatif pada kesehatan sistem. Sebaiknya gunakan strategi alternatif, seperti JobScheduler, untuk mengoordinasikan dengan lebih baik kapan berbagai prasyarat terpenuhi.

  • Saat mengirim siaran, ada sedikit kemampuan untuk memfilter atau menyesuaikan konten yang dikirim ke aplikasi. Hal ini membuat respons terhadap masalah privasi di masa mendatang menjadi sulit atau tidak mungkin, atau memperkenalkan perubahan perilaku berdasarkan SDK target aplikasi penerima.

  • Karena antrean siaran adalah resource bersama, antrean tersebut dapat kelebihan beban dan mungkin tidak menghasilkan pengiriman acara Anda tepat waktu. Kami telah mengamati beberapa antrean siaran di luar sana yang memiliki latensi end-to-end 10 menit atau lebih.

Oleh karena itu, kami mendorong fitur baru untuk mempertimbangkan penggunaan pemroses atau callback atau fasilitas lain seperti JobScheduler, bukan maksud siaran.

Dalam kasus ketika maksud siaran masih menjadi desain yang ideal, berikut beberapa praktik terbaik yang harus dipertimbangkan:

  • Jika memungkinkan, gunakan Intent.FLAG_RECEIVER_REGISTERED_ONLY untuk membatasi siaran ke aplikasi yang sudah berjalan. Misalnya, ACTION_SCREEN_ON menggunakan desain ini untuk menghindari mengaktifkan aplikasi.
  • Jika memungkinkan, gunakan Intent.setPackage() atau Intent.setComponent() untuk menargetkan siaran ke aplikasi tertentu yang diminati. Misalnya, ACTION_MEDIA_BUTTON menggunakan desain ini untuk berfokus pada penanganan aplikasi saat ini kontrol pemutaran.
  • Jika memungkinkan, tentukan siaran Anda sebagai <protected-broadcast> untuk mencegah aplikasi berbahaya meniru OS.

Intent dalam layanan developer yang terikat sistem

Layanan yang dimaksudkan untuk diperluas oleh developer dan diikat oleh sistem, misalnya layanan abstrak seperti NotificationListenerService, dapat merespons tindakan Intent dari sistem. Layanan tersebut harus memenuhi kriteria berikut:

  1. Tentukan konstanta string SERVICE_INTERFACE pada class yang berisi nama class layanan yang memenuhi syarat sepenuhnya. Konstanta ini harus diberi anotasi dengan @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION).
  2. Dokumen di kelas yang harus ditambahkan <intent-filter> oleh developer ke AndroidManifest.xml mereka untuk menerima Maksud dari platform.
  3. Sangat disarankan untuk menambahkan izin tingkat sistem guna mencegah aplikasi berbahaya mengirim Intent ke layanan developer.

Interop Kotlin-Java

Lihat panduan interoperabilitas Kotlin-Java Android resmi untuk mengetahui daftar lengkap pedoman. Pedoman yang dipilih telah disalin ke panduan ini untuk meningkatkan visibilitas.

Visibilitas API

Beberapa API Kotlin, seperti suspend fun, tidak dimaksudkan untuk digunakan oleh developer Java; namun, jangan mencoba mengontrol visibilitas khusus bahasa menggunakan @JvmSynthetic karena memiliki efek samping pada cara API ditampilkan di debugger yang membuat proses debug menjadi lebih sulit.

Lihat Panduan interop Kotlin-Java atau Panduan asinkron untuk mendapatkan panduan khusus.

Objek pendamping

Kotlin menggunakan companion object untuk mengekspos anggota statis. Dalam beberapa kasus, ini akan muncul dari Java di class dalam bernama Companion, bukan di class yang berisi. Class Companion dapat ditampilkan sebagai class kosong dalam file teks API -- hal ini berfungsi sebagaimana mestinya.

Untuk memaksimalkan kompatibilitas dengan Java, anotasi kolom non-konstan objek pendamping dengan @JvmField dan fungsi publik dengan @JvmStatic untuk mengeksposnya secara langsung pada class yang memuatnya.

companion object {
  @JvmField val BIG_INTEGER_ONE = BigInteger.ONE
  @JvmStatic fun fromPointF(pointf: PointF) {
    /* ... */
  }
}

Perkembangan API platform Android

Bagian ini menjelaskan kebijakan terkait jenis perubahan yang dapat Anda lakukan pada API Android yang ada dan cara menerapkan perubahan tersebut untuk memaksimalkan kompatibilitas dengan aplikasi dan codebase yang ada.

Perubahan yang dapat menyebabkan gangguan biner

Hindari perubahan yang dapat menyebabkan gangguan pada biner di permukaan API publik yang telah difinalisasi. Jenis perubahan ini umumnya menimbulkan error saat menjalankan make update-api, tetapi mungkin ada kasus ekstrem yang tidak terdeteksi oleh pemeriksaan API Metalava. Jika ragu, lihat panduan Evolving Java-based APIs dari Eclipse Foundation untuk mengetahui penjelasan mendetail tentang jenis perubahan API yang kompatibel di Java. Perubahan yang merusak biner dalam API tersembunyi (misalnya, sistem) harus mengikuti siklus penghentian penggunaan/penggantian.

Perubahan yang dapat menyebabkan gangguan pada sumber

Kami tidak menganjurkan perubahan yang merusak sumber meskipun tidak merusak biner. Salah satu contoh perubahan yang kompatibel biner tetapi merusak sumber adalah menambahkan generik ke class yang ada, yang kompatibel biner tetapi dapat menimbulkan error kompilasi karena pewarisan atau referensi yang ambigu. Perubahan yang menyebabkan gangguan pada sumber tidak akan menimbulkan error saat menjalankan make update-api, jadi Anda harus berhati-hati untuk memahami dampak perubahan pada tanda tangan API yang ada.

Dalam beberapa kasus, perubahan yang merusak sumber menjadi diperlukan untuk meningkatkan pengalaman developer atau kebenaran kode. Misalnya, menambahkan anotasi nullability ke sumber Java meningkatkan interoperabilitas dengan kode Kotlin dan mengurangi kemungkinan terjadinya error, tetapi sering kali memerlukan perubahan -- terkadang perubahan signifikan -- pada kode sumber.

Perubahan pada API pribadi

Anda dapat mengubah API yang diberi anotasi dengan @TestApi kapan saja.

Anda harus mempertahankan API yang dianotasi dengan @SystemApi selama tiga tahun. Anda harus menghapus atau memfaktorkan ulang API sistem sesuai jadwal berikut:

  • API y - Ditambahkan
  • API y+1 - Penghentian penggunaan
    • Tandai kode dengan @Deprecated.
    • Tambahkan penggantian, dan tautkan ke penggantian di Javadoc untuk kode yang tidak digunakan lagi menggunakan anotasi dokumen @deprecated.
    • Selama siklus pengembangan, ajukan bug file terhadap pengguna internal yang memberi tahu mereka bahwa API tidak digunakan lagi. Hal ini membantu memvalidasi bahwa API pengganti sudah memadai.
  • API y+2 - Penghapusan ringan
    • Tandai kode dengan @removed.
    • Secara opsional, lempar atau no-op untuk aplikasi yang menargetkan level SDK saat ini untuk rilis.
  • API y+3 - Penghapusan paksa
    • Hapus kode sepenuhnya dari pohon sumber.

Penghentian penggunaan

Kami menganggap penghentian penggunaan sebagai perubahan API, dan hal ini dapat terjadi dalam rilis utama (seperti huruf). Gunakan anotasi sumber @Deprecated dan anotasi dokumen @deprecated <summary> bersama-sama saat menghentikan penggunaan API. Ringkasan Anda harus mencakup strategi migrasi. Strategi ini dapat ditautkan ke API pengganti atau menjelaskan alasan Anda tidak boleh menggunakan API:

/**
 * Simple version of ...
 *
 * @deprecated Use the {@link androidx.fragment.app.DialogFragment}
 *             class with {@link androidx.fragment.app.FragmentManager}
 *             instead.
 */
@Deprecated
public final void showDialog(int id)

Anda juga harus menghentikan penggunaan API yang ditentukan dalam XML dan diekspos di Java, termasuk atribut dan properti yang dapat distilisasi yang diekspos di class android.R, dengan ringkasan:

<!-- Attribute whether the accessibility service ...
     {@deprecated Not used by the framework}
 -->
<attr name="canRequestEnhancedWebAccessibility" format="boolean" />

Kapan API dihentikan penggunaannya

Penghentian penggunaan paling berguna untuk mencegah penggunaan API dalam kode baru.

Kami juga mewajibkan Anda menandai API sebagai @deprecated sebelum API tersebut @removed, tetapi hal ini tidak memberikan motivasi yang kuat bagi developer untuk bermigrasi dari API yang sudah mereka gunakan.

Sebelum menghentikan penggunaan API, pertimbangkan dampaknya terhadap developer. Dampak penghentian penggunaan API meliputi:

  • javac akan memunculkan peringatan selama kompilasi.
    • Peringatan penghentian penggunaan tidak dapat dihentikan secara global atau ditetapkan sebagai dasar, sehingga developer yang menggunakan -Werror harus memperbaiki atau menghentikan penggunaan setiap penggunaan API yang tidak digunakan lagi satu per satu sebelum mereka dapat memperbarui versi SDK kompilasi.
    • Peringatan penghentian penggunaan pada impor class yang tidak digunakan lagi tidak dapat dihilangkan. Akibatnya, developer harus menyisipkan nama class yang sepenuhnya memenuhi syarat untuk setiap penggunaan class yang tidak digunakan lagi sebelum mereka dapat mengupdate versi SDK kompilasi.
  • Dokumentasi tentang d.android.com menampilkan pemberitahuan penghentian.
  • IDE seperti Android Studio menampilkan peringatan di situs penggunaan API.
  • IDE dapat menurunkan peringkat atau menyembunyikan API dari pelengkapan otomatis.

Akibatnya, penghentian penggunaan API dapat membuat developer yang paling memperhatikan kualitas kode (yang menggunakan -Werror) enggan mengadopsi SDK baru. Developer yang tidak khawatir dengan peringatan dalam kode yang ada cenderung mengabaikan penghentian penggunaan sama sekali.

SDK yang memperkenalkan sejumlah besar penghentian penggunaan akan memperburuk kedua kasus ini.

Oleh karena itu, sebaiknya hentikan penggunaan API hanya dalam kasus berikut:

  • Kami berencana untuk @remove API ini dalam rilis mendatang.
  • Penggunaan API menyebabkan perilaku yang salah atau tidak terdefinisi yang tidak dapat kami perbaiki tanpa merusak kompatibilitas.

Saat Anda menghentikan penggunaan API dan menggantinya dengan API baru, kami sangat merekomendasikan penambahan API kompatibilitas yang sesuai ke library Jetpack seperti androidx.core untuk menyederhanakan dukungan bagi perangkat lama dan baru.

Kami tidak merekomendasikan penghentian penggunaan API yang berfungsi sebagaimana mestinya dalam rilis saat ini dan mendatang:

/**
 * ...
 * @deprecated Use {@link #doThing(int, Bundle)} instead.
 */
@Deprecated
public void doThing(int action) {
  ...
}

public void doThing(int action, @Nullable Bundle extras) {
  ...
}

Penghentian penggunaan sesuai dalam kasus saat API tidak dapat lagi mempertahankan perilaku yang didokumentasikan:

/**
 * ...
 * @deprecated No longer displayed in the status bar as of API 21.
 */
@Deprecated
public RemoteViews tickerView;

Perubahan pada API yang tidak digunakan lagi

Anda harus mempertahankan perilaku API yang tidak digunakan lagi. Artinya, penerapan pengujian harus tetap sama, dan pengujian harus terus berhasil setelah Anda menghentikan penggunaan API. Jika API tidak memiliki pengujian, Anda harus menambahkan pengujian.

Jangan memperluas platform API yang tidak digunakan lagi dalam rilis mendatang. Anda dapat menambahkan anotasi kebenaran lint (misalnya, @Nullable) ke API yang sudah tidak digunakan lagi, tetapi tidak boleh menambahkan API baru.

Jangan menambahkan API baru sebagai API yang tidak digunakan lagi. Jika ada API yang ditambahkan dan kemudian dihentikan penggunaannya dalam siklus pra-rilis (sehingga awalnya akan masuk ke permukaan API publik sebagai tidak digunakan lagi), Anda harus menghapusnya sebelum menyelesaikan API.

Penghapusan sementara

Penghapusan sementara adalah perubahan yang dapat menyebabkan gangguan pada sumber, dan Anda harus menghindarinya di API publik kecuali jika Dewan API menyetujuinya secara eksplisit. Untuk API sistem, Anda harus menghentikan penggunaan API selama durasi rilis utama sebelum penghapusan sementara. Hapus semua referensi dokumen ke API dan gunakan anotasi dokumen @removed <summary> saat menghapus API secara halus. Ringkasan Anda harus menyertakan alasan penghapusan dan dapat menyertakan strategi migrasi, seperti yang kami jelaskan dalam Penghentian Penggunaan.

Perilaku API yang dihapus sementara dapat dipertahankan seperti apa adanya, tetapi yang lebih penting harus dipertahankan agar pemanggil yang ada tidak mengalami error saat memanggil API. Dalam beberapa kasus, hal itu mungkin berarti mempertahankan perilaku.

Cakupan pengujian harus dipertahankan, tetapi konten pengujian mungkin perlu diubah untuk mengakomodasi perubahan perilaku. Pengujian tetap harus memvalidasi bahwa penelepon yang sudah ada tidak mengalami error saat runtime. Anda dapat mempertahankan perilaku API yang dihapus sementara seperti apa adanya, tetapi yang lebih penting, Anda harus mempertahankannya agar pemanggil yang ada tidak mengalami error saat memanggil API. Dalam beberapa kasus, hal itu mungkin berarti mempertahankan perilaku.

Anda harus mempertahankan cakupan pengujian, tetapi konten pengujian mungkin perlu diubah untuk mengakomodasi perubahan perilaku. Pengujian tetap harus memvalidasi bahwa penelepon yang sudah ada tidak mengalami error saat runtime.

Di tingkat teknis, kita menghapus API dari JAR stub SDK dan classpath waktu kompilasi menggunakan anotasi Javadoc @remove, tetapi API tersebut masih ada di classpath waktu proses -- mirip dengan API @hide:

/**
 * Ringer volume. This is ...
 *
 * @removed Not functional since API 2.
 */
public static final String VOLUME_RING = ...

Dari perspektif developer aplikasi, API tidak lagi muncul di pelengkapan otomatis dan kode sumber yang mereferensikan API tidak akan dikompilasi jika compileSdk sama dengan atau lebih baru dari SDK tempat API dihapus; namun, kode sumber akan terus dikompilasi dengan berhasil terhadap SDK dan biner sebelumnya yang mereferensikan API akan terus berfungsi.

Kategori API tertentu tidak boleh dihapus sementara. Anda tidak boleh menghapus kategori API tertentu secara sementara.

Metode abstrak

Anda tidak boleh menghapus secara halus metode abstrak pada class yang mungkin diperluas oleh developer. Dengan demikian, developer tidak dapat berhasil memperluas kelas di semua tingkat SDK.

Dalam kasus yang jarang terjadi ketika developer tidak pernah dan tidak akan dapat memperluas class, Anda tetap dapat menghapus metode abstrak secara halus.

Penghapusan permanen

Penghapusan paksa adalah perubahan yang merusak biner dan tidak boleh terjadi di API publik.

Anotasi yang tidak direkomendasikan

Kami menggunakan anotasi @Discouraged untuk menunjukkan bahwa kami tidak merekomendasikan API dalam sebagian besar (>95%) kasus. API yang tidak disarankan berbeda dengan API yang tidak digunakan lagi karena ada kasus penggunaan penting yang sempit yang mencegah penghentian penggunaan. Saat menandai API sebagai tidak disarankan, Anda harus memberikan penjelasan dan solusi alternatif:

@Discouraged(message = "Use of this function is discouraged because resource
                        reflection makes it harder to perform build
                        optimizations and compile-time verification of code. It
                        is much more efficient to retrieve resources by
                        identifier (such as `R.foo.bar`) than by name (such as
                        `getIdentifier()`)")
public int getIdentifier(String name, String defType, String defPackage) {
    return mResourcesImpl.getIdentifier(name, defType, defPackage);
}

Anda tidak boleh menambahkan API baru karena tidak disarankan.

Perubahan pada perilaku API yang ada

Dalam beberapa kasus, Anda mungkin ingin mengubah perilaku penerapan API yang ada. Misalnya, di Android 7.0, kami meningkatkan DropBoxManager untuk mengomunikasikan dengan jelas saat developer mencoba memposting peristiwa yang terlalu besar untuk dikirim melalui Binder.

Namun, untuk menghindari masalah pada aplikasi yang sudah ada, sebaiknya pertahankan perilaku yang aman untuk aplikasi lama. Sebelumnya, kami melindungi perubahan perilaku ini berdasarkan ApplicationInfo.targetSdkVersion aplikasi, tetapi baru-baru ini kami bermigrasi untuk mewajibkan penggunaan Framework Kompatibilitas Aplikasi. Berikut contoh cara menerapkan perubahan perilaku menggunakan framework baru ini:

import android.app.compat.CompatChanges;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledSince;

public class MyClass {
  @ChangeId
  // This means the change will be enabled for target SDK R and higher.
  @EnabledSince(targetSdkVersion=android.os.Build.VERSION_CODES.R)
  // Use a bug number as the value, provide extra detail in the bug.
  // FOO_NOW_DOES_X will be the change name, and 123456789 the change ID.
  static final long FOO_NOW_DOES_X = 123456789L;

  public void doFoo() {
    if (CompatChanges.isChangeEnabled(FOO_NOW_DOES_X)) {
      // do the new thing
    } else {
      // do the old thing
    }
  }
}

Dengan desain Framework Kompatibilitas Aplikasi ini, developer dapat menonaktifkan sementara perubahan perilaku tertentu selama rilis pratinjau dan beta sebagai bagian dari proses debug aplikasi mereka, alih-alih memaksa mereka menyesuaikan diri dengan puluhan perubahan perilaku secara bersamaan.

Kompatibilitas dengan versi baru

Kompatibilitas ke depan adalah karakteristik desain yang memungkinkan sistem menerima input yang ditujukan untuk versi yang lebih baru. Dalam kasus desain API, Anda harus memberikan perhatian khusus pada desain awal serta perubahan di masa mendatang karena developer berharap untuk menulis kode sekali, mengujinya sekali, dan menjalankannya di mana saja tanpa masalah.

Berikut adalah penyebab masalah kompatibilitas ke depan yang paling umum di Android:

  • Menambahkan konstanta baru ke set (seperti @IntDef atau enum) yang sebelumnya dianggap lengkap (misalnya, jika switch memiliki default yang memunculkan pengecualian).
  • Menambahkan dukungan untuk fitur yang tidak tercakup secara langsung di permukaan API (misalnya, dukungan untuk menetapkan resource jenis ColorStateList di XML yang sebelumnya hanya mendukung resource <color>).
  • Melonggarkan batasan pada pemeriksaan run-time, misalnya menghapus pemeriksaan requireNotNull() yang ada pada versi yang lebih rendah.

Dalam semua kasus ini, developer baru mengetahui bahwa ada yang salah saat runtime. Lebih buruk lagi, mereka mungkin mengetahuinya sebagai akibat dari laporan error dari perangkat lama di lapangan.

Selain itu, semua kasus ini adalah perubahan API yang secara teknis valid. Perubahan ini tidak merusak kompatibilitas biner atau sumber, dan lint API tidak akan mendeteksi masalah ini.

Oleh karena itu, desainer API harus berhati-hati saat mengubah class yang ada. Ajukan pertanyaan, "Apakah perubahan ini akan menyebabkan kode yang ditulis dan diuji hanya terhadap platform versi terbaru gagal pada versi yang lebih rendah?"

Skema XML

Jika skema XML berfungsi sebagai antarmuka yang stabil antar-komponen, skema tersebut harus ditentukan secara eksplisit dan harus berkembang dengan cara yang kompatibel mundur, mirip dengan API Android lainnya. Misalnya, struktur elemen dan atribut XML harus dipertahankan seperti cara metode dan variabel dipertahankan di platform Android API lainnya.

Penghentian penggunaan XML

Jika ingin menghentikan penggunaan elemen atau atribut XML, Anda dapat menambahkan penanda xs:annotation, tetapi Anda harus terus mendukung file XML yang ada dengan mengikuti siklus proses evolusi @SystemApi yang umum.

<xs:element name="foo">
    <xs:complexType>
        <xs:sequence>
            <xs:element name="name" type="xs:string">
                <xs:annotation name="Deprecated"/>
            </xs:element>
        </xs:sequence>
    </xs:complexType>
</xs:element>

Jenis elemen harus dipertahankan

Skema mendukung elemen sequence, elemen choice, dan elemen all sebagai elemen turunan dari elemen complexType. Namun, elemen turunan ini berbeda dalam jumlah dan urutan elemen turunannya, sehingga mengubah jenis yang ada akan menjadi perubahan yang tidak kompatibel.

Jika Anda ingin mengubah jenis yang ada, praktik terbaiknya adalah menghentikan penggunaan jenis lama dan memperkenalkan jenis baru untuk menggantikannya.

<!-- Original "sequence" value -->
<xs:element name="foo">
    <xs:complexType>
        <xs:sequence>
            <xs:element name="name" type="xs:string">
                <xs:annotation name="Deprecated"/>
            </xs:element>
        </xs:sequence>
    </xs:complexType>
</xs:element>

<!-- New "choice" value -->
<xs:element name="fooChoice">
    <xs:complexType>
        <xs:choice>
            <xs:element name="name" type="xs:string"/>
        </xs:choice>
    </xs:complexType>
</xs:element>

Pola khusus mainline

Mainline adalah project untuk mengizinkan update subsistem ("modul mainline") Android OS satu per satu, bukan mengupdate seluruh image sistem.

Modul utama harus "dibatalkan penggabungannya" dari platform inti, yang berarti semua interaksi antara setiap modul dan seluruh dunia harus dilakukan menggunakan API formal (publik atau sistem).

Ada pola desain tertentu yang harus diikuti oleh modul utama. Bagian ini menjelaskannya.

Pola <Module>FrameworkInitializer

Jika modul utama perlu mengekspos class @SystemService (misalnya, JobScheduler), gunakan pola berikut:

  • Ekspos class <YourModule>FrameworkInitializer dari modul Anda. Class ini harus berada di $BOOTCLASSPATH. Contoh: 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, orang akan menggunakan ServiceManager, tetapi modul utama tidak dapat menggunakannya karena tersembunyi. Class ini disembunyikan karena modul utama tidak boleh mendaftarkan atau merujuk ke objek binder layanan sistem yang diekspos oleh platform statis atau oleh modul lain.

Modul utama dapat menggunakan pola berikut untuk dapat mendaftarkan dan mendapatkan referensi ke layanan binder yang diimplementasikan di dalam modul.

  • Buat class <YourModule>ServiceManager, dengan mengikuti desain 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-nya.
    • Metode getter publik yang menampilkan instance ServiceRegisterer untuk nama tertentu. Jika Anda memiliki satu objek binder, Anda memerlukan satu metode pengambilan. Jika Anda memiliki dua, Anda memerlukan dua getter.
    • Di ActivityThread.initializeMainlineModules(), buat instance class ini, dan teruskan ke metode statis yang diekspos oleh modul Anda. Biasanya, Anda menambahkan API @SystemApi(client = MODULE_LIBRARIES) statis di class FrameworkInitializer yang menerimanya.

Pola ini akan mencegah modul utama lainnya mengakses API ini karena tidak ada cara bagi modul lain untuk mendapatkan instance <YourModule>ServiceManager, meskipun API get() dan register() terlihat olehnya.

Berikut cara telepon mendapatkan referensi ke layanan telepon: link penelusuran kode.

Jika Anda menerapkan objek binder layanan dalam kode native, Anda menggunakan API native AServiceManager. API ini sesuai dengan ServiceManager Java API, tetapi API native-nya diekspos langsung ke modul utama. Jangan menggunakannya untuk mendaftarkan atau merujuk ke objek binder yang tidak dimiliki oleh modul Anda. Jika Anda mengekspos objek binder dari native, <YourModule>ServiceManager.ServiceRegisterer Anda tidak memerlukan metode register().

Definisi izin dalam modul Mainline

Modul utama yang berisi APK dapat menentukan izin (kustom) dalam AndroidManifest.xml APK-nya dengan cara yang sama seperti APK biasa.

Jika izin yang ditentukan hanya digunakan secara internal dalam modul, nama izinnya harus diberi awalan dengan nama paket APK, misalnya:

<permission
    android:name="com.android.permissioncontroller.permission.MANAGE_ROLES_FROM_CONTROLLER"
    android:protectionLevel="signature" />

Jika izin yang ditentukan akan diberikan sebagai bagian dari API platform yang dapat diupdate ke aplikasi lain, nama izinnya harus diawali dengan "android.permission." (seperti izin platform statis) ditambah nama paket modul, untuk menandakan bahwa API tersebut adalah API platform dari modul sekaligus menghindari konflik penamaan, misalnya:

<permission
    android:name="android.permission.health.READ_ACTIVE_CALORIES_BURNED"
    android:label="@string/active_calories_burned_read_content_description"
    android:protectionLevel="dangerous"
    android:permissionGroup="android.permission-group.HEALTH" />

Kemudian, modul dapat mengekspos nama izin ini sebagai konstanta API di permukaan API-nya, misalnya HealthPermissions.READ_ACTIVE_CALORIES_BURNED.