หน้านี้มีไว้เพื่อเป็นแนวทางให้นักพัฒนาแอปเข้าใจหลักการทั่วไปที่สภา API บังคับใช้ในการตรวจสอบ API
นอกจากการปฏิบัติตามหลักเกณฑ์เหล่านี้เมื่อเขียน API แล้ว นักพัฒนาซอฟต์แวร์ควรเรียกใช้เครื่องมือ API Lint ซึ่งจะเข้ารหัสกฎเหล่านี้จำนวนมากในการตรวจสอบที่ทำงานกับ API
โปรดคิดว่านี่เป็นคำแนะนำเกี่ยวกับกฎที่เครื่องมือ Lint นั้นปฏิบัติตาม รวมถึงคำแนะนำทั่วไปเกี่ยวกับกฎที่ไม่สามารถเขียนเป็นโค้ดในเครื่องมือดังกล่าวได้อย่างแม่นยำ
เครื่องมือ Lint ของ API
API Lint ผสานรวมอยู่ในเครื่องมือการวิเคราะห์แบบคงที่ของ Metalava และทำงานโดยอัตโนมัติระหว่างการตรวจสอบใน CI คุณสามารถเรียกใช้ด้วยตนเองจากจุดชำระเงินของแพลตฟอร์มในเครื่องโดยใช้ m
checkapi
หรือจุดชำระเงิน AndroidX ในเครื่องโดยใช้ ./gradlew :path:to:project:checkApi
กฎ API
แพลตฟอร์ม Android และไลบรารี Jetpack จำนวนมากมีอยู่แล้วก่อนที่เราจะจัดทำชุดหลักเกณฑ์นี้ขึ้น และนโยบายที่ระบุไว้ในส่วนถัดไปของหน้านี้จะพัฒนาอย่างต่อเนื่องเพื่อตอบสนองความต้องการของระบบนิเวศ Android
ดังนั้น API บางรายการที่มีอยู่จึงอาจไม่เป็นไปตามหลักเกณฑ์ ในกรณีอื่นๆ การใช้ API ใหม่ให้สอดคล้องกับ API ที่มีอยู่แทนการปฏิบัติตามหลักเกณฑ์อย่างเคร่งครัดอาจมอบประสบการณ์การใช้งานที่ดีขึ้นให้แก่นักพัฒนาแอป
ใช้วิจารณญาณและติดต่อสภา API หากมีคำถามที่ตอบยากเกี่ยวกับ API ที่ต้องแก้ไขหรือหลักเกณฑ์ที่ต้องอัปเดต
ข้อมูลเบื้องต้นเกี่ยวกับ API
หมวดหมู่นี้เกี่ยวข้องกับแง่มุมหลักของ Android API
ต้องติดตั้งใช้งาน API ทั้งหมด
ไม่ว่ากลุ่มเป้าหมายของ API จะเป็นอย่างไร (เช่น สาธารณะหรือ @SystemApi
) คุณต้องติดตั้งใช้งานแพลตฟอร์ม API ทั้งหมดเมื่อผสานรวมหรือแสดงเป็น API อย่าผสานสแต็บ API เข้ากับการใช้งานที่จะนำมาใช้ในภายหลัง
แพลตฟอร์ม API ที่ไม่มีการใช้งานจะมีปัญหาหลายประการ ดังนี้
- ไม่มีการรับประกันว่าจะมีการแสดงพื้นผิวที่เหมาะสมหรือสมบูรณ์ จนกว่าไคลเอ็นต์จะทดสอบหรือใช้งาน API จะไม่มีวิธียืนยันได้ว่าไคลเอ็นต์มี API ที่เหมาะสมเพื่อใช้ฟีเจอร์
- คุณไม่สามารถทดสอบ API ที่ไม่มีการใช้งานในตัวอย่างสำหรับนักพัฒนาซอฟต์แวร์
- API ที่ไม่มีการใช้งานจะทดสอบใน CTS ไม่ได้
ต้องทดสอบ API ทั้งหมด
ซึ่งสอดคล้องกับข้อกำหนด CTS ของแพลตฟอร์ม นโยบาย AndroidX และแนวคิดทั่วไปที่ว่าต้องใช้ API
การทดสอบแพลตฟอร์ม API เป็นการรับประกันพื้นฐานว่าแพลตฟอร์ม API ใช้งานได้และเราจัดการกับ Use Case ที่คาดไว้แล้ว การทดสอบการมีอยู่นั้นไม่เพียงพอ คุณต้องทดสอบลักษณะการทํางานของ API ด้วย
การเปลี่ยนแปลงที่เพิ่ม API ใหม่ควรมีการทดสอบที่เกี่ยวข้องใน CL หรือหัวข้อ Gerrit เดียวกัน
นอกจากนี้ API ยังควรทดสอบได้ด้วย คุณควรตอบคำถามที่ว่า "นักพัฒนาแอปจะทดสอบโค้ดที่ใช้ API ของคุณได้อย่างไร" ได้
ต้องจัดทำเอกสารประกอบสำหรับ API ทั้งหมด
เอกสารประกอบเป็นส่วนสําคัญของความสามารถในการใช้งาน API แม้ว่าไวยากรณ์ของอินเทอร์เฟซ API อาจดูชัดเจน แต่ไคลเอ็นต์ใหม่จะไม่เข้าใจความหมาย ลักษณะการทำงาน หรือบริบทที่อยู่เบื้องหลัง API
API ที่สร้างขึ้นทั้งหมดต้องเป็นไปตามหลักเกณฑ์
API ที่เครื่องมือสร้างขึ้นต้องเป็นไปตามหลักเกณฑ์ API เดียวกันกับโค้ดที่เขียนด้วยตนเอง
เครื่องมือที่ไม่แนะนําสําหรับการสร้าง API
AutoValue
: ละเมิดหลักเกณฑ์ในลักษณะต่างๆ เช่น ไม่มีวิธีใช้คลาสค่าสุดท้ายหรือตัวสร้างขั้นสุดท้ายกับวิธีทํางานของ AutoValue
รูปแบบโค้ด
หมวดหมู่นี้เกี่ยวข้องกับรูปแบบโค้ดทั่วไปที่นักพัฒนาแอปควรใช้ โดยเฉพาะเมื่อเขียน API สาธารณะ
ปฏิบัติตามรูปแบบการเขียนโค้ดมาตรฐาน ยกเว้นที่ระบุไว้
บันทึกข้อกำหนดการเขียนโค้ด Android สำหรับผู้มีส่วนร่วมภายนอกไว้ที่นี่
https://source.android.com/source/code-style.html
โดยรวมแล้ว เรามักจะปฏิบัติตามรูปแบบการเขียนโค้ดมาตรฐานของ Java และ Kotlin
ตัวย่อไม่ควรเป็นตัวพิมพ์ใหญ่ในชื่อเมธอด
เช่น ชื่อเมธอดควรเป็น runCtsTests
ไม่ใช่ runCTSTests
ชื่อไม่ควรลงท้ายด้วย Impl
ซึ่งจะแสดงรายละเอียดการใช้งาน ดังนั้นโปรดหลีกเลี่ยง
ชั้นเรียน
ส่วนนี้จะอธิบายกฎเกี่ยวกับคลาส อินเทอร์เฟซ และการรับช่วง
รับช่วงคลาสสาธารณะใหม่จากคลาสฐานที่เหมาะสม
การสืบทอดจะแสดงองค์ประกอบ API ในคลาสย่อยของคุณซึ่งอาจไม่เหมาะสม
ตัวอย่างเช่น คลาสย่อยแบบสาธารณะใหม่ของ FrameLayout
จะมีลักษณะดังนี้ FrameLayout
พร้อมด้วยลักษณะการทำงานและองค์ประกอบ API ใหม่ หาก API ที่รับช่วงมานั้นไม่เหมาะสมกับ Use Case ของคุณ ให้รับช่วงมาจากคลาสที่สูงขึ้นในลําดับชั้น เช่น ViewGroup
หรือ View
หากต้องการลบล้างเมธอดจากคลาสพื้นฐานเพื่อยกเว้นข้อยกเว้น UnsupportedOperationException
ให้พิจารณาอีกครั้งว่าคุณกำลังใช้คลาสพื้นฐานใด
ใช้คลาสคอลเล็กชันพื้นฐาน
ไม่ว่าจะใช้คอลเล็กชันเป็นอาร์กิวเมนต์หรือแสดงผลเป็นค่า ให้ใช้คลาสพื้นฐานมากกว่าการใช้งานที่เฉพาะเจาะจงเสมอ (เช่น แสดงผล List<Foo>
แทน ArrayList<Foo>
)
ใช้คลาสฐานที่แสดงข้อจำกัดที่เหมาะสมสําหรับ API เช่น ใช้ List
สำหรับ API ที่คอลเล็กชันต้องจัดเรียง และ Set
สำหรับ API ที่คอลเล็กชันต้องประกอบด้วยองค์ประกอบที่ไม่ซ้ำกัน
ใน Kotlin ให้ใช้คอลเล็กชันแบบคงที่ ดูรายละเอียดเพิ่มเติมได้ในความสามารถในการเปลี่ยนแปลงของคอลเล็กชัน
คลาสนามธรรมกับอินเทอร์เฟซ
Java 8 เพิ่มการรองรับเมธอดอินเทอร์เฟซเริ่มต้น ซึ่งช่วยให้นักออกแบบ API เพิ่มเมธอดลงในอินเทอร์เฟซได้ในขณะที่ยังคงความเข้ากันได้ของไบนารี โค้ดแพลตฟอร์มและไลบรารี Jetpack ทั้งหมดควรกำหนดเป้าหมายเป็น Java 8 ขึ้นไป
ในกรณีที่การติดตั้งใช้งานเริ่มต้นเป็นแบบไม่มีสถานะ นักออกแบบ API ควรใช้อินเทอร์เฟซแทนคลาสนามธรรม กล่าวคือ เมธอดอินเทอร์เฟซเริ่มต้นสามารถติดตั้งใช้งานเป็นการเรียกเมธอดอินเทอร์เฟซอื่นๆ ได้
ในกรณีที่ต้องใช้ตัวสร้างหรือสถานะภายในโดยการใช้งานเริ่มต้น คุณต้องใช้คลาสนามธรรม
ไม่ว่าในกรณีใด นักออกแบบ API จะเลือกทำให้เมธอดเดียวเป็นแบบนามธรรมเพื่อลดความซับซ้อนในการใช้งานแบบ Lambda ก็ได้
public interface AnimationEndCallback {
// Always called, must be implemented.
public void onFinished(Animation anim);
// Optional callbacks.
public default void onStopped(Animation anim) { }
public default void onCanceled(Animation anim) { }
}
ชื่อคลาสควรแสดงถึงสิ่งที่ขยาย
ตัวอย่างเช่น คลาสที่ขยาย Service
ควรตั้งชื่อว่า FooService
เพื่อให้ชัดเจน
public class IntentHelper extends Service {}
public class IntentService extends Service {}
คำเสริมท้ายทั่วไป
หลีกเลี่ยงการใช้ส่วนต่อท้ายชื่อคลาสทั่วไป เช่น Helper
และ Util
สำหรับคอลเล็กชันของเมธอดยูทิลิตี แต่ให้ใส่เมธอดในคลาสที่เกี่ยวข้องโดยตรงหรือใส่ไว้ในฟังก์ชันส่วนขยาย Kotlin
ในกรณีที่เมธอดเป็นบริดจ์ระหว่างหลายคลาส ให้ตั้งชื่อคลาสที่รวมไว้ให้สื่อความหมายถึงสิ่งที่คลาสนั้นทํา
การใช้ส่วนต่อท้าย Helper
อาจเหมาะสมในบางกรณีที่จํากัดมาก ดังนี้
- ใช้สำหรับองค์ประกอบของลักษณะการทำงานเริ่มต้น
- อาจเกี่ยวข้องกับการมอบหมายลักษณะการทำงานที่มีอยู่ไปยังชั้นเรียนใหม่
- อาจต้องใช้สถานะที่เก็บไว้
- โดยทั่วไปเกี่ยวข้องกับ
View
เช่น หากการพอร์ตเคล็ดลับเครื่องมือย้อนหลังต้องเก็บสถานะที่เชื่อมโยงกับ View
ไว้และเรียกใช้เมธอดหลายรายการใน View
เพื่อติดตั้งการพอร์ตย้อนหลัง TooltipHelper
จะเป็นชื่อคลาสที่ยอมรับได้
อย่าแสดงโค้ดที่ IDL สร้างขึ้นเป็น API สาธารณะโดยตรง
เก็บโค้ดที่ IDL สร้างขึ้นไว้เป็นรายละเอียดการใช้งาน ซึ่งรวมถึง protobuf, socket, FlatBuffers หรือแพลตฟอร์ม API ที่ไม่ใช่ Java และไม่ใช่ NDK อื่นๆ อย่างไรก็ตาม IDL ส่วนใหญ่ใน Android อยู่ในรูปแบบ AIDL หน้านี้จะเน้นไปที่ AIDL
คลาส AIDL ที่สร้างขึ้นไม่เป็นไปตามข้อกําหนดของคู่มือสไตล์ API (เช่น ใช้การโอเวอร์โหลดไม่ได้) และเครื่องมือ AIDL ไม่ได้ออกแบบมาเพื่อรักษาความเข้ากันได้ของ API ภาษาโดยเฉพาะ คุณจึงฝังคลาสเหล่านี้ใน API สาธารณะไม่ได้
แต่ให้เพิ่มเลเยอร์ API สาธารณะไว้ด้านบนอินเทอร์เฟซ AIDL แทน แม้ว่าในตอนแรกจะเป็น Wrapper แบบตื้นก็ตาม
อินเทอร์เฟซ Binder
หากอินเทอร์เฟซ Binder
เป็นรายละเอียดการใช้งาน คุณจะเปลี่ยนแปลงได้อย่างอิสระในอนาคต โดยเลเยอร์สาธารณะจะรักษาความเข้ากันได้แบบย้อนหลังที่จำเป็นไว้ เช่น คุณอาจต้องเพิ่มอาร์กิวเมนต์ใหม่ในการเรียกใช้ภายใน หรือเพิ่มประสิทธิภาพการรับส่งข้อมูล IPC โดยใช้การรวมกลุ่มหรือสตรีมมิง การใช้หน่วยความจำที่ใช้ร่วมกัน หรือการดำเนินการที่คล้ายกัน คุณจะดำเนินการเหล่านี้ไม่ได้หากอินเทอร์เฟซ AIDL เป็น API สาธารณะด้วย
เช่น อย่าแสดง FooService
เป็น API สาธารณะโดยตรง
// BAD: Public API generated from IFooService.aidl
public class IFooService {
public void doFoo(String foo);
}
แต่ให้รวมอินเทอร์เฟซ Binder
ไว้ภายในตัวจัดการหรือคลาสอื่นแทน ดังนี้
/**
* @hide
*/
public class IFooService {
public void doFoo(String foo);
}
public IFooManager {
public void doFoo(String foo) {
mFooService.doFoo(foo);
}
}
หากในภายหลังต้องใช้อาร์กิวเมนต์ใหม่สำหรับการเรียกนี้ อินเทอร์เฟซภายในสามารถเพิ่มการโอเวอร์โหลดที่สะดวกและน้อยที่สุดลงใน API สาธารณะ คุณสามารถใช้เลเยอร์การรวมเพื่อจัดการข้อกังวลอื่นๆ เกี่ยวกับความเข้ากันได้แบบย้อนหลังได้เมื่อการใช้งานพัฒนาขึ้น
/**
* @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);
}
}
สำหรับอินเทอร์เฟซ Binder
ที่ไม่ได้เป็นส่วนหนึ่งของแพลตฟอร์ม Android (เช่น อินเทอร์เฟซบริการที่ Google Play Services ส่งออกเพื่อให้แอปใช้) ข้อกำหนดสำหรับอินเทอร์เฟซ IPC ที่เผยแพร่แล้ว เวอร์ชันที่มีเสถียร และใช้งานได้จริงจะทำให้การพัฒนาอินเทอร์เฟซนั้นๆ ยากขึ้นมาก อย่างไรก็ตาม คุณควรมีเลเยอร์ Wrapper อยู่รอบๆ เพื่อใช้กับหลักเกณฑ์ API อื่นๆ และเพื่อให้ใช้ API สาธารณะเดียวกันกับอินเทอร์เฟซ IPC เวอร์ชันใหม่ได้ง่ายขึ้น (หากจำเป็น)
อย่าใช้ออบเจ็กต์ Binder ดิบใน API สาธารณะ
ออบเจ็กต์ Binder
ไม่มีความหมายใดๆ ในตัวของมันเอง จึงไม่ควรใช้ใน API สาธารณะ กรณีการใช้งานที่พบบ่อยอย่างหนึ่งคือการใช้ Binder
หรือ IBinder
เป็นโทเค็นเนื่องจากมีความหมายเชิงอรรถศาสตร์ แทนที่จะใช้ออบเจ็กต์ Binder
ดิบ ให้ใช้คลาสโทเค็น 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() {...}
}
ชั้นเรียนผู้จัดการต้องเป็นชั้นเรียนสุดท้าย
ควรประกาศคลาสตัวจัดการเป็น final
คลาสตัวจัดการจะสื่อสารกับบริการของระบบและเป็นจุดเดียวของการโต้ตอบ คุณไม่จำเป็นต้องปรับแต่ง ดังนั้นให้ประกาศเป็น final
อย่าใช้ CompletableFuture หรือ Future
java.util.concurrent.CompletableFuture
มีแพลตฟอร์ม API ขนาดใหญ่ที่อนุญาตให้มีการเปลี่ยนแปลงค่าในอนาคตแบบกำหนดเองและมีค่าเริ่มต้นที่ทำให้เกิดข้อผิดพลาดได้ง่าย
ในทางกลับกัน java.util.concurrent.Future
ไม่มีการรับฟังแบบไม่บล็อก ทำให้ใช้งานกับโค้ดแบบอะซิงโครนัสได้ยาก
ในโค้ดแพลตฟอร์มและ API ไลบรารีระดับต่ำที่ใช้โดยทั้ง Kotlin และ Java ให้ใช้การผสมผสานระหว่างการเรียกกลับเมื่อดำเนินการเสร็จสมบูรณ์ Executor
และหาก API รองรับการยกเลิก CancellationSignal
public void asyncLoadFoo(android.os.CancellationSignal cancellationSignal,
Executor callbackExecutor,
android.os.OutcomeReceiver<FooResult, Throwable> callback);
หากคุณกําหนดเป้าหมาย Kotlin ให้ใช้ฟังก์ชัน suspend
suspend fun asyncLoadFoo(): Foo
ในไลบรารีการผสานรวมสำหรับ Java โดยเฉพาะ คุณสามารถใช้ ListenableFuture
ของ Guava
public com.google.common.util.concurrent.ListenableFuture<Foo> asyncLoadFoo();
อย่าใช้ "ไม่บังคับ"
แม้ว่า Optional
จะมีข้อได้เปรียบในแพลตฟอร์ม API บางแพลตฟอร์ม แต่ก็ไม่สอดคล้องกับแพลตฟอร์ม API ของ Android ที่มีอยู่ @Nullable
และ @NonNull
ให้ความช่วยเหลือเกี่ยวกับเครื่องมือเพื่อความปลอดภัยของ null
และ Kotlin จะบังคับใช้สัญญาเกี่ยวกับ Nullability ที่ระดับคอมไพเลอร์ ทำให้ไม่จําเป็นต้องใช้ Optional
สําหรับพรอมิเตีที่ไม่บังคับ ให้ใช้เมธอด has
และ get
ที่จับคู่กัน หากไม่ได้ตั้งค่า (has
แสดงผล false
) เมธอด get
ควรแสดงข้อผิดพลาด IllegalStateException
public boolean hasAzimuth() { ... }
public int getAzimuth() {
if (!hasAzimuth()) {
throw new IllegalStateException("azimuth is not set");
}
return azimuth;
}
ใช้ตัวสร้างแบบส่วนตัวสำหรับคลาสที่ไม่สามารถสร้างอินสแตนซ์ได้
คลาสที่สร้างได้โดยใช้ Builder
เท่านั้น คลาสที่มีเฉพาะค่าคงที่หรือเมธอดแบบคงที่ หรือคลาสที่ไม่สามารถสร้างอินสแตนซ์ได้ ควรมีคอนสตรัคเตอร์ส่วนตัวอย่างน้อย 1 รายการเพื่อป้องกันการสร้างอินสแตนซ์โดยใช้คอนสตรัคเตอร์แบบไม่มีอาร์กิวเมนต์เริ่มต้น
public final class Log {
// Not instantiable.
private Log() {}
}
Singleton
ไม่แนะนําให้ใช้ Singleton เนื่องจากมีข้อเสียต่อไปนี้ซึ่งเกี่ยวข้องกับการทดสอบ
- ชั้นเรียนจะเป็นผู้จัดการการสร้าง ซึ่งจะช่วยป้องกันการใช้เนื้อหาปลอม
- การทดสอบไม่สามารถปิดผนึกได้เนื่องจากลักษณะแบบคงที่ของ Singleton
- หากต้องการแก้ปัญหาเหล่านี้ นักพัฒนาแอปต้องรู้รายละเอียดภายในของ Singleton หรือสร้าง Wrapper ขึ้นมา
โปรดใช้รูปแบบอินสแตนซ์เดียว ซึ่งอาศัยคลาสฐานแบบนามธรรมเพื่อแก้ไขปัญหาเหล่านี้
อินสแตนซ์เดียว
คลาสอินสแตนซ์เดียวใช้คลาสฐานแบบนามธรรมที่มีตัวสร้าง private
หรือ internal
และให้เมธอด getInstance()
แบบคงที่เพื่อรับอินสแตนซ์ เมธอด getInstance()
ต้องแสดงผลออบเจ็กต์เดียวกันในการเรียกใช้ครั้งต่อๆ ไป
ออบเจ็กต์ที่ getInstance()
แสดงผลควรเป็นการใช้งานแบบส่วนตัวของคลาสพื้นฐานแบบนามธรรม
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
}
}
}
อินสแตนซ์เดียวแตกต่างจาก singleton ตรงที่นักพัฒนาแอปสามารถสร้าง SingleInstance
เวอร์ชันจำลองและใช้เฟรมเวิร์ก Dependency Injection ของตนเองเพื่อจัดการการใช้งานโดยไม่ต้องสร้าง Wrapper หรือไลบรารีอาจให้ข้อมูลจำลองของตนเองในอาร์ติแฟกต์ -testing
คลาสที่ปล่อยทรัพยากรควรใช้ AutoCloseable
คลาสที่ปล่อยทรัพยากรผ่านเมธอด close
, release
, destroy
หรือเมธอดที่คล้ายกันควรใช้ java.lang.AutoCloseable
เพื่อให้นักพัฒนาแอปล้างทรัพยากรเหล่านี้โดยอัตโนมัติเมื่อใช้บล็อก try-with-resources
หลีกเลี่ยงการเปิดตัวคลาสย่อย View ใหม่ใน Android*
อย่าเปิดตัวคลาสใหม่ที่รับค่ามาจาก android.view.View
ใน API สาธารณะของแพลตฟอร์มโดยตรงหรือโดยอ้อม (นั่นคือใน android.*
)
ตอนนี้ชุดเครื่องมือ UI ของ Android เป็นแบบCompose เป็นหลัก ฟีเจอร์ UI ใหม่ของแพลตฟอร์มควรแสดงเป็น API ระดับล่างที่นักพัฒนาซอฟต์แวร์สามารถใช้เพื่อติดตั้งใช้งาน Jetpack Compose และคอมโพเนนต์ UI ที่อิงตาม View (ไม่บังคับ) ในไลบรารี Jetpack การมีคอมโพเนนต์เหล่านี้ในไลบรารีจะเปิดโอกาสให้นําไปใช้กับเวอร์ชันเก่าได้เมื่อฟีเจอร์ของแพลตฟอร์มไม่พร้อมใช้งาน
ช่อง
กฎเหล่านี้เกี่ยวข้องกับช่องสาธารณะในคลาส
อย่าแสดงช่องข้อมูลดิบ
คลาส Java ไม่ควรแสดงช่องโดยตรง ฟิลด์ควรเป็นแบบส่วนตัวและเข้าถึงได้โดยใช้ Get และ Set สาธารณะเท่านั้น ไม่ว่าฟิลด์เหล่านี้จะเป็นแบบสุดท้ายหรือไม่ก็ตาม
ข้อยกเว้นที่พบได้น้อย ได้แก่ โครงสร้างข้อมูลพื้นฐานที่ไม่จำเป็นต้องปรับปรุงลักษณะการระบุหรือดึงข้อมูลช่อง ในกรณีเช่นนี้ คุณควรตั้งชื่อช่องโดยใช้รูปแบบการตั้งชื่อตัวแปรมาตรฐาน เช่น Point.x
และ Point.y
คลาส Kotlin สามารถแสดงพร็อพเพอร์ตี้ได้
ฟิลด์ที่แสดงควรทําเครื่องหมายเป็น "ขั้นสุดท้าย"
เราไม่แนะนําอย่างยิ่งให้ใช้ฟิลด์ข้อมูลดิบ (@ดูหัวข้ออย่าแสดงฟิลด์ข้อมูลดิบ) แต่ในกรณีที่พบไม่บ่อยที่ช่องแสดงเป็นช่องสาธารณะ ให้ทําเครื่องหมายช่องนั้น final
ไม่ควรแสดงฟิลด์ภายใน
อย่าอ้างอิงชื่อฟิลด์ภายในใน API สาธารณะ
public int mFlags;
ใช้ "สาธารณะ" แทน "ที่ได้รับการคุ้มครอง"
@ดูใช้แบบสาธารณะแทนแบบมีการป้องกัน
ค่าคงที่
กฎเหล่านี้เกี่ยวกับค่าคงที่แบบสาธารณะ
ค่าคงที่ Flag ไม่ควรซ้อนทับกับค่า int หรือ long
Flag หมายถึงบิตที่รวมเข้ากับค่ายูเนียนได้ หากไม่ใช่กรณีนี้ อย่าเรียกตัวแปรหรือค่าคงที่ 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;
ดูข้อมูลเพิ่มเติมเกี่ยวกับการกำหนดค่าคงที่ของ Flag สาธารณะได้ที่ @IntDef
สำหรับ Flag รูปแบบบิตมาสก์
ค่าคงที่แบบสุดท้ายแบบคงที่ควรใช้รูปแบบการตั้งชื่อแบบตัวพิมพ์ใหญ่ล้วนคั่นด้วยขีดล่าง
คำทั้งหมดในค่าคงที่ควรเป็นตัวพิมพ์ใหญ่ และคำหลายคำควรคั่นด้วย _
เช่น
public static final int fooThing = 5
public static final int FOO_THING = 5
ใช้คำนำหน้ามาตรฐานสำหรับค่าคงที่
ค่าคงที่จำนวนมากที่ใช้ใน Android มีไว้สำหรับสิ่งต่างๆ ที่เป็นมาตรฐาน เช่น Flag, แป้นพิมพ์ และการดำเนินการ ค่าคงที่เหล่านี้ควรมีคำนำหน้ามาตรฐานเพื่อให้ระบุได้ง่ายขึ้นว่าเป็นค่าคงที่
เช่น ข้อมูลเพิ่มเติมของ Intent ควรขึ้นต้นด้วย EXTRA_
การดำเนินการตาม Intent ควรขึ้นต้นด้วย ACTION_
ค่าคงที่ที่ใช้กับ Context.bindService()
ควรขึ้นต้นด้วย BIND_
ชื่อและขอบเขตของคีย์คงที่
ค่าคงที่สตริงควรสอดคล้องกับชื่อค่าคงที่เอง และโดยทั่วไปควรกำหนดขอบเขตไว้ที่แพ็กเกจหรือโดเมน เช่น
public static final String FOO_THING = "foo"
มีการตั้งชื่อไม่สอดคล้องกันหรือไม่มีขอบเขตที่เหมาะสม แต่ให้ลองทำดังนี้
public static final String FOO_THING = "android.fooservice.FOO_THING"
ส่วนนำหน้า android
ในค่าคงที่สตริงที่มีขอบเขตสงวนไว้สำหรับโครงการโอเพนซอร์ส Android
การดำเนินการและรายการเพิ่มเติมของ Intent รวมถึงรายการ Bundle ควรมีเนมสเปซโดยใช้ชื่อแพ็กเกจที่กําหนดไว้
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"
}
ใช้ "สาธารณะ" แทน "ที่ได้รับการคุ้มครอง"
@ดูใช้แบบสาธารณะแทนแบบมีการป้องกัน
ใช้คำนำหน้าที่สอดคล้องกัน
ค่าคงที่ที่เกี่ยวข้องทั้งหมดควรขึ้นต้นด้วยคำนำหน้าเดียวกัน ตัวอย่างเช่น สําหรับชุดค่าคงที่ที่จะใช้กับค่า 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;
@ดูใช้คำนำหน้ามาตรฐานสำหรับค่าคงที่
ใช้ชื่อทรัพยากรที่สอดคล้องกัน
ตัวระบุ แอตทริบิวต์ และค่าสาธารณะต้องตั้งชื่อโดยใช้รูปแบบการตั้งชื่อแบบ camelCase เช่น @id/accessibilityActionPageUp
หรือ @attr/textAppearance
ซึ่งคล้ายกับช่องสาธารณะใน Java
ในบางกรณี ตัวระบุสาธารณะหรือแอตทริบิวต์จะมีคำนำหน้าทั่วไปคั่นด้วยขีดล่าง ดังนี้
- ค่าการกําหนดค่าแพลตฟอร์ม เช่น
@string/config_recentsComponentName
ใน config.xml - แอตทริบิวต์มุมมองเฉพาะเลย์เอาต์ เช่น
@attr/layout_marginStart
ใน attrs.xml
ธีมและสไตล์สาธารณะต้องเป็นไปตามรูปแบบการตั้งชื่อแบบลําดับชั้น PascalCase เช่น @style/Theme.Material.Light.DarkActionBar
หรือ @style/Widget.Material.SearchView.ActionBar
ซึ่งคล้ายกับคลาสที่ฝังใน Java
ไม่ควรแสดงเลย์เอาต์และทรัพยากรที่วาดได้เป็น API สาธารณะ อย่างไรก็ตาม หากต้องแสดงเลย์เอาต์และไฟล์วาดภาพแบบสาธารณะ ต้องตั้งชื่อโดยใช้รูปแบบการตั้งชื่อ underscore เช่น layout/simple_list_item_1.xml
หรือ drawable/title_bar_tall.xml
เมื่อค่าคงที่อาจมีการเปลี่ยนแปลง ให้ทําให้ค่าคงที่เป็นแบบไดนามิก
คอมไพเลอร์อาจใส่ค่าคงที่ไว้ในบรรทัด ดังนั้นการคงค่าไว้เหมือนเดิมจึงถือว่าเป็นส่วนหนึ่งของสัญญา API หากค่าของค่าคงที่ MIN_FOO
หรือ MAX_FOO
อาจเปลี่ยนแปลงได้ในอนาคต ให้พิจารณาเปลี่ยนเป็นเมธอดแบบไดนามิกแทน
CameraManager.MAX_CAMERAS
CameraManager.getMaxCameras()
พิจารณาความเข้ากันได้ในอนาคตสําหรับการเรียกกลับ
แอปที่กำหนดเป้าหมายเป็น API เวอร์ชันเก่าจะไม่รู้จักค่าคงที่ที่กําหนดไว้ใน API เวอร์ชันอนาคต ด้วยเหตุนี้ ค่าคงที่ที่ส่งไปยังแอปจึงควรคำนึงถึงเวอร์ชัน API เป้าหมายของแอปและจับคู่ค่าคงที่ใหม่กับค่าที่สอดคล้องกัน พิจารณาสถานการณ์ต่อไปนี้
แหล่งที่มาของ SDK สมมติ:
// 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;
แอปสมมติที่มี targetSdkVersion="22"
if (result == STATUS_FAILURE) {
// Oh no!
} else {
// Success!
}
ในกรณีนี้ แอปได้รับการออกแบบภายใต้ข้อจำกัดของ API ระดับ 22 และทำการคาดการณ์ (ค่อนข้าง) สมเหตุสมผลว่ามีสถานะเป็นไปได้เพียง 2 สถานะเท่านั้น อย่างไรก็ตาม หากแอปได้รับ STATUS_FAILURE_RETRY
ที่เพิ่มเข้ามาใหม่ แอปจะตีความว่าการดำเนินการนี้สำเร็จ
เมธอดที่แสดงผลค่าคงที่สามารถจัดการกรณีเช่นนี้ได้อย่างปลอดภัยโดยจำกัดเอาต์พุตให้ตรงกับระดับ API ที่แอปกำหนดเป้าหมาย ดังนี้
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;
}
นักพัฒนาแอปไม่สามารถคาดการณ์ได้ว่ารายการค่าคงที่อาจเปลี่ยนแปลงในอนาคตหรือไม่ หากคุณกำหนด API ด้วยค่าคงที่ UNKNOWN
หรือ UNSPECIFIED
ที่ดูเหมือนจะครอบคลุมทั้งหมด นักพัฒนาแอปจะถือว่าค่าคงที่ที่เผยแพร่เมื่อเขียนแอปนั้นครอบคลุมทั้งหมด หากไม่ต้องการกำหนดความคาดหวังนี้ ให้พิจารณาอีกครั้งว่าการใช้ค่าคงที่ที่รับค่าทั้งหมดเหมาะกับ API ของคุณหรือไม่
นอกจากนี้ ไลบรารีไม่สามารถระบุ targetSdkVersion
ของตนเองแยกจากแอป และการจัดการการเปลี่ยนแปลงลักษณะการทำงาน targetSdkVersion
จากโค้ดไลบรารีนั้นมีความซับซ้อนและเกิดข้อผิดพลาดได้ง่าย
ค่าคงที่จำนวนเต็มหรือสตริง
ใช้ค่าคงที่แบบจำนวนเต็มและ @IntDef
หากเนมสเปซสำหรับค่าไม่สามารถขยายออกไปนอกแพ็กเกจได้ ใช้ค่าคงที่สตริงหากเนมสเปซมีการแชร์หรือขยายโดยโค้ดภายนอกแพ็กเกจ
คลาสข้อมูล
คลาสข้อมูลแสดงชุดพร็อพเพอร์ตี้แบบคงที่ และมีชุดฟังก์ชันยูทิลิตีขนาดเล็กที่กําหนดไว้อย่างดีสําหรับการโต้ตอบกับข้อมูลนั้น
อย่าใช้ data class
ใน Kotlin API สาธารณะ เนื่องจากคอมไพเลอร์ Kotlin ไม่รับประกันความเข้ากันได้ของ API ภาษาหรือไบนารีสำหรับโค้ดที่สร้างขึ้น แต่ให้ติดตั้งใช้งานฟังก์ชันที่จำเป็นด้วยตนเอง
การสร้างอินสแตนซ์
ใน Java คลาสข้อมูลควรมีคอนสตรัคเตอร์เมื่อมีพร็อพเพอร์ตี้ไม่มากนัก หรือใช้รูปแบบ Builder
เมื่อมีพร็อพเพอร์ตี้จำนวนมาก
ใน Kotlin คลาสข้อมูลควรมีคอนสตรัคเตอร์ที่มีอาร์กิวเมนต์เริ่มต้น ไม่ว่าจะมีจำนวนพร็อพเพอร์ตี้เท่าใดก็ตาม คลาสข้อมูลที่กําหนดใน Kotlin อาจได้รับประโยชน์จากการระบุตัวสร้างเมื่อกําหนดเป้าหมายไปยังไคลเอ็นต์ Java ด้วย
การแก้ไขและการคัดลอก
ในกรณีที่ต้องแก้ไขข้อมูล ให้ระบุคลาส Builder
ที่มีตัวสร้างโคลน (Java) หรือฟังก์ชันสมาชิก copy()
(Kotlin) ที่แสดงผลออบเจ็กต์ใหม่
เมื่อระบุฟังก์ชัน copy()
ใน Kotlin อาร์กิวเมนต์ต้องตรงกับคอนสตรัคเตอร์ของคลาส และต้องป้อนค่าเริ่มต้นโดยใช้ค่าปัจจุบันของออบเจ็กต์ ดังนี้
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
)
}
ลักษณะการทํางานเพิ่มเติม
คลาสข้อมูลควรใช้ทั้ง equals()
และ hashCode()
และจะต้องพิจารณาพร็อพเพอร์ตี้ทุกรายการในการใช้งานเมธอดเหล่านี้
คลาสข้อมูลสามารถใช้ toString()
กับรูปแบบที่แนะนำซึ่งตรงกับการใช้งานคลาสข้อมูลของ Kotlin เช่น User(var1=Alex, var2=42)
วิธีการ
กฎเหล่านี้เป็นกฎเกี่ยวกับรายละเอียดต่างๆ ในเมธอด พารามิเตอร์ ชื่อเมธอด ประเภทผลลัพธ์ และตัวระบุการเข้าถึง
เวลา
กฎเหล่านี้ครอบคลุมถึงวิธีแสดงแนวคิดเกี่ยวกับเวลา เช่น วันที่และระยะเวลา ใน API
โปรดใช้ประเภท java.time.* ทุกครั้งที่ทำได้
java.time.Duration
, java.time.Instant
และ java.time.*
ประเภทอื่นๆ อีกมากมายพร้อมใช้งานในแพลตฟอร์มทุกเวอร์ชันผ่านการถอดรูปแบบ และควรใช้เมื่อแสดงเวลาในพารามิเตอร์หรือค่าที่แสดงผลของ API
แนะนำให้แสดงเฉพาะตัวแปรของ API ที่ยอมรับหรือแสดงผล java.time.Duration
หรือ java.time.Instant
และละเว้นตัวแปรพื้นฐานที่มีกรณีการใช้งานเดียวกัน เว้นแต่โดเมน API จะเป็นโดเมนที่การจัดสรรออบเจ็กต์ในรูปแบบการใช้งานที่ต้องการจะส่งผลเสียต่อประสิทธิภาพ
เมธอดที่แสดงระยะเวลาควรตั้งชื่อว่า duration
หากค่าเวลาแสดงระยะเวลาที่เกี่ยวข้อง ให้ตั้งชื่อพารามิเตอร์ว่า "duration" ไม่ใช่ "time"
ValueAnimator.setTime(java.time.Duration);
ValueAnimator.setDuration(java.time.Duration);
ข้อยกเว้น:
"timeout" เหมาะสําหรับกรณีที่ระยะเวลามีผลกับค่าการหมดเวลาโดยเฉพาะ
"time" ที่มีประเภท java.time.Instant
เหมาะสําหรับการอ้างอิงถึงจุดหนึ่งๆ ของเวลา ไม่ใช่ระยะเวลา
เมธอดที่แสดงระยะเวลาหรือเวลาเป็นค่าพื้นฐานควรตั้งชื่อตามหน่วยเวลาและใช้ long
เมธอดที่รับหรือแสดงผลระยะเวลาเป็นประเภทพื้นฐานควรต่อท้ายชื่อเมธอดด้วยหน่วยเวลาที่เกี่ยวข้อง (เช่น Millis
, Nanos
, Seconds
) เพื่อสงวนชื่อที่ไม่มีการตกแต่งไว้ใช้กับ java.time.Duration
ดูเวลา
นอกจากนี้ คุณควรกำกับเนื้อหาของวิธีการด้วยหน่วยและฐานเวลาที่เหมาะสมด้วย
@CurrentTimeMillisLong
: ค่าเป็นการประทับเวลาที่ไม่ใช่ค่าลบซึ่งวัดเป็นจํานวนมิลลิวินาทีนับจาก 1970-01-01T00:00:00Z@CurrentTimeSecondsLong
: ค่าเป็นการประทับเวลาที่ไม่ใช่ค่าลบซึ่งวัดเป็นจํานวนวินาทีนับตั้งแต่ 1970-01-01T00:00:00Z@DurationMillisLong
: ค่าคือระยะเวลาที่ไม่ใช่ค่าลบเป็นมิลลิวินาที@ElapsedRealtimeLong
: ค่าเป็นการประทับเวลาที่ไม่ใช่ค่าลบในฐานเวลาSystemClock.elapsedRealtime()
@UptimeMillisLong
: ค่าเป็นการประทับเวลาที่ไม่ใช่ค่าลบในฐานเวลาSystemClock.uptimeMillis()
พารามิเตอร์เวลาพื้นฐานหรือค่าที่แสดงผลควรใช้ long
ไม่ใช่ int
ValueAnimator.setDuration(@DurationMillisLong long);
ValueAnimator.setDurationNanos(long);
วิธีการที่แสดงหน่วยเวลาควรใช้ชื่อหน่วยแบบย่อ
public void setIntervalNs(long intervalNs);
public void setTimeoutUs(long timeoutUs);
public void setIntervalNanos(long intervalNanos);
public void setTimeoutMicros(long timeoutMicros);
กำกับเนื้อหาอาร์กิวเมนต์เวลาแบบยาว
แพลตฟอร์มนี้มีคำอธิบายประกอบหลายรายการเพื่อให้การพิมพ์หน่วยเวลาประเภท long
แม่นยำยิ่งขึ้น ดังนี้
@CurrentTimeMillisLong
: ค่าเป็นการประทับเวลาที่ไม่ใช่ค่าลบซึ่งวัดเป็นจํานวนมิลลิวินาทีนับจาก1970-01-01T00:00:00Z
จึงอยู่ในฐานเวลาSystem.currentTimeMillis()
@CurrentTimeSecondsLong
: ค่าเป็นการประทับเวลาที่มีค่าไม่ติดลบซึ่งวัดเป็นจํานวนวินาทีนับตั้งแต่1970-01-01T00:00:00Z
@DurationMillisLong
: ค่าคือระยะเวลาที่ไม่ใช่ค่าลบเป็นมิลลิวินาที@ElapsedRealtimeLong
: ค่าเป็นการประทับเวลาที่ไม่ใช่ค่าลบในฐานเวลาSystemClock#elapsedRealtime()
@UptimeMillisLong
: ค่าเป็นการประทับเวลาที่ไม่ใช่ค่าลบในฐานเวลาSystemClock#uptimeMillis()
หน่วยวัด
สำหรับวิธีการทั้งหมดที่แสดงหน่วยวัดอื่นนอกเหนือจากเวลา ให้ใช้คำนำหน้าหน่วย SI แบบ CamelCase
public long[] getFrequenciesKhz();
public float getStreamVolumeDb();
ใส่พารามิเตอร์ที่ไม่บังคับไว้ที่ท้ายการโอเวอร์โหลด
หากคุณมีเมธอดที่โอเวอร์โหลดซึ่งมีพารามิเตอร์ที่ไม่บังคับ ให้ใส่พารามิเตอร์เหล่านั้นไว้ที่ท้ายสุดและจัดเรียงให้สอดคล้องกับพารามิเตอร์อื่นๆ ดังนี้
public int doFoo(boolean flag);
public int doFoo(int id, boolean flag);
public int doFoo(boolean flag);
public int doFoo(boolean flag, int id);
เมื่อเพิ่มการโอเวอร์โหลดสำหรับอาร์กิวเมนต์ที่ไม่บังคับ ลักษณะการทํางานของเมธอดที่เรียบง่ายควรทํางานในลักษณะเดียวกับที่มีการให้อาร์กิวเมนต์เริ่มต้นแก่เมธอดที่ซับซ้อนมากขึ้น
ผลที่ตามมา: อย่าเพิ่มวิธีการเกินความจำเป็น ยกเว้นเพื่อเพิ่มอาร์กิวเมนต์ที่ไม่บังคับหรือเพื่อยอมรับอาร์กิวเมนต์ประเภทต่างๆ หากวิธีการเป็นแบบหลายรูปแบบ หากเมธอดที่โอเวอร์โหลดทําสิ่งที่แตกต่างกันโดยพื้นฐาน ให้ตั้งชื่อใหม่
เมธอดที่มีพารามิเตอร์เริ่มต้นต้องมีการกำกับเนื้อหาด้วย @JvmOverloads (Kotlin เท่านั้น)
เมธอดและคอนสตรัคเตอร์ที่มีพารามิเตอร์เริ่มต้นต้องกำกับเนื้อหาด้วย @JvmOverloads
เพื่อรักษาความเข้ากันได้ของไบนารี
ดูรายละเอียดเพิ่มเติมเกี่ยวกับการโอเวอร์โหลดฟังก์ชันสำหรับค่าเริ่มต้นในคู่มือการทำงานร่วมกันอย่างเป็นทางการของ Kotlin-Java
class Greeting @JvmOverloads constructor(
loudness: Int = 5
) {
@JvmOverloads
fun sayHello(prefix: String = "Dr.", name: String) = // ...
}
อย่านำค่าพารามิเตอร์เริ่มต้นออก (Kotlin เท่านั้น)
หากมีวิธีการที่มาพร้อมกับพารามิเตอร์ที่มีค่าเริ่มต้น การนําค่าเริ่มต้นออกถือเป็นการเปลี่ยนแปลงที่ทําให้แหล่งที่มาใช้งานไม่ได้
พารามิเตอร์ของเมธอดที่โดดเด่นและระบุตัวตนได้มากที่สุดควรอยู่ก่อน
หากคุณมีเมธอดที่มีพารามิเตอร์หลายรายการ ให้ใส่พารามิเตอร์ที่เกี่ยวข้องที่สุดก่อน พารามิเตอร์ที่ระบุ Flag และตัวเลือกอื่นๆ มีความสำคัญน้อยกว่าพารามิเตอร์ที่อธิบายออบเจ็กต์ที่ดำเนินการ หากมี callback การทำงานเสร็จสมบูรณ์ ให้ใส่ไว้เป็นลำดับสุดท้าย
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);
ดูข้อมูลเพิ่มเติมได้ที่ใส่พารามิเตอร์ที่ไม่บังคับไว้ที่ท้ายการโอเวอร์โหลด
ผู้สร้าง
เราขอแนะนำให้ใช้รูปแบบตัวสร้างเพื่อสร้างออบเจ็กต์ Java ที่ซับซ้อน และมักใช้ใน Android ในกรณีต่อไปนี้
- พร็อพเพอร์ตี้ของออบเจ็กต์ที่ได้ควรเป็นพร็อพเพอร์ตี้ที่เปลี่ยนแปลงไม่ได้
- มีพร็อพเพอร์ตี้ที่ต้องระบุจํานวนมาก เช่น อาร์กิวเมนต์คอนสตรัคเตอร์จํานวนมาก
- พร็อพเพอร์ตี้มีความสัมพันธ์ที่ซับซ้อนระหว่างกันเมื่อสร้าง เช่น ต้องมีขั้นตอนการยืนยัน โปรดทราบว่าความซับซ้อนในระดับนี้มักบ่งบอกถึงปัญหาด้านความสามารถในการใช้งานของ API
พิจารณาว่าจำเป็นต้องใช้เครื่องมือสร้างหรือไม่ ตัวสร้างจะมีประโยชน์ในแพลตฟอร์ม API หากมีการใช้เพื่อดำเนินการต่อไปนี้
- กำหนดค่าพารามิเตอร์การสร้างที่ไม่บังคับเพียงไม่กี่รายการจากชุดที่อาจมีอยู่เป็นจำนวนมาก
- กำหนดค่าพารามิเตอร์การสร้างที่ไม่บังคับหรือต้องระบุหลายรายการ ซึ่งบางครั้งอาจเป็นประเภทที่คล้ายกันหรือตรงกัน ซึ่งทำให้การอ่านตำแหน่งการเรียกใช้เกิดความสับสนหรือเขียนให้มีข้อผิดพลาดได้ง่าย
- กำหนดค่าการสร้างออบเจ็กต์ทีละรายการ โดยที่โค้ดการกําหนดค่าหลายส่วนอาจเรียกใช้ตัวสร้างเป็นรายละเอียดการใช้งาน
- อนุญาตให้ประเภทเติบโตด้วยการเพิ่มพารามิเตอร์การสร้างที่ไม่บังคับเพิ่มเติมใน API เวอร์ชันในอนาคต
หากมีประเภทที่มีพารามิเตอร์ที่ต้องระบุไม่เกิน 3 รายการและไม่มีพารามิเตอร์ที่ไม่บังคับ คุณมักจะข้ามตัวสร้างและใช้ตัวสร้างแบบธรรมดาได้
คลาสที่มาจาก Kotlin ควรใช้ตัวสร้างที่มีการกำกับด้วย @JvmOverloads
ที่มีอาร์กิวเมนต์เริ่มต้นแทนการใช้ตัวสร้าง แต่อาจเลือกปรับปรุงความสามารถในการใช้งานสำหรับไคลเอ็นต์ Java ด้วยการระบุตัวสร้างด้วยเช่นกันในกรณีตามที่ระบุไว้ก่อนหน้านี้
class Tone @JvmOverloads constructor(
val duration: Long = 1000,
val frequency: Int = 2600,
val dtmfConfigs: List<DtmfConfig> = emptyList()
) {
class Builder {
// ...
}
}
คลาสตัวสร้างต้องแสดงผลตัวสร้าง
คลาสตัวสร้างต้องเปิดใช้การต่อเมธอดโดยแสดงผลออบเจ็กต์ตัวสร้าง (เช่น this
) จากทุกเมธอด ยกเว้น build()
ออบเจ็กต์ที่สร้างขึ้นเพิ่มเติมควรส่งเป็นอาร์กิวเมนต์ อย่าแสดงผลตัวสร้างของออบเจ็กต์อื่น
เช่น
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();
}
}
ในกรณีที่คลาสเครื่องมือสร้างพื้นฐานต้องรองรับส่วนขยาย ซึ่งเกิดขึ้นไม่บ่อยนัก ให้ใช้ประเภทการคืนค่าทั่วไป ดังนี้
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);
}
ต้องสร้างคลาสตัวสร้างผ่านเครื่องมือสร้าง
เพื่อสร้างตัวสร้างที่สอดคล้องกันผ่านแพลตฟอร์ม Android API ตัวสร้างทั้งหมดต้องสร้างขึ้นผ่านคอนสตรัคเตอร์ ไม่ใช่เมธอดตัวสร้างแบบคงที่ สำหรับ API ที่ใช้ Kotlin Builder
ต้องเป็นแบบสาธารณะ แม้ว่าผู้ใช้ Kotlin จะต้องอาศัย Builder โดยนัยผ่านกลไกการสร้างสไตล์เมธอด Factory/DSL ไลบรารีต้องไม่ใช้ @PublishedApi internal
เพื่อซ่อนตัวสร้างคลาส Builder
จากไคลเอ็นต์ Kotlin แบบเลือก
public class Tone {
public static Builder builder();
public static class Builder {
}
}
public class Tone {
public static class Builder {
public Builder();
}
}
อาร์กิวเมนต์ทั้งหมดในคอนสตรัคเตอร์ของบิลเดอร์ต้องเป็นแบบบังคับ (เช่น @NonNull)
ไม่บังคับ เช่น @Nullable
ควรย้ายอาร์กิวเมนต์ไปยังเมธอดตัวตั้งค่า
ตัวสร้างของ Builder ควรแสดงข้อยกเว้น NullPointerException
(พิจารณาใช้
Objects.requireNonNull
) หากไม่ได้ระบุอาร์กิวเมนต์ที่จําเป็น
คลาสตัวสร้างควรเป็นคลาสภายในแบบคงที่ระดับสุดท้ายของประเภทที่สร้างขึ้น
โดยทั่วไปแล้ว คลาสตัวสร้างควรแสดงเป็นคลาสภายในระดับสุดท้ายของประเภทที่สร้างขึ้น เช่น Tone.Builder
แทน ToneBuilder
เพื่อการจัดระเบียบอย่างเป็นเหตุเป็นผลภายในแพ็กเกจ
ตัวสร้างอาจรวมตัวสร้างคอนสตรัคเตอร์เพื่อสร้างอินสแตนซ์ใหม่จากอินสแตนซ์ที่มีอยู่
ตัวสร้างอาจมีตัวสร้างแบบคัดลอกเพื่อสร้างอินสแตนซ์ตัวสร้างใหม่จากตัวสร้างหรือออบเจ็กต์ที่สร้างอยู่แล้ว ไม่ควรระบุวิธีการอื่นในการสร้างอินสแตนซ์ของบิลเดอร์จากบิลเดอร์หรือออบเจ็กต์การสร้างที่มีอยู่
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);
}
}
ตัวตั้งค่าของ Builder ควรใช้อาร์กิวเมนต์ @Nullable หาก Builder มีเครื่องมือสร้างแบบคัดลอก
การรีเซ็ตเป็นสิ่งจําเป็นหากอาจสร้างอินสแตนซ์ใหม่ของเครื่องมือสร้างจากอินสแตนซ์ที่มีอยู่ หากไม่มีเครื่องมือสร้างสำเนา เครื่องมือสร้างอาจมีอาร์กิวเมนต์ @Nullable
หรือ @NonNullable
public static class Builder {
public Builder(Builder original);
public Builder setObjectValue(@Nullable Object value);
}
ตัวตั้งค่าของ Builder อาจใช้อาร์กิวเมนต์ @Nullable สำหรับพร็อพเพอร์ตี้ที่ไม่บังคับ
การใช้ค่าที่อนุญาตค่า Null สำหรับอินพุตระดับ 2 มักจะง่ายกว่า โดยเฉพาะใน Kotlin ซึ่งใช้อาร์กิวเมนต์เริ่มต้นแทนตัวสร้างและโอเวอร์โหลด
นอกจากนี้ @Nullable
setter จะจับคู่กับ getter ซึ่งต้องเป็น @Nullable
สำหรับพร็อพเพอร์ตี้ที่ไม่บังคับ
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();
}
การใช้งานทั่วไปใน Kotlin
fun createValue(optionalValue: OptionalValue? = null) =
Value.Builder()
.apply { optionalValue?.let { setOptionalValue(it) } }
.build()
fun createValue(optionalValue: OptionalValue? = null) =
Value.Builder()
.setOptionalValue(optionalValue)
.build()
ค่าเริ่มต้น (หากไม่ได้เรียกใช้ตัวตั้งค่า) และความหมายของ null
ต้องได้รับการบันทึกอย่างถูกต้องทั้งในตัวตั้งค่าและตัวรับค่า
/**
* ...
*
* <p>Defaults to {@code null}, which means the optional value won't be used.
*/
คุณระบุตัวตั้งค่าตัวสร้างสำหรับพร็อพเพอร์ตี้ที่เปลี่ยนแปลงได้ในกรณีที่มีตัวตั้งค่าในคลาสที่สร้างขึ้น
หากคลาสของคุณมีพร็อพเพอร์ตี้ที่เปลี่ยนแปลงได้และต้องใช้คลาส Builder
ก่อนอื่นให้ถามตัวเองว่าคลาสของคุณควรมีพร็อพเพอร์ตี้ที่เปลี่ยนแปลงได้หรือไม่
ถัดไป หากคุณแน่ใจว่าต้องใช้พร็อพเพอร์ตี้ที่เปลี่ยนแปลงได้ ให้ตัดสินใจว่าสถานการณ์ใดต่อไปนี้เหมาะกับกรณีการใช้งานที่คาดไว้มากกว่า
ออบเจ็กต์ที่สร้างขึ้นควรใช้งานได้ทันที ดังนั้นจึงควรระบุตัวตั้งค่าสำหรับพร็อพเพอร์ตี้ที่เกี่ยวข้องทั้งหมด ไม่ว่าจะเปลี่ยนค่าได้หรือไม่
map.put(key, new Value.Builder(requiredValue) .setImmutableProperty(immutableValue) .setUsefulMutableProperty(usefulValue) .build());
คุณอาจต้องเรียกใช้เพิ่มเติมอีกเล็กน้อยก่อนจึงจะใช้ออบเจ็กต์ที่สร้างขึ้นได้ ดังนั้นจึงไม่ควรระบุตัวตั้งค่าสำหรับพร็อพเพอร์ตี้ที่เปลี่ยนแปลงได้
Value v = new Value.Builder(requiredValue) .setImmutableProperty(immutableValue) .build(); v.setUsefulMutableProperty(usefulValue) Result r = v.performSomeAction(); Key k = callSomeMethod(r); map.put(k, v);
อย่าผสมผสาน 2 สถานการณ์นี้
Value v = new Value.Builder(requiredValue)
.setImmutableProperty(immutableValue)
.setUsefulMutableProperty(usefulValue)
.build();
Result r = v.performSomeAction();
Key k = callSomeMethod(r);
map.put(k, v);
ตัวสร้างไม่ควรมีตัวรับ
Getter ควรอยู่ในออบเจ็กต์ที่สร้าง ไม่ใช่ในตัวสร้าง
ตัวตั้งค่าของ Builder ต้องมีตัวรับค่าที่เกี่ยวข้องในคลาสที่สร้างขึ้น
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();
}
การตั้งชื่อเมธอดของเครื่องมือสร้าง
ชื่อเมธอดของเครื่องมือสร้างควรใช้รูปแบบ setFoo()
, addFoo()
หรือ clearFoo()
คลาสตัวสร้างควรประกาศเมธอด build()
คลาสตัวสร้างควรประกาศเมธอด build()
ที่แสดงผลอินสแตนซ์ของออบเจ็กต์ที่สร้าง
เมธอด build() ของ Builder ต้องแสดงผลออบเจ็กต์ @NonNull
เมธอด build()
ของบิลเดอร์ควรแสดงผลอินสแตนซ์ที่ไม่ใช่ค่า Null ของออบเจ็กต์ที่สร้างขึ้น ในกรณีที่สร้างออบเจ็กต์ไม่ได้เนื่องจากพารามิเตอร์ไม่ถูกต้อง คุณสามารถเลื่อนการตรวจสอบไปยังเมธอด build และควรแสดงIllegalStateException
อย่าเปิดเผยล็อกภายใน
วิธีการใน API สาธารณะไม่ควรใช้คีย์เวิร์ด synchronized
คีย์เวิร์ดนี้ทําให้ระบบใช้ออบเจ็กต์หรือคลาสของคุณเป็นล็อก และเนื่องจากคีย์เวิร์ดนี้เปิดเผยต่อผู้อื่น คุณจึงอาจพบผลข้างเคียงที่ไม่คาดคิดหากโค้ดอื่นๆ ภายนอกคลาสของคุณเริ่มใช้คีย์เวิร์ดดังกล่าวเพื่อวัตถุประสงค์ในการล็อก
แต่ให้ทำการล็อกที่จำเป็นกับออบเจ็กต์ภายในที่เป็นส่วนตัวแทน
public synchronized void doThing() { ... }
private final Object mThingLock = new Object();
public void doThing() {
synchronized (mThingLock) {
...
}
}
เมธอดที่มีรูปแบบตัวรับค่าควรเป็นไปตามหลักเกณฑ์ของพร็อพเพอร์ตี้ Kotlin
เมื่อดูจากแหล่งที่มาของ Kotlin เมธอดที่มีรูปแบบตัวรับค่า ซึ่งใช้คำนำหน้า get
, set
หรือ is
ก็จะพร้อมใช้งานเป็นพร็อพเพอร์ตี้ Kotlin ด้วย
เช่น int getField()
ที่กําหนดใน Java จะพร้อมใช้งานใน Kotlin เป็นพร็อพเพอร์ตี้ val field: Int
ด้วยเหตุนี้ และเพื่อตอบสนองความคาดหวังของนักพัฒนาซอฟต์แวร์เกี่ยวกับลักษณะการทํางานของเมธอดการเข้าถึงโดยทั่วไป เมธอดที่ใช้คำนำหน้าเมธอดการเข้าถึงควรทํางานคล้ายกับฟิลด์ Java หลีกเลี่ยงการใช้คำนำหน้าสไตล์ตัวเข้าถึงในกรณีต่อไปนี้
- เมธอดมีผลข้างเคียง โปรดใช้ชื่อเมธอดที่สื่อความหมายมากกว่า
- เมธอดนี้เกี่ยวข้องกับการประมวลผลที่ต้องใช้ทรัพยากรมาก โปรดใช้
compute
- เมธอดนี้เกี่ยวข้องกับการบล็อกหรืองานที่ทำงานเป็นเวลานานเพื่อแสดงผลค่า เช่น IPC หรือ I/O อื่นๆ โปรดใช้
fetch
- เมธอดจะบล็อกเธรดจนกว่าจะแสดงผลค่าได้ โปรดใช้
await
- เมธอดจะแสดงผลอินสแตนซ์ออบเจ็กต์ใหม่ทุกครั้งที่เรียกใช้ โปรดใช้
create
- วิธีการอาจแสดงผลลัพธ์ไม่สําเร็จ โปรดใช้
request
โปรดทราบว่าการทํางานที่ต้องใช้การประมวลผลมาก 1 ครั้งและแคชค่าไว้สําหรับการเรียกใช้ครั้งต่อๆ ไปจะยังคงนับเป็นการทํางานที่ต้องใช้การประมวลผลมาก ระบบจะไม่กระจายความล่าช้าในเฟรม
ใช้คำนำหน้า is สำหรับเมธอดการเข้าถึงข้อมูลบูลีน
นี่คือรูปแบบการตั้งชื่อมาตรฐานสำหรับเมธอดและฟิลด์บูลีนใน Java โดยทั่วไปแล้ว ชื่อเมธอดและตัวแปรบูลีนควรเขียนเป็นคําถามที่ตอบด้วยค่าที่แสดงผล
เมธอดการเข้าถึงบูลีนของ Java ควรเป็นไปตามรูปแบบการตั้งชื่อ set
/is
และควรใช้ is
สำหรับฟิลด์ ดังตัวอย่างต่อไปนี้
// 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;
การใช้ set
/is
สำหรับเมธอดการเข้าถึงของ Java หรือ is
สำหรับฟิลด์ Java จะช่วยให้ใช้เมธอดหรือฟิลด์ดังกล่าวเป็นพร็อพเพอร์ตี้จาก Kotlin ได้
obj.isVisible = true
obj.isFactoryResetProtectionEnabled = false
if (!obj.isAvailable) return
โดยทั่วไปแล้ว พร็อพเพอร์ตี้และเมธอดการเข้าถึงควรใช้การตั้งชื่อเชิงบวก เช่น Enabled
แทน Disabled
การใช้คําศัพท์เชิงลบจะทําให้ความหมายของ true
และ false
กลับกัน และทําให้หาเหตุผลเกี่ยวกับลักษณะการทํางานได้ยากขึ้น
// Passing false here is a double-negative.
void setFactoryResetProtectionDisabled(boolean disabled);
ในกรณีที่บูลีนอธิบายการรวมหรือความเป็นเจ้าของพร็อพเพอร์ตี้ คุณอาจใช้ has แทน is แต่วิธีนี้ไม่สามารถใช้กับไวยากรณ์พร็อพเพอร์ตี้ 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();
คำนำหน้าทางเลือกที่อาจเหมาะสมกว่า ได้แก่ can และ should
// "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();
เมธอดที่สลับลักษณะการทำงานหรือฟีเจอร์อาจใช้คำนำหน้า is และคำต่อท้าย 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()
ในทำนองเดียวกัน เมธอดที่ระบุการพึ่งพาลักษณะการทำงานหรือฟีเจอร์อื่นๆ อาจใช้คำนำหน้า is และคำต่อท้าย Supported หรือ 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()
โดยทั่วไปแล้ว ชื่อเมธอดควรเขียนเป็นคำถามที่ตอบด้วยค่าที่แสดงผล
เมธอดพร็อพเพอร์ตี้ของ Kotlin
สำหรับพร็อพเพอร์ตี้ของคลาส var foo: Foo
Kotlin จะสร้างเมธอด get
/set
โดยใช้กฎที่สอดคล้องกัน โดยนำ get
ไปไว้ข้างหน้าและขึ้นต้นอักขระแรกด้วยตัวพิมพ์ใหญ่สำหรับเมธอดรับค่า และนำ set
ไปไว้ข้างหน้าและขึ้นต้นอักขระแรกด้วยตัวพิมพ์ใหญ่สำหรับเมธอดตั้งค่า การประกาศพร็อพเพอร์ตี้จะสร้างเมธอดชื่อ public Foo getFoo()
และ public void setFoo(Foo foo)
ตามลำดับ
หากพร็อพเพอร์ตี้เป็นประเภท Boolean
ระบบจะใช้กฎเพิ่มเติมในการสร้างชื่อ โดยหากชื่อพร็อพเพอร์ตี้ขึ้นต้นด้วย is
ระบบจะไม่ใส่ get
ไว้หน้าชื่อเมธอด getter แต่จะใช้เป็น getter แทน
ดังนั้น โปรดตั้งชื่อที่พัก Boolean
ด้วยคำนำหน้า is
เพื่อให้เป็นไปตามหลักเกณฑ์การตั้งชื่อ
var isVisible: Boolean
หากพร็อพเพอร์ตี้ของคุณเป็นหนึ่งในข้อยกเว้นที่กล่าวถึงข้างต้นและขึ้นต้นด้วยคำนำหน้าที่เหมาะสม ให้ใช้คำอธิบายประกอบ @get:JvmName
ในพร็อพเพอร์ตี้เพื่อระบุชื่อที่เหมาะสมด้วยตนเอง
@get:JvmName("hasTransientState")
var hasTransientState: Boolean
@get:JvmName("canRecord")
var canRecord: Boolean
@get:JvmName("shouldFitWidth")
var shouldFitWidth: Boolean
ตัวแปรการเข้าถึงบิตมาสก์
ดูหลักเกณฑ์เกี่ยวกับการกำหนด Flag รูปแบบบิตมาสก์ใน API ได้ที่ใช้ @IntDef
สำหรับ Flag รูปแบบบิตมาสก์
Setter
คุณควรระบุเมธอด setter 2 รายการ ได้แก่ เมธอดที่รับสตริงบิตแบบเต็มและเขียนทับ Flag ที่มีอยู่ทั้งหมด และเมธอดที่รับ Bitmask ที่กําหนดเองเพื่อให้มีความยืดหยุ่นมากขึ้น
/**
* 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);
Getter
คุณควรระบุ getter 1 รายการเพื่อรับหน้ากากบิตแบบเต็ม
/**
* 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();
ใช้ "สาธารณะ" แทน "ที่ได้รับการคุ้มครอง"
โปรดใช้ public
แทน protected
ใน API สาธารณะเสมอ การเข้าถึงที่มีการป้องกันจะทำให้เกิดปัญหาในระยะยาว เนื่องจากผู้ติดตั้งใช้งานต้องลบล้างเพื่อระบุตัวเข้าถึงแบบสาธารณะในกรณีที่การเข้าถึงจากภายนอกโดยค่าเริ่มต้นจะมีประสิทธิภาพเท่าๆ กัน
โปรดทราบว่าprotected
ระดับการแชร์ไม่ได้ป้องกันไม่ให้นักพัฒนาแอปเรียกใช้ API เพียงแต่ทำให้การเรียกใช้ API นั้นน่ารำคาญขึ้นเล็กน้อย
ใช้ทั้ง equals() และ hashCode() หรือใช้อย่างใดอย่างหนึ่ง
หากลบล้างรายการใดรายการหนึ่ง คุณต้องลบล้างรายการอื่นด้วย
ใช้ toString() สําหรับคลาสข้อมูล
เราขอแนะนำให้คลาสข้อมูลลบล้าง toString()
เพื่อช่วยนักพัฒนาซอฟต์แวร์แก้ไขข้อบกพร่องของโค้ด
ระบุว่าเอาต์พุตมีไว้สำหรับลักษณะการทํางานของโปรแกรมหรือการแก้ไขข้อบกพร่อง
เลือกว่าต้องการให้ลักษณะการทํางานของโปรแกรมขึ้นอยู่กับการติดตั้งใช้งานหรือไม่ เช่น UUID.toString() และ File.toString() แสดงรูปแบบที่เฉพาะเจาะจงสำหรับโปรแกรมต่างๆ ที่จะใช้งาน หากคุณแสดงข้อมูลสำหรับการแก้ไขข้อบกพร่องเท่านั้น เช่น Intent ให้ใช้เอกสารที่รับค่ามาจากซุปเปอร์คลาส
อย่าใส่ข้อมูลเพิ่มเติม
ข้อมูลทั้งหมดที่มีจาก toString()
ควรมีให้ใช้งานผ่าน API สาธารณะของออบเจ็กต์ด้วย ไม่เช่นนั้น คุณกําลังส่งเสริมให้นักพัฒนาซอฟต์แวร์แยกวิเคราะห์และอาศัยเอาต์พุต toString()
ซึ่งจะป้องกันการเปลี่ยนแปลงในอนาคต แนวทางปฏิบัติแนะนำคือให้ติดตั้งใช้งาน toString()
โดยใช้เฉพาะ API สาธารณะของออบเจ็กต์
ไม่แนะนำให้ใช้เอาต์พุตการแก้ไขข้อบกพร่อง
แม้ว่าจะไม่สามารถป้องกันไม่ให้นักพัฒนาซอฟต์แวร์ใช้เอาต์พุตการแก้ไขข้อบกพร่องได้ แต่การรวม System.identityHashCode
ของออบเจ็กต์ไว้ในtoString()
เอาต์พุตของออบเจ็กต์จะทำให้โอกาสที่ออบเจ็กต์ 2 รายการจะมีtoString()
เอาต์พุตเท่ากันนั้นต่ำมาก
@Override
public String toString() {
return getClass().getSimpleName() + "@" + Integer.toHexString(System.identityHashCode(this)) + " {mFoo=" + mFoo + "}";
}
วิธีนี้ช่วยยับยั้งนักพัฒนาแอปไม่ให้เขียนการยืนยันการทดสอบ เช่น assertThat(a.toString()).isEqualTo(b.toString())
บนออบเจ็กต์ของคุณได้อย่างมีประสิทธิภาพ
ใช้ createFoo เมื่อแสดงผลออบเจ็กต์ที่สร้างขึ้นใหม่
ใช้คำนำหน้า create
ไม่ใช่ get
หรือ new
สำหรับเมธอดที่จะสร้างค่าที่แสดงผล เช่น การสร้างออบเจ็กต์ใหม่
เมื่อเมธอดจะสร้างออบเจ็กต์เพื่อแสดงผล ให้ระบุข้อมูลดังกล่าวอย่างชัดเจนในชื่อเมธอด
public FooThing getFooThing() {
return new FooThing();
}
public FooThing createFooThing() {
return new FooThing();
}
เมธอดที่ยอมรับออบเจ็กต์ไฟล์ควรยอมรับสตรีมด้วย
ตำแหน่งการจัดเก็บข้อมูลใน Android ไม่ได้เป็นไฟล์ในดิสก์เสมอไป ตัวอย่างเช่น เนื้อหาที่ส่งข้ามขอบเขตผู้ใช้จะแสดงเป็น content://
Uri
หากต้องการเปิดใช้การประมวลผลแหล่งข้อมูลต่างๆ API ที่ยอมรับออบเจ็กต์ File
ควรยอมรับ InputStream
, OutputStream
หรือทั้ง 2 อย่าง
public void setDataSource(File file)
public void setDataSource(InputStream stream)
รับและแสดงผลค่าพื้นฐานแบบดิบแทนเวอร์ชันที่มีกล่อง
หากต้องการสื่อสารค่าที่ขาดหายไปหรือค่าว่าง ให้ลองใช้ -1
,
Integer.MAX_VALUE
หรือ Integer.MIN_VALUE
public java.lang.Integer getLength()
public void setLength(java.lang.Integer)
public int getLength()
public void setLength(int value)
การหลีกเลี่ยงคลาสที่เทียบเท่าประเภทพื้นฐานจะช่วยหลีกเลี่ยงค่าใช้จ่ายเพิ่มเติมของหน่วยความจำสำหรับคลาสเหล่านี้ การเข้าถึงค่าด้วยเมธอด และที่สำคัญกว่านั้นคือ การแปลงอัตโนมัติที่เกิดจากการแคสต์ระหว่างประเภทพื้นฐานกับประเภทออบเจ็กต์ การหลีกเลี่ยงลักษณะการทำงานเหล่านี้จะช่วยประหยัดหน่วยความจำและการจัดสรรชั่วคราวที่อาจทําให้การเก็บขยะมีค่าใช้จ่ายสูงและเกิดขึ้นบ่อยครั้ง
ใช้คำอธิบายประกอบเพื่อชี้แจงพารามิเตอร์และค่าที่ถูกต้อง
เพิ่มคำอธิบายประกอบของนักพัฒนาแอปเพื่อช่วยชี้แจงค่าที่อนุญาตในสถานการณ์ต่างๆ ซึ่งช่วยให้เครื่องมือช่วยเหลือนักพัฒนาแอปได้ง่ายขึ้นเมื่อนักพัฒนาแอประบุค่าที่ไม่ถูกต้อง (เช่น ส่ง int
ที่กำหนดเองเมื่อเฟรมเวิร์กต้องการค่าคงที่ชุดใดชุดหนึ่งโดยเฉพาะ) ใช้คำอธิบายประกอบต่อไปนี้ทั้งหมดตามความเหมาะสม
ความสามารถในการเว้นว่าง
ต้องใช้การกำกับเนื้อหาเกี่ยวกับ Nullability ที่ชัดเจนสำหรับ Java API แต่แนวคิดเกี่ยวกับ Nullability เป็นส่วนหนึ่งของภาษา Kotlin และไม่ควรใช้การกำกับเนื้อหาเกี่ยวกับ Nullability ใน Kotlin API
@Nullable
: บ่งบอกว่าผลลัพธ์ พารามิเตอร์ หรือฟิลด์ที่ระบุอาจเป็นค่าว่างได้
@Nullable
public String getName()
public void setName(@Nullable String name)
@NonNull
: บ่งบอกว่าค่าที่แสดงผล พารามิเตอร์ หรือช่องหนึ่งๆ ต้องไม่มีค่า Null การทําเครื่องหมายเป็น @Nullable
เป็นสิ่งที่ค่อนข้างใหม่สําหรับ Android ดังนั้นวิธีการ API ส่วนใหญ่ของ Android จึงไม่มีเอกสารประกอบที่สอดคล้องกัน ดังนั้น เราจึงมีสถานะ 3 สถานะ ได้แก่ "ไม่ทราบ @Nullable
@NonNull
" ซึ่งเป็นเหตุผลที่ @NonNull
เป็นส่วนหนึ่งของหลักเกณฑ์ API
@NonNull
public String getName()
public void setName(@NonNull String name)
สําหรับเอกสารแพลตฟอร์ม Android การกำกับเนื้อหาประกอบพารามิเตอร์เมธอดจะสร้างเอกสารประกอบในรูปแบบ "ค่านี้อาจเป็นค่าว่าง" โดยอัตโนมัติ เว้นแต่จะมีการใช้ "null" อย่างชัดเจนในที่อื่นในเอกสารพารามิเตอร์
เมธอด "ไม่ใช่ Null จริงๆ" ที่มีอยู่: เมธอดที่มีอยู่ใน API ที่ไม่มีแอตทริบิวต์ @Nullable
ที่ประกาศไว้อาจมีการใส่แอตทริบิวต์ @Nullable
หากเมธอดสามารถแสดงผล null
ภายใต้สถานการณ์ที่เฉพาะเจาะจงและชัดเจน (เช่น findViewById()
) ควรเพิ่มเมธอด @NotNull requireFoo()
ที่ใช้ร่วมกันซึ่งจะแสดงผล IllegalArgumentException
สำหรับนักพัฒนาแอปที่ไม่ต้องการทำการตรวจสอบ Null
เมธอดอินเทอร์เฟซ: API ใหม่ควรเพิ่มคำอธิบายประกอบที่เหมาะสมเมื่อติดตั้งใช้งานเมธอดอินเทอร์เฟซ เช่น Parcelable.writeToParcel()
(กล่าวคือ เมธอดนั้นในคลาสที่ติดตั้งใช้งานควรเป็น writeToParcel(@NonNull Parcel,
int)
ไม่ใช่ writeToParcel(Parcel, int)
) แต่ไม่จำเป็นต้อง "แก้ไข" API ที่มีอยู่ซึ่งไม่มีคำอธิบายประกอบ
การบังคับใช้ความสามารถในการเว้นว่าง
ใน Java เราแนะนําให้ใช้เมธอดเพื่อตรวจสอบอินพุตสําหรับ@NonNull
พารามิเตอร์โดยใช้
Objects.requireNonNull()
และแสดงข้อผิดพลาด NullPointerException
เมื่อพารามิเตอร์เป็นค่าว่าง ซึ่งจะดำเนินการโดยอัตโนมัติใน Kotlin
แหล่งข้อมูล
ตัวระบุทรัพยากร: พารามิเตอร์จำนวนเต็มที่ใช้ระบุรหัสสำหรับทรัพยากรที่เฉพาะเจาะจงควรมีคำอธิบายประกอบพร้อมคำจำกัดความประเภททรัพยากรที่เหมาะสม
มีการกำกับเนื้อหาสําหรับทรัพยากรทุกประเภท เช่น @StringRes
,
@ColorRes
และ @AnimRes
นอกเหนือจาก @AnyRes
ที่รับทุกประเภท เช่น
public void setTitle(@StringRes int resId)
@IntDef สำหรับชุดค่าคงที่
ค่าคงที่ที่กําหนดเอง: พารามิเตอร์ String
และ int
ที่มีไว้เพื่อรับค่าที่เป็นไปได้ชุดหนึ่งๆ ซึ่งระบุด้วยค่าคงที่สาธารณะควรมีคำอธิบายประกอบที่เหมาะสมด้วย @StringDef
หรือ @IntDef
คําอธิบายประกอบเหล่านี้ช่วยให้คุณสร้างคําอธิบายประกอบใหม่ที่ทํางานเหมือน typedef สําหรับพารามิเตอร์ที่อนุญาตได้ เช่น
/** @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);
เราแนะนําให้ใช้เมธอดเพื่อตรวจสอบความถูกต้องของพารามิเตอร์ที่มีคำอธิบายประกอบ และแสดง IllegalArgumentException
หากพารามิเตอร์ไม่ได้เป็นส่วนหนึ่งของ @IntDef
@IntDef สำหรับ Flag รูปแบบบิตมาสก์
นอกจากนี้ คําอธิบายประกอบยังระบุได้ว่าค่าคงที่คือ Flag และสามารถรวมกับ & และ 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 สำหรับชุดค่าคงที่สตริง
นอกจากนี้ยังมีคำอธิบายประกอบ @StringDef
ซึ่งเหมือนกับ @IntDef
ในส่วนก่อนหน้าทุกประการ แต่ใช้กับค่าคงที่ String
คุณสามารถใส่ค่า "prefix" ได้หลายค่า ซึ่งจะใช้เพื่อแสดงเอกสารประกอบสำหรับค่าทั้งหมดโดยอัตโนมัติ
@SdkConstant สำหรับค่าคงที่ SDK
@SdkConstant กำกับเนื้อหาในช่องสาธารณะเมื่อเป็นค่า SdkConstant
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";
ระบุ Nullability ที่เข้ากันได้สำหรับการลบล้าง
สำหรับการเข้ากันได้ของ API ความสามารถในการเป็น Null ของการลบล้างควรเข้ากันได้กับความสามารถในการเป็น Null ในปัจจุบันของรายการหลัก ตารางต่อไปนี้แสดงความคาดหวังด้านความเข้ากันได้ กล่าวอย่างง่ายคือ การลบล้างควรมีข้อจํากัดเท่ากับหรือจํากัดมากกว่าองค์ประกอบที่ลบล้าง
ประเภท | ผู้ปกครอง | บุตร |
---|---|---|
ประเภทการแสดงผล | ไม่มีการกำกับเนื้อหา | ไม่มีคำอธิบายประกอบหรือไม่ใช่ค่า Null |
ประเภทการแสดงผล | เว้นว่างได้ | Nullable หรือ nonnull |
ประเภทการแสดงผล | Nonnull | Nonnull |
อาร์กิวเมนต์ที่สนุก | ไม่มีการกำกับเนื้อหา | ไม่มีคำอธิบายประกอบหรือมี Null |
อาร์กิวเมนต์ที่สนุก | เว้นว่างได้ | เว้นว่างได้ |
อาร์กิวเมนต์ที่สนุก | Nonnull | Nullable หรือ nonnull |
โปรดใช้อาร์กิวเมนต์ที่ไม่ใช่ Null (เช่น @NonNull) หากเป็นไปได้
เมื่อโอเวอร์โหลดเมธอด แนะนำให้อาร์กิวเมนต์ทั้งหมดไม่ใช่ค่า Null
public void startActivity(@NonNull Component component) { ... }
public void startActivity(@NonNull Component component, @NonNull Bundle options) { ... }
กฎนี้มีผลกับตัวตั้งค่าพร็อพเพอร์ตี้ที่โอเวอร์โหลดด้วย อาร์กิวเมนต์หลักไม่ควรเป็นค่า Null และควรล้างพร็อพเพอร์ตี้เป็นเมธอดแยกต่างหาก ซึ่งจะช่วยป้องกันคําเรียกที่ "ไร้ความหมาย" ซึ่งนักพัฒนาแอปต้องตั้งค่าพารามิเตอร์ต่อท้ายแม้ว่าจะไม่จําเป็นก็ตาม
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()
โปรดใช้ประเภทผลลัพธ์ที่ไม่ใช่ Null (เช่น @NonNull) สำหรับคอนเทนเนอร์
สําหรับคอนเทนเนอร์ประเภทต่างๆ เช่น Bundle
หรือ Collection
ให้แสดงผลคอนเทนเนอร์ว่างเปล่าและ (หากมี) คอนเทนเนอร์ที่แก้ไขไม่ได้ ในกรณีที่จะใช้ null
เพื่อแยกความแตกต่างของความพร้อมใช้งานของคอนเทนเนอร์ ให้พิจารณาระบุเมธอดบูลีนแยกต่างหาก
@NonNull
public Bundle getExtras() { ... }
คําอธิบายประกอบแบบ Nullability สําหรับคู่ get และ set ต้องตรงกัน
คู่เมธอด get และ set สําหรับพร็อพเพอร์ตี้แบบลอจิคัลรายการเดียวควรตรงกันเสมอในแอตทริบิวต์ Nullability การไม่ปฏิบัติตามหลักเกณฑ์นี้จะขัดต่อไวยากรณ์ของพร็อพเพอร์ตี้ Kotlin และการเพิ่มแอตทริบิวต์กำกับแบบไม่แน่ใจที่ไม่ตรงกันลงในเมธอดพร็อพเพอร์ตี้ที่มีอยู่จึงเป็นการเปลี่ยนแปลงที่ทำลายซอร์สโค้ดสำหรับผู้ใช้ Kotlin
@NonNull
public Bundle getExtras() { ... }
public void setExtras(@NonNull Bundle bundle) { ... }
แสดงผลค่าในเงื่อนไขที่ดำเนินการไม่สำเร็จหรือมีข้อผิดพลาด
API ทั้งหมดควรอนุญาตให้แอปตอบสนองต่อข้อผิดพลาด การแสดงผล false
, -1
, null
หรือค่าอื่นๆ ที่รวมทุกอย่างไว้สำหรับ "เกิดข้อผิดพลาด" นั้นไม่เพียงพอที่จะบอกให้นักพัฒนาแอปทราบถึงปัญหาในการกำหนดความคาดหวังของผู้ใช้ หรือติดตามความน่าเชื่อถือของแอปในสนามได้อย่างถูกต้อง เมื่อออกแบบ API ให้จินตนาการว่าคุณกำลังสร้างแอป หากพบข้อผิดพลาด API ให้ข้อมูลเพียงพอที่จะแสดงต่อผู้ใช้หรือตอบสนองอย่างเหมาะสมหรือไม่
- คุณสามารถใส่ข้อมูลโดยละเอียดในข้อความข้อยกเว้นได้ (และเราขอแนะนำให้ทำ) แต่นักพัฒนาแอปไม่ควรต้องแยกวิเคราะห์ข้อความดังกล่าวเพื่อจัดการข้อผิดพลาดอย่างเหมาะสม รหัสข้อผิดพลาดแบบละเอียดหรือข้อมูลอื่นๆ ควรแสดงเป็นเมธอด
- ตรวจสอบว่าตัวเลือกการจัดการข้อผิดพลาดที่คุณเลือกมีความยืดหยุ่นในการนําประเภทข้อผิดพลาดใหม่มาใช้ในอนาคต สําหรับ
@IntDef
หมายความว่าให้ใส่ค่าOTHER
หรือUNKNOWN
เมื่อแสดงผลรหัสใหม่ คุณสามารถตรวจสอบtargetSdkVersion
ของผู้เรียกเพื่อหลีกเลี่ยงการแสดงผลรหัสข้อผิดพลาดที่แอปไม่รู้ สำหรับข้อยกเว้น ให้ใช้ซุปเปอร์คลาสทั่วไปที่ข้อยกเว้นของคุณนำมาใช้ เพื่อให้โค้ดที่จัดการประเภทนั้นจับและจัดการประเภทย่อยได้ด้วย - นักพัฒนาซอฟต์แวร์ไม่ควรมองข้ามข้อผิดพลาดโดยไม่ตั้งใจ หากมีการส่งข้อผิดพลาดโดยการแสดงผลค่า ให้ใส่คำอธิบายประกอบเมธอดด้วย
@CheckResult
แนะนำให้แสดง ? extends RuntimeException
เมื่อเกิดเงื่อนไขความล้มเหลวหรือข้อผิดพลาดเนื่องจากนักพัฒนาซอฟต์แวร์ทำสิ่งผิดพลาด เช่น ไม่สนใจข้อจำกัดของพารามิเตอร์อินพุตหรือไม่ตรวจสอบสถานะที่สังเกตได้
เมธอดตัวตั้งค่าหรือการดําเนินการ (เช่น perform
) อาจแสดงผลรหัสสถานะแบบจำนวนเต็มหากการดำเนินการอาจล้มเหลวเนื่องจากสถานะหรือเงื่อนไขที่อัปเดตแบบไม่พร้อมกันซึ่งอยู่นอกเหนือการควบคุมของนักพัฒนาซอฟต์แวร์
คุณควรกำหนดรหัสสถานะในคลาสที่ประกอบด้วยเป็นpublic static final
ช่องที่มีERROR_
นำหน้า และระบุไว้ในคำอธิบายประกอบ @hide
@IntDef
ชื่อเมธอดควรขึ้นต้นด้วยคํากริยาเสมอ ไม่ใช่คํานาม
ชื่อเมธอดควรขึ้นต้นด้วยคำกริยาเสมอ (เช่น get
,
create
, reload
ฯลฯ) ไม่ใช่ออบเจ็กต์ที่คุณดำเนินการ
public void tableReload() {
mTable.reload();
}
public void reloadTable() {
mTable.reload();
}
โปรดใช้ประเภท คอลเล็กชันแทนอาร์เรย์เป็นประเภทผลลัพธ์หรือพารามิเตอร์
อินเทอร์เฟซคอลเล็กชันที่มีประเภททั่วไปมีข้อดีหลายประการเหนือกว่าอาร์เรย์ ซึ่งรวมถึงสัญญา API ที่เข้มงวดยิ่งขึ้นเกี่ยวกับความเป็นเอกลักษณ์และการจัดเรียง การสนับสนุนแบบทั่วไป และเมธอดอำนวยความสะดวกที่นักพัฒนาแอปใช้งานได้ง่าย
ข้อยกเว้นสำหรับองค์ประกอบพื้นฐาน
หากองค์ประกอบเป็นองค์ประกอบพื้นฐาน ให้ใช้อาร์เรย์แทนเพื่อหลีกเลี่ยงค่าใช้จ่ายในการแปลงอัตโนมัติ ดูหัวข้อรับและแสดงผลค่าพื้นฐานแบบดิบแทนเวอร์ชันที่มีกล่อง
ข้อยกเว้นสําหรับโค้ดที่ไวต่อประสิทธิภาพ
ในบางสถานการณ์ที่ API ใช้ในโค้ดที่ไวต่อประสิทธิภาพ (เช่น กราฟิกหรือ API การวัด/เลย์เอาต์/วาดอื่นๆ) คุณสามารถใช้อาร์เรย์แทนคอลเล็กชันเพื่อลดการจัดสรรและการเปลี่ยนแปลงหน่วยความจําได้
ข้อยกเว้นสำหรับ Kotlin
อาร์เรย์ Kotlin เป็นแบบคงที่และภาษา Kotlin มี API ยูทิลิตีมากมายสำหรับอาร์เรย์ ดังนั้นอาร์เรย์จึงเทียบเท่ากับ List
และ Collection
สำหรับ Kotlin API ที่ต้องการเข้าถึงจาก Kotlin
โปรดใช้คอลเล็กชัน @NonNull
โปรดใช้ @NonNull
สำหรับออบเจ็กต์คอลเล็กชันเสมอ เมื่อแสดงผลคอลเล็กชันว่าง ให้ใช้เมธอด Collections.empty
ที่เหมาะสมเพื่อแสดงผลออบเจ็กต์คอลเล็กชันที่มีต้นทุนต่ำ ประเภทถูกต้อง และแก้ไขไม่ได้
ในกรณีที่ระบบรองรับคําอธิบายประกอบประเภท ให้ใช้ @NonNull
สําหรับองค์ประกอบของคอลเล็กชันเสมอ
นอกจากนี้ คุณควรใช้ @NonNull
เมื่อใช้อาร์เรย์แทนคอลเล็กชัน (ดูรายการก่อนหน้า) หากมีข้อกังวลเกี่ยวกับการจองพื้นที่สำหรับออบเจ็กต์ ให้สร้างค่าคงที่และส่งต่อ ท้ายที่สุดแล้ว อาร์เรย์ว่างจะแก้ไขไม่ได้ ตัวอย่าง
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;
}
ความสามารถในการเปลี่ยนแปลงของคอลเล็กชัน
API ของ Kotlin ควรใช้ประเภทผลลัพธ์แบบอ่านอย่างเดียว (ไม่ใช่ Mutable
) สำหรับคอลเล็กชันโดยค่าเริ่มต้น เว้นแต่สัญญา API จะกำหนดให้ใช้ประเภทผลลัพธ์ที่เปลี่ยนแปลงได้โดยเฉพาะ
อย่างไรก็ตาม Java API ควรใช้ประเภทผลลัพธ์ที่เปลี่ยนแปลงได้โดยค่าเริ่มต้น เนื่องจากการใช้งาน Java API บนแพลตฟอร์ม Android ยังไม่รองรับการใช้งานคอลเล็กชันแบบคงที่ ข้อยกเว้นสำหรับกฎข้อนี้คือCollections.empty
ประเภทผลลัพธ์ ซึ่งแก้ไขไม่ได้ ในกรณีที่ลูกค้าอาจใช้ประโยชน์จากความสามารถในการเปลี่ยนแปลงได้ ไม่ว่าจะตั้งใจหรือไม่ตั้งใจ เพื่อทำลายรูปแบบการใช้งานที่ API ตั้งใจไว้ Java API ควรพิจารณาแสดงผลสำเนาระดับตื้นของคอลเล็กชัน
@Nullable
public PermissionInfo[] getGrantedPermissions() {
return mPermissions;
}
@NonNull
public Set<PermissionInfo> getGrantedPermissions() {
if (mPermissions == null) {
return Collections.emptySet();
}
return new ArraySet<>(mPermissions);
}
ประเภทผลลัพธ์ที่เปลี่ยนแปลงได้อย่างชัดเจน
API ที่แสดงผลคอลเล็กชันไม่ควรแก้ไขออบเจ็กต์คอลเล็กชันที่แสดงผลหลังจากแสดงผล หากคอลเล็กชันที่แสดงผลต้องเปลี่ยนแปลงหรือนํามาใช้ซ้ำในลักษณะใดลักษณะหนึ่ง เช่น มุมมองที่ปรับให้เหมาะกับชุดข้อมูลที่เปลี่ยนแปลงได้ คุณต้องบันทึกลักษณะการทํางานที่ชัดเจนของเวลาที่เนื้อหาจะเปลี่ยนแปลง หรือปฏิบัติตามรูปแบบการตั้งชื่อ API ที่กำหนดไว้
/**
* Returns a view of this object as a list of [Item]s.
*/
fun MyObject.asList(): List<Item> = MyObjectListWrapper(this)
รูปแบบ .asFoo()
ของ Kotlin มีคำอธิบายอยู่ด้านล่าง และอนุญาตให้คอลเล็กชันที่ .asList()
แสดงผลเปลี่ยนแปลงได้หากคอลเล็กชันเดิมมีการเปลี่ยนแปลง
ความสามารถในการเปลี่ยนแปลงของออบเจ็กต์ประเภทข้อมูลที่แสดงผล
API ที่แสดงผลออบเจ็กต์ประเภทข้อมูลควรไม่แก้ไขพร็อพเพอร์ตี้ของออบเจ็กต์ที่แสดงผลหลังจากแสดงผลแล้ว ซึ่งคล้ายกับ API ที่แสดงผลคอลเล็กชัน
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)
}
ในบางกรณีที่น้อยมาก โค้ดที่ไวต่อประสิทธิภาพบางรายการอาจได้รับประโยชน์จากการรวมหรือนําวัตถุมาใช้ซ้ำ อย่าเขียนโครงสร้างข้อมูลพูลออบเจ็กต์ของคุณเอง และอย่าแสดงออบเจ็กต์ที่นํามาใช้ซ้ำใน API สาธารณะ ไม่ว่าจะเลือกแบบใดก็ตาม ให้ระมัดระวังอย่างยิ่งในการจัดการการเข้าถึงพร้อมกัน
การใช้ประเภทพารามิเตอร์ vararg
เราขอแนะนำให้ทั้ง Kotlin และ Java API ใช้ vararg
ในกรณีที่นักพัฒนาซอฟต์แวร์มีแนวโน้มที่จะสร้างอาร์เรย์ที่ตำแหน่งการเรียกใช้เพื่อวัตถุประสงค์เดียวในการส่งพารามิเตอร์ที่เกี่ยวข้องหลายรายการของประเภทเดียวกัน
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);
สำเนาการป้องกัน
การใช้งานพารามิเตอร์ vararg
ทั้งของ Java และ Kotlin จะคอมไพล์เป็นไบต์โค้ดที่สนับสนุนอาร์เรย์เดียวกัน และอาจเรียกใช้จากโค้ด Java ที่มีอาร์เรย์แบบปรับเปลี่ยนได้ เราขอแนะนําอย่างยิ่งให้นักออกแบบ API สร้างสำเนาระดับตื้นแบบป้องกันของพารามิเตอร์อาร์เรย์ในกรณีที่ระบบจะเก็บพารามิเตอร์ดังกล่าวไว้ในช่องหรือคลาสภายในแบบไม่ระบุชื่อ
public void setValues(SomeObject... values) {
this.values = Arrays.copyOf(values, values.length);
}
โปรดทราบว่าการสร้างสำเนาเพื่อป้องกันไม่ได้ให้การปกป้องใดๆ กับการแก้ไขพร้อมกันระหว่างการเรียกใช้เมธอดครั้งแรกกับการสร้างสำเนา และไม่ป้องกันการกลายพันธุ์ของออบเจ็กต์ที่อยู่ในอาร์เรย์
ระบุความหมายที่ถูกต้องด้วยพารามิเตอร์ประเภทคอลเล็กชันหรือประเภทที่แสดงผล
List<Foo>
คือตัวเลือกเริ่มต้น แต่ให้พิจารณาประเภทอื่นๆ เพื่อระบุความหมายเพิ่มเติม
ใช้
Set<Foo>
หาก API ไม่สนใจลําดับขององค์ประกอบ ไม่อนุญาตให้มีรายการที่ซ้ำกัน หรือรายการที่ซ้ำกันไม่มีความหมายCollection<Foo>,
หาก API ของคุณไม่สนใจลําดับและอนุญาตให้มีรายการที่ซ้ำกัน
ฟังก์ชัน Conversion ของ Kotlin
Kotlin ใช้ .toFoo()
และ .asFoo()
บ่อยครั้งเพื่อรับออบเจ็กต์ประเภทอื่นจากออบเจ็กต์ที่มีอยู่ โดยที่ Foo
คือชื่อของประเภทผลลัพธ์ของการเปลี่ยนรูปแบบ ซึ่งสอดคล้องกับ JDK ที่ทุกคนคุ้นเคย
Object.toString()
Kotlin พัฒนาแนวคิดนี้ไปอีกขั้นด้วยการใช้รูปแบบนี้กับการเปลี่ยนรูปแบบพื้นฐาน เช่น 25.toFloat()
ความแตกต่างระหว่าง Conversion ที่ชื่อ .toFoo()
กับ .asFoo()
มีความสำคัญดังนี้
ใช้ .toFoo() เมื่อสร้างออบเจ็กต์ใหม่ที่เป็นอิสระ
เช่นเดียวกับ .toString()
การแปลง "เป็น" จะแสดงผลออบเจ็กต์ใหม่ที่เป็นอิสระ หากมีการแก้ไขออบเจ็กต์เดิมในภายหลัง ออบเจ็กต์ใหม่จะไม่แสดงการเปลี่ยนแปลงเหล่านั้น
ในทํานองเดียวกัน หากมีการแก้ไขออบเจ็กต์ใหม่ในภายหลัง ออบเจ็กต์เก่าจะไม่แสดงการเปลี่ยนแปลงเหล่านั้น
fun Foo.toBundle(): Bundle = Bundle().apply {
putInt(FOO_VALUE_KEY, value)
}
ใช้ .asFoo() เมื่อสร้าง Wrapper แบบมีเงื่อนไข ออบเจ็กต์ที่มีการตกแต่ง หรือแคสต์
การแคสต์ใน Kotlin ทำได้โดยใช้คีย์เวิร์ด as
การเปลี่ยนแปลงนี้แสดงถึงการเปลี่ยนแปลงอินเทอร์เฟซ แต่ไม่ได้แสดงถึงการเปลี่ยนแปลงตัวตน เมื่อใช้เป็นคำนำหน้าในฟังก์ชันส่วนขยาย .asFoo()
จะตกแต่งตัวรับ การเปลี่ยนแปลงในออบเจ็กต์ตัวรับเดิมจะแสดงในออบเจ็กต์ที่ asFoo()
แสดงผล
การเปลี่ยนแปลงในFoo
ออบเจ็กต์ใหม่อาจแสดงในออบเจ็กต์เดิม
fun <T> Flow<T>.asLiveData(): LiveData<T> = liveData {
collect {
emit(it)
}
}
ฟังก์ชัน Conversion ควรเขียนเป็นฟังก์ชันส่วนขยาย
การเขียนฟังก์ชันการแปลงที่อยู่นอกทั้งตัวรับและคำจำกัดความของคลาสผลลัพธ์จะลดการเชื่อมโยงระหว่างประเภท Conversion ที่เหมาะสําหรับคุณต้องมีเพียงสิทธิ์เข้าถึง API สาธารณะสําหรับออบเจ็กต์เดิม ตัวอย่างนี้แสดงให้เห็นว่านักพัฒนาซอฟต์แวร์สามารถเขียน Conversion ที่คล้ายกับประเภทที่ต้องการได้เช่นกัน
ส่งข้อยกเว้นที่เฉพาะเจาะจงตามความเหมาะสม
เมธอดต้องไม่แสดงข้อยกเว้นทั่วไป เช่น java.lang.Exception
หรือ java.lang.Throwable
แต่ต้องใช้ข้อยกเว้นที่เฉพาะเจาะจงที่เหมาะสม เช่น java.lang.NullPointerException
เพื่อให้นักพัฒนาแอปจัดการข้อยกเว้นได้โดยไม่กว้างเกินไป
ข้อผิดพลาดที่ไม่เกี่ยวข้องกับอาร์กิวเมนต์ที่ส่งไปยังเมธอดที่เรียกใช้แบบสาธารณะโดยตรงควรแสดง java.lang.IllegalStateException
แทน java.lang.IllegalArgumentException
หรือ java.lang.NullPointerException
Listeners และ Callback
กฎเหล่านี้เกี่ยวข้องกับคลาสและเมธอดที่ใช้สำหรับกลไกของ Listener และ Callback
ชื่อคลาสการเรียกกลับควรเป็นเอกพจน์
ใช้ MyObjectCallback
แทน MyObjectCallbacks
ชื่อเมธอดการเรียกกลับควรอยู่ในรูปแบบ on
onFooEvent
หมายความว่า FooEvent
กำลังเกิดขึ้นและควรมีการเรียกกลับเพื่อดำเนินการตอบสนอง
กาลอดีตกับกาลปัจจุบันควรอธิบายลักษณะการกําหนดเวลา
เมธอด Callback ที่เกี่ยวข้องกับเหตุการณ์ควรตั้งชื่อเพื่อระบุว่าเหตุการณ์เกิดขึ้นแล้วหรือกำลังเกิดขึ้น
ตัวอย่างเช่น หากเรียกใช้เมธอดหลังจากดำเนินการคลิกแล้ว
public void onClicked()
อย่างไรก็ตาม หากเมธอดมีหน้าที่รับผิดชอบในการดําเนินการคลิก ให้ทําดังนี้
public boolean onClick()
การลงทะเบียนการโทรกลับ
เมื่อเพิ่มหรือนํา Listener หรือ Callback ออกจากออบเจ็กต์ได้ วิธีการที่เกี่ยวข้องควรตั้งชื่อว่า add และ remove หรือ register และ unregister สอดคล้องกับรูปแบบที่มีอยู่ซึ่งคลาสหรือคลาสอื่นๆ ใช้อยู่ในแพ็กเกจเดียวกัน เมื่อไม่มีแบบอย่างดังกล่าว ให้เลือกเพิ่มและนำออก
เมธอดที่เกี่ยวข้องกับการลงทะเบียนหรือการยกเลิกการลงทะเบียนการเรียกกลับควรระบุชื่อแบบเต็มของประเภทการเรียกกลับ
public void addFooCallback(@NonNull FooCallback callback);
public void removeFooCallback(@NonNull FooCallback callback);
public void registerFooCallback(@NonNull FooCallback callback);
public void unregisterFooCallback(@NonNull FooCallback callback);
หลีกเลี่ยงการใช้ Geter สําหรับการเรียกกลับ
อย่าเพิ่มวิธีการ getFooCallback()
นี่เป็นทางออกที่น่าสนใจสำหรับกรณีที่นักพัฒนาซอฟต์แวร์อาจต้องการใช้การเรียกกลับที่มีอยู่ร่วมกับการแทนที่ของตนเอง แต่วิธีนี้ไม่เสถียรและทำให้นักพัฒนาคอมโพเนนต์คาดเดาสถานะปัจจุบันได้ยาก ตัวอย่างเช่น
- นักพัฒนาแอป ก โทรหา
setFooCallback(a)
- นักพัฒนาแอป ข โทรหา
setFooCallback(new B(getFooCallback()))
- นักพัฒนาแอป ก ต้องการนําการเรียกกลับ
a
ออก แต่ทําไม่ได้เนื่องจากไม่ทราบประเภทของB
และB
สร้างขึ้นเพื่ออนุญาตให้แก้ไขการเรียกกลับที่รวมไว้
ยอมรับ Executor เพื่อควบคุมการส่งออกการเรียกกลับ
เมื่อลงทะเบียนการเรียกกลับที่ไม่มีการกำหนดการแยกชุดข้อความอย่างชัดเจน (เกือบทุกที่นอกเครื่องมือชุด UI) เราขอแนะนำให้ใส่พารามิเตอร์ Executor
เป็นส่วนหนึ่งของการลงทะเบียนเพื่อให้นักพัฒนาแอประบุชุดข้อความที่จะเรียกใช้การเรียกกลับได้
public void registerFooCallback(
@NonNull @CallbackExecutor Executor executor,
@NonNull FooCallback callback)
เพื่อเป็นการยกเว้นหลักเกณฑ์เกี่ยวกับพารามิเตอร์ที่ไม่บังคับตามปกติ เราอนุญาตให้ระบุการโอเวอร์โหลดโดยละเว้น Executor
แม้ว่าจะไม่ใช่อาร์กิวเมนต์สุดท้ายในรายการพารามิเตอร์ก็ตาม หากไม่ได้ระบุ Executor
ระบบจะเรียกใช้การเรียกคืนในเธรดหลักโดยใช้ Looper.getMainLooper()
และควรบันทึกไว้ในเมธอดที่โอเวอร์โหลดที่เกี่ยวข้อง
/**
* ...
* 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)
Executor
ข้อควรทราบในการติดตั้งใช้งาน: โปรดทราบว่าตัวดำเนินการต่อไปนี้ถูกต้อง
public class SynchronousExecutor implements Executor {
@Override
public void execute(Runnable r) {
r.run();
}
}
ซึ่งหมายความว่าเมื่อติดตั้งใช้งาน API ที่ใช้รูปแบบนี้ การติดตั้งใช้งานออบเจ็กต์ Binder ที่เข้ามาทางด้านกระบวนการของแอปต้องเรียกใช้ Binder.clearCallingIdentity()
ก่อนเรียกใช้การเรียกกลับของแอปใน Executor
ที่ได้จากแอป วิธีนี้ช่วยให้โค้ดแอปที่ใช้ข้อมูลประจำตัว Binder (เช่น Binder.getCallingUid()
) สำหรับการตรวจสอบสิทธิ์ระบุแหล่งที่มาของโค้ดที่ทำงานในแอปอย่างถูกต้อง ไม่ใช่กระบวนการของระบบที่เรียกใช้แอป หากผู้ใช้ API ต้องการข้อมูล UID หรือ PID ของผู้เรียกใช้ ข้อมูลนี้ควรเป็นส่วนหนึ่งของอินเทอร์เฟซ API อย่างชัดเจน ไม่ใช่ระบุโดยนัยตามตำแหน่งที่Executor
ที่ระบุทำงาน
API ของคุณควรรองรับการระบุ Executor
ในบางกรณีที่ส่งผลต่อประสิทธิภาพของแอปอย่างมาก แอปอาจต้องเรียกใช้โค้ดทันทีหรือทำงานพร้อมกันกับความคิดเห็นจาก API การยอมรับExecutor
จะอนุญาตการดำเนินการนี้
การสร้าง HandlerThread
เพิ่มเติมหรือรายการที่คล้ายกับแทรมโพลีนเพื่อการป้องกันเป็นการทำลาย Use Case ที่ต้องการนี้
หากแอปจะเรียกใช้โค้ดที่มีราคาแพงในกระบวนการของตนเอง โปรดอนุญาต การแก้ปัญหาชั่วคราวที่นักพัฒนาแอปจะพบเพื่อฝ่าฝืนข้อจำกัดของคุณจะได้รับการรองรับได้ยากขึ้นมากในระยะยาว
ข้อยกเว้นสําหรับการเรียกกลับรายการเดียว: เมื่อลักษณะของเหตุการณ์ที่รายงานเรียกร้องให้รองรับอินสแตนซ์การเรียกกลับรายการเดียว ให้ใช้รูปแบบต่อไปนี้
public void setFooCallback(
@NonNull @CallbackExecutor Executor executor,
@NonNull FooCallback callback)
public void clearFooCallback()
ใช้ Executor แทน Handler
ก่อนหน้านี้ Android ใช้ Handler
เป็นมาตรฐานในการเปลี่ยนเส้นทางการเรียกกลับไปยังเธรด Looper
ที่เฉพาะเจาะจง มาตรฐานนี้เปลี่ยนเป็นExecutor
เนื่องจากนักพัฒนาแอปส่วนใหญ่จัดการพูลเธรดของตนเอง ทำให้เธรดหลักหรือเธรด UI เป็นเธรด Looper
เพียงรายการเดียวที่แอปใช้ได้ ใช้ Executor
เพื่อให้นักพัฒนาแอปมีการควบคุมที่จำเป็นในการนําบริบทการดําเนินการที่มีอยู่/ต้องการกลับมาใช้ใหม่
ไลบรารีการทำงานพร้อมกันสมัยใหม่อย่าง kotlinx.coroutines หรือ RxJava มีกลไกการจัดตารางเวลาของตัวเองซึ่งจะดำเนินการส่งออกของตนเองเมื่อจำเป็น จึงจำเป็นต้องมีความสามารถในการใช้ผู้ดำเนินการโดยตรง (เช่น Runnable::run
) เพื่อหลีกเลี่ยงเวลาในการตอบสนองจากการข้ามเธรด 2 ครั้ง เช่น ฮอปเดียวเพื่อโพสต์ไปยังชุดข้อความ Looper
โดยใช้ Handler
ตามด้วยฮอปอีกรายการจากเฟรมเวิร์กการทำงานพร้อมกันของแอป
ข้อยกเว้นสำหรับหลักเกณฑ์นี้เกิดขึ้นได้น้อยมาก การอุทธรณ์เพื่อขอรับข้อยกเว้นที่พบบ่อยมีดังนี้
ฉันต้องใช้ Looper
เนื่องจากฉันต้องการ Looper
เพื่อ epoll
สำหรับกิจกรรม
คำขอยกเว้นนี้ได้รับอนุมัติเนื่องจากไม่สามารถรับสิทธิประโยชน์ของ Executor
ในสถานการณ์นี้
ฉันไม่ต้องการให้โค้ดแอปบล็อกการเผยแพร่เหตุการณ์ของชุดข้อความ โดยทั่วไปแล้ว คำขอการยกเว้นนี้ไม่ได้รับอนุญาตสำหรับโค้ดที่ทำงานในกระบวนการของแอป แอปที่ดำเนินการไม่ถูกต้องจะส่งผลเสียต่อตัวแอปเองเท่านั้น โดยไม่ส่งผลต่อภาพรวมของระบบ แอปที่ดำเนินการอย่างถูกต้องหรือใช้เฟรมเวิร์กการทำงานพร้อมกันทั่วไปไม่ควรต้องเสียค่าปรับเพิ่มเติมสำหรับเวลาในการตอบสนอง
Handler
สอดคล้องกับ API อื่นๆ ที่คล้ายกันในคลาสเดียวกัน
คำขอข้อยกเว้นนี้จะได้รับการอนุมัติตามสถานการณ์ แนะนำให้เพิ่มการโอเวอร์โหลดตาม Executor
เพื่อย้ายข้อมูลการใช้งาน Handler
ไปใช้การใช้งาน Executor
ใหม่ (myHandler::post
ถูกต้อง
Executor
!) ระบบอาจให้ข้อยกเว้นในการเพิ่มเมธอดใหม่ที่ใช้ Handler
ขึ้นอยู่กับขนาดของคลาส จำนวนเมธอด Handler
ที่มีอยู่ และแนวโน้มที่นักพัฒนาซอฟต์แวร์จะต้องใช้เมธอดที่ใช้ Handler
ที่มีอยู่ควบคู่ไปกับเมธอดใหม่
สมมาตรในการจดทะเบียน
หากมีวิธีเพิ่มหรือลงทะเบียนสิ่งใดสิ่งหนึ่ง ก็ควรมีวิธีนำออก/ยกเลิกการลงทะเบียนด้วย วิธีการ
registerThing(Thing)
ควรมี
unregisterThing(Thing)
ระบุตัวระบุคำขอ
หากนักพัฒนาแอปใช้การเรียกกลับซ้ำได้ ให้ระบุออบเจ็กต์ตัวระบุเพื่อเชื่อมโยงการเรียกกลับกับคำขอ
class RequestParameters {
public int getId() { ... }
}
class RequestExecutor {
public void executeRequest(
RequestParameters parameters,
Consumer<RequestParameters> onRequestCompletedListener) { ... }
}
ออบเจ็กต์การเรียกคืนหลายเมธอด
Callbacks แบบหลายเมธอดควรใช้เมธอด interface
และใช้เมธอด default
เมื่อเพิ่มลงในอินเทอร์เฟซที่เผยแพร่ก่อนหน้านี้ ก่อนหน้านี้ หลักเกณฑ์นี้แนะนำให้ใช้ abstract class
เนื่องจาก Java 7 ไม่มีเมธอด default
public interface MostlyOptionalCallback {
void onImportantAction();
default void onOptionalInformation() {
// Empty stub, this method is optional.
}
}
ใช้ android.os.OutcomeReceiver เมื่อประมาณการการเรียกฟังก์ชันแบบไม่บล็อก
OutcomeReceiver<R,E>
แสดงค่าผลลัพธ์ R
เมื่อดำเนินการสำเร็จ หรือ E : Throwable
เมื่อดำเนินการไม่สำเร็จ ซึ่งเหมือนกับการเรียกใช้เมธอดธรรมดา โปรดใช้ OutcomeReceiver
เป็นประเภทการเรียกกลับเมื่อแปลงเมธอดแบบบล็อกที่แสดงผลลัพธ์หรือยกเว้นข้อยกเว้นเป็นเมธอดแบบแอสซิงค์แบบไม่บล็อก ดังนี้
interface FooType {
// Before:
public FooResult requestFoo(FooRequest request);
// After:
public void requestFooAsync(FooRequest request, Executor executor,
OutcomeReceiver<FooResult, Throwable> callback);
}
เมธอดแบบแอซิงโครนัสที่แปลงด้วยวิธีนี้จะแสดงผลเป็น void
เสมอ ระบบจะรายงานผลลัพธ์ที่ requestFoo
แสดงแทนไปยัง OutcomeReceiver.onResult
ของพารามิเตอร์ callback
ของ requestFooAsync
โดยการเรียกใช้พารามิเตอร์นั้นใน executor
ที่ระบุ
ระบบจะรายงานข้อยกเว้นที่ requestFoo
จะแสดงไปยังเมธอด OutcomeReceiver.onError
ในลักษณะเดียวกัน
การใช้ OutcomeReceiver
เพื่อรายงานผลลัพธ์ของเมธอดแบบแอ็กซิงโครนัสยังมี Wrapper แบบ Kotlinsuspend fun
สำหรับเมธอดแบบแอ็กซิงโครนัสที่ใช้ส่วนขยาย Continuation.asOutcomeReceiver
จาก androidx.core:core-ktx
อีกด้วย
suspend fun FooType.requestFoo(request: FooRequest): FooResult =
suspendCancellableCoroutine { continuation ->
requestFooAsync(request, Runnable::run, continuation.asOutcomeReceiver())
}
ส่วนขยายเช่นนี้ช่วยให้ไคลเอ็นต์ Kotlin สามารถเรียกใช้เมธอดแบบแอ็กซิงโครนัสแบบไม่บล็อกได้ด้วยความสะดวกของการเรียกใช้ฟังก์ชันธรรมดาโดยไม่ต้องบล็อกเธรดเรียกใช้ ส่วนขยายแบบ 1:1 สำหรับ API ของแพลตฟอร์มเหล่านี้อาจรวมอยู่ในandroidx.core:core-ktx
อาร์ติแฟกต์ใน Jetpack เมื่อใช้ร่วมกับการตรวจสอบและการพิจารณาความเข้ากันได้ของเวอร์ชันมาตรฐาน ดูข้อมูลเพิ่มเติม ข้อควรพิจารณาเกี่ยวกับการยกเลิก และตัวอย่างได้ที่เอกสารประกอบของ asOutcomeReceiver
วิธีการแบบแอ็กซิงโครนัสที่ไม่ตรงกับความหมายของวิธีการที่แสดงผลลัพธ์หรือแสดงข้อยกเว้นเมื่อทํางานเสร็จแล้วไม่ควรใช้ OutcomeReceiver
เป็นประเภทการเรียกกลับ โปรดพิจารณาใช้ตัวเลือกอื่นๆ ที่ระบุไว้ในส่วนต่อไปนี้แทน
เลือกใช้อินเทอร์เฟซแบบฟังก์ชันมากกว่าการสร้างเมธอดนามธรรมเดี่ยว (SAM) ประเภทใหม่
API ระดับ 24 เพิ่มประเภท java.util.function.*
(เอกสารอ้างอิง) ซึ่งมีอินเทอร์เฟซ SAM ทั่วไป เช่น Consumer<T>
ซึ่งเหมาะสำหรับใช้เป็นแลมดาการเรียกกลับ ในหลายกรณี การสร้างอินเทอร์เฟซ SAM ใหม่จะมีประโยชน์น้อยมากในแง่ของความปลอดภัยของประเภทหรือการสื่อสารความตั้งใจ ในขณะที่ขยายพื้นที่การทำงานของ Android API โดยไม่จำเป็น
ลองใช้อินเทอร์เฟซทั่วไปเหล่านี้แทนการสร้างอินเทอร์เฟซใหม่
Runnable
:() -> Unit
Supplier<R>
:() -> R
Consumer<T>
:(T) -> Unit
Function<T,R>
:(T) -> R
Predicate<T>
:(T) -> Boolean
- มีอีกมากมายในเอกสารอ้างอิง
ตําแหน่งพารามิเตอร์ SAM
พารามิเตอร์ SAM ควรอยู่ท้ายสุดเพื่อให้ใช้รูปแบบภาษาจาก Kotlin ได้ แม้ว่าจะมีการเพิ่มพารามิเตอร์อื่นๆ ลงในเมธอดก็ตาม
public void schedule(Runnable runnable)
public void schedule(int delay, Runnable runnable)
เอกสาร
กฎเหล่านี้เกี่ยวกับเอกสารสาธารณะ (Javadoc) สําหรับ API
ต้องจัดทำเอกสารประกอบ API สาธารณะทั้งหมด
API สาธารณะทั้งหมดต้องมีเอกสารประกอบที่เพียงพอเพื่ออธิบายวิธีที่นักพัฒนาแอปจะใช้ API สมมติว่านักพัฒนาซอฟต์แวร์พบเมธอดโดยใช้ฟีเจอร์เติมข้อความอัตโนมัติหรือขณะเรียกดูเอกสารอ้างอิง API และมีบริบทเพียงเล็กน้อยจากแพลตฟอร์ม API ที่อยู่ใกล้เคียง (เช่น คลาสเดียวกัน)
วิธีการ
ต้องบันทึกพารามิเตอร์ของเมธอดและค่าที่แสดงผลโดยใช้แอตทริบิวต์เอกสาร @param
และ @return
ตามลำดับ จัดรูปแบบเนื้อหา Javadoc ราวกับว่าอยู่หน้า "วิธีการนี้..."
ในกรณีที่เมธอดไม่ใช้พารามิเตอร์ ไม่มีข้อควรพิจารณาพิเศษ และแสดงผลตามที่ชื่อเมธอดระบุไว้ คุณก็ละเว้น @return
และเขียนเอกสารได้ดังนี้
/**
* Returns the priority of the thread.
*/
@IntRange(from = 1, to = 10)
public int getPriority() { ... }
ใช้ลิงก์ใน Javadoc เสมอ
เอกสารควรลิงก์กับเอกสารอื่นๆ สำหรับค่าคงที่ เมธอด และองค์ประกอบอื่นๆ ที่เกี่ยวข้อง ใช้แท็ก Javadoc (เช่น @see
และ {@link foo}
) ไม่ใช่แค่คำที่เป็นข้อความธรรมดา
สําหรับตัวอย่างแหล่งที่มาต่อไปนี้
public static final int FOO = 0;
public static final int BAR = 1;
อย่าใช้ข้อความดิบหรือแบบอักษรโค้ด
/**
* 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) { ... }
แต่ให้ใช้ลิงก์แทน
/**
* Sets value to one of {@link #FOO} or {@link #BAR}.
*
* @param value the value being set
*/
public void setValue(@ValueType int value) { ... }
โปรดทราบว่าการใช้คำอธิบายประกอบ IntDef
เช่น @ValueType
ในพารามิเตอร์จะสร้างเอกสารประกอบที่ระบุประเภทที่อนุญาตโดยอัตโนมัติ ดูข้อมูลเพิ่มเติมเกี่ยวกับ IntDef
ได้จากคำแนะนำเกี่ยวกับคำอธิบายประกอบ
เรียกใช้ update-api หรือ docs target เมื่อเพิ่ม Javadoc
กฎนี้สำคัญอย่างยิ่งเมื่อเพิ่มแท็ก @link
หรือ @see
และตรวจสอบว่าเอาต์พุตมีลักษณะตามที่คาดไว้ เอาต์พุต ERROR ใน Javadoc มักเกิดจากลิงก์ที่ไม่ถูกต้อง ทั้งเป้าหมาย update-api
หรือ docs
Make จะดำเนินการตรวจสอบนี้ แต่เป้าหมาย docs
อาจเร็วกว่าหากคุณเปลี่ยนเฉพาะ Javadoc และไม่จำเป็นต้องเรียกใช้เป้าหมาย update-api
ใช้ {@code foo} เพื่อแยกแยะค่า Java
ตัดค่า Java เช่น true
, false
และ null
ขึ้นบรรทัดใหม่ด้วย {@code...}
เพื่อแยกค่าดังกล่าวออกจากข้อความในเอกสารประกอบ
เมื่อเขียนเอกสารประกอบในซอร์สโค้ด Kotlin คุณสามารถตัดโค้ดด้วยเครื่องหมายแบ็กทิกได้เช่นเดียวกับที่ใช้กับมาร์กดาวน์
สรุป @param และ @return ควรเป็นประโยคสั้นๆ ประโยคเดียว
ข้อมูลสรุปพารามิเตอร์และค่าที่แสดงผลควรขึ้นต้นด้วยอักขระตัวพิมพ์เล็กและต้องมีเพียงประโยคเดียว หากมีข้อมูลเพิ่มเติมที่ยาวเกินกว่า 1 ประโยค ให้ย้ายข้อมูลดังกล่าวไปยังเนื้อหา Javadoc ของเมธอด ดังนี้
/**
* @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.
*/
ควรเปลี่ยนเป็น
/**
* @param e element to be appended to this list, must be non-{@code null}
* @return {@code true} on success, {@code false} otherwise
*/
คำอธิบายประกอบของเอกสารต้องมีคำอธิบาย
อธิบายสาเหตุที่คำอธิบายประกอบ @hide
และ @removed
ซ่อนอยู่จาก API สาธารณะ
ระบุวิธีการแทนที่องค์ประกอบ API ที่ทำเครื่องหมายด้วยคำอธิบายประกอบ @deprecated
ใช้ @throws เพื่อบันทึกข้อยกเว้น
หากเมธอดส่งข้อยกเว้นที่ตรวจสอบได้ เช่น IOException
ให้บันทึกข้อยกเว้นด้วย @throws
สําหรับ API ที่มาของ Kotlin ที่มีไว้สําหรับไคลเอ็นต์ Java ให้ใส่คำอธิบายประกอบฟังก์ชันด้วย @Throws
หากเมธอดส่งข้อยกเว้นที่ไม่มีการระบุซึ่งบ่งบอกถึงข้อผิดพลาดที่ป้องกันได้ เช่น IllegalArgumentException
หรือ IllegalStateException
ให้บันทึกข้อยกเว้นพร้อมคำอธิบายสาเหตุที่ระบบส่งข้อยกเว้น ข้อยกเว้นที่แสดงควรระบุสาเหตุที่แสดงด้วย
ในกรณีที่มีข้อยกเว้นที่ไม่มีการระบุ บางอย่างจะถือว่าไม่ได้ระบุไว้อย่างชัดเจนและไม่จำเป็นต้องมีการบันทึก เช่น NullPointerException
หรือ IllegalArgumentException
ในกรณีที่อาร์กิวเมนต์ไม่ตรงกับ @IntDef
หรือคำอธิบายประกอบที่คล้ายกันซึ่งฝังสัญญา API ไว้ในลายเซ็นเมธอด
/**
* ...
* @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.");
}
// ...
หรือใน 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")
}
}
// ...
หากเมธอดเรียกใช้โค้ดแบบแอซิงโครนัสที่อาจทำให้เกิดข้อยกเว้น ให้พิจารณาว่านักพัฒนาแอปจะรู้และตอบสนองต่อข้อยกเว้นดังกล่าวได้อย่างไร โดยปกติแล้ว การดำเนินการนี้เกี่ยวข้องกับการส่งต่อข้อยกเว้นไปยังการเรียกกลับและบันทึกข้อยกเว้นที่ส่งกลับในเมธอดที่รับ ข้อยกเว้นแบบไม่พร้อมกันไม่ควรได้รับการบันทึกด้วย @throws
เว้นแต่ว่าจะมีการโยนข้อยกเว้นเหล่านั้นอีกครั้งจากเมธอดที่มีคำอธิบายประกอบ
จบประโยคแรกของเอกสารด้วยเครื่องหมายจุด
เครื่องมือ Doclava จะแยกวิเคราะห์เอกสารอย่างง่ายดาย โดยจะสิ้นสุดเอกสารข้อมูลสรุป (ประโยคแรกที่ใช้ในคำอธิบายสั้นๆ ที่ด้านบนของเอกสารของชั้นเรียน) ทันทีที่พบเครื่องหมายจุด (.) ตามด้วยเว้นวรรค ซึ่งทำให้เกิดปัญหา 2 อย่าง ดังนี้
- หากเอกสารสั้นๆ ไม่ได้ลงท้ายด้วยเครื่องหมายจุด และหากสมาชิกรายนั้นรับเอกสารที่เครื่องมือเลือกไว้ ข้อมูลสรุปก็จะรับเอกสารที่รับมาเหล่านั้นด้วย เช่น ดู
actionBarTabStyle
ในR.attr
docs ซึ่งมีคำอธิบายของมิติข้อมูลที่เพิ่มลงในข้อมูลสรุป - หลีกเลี่ยงการใช้ "เช่น" ในประโยคแรกด้วยเหตุผลเดียวกัน เนื่องจาก Doclava จะสิ้นสุดเอกสารข้อมูลสรุปหลัง "เช่น" เช่น ดู
TEXT_ALIGNMENT_CENTER
ในView.java
โปรดทราบว่า Metalava จะแก้ไขข้อผิดพลาดนี้โดยอัตโนมัติด้วยการแทรกการเว้นวรรคที่ไม่แบ่งบรรทัดไว้หลังจุด แต่คุณก็ไม่ควรทำผิดพลาดนี้ตั้งแต่แรก
จัดรูปแบบเอกสารให้แสดงผลเป็น HTML
Javadoc จะแสดงผลเป็น HTML ดังนั้นให้จัดรูปแบบเอกสารเหล่านี้ตามรูปแบบดังกล่าว
การขึ้นบรรทัดใหม่ควรใช้แท็ก
<p>
ที่ชัดเจน อย่าเพิ่มแท็ก</p>
แบบปิดอย่าใช้ ASCII เพื่อแสดงผลรายการหรือตาราง
รายการควรใช้
<ul>
หรือ<ol>
สำหรับรายการที่ไม่เรียงลำดับและรายการที่เรียงลำดับตามลำดับ แต่ละรายการควรขึ้นต้นด้วยแท็ก<li>
แต่ไม่จำเป็นต้องมีแท็ก</li>
ปิด ต้องใส่แท็ก</ul>
หรือ</ol>
ต่อท้ายรายการสุดท้ายตารางควรใช้
<table>
,<tr>
สำหรับแถว,<th>
สำหรับส่วนหัว และ<td>
สำหรับเซลล์ แท็กตารางทั้งหมดต้องมีแท็กปิดที่ตรงกัน คุณใช้class="deprecated"
ในแท็กใดก็ได้เพื่อระบุว่าเลิกใช้งานหากต้องการสร้างแบบอักษรโค้ดแบบแทรกในบรรทัด ให้ใช้
{@code foo}
หากต้องการสร้างโค้ดบล็อก ให้ใช้
<pre>
เบราว์เซอร์จะแยกวิเคราะห์ข้อความทั้งหมดภายในบล็อก
<pre>
ดังนั้นโปรดระมัดระวังเรื่องวงเล็บ<>
คุณสามารถหลีกหนีได้โดยการใช้เอนทิตี HTML<
และ>
หรือจะปล่อยวงเล็บเหลี่ยมแบบไม่ผ่านการจัดรูปแบบ
<>
ไว้ในข้อมูลโค้ดก็ได้หากห่อส่วนที่เป็นปัญหาไว้ใน{@code foo}
เช่น<pre>{@code <manifest>}</pre>
ปฏิบัติตามคู่มือสไตล์การอ้างอิง API
โปรดทำตามคำแนะนำในหลักเกณฑ์ภาษา Java อย่างเป็นทางการที่หัวข้อวิธีเขียนความคิดเห็นแบบเอกสารสำหรับเครื่องมือ Javadoc เพื่อให้รูปแบบของข้อมูลสรุปของคลาส คำอธิบายเมธอด คำอธิบายพารามิเตอร์ และรายการอื่นๆ สอดคล้องกัน
กฎเฉพาะเฟรมเวิร์ก Android
กฎเหล่านี้เกี่ยวข้องกับ API, รูปแบบ และโครงสร้างข้อมูลที่เจาะจงสำหรับ API และลักษณะการทำงานที่ฝังอยู่ในเฟรมเวิร์ก Android (เช่น Bundle
หรือ Parcelable
)
ตัวสร้าง Intent ควรใช้รูปแบบ create*Intent()
ครีเอเตอร์สำหรับ Intent ควรใช้เมธอดที่มีชื่อว่า createFooIntent()
ใช้ Bundle แทนการสร้างโครงสร้างข้อมูลอเนกประสงค์ใหม่
หลีกเลี่ยงการสร้างโครงสร้างข้อมูลแบบทั่วไปใหม่เพื่อแสดงการแมปคีย์แบบไม่เจาะจงกับค่าที่มีการจัดประเภท แต่ให้พิจารณาใช้ Bundle
แทน
ปัญหานี้มักเกิดขึ้นเมื่อเขียน API ของแพลตฟอร์มที่ทำหน้าที่เป็นช่องทางการสื่อสารระหว่างแอปและบริการที่ไม่ใช่แพลตฟอร์ม ซึ่งแพลตฟอร์มไม่ได้อ่านข้อมูลที่ส่งผ่านช่องทางดังกล่าว และสัญญา API อาจมีการกําหนดบางส่วนนอกแพลตฟอร์ม (เช่น ในไลบรารี Jetpack)
ในกรณีที่แพลตฟอร์มอ่านข้อมูล โปรดหลีกเลี่ยงการใช้ Bundle
และเลือกใช้คลาสข้อมูลที่เป็นแบบคงที่
การใช้งาน Parcelable ต้องมีช่อง CREATOR สาธารณะ
ระบบจะแสดงการแยกส่วนได้ผ่าน CREATOR
ไม่ใช่เครื่องมือสร้างแบบดิบ หากคลาสใช้ Parcelable
ฟิลด์ CREATOR
ของคลาสนั้นต้องเป็น API แบบสาธารณะด้วย และตัวสร้างคลาสที่ใช้อาร์กิวเมนต์ Parcel
ต้องเป็นแบบส่วนตัว
ใช้ CharSequence สำหรับสตริง UI
เมื่อแสดงสตริงในอินเทอร์เฟซผู้ใช้ ให้ใช้ CharSequence
เพื่ออนุญาตอินสแตนซ์ Spannable
หากเป็นคีย์หรือป้ายกำกับหรือค่าอื่นๆ ที่ผู้ใช้มองไม่เห็น ก็ใช้String
ได้
หลีกเลี่ยงการใช้ Enums
IntDef
ต้องใช้ใน API ของแพลตฟอร์มทั้งหมดแทน Enum และควรพิจารณาอย่างจริงจังใน API ของไลบรารีที่ไม่ได้รวมไว้ ใช้ Enum เฉพาะในกรณีที่คุณแน่ใจว่าจะไม่มีการเพิ่มค่าใหม่
ประโยชน์ของIntDef
- เปิดใช้การเพิ่มค่าเมื่อเวลาผ่านไป
when
คำสั่ง Kotlin อาจทำงานไม่สำเร็จเมื่อรันไทม์หากไม่ครอบคลุมอีกต่อไปเนื่องจากมีการเพิ่มค่า Enum ในแพลตฟอร์ม
- ไม่มีคลาสหรือออบเจ็กต์ที่ใช้ในรันไทม์ มีเพียงองค์ประกอบพื้นฐานเท่านั้น
- แม้ว่า R8 หรือการทำให้ไฟล์มีขนาดเล็กจะหลีกเลี่ยงค่าใช้จ่ายนี้สำหรับ API ของไลบรารีที่ไม่ได้รวมกลุ่ม แต่การเพิ่มประสิทธิภาพนี้จะไม่ส่งผลต่อคลาส API ของแพลตฟอร์ม
ประโยชน์ของ Enum
- ฟีเจอร์ภาษาที่เป็นสำนวนเฉพาะของ Java, Kotlin
- เปิดใช้สวิตช์แบบครอบคลุม
when
การใช้คำสั่ง- หมายเหตุ - ค่าต้องไม่เปลี่ยนแปลงเมื่อเวลาผ่านไป โปรดดูรายการก่อนหน้า
- การตั้งชื่อที่ชัดเจนและค้นพบได้
- เปิดใช้การยืนยันเมื่อคอมไพล์
- เช่น คำสั่ง
when
ใน Kotlin ที่แสดงผลค่า
- เช่น คำสั่ง
- เป็นคลาสที่ใช้งานได้ซึ่งสามารถใช้งานอินเทอร์เฟซ มีแฮนเดิลแบบคงที่ แสดงเมธอดสมาชิกหรือเมธอดส่วนขยาย และแสดงฟิลด์
ทำตามลําดับชั้นการวางแพ็กเกจ Android
ลำดับชั้นแพ็กเกจ android.*
มีลําดับโดยนัย ซึ่งแพ็กเกจระดับล่างต้องไม่ใช้แพ็กเกจระดับสูงกว่า
หลีกเลี่ยงการอ้างอิงถึง Google, บริษัทอื่นๆ และผลิตภัณฑ์ของบริษัทเหล่านั้น
แพลตฟอร์ม Android เป็นโปรเจ็กต์โอเพนซอร์สและมีเป้าหมายที่จะไม่เอนเอียงไปทางผู้ให้บริการรายใดรายหนึ่ง API ควรเป็นแบบทั่วไปและใช้งานได้อย่างเท่าเทียมกันโดยผู้ผสานรวมระบบหรือแอปที่มีสิทธิ์ที่จําเป็น
การใช้งาน Parcelable ควรเป็นเวอร์ชันสุดท้าย
ระบบจะโหลดคลาสที่แยกส่วนได้ซึ่งแพลตฟอร์มกำหนดไว้จาก framework.jar
เสมอ ดังนั้นแอปจึงไม่สามารถพยายามลบล้างการใช้งาน Parcelable
ได้
หากแอปที่ส่งขยาย Parcelable
แอปที่รับจะไม่มีการใช้งานที่กําหนดเองของผู้ส่งเพื่อแตกไฟล์ หมายเหตุเกี่ยวกับความเข้ากันได้ย้อนหลัง: หากที่ผ่านมาคลาสของคุณไม่ได้เป็น final แต่ไม่มีคอนสตรัคเตอร์ที่เข้าถึงได้แบบสาธารณะ คุณจะยังคงทำเครื่องหมายเป็น final
ได้
เมธอดที่เรียกใช้กระบวนการของระบบควรโยน RemoteException เป็น RuntimeException อีกครั้ง
RemoteException
มักเกิดจาก AIDL ภายใน และบ่งบอกว่ากระบวนการของระบบหยุดทำงาน หรือแอปพยายามส่งข้อมูลมากเกินไป ไม่ว่าในกรณีใด สาธารณะ API ควรโยนข้อยกเว้นเป็น RuntimeException
อีกครั้งเพื่อป้องกันไม่ให้แอปเก็บการตัดสินใจด้านความปลอดภัยหรือนโยบายไว้
หากคุณทราบว่าอีกฝั่งของBinder
call คือกระบวนการของระบบ โค้ดที่เขียนไว้ล่วงหน้านี้ถือเป็นแนวทางปฏิบัติแนะนำ
try {
...
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
แสดงข้อยกเว้นที่เฉพาะเจาะจงสำหรับการเปลี่ยนแปลง API
ลักษณะการทํางานของ API สาธารณะอาจเปลี่ยนแปลงไปตามระดับ API และทําให้แอปขัดข้อง (เช่น เพื่อบังคับใช้นโยบายความปลอดภัยใหม่)
เมื่อ API ต้องแสดงข้อยกเว้นสําหรับคําขอที่ก่อนหน้านี้ถูกต้อง ให้แสดงข้อยกเว้นใหม่เฉพาะเจาะจงแทนข้อยกเว้นทั่วไป เช่น ExportedFlagRequired
แทนที่จะเป็น SecurityException
(และ ExportedFlagRequired
สามารถขยายSecurityException
)
ซึ่งจะช่วยให้นักพัฒนาแอปและเครื่องมือต่างๆ ตรวจหาการเปลี่ยนแปลงของลักษณะการทํางานของ API ได้
ใช้ตัวสร้างโคลนแทนการโคลน
เราขอแนะนำอย่างยิ่งว่าอย่าใช้เมธอด clone()
ของ Java เนื่องจากคลาส Object
ไม่มีสัญญา API และมีความยากลำบากในการขยายคลาสที่ใช้ clone()
แต่ให้ใช้ตัวสร้างโค้ดสำเนาที่รับออบเจ็กต์ประเภทเดียวกันแทน
/**
* Constructs a shallow copy of {@code other}.
*/
public Foo(Foo other)
คลาสที่อาศัย Builder ในการสร้างควรพิจารณาเพิ่มตัวสร้างแบบคัดลอกของ Builder เพื่ออนุญาตให้แก้ไขสำเนาได้
public class Foo {
public static final class Builder {
/**
* Constructs a Foo builder using data from {@code other}.
*/
public Builder(Foo other)
ใช้ ParcelFileDescriptor แทน FileDescriptor
ออบเจ็กต์ java.io.FileDescriptor
มีคำจำกัดความการเป็นเจ้าของที่ไม่ชัดเจน ซึ่งอาจทำให้เกิดข้อบกพร่องในการใช้หลังจากปิด แต่ควรแสดงผลหรือยอมรับอินสแตนซ์ ParcelFileDescriptor
แทน โค้ดเดิมจะแปลงระหว่าง PFD และ FD ได้หากจำเป็นโดยใช้ dup() หรือ getFileDescriptor()
หลีกเลี่ยงการใช้ค่าตัวเลขที่มีขนาดแปลกๆ
หลีกเลี่ยงการใช้ค่า short
หรือ byte
โดยตรง เนื่องจากมักจะจํากัดวิธีที่คุณอาจพัฒนา API ในอนาคต
หลีกเลี่ยงการใช้ BitSet
java.util.BitSet
เหมาะสําหรับการใช้งาน แต่ไม่เหมาะกับ API สาธารณะ ข้อมูลนี้เปลี่ยนแปลงได้ ต้องมีการจัดสรรสำหรับการเรียกใช้เมธอดที่มีความถี่สูง และไม่ระบุความหมายเชิงอรรถสำหรับสิ่งที่แต่ละบิตแสดง
สำหรับสถานการณ์ที่มีประสิทธิภาพสูง ให้ใช้ int
หรือ long
กับ @IntDef
สําหรับสถานการณ์ที่มีประสิทธิภาพต่ำ ให้พิจารณาใช้ Set<EnumType>
สำหรับข้อมูลไบนารีดิบ ให้ใช้
byte[]
โปรดใช้ android.net.Uri
android.net.Uri
คือการรวมแพ็กเกจ URI ที่แนะนำใน Android API
หลีกเลี่ยงการใช้ java.net.URI
เนื่องจากเข้มงวดเกินไปในการแยกวิเคราะห์ URI และอย่าใช้ java.net.URL
เนื่องจากคําจํากัดความของ "เท่ากับ" นั้นใช้งานไม่ได้
ซ่อนคำอธิบายประกอบที่ทำเครื่องหมายเป็น @IntDef, @LongDef หรือ @StringDef
คําอธิบายประกอบที่ทําเครื่องหมายเป็น @IntDef
, @LongDef
หรือ @StringDef
หมายถึงชุดค่าคงที่ที่ถูกต้องซึ่งสามารถส่งไปยัง API ได้ อย่างไรก็ตาม เมื่อส่งออกเป็น API เอง คอมไพเลอร์จะแทรกค่าคงที่ไว้และจะมีเพียงค่า (ที่ตอนนี้ไร้ประโยชน์) ที่เหลืออยู่ในสแต็บ API ของคำอธิบายประกอบ (สำหรับแพลตฟอร์ม) หรือ JAR (สำหรับไลบรารี)
ดังนั้น การใช้คำอธิบายประกอบเหล่านี้ต้องทําเครื่องหมายด้วยคำอธิบายประกอบ @hide
docs ในแพลตฟอร์ม หรือคำอธิบายประกอบ @RestrictTo.Scope.LIBRARY)
code ในไลบรารี โดยต้องทําเครื่องหมายเป็น @Retention(RetentionPolicy.SOURCE)
ทั้งใน 2 กรณีนี้เพื่อป้องกันไม่ให้ปรากฏในสตั๊บ API หรือ JAR
@RestrictTo(RestrictTo.Scope.LIBRARY)
@Retention(RetentionPolicy.SOURCE)
@IntDef({
STREAM_TYPE_FULL_IMAGE_DATA,
STREAM_TYPE_EXIF_DATA_ONLY,
})
public @interface ExifStreamType {}
เมื่อสร้าง SDK ของแพลตฟอร์มและ AAR ของไลบรารี เครื่องมือจะดึงข้อมูลคําอธิบายประกอบและรวมไว้แยกต่างหากจากแหล่งที่มาที่คอมไพล์ Android Studio จะอ่านรูปแบบที่รวมกลุ่มนี้และบังคับใช้คำจำกัดความของประเภท
อย่าเพิ่มคีย์ผู้ให้บริการการตั้งค่าใหม่
อย่าแสดงคีย์ใหม่จาก Settings.Global
, Settings.System
หรือ Settings.Secure
แต่ให้เพิ่ม Java API ของ getter และ setter ที่เหมาะสมในคลาสที่เกี่ยวข้อง ซึ่งมักจะเป็นคลาส "manager" เพิ่มกลไกการฟังหรือการออกอากาศเพื่อแจ้งให้ลูกค้าทราบถึงการเปลี่ยนแปลงตามต้องการ
การตั้งค่า SettingsProvider
มีปัญหาหลายประการเมื่อเทียบกับ Getter/Setter
- ไม่มีความปลอดภัยในการกำหนดประเภท
- ไม่มีวิธีแบบรวมในการระบุค่าเริ่มต้น
- ไม่มีวิธีที่เหมาะสมในการปรับแต่งสิทธิ์
- เช่น คุณจะปกป้องการตั้งค่าด้วยสิทธิ์ที่กำหนดเองไม่ได้
- ไม่มีวิธีที่เหมาะสมในการเพิ่มตรรกะที่กำหนดเอง
- ตัวอย่างเช่น คุณจะเปลี่ยนค่าของการตั้งค่า ก. ตามค่าของการตั้งค่า ข. ไม่ได้
ตัวอย่าง:
Settings.Secure.LOCATION_MODE
มีมานานแล้ว แต่ทีมตำแหน่งได้เลิกใช้งานแล้วเพื่อใช้ LocationManager.isLocationEnabled()
ซึ่งเป็น API ของ Java ที่ถูกต้อง และ MODE_CHANGED_ACTION
ออกอากาศ ซึ่งทำให้ทีมมีความยืดหยุ่นมากขึ้นมาก และความหมายของ API ชัดเจนขึ้นมาก
อย่าขยาย Activity และ AsyncTask
AsyncTask
คือรายละเอียดการใช้งาน แต่ให้แสดง Listener หรือ ListenableFuture
API ใน androidx แทน
ไม่สามารถคอมโพสิทคลาสย่อย Activity
การขยายกิจกรรมสำหรับฟีเจอร์จะทำให้ฟีเจอร์ดังกล่าวใช้ร่วมกับฟีเจอร์อื่นๆ ที่กำหนดให้ผู้ใช้ทำแบบเดียวกันไม่ได้ แต่ให้ใช้การคอมโพสิชันโดยใช้เครื่องมือต่างๆ เช่น LifecycleObserver
ใช้ getUser() ของบริบท
คลาสที่เชื่อมโยงกับ Context
เช่น ทุกอย่างที่แสดงผลจาก Context.getSystemService()
ควรใช้ผู้ใช้ที่เชื่อมโยงกับ Context
แทนการแสดงสมาชิกที่กำหนดเป้าหมายไปยังผู้ใช้ที่เฉพาะเจาะจง
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);
}
}
ข้อยกเว้น: เมธอดอาจยอมรับอาร์กิวเมนต์ผู้ใช้หากยอมรับค่าที่ไม่ได้แสดงถึงผู้ใช้รายเดียว เช่น UserHandle.ALL
ใช้ UserHandle แทน int ธรรมดา
เราขอแนะนำให้ใช้ UserHandle
เพื่อให้มีความปลอดภัยของประเภทและหลีกเลี่ยงการผสมผสานรหัสผู้ใช้กับ uid
Foobar getFoobarForUser(UserHandle user);
Foobar getFoobarForUser(int userId);
ในกรณีที่หลีกเลี่ยงไม่ได้ int
ที่แสดงถึงรหัสผู้ใช้ต้องมีการกำกับเนื้อหาด้วย
@UserIdInt
Foobar getFoobarForUser(@UserIdInt int user);
แนะนำให้ใช้ Listener หรือ Callback เพื่อออกอากาศ Intent
Intent แบบออกอากาศมีประสิทธิภาพมาก แต่ส่งผลให้เกิดลักษณะการทำงานใหม่ๆ ที่อาจส่งผลเสียต่อประสิทธิภาพของระบบ ดังนั้นจึงควรเพิ่ม Intent แบบออกอากาศใหม่อย่างรอบคอบ
ข้อกังวลบางประการที่ทำให้เราไม่แนะนำให้เปิดตัวความตั้งใจในการออกอากาศใหม่มีดังนี้
เมื่อส่งการออกอากาศโดยไม่มี Flag
FLAG_RECEIVER_REGISTERED_ONLY
ระบบจะบังคับให้เริ่มแอปที่ไม่ได้ทำงานอยู่ แม้ว่าบางครั้งผลลัพธ์นี้อาจเป็นสิ่งที่ตั้งใจไว้ แต่อาจส่งผลให้มีการติดตั้งแอปหลายสิบรายการพร้อมกัน ซึ่งส่งผลเสียต่อประสิทธิภาพของระบบ เราขอแนะนำให้ใช้กลยุทธ์อื่น เช่นJobScheduler
เพื่อประสานงานให้ดียิ่งขึ้นเมื่อเป็นไปตามเงื่อนไขเบื้องต้นต่างๆเมื่อส่งการออกอากาศ คุณจะมีความสามารถในการกรองหรือปรับเนื้อหาที่ส่งไปยังแอปได้เพียงเล็กน้อย ซึ่งทำให้ตอบสนองต่อข้อกังวลด้านความเป็นส่วนตัวในอนาคตหรือเปลี่ยนแปลงลักษณะการทํางานตาม SDK เป้าหมายของแอปที่รับได้ยากหรือเป็นไปไม่ได้
เนื่องจากคิวการออกอากาศเป็นทรัพยากรที่ใช้ร่วมกัน จึงอาจทำให้คิวมีงานมากเกินไปและอาจส่งผลให้กิจกรรมของคุณไม่ได้รับการเผยแพร่อย่างทันท่วงที เราสังเกตเห็นคิวการออกอากาศหลายรายการในการใช้งานจริงที่เวลาในการตอบสนองจากต้นทางถึงปลายทางนานกว่า 10 นาที
ด้วยเหตุนี้ เราจึงขอแนะนำให้ฟีเจอร์ใหม่ๆ พิจารณาใช้ Listeners หรือ Callback หรือสิ่งอำนวยความสะดวกอื่นๆ เช่น JobScheduler
แทน Intent แบบ Broadcast
ในกรณีที่ความตั้งใจในการออกอากาศยังคงเป็นการออกแบบที่เหมาะที่สุด แนวทางปฏิบัติแนะนำบางส่วนที่ควรพิจารณามีดังนี้
- หากเป็นไปได้ ให้ใช้
Intent.FLAG_RECEIVER_REGISTERED_ONLY
เพื่อจำกัดการออกอากาศไปยังแอปที่ทำงานอยู่ เช่นACTION_SCREEN_ON
ใช้การออกแบบนี้เพื่อหลีกเลี่ยงการปลุกแอป - หากเป็นไปได้ ให้ใช้
Intent.setPackage()
หรือIntent.setComponent()
เพื่อกําหนดเป้าหมายการออกอากาศไปยังแอปที่สนใจ ตัวอย่างเช่นACTION_MEDIA_BUTTON
ใช้การออกแบบนี้เพื่อเน้นที่การควบคุมการเล่นของแอปปัจจุบัน - หากเป็นไปได้ ให้กำหนดการออกอากาศเป็น
<protected-broadcast>
เพื่อป้องกันไม่ให้แอปที่เป็นอันตรายแอบอ้างเป็นระบบปฏิบัติการ
Intent ในบริการสําหรับนักพัฒนาแอปที่เชื่อมโยงกับระบบ
บริการที่นักพัฒนาแอปตั้งใจจะขยายและระบบได้กำหนดไว้ เช่น บริการนามธรรมอย่าง NotificationListenerService
อาจตอบสนองต่อการดำเนินการ Intent
จากระบบ บริการดังกล่าวควรเป็นไปตามเกณฑ์ต่อไปนี้
- กำหนดค่าคงที่สตริง
SERVICE_INTERFACE
ในคลาสที่มีชื่อคลาสแบบเต็มที่สมบูรณ์ของบริการ ต้องกำกับเนื้อหาของค่าคงที่นี้ด้วย@SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
- เอกสารเกี่ยวกับชั้นเรียนที่นักพัฒนาแอปต้องเพิ่ม
<intent-filter>
ลงในAndroidManifest.xml
เพื่อรับ Intent จากแพลตฟอร์ม - เราขอแนะนำให้เพิ่มสิทธิ์ระดับระบบเพื่อป้องกันไม่ให้แอปที่เป็นอันตรายส่ง
Intent
ไปยังบริการของนักพัฒนาแอป
การทำงานร่วมกันของ Kotlin-Java
ดูรายการหลักเกณฑ์ทั้งหมดได้ในคู่มือการทำงานร่วมกันของ Kotlin-Java อย่างเป็นทางการของ Android เราได้คัดลอกหลักเกณฑ์บางรายการมาไว้ในคู่มือนี้เพื่อปรับปรุงการค้นพบ
ระดับการมองเห็น API
API ของ Kotlin บางรายการ เช่น suspend fun
ไม่ได้มีไว้สำหรับนักพัฒนาซอฟต์แวร์ Java แต่อย่าพยายามควบคุมระดับการเข้าถึงเฉพาะภาษาโดยใช้ @JvmSynthetic
เนื่องจากจะมีผลข้างเคียงต่อวิธีนำเสนอ API ในโปรแกรมแก้ไขข้อบกพร่อง ซึ่งทำให้การแก้ไขข้อบกพร่องยากขึ้น
ดูคำแนะนำที่เฉพาะเจาะจงได้จากคู่มือการทำงานร่วมกันของ Kotlin-Java หรือคู่มือการทำงานแบบแอสซิงค์
วัตถุที่แสดงร่วม
Kotlin ใช้ companion object
เพื่อแสดงสมาชิกแบบคงที่ ในบางกรณี ตัวแปรเหล่านี้จะแสดงจาก Java ในคลาสภายในชื่อ Companion
แทนที่จะเป็นในคลาสที่บรรจุ ชั้นเรียน Companion
อาจแสดงเป็นชั้นเรียนว่างในไฟล์ข้อความ API ซึ่งทำงานได้ตามที่ตั้งใจไว้
หากต้องการเพิ่มความเข้ากันได้กับ Java ให้กำกับฟิลด์ที่ไม่ใช่ค่าคงที่ของออบเจ็กต์ที่ใช้ร่วมกันด้วย @JvmField
และกำกับฟังก์ชันสาธารณะด้วย @JvmStatic
เพื่อแสดงออบเจ็กต์และฟังก์ชันดังกล่าวในคลาสที่รวมไว้โดยตรง
companion object {
@JvmField val BIG_INTEGER_ONE = BigInteger.ONE
@JvmStatic fun fromPointF(pointf: PointF) {
/* ... */
}
}
วิวัฒนาการของ API แพลตฟอร์ม Android
ส่วนนี้จะอธิบายนโยบายเกี่ยวกับประเภทการเปลี่ยนแปลงที่คุณทำได้กับ Android API ที่มีอยู่ และวิธีที่คุณควรใช้การเปลี่ยนแปลงเหล่านั้นเพื่อเพิ่มความสามารถในการใช้งานร่วมกันกับแอปและโค้ดเบสที่มีอยู่ให้ได้สูงสุด
การเปลี่ยนแปลงที่ส่งผลกับไบนารี
หลีกเลี่ยงการเปลี่ยนแปลงที่ทำให้เกิดข้อบกพร่องในไบนารีในแพลตฟอร์ม API สาธารณะที่เสร็จสมบูรณ์แล้ว โดยทั่วไปแล้ว การเปลี่ยนแปลงประเภทเหล่านี้จะทำให้เกิดข้อผิดพลาดเมื่อเรียกใช้ make update-api
แต่อาจมีกรณีพิเศษที่การตรวจสอบ API ของ Metalava ตรวจไม่พบ หากมีข้อสงสัย โปรดดูคู่มือการพัฒนา API ที่ใช้ Java ของมูลนิธิ Eclipse เพื่อดูคำอธิบายโดยละเอียดเกี่ยวกับการเปลี่ยนแปลง API ประเภทใดที่เข้ากันได้ใน Java การเปลี่ยนแปลงที่ทำให้เกิดข้อขัดข้องในไบนารีใน API ที่ซ่อนอยู่ (เช่น API ของระบบ) ควรเป็นไปตามวงจรเลิกใช้งาน/แทนที่
การเปลี่ยนแปลงที่ทําให้แหล่งที่มาใช้งานไม่ได้
เราไม่แนะนำให้ทำการเปลี่ยนแปลงที่ทําให้แหล่งที่มาใช้งานไม่ได้ แม้ว่าจะไม่ทําให้ไฟล์ไบนารีใช้งานไม่ได้ก็ตาม ตัวอย่างการเปลี่ยนแปลงที่เข้ากันได้แบบไบนารีแต่ทำให้แหล่งที่มาใช้งานไม่ได้คือการเพิ่มแบบทั่วไปในคลาสที่มีอยู่ ซึ่งเข้ากันได้แบบไบนารี แต่อาจทำให้เกิดข้อผิดพลาดในการคอมไพล์เนื่องจากการรับช่วงหรือการอ้างอิงที่ไม่ชัดเจน
การเปลี่ยนแปลงที่ทําให้แหล่งที่มาใช้งานไม่ได้จะไม่แสดงข้อผิดพลาดเมื่อเรียกใช้ make update-api
คุณจึงต้องเข้าใจผลกระทบของการเปลี่ยนแปลงต่อลายเซ็น API ที่มีอยู่
ในบางกรณี การเปลี่ยนแปลงที่ทำให้เกิดข้อขัดข้องในซอร์สโค้ดอาจจำเป็นเพื่อปรับปรุงประสบการณ์การใช้งานของนักพัฒนาซอฟต์แวร์หรือความถูกต้องของโค้ด ตัวอย่างเช่น การเพิ่มคําอธิบายประกอบเกี่ยวกับ Nullability ลงในซอร์สโค้ด Java จะปรับปรุงความสามารถในการทำงานร่วมกันกับโค้ด Kotlin และลดโอกาสที่จะเกิดข้อผิดพลาด แต่มักต้องมีการเปลี่ยนแปลงซอร์สโค้ด ซึ่งบางครั้งเป็นการเปลี่ยนแปลงที่สําคัญ
การเปลี่ยนแปลงใน API ส่วนบุคคล
คุณเปลี่ยน API ที่มีคำอธิบายประกอบ @TestApi
ได้ทุกเมื่อ
คุณต้องเก็บรักษา API ที่มีคำอธิบายประกอบ @SystemApi
ไว้เป็นเวลา 3 ปี คุณต้องนํา API ของระบบออกหรือเปลี่ยนโครงสร้างภายในโค้ดตามกำหนดการต่อไปนี้
- API y - เพิ่มแล้ว
- API ปี y+1 - การเลิกใช้งาน
- ทำเครื่องหมายโค้ดด้วย
@Deprecated
- เพิ่มรายการที่จะแทนที่ และลิงก์ไปยังรายการที่จะแทนที่ใน Javadoc สำหรับโค้ดที่เลิกใช้งานโดยใช้คำอธิบายประกอบเอกสาร
@deprecated
- ในระหว่างวงจรการพัฒนา ให้รายงานข้อบกพร่องกับผู้ใช้ภายในโดยแจ้งให้ผู้ใช้ทราบว่า API กำลังจะเลิกใช้งาน ซึ่งจะช่วยยืนยันว่า API เปลี่ยนทดแทนนั้นเพียงพอ
- ทำเครื่องหมายโค้ดด้วย
- API ปี +2 - การนําออกแบบไม่ลบ
- ทำเครื่องหมายโค้ดด้วย
@removed
- ไม่บังคับ ให้แสดงข้อยกเว้นหรือไม่ดำเนินการใดๆ สำหรับแอปที่กำหนดเป้าหมายเป็นระดับ SDK ปัจจุบันสำหรับรุ่น
- ทำเครื่องหมายโค้ดด้วย
- API y+3 - การนำออกอย่างถาวร
- นําโค้ดออกจากลําดับชั้นซอร์สโค้ดโดยสมบูรณ์
การเลิกใช้งาน
เราถือว่าการเลิกใช้งานเป็นการเปลี่ยนแปลง API และอาจเกิดขึ้นในรุ่นหลัก (เช่น เวอร์ชันที่มีตัวอักษร) ใช้คำอธิบายประกอบแหล่งที่มา @Deprecated
และคำอธิบายประกอบเอกสาร @deprecated
<summary>
ร่วมกันเมื่อเลิกใช้งาน API ข้อมูลสรุปต้องระบุกลยุทธ์การย้ายข้อมูล กลยุทธ์นี้อาจลิงก์ไปยัง API ที่จะมาแทนที่หรืออธิบายเหตุผลที่คุณไม่ควรใช้ 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)
นอกจากนี้ คุณยังต้องเลิกใช้งาน API ที่กําหนดใน XML และแสดงใน Java ด้วย ซึ่งรวมถึงแอตทริบิวต์และพร็อพเพอร์ตี้ที่กำหนดสไตล์ได้ซึ่งแสดงในคลาส android.R
โดยสรุปมีดังนี้
<!-- Attribute whether the accessibility service ...
{@deprecated Not used by the framework}
-->
<attr name="canRequestEnhancedWebAccessibility" format="boolean" />
กรณีที่ควรเลิกใช้งาน API
การเลิกใช้งานมีประโยชน์มากที่สุดในการไม่สนับสนุนการนำ API ไปใช้ในโค้ดใหม่
นอกจากนี้ เรายังกำหนดให้คุณทําเครื่องหมาย API เป็น @deprecated
ก่อนที่ API ดังกล่าวจะกลายเป็น @removed
แต่ก็ไม่ได้เป็นแรงจูงใจสําคัญให้นักพัฒนาแอปย้ายข้อมูลออกจาก API ที่ใช้อยู่
โปรดพิจารณาผลกระทบต่อนักพัฒนาซอฟต์แวร์ก่อนเลิกใช้งาน API ผลกระทบของการเลิกใช้งาน API มีดังนี้
javac
แสดงคำเตือนระหว่างการคอมไพล์- คุณจะระงับคำเตือนการเลิกใช้งานในวงกว้างหรือกำหนดเป็นค่าฐานไม่ได้ ดังนั้นนักพัฒนาซอฟต์แวร์ที่ใช้
-Werror
จะต้องแก้ไขหรือระงับการใช้ API ที่เลิกใช้งานทั้งหมดทีละรายการก่อนจึงจะอัปเดตเวอร์ชัน SDK ที่คอมไพล์ได้ - คุณจะซ่อนคําเตือนการเลิกใช้งานเกี่ยวกับการนําเข้าคลาสที่เลิกใช้งานไม่ได้ ดังนั้น นักพัฒนาแอปจึงต้องใส่ชื่อคลาสแบบเต็มที่สมบูรณ์ในบรรทัดสำหรับการใช้งานทุกครั้งของคลาสที่เลิกใช้งานก่อนจึงจะอัปเดตเวอร์ชัน SDK ที่คอมไพล์ได้
- คุณจะระงับคำเตือนการเลิกใช้งานในวงกว้างหรือกำหนดเป็นค่าฐานไม่ได้ ดังนั้นนักพัฒนาซอฟต์แวร์ที่ใช้
- เอกสารประกอบใน
d.android.com
แสดงการประกาศการเลิกใช้งาน - IDE เช่น Android Studio จะแสดงคำเตือนในเว็บไซต์การใช้งาน API
- IDE อาจลดการจัดอันดับหรือซ่อน API จากการเติมข้อความอัตโนมัติ
ดังนั้น การเลิกใช้งาน API อาจทําให้นักพัฒนาซอฟต์แวร์ที่ให้ความสําคัญกับคุณภาพโค้ดมากที่สุด (ผู้ใช้ -Werror
) เลิกใช้ SDK ใหม่
นักพัฒนาซอฟต์แวร์ที่ไม่กังวลเกี่ยวกับคำเตือนในโค้ดที่มีอยู่มีแนวโน้มที่จะไม่สนใจการเลิกใช้งาน
SDK ที่เลิกใช้งานจำนวนมากจะทำให้ทั้ง 2 กรณีแย่ลง
ด้วยเหตุนี้ เราจึงขอแนะนำให้เลิกใช้งาน API เฉพาะในกรณีที่มีลักษณะดังนี้
- เราวางแผนที่จะ
@remove
API ในรุ่นต่อๆ ไป - การใช้ API ทําให้เกิดความผิดพลาดหรือลักษณะการทำงานที่ไม่ชัดเจนซึ่งเราไม่สามารถแก้ไขได้โดยไม่ทำให้ความเข้ากันได้หยุดชะงัก
เมื่อเลิกใช้งาน API รายการหนึ่งและแทนที่ด้วย API ใหม่ เราขอแนะนำอย่างยิ่งให้เพิ่ม API ที่เข้ากันได้ที่เกี่ยวข้องลงในไลบรารี Jetpack เช่น androidx.core
เพื่อลดความซับซ้อนในการสนับสนุนทั้งอุปกรณ์เก่าและใหม่
เราไม่แนะนำให้เลิกใช้งาน API ที่ทำงานได้ตามที่ตั้งใจไว้ในรุ่นปัจจุบันและรุ่นต่อๆ ไป
/**
* ...
* @deprecated Use {@link #doThing(int, Bundle)} instead.
*/
@Deprecated
public void doThing(int action) {
...
}
public void doThing(int action, @Nullable Bundle extras) {
...
}
การเลิกใช้งานเหมาะสมในกรณีที่ API ไม่สามารถรักษาลักษณะการทำงานที่บันทึกไว้ได้อีกต่อไป ดังนี้
/**
* ...
* @deprecated No longer displayed in the status bar as of API 21.
*/
@Deprecated
public RemoteViews tickerView;
การเปลี่ยนแปลง API ที่เลิกใช้งานแล้ว
คุณต้องรักษาลักษณะการทํางานของ API ที่เลิกใช้งาน ซึ่งหมายความว่าการติดตั้งใช้งานการทดสอบต้องเหมือนเดิม และการทดสอบต้องผ่านต่อไปหลังจากที่คุณเลิกใช้งาน API แล้ว หาก API ไม่มีการทดสอบ คุณควรเพิ่มการทดสอบ
อย่าขยายแพลตฟอร์ม API ที่เลิกใช้งานในรุ่นต่อๆ ไป คุณสามารถเพิ่มคำอธิบายประกอบเกี่ยวกับความถูกต้องของ Lint (เช่น @Nullable
) ลงใน API ที่เลิกใช้งานอยู่แล้วได้ แต่ไม่ควรเพิ่ม API ใหม่
อย่าเพิ่ม API ใหม่เป็น "เลิกใช้งานแล้ว" หากมีการเพิ่ม API ใดก็ตามและเลิกใช้งานในภายหลังภายในรอบการเผยแพร่ก่อนเปิดตัว (ซึ่งจะเข้าสู่แพลตฟอร์ม API สาธารณะในขั้นต้นโดยระบุว่าเลิกใช้งานแล้ว) คุณต้องต้องนำ API ดังกล่าวออกก่อนที่จะเผยแพร่ API เวอร์ชันสุดท้าย
นําออกชั่วคราว
การนําออกอย่างนุ่มนวลเป็นการเปลี่ยนแปลงที่ทําให้แหล่งที่มาใช้งานไม่ได้ และคุณควรหลีกเลี่ยงใน API สาธารณะ เว้นแต่ว่า API Council จะอนุมัติอย่างชัดแจ้ง
สําหรับ API ของระบบ คุณต้องเลิกใช้งาน API ดังกล่าวตลอดระยะเวลาของรุ่นหลักก่อนที่จะมีการนําออกอย่างค่อยเป็นค่อยไป นำการอ้างอิงเอกสารทั้งหมดซึ่งกล่าวถึง API ออก และใช้คำอธิบายประกอบเอกสาร @removed <summary>
เมื่อนำ API ออกอย่างนุ่มนวล สรุปของคุณต้องระบุเหตุผลในการนําออกและอาจระบุกลยุทธ์การย้ายข้อมูลด้วย ตามที่อธิบายไว้ในการเลิกใช้งาน
ลักษณะการทํางานของ API ที่นําออกอย่างนุ่มนวลสามารถคงไว้เหมือนเดิม แต่ที่สำคัญกว่านั้นคือต้องคงไว้เพื่อให้ผู้เรียกที่มีอยู่ไม่ขัดข้องเมื่อเรียก API ซึ่งในบางกรณีอาจหมายถึงการคงลักษณะการทำงานไว้
คุณต้องรักษาความครอบคลุมของการทดสอบไว้ แต่เนื้อหาการทดสอบอาจต้องเปลี่ยนแปลงเพื่อให้สอดคล้องกับการเปลี่ยนแปลงของลักษณะการใช้งาน การทดสอบยังคงต้องตรวจสอบว่าตัวแปรที่เรียกใช้ที่มีอยู่จะไม่ขัดข้องเมื่อรันไทม์ คุณสามารถคงลักษณะการทํางานของ API ที่นําออกอย่างค่อยเป็นค่อยไปไว้ได้ แต่ที่สำคัญกว่านั้น คุณต้องคงลักษณะการทํางานดังกล่าวไว้เพื่อให้ผู้เรียกที่มีอยู่ไม่ขัดข้องเมื่อเรียกใช้ API ในบางกรณี การดำเนินการดังกล่าวอาจหมายถึงการคงลักษณะการทำงานไว้
คุณต้องต้องรักษาความครอบคลุมของการทดสอบ แต่เนื้อหาของการทดสอบอาจต้องเปลี่ยนแปลงเพื่อให้สอดคล้องกับการเปลี่ยนแปลงของลักษณะการทำงาน การทดสอบยังคงต้องตรวจสอบว่าตัวแปรที่เรียกใช้ที่มีอยู่จะไม่ขัดข้องเมื่อรันไทม์
ในระดับเทคนิค เราจะนํา API ออกจากไฟล์ JAR สแต็บ SDK และ classpath ของเวลาคอมไพล์โดยใช้คําอธิบายประกอบ Javadoc ของ @remove
แต่ API จะยังคงอยู่ใน classpath ของรันไทม์ ซึ่งคล้ายกับ API ของ @hide
/**
* Ringer volume. This is ...
*
* @removed Not functional since API 2.
*/
public static final String VOLUME_RING = ...
จากมุมมองของนักพัฒนาแอป API จะไม่ปรากฏในการเติมข้อความอัตโนมัติอีกต่อไป และซอร์สโค้ดที่อ้างอิง API จะไม่คอมไพล์เมื่อ compileSdk
เท่ากับหรือใหม่กว่า SDK ที่นํา API ออก อย่างไรก็ตาม ซอร์สโค้ดจะยังคงคอมไพล์กับ SDK เวอร์ชันก่อนหน้าได้สําเร็จ และไบนารีที่อ้างอิง API จะยังคงทํางานต่อไป
API บางหมวดหมู่ต้องไม่ถูกนำออกอย่างค่อยเป็นค่อยไป คุณต้องไม่นํา API บางหมวดหมู่ออกอย่างค่อยเป็นค่อยไป
เมธอดนามธรรม
คุณต้องไม่นำเมธอดนามธรรมในคลาสที่นักพัฒนาแอปอาจขยายออกออกอย่างค่อยเป็นค่อยไป การทำเช่นนั้นจะทำให้นักพัฒนาซอฟต์แวร์ขยายคลาสใน SDK ทุกระดับไม่สำเร็จ
ในกรณีที่นักพัฒนาแอปไม่เคยและจะไม่สามารถขยายคลาสได้ (ซึ่งเกิดขึ้นไม่บ่อยนัก) คุณจะยังคงนําเมธอดนามธรรมออกแบบเบาได้
การนำออกอย่างสมบูรณ์
การนําออกอย่างถาวรเป็นการเปลี่ยนแปลงที่ทําให้ไฟล์ไบนารีใช้งานไม่ได้ และไม่ควรเกิดขึ้นใน API สาธารณะ
คำอธิบายประกอบที่ไม่แนะนํา
เราใช้คำอธิบายประกอบ @Discouraged
เพื่อระบุว่าเราไม่แนะนำ API ในกรณีส่วนใหญ่ (>95%) API ที่เลิกใช้งานแล้วแตกต่างจาก API ที่ไม่แนะนําตรงที่มี Use Case ที่สำคัญเพียงไม่กี่รายการที่ป้องกันการเลิกใช้งาน เมื่อทำเครื่องหมาย API ว่า "ไม่แนะนํา" คุณต้องระบุคําอธิบายและโซลูชันอื่น ดังนี้
@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);
}
คุณไม่ควรเพิ่ม API ใหม่เนื่องจากไม่แนะนํา
การเปลี่ยนแปลงลักษณะการทํางานของ API ที่มีอยู่
ในบางกรณี คุณอาจต้องการเปลี่ยนลักษณะการทํางานของการติดตั้งใช้งาน API ที่มีอยู่ ตัวอย่างเช่น ใน Android 7.0 เราได้ปรับปรุง DropBoxManager
เพื่อสื่อสารอย่างชัดเจนเมื่อนักพัฒนาแอปพยายามโพสต์เหตุการณ์ที่มีขนาดใหญ่เกินกว่าจะส่งผ่าน Binder
อย่างไรก็ตาม เราขอแนะนำอย่างยิ่งให้คงลักษณะการทำงานที่ปลอดภัยไว้สำหรับแอปเวอร์ชันเก่าเพื่อหลีกเลี่ยงปัญหาในแอปที่มีอยู่ ที่ผ่านมาเราป้องกันการเปลี่ยนแปลงลักษณะการทำงานเหล่านี้ตาม ApplicationInfo.targetSdkVersion
ของแอป แต่เมื่อเร็วๆ นี้เราได้เปลี่ยนไปใช้การกำหนดให้ต้องใช้ App Compatibility Framework ตัวอย่างวิธีใช้การเปลี่ยนแปลงพฤติกรรมโดยใช้เฟรมเวิร์กใหม่มีดังนี้
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
}
}
}
การใช้การออกแบบเฟรมเวิร์กความเข้ากันได้ของแอปนี้ช่วยให้นักพัฒนาแอปปิดใช้การเปลี่ยนแปลงลักษณะการทำงานบางอย่างชั่วคราวในระหว่างรุ่นตัวอย่างและรุ่นเบต้าได้ ซึ่งเป็นส่วนหนึ่งของการแก้ไขข้อบกพร่องของแอป แทนที่จะบังคับให้นักพัฒนาแอปต้องปรับตัวให้เข้ากับการเปลี่ยนแปลงลักษณะการทำงานหลายสิบรายการพร้อมกัน
ความเข้ากันได้แบบไปข้างหน้า
ความเข้ากันได้แบบย้อนหลังเป็นลักษณะการออกแบบที่ช่วยให้ระบบยอมรับอินพุตที่มีไว้สำหรับเวอร์ชันที่ใหม่กว่าได้ ในกรณีของการออกแบบ API คุณต้องให้ความสำคัญเป็นพิเศษกับการออกแบบเริ่มต้นและการเปลี่ยนแปลงในอนาคต เนื่องจากนักพัฒนาแอปคาดหวังว่าจะเขียนโค้ดเพียงครั้งเดียว ทดสอบเพียงครั้งเดียว และทำให้โค้ดทำงานได้ทุกที่โดยไม่มีปัญหา
ปัญหาความเข้ากันได้แบบย้อนหลังที่พบบ่อยที่สุดใน Android มีดังนี้
- การเพิ่มค่าคงที่ใหม่ลงในชุด (เช่น
@IntDef
หรือenum
) ที่ก่อนหน้านี้ถือว่าสมบูรณ์ (เช่น ในกรณีที่switch
มีdefault
ที่ทำให้เกิดข้อยกเว้น) - การเพิ่มการรองรับฟีเจอร์ที่ไม่ได้บันทึกไว้ในแพลตฟอร์ม API โดยตรง (เช่น การรองรับการกำหนดทรัพยากรประเภท
ColorStateList
ใน XML ซึ่งก่อนหน้านี้รองรับเฉพาะทรัพยากร<color>
เท่านั้น) - ผ่อนคลายข้อจำกัดในการตรวจสอบรันไทม์ เช่น นำการตรวจสอบ
requireNotNull()
ที่อยู่ในเวอร์ชันที่ต่ำกว่าออก
ในทุกกรณีเหล่านี้ นักพัฒนาแอปจะพบว่ามีสิ่งผิดปกติเฉพาะเมื่อรันไทม์เท่านั้น ที่แย่กว่านั้นคือผู้ใช้อาจพบปัญหานี้จากรายงานข้อขัดข้องของอุปกรณ์รุ่นเก่าที่ใช้งานอยู่
นอกจากนี้ กรณีเหล่านี้เป็นการเปลี่ยนแปลง API ที่ถูกต้องทางเทคนิคทั้งหมด แต่จะไม่มีการละเมิดความเข้ากันได้ของไบนารีหรือซอร์สโค้ด และเครื่องมือตรวจสอบ API จะไม่พบปัญหาเหล่านี้
ดังนั้น นักออกแบบ API จึงต้องระมัดระวังอย่างยิ่งเมื่อแก้ไขคลาสที่มีอยู่ ถามคำถามว่า "การเปลี่ยนแปลงนี้จะทำให้โค้ดที่เขียนและทดสอบเฉพาะกับแพลตฟอร์มเวอร์ชันล่าสุดใช้งานไม่ได้ในเวอร์ชันที่ต่ำกว่าหรือไม่"
สคีมา XML
หากสคีมา XML ทำหน้าที่เป็นอินเทอร์เฟซที่เสถียรระหว่างคอมโพเนนต์ สคีมานั้นต้องระบุอย่างชัดเจนและต้องพัฒนาในลักษณะที่เข้ากันได้แบบย้อนหลัง คล้ายกับ API อื่นๆ ของ Android ตัวอย่างเช่น โครงสร้างขององค์ประกอบและแอตทริบิวต์ XML ต้องได้รับการคงไว้ เช่นเดียวกับการรักษาเมธอดและตัวแปรในแพลตฟอร์ม Android API อื่นๆ
การเลิกใช้งาน XML
หากต้องการเลิกใช้งานองค์ประกอบหรือแอตทริบิวต์ XML ให้เพิ่มเครื่องหมาย xs:annotation
แต่คุณต้องรองรับไฟล์ XML ที่มีอยู่ต่อไปโดยทำตามวงจรการพัฒนา @SystemApi
ทั่วไป
<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>
ต้องเก็บรักษาประเภทองค์ประกอบ
สคีมารองรับองค์ประกอบ sequence
, องค์ประกอบ choice
และองค์ประกอบ all
เป็นองค์ประกอบย่อยขององค์ประกอบ complexType
อย่างไรก็ตาม องค์ประกอบย่อยเหล่านี้แตกต่างกันในด้านจำนวนและลําดับขององค์ประกอบย่อย ดังนั้นการแก้ไขประเภทที่มีอยู่จึงเป็นการเปลี่ยนแปลงที่เข้ากันไม่ได้
หากต้องการแก้ไขประเภทที่มีอยู่ แนวทางปฏิบัติแนะนำคือเลิกใช้งานประเภทเก่าและเปิดตัวประเภทใหม่มาแทนที่
<!-- 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>
รูปแบบเฉพาะสำหรับเมนไลน์
Mainline เป็นโปรเจ็กต์ที่อนุญาตให้อัปเดตระบบย่อย ("โมดูล Mainline") ของ Android OS ทีละรายการแทนการอัปเดตทั้งอิมเมจระบบ
โมดูลหลักต้อง "ยกเลิกการรวมกลุ่ม" จากแพลตฟอร์มหลัก ซึ่งหมายความว่าการโต้ตอบทั้งหมดระหว่างโมดูลแต่ละรายการกับส่วนอื่นๆ จะต้องดำเนินการโดยใช้ API อย่างเป็นทางการ (สาธารณะหรือระบบ)
โมดูลหลักควรเป็นไปตามรูปแบบการออกแบบบางอย่าง ส่วนนี้จะอธิบาย
รูปแบบ <Module>FrameworkInitializer
หากโมดูลหลักต้องแสดงคลาส @SystemService
(เช่น
JobScheduler
) ให้ใช้รูปแบบต่อไปนี้
แสดงคลาส
<YourModule>FrameworkInitializer
จากโมดูล คลาสนี้ต้องอยู่ใน$BOOTCLASSPATH
ตัวอย่าง: StatsFrameworkInitializerทำเครื่องหมายด้วย
@SystemApi(client = MODULE_LIBRARIES)
เพิ่มเมธอด
public static void registerServiceWrappers()
ลงไปใช้
SystemServiceRegistry.registerContextAwareService()
เพื่อลงทะเบียนคลาสตัวจัดการบริการเมื่อต้องมีการอ้างอิงถึงContext
ใช้
SystemServiceRegistry.registerStaticService()
เพื่อลงทะเบียนคลาสตัวจัดการบริการเมื่อไม่จำเป็นต้องอ้างอิงContext
เรียกใช้เมธอด
registerServiceWrappers()
จากตัวเริ่มต้นแบบคงที่ของSystemServiceRegistry
รูปแบบ <Module>ServiceManager
โดยปกติแล้ว หากต้องการลงทะเบียนออบเจ็กต์ Binder ของบริการระบบหรือรับข้อมูลอ้างอิงเกี่ยวกับออบเจ็กต์ดังกล่าว คุณต้องใช้ ServiceManager
แต่โมดูลหลักจะใช้ไม่ได้เนื่องจากออบเจ็กต์ดังกล่าวซ่อนอยู่ คลาสนี้จะซ่อนอยู่เนื่องจากโมดูลหลักไม่ควรลงทะเบียนหรืออ้างอิงออบเจ็กต์ Binder ของบริการระบบที่แพลตฟอร์มแบบคงที่หรือโมดูลอื่นๆ แสดง
โมดูลหลักสามารถใช้รูปแบบต่อไปนี้แทนเพื่อให้ลงทะเบียนและรับการอ้างอิงบริการ Binder ที่ติดตั้งใช้งานภายในโมดูลได้
สร้างคลาส
<YourModule>ServiceManager
ตามการออกแบบของ TelephonyServiceManagerแสดงชั้นเรียนเป็น
@SystemApi
หากต้องการเข้าถึงจากชั้นเรียน$BOOTCLASSPATH
หรือชั้นเรียนเซิร์ฟเวอร์ของระบบเท่านั้น ให้ใช้@SystemApi(client = MODULE_LIBRARIES)
แต่หากต้องการเข้าถึงจากชั้นเรียนอื่นๆ จะใช้@SystemApi(client = PRIVILEGED_APPS)
ได้ชั้นเรียนนี้จะประกอบด้วยข้อมูลต่อไปนี้
- ตัวสร้างแบบซ่อนเพื่อให้มีเพียงโค้ดแพลตฟอร์มแบบคงที่เท่านั้นที่สร้างอินสแตนซ์ได้
- เมธอด Gettier สาธารณะที่แสดงผลอินสแตนซ์
ServiceRegisterer
สำหรับชื่อที่เฉพาะเจาะจง หากมีออบเจ็กต์ Binder 1 รายการ คุณจะต้องมีเมธอด getter 1 รายการ หากมี 2 รายการ คุณจะต้องมีตัวรับ 2 รายการ - ใน
ActivityThread.initializeMainlineModules()
ให้สร้างอินสแตนซ์ของคลาสนี้ แล้วส่งไปยังเมธอดแบบคงที่ที่โมดูลของคุณแสดง ปกติแล้ว คุณจะต้องเพิ่ม@SystemApi(client = MODULE_LIBRARIES)
API แบบคงที่ในคลาสFrameworkInitializer
ที่ใช้ API ดังกล่าว
รูปแบบนี้จะป้องกันไม่ให้โมดูลหลักอื่นๆ เข้าถึง API เหล่านี้เนื่องจากโมดูลอื่นๆ ไม่มีทางรับอินสแตนซ์ของ <YourModule>ServiceManager
ได้ แม้ว่า API ของ get()
และ register()
จะแสดงให้มองเห็นได้ก็ตาม
ต่อไปนี้คือวิธีที่โทรศัพท์รับการอ้างอิงบริการโทรศัพท์ ลิงก์การค้นหาโค้ด
หากใช้ออบเจ็กต์ Binder บริการในโค้ดเนทีฟ คุณจะใช้AServiceManager
API เนทีฟ
API เหล่านี้สอดคล้องกับ ServiceManager
Java API แต่ API เดิมจะแสดงต่อโมดูลหลักโดยตรง อย่าใช้เพื่อลงทะเบียนหรืออ้างอิงออบเจ็กต์ Binder ที่โมดูลของคุณไม่ได้เป็นเจ้าของ หากคุณแสดงออบเจ็กต์ Binder จากเนทีฟ <YourModule>ServiceManager.ServiceRegisterer
ของคุณไม่จำเป็นต้องมีเมธอด register()
คำจำกัดความของสิทธิ์ในโมดูลเมนไลน์
โมดูลหลักที่มี APK อาจกำหนดสิทธิ์ (ที่กำหนดเอง) ใน APK
AndroidManifest.xml
ของตนได้เช่นเดียวกับ APK ปกติ
หากสิทธิ์ที่กําหนดไว้ใช้ภายในโมดูลเท่านั้น ชื่อสิทธิ์ควรมีชื่อแพ็กเกจ APK อยู่ข้างหน้า เช่น
<permission
android:name="com.android.permissioncontroller.permission.MANAGE_ROLES_FROM_CONTROLLER"
android:protectionLevel="signature" />
หากต้องการระบุสิทธิ์ที่กําหนดไว้เป็นส่วนหนึ่งของ API แพลตฟอร์มที่อัปเดตได้ให้กับแอปอื่นๆ ชื่อของสิทธิ์ควรขึ้นต้นด้วย "android.permission." (เช่น สิทธิ์แบบคงที่ของแพลตฟอร์ม) พร้อมชื่อแพ็กเกจของโมดูลเพื่อบ่งบอกว่าเป็น API ของแพลตฟอร์มจากโมดูล ขณะเดียวกันก็หลีกเลี่ยงความขัดแย้งของชื่อ เช่น
<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" />
จากนั้นโมดูลจะแสดงชื่อสิทธิ์นี้เป็นค่าคงที่ API ในแพลตฟอร์ม API ของตนได้ เช่น HealthPermissions.READ_ACTIVE_CALORIES_BURNED