تهدف هذه الصفحة إلى أن تكون دليلاً للمطوّرين لفهم مبادئ العامة التي يفرضها مجلس واجهة برمجة التطبيقات في مراجعات واجهات برمجة التطبيقات.
بالإضافة إلى اتّباع هذه الإرشادات عند كتابة واجهات برمجة التطبيقات، على المطوّرين استخدام أداة API Lint التي تُشفِّر العديد من هذه القواعد في عمليات التحقّق التي تُجريها على واجهات برمجة التطبيقات.
يمكنك اعتبار هذا الدليل دليلاً للقواعد التي تلتزم بها أداة Lint، بالإضافة إلى نصائح عامة حول القواعد التي لا يمكن وضع قواعد لها في هذه الأداة بدقة عالية.
أداة API Lint
تم دمج API Lint
في أداة التحليل الثابت Metalava ويتم تشغيله تلقائيًا
أثناء عملية التحقّق في عملية التطوير المتكامل (CI). يمكنك تشغيله يدويًا من فحص المنصة على الجهاز باستخدام m
checkapi
أو من فحص AndroidX على الجهاز باستخدام./gradlew :path:to:project:checkApi
.
قواعد واجهة برمجة التطبيقات
كانت منصة Android والعديد من مكتبات Jetpack متوفّرة قبل إنشاء هذه المجموعة من الإرشادات، وتطوّر السياسات الموضّحة لاحقًا في هذه الصفحة باستمرار لتلبية احتياجات منظومة Android المتكاملة.
ونتيجةً لذلك، قد لا تلتزم بعض واجهات برمجة التطبيقات الحالية بالإرشادات. وفي حالات أخرى، قد توفّر تجربة أفضل للمستخدِمين لمطوّري التطبيقات إذا كانت واجهة برمجة التطبيقات الجديدة متسقة مع واجهات برمجة التطبيقات الحالية بدلاً من الالتزام الصارم بالإرشادات.
استخدِم حدسك وتواصل مع "مجلس واجهات برمجة التطبيقات" إذا كانت هناك أسئلة صعبة حول واجهة برمجة تطبيقات يجب حلّها أو إرشادات يجب تعديلها.
أساسيات واجهة برمجة التطبيقات
تتعلّق هذه الفئة بالجوانب الأساسية لواجهة برمجة تطبيقات Android.
يجب تنفيذ جميع واجهات برمجة التطبيقات.
بغض النظر عن جمهور واجهة برمجة التطبيقات (على سبيل المثال، واجهة برمجة التطبيقات المتاحة للجميع أو @SystemApi
)، يجب تنفيذ جميع مساحات عرض واجهة برمجة التطبيقات عند دمجها أو عرضها كواجهة برمجة تطبيقات. لا تدمج نماذج واجهة برمجة التطبيقات
المعدّة للتنفيذ في وقت لاحق.
تواجه مساحات عرض واجهات برمجة التطبيقات التي لا تتضمّن عمليات تنفيذ مشاكل متعدّدة:
- ولا يمكن ضمان أنّه تم الكشف عن سطح مناسب أو كامل. إلى أن يتم اختبار واجهة برمجة التطبيقات أو استخدامها من قِبل العملاء، لا تتوفّر طريقة للتأكّد من أنّ العميل لديه واجهات برمجة التطبيقات المناسبة لاستخدام الميزة.
- لا يمكن اختبار واجهات برمجة التطبيقات التي لم يتم تنفيذها في إصدارات المطوّرين التجريبية.
- لا يمكن اختبار واجهات برمجة التطبيقات التي لم يتم تنفيذها في مجموعة اختبار التوافق (CTS).
يجب اختبار جميع واجهات برمجة التطبيقات.
يتوافق ذلك مع متطلبات CTS للمنصة وسياسات AndroidX وبشكل عام فكرة أنّه يجب تنفيذ واجهات برمجة التطبيقات.
يقدّم اختبار مساحات عرض واجهات برمجة التطبيقات ضمانًا أساسيًا بأنّ مساحة عرض واجهة برمجة التطبيقات قابلة للاستخدام وقد تناولنا حالات الاستخدام المتوقّعة. لا يكفي اختبار التوفّر، بل يجب اختبار سلوك واجهة برمجة التطبيقات نفسها.
يجب أن يتضمّن التغيير الذي يضيف واجهة برمجة تطبيقات جديدة الاختبارات المقابلة في موضوع CL أو Gerrit نفسه.
يجب أن تكون واجهات برمجة التطبيقات أيضًا قابلة للاختبار. من المفترض أن تتمكّن من الإجابة عن السؤال "كيف سيختبر مطوّر التطبيقات الرمز البرمجي الذي يستخدم واجهة برمجة التطبيقات؟"
يجب توثيق جميع واجهات برمجة التطبيقات.
يشكّل التوثيق جزءًا أساسيًا من قابلية استخدام واجهة برمجة التطبيقات. على الرغم من أنّ بنية واجهة برمجة التطبيقات قد تبدو واضحة، لن يفهم أي عملاء جدد الدلالات أو السلوك أو السياق المتعلّق بواجهة برمجة التطبيقات.
يجب أن تكون جميع واجهات برمجة التطبيقات التي تم إنشاؤها متوافقة مع الإرشادات.
يجب أن تلتزم واجهات برمجة التطبيقات التي يتم إنشاؤها بواسطة الأدوات بإرشادات واجهات برمجة التطبيقات نفسها المتّبعة في رمز البرمجة المكتوب يدويًا.
الأدوات غير المُقترَحة لإنشاء واجهات برمجة التطبيقات:
AutoValue
: ينتهك الإرشادات بطرق مختلفة، على سبيل المثال، لا تتوفّر طريقة لتنفيذ فئات القيم النهائية أو منشئي القيم النهائية بالطريقة التي يعمل بها AutoValue.
نمط الرمز
تتعلّق هذه الفئة بأسلوب الرموز البرمجية العام الذي يجب أن يستخدمه المطوّرون، خاصةً عند كتابة واجهات برمجة التطبيقات المتاحة للجميع.
اتّباع اصطلاحات الترميز العادية، باستثناء الحالات التي تمّت الإشارة إليها
يمكنك الاطّلاع على قواعد الترميز في Android للمشاركين الخارجيين هنا:
https://source.android.com/source/code-style.html
بشكل عام، نميل إلى اتّباع اصطلاحات الترميز العادية في Java وKotlin.
يجب عدم كتابة الاختصارات بأحرف كبيرة في أسماء الطرق.
على سبيل المثال، يجب أن يكون اسم الطريقة runCtsTests
وليس runCTSTests
.
يجب ألا تنتهي الأسماء بـ Impl.
يؤدي ذلك إلى الكشف عن تفاصيل التنفيذ، لذا تجنَّب ذلك.
صفوف
يصف هذا القسم القواعد المتعلقة بالفئات والواجهات والوراثة.
اكتساب فئات عامة جديدة من الفئة الأساسية المناسبة
تؤدي عملية اكتساب السمات إلى عرض عناصر واجهة برمجة التطبيقات في الدرجة الفرعية التي قد لا تكون مناسبة.
على سبيل المثال، تبدو الفئة الفرعية العامة الجديدة من FrameLayout
مثل FrameLayout
بالإضافة إلى السلوكيات الجديدة وعناصر واجهة برمجة التطبيقات. إذا لم تكن واجهة برمجة التطبيقات المُكتسَبة هذه مناسبة
لحالة الاستخدام، يمكنك اكتسابها من فئة أعلى في الشجرة، على سبيل المثال،
ViewGroup
أو View
.
إذا كنت تميل إلى إلغاء طرق من الفئة الأساسية لرمي سوى
UnsupportedOperationException
، راجِع الفئة الأساسية التي تستخدمها.
استخدام فئات المجموعات الأساسية
سواء كنت تأخذ مجموعة كوسيطة أو تعرضها كقيمة، عليك دائمًا
استخدام الفئة الأساسية بدلاً من التنفيذ المحدّد (مثل عرض
List<Foo>
بدلاً من ArrayList<Foo>
).
استخدِم فئة أساسية تعبّر عن القيود المناسبة لواجهة برمجة التطبيقات. على سبيل المثال، استخدِم List
لواجهة برمجة تطبيقات يجب أن تكون مجموعتها مرتبة، واستخدِم Set
لواجهة برمجة تطبيقات يجب أن تتألّف مجموعتها من عناصر فريدة.
في Kotlin، يُفضَّل استخدام المجموعات الثابتة. راجِع قابلية تغيير المجموعة لمزيد من التفاصيل.
الفئات المجردة مقابل الواجهات
توفّر Java 8 إمكانية استخدام طرق الواجهات التلقائية، ما يسمح لمصمّمي واجهات برمجة التطبيقات بإضافة طرق إلى الواجهات مع الحفاظ على التوافق الثنائي. يجب أن يستهدف رمز النظام الأساسي وجميع مكتبات Jetpack الإصدار 8 من Java أو الإصدارات الأحدث.
في الحالات التي يكون فيها التنفيذ التلقائي غير مرتبط بحالة، يجب أن يفضّل مصمّمو واجهات برمجة التطبيقات استخدام الواجهات بدلاً من الفئات المجردة، أي أنّه يمكن تنفيذ طرق الواجهات التلقائية كطلبات إلى طرق الواجهات الأخرى.
في الحالات التي يكون فيها إنشاء أو حالة داخلية مطلوبَين من التنفيذ default ، يجب استخدام الفئات المجردة.
في كلتا الحالتَين، يمكن لمصمّمي واجهات برمجة التطبيقات اختيار ترك طريقة واحدة مجردة لمحاولة تبسيط الاستخدام كدالّة لامبادية:
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
، في مجموعات methods المساعدة. بدلاً من ذلك، ضَع الطرق مباشرةً في الفئات المرتبطة
أو في دوالّ إضافة Kotlin.
في الحالات التي تربط فيها الطرق بين فئات متعددة، امنح الفئة التي تحتوي على الطرق اسمًا ذا معنى يوضّح وظيفتها.
في حالات محدودة جدًا، قد يكون استخدام اللاحقة Helper
مناسبًا:
- تُستخدَم لإنشاء السلوك التلقائي.
- قد يشمل ذلك تفويض السلوك الحالي إلى فئات جديدة
- قد تتطلّب الحالة ثباتًا
- تشمل عادةً
View
على سبيل المثال، إذا كان نقل التلميحات التوضيحية إلى الإصدارات القديمة يتطلّب الاحتفاظ بالحالة المرتبطة
بـ View
واستدعاء عدة طرق في View
لتثبيت الإصدار القديم،
سيكون TooltipHelper
اسم فئة مقبولًا.
لا تعرض الرمز الذي تم إنشاؤه باستخدام لغة IDL كواجهات برمجة تطبيقات عامة مباشرةً.
احتفظ بالرمز الذي تم إنشاؤه باستخدام لغة IDL كتفاصيل التنفيذ. ويشمل ذلك protobuf أو sockets أو FlatBuffers أو أي واجهة برمجة تطبيقات أخرى غير Java أو NDK. ومع ذلك، فإنّ معظم IDL في Android مضمّنة في AIDL، لذا تركّز هذه الصفحة على AIDL.
لا تستوفي فئات AIDL التي تم إنشاؤها متطلبات دليل أسلوب واجهة برمجة التطبيقات (على سبيل المثال، لا يمكنها استخدام التحميل الزائد)، ولم يتم تصميم أداة AIDL صراحةً للحفاظ على توافق واجهة برمجة التطبيقات للغة، لذا لا يمكنك تضمينها في واجهة برمجة تطبيقات عامة.
بدلاً من ذلك، أضِف طبقة واجهة برمجة تطبيقات علنية فوق واجهة AIDL، حتى إذا كانت في البداية عبارة عن حزمة برمجية خارجية بسيطة.
واجهات Binder
إذا كانت واجهة Binder
هي تفاصيل تنفيذ، يمكن تغييرها بحرية
في المستقبل، مع السماح للطبقة العامة بالحفاظ على التوافق المطلوب مع الإصدارات السابقة. على سبيل المثال، قد تحتاج إلى إضافة دلايلات
جديدة إلى الطلبات الداخلية، أو تحسين عدد عمليات تبادل البيانات بين العمليات باستخدام
التجميع أو البث، أو باستخدام الذاكرة المشتركة، أو ما شابه ذلك. لا يمكن تنفيذ أيّ من هذه الإجراءات
إذا كانت واجهة AIDL هي أيضًا واجهة برمجة التطبيقات المتاحة للجميع.
على سبيل المثال، لا تعرض FooService
كواجهة برمجة تطبيقات عامة مباشرةً:
// 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);
}
}
إذا احتجت لاحقًا إلى وسيطة جديدة لهذا الطلب، يمكن أن تكون الواجهة الداخلية مهام زائدة قليلة ومريحة تتم إضافتها إلى واجهة برمجة التطبيقات المتاحة للجميع. يمكنك استخدام الطبقة الخارجية للتعامل مع المخاوف الأخرى المتعلقة بالتوافق مع الإصدارات القديمة أثناء تطوير التنفيذ:
/**
* @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" لاستخدامها في التطبيقات)، فإنّ
متطلبات واجهة IPC الثابتة والمنشورة والمزوّدة بإصدار يعني أنّه
يصعب كثيرًا تطوير الواجهة نفسها. ومع ذلك، لا يزال من المفيد
أن تتضمّن طبقة ملف تعريف لواجهة برمجة التطبيقات، وذلك لمطابقة إرشادات واجهة برمجة التطبيقات الأخرى وتسهيل استخدام واجهة برمجة التطبيقات العامة نفسها في إصدار جديد من واجهة IPC، إذا أصبح ذلك ضروريًا.
عدم استخدام عناصر Binder الأوّلية في واجهة برمجة التطبيقات المتاحة للجميع
لا يحمل عنصر Binder
أي معنى من تلقاء نفسه، وبالتالي يجب عدم
استخدامه في واجهة برمجة التطبيقات المتاحة للجميع. ومن حالات الاستخدام الشائعة استخدام Binder
أو IBinder
كرمز
مميّز لأنّه يحتوي على دلالات الهوية. بدلاً من استخدام عنصر Binder
أولي،
استخدِم فئة رمز تعبيري ملفوف بدلاً من ذلك.
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
مساحة كبيرة لواجهة برمجة التطبيقات تسمح
بتغيير عشوائي لقيمة المستقبل وتتضمّن إعدادات تلقائية معرّضة للخطأ
.
في المقابل، لا تتوفّر في java.util.concurrent.Future
ميزة الاستماع غير المحظور،
ما يجعل من الصعب استخدامها مع الرموز البرمجية غير المتزامنة.
في رمز النظام الأساسي وواجهات برمجة تطبيقات المكتبات ذات المستوى الأدنى التي يستخدمها كلّ من Kotlin و
Java، يُفضَّل استخدام تركيبة من دالة الاستدعاء عند اكتمال العملية Executor
، وإذا كانت
واجهة برمجة التطبيقات تتيح الإلغاء 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
يمكن أن يكون له مزايا في بعض مساحات عرض واجهة برمجة التطبيقات، إلا أنّه غير متسق
مع مساحة عرض واجهة برمجة تطبيقات Android الحالية. توفّر @Nullable
و@NonNull
مساعدة في توفير أدوات لأمان null
، وتفرض Kotlin عقود إمكانية قبول القيم الفارغة
على مستوى المُجمِّع، ما يجعل 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
أو الفئات التي تحتوي فقط على
ثوابت أو طرق ثابتة أو الفئات التي لا يمكن إنشاء مثيل لها
أداة إنشاء خاصة واحدة على الأقل لمنع إنشاء مثيل باستخدام
أداة الإنشاء التلقائية التي لا تحتوي على أيّ مَعلمات.
public final class Log {
// Not instantiable.
private Log() {}
}
العناصر الفردية
لا يُنصح باستخدام العناصر الفردية لأنّها تتضمن العيوب التالية المتعلّقة بالاختبار:
- تتم إدارة الإنشاء من خلال الصف، ما يمنع استخدام العناصر المزيّفة.
- لا يمكن أن تكون الاختبارات محكمة بسبب الطبيعة الثابتة للعنصر الفردي.
- لحلّ هذه المشاكل، على المطوّرين معرفة تفاصيل العنصر الفردي الداخلي أو إنشاء غلاف حوله.
ننصحك باستخدام نمط المثيل الفردي الذي يعتمد على فئة أساسية مجردة لمعالجة هذه المشاكل.
مثيل واحد
تستخدِم فئات المثيل الواحد فئة أساسية مجردة مع دالة 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
}
}
}
تختلف المثيل الفردي عن العنصر الفردي في أنّه يمكن للمطوّرين
إنشاء نسخة زائفة من SingleInstance
واستخدام إطار عمل حقن التبعيات الخاص بهم لإدارة التنفيذ بدون الحاجة إلى إنشاء حزمة ملف برمجي، أو يمكن للمكتبة توفير نسخة زائفة خاصة بها في عنصر -testing
.
يجب أن تنفّذ الفئات التي تُطلق الموارد واجهة AutoCloseable.
يجب أن تنفِّذ الفصول التي تُطلق الموارد من خلال close
أو release
أو destroy
أو مثيلاتها
طريقة java.lang.AutoCloseable
للسماح للمطوّرين بتنظيف
هذه الموارد تلقائيًا عند استخدام كتلة try-with-resources
.
تجنَّب تقديم فئات فرعية جديدة من View في Android.*
لا تُعرِض فئات جديدة تكتسِب مباشرةً أو بشكل غير مباشر من
android.view.View
في واجهة برمجة التطبيقات العامة للمنصة (أي في android.*
).
أصبحت مجموعة أدوات واجهة المستخدم من Android الآن تعتمد على Compose أولاً. يجب أن تكون ميزات واجهة المستخدم الجديدة التي تعرضها المنصة متاحة كواجهات برمجة تطبيقات من المستوى الأدنى يمكن استخدامها لتنفيذ Jetpack Compose ومكونات واجهة المستخدم المستندة إلى View اختياريًا للمطوّرين في مكتبات Jetpack. من خلال توفير هذه المكوّنات في المكتبات، تتوفر فرص لتنفيذ عمليات النقل إلى الإصدارات القديمة عندما لا تكون ميزات المنصة متوفّرة.
الحقول
تتناول هذه القواعد الحقول العامة في الفئات.
عدم عرض الحقول الأوّلية
يجب ألا تعرض فئات Java الحقول مباشرةً. يجب أن تكون الحقول خاصة ولا يمكن الوصول إليها إلا باستخدام وظائف الحصول على القيم وضبطها العامة بغض النظر عمّا إذا كانت هذه الحقول نهائية أم لا.
تشمل الاستثناءات النادرة هياكل البيانات الأساسية التي لا حاجة فيها إلى تحسين
سلوك تحديد حقل أو استرجاعه. في هذه الحالات، يجب تسمية الحقول باستخدام اصطلاحات التسمية العادية للمتغيّرات، على سبيل المثال، Point.x
و
Point.y
.
يمكن لفئات Kotlin عرض السمات.
يجب وضع علامة "نهائي" على الحقول المعروضة.
لا يُنصح بشدة باستخدام الحقول الأوّلية (@see
عدم عرض الحقول الأوّلية). وفي الحالات النادرة التي يتم فيها عرض حقل
كحقل متاح للجميع، ضَع علامة final
على هذا الحقل.
يجب عدم عرض الحقول الداخلية.
لا تُشير إلى أسماء الحقول الداخلية في واجهة برمجة التطبيقات المتاحة للجميع.
public int mFlags;
استخدام "الملف العلني" بدلاً من "الملف المحمي"
@راجِع استخدام الإعداد "علني" بدلاً من "محمي".
الثابتة
هذه قواعد حول الثوابت العلنية.
يجب ألّا تتداخل ثوابت العلامة مع القيم int أو long.
تشير العلامات إلى الوحدات التي يمكن دمجها في بعض قيم الائتلاف. إذا لم يكن الأمر كذلك،
لا تستخدِم المتغيّر أو الثابت 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;
اطّلِع على @IntDef
لعلامات قناع الوحدات للحصول على مزيد من
المعلومات عن تحديد الثوابت العامة للعلامات.
يجب أن تستخدم الثوابت النهائية الثابتة أسلوب تسمية يعتمد على الأحرف اللاتينية الكبيرة فقط مع الفصل بشرطة سفلية.
يجب كتابة جميع الكلمات في الثابت بأحرف كبيرة، ويجب _
فصل الكلمات المتعددة. مثلاً:
public static final int fooThing = 5
public static final int FOO_THING = 5
استخدام البادئات العادية للثوابت
إنّ العديد من الثوابت المستخدَمة في Android مخصّصة لعناصر عادية، مثل العلامات والمفاتيح والإجراءات. يجب أن تحتوي هذه الثوابت على بادئات عادية لكي يسهل التعرّف عليها.
على سبيل المثال، يجب أن تبدأ عناصر المعلومات الإضافية عن النية بـ EXTRA_
. يجب أن تبدأ إجراءات الأهداف بـ ACTION_
. يجب أن تبدأ الثوابت المستخدَمة مع Context.bindService()
بـ BIND_
.
أسماء ونطاقات الثابتات الرئيسية
يجب أن تكون قيم السلسلة الثابتة متسقة مع اسم الثابت نفسه، ويجب أن تكون محدودة النطاق بشكل عام بالحزمة أو النطاق. مثلاً:
public static final String FOO_THING = "foo"
لا يحمل اسمًا متّسقًا أو نطاقًا مناسبًا. بدلاً من ذلك، ننصحك باتّباع الخطوات التالية:
public static final String FOO_THING = "android.fooservice.FOO_THING"
تم حجز البادئات android
في الثوابت السلاسل ذات النطاق المحدّد لمشروع Android
المفتوح المصدر.
يجب تحديد مساحة اسم لإجراءات النوايا والإضافات، بالإضافة إلى إدخالات الحِزم، باستخدام اسم الحزمة التي تم تحديدها فيها.
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"
}
استخدام "الملف العلني" بدلاً من "الملف المحمي"
@راجِع استخدام الإعداد "علني" بدلاً من "محمي".
استخدام بادئات متّسقة
يجب أن تبدأ جميع الثوابت ذات الصلة بالبادئة نفسها. على سبيل المثال، لمجموعة من الثوابت لاستخدامها مع قيم العلامات:
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
، مثل الفئات المُدمجة في IDE IDE.
يجب عدم عرض تنسيقات الموارد والعناصر القابلة للرسم كواجهات برمجة تطبيقات علنية. إذا كان يجب عرضها، يجب تسمية التنسيقات والعناصر القابلة للرسم العلنية باستخدام اصطلاح التسمية under_score، على سبيل المثال،
layout/simple_list_item_1.xml
أو drawable/title_bar_tall.xml
.
عندما يمكن أن تتغيّر الثوابت، اجعلها ديناميكية.
قد يضمِّن المُجمِّع القيم الثابتة، لذا يُعتبَر إبقاء القيم كما هي
جزءًا من عقد واجهة برمجة التطبيقات. إذا كان من الممكن أن تتغيّر قيمة الثابت MIN_FOO
أو MAX_FOO
في المستقبل، ننصحك بجعلها طُرقًا ديناميكية بدلاً من ذلك.
CameraManager.MAX_CAMERAS
CameraManager.getMaxCameras()
مراعاة التوافق مع الإصدارات المستقبلية لوظائف الاستدعاء
إنّ الثوابت المحدّدة في الإصدارات المستقبلية من واجهات برمجة التطبيقات غير معروفة للتطبيقات التي تستهدف واجهات برمجة التطبيقات القديمة. لهذا السبب، يجب أن تأخذ الثوابت التي يتم إرسالها إلى التطبيقات في الاعتبار إصدار واجهة برمجة التطبيقات المستهدَف للتطبيق وأن تربط الثوابت الأحدث بقيمة متسقة. إليك السيناريو التالي:
مصدر حزمة 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!
}
في هذه الحالة، تم تصميم التطبيق ضمن قيود المستوى 22 من واجهة برمجة التطبيقات، وافترض (بشكل معقول إلى حدٍ ما) أنّه لا تتوفّر سوى حالتان ممكنتَان. في حال تلقّي التطبيق الرمز STATUS_FAILURE_RETRY
الذي تمت إضافته حديثًا، يصنّفه
على أنّه نجاح.
يمكن للطرق التي تعرض ثوابت معالجة حالات مماثلة بأمان من خلال تقييد مخرجاتها لمطابقة مستوى واجهة برمجة التطبيقات الذي يستهدفه التطبيق:
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;
}
لا يمكن للمطوّرين توقّع ما إذا كانت قائمة الثوابت قد تتغيّر في
المستقبل. إذا حدّدت واجهة برمجة تطبيقات باستخدام الثابت UNKNOWN
أو UNSPECIFIED
الذي
يبدو أنّه عنصر شامل، يفترض المطوّرون أنّ الثوابت المنشورة عند
كتابة تطبيقاتهم شاملة. إذا كنت لا تريد ضبط هذا التوقّع،
راجِع ما إذا كان استخدام ثابت شامل هو فكرة جيدة لواجهة برمجة التطبيقات.
بالإضافة إلى ذلك، لا يمكن للمكتبات تحديد targetSdkVersion
الخاصة بها بشكل منفصل عن
التطبيق، كما أنّ التعامل مع تغييرات سلوك targetSdkVersion
من رمز المكتبة هو عملية
معقدة وعرضة للخطأ.
عدد صحيح أو سلسلة ثابتة
استخدِم الثوابت الصحيحة و@IntDef
إذا لم تكن مساحة الاسم للقيم
قابلة للتوسيع خارج حِزمك. استخدِم الثوابت السلاسل إذا كانت مساحة الاسم
مشترَكة أو يمكن تمديدها باستخدام رمز خارج حِزمتك.
فئات البيانات
تمثّل فئات البيانات مجموعة من الخصائص الثابتة، وتقدّم مجموعة صغيرة و محدّدة جيدًا من دوال المرافق للتفاعل مع هذه البيانات.
لا تستخدِم data class
في واجهات برمجة تطبيقات Kotlin العامة، لأنّ مُجمِّع Kotlin لا يضمن
توافق واجهة برمجة التطبيقات للغة أو التوافق الثنائي للرمز البرمجي الذي تم إنشاؤه. بدلاً من ذلك،
نفِّذ الدوالّ المطلوبة يدويًا.
إنشاء مثيل
في Java، يجب أن تقدّم فئات البيانات أسلوب إنشاء عندما يكون هناك عدد قليل من السمات
أو استخدام نمط Builder
عندما يكون هناك العديد من السمات.
في Kotlin، يجب أن تقدّم فئات البيانات عنصرًا بانيًا مع وسيطات تلقائية بغض النظر عن عدد السمات. قد تستفيد أيضًا فئات البيانات المحدّدة في Kotlin من توفير أداة إنشاء عند استهداف عملاء Java.
التعديل والنسخ
في الحالات التي يجب فيها تعديل البيانات، قدِّم إما فئة
Builder
تتضمّن أسلوبًا لإنشاء نسخة (Java) أو دالة عضو
copy()
(Kotlin) تُعيد كائنًا جديدًا.
عند تقديم دالة copy()
في Kotlin، يجب أن تتطابق الوسيطات مع كونstructor للفئة، ويجب تعبئة الإعدادات التلقائية باستخدام القيم الحالية للكائن:
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)
.
الطرق
هذه قواعد حول تفاصيل مختلفة في الطرق، حول المَعلمات وأسماء methods وأنواع الإرجاع ومُحدِّدات الوصول.
الوقت
تتناول هذه القواعد كيفية التعبير عن مفاهيم الوقت، مثل التواريخ والمدة، في واجهات برمجة التطبيقات.
يُفضَّل استخدام أنواع java.time.* كلما أمكن.
تتوفّر java.time.Duration
وjava.time.Instant
والعديد من أنواع java.time.*
الأخرى
في جميع إصدارات النظام الأساسي من خلال
إزالة الرمز البرمجي المُعقّد و
يجب استخدامها عند التعبير عن الوقت في مَعلمات واجهة برمجة التطبيقات أو قيم الإرجاع.
يُفضَّل عرض صيغ واجهة برمجة التطبيقات التي تقبل أو تعرض
java.time.Duration
أو java.time.Instant
فقط، مع حذف الصيغ البدائية التي تستخدم
حالة الاستخدام نفسها، ما لم يكن نطاق واجهة برمجة التطبيقات هو نطاق يؤثر فيه تخصيص العناصر في أنماط الاستخدام المقصودة على الأداء بشكل كبير.
يجب تسمية الطرق التي تعبّر عن المدّة باسم duration.
إذا كانت قيمة الوقت تعبّر عن المدة الزمنية المعنيّة، اضبط اسم المَعلمة على "duration" وليس "time".
ValueAnimator.setTime(java.time.Duration);
ValueAnimator.setDuration(java.time.Duration);
الاستثناءات:
يكون "المهلة" مناسبًا عندما تنطبق المدة على قيمة مهلة تحديدًا.
يكون "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 فقط)
إذا تم شحن طريقة مع مَعلمة لها قيمة تلقائية، فإنّ إزالة القيمة التلقائية تؤدي إلى حدوث تغيير في المصدر.
يجب أن تكون مَعلمات الطريقة الأكثر تمييزًا وتحديدًا أولاً.
إذا كانت لديك طريقة تتضمّن مَعلمات متعدّدة، ضَع المَعلمات الأكثر صلة بالموضوع أولاً. إنّ المَعلمات التي تحدّد العلامات والخيارات الأخرى أقل أهمية من تلك التي تصف العنصر الذي يتم اتّخاذ إجراء بشأنه. إذا كان هناك رمز برمجي للاستدعاء عند اكتمال العملية، ضَع هذا الرمز في آخر الإجراء.
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 في الحالات التالية:
- يجب أن تكون خصائص العنصر الناتج غير قابلة للتغيير.
- هناك عدد كبير من السمات المطلوبة، على سبيل المثال، العديد من وسيطات المُنشئ
- هناك علاقة معقّدة بين المواقع في وقت إنشائها، مثلاً، يجب إكمال خطوة إثبات الهوية. يُرجى العِلم أنّ هذا المستوى من التعقيد يشير غالبًا إلى مشاكل في سهولة استخدام واجهة برمجة التطبيقات.
فكِّر في ما إذا كنت بحاجة إلى مصمّم. تكون أدوات الإنشاء مفيدة في واجهة برمجة التطبيقات إذا كانت تُستخدَم لإجراء ما يلي:
- ضبط عدد قليل فقط من مجموعة كبيرة من مَعلمات الإنشاء الاختيارية
- ضبط العديد من مَعلمات الإنشاء الاختيارية أو المطلوبة المختلفة، التي تكون أحيانًا من أنواع مشابهة أو متطابقة، حيث يمكن أن تصبح مواقع الاستدعاء مربكة عند قراءتها أو عرضة للخطأ عند كتابتها
- يمكنك ضبط عملية إنشاء عنصر بشكل تدريجي، حيث قد تُجري عدة قطعة مختلفة من رمز الإعدادات مكالمات على أداة الإنشاء كتفاصيل التنفيذ.
- السماح بتوسيع نطاق نوع معيّن من خلال إضافة مَعلمات اختيارية إضافية لإنشاء العناصر في إصدارات واجهة برمجة التطبيقات المستقبلية
إذا كان لديك نوع يتضمّن ثلاث مَعلمات مطلوبة أو أقلّ بدون مَعلمات اختيارية، يمكنك في معظم الأحيان تخطّي أداة الإنشاء واستخدام أسلوب إنشاء عادي.
يجب أن تفضّل الفصول المشتقة من 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، يجب إنشاء كل
أدوات الإنشاء من خلال طريقة إنشاء وليس من خلال أسلوب إنشاء static. بالنسبة إلى واجهات برمجة التطبيقات المستندة إلى Kotlin، يجب أن تكون Builder
علنية حتى إذا كان من المتوقّع أن يعتمد مستخدمو Kotlin
بشكل ضمني على أداة الإنشاء من خلال آلية إنشاء على غرار أسلوب أسلوب المصنع/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
، إلى طرق ضبط القيمة.
من المفترض أن يُعرِض مُنشئ "المُنشئ" خطأ NullPointerException
(ننصح باستخدام
Objects.requireNonNull
) في حال عدم تحديد أيّ وسيطات مطلوبة.
يجب أن تكون فئات "أداة الإنشاء" فئات داخلية ثابتة نهائية من أنواعها المُنشأة.
من أجل التنظيم المنطقي داخل الحزمة، يجب
عادةً عرض فئات المُنشئ كفئات داخلية نهائية لأنواعها المُنشأة، على سبيل المثال
Tone.Builder
بدلاً من ToneBuilder
.
يمكن أن تتضمّن الوحدات الأساسية أسلوب إنشاء لإنشاء مثيل جديد من مثيل حالي.
يمكن أن تتضمّن أدوات الإنشاء دالة إنشاء بالنسخ لإنشاء مثيل جديد من أدوات الإنشاء أو كائن تم إنشاؤه. يجب عدم توفير methods بديلة لإنشاء نُسخ من أدوات الإنشاء من أدوات إنشاء حالية أو عناصر إنشاء.
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);
}
}
يجب أن تأخذ وظائف الإعداد في أداة الإنشاء وسيطات @Nullable إذا كانت أداة الإنشاء تحتوي على دالة إنشاء بالنسخ.
من الضروري إعادة الضبط إذا كان من الممكن إنشاء مثيل جديد من أداة إنشاء من مثيل
حالي. إذا لم تتوفّر طريقة وضع تصميم للنسخ، قد يحتوي المُنشئ على @Nullable
أو @NonNullable
.
public static class Builder {
public Builder(Builder original);
public Builder setObjectValue(@Nullable Object value);
}
يمكن أن تأخذ وظائف الإعداد في أداة الإنشاء وسيطات @Nullable للسمات الاختيارية.
غالبًا ما يكون من الأسهل استخدام قيمة nullable للإدخال من الدرجة الثانية، خاصةً في Kotlin التي تستخدِم الوسيطات التلقائية بدلاً من أدوات الإنشاء وعمليات التحميل الزائد.
بالإضافة إلى ذلك، ستطابق @Nullable
وظائف الإعداد هذه مع وظائف الحصول عليها، والتي يجب أن تكون
@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);
لا تُخلط بين السيناريوهَين.
Value v = new Value.Builder(requiredValue)
.setImmutableProperty(immutableValue)
.setUsefulMutableProperty(usefulValue)
.build();
Result r = v.performSomeAction();
Key k = callSomeMethod(r);
map.put(k, v);
يجب ألّا تتضمّن العناصر المصنّعة طرق الحصول.
يجب أن يكون مُنشئ القيمة في الكائن الذي تم إنشاؤه، وليس في أداة الإنشاء.
يجب أن تتضمّن وظائف الإعداد في أداة الإنشاء وظائف جلب مقابلة في الفئة التي تم إنشاؤها.
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()
تُعرِض مثيلًا
للكائن الذي تم إنشاؤه.
يجب أن تُعرِض طرق Builder.build() عناصر @NonNull.
من المتوقّع أن تُعرِض طريقة build()
لصانع النموذج مثيلًا غير صفري من
الكائن الذي تم إنشاؤه. في حال تعذّر إنشاء العنصر بسبب paramaters
غير صالحة، يمكن تأجيل عملية التحقّق من الصحة إلى طريقة الإنشاء ويجب طرح سوى
IllegalStateException
.
عدم الكشف عن الأقفال الداخلية
يجب عدم استخدام الكلمة الرئيسية 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
.
يُرجى العِلم أنّ تنفيذ عمل كثيف الحساب مرة واحدة وتخزين القيمة في ذاكرة التخزين المؤقت للمكالمات اللاحقة لا يزال يُعدّ تنفيذ عمل كثيف الحساب. لا يتم توزيع وقت الاستجابة غير المقبول على اللقطات.
استخدام 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()
بشكل عام، يجب كتابة أسماء الطرق على شكل أسئلة يتم الردّ عليها من خلال قيمة العبارة return.
طرق خصائص Kotlin
بالنسبة إلى خاصية فئة var foo: Foo
، ستُنشئ Kotlin طريقتَي get
/set
باستخدام قاعدة متّسقة: إضافة get
في بداية الحرف الأول وكتابته بأحرف كبيرة لطريقة
الحصول، وإضافة set
في بداية الحرف الأول وكتابته بأحرف كبيرة لطريقةset
. سيؤدي تعريف
السمة إلى إنشاء طريقتَين باسمَي public Foo getFoo()
و
public void setFoo(Foo foo)
على التوالي.
إذا كانت السمة من النوع Boolean
، يتم تطبيق قاعدة إضافية في توليد اسم العنصر: إذا كان اسم السمة يبدأ بالرمز is
، لا يتم prependedget
لاسم طريقة 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
عناصر وصول إلى قناع الشبكة
راجِع مقالة استخدام @IntDef
لعلامات قناع الوحدات للاطّلاع على إرشادات API
بشأن تحديد علامات قناع الوحدات.
أدوات الضبط
يجب توفير طريقتَي ضبط: إحداهما تأخذ سلسلة بتات كاملة و تُلغي جميع العلامات الحالية، والأخرى تأخذ قناع بتات مخصّصًا للسماح بقدر أكبر من المرونة.
/**
* 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);
أدوات الحصول على البيانات
يجب توفير دالة واحدة للحصول على قناع البتات الكامل.
/**
* 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
في واجهة برمجة التطبيقات المتاحة للجميع. في نهاية المطاف، يؤدي الوصول المحمي إلى
إزعاج المطوّرين على المدى الطويل، لأنّه عليهم إلغاء الإعدادات لتوفير
موصّلات عامة في الحالات التي كان فيها الوصول الخارجي تلقائيًا مماثلاً
لهذه الطريقة.
تذكَّر أنّ protected
مستوى العرض لا يمنع المطوّرين من طلب
واجهة برمجة تطبيقات، بل يجعل الأمر أكثر إزعاجًا قليلاً.
عدم تنفيذ equals() أو hashCode() أو كليهما
إذا تجاوزت أحدهما، عليك تجاوز الآخر.
تنفيذ toString() لفئات البيانات
ننصح بتجاهل toString()
في فئات البيانات لمساعدة المطوّرين في تصحيح أخطاء
التعليمات البرمجية.
يجب توضيح ما إذا كان الناتج يخصّ سلوك البرنامج أو تصحيح الأخطاء.
حدِّد ما إذا كنت تريد أن يعتمد سلوك البرنامج على عملية التنفيذ أم لا. على سبيل المثال، توثّق UUID.toString() وFile.toString() تنسيقهما المحدّد للبرامج لاستخدامه. إذا كنت تعرض معلومات لتصحيح الأخطاء فقط، مثل Intent، يُفترض أن تكون المستندات مُكتسَبة من الفئة الرئيسية.
عدم تضمين معلومات إضافية
يجب أن تكون جميع المعلومات المتاحة من toString()
متاحة أيضًا من خلال
واجهة برمجة التطبيقات العامة للعنصر. وفي حال عدم إجراء ذلك، فإنّك تشجّع المطوّرين على تحليل
البيانات الناتجة من toString()
والاعتماد عليها، ما سيؤدي إلى منع إجراء تغييرات في المستقبل. من الممارسات الجيدة
تنفيذ toString()
باستخدام واجهة برمجة التطبيقات العامة للعنصر فقط.
عدم الاعتماد على نتائج تصحيح الأخطاء
على الرغم من أنّه من المستحيل منع المطوّرين من الاعتماد على نتائج تصحيح الأخطاء، فإنّ تضمين System.identityHashCode
العنصر في toString()
الناتج سيجعل من غير المرجّح أن يكون لعنصرَين مختلفَين 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();
}
يجب أن تقبل الطرق التي تقبل عناصر File أيضًا مصادر البيانات.
لا تكون مواقع تخزين البيانات على Android دائمًا ملفات على القرص. على سبيل المثال، يتم تمثيل
المحتوى الذي يتم تمريره عبر حدود المستخدِم على شكل content://
Uri
. لأجل
تفعيل معالجة مصادر البيانات المختلفة، يجب أن تقبل واجهات برمجة التطبيقات التي تقبل عناصر File
عناصر InputStream
أو OutputStream
أو كليهما.
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
عشوائي عندما يتطلب الإطار المرجعي
استخدام إحدى القيم الثابتة المحدّدة). استخدِم أيًا من التعليقات التوضيحية التالية أو كلها عند الاقتضاء:
إمكانية قبول القيم الفارغة
تكون التعليقات التوضيحية الصريحة عن إمكانية قبول القيم الفارغة مطلوبة لواجهات برمجة تطبيقات Java، ولكنّ مفهوم إمكانية قبول القيم الفارغة هو جزء من لغة Kotlin، ويجب عدم استخدام التعليقات التوضيحية عن إمكانية قبول القيم الفارغة مطلقًا في واجهات برمجة تطبيقات Kotlin.
@Nullable
: يشير إلى أنّه يمكن أن تكون قيمة الإرجاع أو المَعلمة أو الحقل المحدّدة
فارغة:
@Nullable
public String getName()
public void setName(@Nullable String name)
@NonNull
: يشير إلى أنّه لا يمكن
أن تكون قيمة الإرجاع أو المَعلمة أو الحقل معيّنة فارغة. إنّ وضع علامة @Nullable
على العناصر أمر جديد نسبيًا في Android، لذا لم يتم توثيق معظم methods في واجهة برمجة التطبيقات لنظام التشغيل Android بشكل منتظم. لذلك، لدينا
ثلاث حالات: "غير معروف، @Nullable
، @NonNull
"، ولهذا السبب، يُعدّ @NonNull
جزءًا
من إرشادات واجهة برمجة التطبيقات:
@NonNull
public String getName()
public void setName(@NonNull String name)
بالنسبة إلى مستندات نظام Android الأساسي، سيؤدي التعليق التوضيحي لمَعلمات الطريقة إلى توليد مستندات تلقائيًا في شكل "قد تكون هذه القيمة فارغة" ما لم يتم استخدام "فارغة" بشكل صريح في مكان آخر من مستند المَعلمة.
الطرق الحالية "التي لا يمكن أن تكون فارغة": يمكن وضع تعليق توضيحي @Nullable
على الطرق الحالية في واجهة برمجة التطبيقات التي لا تحتوي على@Nullable
تعليق توضيحي مُعلَن إذا كانت الطريقة يمكنها عرضnull
في ظل ظروف محدّدة وواضحة (مثل findViewById()
).
يجب إضافة طرق @NotNull requireFoo()
مصاحبة تُعرِضIllegalArgumentException
للمطوّرين الذين لا يريدون التحقّق من القيمة الخالية.
طرق الواجهة: يجب أن تضيف واجهات برمجة التطبيقات الجديدة التعليق التوضيحي المناسب عند تنفيذ طرق الواجهة، مثل Parcelable.writeToParcel()
(أي أنّ الطريقة في فئة التنفيذ يجب أن تكون writeToParcel(@NonNull Parcel,
int)
، وليس writeToParcel(Parcel, int)
). ومع ذلك، لا يلزم "إصلاح" واجهات برمجة التطبيقات الحالية التي لا تتضمّن التعليقات التوضيحية.
فرض قابلية الإبطال
في Java، يُنصح باستخدام الطرق لإجراء التحقّق من صحة الإدخال للمَعلمات @NonNull
باستخدام
Objects.requireNonNull()
وطرح NullPointerException
عندما تكون المَعلمات فارغة. ويتم تنفيذ ذلك
تلقائيًا في Kotlin.
المراجع
معرّفات الموارد: يجب إضافة تعليق توضيحي إلى المَعلمات الصحيحة التي تشير إلى معرّفات موارد معيّنة
باستخدام تعريف نوع الموارد المناسب.
يتوفّر تعليق توضيحي لكل نوع من أنواع الموارد، مثل @StringRes
و@ColorRes
و@AnimRes
، بالإضافة إلى العنوان النائب @AnyRes
. على سبيل المثال:
public void setTitle(@StringRes int resId)
@IntDef للمجموعات الثابتة
الثوابت السحرية: يجب وضع تعليق توضيحي مناسب باستخدام @StringDef
أو @IntDef
على مَعلمتَي String
وint
اللتين تهدفان إلى تلقّي واحدة
من مجموعة محدودة من القيم المحتمَلة التي يُشار إليها باستخدام الثوابت العامة. تسمح لك هذه التعليقات التوضيحية
بإنشاء تعليق توضيحي جديد يمكنك استخدامه يعمل مثل 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);
يُنصح باستخدام الطرق للتحقّق من صحة المَعلمات المُشار إليها annotated parameters
وعرض خطأ IllegalArgumentException
إذا لم تكن المَعلمة جزءًا من
@IntDef
@IntDef لعلامات قناع البتات
يمكن أن يحدّد التعليق التوضيحي أيضًا أنّ الثوابت هي علامات، ويمكن دمجها مع & و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
. يمكنك تضمين قيم متعددة لـ "البادئة" تُستخدَم لإصدار مستندات تلقائيًا لجميع القيم.
@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";
توفير قيمة صفرية متوافقة للعناصر التي يمكن تجاوزها
لضمان توافق واجهة برمجة التطبيقات، يجب أن تكون قيمة عدم توفّر القيم في عمليات الإلغاء متوافقة مع قيمة عدم توفّر القيم الحالية للعنصر الرئيسي. يوضّح الجدول التالي متطلبات التوافق. بوضوح، يجب أن تكون عمليات الإلغاء بنفس مستوى القيود أو أكثر تقييدًا من العنصر الذي تلغيه.
النوع | أحد الوالدين | طفل |
---|---|---|
نوع القيمة التي يتم عرضها | غير مُشارَك فيها | غير مُشارَك في التعليقات التوضيحية أو غير صفري |
نوع القيمة التي يتم عرضها | يمكن حذفها | يمكن أن تكون فارغة أو غير فارغة |
نوع القيمة التي يتم عرضها | Nonnull | Nonnull |
وسيطة طريفة | غير مُشارَك فيها | غير مُعلَق أو قابل للحذف |
وسيطة طريفة | يمكن حذفها | يمكن حذفها |
وسيطة طريفة | Nonnull | يمكن أن تكون فارغة أو غير فارغة |
يُفضَّل استخدام وسيطات غير فارغة (مثل @NonNull) كلما أمكن ذلك.
عند تحميل الطرق بشكل زائد، يُفضَّل أن تكون جميع الوسيطات غير صفرية.
public void startActivity(@NonNull Component component) { ... }
public void startActivity(@NonNull Component component, @NonNull Bundle options) { ... }
تنطبق هذه القاعدة أيضًا على وظائف ضبط السمات التي تمّ تحميلها بشكل زائد. يجب أن تكون الوسيطة الأساسية غير صفرية، ويجب تنفيذ محو السمة كطريقة منفصلة. ويمنع ذلك إجراء طلبات غير منطقية يجب فيها على المطوّر ضبط مَعلمات ختامية حتى لو لم تكن مطلوبة.
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()
استخدام أنواع الإرجاع غير الفارغة (مثل @NonNull) للحاويات
بالنسبة إلى أنواع الحاويات، مثل Bundle
أو Collection
، يجب عرض حاوية فارغة
وغير قابلة للتغيير، حيثما ينطبق ذلك. في الحالات التي يتم فيها استخدام null
لتمييز مدى توفّر حاوية، ننصحك بتوفير طريقة برمجية ثنائية الترميز منفصلة.
@NonNull
public Bundle getExtras() { ... }
يجب أن تتطابق التعليقات التوضيحية لقابلية العدم لزوجَي get وset.
يجب أن تتطابق دائمًا بين علامتَي التعليق التوضيحي للقيمة الخالية من القيمة في مزدوجة الطريقتَين للحصول على خاصيّة منطقية واحدة وضبطها. سيؤدي عدم اتّباع هذه الإرشادات إلى عدم الالتزام ببنية سمات Kotlin، وبالتالي فإنّ إضافة تعليقات توضيحية غير متّفقة حول إمكانية عدم توفّر القيمة إلى طرق السمات الحالية هي تغيير يؤدي إلى إيقاف مصدر Kotlin.
@NonNull
public Bundle getExtras() { ... }
public void setExtras(@NonNull Bundle bundle) { ... }
عرض قيمة في حالات الخطأ أو العيوب
يجب أن تسمح جميع واجهات برمجة التطبيقات للتطبيقات بالاستجابة للأخطاء. إنّ عرض القيمة false
أو -1
أو null
أو أي قيم أخرى عامة للخطأ "حدث خطأ ما" لا يقدّم للمطوّر
معلومات كافية عن تعذُّر تلبية توقعات المستخدمين أو تتبُّع دقة
موثوقية تطبيقه في المجال. عند تصميم واجهة برمجة تطبيقات، تخيّل أنّك
تُنشئ تطبيقًا. إذا واجهت خطأ، هل تمنحك واجهة برمجة التطبيقات معلومات
كافية لتقديمها للمستخدم أو الردّ عليها بشكل مناسب؟
- من الجيد (وننصحك) بتضمين معلومات تفصيلية في رسائل الصعوبات، ولكن يجب ألا يضطر المطوّرون إلى تحليلها للتعامل مع الخطأ بشكلٍ مناسب. يجب عرض رموز الخطأ المفصّلة أو المعلومات الأخرى على أنّها methods.
- تأكَّد من أنّ خيار معالجة الأخطاء الذي اخترته يمنحك المرونة اللازمة لintroduce new error types in the future. بالنسبة إلى
@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();
}
استخدِم أنواع المجموعات بدلاً من المصفوفات كنوع عرض أو مَعلمة
توفّر واجهات المجموعات ذات النوع العام العديد من المزايا مقارنةً بالأعمدة، بما في ذلك عقود واجهات برمجة التطبيقات الأكثر صرامة حول التفرد والترتيب، ودعم الأنواع العامة، وعدد من طرق الراحة المناسبة للمطوّرين.
استثناء للعناصر الأساسية
إذا كانت العناصر عناصر أساسية، استخدِم المصفوفات بدلاً من ذلك لتجنُّب تكلفة التجميع التلقائي. راجِع مقالة استخدام العناصر الأساسية الأوّلية وإعادتها بدلاً من الإصدارات المعبأة.
استثناء للرمز البرمجي الحسّاس للأداء
في سيناريوهات معيّنة، حيث يتم استخدام واجهة برمجة التطبيقات في رمز حساس للأداء (مثل الرسومات أو واجهات برمجة تطبيقات أخرى لقياس/تنسيق/الرسم)، من المقبول استخدام المصفوفات بدلاً من المجموعات من أجل تقليل عمليات التخصيص وتغيير الذاكرة.
استثناء لـ Kotlin
تكون صفائف Kotlin ثابتة، وتوفر لغة Kotlin واجهات برمجة تطبيقات أدوات برمجية متوفّرة بشكلٍ واسع
حول الصفائف، لذا تكون الصفائف مساوية List
وCollection
لواجهات برمجة تطبيقات Kotlin
المخصّصة للوصول إليها من 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;
}
قابلية المجموعة للتبديل
يجب أن تفضّل واجهات برمجة تطبيقات Kotlin أنواع الإرجاع للقراءة فقط (وليس Mutable
) للمجموعات
بشكل تلقائي ما لم يتطلّب عقد واجهة برمجة التطبيقات على وجه التحديد نوع إرجاع
قابل للتغيير.
ومع ذلك، يجب أن تفضّل واجهات برمجة تطبيقات Java أنواع الإرجاع القابلة للتغيير تلقائيًا لأنّ
تنفيذ نظام Android الأساسي لواجهات برمجة تطبيقات Java لا يقدّم بعد
تنفيذًا ملائمًا للمجموعات الثابتة. ويُستثنى من هذه القاعدة
Collections.empty
أنواع الإرجاع التي لا يمكن تغييرها. في الحالات التي يمكن فيها للعملاء استغلال قابلية التطوّر
- عن قصد أو عن طريق الخطأ - لتعطيل نمط الاستخدام المقصود لواجهة برمجة التطبيقات، يجب أن تضع واجهات برمجة تطبيقات Java في الاعتبار بشدة عرض نسخة سطحية
من المجموعة.
@Nullable
public PermissionInfo[] getGrantedPermissions() {
return mPermissions;
}
@NonNull
public Set<PermissionInfo> getGrantedPermissions() {
if (mPermissions == null) {
return Collections.emptySet();
}
return new ArraySet<>(mPermissions);
}
أنواع النتائج القابلة للتغيير بشكل صريح
من المفترض ألا تعدّل واجهات برمجة التطبيقات التي تعرض مجموعات ملف شخصي العنصر الذي تم عرضه بعد عرضه. إذا كان يجب تغيير المجموعة المعروضة أو إعادة استخدامها بطريقة ما، على سبيل المثال، عرض معدَّل لبيانات مجموعة قابلة للتغيير، يجب توثيق السلوك الدقيق لحالات إمكانية تغيير المحتوى بشكل صريح أو اتّباع اصطلاحات تسمية واجهة برمجة التطبيقات المعمول بها.
/**
* Returns a view of this object as a list of [Item]s.
*/
fun MyObject.asList(): List<Item> = MyObjectListWrapper(this)
يتم وصف اصطلاح .asFoo()
في Kotlin
أدناه ويسمح بتغيير المجموعة التي يعرضها
.asList()
في حال تغيّرت المجموعة الأصلية.
إمكانية تغيير كائنات أنواع البيانات التي يتم عرضها
على غرار واجهات برمجة التطبيقات التي تعرض مجموعات، يجب ألا تعدّل واجهات برمجة التطبيقات التي تعرض عناصر من النوع "بيانات" خصائص العنصر المعروض بعد عرضه.
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)
}
في حالات محدودة للغاية، قد تستفيد بعض الرموز البرمجية الحسّاسة للأداء من تجميع أو إعادة استخدام العناصر. لا تكتب بنية بيانات تجمع الكائنات الخاصة بك ولا تعرض الكائنات المُعاد استخدامها في واجهات برمجة التطبيقات المتاحة للجميع. في كلتا الحالتَين، يجب أن تحرص بشدة على إدارة الوصول المتزامن.
استخدام نوع المَعلمة vararg
ننصح باستخدام vararg
في واجهات برمجة التطبيقات Kotlin وJava في الحالات التي يُرجّح فيها أن ينشئ المطوّر صفيفًا في موقع الاستدعاء بغرض واحد فقط وهو تمرير مَعلمات متعددة ذات صلة من النوع نفسه.
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 إلى رمز bytecode المستند إلى المصفوفة نفسه، وبالتالي يمكن استدعاؤها من رمز Java باستخدام صفيف قابل للتغيير. ننصح بشدّة مصمّمي واجهات برمجة التطبيقات بإنشاء نسخة احتياطية
سطحية من مَعلمة الصفيف في الحالات التي سيتم فيها الاحتفاظ بها في
حقل أو فئة داخلية مجهولة الهوية.
public void setValues(SomeObject... values) {
this.values = Arrays.copyOf(values, values.length);
}
يُرجى العلم أنّ إنشاء نسخة دفاعية لا يقدّم أي حماية ضد التعديل المتزامن بين طلب الطريقة الأولي وإنشاء النسخة، ولا يحمي من حدوث طفرة في العناصر المضمّنة في الصفيف.
تقديم دلالات صحيحة باستخدام مَعلمات نوع المجموعة أو الأنواع المعروضة
List<Foo>
هو الخيار التلقائي، ولكن ننصحك بالتفكير في أنواع أخرى لتوفير معنًى إضافي:
استخدِم
Set<Foo>
إذا كانت واجهة برمجة التطبيقات لا تهتم بترتيب العناصر ولا تسمح بالنسخ المكرّرة أو إذا كانت النُسخ المكرّرة لا معنى لها.Collection<Foo>,
إذا كانت واجهة برمجة التطبيقات لا تهتم بالترتيب وتسمح بالملفات المكرّرة.
دوال التحويل في Kotlin
تستخدم Kotlin غالبًا .toFoo()
و.asFoo()
للحصول على عنصر من نوع
مختلف عن عنصر حالي، حيث يكون Foo
هو اسم نوع القيمة المعروضة عند إجراء عملية التحويل. وهذا يتوافق مع JDK
Object.toString()
المعتاد. تأخذ Kotlin هذه الخطوة إلى أبعد حدّ من خلال استخدامها في
الإحالات الناجحة البدائية مثل 25.toFloat()
.
هناك فرق مهم بين الإحالات الناجحة المُسمّاة .toFoo()
و.asFoo()
:
استخدِم .toFoo() عند إنشاء عنصر جديد ومستقل.
مثل .toString()
، تُعرِض الإحالة الناجحة "إلى" عنصرًا جديدًا ومستقلّاً. إذا تم تعديل
العنصر الأصلي لاحقًا، لن يعكس العنصر الجديد تلك التغييرات.
وبالمثل، إذا تم تعديل العنصر الجديد لاحقًا، لن يعكس العنصر القديم
هذه التغييرات.
fun Foo.toBundle(): Bundle = Bundle().apply {
putInt(FOO_VALUE_KEY, value)
}
استخدِم .asFoo() عند إنشاء غلاف تابع أو عنصر مُزيَّن أو تحويل
يتم تنفيذ التحويل في Kotlin باستخدام الكلمة الرئيسية as
. ويشير ذلك إلى تغيير في
الواجهة وليس في الهوية. عند استخدامها كبادئة في دالة
إضافية، تُزيّن .asFoo()
المُستلِم. سيظهر أي تغيير في
عنصر المستلِم الأصلي في العنصر الذي تعرضه الدالة asFoo()
.
قد تؤثر طفرة في كائن Foo
الجديد في الكائن الأصلي.
fun <T> Flow<T>.asLiveData(): LiveData<T> = liveData {
collect {
emit(it)
}
}
يجب كتابة دوالّ الإحالات الناجحة كدوالّ إضافية.
إنّ كتابة دوالّ التحويل خارج كلّ من تعريفات klassen المستلِم والنتيجة تقلّل من الربط بين الأنواع. لا تحتاج الإحالة الناجحة المثالية سوى إذن الوصول إلى واجهة برمجة التطبيقات العامة للكائن الأصلي. يثبت ذلك من خلال مثال أنّه يمكن للمطوّر كتابة إحالات ناجحة مشابهة لأنواعه المفضّلة أيضًا.
طرح استثناءات محدّدة مناسبة
يجب ألّا تُعرِض الطرق استثناءات عامة مثل java.lang.Exception
أو
java.lang.Throwable
، بل يجب استخدام استثناء محدّد ومناسب
مثل java.lang.NullPointerException
للسماح للمطوّرين بمعالجة الاستثناءات
بدون أن تكون واسعة جدًا.
يجب أن تؤدي الأخطاء غير المرتبطة بالوسيطات المقدَّمة مباشرةً إلى الطريقة التي يتمّ استدعاؤها بشكل علني إلى عرض java.lang.IllegalStateException
بدلاً من
java.lang.IllegalArgumentException
أو java.lang.NullPointerException
.
المستمعون وعمليات الاستدعاء
في ما يلي القواعد المتعلقة بالفئات والطُرق المستخدَمة لآليتي المستمع والاستدعاء.
يجب أن تكون أسماء فئات طلبات إعادة الاتصال مفردة.
استخدِم MyObjectCallback
بدلاً من MyObjectCallbacks
.
يجب أن تكون أسماء طرق الاستدعاء على النحو التالي: on
يشير الرمز onFooEvent
إلى أنّه يتم تنفيذ FooEvent
وأنّه يجب أن يستجيب الإجراء المُعاد الاتصال به.
يجب أن يصف زمن الماضي مقارنةً بزمن الحاضر سلوك التوقيت.
يجب تسمية طرق الاستدعاء المتعلّقة بالأحداث للإشارة إلى ما إذا كان الحدث قد حدث من قبل أو ما إذا كان في مرحلة حدوثه.
على سبيل المثال، إذا تمّ استدعاء الطريقة بعد تنفيذ إجراء النقر:
public void onClicked()
ومع ذلك، إذا كانت الطريقة مسؤولة عن تنفيذ إجراء النقر:
public boolean onClick()
تسجيل طلب معاودة الاتصال
عندما يمكن إضافة مستمع أو وظيفة استدعاء إلى كائن أو إزالتهما منه، يجب تسمية ال methods المرتبطة بهما 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);
تجنَّب استخدام وظائف الحصول على وظائف الاستدعاء.
لا تُضِف طرقًا getFooCallback()
. يُعدّ هذا الإجراء مخرجًا مغريًا في الحالات التي قد يريد فيها المطوّرون ربط طلب استدعاء حالي مع البديل الذي يقدّمونه، ولكنّه إجراء غير متّسق ويصعّب على مطوّري المكوّنات فهم الحالة الحالية. على سبيل المثال:
- يتصل المطوِّر "أ" برقم
setFooCallback(a)
. - يتصل المطوّر "ب" بالمطوّر "أ".
setFooCallback(new B(getFooCallback()))
- يريد المطوّر "أ" إزالة دالة الاستدعاء
a
وليس لديه طريقة لإجراء ذلك بدون معرفة نوعB
، وبما أنّB
تم إنشاؤه للسماح بإجراء تعديلات مماثلة على دالة الاستدعاء المُغلفة.
قبول "المنفِّذ" للتحكّم في إرسال طلب معاودة الاتصال
عند تسجيل طلبات استدعاء لا تتضمّن توقّعات صريحة بشأن تسلسل المهام (أي في أي مكان خارج حزمة أدوات واجهة المستخدم)، ننصح بشدة بتضمين مَعلمة
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();
}
}
وهذا يعني أنّه عند تنفيذ واجهات برمجة التطبيقات التي تتّخذ هذا الشكل، يجب أن يستدعي تنفيذ Binder.clearCallingIdentity()
في مثيل Binder القادم من جهة عملية التطبيق قبل استدعاء دالة الاستدعاء العكسي للتطبيق في Executor
المقدَّمة من التطبيق. بهذه الطريقة، يُنسَب أي رمز برمجي للتطبيق يستخدم هوية الربط (مثل Binder.getCallingUid()
) للتحقّق من الأذونات بشكل صحيح إلى التطبيق الذي يُنفِّذ الرمز البرمجي وليس إلى عملية النظام التي تُطلِق التطبيق. إذا كان مستخدمو واجهة برمجة التطبيقات يريدون الحصول على معلومات UID أو PID للمُتصل، يجب أن يكون ذلك جزءًا واضحًا من واجهة برمجة التطبيقات، وليس ضمنيًا استنادًا إلى مكان تنفيذ Executor
الذي قدّموه.
يجب أن تكون واجهة برمجة التطبيقات متوافقة مع تحديد Executor
. في الحالات التي تُعدّ فيها سرعة الأداء مُهمّة، قد تحتاج التطبيقات إلى تنفيذ الرمز البرمجي فورًا أو
بشكل متزامن مع الملاحظات الواردة من واجهة برمجة التطبيقات. ويسمح قبول Executor
بذلك.
إنّ إنشاء HandlerThread
إضافي أو ما شابهه من
بشكل دفاعي يبطل حالة الاستخدام المرغوب فيها هذه.
إذا كان التطبيق سيشغّل رمزًا برمجيًا باهظ التكلفة في مكان ما من العملية الخاصة به، عليك السماح له بالاستمرار. إنّ الحلول البديلة التي سيعثر عليها مطوّرو التطبيقات للتغلب على قيودك ستكون أكثر صعوبة في توفير الدعم لها على المدى الطويل.
استثناء للطلب المتكرّر الوحيد: عندما تتطلّب طبيعة الأحداث التي يتم الإبلاغ عنها استخدام مثيل واحد فقط من الطلب المتكرّر، استخدِم النمط التالي:
public void setFooCallback(
@NonNull @CallbackExecutor Executor executor,
@NonNull FooCallback callback)
public void clearFooCallback()
استخدام Executor بدلاً من Handler
في السابق، كان يتم استخدام Handler
في نظام التشغيل Android كمعيار لإعادة توجيه تنفيذ ردّ الاتصال إلى سلسلت محادثات Looper
معيّنة. تم تغيير هذا المعيار لتفضيل استخدام Executor
لأنّ معظم مطوّري التطبيقات يديرون مجموعات مؤشرات الترابط الخاصة بهم، ما يجعل مؤشر الترابط الرئيسي
أو مؤشر ترابط واجهة المستخدم هو مؤشر الترابطLooper
الوحيد المتاح للتطبيق. استخدِم Executor
لمنح المطوّرين التحكّم الذي يحتاجون إليه لإعادة استخدام سياقات التنفيذ الحالية/المفضّلة.
توفّر مكتبات المعالجة المتزامنة الحديثة، مثل kotlinx.coroutines أو RxJava،
آليات جدولة خاصة بها تُجري عمليات الإرسال الخاصة بها عند الحاجة، ما يجعل
من المهم توفير إمكانية استخدام أداة تنفيذ مباشرة (مثل
Runnable::run
) لتجنّب وقت الاستجابة الناتج عن عمليات القفزة المزدوجة للخيوط. على سبيل المثال، قفزة واحدة
للنشر في سلسلة محادثات Looper
باستخدام Handler
متبوعة بقفزة أخرى من
إطار عمل التزامن في التطبيق.
إنّ الاستثناءات لهذه الإرشادات نادرة. تشمل طلبات إعادة النظر الشائعة للحصول على استثناء ما يلي:
عليّ استخدام Looper
لأنّني أحتاج إلى Looper
لepoll
في الحدث.
تمّ منح طلب الاستثناء هذا لأنّه لا يمكن استفادة العميل من Executor
في هذه الحالة.
لا أريد أن يحظر رمز التطبيق سلسلة المحادثات التي تنشر الحدث. لا يتم عادةً منح طلب المعالجة الخاصة بالاستثناء للرمز الذي يتم تشغيله في عملية التطبيق. إنّ التطبيقات التي تُجري هذا الإجراء بشكل خاطئ تضرّ نفسها فقط، ولا تؤثّر في صحة النظام العامة. إنّ التطبيقات التي تُجري ذلك بشكل صحيح أو تستخدم إطار عمل معالجة متزامنة شائعًا لن تواجه عقوبات إضافية بسبب وقت الاستجابة.
Handler
متّسقة محليًا مع واجهات برمجة التطبيقات المشابهة الأخرى في الفئة نفسها.
يتم منح طلب الاستثناء هذا حسب الحالة. يُفضّل
إضافة عمليات التحميل الزائد المستندة إلى 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) { ... }
}
عناصر ردّ الاتصال التي تتضمّن طرقًا متعددة
يجب أن تفضّل وظائف الاستدعاء المتعدّدة الطرق interface
واستخدام طرق default
عند إضافتها إلى الواجهات التي تم إصدارها سابقًا. في السابق، كانت هذه الإرشادات تُنصح باستخدام abstract class
بسبب عدم توفّر طرق default
في Java 7.
public interface MostlyOptionalCallback {
void onImportantAction();
default void onOptionalInformation() {
// Empty stub, this method is optional.
}
}
استخدام android.os.OutcomeReceiver عند وضع نماذج لاستدعاء دالة غير محظورة
OutcomeReceiver<R,E>
يعرض قيمة النتيجة R
عند نجاح العملية أو E : Throwable
في حال عدم نجاحها، وهي
الإجراءات نفسها التي يمكن أن تنفّذها طريقة طلب بسيطة. استخدِم OutcomeReceiver
كنوع callback
عند تحويل طريقة حظر تعرض نتيجة أو تُعرِض استثناءً إلى طريقة غير حظرية ومستخدِمة للطلبات غير المتزامنة:
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
لإعداد تقارير عن نتائج الطرق غير المتزامنة، يمكنك أيضًا الحصول على ملف Kotlin
suspend fun
لتغليف الطرق غير المتزامنة باستخدام الإضافة
Continuation.asOutcomeReceiver
من androidx.core:core-ktx
:
suspend fun FooType.requestFoo(request: FooRequest): FooResult =
suspendCancellableCoroutine { continuation ->
requestFooAsync(request, Runnable::run, continuation.asOutcomeReceiver())
}
تتيح الإضافات مثل هذه لعملاء Kotlin استدعاء طُرق غير مُعلّقة ومتسلسلة
بسهولة استدعاء دالة عادية بدون حظر سلسلت
التنفيذ. قد يتم تقديم هذه الإضافات الخاصة لواجهات برمجة التطبيقات الخاصة بالنظام الأساسي كجزء من ملف androidx.core:core-ktx
في Jetpack عند دمجها مع عمليات التحقّق من التوافق مع الإصدارات العادية وغيرها من الاعتبارات. اطّلِع على مستندات asOutcomeReceiver للحصول على مزيد من المعلومات والاعتبارات المتعلّقة بالإلغاء والعيّنات.
يجب عدم استخدام OutcomeReceiver
كنوع من أنواع الاستدعاءات المرجعية في الطرق غير المتزامنة التي لا تتطابق مع دلالة طريقة تُعرِض نتيجة أو تُثير استثناءً عند اكتمال عملها. بدلاً من ذلك، ننصحك بتجربة أحد الخيارات الأخرى
المُدرَجة في القسم التالي.
تفضيل الواجهات الوظيفية على إنشاء أنواع جديدة من الطرق المجردة الفردية (SAM)
أضاف المستوى 24 من واجهة برمجة التطبيقات أنواع java.util.function.*
(مستندات مرجعية)
، التي توفّر واجهات SAM عامة مثل Consumer<T>
التي
تكون مناسبة للاستخدام كوظائف lambda لطلبات إعادة الاتصال. في كثير من الحالات، لا يقدّم إنشاء واجهات 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)
مستندات Google
هذه هي القواعد المتعلقة بالمستندات المتاحة للجميع (Javadoc) لواجهات برمجة التطبيقات.
يجب توثيق جميع واجهات برمجة التطبيقات المتاحة للجميع.
يجب أن تتضمّن جميع واجهات برمجة التطبيقات المتاحة للجميع مستندات كافية لشرح كيفية استخدام المطوّر لواجهة برمجة التطبيقات. لنفترض أنّ المطوّر عثر على الطريقة باستخدام ميزة "الإكمال التلقائي" أو أثناء تصفّح مستندات مرجع واجهة برمجة التطبيقات وكان لديه الحد الأدنى من السياق من واجهة برمجة التطبيقات المجاورة (على سبيل المثال، الفئة نفسها).
الطرق
يجب توثيق مَعلمات الطريقة والقيم المعروضة باستخدام التعليقات التوضيحية @param
و
@return
في المستندات، على التوالي. تنسيق نص Javadoc كما لو كان precededب "هذه الطريقة..."
في الحالات التي لا تأخذ فيها الطريقة أيّ مَعلمات، ولا تتضمّن أيّ اعتبارات خاصة، و
تُعرِض ما يُشير إليه اسم الطريقة، يمكنك حذف @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 عند إضافة Javadoc
هذه القاعدة مهمة بشكل خاص عند إضافة علامتَي @link
أو @see
، ويجب
التأكّد من أنّ النتيجة تظهر على النحو المتوقّع. غالبًا ما يكون سبب ظهور ERROR في Javadoc هو روابط
مُعطلة. يُجري هدف الإنشاء update-api
أو docs
هذا التحقّق، ولكن
قد يكون هدف docs
أسرع إذا كنت تغيّر Javadoc فقط ولا تحتاج
بخلاف ذلك إلى تشغيل هدف update-api
.
استخدام {@code foo} للتمييز بين قيم Java
يجب إحاطة قيم Java مثل true
وfalse
وnull
برمز {@code...}
لتمييزها عن نص المستندات.
عند كتابة مستندات في مصادر Kotlin، يمكنك إحاطة الرمز البرمجي بعلامات اقتباس عكسية كما تفعل مع Markdown.
يجب أن تكون ملخّصات @param و @return عبارة عن جزء من جملة واحدة.
يجب أن تبدأ ملخّصات المَعلمات والقيمة المعروضة بحرف صغير، ويجب أن تحتوي على مقتطف جملة واحد فقط. إذا كانت لديك معلومات إضافية تتعدى الجملة الواحدة، انقلها إلى نص 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
*/
تحتاج التعليقات التوضيحية في "مستندات Google" إلى تفسيرات
سجِّل سبب إخفاء التعليقَين التوضيحيَين @hide
و@removed
من واجهة برمجة التطبيقات المتاحة للجميع.
أدرِج تعليمات حول كيفية استبدال عناصر واجهة برمجة التطبيقات التي تم وضع التعليق التوضيحي
@deprecated
عليها.
استخدام @throws لتوثيق الاستثناءات
إذا كانت إحدى الطرق تُعرِض استثناءً قابلاً للتحقّق، مثل IOException
، سجِّل
الاستثناء باستخدام @throws
. بالنسبة إلى واجهات برمجة التطبيقات المستندة إلى Kotlin والمخصّصة للاستخدام من قِبل
عملاء Java، أضِف تعليقات توضيحية للدوالّ باستخدام
@Throws
.
إذا كانت إحدى الطرق تُلقي استثناءً غير مدقَّق يشير إلى خطأ يمكن تجنُّبه، على سبيل المثال IllegalArgumentException
أو IllegalStateException
، عليك توثيق
الاستثناء مع شرح سبب ظهوره. يجب أن يشير الثناء
الاستثناء أيضًا إلى سبب ظهوره.
تُعتبر حالات معيّنة من الاستثناءات غير الخاضعة للتحقّق ضمنية ولا يلزم
توثيقها، مثل NullPointerException
أو IllegalArgumentException
حيث لا تتطابق الوسيطة مع @IntDef
أو تعليق توضيحي مشابه يضمِّن
عقد واجهة برمجة التطبيقات في توقيع الطريقة:
/**
* ...
* @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 المستندات بطريقة بسيطة، وتنتهي من تحليل مستند الملخّص (أول جملة يتم استخدامها في الوصف السريع في أعلى مستندات الصف) فور ظهور نقطة (.) متبوعة بمسافة. ويؤدي ذلك إلى حدوث مشكلتَين:
- إذا لم ينتهِ مستند قصير بنقطة، وإذا كان هذا العضو قد اكتسب
مستندات تلتقطها الأداة، سيلتقط الملخّص أيضًا
هذه المستندات المكتسَبة. على سبيل المثال، اطّلِع على
actionBarTabStyle
في مستنداتR.attr
، التي تتضمّن وصف السمة المُضافة إلى الملخّص. - تجنَّب استخدام "مثل" في الجملة الأولى للسبب نفسه، لأنّ Doclava تنتهي
بمستندات الملخّص بعد "g". على سبيل المثال، يمكنك الاطّلاع على
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>
، لذا عليك الحذر عند استخدام brackets<>
. ويمكنك تجنُّبها باستخدام عنصرَي HTML<
و>
.بدلاً من ذلك، يمكنك ترك الأقواس الأولية
<>
في مقتطف الرمز إذا لفّت الأقسام التي تتضمّن أخطاء في{@code foo}
. مثلاً:<pre>{@code <manifest>}</pre>
اتّباع دليل تنسيق المرجع لواجهة برمجة التطبيقات
لضمان اتساق أسلوب ملخّصات الفئات وأوصاف الطرق وأوصاف المَعلمات والعناصر الأخرى، اتّبِع الاقتراحات الواردة في الإرشادات الرسمية للغة Java على الرابط كيفية كتابة تعليقات Doc لأداة Javadoc.
القواعد المتعلّقة بإطار عمل Android
تتناول هذه القواعد واجهات برمجة التطبيقات والأنماط وبنى البيانات الخاصة
بواجهات برمجة التطبيقات والسلوكيات المضمّنة في إطار عمل Android (مثل Bundle
أو
Parcelable
).
على منشئي النِيّات استخدام نمط create*Intent().
على صنّاع النية استخدام طرق باسم createFooIntent()
.
استخدام حِزمة بدلاً من إنشاء هياكل بيانات جديدة للأغراض العامة
تجنَّب إنشاء هياكل بيانات جديدة للأغراض العامة لتمثيل عمليات الربط العشوائية بين المفاتيح والقيم المكتوبة. بدلاً من ذلك، يمكنك استخدام Bundle
.
ويحدث ذلك عادةً عند كتابة واجهات برمجة تطبيقات النظام الأساسي التي تعمل كقنوات تواصل بين التطبيقات والخدمات غير المتوافقة مع النظام الأساسي، حيث لا يقرأ النظام الأساسي البيانات المُرسَلة عبر القناة، وقد يتم تحديد عقد واجهة برمجة التطبيقات جزئيًا خارج النظام الأساسي (على سبيل المثال، في مكتبة Jetpack).
في الحالات التي تقرأ فيها المنصة البيانات، تجنَّب استخدام Bundle
و
وافضِل استخدام فئة بيانات ذات بنية قوية.
يجب أن تحتوي عمليات تنفيذ Parcelable على حقل CREATOR علني
يتم عرض تضخيم Parcelable من خلال CREATOR
، وليس من خلال طرق وضع التصميم الأوّلية. إذا كانت
الفئة تنفِّذ Parcelable
، يجب أن يكون الحقل CREATOR
أيضًا واجهة برمجة تطبيقات
عامة، ويجب أن يكون مُنشئ الفئة الذي يأخذ وسيطة Parcel
خاصًا.
استخدام CharSequence لسلاسل واجهة المستخدم
عند عرض سلسلة في واجهة مستخدم، استخدِم CharSequence
للسماح بمثيلات
Spannable
.
إذا كان الأمر يتعلق بمفتاح أو تصنيف أو قيمة أخرى غير مرئية للمستخدمين،
String
لا بأس بذلك.
تجنَّب استخدام Enums
يجب استخدام IntDef
بدلاً من القوائم المحددة في جميع واجهات برمجة التطبيقات الخاصة بالنظام الأساسي، ويجب أن يُنظر بشدة في استخدامها
في واجهات برمجة التطبيقات غير المجمّعة للمكتبات. لا تستخدِم القوائم المحدَّدة إلا عندما تكون متأكّدًا من عدم
إضافة قيم جديدة.
مزاياIntDef
:
- يتيح إضافة قيم بمرور الوقت
- يمكن أن تؤدي عبارات
when
في Kotlin إلى تعذُّر التنفيذ إذا ceased من أن تكون شاملة بسبب قيمة مصنّف مضافة في المنصة.
- يمكن أن تؤدي عبارات
- لا يتم استخدام أي فئات أو عناصر أثناء التشغيل، بل يتم استخدام العناصر الأساسية فقط.
- على الرغم من أنّ R8 أو التصغير يمكن أن يتجنّب هذه التكلفة لواجهات برمجة التطبيقات غير المُجمَّعة للمكتبة، لا يمكن أن يؤثر هذا التحسين في فئات واجهات برمجة التطبيقات الخاصة بالمنصة.
مزايا Enum
- ميزات اللغة الاصطلاحية في Java وKotlin
- تفعيل استخدام عبارة التبديل الشاملة
when
- ملاحظة: يجب ألّا تتغيّر القيم بمرور الوقت، راجِع القائمة السابقة.
- التسمية الواضحة النطاق والقابلة للاكتشاف
- تفعيل عملية التحقّق في وقت الترجمة
- على سبيل المثال، عبارة
when
في Kotlin تُعرِض قيمة
- على سبيل المثال، عبارة
- هي فئة عاملة يمكنها تنفيذ الواجهات، وتحتوي على أدوات مساعدة ثابتة، وتعرض طرق الأعضاء أو الإضافات، وتعرض الحقول.
اتّباع التسلسل الهرمي لطبقات حِزم Android
يتضمّن التسلسل الهرمي لحِزم android.*
ترتيبًا ضمنيًا، حيث لا يمكن أن تعتمد الحِزم ذات المستوى الأدنى على الحِزم ذات المستوى الأعلى.
تجنَّب الإشارة إلى Google والشركات الأخرى ومنتجاتها.
منصة Android هي مشروع مفتوح المصدر ويهدف إلى أن يكون محايدًا من حيث المورّدين. يجب أن تكون واجهة برمجة التطبيقات عامة ويمكن لمُدمِجي الأنظمة أو التطبيقات استخدامها بالطريقة نفسها إذا كانت توفّر الأذونات المطلوبة.
يجب أن تكون عمليات تنفيذ Parcelable نهائية.
يتم دائمًا تحميل فئات Parcelable التي يحدّدها النظام الأساسي من
framework.jar
، لذا لا يجوز للتطبيق محاولة إلغاء تنفيذ Parcelable
.
إذا كان التطبيق المُرسِل يمدّد Parcelable
، لن يتوفّر للتطبيق المستلِم
التنفيذ المخصّص للمُرسِل لفك الحزمة. ملاحظة بشأن التوافق مع الإصدارات القديمة: إذا لم تكن صفتك نهائية في السابق، ولكن لم يكن لديها final
مُنشئ متاح للجميع، سيظل بإمكانك وضع علامة عليها.
يجب أن تعيد الطرق التي تستدعي عملية النظام رمي RemoteException كـ RuntimeException.
يتم عادةً طرح RemoteException
من خلال واجهة برمجة التطبيقات الداخلية لنظام Android، ويشير ذلك إلى أنّه تم إنهاء عملية النظام أو أنّ التطبيق يحاول إرسال بيانات كثيرة جدًا. في كلا القصتين، يجب إعادة طرح واجهة برمجة التطبيقات العامة كخطأ RuntimeException
لمنع التطبيقات من
الاستمرار في اتخاذ قرارات الأمان أو السياسة.
إذا كنت تعرف أنّ الجهة الأخرى من طلب Binder
هي عملية النظام، فإنّ
الرمز البرمجي الجاهز هذا هو أفضل الممارسات:
try {
...
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
طرح استثناءات محدّدة لتغييرات واجهة برمجة التطبيقات
قد تتغيّر سلوكيات واجهات برمجة التطبيقات المتاحة للجميع على مستوى واجهات برمجة التطبيقات وقد تؤدي إلى تعطُّل التطبيقات (مثلاً لفرض سياسات أمان جديدة).
عندما تحتاج واجهة برمجة التطبيقات إلى طرح استثناء لطلب كان صالحًا في السابق، يجب طرح استثناء جديد
محدد بدلاً من استثناء عام. على سبيل المثال، ExportedFlagRequired
بدلاً من SecurityException
(وExportedFlagRequired
يمكن أن يمتد
SecurityException
).
سيساعد ذلك مطوّري التطبيقات والأدوات في رصد التغييرات في سلوك واجهات برمجة التطبيقات.
تنفيذ مُنشئ النسخ بدلاً من الاستنساخ
لا يُنصح بشدة باستخدام طريقة Java clone()
بسبب عدم توفّر عقود واجهة برمجة التطبيقات
المقدَّمة من فئة Object
والصعوبات المتأصلة في توسيع
الفئات التي تستخدم clone()
. بدلاً من ذلك، استخدِم أسلوب إنشاء نسخة يأخذ عنصرًا
من النوع نفسه.
/**
* Constructs a shallow copy of {@code other}.
*/
public Foo(Foo other)
بالنسبة إلى الفئات التي تعتمد على أداة إنشاء لدوالّ الإنشاء، يجب أن تفكر في إضافة دالة إنشاء ملف تعريف شخصي نسخة من أداة الإنشاء للسماح بإجراء تعديلات على النسخة.
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
مباشرةً، لأنّها غالبًا ما تحدّ من مدى
إمكانية تطوير واجهة برمجة التطبيقات في المستقبل.
تجنُّب استخدام BitSet
java.util.BitSet
رائعة للتنفيذ، ولكن ليس لواجهة برمجة التطبيقات المتاحة للجميع. وهي
قابلة للتغيير، وتتطلّب تخصيصًا لطلبات استدعاء الطريقة ذات معدّل التكرار العالي، ولا تُقدّم
معنى دلاليًا لما يمثّله كلّ بت.
في سيناريوهات الأداء العالي، استخدِم int
أو long
مع @IntDef
. بالنسبة إلى
السيناريوهات ذات الأداء المنخفض، ننصحك باستخدام Set<EnumType>
. بالنسبة إلى البيانات الثنائية الأولية، استخدِم
byte[]
.
يُفضَّل استخدام android.net.Uri
android.net.Uri
هي طريقة الحِزم المفضّلة لمعرّفات الموارد المنتظمة في واجهات برمجة تطبيقات Android.
تجنَّب استخدام java.net.URI
لأنّه صارم جدًا في تحليل عناوين URL، ولا تستخدِم أبدًا
java.net.URL
لأنّ تعريفه للمساواة معطّل بشكل كبير.
إخفاء التعليقات التوضيحية التي تم وضع علامة عليها باسم @IntDef أو @LongDef أو @StringDef
تشير التعليقات التوضيحية التي تم وضع علامة @IntDef
أو @LongDef
أو @StringDef
عليها إلى مجموعة من
الثوابت الصالحة التي يمكن تمريرها إلى واجهة برمجة التطبيقات. ومع ذلك، عند تصديرها كواجهات برمجة تطبيقات، يدمج المُجمِّع الثوابت ولا تبقى سوى القيم (غير المفيدة الآن) في الرمز المرجعي لواجهة برمجة التطبيقات (للمنصّة) أو JAR (للمكتبات).
ولذلك، يجب وضع علامة على استخدامات هذه التعليقات التوضيحية باستخدام التعليق التوضيحي @hide
docs
في المنصة أو التعليق التوضيحي @RestrictTo.Scope.LIBRARY)
code في مكتبات. ويجب وضع علامة @Retention(RetentionPolicy.SOURCE)
عليها في كلا
الحالتَين لمنع ظهورها في نماذج واجهة برمجة التطبيقات أو ملفات JAR.
@RestrictTo(RestrictTo.Scope.LIBRARY)
@Retention(RetentionPolicy.SOURCE)
@IntDef({
STREAM_TYPE_FULL_IMAGE_DATA,
STREAM_TYPE_EXIF_DATA_ONLY,
})
public @interface ExifStreamType {}
عند إنشاء حِزم AAR لحِزم SDK للمنصة والمكتبات، تُستخرج أداة التعليقات التوضيحية وتجمّعها بشكل منفصل عن المصادر المجمّعة. يقرأ "استوديو Android" هذا التنسيق المجمّع ويفرض تعريفات الأنواع.
عدم إضافة مفاتيح جديدة لموفّري الإعدادات
لا تُظهِر مفاتيح جديدة من
Settings.Global
،
Settings.System
،
أو
Settings.Secure
.
بدلاً من ذلك، أضِف واجهة برمجة تطبيقات Java مناسبة للحصول على البيانات وضبطها في فئة ذات صلة، وهي عادةً ما تكون فئة "مدير". أضِف آلية استماع أو بث لإعلام العميل بالتغييرات حسب الحاجة.
تحتوي إعدادات SettingsProvider
على عدد من المشاكل مقارنةً ب
الحصول على القيم أو ضبطها:
- لا تتوفّر ميزة "منع أخطاء الكتابة".
- لا تتوفّر طريقة موحّدة لتقديم قيمة تلقائية.
- لا تتوفّر طريقة مناسبة لتخصيص الأذونات.
- على سبيل المثال، لا يمكن حماية الإعداد باستخدام إذن مخصّص.
- لا تتوفّر طريقة مناسبة لإضافة منطق مخصّص بشكلٍ صحيح.
- على سبيل المثال، لا يمكن تغيير قيمة الإعداد (أ) استنادًا إلى قيمة الإعداد (ب).
مثال: كان التنسيق
Settings.Secure.LOCATION_MODE
متاحًا لفترة طويلة، ولكن أوقف فريق الموقع الجغرافي استخدامه نهائيًا واستبدله بواجهة برمجة تطبيقات Java
LocationManager.isLocationEnabled()
وبث
MODE_CHANGED_ACTION
، ما منح الفريق مرونة أكبر بكثير، وأصبحت دلالات
واجهات برمجة التطبيقات أكثر وضوحًا الآن.
لا توسِّع Activity وAsyncTask.
AsyncTask
هي تفاصيل التنفيذ. بدلاً من ذلك، يمكنك عرض مستمع أو واجهة برمجة تطبيقات ListenableFuture
في
androidx.
لا يمكن إنشاء Activity
فئة فرعية. يؤدي تمديد النشاط في
الميزة إلى جعلها غير متوافقة مع الميزات الأخرى التي تتطلّب من المستخدمين تنفيذ
الشيء نفسه. بدلاً من ذلك، استخدِم أدوات مثل
LifecycleObserver.
استخدام getUser() في Context
يجب أن تستخدم الفصول المرتبطة بـ 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 بدلاً من الأعداد الصحيحة العادية.
يُفضَّل استخدام UserHandle
لتوفير أمان النوع وتجنُّب الخلط بين أرقام تعريف المستخدمين
وأرقام تعريف المستخدمين.
Foobar getFoobarForUser(UserHandle user);
Foobar getFoobarForUser(int userId);
يجب إضافة التعليق التوضيحي
@UserIdInt
إلى العنصر int
الذي يمثّل معرّف مستخدم، وذلك عندما يكون ذلك لا مفر منه.
Foobar getFoobarForUser(@UserIdInt int user);
استخدام أدوات الاستماع أو عمليات الاستدعاء بدلاً من نوايا البث
إنّ نوايا البث فعّالة جدًا، ولكنّها أدّت إلى ظهور سلوكيات جديدة يمكن أن تؤثّر سلبًا في صحة النظام، لذا يجب إضافة نوايا البث الجديدة بطريقة مدروسة.
في ما يلي بعض المخاوف المحدّدة التي تدفعنا إلى عدم تقديم اقتراحات بشأن استخدام نيات البث الجديدة:
عند إرسال عمليات البث بدون العلامة
FLAG_RECEIVER_REGISTERED_ONLY
، يتم فرض بدء أي تطبيقات غير قيد التشغيل. وفي حين أنّ ذلك قد يكون في بعض الأحيان نتيجة مقصودة، إلا أنّه يمكن أن يؤدي إلى بدء عشرات التطبيقات في العمل معًا، ما يؤثر سلبًا في حالة النظام. ننصحك باستخدام استراتيجية بديلة، مثلJobScheduler
، للتنسيق بشكل أفضل عند استيفاء مختلف الشروط المسبقة.عند إرسال أحداث البث، لا تتوفّر إمكانية كبيرة لفلترة المحتوى المرسَل إلى التطبيقات أو تعديله. ويجعل ذلك من الصعب أو المستحيل الردّ على المخاوف المستقبلية المتعلّقة بالخصوصية، أو إدخال تغييرات على السلوك استنادًا إلى حزمة SDK المستهدَفة للتطبيق المستلِم.
بما أنّ قوائم البث هي مورد مشترَك، يمكن أن تصبح مثقلة بالطلبات، وبالتالي قد لا تؤدي إلى تسليم الحدث في الوقت المناسب. لاحظنا عدة طوابير بث في الوقت الفعلي تبلغ مدة استجابةها من البداية إلى النهاية 10 دقائق أو أكثر.
لهذه الأسباب، ننصحك باستخدام مستمعين أو callbacks أو مرافق أخرى مثل JobScheduler
بدلاً من استخدام بث
intents في الميزات الجديدة.
في الحالات التي لا تزال فيها نوايا البث هي التصميم المثالي، إليك بعض أفضل الممارسات التي يجب مراعاتها:
- استخدِم
Intent.FLAG_RECEIVER_REGISTERED_ONLY
، إن أمكن، لتقتصر البث على التطبيقات التي تعمل حاليًا. على سبيل المثال، يستخدمACTION_SCREEN_ON
هذا التصميم لتجنُّب تنشيط التطبيقات. - استخدِم
Intent.setPackage()
أوIntent.setComponent()
، إن أمكن، لاستهداف البث على تطبيق معيّن يهمّك. على سبيل المثال، يستخدمACTION_MEDIA_BUTTON
هذا التصميم للتركيز على التطبيق الحالي الذي يتعامل مع عناصر التحكّم في التشغيل. - حدِّد البث على أنّه
<protected-broadcast>
إذا أمكن، لمنع التطبيقات الضارّة من انتحال هوية نظام التشغيل.
النوايا في خدمات المطوّرين المرتبطة بالنظام
إنّ الخدمات التي ينوي المطوّر توسيع نطاقها والتي تخضع لل
النظام، مثل الخدمات المجردة مثل NotificationListenerService
، قد
تستجيب لفعل Intent
من النظام. يجب أن تستوفي هذه الخدمات المعايير التالية:
- حدِّد ثابت سلسلة
SERVICE_INTERFACE
في الفئة التي تحتوي على اسم الفئة المؤهَّل بالكامل للخدمة. يجب وضع تعليق توضيحي على هذا الثابت باستخدام@SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
. - مستند يوضّح أنّه على المطوّر إضافة
<intent-filter>
إلىAndroidManifest.xml
ليتمكّن من تلقّي "الإشارات" من المنصة - ننصحك بشدة بإضافة إذن على مستوى النظام لمنع التطبيقات الخادعة
من إرسال
Intent
إلى خدمات المطوّرين.
إمكانية التشغيل التفاعلي بين Kotlin وJava
اطّلِع على دليل التشغيل التفاعلي بين Kotlin وJava الرسمي من Android للحصول على قائمة كاملة بالإرشادات. تمّت إزالة إرشادات محدّدة ونسخها إلى هذا الدليل لمحاولة تحسين قابلية العثور على المحتوى.
إذن الوصول إلى واجهة برمجة التطبيقات
إنّ بعض واجهات برمجة تطبيقات Kotlin، مثل suspend fun
، غير مخصّصة لاستخدامها من قِبل مطوّري Java، ومع ذلك، لا تحاول التحكّم في مستوى الوصول الخاص باللغة
باستخدام @JvmSynthetic
لأنّه يترك آثارًا جانبية على كيفية عرض واجهة برمجة التطبيقات في
محللّات الأخطاء، ما يجعل تصحيح الأخطاء أكثر صعوبة.
اطّلِع على دليل التشغيل التفاعلي بين Kotlin وJava أو دليل Async للحصول على إرشادات محددة.
الكائنات المصاحبة
تستخدم Kotlin companion object
لعرض الأعضاء الثابتة. في بعض الحالات، ستظهر هذه
العناصر من Java في فئة داخلية باسم Companion
بدلاً من الفئة التي تحتوي على
العناصر. Companion
قد تظهر فئات Companion
كصفوف فارغة في ملفات نص واجهة برمجة التطبيقات، وهذا يعمل على النحو المطلوب.
لزيادة التوافق مع Java إلى أقصى حد، أضِف تعليقًا توضيحيًا على
الحقول غير الثابتة
للكائنات المصاحبة باستخدام @JvmField
و
الدوال العامة
باستخدام @JvmStatic
لعرضها مباشرةً في الفئة التي تحتوي عليها.
companion object {
@JvmField val BIG_INTEGER_ONE = BigInteger.ONE
@JvmStatic fun fromPointF(pointf: PointF) {
/* ... */
}
}
تطوُّر واجهات برمجة التطبيقات لمنصة Android
يوضّح هذا القسم السياسات المتعلقة بأنواع التغييرات التي يمكنك إجراؤها على واجهات برمجة تطبيقات Android الحالية وكيفية تنفيذ هذه التغييرات لزيادة التوافق إلى أقصى حدّ مع التطبيقات وقواعد البيانات الحالية.
التغييرات التي تؤدي إلى إيقاف الإصدار الثنائي
تجنَّب التغييرات التي تؤدي إلى إيقاف الإصدار الثنائي في مساحات عرض واجهات برمجة التطبيقات العلنية التي تم الانتهاء منها. تؤدي هذه الأنواع من
التغييرات عادةً إلى ظهور أخطاء عند تشغيل make update-api
، ولكن قد
تكون هناك حالات استثنائية لا ترصدها عملية التحقّق من واجهة برمجة التطبيقات في Metalava. إذا لم تكن متأكّدًا من أيّ تغييرات، يمكنك الرجوع إلى دليل
تطوير واجهات برمجة التطبيقات المستندة إلى Java
الذي يوفّره Eclipse Foundation للحصول على شرح مفصّل لأنواع تغييرات واجهات برمجة التطبيقات المتوافقة مع IDE IDE
Java. يجب أن تتّبع التغييرات التي تؤدي إلى إيقاف استخدام الثنائيات في واجهات برمجة التطبيقات المخفية (مثل واجهات برمجة تطبيقات النظام)
دورة إيقاف نهائي/استبدال.
التغييرات التي تؤدي إلى تغيير المصدر
لا ننصح بإجراء تغييرات تؤدي إلى إيقاف المصدر حتى إذا لم تكن تؤدي إلى إيقاف الثنائي. أحد مثالي
التغييرات المتوافقة مع الثنائي ولكنّها تؤدي إلى إيقاف المصدر هو إضافة عنصر عام إلى
فئة حالية، وهو
متوافق مع الثنائي
ولكن يمكن أن يؤدي إلى ظهور أخطاء في عملية الترجمة بسبب اكتساب القيمة أو الإحالات الغامضة.
لن تؤدي التغييرات التي تؤدي إلى إيقاف المصدر إلى ظهور أخطاء عند تشغيل make update-api
، لذا
عليك الانتباه إلى تأثير التغييرات على توقيعات واجهة برمجة التطبيقات
الحالية.
في بعض الحالات، يصبح من الضروري إجراء تغييرات تؤدي إلى إيقاف المصدر لتحسين تجربت المطوّر أو صحة الرمز البرمجي. على سبيل المثال، تؤدي إضافة التعليقات التوضيحية حول قابلية القيمة للعدم إلى مصادر Java إلى تحسين إمكانية التشغيل التفاعلي مع رمز Kotlin وتقليل احتمالية حدوث أخطاء، ولكنّها غالبًا ما تتطلّب إجراء تغييرات على رمز المصدر، وقد تكون أحيانًا تغييرات كبيرة.
تغييرات على واجهات برمجة التطبيقات الخاصة
يمكنك تغيير واجهات برمجة التطبيقات التي تمت إضافة تعليقات توضيحية لها باستخدام @TestApi
في أي وقت.
يجب الاحتفاظ بواجهات برمجة التطبيقات التي تمّت إضافة تعليقات توضيحية لها باستخدام @SystemApi
لمدة ثلاث سنوات. يجب
إزالة واجهة برمجة تطبيقات النظام أو إعادة هيكلتها وفقًا للجدول الزمني التالي:
- واجهة برمجة التطبيقات y - تمت الإضافة
- واجهة برمجة التطبيقات السنة +1 - إيقاف نهائي
- ضَع علامة
@Deprecated
على الرمز. - أضِف بدائل واربطها بالبديل في Javadoc للرمز المُعتمَد نهائيًا باستخدام التعليق التوضيحي
@deprecated
في المستندات. - خلال دورة التطوير، أبلِغ المستخدمين الداخليين عن الأخطاء وأخبرهم بأنّه سيتم إيقاف واجهة برمجة التطبيقات نهائيًا. يساعد ذلك في التحقّق من أنّ واجهات برمجة التطبيقات البديلة مناسبة.
- ضَع علامة
- العام +2 لواجهة برمجة التطبيقات: الإزالة المؤقتة
- ضَع علامة
@removed
على الرمز. - اختياريًا، يمكنك طرح استثناء أو عدم تنفيذ أي إجراء للتطبيقات التي تستهدف مستوى حزمة SDK الحالي للإصدار.
- ضَع علامة
- واجهة برمجة التطبيقات بعد 3 سنوات من تاريخ الإصدار - الإزالة النهائية
- أزِل الرمز البرمجي بالكامل من شجرة المصدر.
الإيقاف النهائي
نعتبر إيقاف الميزة نهائيًا تغييرًا في واجهة برمجة التطبيقات، ويمكن أن يحدث في إصدار رئيسي (مثل
letter). استخدِم التعليق التوضيحي @Deprecated
للمصدر والتعليق التوضيحي @deprecated
<summary>
للمستندات معًا عند إيقاف واجهات برمجة التطبيقات نهائيًا. يجب أن يتضمّن الملخّص استراتيجية نقل البيانات. قد تؤدي هذه الاستراتيجية إلى ربط واجهة برمجة تطبيقات بديلة أو
شرح سبب عدم استخدام واجهة برمجة التطبيقات:
/**
* 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)
يجب أيضًا إيقاف واجهات برمجة التطبيقات التي تم تعريفها في XML وعرضها في Java، بما في ذلك
السمات والخصائص التي يمكن تطبيق أنماط عليها والتي تم عرضها في فئة android.R
، مع
ملخّص:
<!-- Attribute whether the accessibility service ...
{@deprecated Not used by the framework}
-->
<attr name="canRequestEnhancedWebAccessibility" format="boolean" />
حالات إيقاف واجهة برمجة التطبيقات نهائيًا
تكون عمليات الإيقاف النهائي مفيدة بشكلٍ كبير لمنع استخدام واجهة برمجة تطبيقات في الرمز البرمجي الجديد.
نطلب منك أيضًا وضع علامة @deprecated
على واجهات برمجة التطبيقات قبل أن يتم
@removed
تصنيفها، ولكنّ هذا الإجراء لا يشكّل حافزًا قويًا لشدّد
المطوّرين على نقل بياناتهم من واجهة برمجة التطبيقات التي يستخدمونها حاليًا.
قبل إيقاف واجهة برمجة التطبيقات نهائيًا، ننصحك بالتفكير في تأثير ذلك على المطوّرين. تشمل تأثيرات إيقاف واجهة برمجة التطبيقات نهائيًا ما يلي:
- يُصدر
javac
تحذيرًا أثناء التجميع.- لا يمكن إيقاف تحذيرات الإيقاف النهائي بشكل عام أو ضبطها على القيمة الأساسية، لذا
على المطوّرين الذين يستخدمون
-Werror
إصلاح كل استخدام لواجهة برمجة تطبيقات متوقّفة نهائيًا أو إيقافه بشكل فردي قبل أن يتمكّنوا من تعديل إصدار حزمة تطوير البرامج (SDK) المُجمَّعة. - لا يمكن كتم تحذيرات الإيقاف النهائي عند استيراد الفئات التي سيتم إيقافها نهائيًا. نتيجةً لذلك، على المطوّرين تضمين اسم الفئة المؤهَّل بالكامل في كل استخدام لفئة متوقّفة نهائيًا قبل أن يتمكّنوا من تحديث إصدار حزمة SDK المُجمَّعة.
- لا يمكن إيقاف تحذيرات الإيقاف النهائي بشكل عام أو ضبطها على القيمة الأساسية، لذا
على المطوّرين الذين يستخدمون
- تعرض المستندات حول
d.android.com
إشعارًا بشأن إيقاف الميزة نهائيًا. - تعرض حِزم تطوير البرامج (IDE) مثل "استوديو Android" تحذيرًا في موقع استخدام واجهة برمجة التطبيقات.
- قد تُخفِي حِزم تطوير البرامج (IDE) واجهة برمجة التطبيقات أو تخفض ترتيبها في ميزة "الإكمال التلقائي".
نتيجةً لذلك، يمكن أن يؤدي إيقاف واجهة برمجة تطبيقات نهائيًا إلى تثبيط المطوّرين الذين يهتمون أكثر
بصحة الرموز البرمجية (الذين يستخدمون -Werror
) عن استخدام حِزم SDK الجديدة.
من المحتمل أن يتجاهل المطوّرون الذين لا يهتمون بالتحذيرات في رموزهم البرمجية الحالية
عمليات الإيقاف النهائية تمامًا.
إنّ حزمة SDK التي تُدخل عددًا كبيرًا من عمليات الإيقاف النهائي تزيد من سوء كلتا الحالتَين.
لهذا السبب، ننصحك بإيقاف واجهات برمجة التطبيقات نهائيًا في الحالات التالية فقط:
- نخطّط ل
@remove
واجهة برمجة التطبيقات في إصدار لاحق. - يؤدي استخدام واجهة برمجة التطبيقات إلى سلوك غير صحيح أو غير محدّد لا يمكننا إصلاحه بدون إيقاف التوافق.
عند إيقاف واجهة برمجة تطبيقات نهائيًا واستبدالها بواجهة جديدة، ننصح بشدة
بإضافة واجهة برمجة تطبيقات متوافقة إلى مكتبة Jetpack مثل
androidx.core
لتبسيط عملية توفير التوافق مع الأجهزة القديمة والجديدة.
لا ننصح بإيقاف واجهات برمجة التطبيقات التي تعمل على النحو المطلوب في الإصدارات الحالية والمستقبلية:
/**
* ...
* @deprecated Use {@link #doThing(int, Bundle)} instead.
*/
@Deprecated
public void doThing(int action) {
...
}
public void doThing(int action, @Nullable Bundle extras) {
...
}
يكون إيقاف الإصدار نهائيًا مناسبًا في الحالات التي لم تعُد فيها واجهات برمجة التطبيقات قادرة على الحفاظ على سلوكياتها الموثَّقة:
/**
* ...
* @deprecated No longer displayed in the status bar as of API 21.
*/
@Deprecated
public RemoteViews tickerView;
التغييرات على واجهات برمجة التطبيقات المتوقّفة نهائيًا
يجب الحفاظ على سلوك واجهات برمجة التطبيقات المتوقّفة نهائيًا. وهذا يعني أنّه يجب أن تظل عمليات تنفيذ الاختبار كما هي، ويجب أن تستمر الاختبارات في اجتياز الاختبار بعد إيقاف واجهة برمجة التطبيقات نهائيًا. إذا لم تتضمّن واجهة برمجة التطبيقات اختبارات، عليك إضافتها.
لا توسِّع نطاق ظهور واجهات برمجة التطبيقات المتوقّفة نهائيًا في الإصدارات المستقبلية. يمكنك إضافة تعليقات توضيحية لفحص الأخطاء
للتحقّق من صحة الرمز البرمجي (مثل @Nullable
) إلى واجهة برمجة تطبيقات حالية متوقّفة نهائيًا، ولكن يجب عدم إضافة واجهات برمجة تطبيقات جديدة.
لا تُضِف واجهات برمجة تطبيقات جديدة على أنّها متوقّفة نهائيًا. إذا تمت إضافة أي واجهات برمجة تطبيقات وتم إيقافها نهائيًا بعد ذلك في دورة الإصدار التجريبي (وبالتالي ستظهر في البداية على واجهة برمجة التطبيقات المتاحة للجميع على أنّها متوقّفة نهائيًا)، يجب إزالتها قبل الانتهاء من واجهة برمجة التطبيقات.
الإزالة المؤقتة
إنّ الإزالة المؤقتة هي تغيير يؤدي إلى إيقاف المصدر، ويجب تجنُّبها في واجهات برمجة التطبيقات العامة
ما لم يوافق عليها مجلس واجهات برمجة التطبيقات صراحةً.
بالنسبة إلى واجهات برمجة تطبيقات النظام، يجب إيقاف واجهة برمجة التطبيقات نهائيًا
لمدة إصدار رئيسي قبل إزالتها نهائيًا. أزِل جميع إشارات المستندات إلى
واجهات برمجة التطبيقات واستخدِم التعليق التوضيحي في مستندات @removed <summary>
عند إزالة واجهة برمجة التطبيقات مؤقتًا. يجب أن يتضمّن الملخّص سبب الإزالة ويمكن أن يتضمّن
استراتيجية نقل البيانات، كما شرحنا في مقالة إيقاف الميزة نهائيًا.
يمكن الحفاظ على سلوك واجهات برمجة التطبيقات التي تمّت إزالتها مؤقتًا كما هو، ولكن يجب الحفاظ عليه بشكلٍ أساسي كي لا يتعطل المُطلِبون الحاليون عند طلب بيانات من واجهة برمجة التطبيقات. وفي بعض الحالات، قد يعني ذلك الحفاظ على السلوك.
يجب الحفاظ على تغطية الاختبار، ولكن قد يحتاج محتوى الاختبارات إلى التغيير لاستيعاب التغييرات السلوكية. يجب أن تتأكّد الاختبارات من أنّه لا يتعطل المتصلون الحاليون أثناء التشغيل. يمكنك الحفاظ على سلوك واجهات برمجة التطبيقات التي تمّت إزالتها بشكلٍ مؤقت كما هو، ولكن الأهم من ذلك، يجب الحفاظ عليه بحيث لا يتعطل المُطلِبون الحاليون عند طلب واجهة برمجة التطبيقات. في بعض الحالات، قد يعني ذلك الحفاظ على السلوك.
يجب الحفاظ على تغطية الاختبار، ولكن قد يحتاج محتوى الاختبارات إلى التغيير لاستيعاب التغييرات السلوكية. يجب أن تتأكّد الاختبارات من أنّه لا يتعطل المتصلون الحاليون أثناء التشغيل.
على المستوى الفني، نزيل واجهة برمجة التطبيقات من حزمة JAR المصغّرة لحزمة تطوير البرامج (SDK) وملف ملف JDK classpath في وقت الترجمة باستخدام التعليق التوضيحي @remove
Javadoc، ولكنّها تظل متوفّرة في ملف JDK classpath في وقت التشغيل، تمامًا مثل واجهات برمجة التطبيقات @hide
:
/**
* Ringer volume. This is ...
*
* @removed Not functional since API 2.
*/
public static final String VOLUME_RING = ...
من وجهة نظر مطوّر التطبيقات، لم تعُد واجهة برمجة التطبيقات تظهر في ميزة الإكمال التلقائي،
ولن يتم تجميع الرمز المصدر الذي يشير إلى واجهة برمجة التطبيقات عندما يكون compileSdk
يساوي حزمة SDK التي تمت إزالة واجهة برمجة التطبيقات منها أو إصدارًا أحدث منها. ومع ذلك، يستمر تجميع الرمز
المصدر بنجاح مع حزم SDK السابقة والبرامج الثنائيّة التي تشير إلى واجهة برمجة التطبيقات.
يجب عدم إزالة فئات معيّنة من واجهات برمجة التطبيقات بشكل مؤقت. يجب عدم soft إزالة فئات معيّنة من واجهة برمجة التطبيقات.
الطرق المجردة
يجب عدم إزالة الأساليب المجردة بشكل مؤقت في الفئات التي قد يمتدّ إليها المطوّرون. يؤدي ذلك إلى استحالة تمديد المطوّرين لصنف المعالجة بنجاح على جميع مستويات حزمة تطوير البرامج (SDK).
في الحالات النادرة التي لم يكن فيها من الممكن ولن يكون للمطوّرين توسيع نطاق فئة، سيظل بإمكانك إزالة الطرق المجردة بشكلٍ غير نهائي.
الإزالة النهائية
إنّ الإزالة الكاملة هي تغيير يؤدي إلى إيقاف الثنائيات، ويجب ألا تحدث أبدًا في واجهات برمجة التطبيقات المفتوحة.
التعليق التوضيحي الذي لا ننصح به
نستخدم التعليق التوضيحي @Discouraged
للإشارة إلى أنّنا لا ننصح باستخدام واجهة برمجة تطبيقات في معظم الحالات (>%95). تختلف واجهات برمجة التطبيقات غير المُستحسَنة عن واجهات برمجة التطبيقات المتوقّفة عن العمل في أنّه
توجد حالة استخدام ضيّقة ومهمة تمنع إيقافها نهائيًا. عند وضع علامة "غير مستحسن" على واجهة برمجة التطبيقات، يجب تقديم شرح وحل بديل:
@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);
}
لا يُنصح بإضافة واجهات برمجة تطبيقات جديدة.
التغييرات في سلوك واجهات برمجة التطبيقات الحالية
في بعض الحالات، قد تحتاج إلى تغيير سلوك تنفيذ واجهة برمجة تطبيقات
حالية. على سبيل المثال، في الإصدار 7.0 من نظام التشغيل Android، أجرينا تحسينات على DropBoxManager
ل إعلام المطوّرين بوضوح عندما يحاولون نشر أحداث كبيرة جدًا بحيث لا يمكن
إرسالها عبر Binder
.
ومع ذلك، لتجنُّب حدوث مشاكل للتطبيقات الحالية، ننصحك بشدة
بالاحتفاظ بسلوك آمن للتطبيقات القديمة. في السابق، كنا نحرص على منع
هذه التغييرات في السلوك استنادًا إلى ApplicationInfo.targetSdkVersion
التطبيق، ولكن
لقد انتقلنا مؤخرًا إلى طلب استخدام "إطار عمل توافق التطبيقات". في ما يلي مثال على كيفية تنفيذ تغيير في السلوك باستخدام هذا الإطار الجديد:
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
}
}
}
من خلال استخدام تصميم إطار عمل التوافق مع التطبيقات هذا، يمكن للمطوّرين temporarily إيقاف تغييرات سلوك معيّنة مؤقتًا أثناء إصدارات المعاينة والإصدارات التجريبية كجزء من تصحيح أخطاء تطبيقاتهم، بدلاً من إجبارهم على التكيّف مع عشرات التغييرات في السلوك في الوقت نفسه.
التوافق مع الإصدارات المستقبلية
التوافق مع الإصدارات الأحدث هو سمة تصميم تسمح للنظام بقبول مدخلات مخصّصة لإصدار أحدث منه. في ما يتعلّق بتصميم واجهة برمجة التطبيقات، يجب الاهتمام بشكل خاص بالتصميم الأولي والتغييرات المستقبلية، لأنّ المطوّرين يتوقّعون كتابة الرمز البرمجي مرة واحدة واختباره مرة واحدة وتشغيله في كل مكان بدون مشاكل.
في ما يلي الأسباب الأكثر شيوعًا لمشاكل التوافق المستقبلي في Android:
- إضافة ثوابت جديدة إلى مجموعة (مثل
@IntDef
أوenum
) كان يُفترض سابقًا أنّها كاملة (على سبيل المثال، عندما تحتويswitch
علىdefault
يؤدي إلى طرح استثناء) - إضافة ميزة غير مضمّنة مباشرةً في واجهة برمجة التطبيقات
(على سبيل المثال، إتاحة إمكانية تخصيص موارد من النوع
ColorStateList
في ملف XML حيث كانت موارد<color>
فقط متاحة في السابق) - تخفيف القيود المفروضة على عمليات التحقّق أثناء التشغيل، على سبيل المثال إزالة عملية التحقّق من
requireNotNull()
التي كانت متوفّرة في الإصدارات الأقدم
في جميع هذه الحالات، لا يكتشف المطوّرون وجود مشكلة إلا أثناء وقت التشغيل. والأسوأ من ذلك، قد يكتشفون ذلك نتيجةً لتقارير الأعطال الواردة من الأجهزة القديمة في الميدان.
بالإضافة إلى ذلك، جميع هذه الحالات هي تغييرات فنية صالحة لواجهة برمجة التطبيقات. ولا تؤدي هذه الأخطاء إلى تعطيل التوافق مع الثنائي أو المصدر، ولن ترصد أداة فحص أخطاء واجهة برمجة التطبيقات أيًا من هذه الصعوبات.
ونتيجةً لذلك، على مصمّمي واجهات برمجة التطبيقات الانتباه بعناية عند تعديل الفئات الحالية. اطرح السؤال التالي: "هل سيؤدي هذا التغيير إلى تعطُّل الرمز الذي تم كتابته واختباره فقط على أحدث إصدار من المنصة في الإصدارات الأقدم؟"
مخطّطات XML
إذا كان مخطّط XML يعمل كواجهة ثابتة بين المكوّنات، يجب تحديد هذا المخطّط بوضوح وتطويره بطريقة متوافقة مع الإصدارات القديمة، مثل واجهات برمجة تطبيقات 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 بشكلٍ فردي، بدلاً من تحديث صورة النظام بالكامل.
يجب "إزالة تجميع" الوحدات الرئيسية من النظام الأساسي، ما يعني أنّه يجب إجراء جميع التفاعلات بين كل وحدة وبقية التطبيقات باستخدام واجهات برمجة تطبيقات رسمية (عامة أو خاصة بالنظام).
هناك أنماط تصميم معيّنة يجب أن تتّبعها وحدات التطبيق الرئيسية. يوضّح هذا القسم هذه الشروط.
نمط <Module>FrameworkInitializer
إذا كانت إحدى وحدات التطبيق الرئيسية بحاجة إلى عرض فئات @SystemService
(مثل
JobScheduler
)، استخدِم النمط التالي:
اعرض فئة
<YourModule>FrameworkInitializer
من وحدتك. يجب أن تكون هذه الفئة في$BOOTCLASSPATH
. مثال: StatsFrameworkInitializerضَع علامة عليه باستخدام
@SystemApi(client = MODULE_LIBRARIES)
.أضِف طريقة
public static void registerServiceWrappers()
إليه.استخدِم
SystemServiceRegistry.registerContextAwareService()
لتسجيل فئة مدير الخدمة عندما تحتاج إلى إشارة إلىContext
.استخدِم
SystemServiceRegistry.registerStaticService()
لتسجيل فئةSystemServiceRegistry.registerStaticService()
عندما لا تحتاج إلى إشارة إلىContext
.استخدِم الأسلوب
registerServiceWrappers()
من مُنشئSystemServiceRegistry
الثابت.
نموذج <Module>ServiceManager
في العادة، لتسجيل عناصر رابط خدمات النظام أو الحصول على إحالات
إليها، يتم استخدام
ServiceManager
،
ولكن لا يمكن للوحدات الرئيسية استخدامه لأنّه مخفي. هذه الفئة مخفية
لأنّه من المفترض ألا تسجِّل وحدات الإصدار الرئيسي أو تشير إلى كائنات رابط
خدمة النظام التي يعرضها النظام الأساسي الثابت أو الوحدات الأخرى.
يمكن للوحدات الرئيسية استخدام النمط التالي بدلاً من ذلك لتتمكّن من التسجيل والحصول على مراجع لخدمات الربط التي يتم تنفيذها داخل الوحدة.
أنشئ فئة
<YourModule>ServiceManager
وفقًا لتصميم TelephonyServiceManager.اعرض الصف على أنّه
@SystemApi
. إذا كنت بحاجة إلى الوصول إليه من فئات$BOOTCLASSPATH
أو فئات خادم النظام فقط، يمكنك استخدام@SystemApi(client = MODULE_LIBRARIES)
، وإلا سيعمل@SystemApi(client = PRIVILEGED_APPS)
.سيتألف هذا الصف من:
- عنصر إنشاء مخفي، بحيث لا يمكن إلا لرمز المنصة الثابت إنشاء مثيل له
- طرق الحصول العامة التي تُرجع مثيلًا من
ServiceRegisterer
لاسم معيّن إذا كان لديك عنصر رابط واحد، ستحتاج إلى طريقة واحدة للحصول على قيمة. إذا كان لديك اثنان، ستحتاج إلى اثنَين من وظائف الحصول. - في ملف برمجي
ActivityThread.initializeMainlineModules()
، أنشئ مثيلًا لهذه الفئة، ثم أعِد توجيهه إلى طريقة ثابتة تعرِضها وحدتك. عادةً ما تضيف واجهة برمجة تطبيقات@SystemApi(client = MODULE_LIBRARIES)
ثابتة في فئةFrameworkInitializer
التي تأخذ القيمة.
سيؤدي هذا النمط إلى منع الوحدات الرئيسية الأخرى من الوصول إلى واجهات برمجة التطبيقات هذه
لأنّه لا تتوفّر طريقة للوحدات الأخرى للحصول على مثيل
<YourModule>ServiceManager
، على الرغم من أنّ واجهات برمجة التطبيقات get()
وregister()
تكون مرئية لها.
في ما يلي كيفية حصول خدمة الاتصال الهاتفي على مرجع للخدمة: رابط البحث عن الرمز.
إذا كنت تُنفِّذ عنصر رابط خدمة في رمز أصلي، يمكنك استخدام
واجهات برمجة التطبيقات الأصلية AServiceManager
.
تتوافق واجهات برمجة التطبيقات هذه مع واجهات برمجة تطبيقات Java في ServiceManager
، ولكن يتم الكشف عن واجهات برمجة التطبيقات الأصلية
مباشرةً في وحدات الإصدار الرئيسي. لا تستخدِم هذه العناصر لتسجيل أو الإشارة إلى
عناصر الربط التي لا تملكها وحدتك. إذا عرضت عنصر رابط
من العنصر الأصلي، لن تحتاج <YourModule>ServiceManager.ServiceRegisterer
إلى register()
.
تعريفات الأذونات في وحدات Mainline
يمكن للوحدات الرئيسية التي تحتوي على حِزم APK تحديد أذونات (مخصّصة) في حِزم APK
AndroidManifest.xml
بالطريقة نفسها المستخدَمة في حِزم APK العادية.
إذا كان الإذن المحدّد يُستخدَم داخليًا فقط ضمن وحدة، يجب أن يسبق اسم الإذن اسم حزمة APK، على سبيل المثال:
<permission
android:name="com.android.permissioncontroller.permission.MANAGE_ROLES_FROM_CONTROLLER"
android:protectionLevel="signature" />
إذا كان الإذن المحدّد مخصّصًا لتوفيره كجزء من واجهة برمجة تطبيقات نظام أساسي قابل للتعديل للتطبيقات الأخرى، يجب إضافة البادئة "android.permission" إلى اسم الإذن. (مثل أي إذن ثابت للمنصة) بالإضافة إلى اسم حزمة الوحدة، للإشارة إلى أنّها واجهة برمجة تطبيقات للمنصة من وحدة مع تجنُّب أي تعارضات في الأسماء، على سبيل المثال:
<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" />
بعد ذلك، يمكن للوحدة عرض اسم الإذن هذا كثابت لواجهة برمجة التطبيقات في سطح واجهة برمجة التطبيقات، على سبيل المثال
HealthPermissions.READ_ACTIVE_CALORIES_BURNED
.