تهدف هذه الصفحة إلى أن تكون دليلاً للمطوّرين لفهم المبادئ العامة التي يفرضها مجلس واجهات برمجة التطبيقات في مراجعات واجهات برمجة التطبيقات.
بالإضافة إلى اتّباع هذه الإرشادات عند كتابة واجهات برمجة التطبيقات، على المطوّرين تشغيل أداة API Lint التي تدرج العديد من هذه القواعد في عمليات التحقّق التي تجريها على واجهات برمجة التطبيقات.
يمكنك اعتبار هذا المستند دليلاً للقواعد التي تلتزم بها أداة Lint، بالإضافة إلى نصائح عامة بشأن القواعد التي لا يمكن ترميزها في هذه الأداة بدقة عالية.
أداة API Lint
تم دمج API Lint في أداة التحليل الثابت Metalava، ويتم تشغيلها تلقائيًا أثناء عملية التحقّق في عملية الدمج المتواصل. يمكنك تشغيله يدويًا من عملية دفع على منصة محلية باستخدام m
checkapi
أو عملية دفع محلية على AndroidX باستخدام ./gradlew :path:to:project:checkApi
.
قواعد واجهة برمجة التطبيقات
كانت منصة Android والعديد من مكتبات Jetpack متوفّرة قبل إنشاء هذه المجموعة من الإرشادات، كما أنّ السياسات الموضّحة لاحقًا في هذه الصفحة تتطوّر باستمرار لتلبية احتياجات نظام Android الأساسي.
نتيجةً لذلك، قد لا تلتزم بعض واجهات برمجة التطبيقات الحالية بالإرشادات. في حالات أخرى، قد توفّر واجهة برمجة التطبيقات الجديدة تجربة أفضل للمطوّرين إذا كانت متوافقة مع واجهات برمجة التطبيقات الحالية بدلاً من الالتزام الصارم بالإرشادات.
استخدِم تقديرك الخاص وتواصل مع "مجلس واجهات برمجة التطبيقات" إذا كانت لديك أسئلة صعبة حول إحدى واجهات برمجة التطبيقات يجب حلّها أو إرشادات يجب تعديلها.
أساسيات واجهة برمجة التطبيقات
تتعلّق هذه الفئة بالجوانب الأساسية لواجهة برمجة تطبيقات Android.
يجب تنفيذ جميع واجهات برمجة التطبيقات
بغض النظر عن الجمهور المستهدف لواجهة برمجة التطبيقات (على سبيل المثال، الجمهور العام أو @SystemApi
)، يجب تنفيذ جميع أسطح واجهة برمجة التطبيقات عند دمجها أو عرضها كواجهة برمجة تطبيقات. لا تدمج رموز API
البرمجية مع التنفيذ الذي سيتم في وقت لاحق.
تتضمّن مساحات واجهة برمجة التطبيقات التي لا تتضمّن عمليات تنفيذ مشاكل متعدّدة، منها:
- وما مِن ضمان بأنّه تم عرض سطح مناسب أو كامل. إلى أن يتم اختبار واجهة برمجة التطبيقات أو استخدامها من قِبل العملاء، لا توجد طريقة للتحقّق من أنّ العميل لديه واجهات برمجة التطبيقات المناسبة لاستخدام الميزة.
- لا يمكن اختبار واجهات برمجة التطبيقات التي لا تتضمّن تنفيذًا في إصدارات المعاينة للمطوّرين.
- لا يمكن اختبار واجهات برمجة التطبيقات التي لا تتضمّن تنفيذًا في مجموعة اختبار التوافق (CTS).
يجب اختبار جميع واجهات برمجة التطبيقات
يتوافق ذلك مع متطلبات CTS الخاصة بالمنصة وسياسات AndroidX وفكرة أنّه يجب تنفيذ واجهات برمجة التطبيقات بشكل عام.
يوفر اختبار مساحات واجهة برمجة التطبيقات ضمانًا أساسيًا بأنّ مساحة واجهة برمجة التطبيقات قابلة للاستخدام، وأنّنا عالجنا حالات الاستخدام المتوقّعة. لا يكفي اختبار وجود واجهة برمجة التطبيقات، بل يجب اختبار سلوكها أيضًا.
يجب أن يتضمّن التغيير الذي يضيف واجهة برمجة تطبيقات جديدة اختبارات مقابلة في طلب التغيير نفسه أو موضوع 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 أو إصدارًا أحدث.
في الحالات التي يكون فيها التنفيذ التلقائي بلا حالة، يجب أن يفضّل مصمّمو واجهات برمجة التطبيقات استخدام الواجهات على الفئات المجردة، أي يمكن تنفيذ طرق الواجهة التلقائية كطلبات إلى طرق واجهة أخرى.
في الحالات التي تتطلّب دالة إنشائية أو حالة داخلية في التنفيذ التلقائي، يجب استخدام الفئات المجردة.
في كلتا الحالتين، يمكن لمصمّمي واجهات برمجة التطبيقات اختيار ترك طريقة واحدة مجرّدة لتسهيل الاستخدام كدالة lambda:
public interface AnimationEndCallback {
// Always called, must be implemented.
public void onFinished(Animation anim);
// Optional callbacks.
public default void onStopped(Animation anim) { }
public default void onCanceled(Animation anim) { }
}
يجب أن تعكس أسماء الفئات ما توسّعه
على سبيل المثال، يجب تسمية الفئات التي توسّع Service
باسم FooService
لتوضيح ذلك:
public class IntentHelper extends Service {}
public class IntentService extends Service {}
اللاحقات العامة
تجنَّب استخدام لاحقات عامة لأسماء الفئات، مثل Helper
وUtil
للمجموعات التي تتضمّن طرقًا مساعدة. بدلاً من ذلك، ضَع الطرق مباشرةً في الفئات المرتبطة
أو في دوال إضافة Kotlin.
في الحالات التي تربط فيها الطرق عدة فئات، امنح الفئة الحاوية اسمًا ذا معنى يوضّح وظيفتها.
في حالات محدودة جدًا، قد يكون استخدام اللاحقة Helper
مناسبًا:
- تُستخدَم لتحديد السلوك التلقائي
- قد يتضمّن تفويض السلوك الحالي إلى فئات جديدة
- قد يتطلب حالة مستمرة
- يتضمّن ذلك عادةً
View
على سبيل المثال، إذا كان عرض تلميحات الأدوات المتوافقة مع الإصدارات القديمة يتطلّب الحفاظ على الحالة المرتبطة بـ View
واستدعاء عدّة طرق في View
لتثبيت التوافق مع الإصدارات القديمة، سيكون TooltipHelper
اسم فئة مقبولاً.
عدم عرض الرموز التي تم إنشاؤها باستخدام لغة تعريف الواجهة (IDL) كواجهات برمجة تطبيقات عامة مباشرةً
احتفِظ بالرمز البرمجي الذي تم إنشاؤه باستخدام لغة وصف الواجهة (IDL) كأحد تفاصيل التنفيذ. ويشمل ذلك protobuf أو المقابس أو FlatBuffers أو أي مساحة أخرى لواجهة برمجة التطبيقات غير Java وغير NDK. ومع ذلك، فإنّ معظم تعريفات لغة الواجهة (IDL) في Android تكون بتنسيق AIDL، لذا تركّز هذه الصفحة على AIDL.
لا تستوفي فئات AIDL التي يتم إنشاؤها متطلبات دليل أسلوب واجهة برمجة التطبيقات (على سبيل المثال، لا يمكنها استخدام التحميل الزائد)، كما أنّ أداة AIDL غير مصمَّمة بشكل صريح للحفاظ على توافق واجهة برمجة التطبيقات مع اللغة، لذا لا يمكنك تضمينها في واجهة برمجة تطبيقات عامة.
بدلاً من ذلك، أضِف طبقة واجهة برمجة تطبيقات عامة فوق واجهة AIDL، حتى لو كانت في البداية مجرد برنامج تضمين سطحي.
واجهات Binder
إذا كانت واجهة Binder
تفصيلاً تنفيذيًا، يمكن تغييرها بحرية
في المستقبل، مع السماح للطبقة العامة بالحفاظ على التوافق
مع الإصدارات السابقة المطلوب. على سبيل المثال، قد تحتاج إلى إضافة وسيطات جديدة إلى المكالمات الداخلية، أو تحسين حركة بيانات IPC باستخدام التجميع أو البث، أو استخدام الذاكرة المشتركة، أو ما شابه ذلك. لا يمكن تنفيذ أي من هذه الإجراءات إذا كانت واجهة 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
على الرغم من أنّ 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() {}
}
Singletons
لا يُنصح باستخدام الكائنات المفردة لأنّها تتضمّن العيوب التالية المرتبطة بالاختبار:
- تتم إدارة الإنشاء من خلال الفئة، ما يمنع استخدام عمليات تزوير.
- لا يمكن أن تكون الاختبارات محكمة الإغلاق بسبب الطبيعة الثابتة للعنصر الفردي
- لحلّ هذه المشاكل، على المطوّرين إما معرفة التفاصيل الداخلية الخاصة بالكائن الفردي أو إنشاء برنامج تضمين له.
ننصحك باستخدام نمط المثيل الفردي الذي يعتمد على فئة أساسية مجرّدة لمعالجة هذه المشاكل.
مثيل واحد
تستخدم فئات المثيل الفردي فئة أساسية مجرّدة مع الدالة الإنشائية 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-first. يجب توفير ميزات واجهة المستخدم الجديدة التي تعرضها المنصة على شكل واجهات برمجة تطبيقات ذات مستوى أدنى يمكن استخدامها لتنفيذ Jetpack Compose ومكوّنات واجهة المستخدم المستندة إلى العرض بشكل اختياري للمطوّرين في مكتبات Jetpack. ويتيح توفير هذه المكوّنات في المكتبات فرصًا لتنفيذ عمليات نقل إلى إصدارات سابقة عندما لا تتوفّر ميزات النظام الأساسي.
الحقول
تتعلّق هذه القواعد بالحقول العامة في الفئات.
عدم عرض الحقول الأولية
يجب ألا تعرض فئات Java الحقول مباشرةً. يجب أن تكون الحقول خاصة ولا يمكن الوصول إليها إلا باستخدام دوال الجلب والتعديل العامة، بغض النظر عمّا إذا كانت هذه الحقول نهائية أم لا.
تشمل الاستثناءات النادرة بنى البيانات الأساسية التي لا حاجة فيها إلى تحسين سلوك تحديد حقل أو استرجاعه. في مثل هذه الحالات، يجب تسمية الحقول باستخدام اصطلاحات التسمية القياسية للمتغيرات، مثل Point.x
وPoint.y
.
يمكن لفئات Kotlin عرض السمات.
يجب وضع علامة "نهائي" على الحقول المكشوفة
ننصح بشدة بعدم استخدام الحقول الأولية (@see
Don't expose raw fields). ولكن في الحالات النادرة التي يتم فيها عرض حقل كحقل عام، يجب وضع علامة final
على هذا الحقل.
يجب عدم عرض الحقول الداخلية
لا تشِر إلى أسماء الحقول الداخلية في واجهة برمجة التطبيقات العامة.
public int mFlags;
استخدام "عام" بدلاً من "محمي"
@see استخدام "عام" بدلاً من "محمي"
الثوابت
هذه قواعد حول الثوابت العامة.
يجب ألا تتداخل ثوابت العلامات مع قيم 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
للحصول على علامات bitmask لمزيد من المعلومات حول تحديد ثوابت العلامات العامة.
يجب أن تستخدم الثوابت النهائية الثابتة أسلوب التسمية الذي يتضمّن جميع الأحرف الكبيرة والفواصل السفلية
يجب كتابة جميع الكلمات في الثابت بأحرف كبيرة، ويجب الفصل بين الكلمات المتعددة باستخدام _
. مثلاً:
public static final int fooThing = 5
public static final int FOO_THING = 5
استخدام بادئات عادية للثوابت
تُستخدَم العديد من الثوابت في Android لأشياء عادية، مثل العلامات والمفاتيح والإجراءات. يجب أن تتضمّن هذه الثوابت بادئات عادية لتسهيل التعرّف عليها.
على سبيل المثال، يجب أن تبدأ إضافات Intent بالرمز EXTRA_
. يجب أن تبدأ إجراءات Intent بـ ACTION_
. يجب أن تبدأ الثوابت المستخدَمة مع Context.bindService()
بـ BIND_
.
أسماء النطاقات والثوابت الرئيسية
يجب أن تكون قيم السلسلة الثابتة متوافقة مع اسم الثابت نفسه، ويجب أن تكون بشكل عام ضمن نطاق الحزمة أو النطاق. مثلاً:
public static final String FOO_THING = "foo"
لا يتم تسميته بشكل متّسق ولا يتم تحديد نطاقه بشكل مناسب. بدلاً من ذلك، ننصحك بما يلي:
public static final String FOO_THING = "android.fooservice.FOO_THING"
تكون بادئات android
في ثوابت السلسلة ذات النطاق محجوزة لمشروع Android
المفتوح المصدر.
يجب أن يتم تحديد مساحة أسماء إجراءات الأهداف والإضافات، بالإضافة إلى إدخالات الحِزم، باستخدام اسم الحزمة التي تم تحديدها فيها.
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"
}
استخدام "عام" بدلاً من "محمي"
@see استخدام "عام" بدلاً من "محمي"
استخدام البادئات المتطابقة
يجب أن تبدأ جميع الثوابت ذات الصلة بالبادئة نفسها. على سبيل المثال، بالنسبة إلى مجموعة من الثوابت لاستخدامها مع قيم العلامات:
public static final int SOME_VALUE = 0x01;
public static final int SOME_OTHER_VALUE = 0x10;
public static final int SOME_THIRD_VALUE = 0x100;
public static final int FLAG_SOME_VALUE = 0x01;
public static final int FLAG_SOME_OTHER_VALUE = 0x10;
public static final int FLAG_SOME_THIRD_VALUE = 0x100;
@see استخدام البادئات العادية للثوابت
استخدام أسماء موارد متطابقة
يجب تسمية المعرّفات العامة والسمات والقيم باستخدام اصطلاح التسمية camelCase، مثل @id/accessibilityActionPageUp
أو @attr/textAppearance
، على غرار الحقول العامة في Java.
في بعض الحالات، يتضمّن المعرّف العلني أو السمة بادئة شائعة مفصولة بشرطة سفلية:
- قيم إعدادات المنصة، مثل
@string/config_recentsComponentName
في config.xml - سمات العرض الخاصة بالتصميم، مثل
@attr/layout_marginStart
في attrs.xml
يجب أن تتبع السمات والأنماط العامة اصطلاح التسمية الهرمي PascalCase، مثل @style/Theme.Material.Light.DarkActionBar
أو @style/Widget.Material.SearchView.ActionBar
، على غرار الفئات المتداخلة في Java.
يجب عدم عرض موارد التنسيق والرسومات المتجهة كواجهات برمجة تطبيقات عامة. ومع ذلك، إذا كان يجب عرضها، يجب تسمية التصاميم والرسومات القابلة للرسم العامة باستخدام اتفاقية التسمية 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، يجب أن تتطابق الوسيطات مع الدالة الإنشائية للفئة، ويجب ملء القيم التلقائية باستخدام القيم الحالية للكائن:
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)
.
الطرق
هذه قواعد تتعلّق بتفاصيل مختلفة في الطرق، مثل المَعلمات وأسماء الطرق وأنواع الإرجاع ومحدّدات الوصول.
الوقت
تغطّي هذه القواعد كيفية التعبير عن مفاهيم الوقت، مثل التواريخ والمدد، في واجهات برمجة التطبيقات.
استخدام أنواع java.time.* متى أمكن ذلك
تتوفّر java.time.Duration
وjava.time.Instant
والعديد من أنواع java.time.*
الأخرى في جميع إصدارات النظام الأساسي من خلال إزالة التشفير، ويُنصح باستخدامها عند التعبير عن الوقت في مَعلمات واجهة برمجة التطبيقات أو القيم المعروضة.
يُفضّل عرض أشكال مختلفة من واجهة برمجة التطبيقات تقبل أو تعرض java.time.Duration
أو java.time.Instant
فقط، مع إغفال الأشكال المختلفة الأساسية التي لها حالة الاستخدام نفسها، إلا إذا كان نطاق واجهة برمجة التطبيقات هو أحد النطاقات التي يكون فيها تخصيص الكائنات في أنماط الاستخدام المقصودة له تأثير كبير على الأداء.
يجب تسمية الطرق التي تعبّر عن المدد الزمنية بالمدة
إذا كانت قيمة الوقت تعبّر عن مدة الوقت المعنيّ، يجب تسمية المَعلمة "المدة" وليس "الوقت".
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()
قاعدة الوقت.
وحدات القياس
بالنسبة إلى جميع الطرق التي تعبّر عن وحدة قياس غير الوقت، يُفضّل استخدام CamelCased بادئات وحدات النظام الدولي.
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);
راجِع أيضًا: وضع المَعلمات الاختيارية في نهاية عمليات التحميل الزائد
Builders
يُنصح باستخدام نمط Builder لإنشاء عناصر Java معقّدة، ويُستخدَم بشكل شائع في Android في الحالات التالية:
- يجب أن تكون خصائص العنصر الناتج غير قابلة للتغيير
- هناك عدد كبير من الخصائص المطلوبة، مثل العديد من وسيطات الدالة الإنشائية
- هناك علاقة معقّدة بين السمات في وقت الإنشاء، على سبيل المثال، يجب إجراء خطوة إثبات الهوية. يُرجى العِلم أنّ هذا المستوى من التعقيد يشير غالبًا إلى مشاكل في سهولة استخدام واجهة برمجة التطبيقات.
حدِّد ما إذا كنت بحاجة إلى أداة إنشاء. تكون أدوات الإنشاء مفيدة في مساحة واجهة برمجة التطبيقات إذا تم استخدامها في ما يلي:
- ضبط عدد قليل فقط من مجموعة كبيرة محتملة من معلَمات الإنشاء الاختيارية
- ضبط العديد من مَعلمات الإنشاء الاختيارية أو المطلوبة، والتي تكون أحيانًا من أنواع متشابهة أو متطابقة، حيث يمكن أن تصبح مواقع الاتصال مربكة عند قراءتها أو عرضة للأخطاء عند كتابتها
- ضبط إنشاء عنصر بشكل تدريجي، حيث يمكن أن تنفّذ عدة أجزاء مختلفة من رمز الإعدادات طلبات على أداة الإنشاء كتفاصيل التنفيذ
- السماح بتوسيع نوع من خلال إضافة مَعلمات إنشاء اختيارية إضافية في إصدارات واجهة برمجة التطبيقات المستقبلية
إذا كان لديك نوع يتضمّن ثلاث مَعلمات مطلوبة أو أقل وليس به مَعلمات اختيارية، يمكنك دائمًا تخطّي أداة الإنشاء واستخدام أداة إنشاء عادية.
يجب أن تفضّل الفئات المستندة إلى Kotlin دوال الإنشاء التي تحمل التعليق التوضيحي @JvmOverloads
مع الوسيطات التلقائية على أدوات الإنشاء، ولكن يمكن اختيار تحسين سهولة الاستخدام لعملاء Java من خلال توفير أدوات الإنشاء أيضًا في الحالات الموضّحة سابقًا.
class Tone @JvmOverloads constructor(
val duration: Long = 1000,
val frequency: Int = 2600,
val dtmfConfigs: List<DtmfConfig> = emptyList()
) {
class Builder {
// ...
}
}
يجب أن تعرض فئات أداة الإنشاء أداة الإنشاء
يجب أن تتيح فئات Builder ربط الطرق من خلال عرض كائن 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);
}
يجب إنشاء فئات Builder من خلال دالة إنشاء
للحفاظ على اتساق عملية إنشاء أدوات الإنشاء من خلال مساحة عرض واجهة برمجة التطبيقات لنظام Android، يجب إنشاء جميع أدوات الإنشاء من خلال دالة إنشاء وليس من خلال طريقة إنشاء ثابتة. بالنسبة إلى واجهات برمجة التطبيقات المستندة إلى 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
.
قد يتضمّن المنشئ دالة إنشاء لإنشاء مثيل جديد من مثيل حالي
يمكن أن تتضمّن أدوات الإنشاء دالة إنشاء نسخ لإنشاء مثيل جديد لأداة الإنشاء من أداة إنشاء حالية أو كائن تم إنشاؤه. يجب عدم توفير طرق بديلة لإنشاء مثيلات أدوات إنشاء من أدوات إنشاء أو عناصر إنشاء حالية.
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 للسمات الاختيارية
في كثير من الأحيان، يكون من الأسهل استخدام قيمة تقبل القيم الخالية للإدخال من الدرجة الثانية، خاصةً في 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 للخصائص القابلة للتغيير حيث تتوفّر أدوات الضبط في الفئة التي تم إنشاؤها
إذا كان صفك يحتوي على سمات قابلة للتغيير ويحتاج إلى فئة 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);
يجب ألا تحتوي أدوات الإنشاء على دوال جلب
يجب أن تكون الدالة Getter في الكائن الذي تم إنشاؤه، وليس في أداة الإنشاء.
يجب أن تتضمّن أدوات الضبط في أداة الإنشاء أدوات جلب مقابلة في الفئة التي تم إنشاؤها
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()
.
من المتوقّع أن تحدّد فئات Builder طريقة build()
يجب أن تحدّد فئات الإنشاء طريقة build()
تعرض مثيلاً للكائن الذي تم إنشاؤه.
يجب أن تعرض طرق Builder build() عناصر @NonNull
من المتوقّع أن يعرض الإجراء build()
الخاص بأداة الإنشاء مثيلاً غير فارغ للعنصر الذي تم إنشاؤه. في حال تعذّر إنشاء العنصر بسبب معلَمات غير صالحة، يمكن تأجيل عملية التحقّق من الصحة إلى طريقة الإنشاء، ويجب طرح 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 أو غيرها من عمليات الإدخال/الإخراج، لذا يُفضّل استخدام
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" describes a behavior that the object may provide,
// and here is more concise than setRecordingEnabled or
// setRecordingAllowed. The object "can" record:
void setCanRecord(boolean canRecord);
boolean canRecord();
// "Should" describes a hint or property that is not strictly
// enforced, and here is more explicit than setFitWidthEnabled.
// The object "should" fit width:
void setShouldFitWidth(boolean shouldFitWidth);
boolean shouldFitWidth();
قد تستخدم الطرق التي تبدّل السلوكيات أو الميزات البادئة is واللاحقة Enabled:
// "Enabled" describes the availability of a property, and is
// more appropriate here than "can use" or "should use" the
// property:
void setWiFiRoamingSettingEnabled(boolean enabled)
boolean isWiFiRoamingSettingEnabled()
وبالمثل، يمكن أن تستخدم الطرق التي تشير إلى الاعتماد على سلوكيات أو ميزات أخرى البادئة is واللاحقة Supported أو Required:
// "Supported" describes whether this API would work on devices that support
// multiple users. The API "supports" multi-user:
void setMultiUserSupported(boolean supported)
boolean isMultiUserSupported()
// "Required" describes whether this API depends on devices that support
// multiple users. The API "requires" multi-user:
void setMultiUserRequired(boolean required)
boolean isMultiUserRequired()
بشكل عام، يجب كتابة أسماء الطرق على شكل أسئلة يتم الإجابة عنها بواسطة القيمة المعروضة.
طُرق خصائص Kotlin
بالنسبة إلى سمة الفئة var foo: Foo
، ستنشئ لغة Kotlin الطريقتَين get
/set
باستخدام قاعدة متسقة: إضافة get
في البداية وكتابة الحرف الأول بأحرف كبيرة
في طريقة getter، وإضافة set
في البداية وكتابة الحرف الأول بأحرف كبيرة في طريقة setter. سيؤدي تعريف السمة إلى إنشاء طريقتَين باسمَي public Foo getFoo()
وpublic void setFoo(Foo foo)
على التوالي.
إذا كان نوع السمة Boolean
، تسري قاعدة إضافية عند إنشاء الاسم: إذا كان اسم السمة يبدأ بـ is
، لن تتم إضافة get
إلى اسم طريقة getter، وسيتم استخدام اسم السمة نفسه كطريقة getter.
لذلك، ننصح بتسمية سمات Boolean
باستخدام البادئة is
من أجل اتّباع إرشادات التسمية:
var isVisible: Boolean
إذا كان موقعك الإلكتروني من بين الاستثناءات المذكورة أعلاه ويبدأ ببادئة مناسبة، استخدِم التعليق التوضيحي @get:JvmName
على الموقع الإلكتروني لتحديد الاسم المناسب يدويًا:
@get:JvmName("hasTransientState")
var hasTransientState: Boolean
@get:JvmName("canRecord")
var canRecord: Boolean
@get:JvmName("shouldFitWidth")
var shouldFitWidth: Boolean
برامج الوصول إلى قناع البت
راجِع استخدام @IntDef
لعلامات قناع البت للاطّلاع على إرشادات واجهة برمجة التطبيقات بشأن تحديد علامات قناع البت.
تحديد القيمة
يجب توفير طريقتَي ضبط: إحداهما تأخذ سلسلة بت كاملة وتستبدل جميع العلامات الحالية، والأخرى تأخذ قناع بت مخصّصًا للسماح بمزيد من المرونة.
/**
* 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);
Getters
يجب توفير أداة جلب واحدة للحصول على قناع البت الكامل.
/**
* 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، لذا لا تتوفّر مستندات متسقة لمعظم طرق واجهة برمجة التطبيقات في Android. لذلك، لدينا ثلاث حالات: "غير معروف، @Nullable
، @NonNull
"، وهذا هو السبب في أنّ @NonNull
يشكّل جزءًا من إرشادات واجهة برمجة التطبيقات:
@NonNull
public String getName()
public void setName(@NonNull String name)
في مستندات منصة Android، سيؤدي وضع تعليقات توضيحية على مَعلمات الطريقة إلى إنشاء مستندات تلقائيًا بالتنسيق "قد تكون هذه القيمة فارغة"، ما لم يتم استخدام "null" بشكل صريح في مكان آخر في مستند المَعلمة.
الطُرق الحالية "غير القابلة للتصغير": يمكن إضافة التعليق التوضيحي @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
اللتَين من المفترض أن تتلقّيا إحدى مجموعة محدودة من القيم المحتملة التي تشير إليها الثوابت العامة. تتيح لك هذه التعليقات التوضيحية إنشاء تعليق توضيحي جديد يمكنك استخدامه ويعمل مثل تعريف نوع لوسيطات مسموح بها. مثلاً:
/** @hide */
@IntDef(prefix = {"NAVIGATION_MODE_"}, value = {
NAVIGATION_MODE_STANDARD,
NAVIGATION_MODE_LIST,
NAVIGATION_MODE_TABS
})
@Retention(RetentionPolicy.SOURCE)
public @interface NavigationMode {}
public static final int NAVIGATION_MODE_STANDARD = 0;
public static final int NAVIGATION_MODE_LIST = 1;
public static final int NAVIGATION_MODE_TABS = 2;
@NavigationMode
public int getNavigationMode();
public void setNavigationMode(@NavigationMode int mode);
يُنصح باستخدام الطرق المقترَحة للتحقّق من صحة المَعلمات المشروحة
وإصدار IllegalArgumentException
إذا لم تكن المَعلمة جزءًا من
@IntDef
@IntDef لعلامات قناع البت
يمكن أن تحدّد التعليقات التوضيحية أيضًا أنّ الثوابت هي علامات، ويمكن دمجها مع & و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";
توفير إمكانية التوافق مع القيم الخالية لعمليات الإلغاء
لضمان توافق واجهة برمجة التطبيقات، يجب أن يكون التوافق مع القيم الفارغة لعمليات الإلغاء متوافقًا مع التوافق الحالي مع القيم الفارغة للعنصر الأصلي. يوضّح الجدول التالي توقعات التوافق. ببساطة، يجب أن تكون عمليات الإلغاء مقيّدة أو أكثر تقييدًا من العنصر الذي يتم إلغاؤه.
النوع | أحد الوالدين | طفل |
---|---|---|
نوع القيمة التي يتم إرجاعها | غير مشروح | غير مشروح أو غير فارغ |
نوع القيمة التي يتم إرجاعها | Nullable | يمكن أن تكون القيمة فارغة أو غير فارغة |
نوع القيمة التي يتم إرجاعها | NonNull | NonNull |
وسيطة ممتعة | غير مشروح | غير مشروح أو قابل للقيم الفارغة |
وسيطة ممتعة | Nullable | Nullable |
وسيطة ممتعة | 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
أو غيرها من القيم الشاملة التي تشير إلى حدوث خطأ لا يقدّم للمطوّر معلومات كافية عن عدم تلبية توقعات المستخدمين أو تتبُّع مدى موثوقية التطبيق في بيئة التشغيل بدقة. عند تصميم واجهة برمجة تطبيقات، تخيَّل أنّك بصدد إنشاء تطبيق. فإذا واجهت خطأً، هل تقدّم لك واجهة برمجة التطبيقات معلومات كافية لعرضها للمستخدم أو التفاعل معها بشكل مناسب؟
- لا بأس (بل يُنصح) بتضمين معلومات تفصيلية في رسالة الخطأ، ولكن ليس من المفترض أن يضطر المطوّرون إلى تحليلها للتعامل مع الخطأ بشكل مناسب. يجب عرض رموز الخطأ المفصّلة أو المعلومات الأخرى كطُرق.
- تأكَّد من أنّ خيار معالجة الأخطاء الذي اخترته يمنحك المرونة اللازمة
لإضافة أنواع جديدة من الأخطاء في المستقبل. بالنسبة إلى
@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();
}
تفضيل أنواع Collection على المصفوفات كنوع إرجاع أو نوع مَعلمة
توفّر واجهات المجموعات ذات الأنواع العامة العديد من المزايا مقارنةً بالمصفوفات، بما في ذلك عقود واجهة برمجة التطبيقات الأقوى بشأن التفرد والترتيب، وإتاحة استخدام الأنواع العامة، وعدد من طرق الاستخدام المريحة للمطوّرين.
استثناء للأنواع الأساسية
إذا كانت العناصر بدائية، ننصحك باستخدام المصفوفات بدلاً من ذلك لتجنُّب تكلفة التحويل التلقائي إلى كائنات. راجِع استخدام الأنواع الأساسية غير المغلَّفة بدلاً من الأنواع المغلَّفة
استثناء للرمز البرمجي الذي يتطلّب أداءً عاليًا
في سيناريوهات معيّنة، حيث يتم استخدام واجهة برمجة التطبيقات في رمز برمجي حساس للأداء (مثل الرسومات أو واجهات برمجة التطبيقات الأخرى الخاصة بالقياس أو التنسيق أو الرسم)، من المقبول استخدام المصفوفات بدلاً من المجموعات من أجل تقليل عمليات التخصيص والاضطراب في الذاكرة.
استثناء للغة 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 أنواع الإرجاع القابلة للتغيير تلقائيًا لأنّ تنفيذ واجهات برمجة التطبيقات في Java على نظام Android الأساسي لا يوفّر بعد تنفيذًا مناسبًا للمجموعات غير القابلة للتغيير. الاستثناء من هذه القاعدة هو
أنواع الإرجاع 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 إلى الرمز الثانوي نفسه المستند إلى مصفوفة، ونتيجةً لذلك، يمكن استدعاؤها من رمز 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)
}
}
يجب كتابة دوال التحويل كدوال إضافية
تؤدي كتابة دوال التحويل خارج تعريفات كل من فئة المتلقّي وفئة النتيجة إلى تقليل الربط بين الأنواع. لا تحتاج عملية التحويل المثالية إلا إلى إذن الوصول إلى واجهة برمجة التطبيقات العامة للكائن الأصلي. ويثبت ذلك من خلال المثال أنّ المطوّر يمكنه كتابة عمليات تحويل مماثلة إلى الأنواع المفضّلة لديه أيضًا.
طرح استثناءات محدّدة مناسبة
يجب ألا تعرض الطرق استثناءات عامة، مثل 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()
تسجيل معاودة الاتصال
عندما يمكن إضافة مستمع أو دالة ردّ الاتصال إلى كائن أو إزالته منه، يجب أن يكون اسم الطريقتَين المرتبطتَين هو 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 للتحكّم في إرسال عمليات رد الاتصال
عند تسجيل عمليات ردّ الاتصال التي لا تتضمّن توقعات واضحة بشأن سلاسل المحادثات (في أي مكان تقريبًا خارج مجموعة أدوات واجهة المستخدم)، يُنصح بشدة بتضمين المَعلمة 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 الوارد في جهة عملية التطبيق Binder.clearCallingIdentity()
قبل استدعاء معاودة الاتصال الخاصة بالتطبيق على Executor
التي يوفّرها التطبيق. بهذه الطريقة، سيتم بشكل صحيح ربط أي رمز برمجي للتطبيق يستخدم هوية Binder (مثل 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
كنوع
للدالة عند تحويل طريقة حظر تعرض نتيجة أو تطرح استثناءً إلى طريقة غير حظر وغير متزامنة:
interface FooType {
// Before:
public FooResult requestFoo(FooRequest request);
// After:
public void requestFooAsync(FooRequest request, Executor executor,
OutcomeReceiver<FooResult, Throwable> callback);
}
تُرجع الطرق غير المتزامنة التي يتم تحويلها بهذه الطريقة القيمة void
دائمًا. يتم إرسال أي نتيجة كان من المفترض أن تعرضها requestFoo
إلى requestFooAsync
من خلال callback
OutcomeReceiver.onResult
عن طريق استدعائها على executor
المقدَّمة.
يتم بدلاً من ذلك إرسال أي استثناء قد يطرحه requestFoo
إلى الطريقة OutcomeReceiver.onError
بالطريقة نفسها.
يتيح استخدام OutcomeReceiver
لإعداد تقارير عن نتائج الطرق غير المتزامنة أيضًا إنشاء برنامج تضمين suspend fun
في Kotlin للطرق غير المتزامنة باستخدام الإضافة 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 بدون داعٍ.
ننصحك باستخدام هذه الواجهات العامة بدلاً من إنشاء واجهات جديدة:
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 كما لو كان مسبوقًا بعبارة "تنفّذ هذه الطريقة...".
في الحالات التي لا تتطلّب فيها الطريقة أي مَعلمات ولا تتضمّن أي اعتبارات خاصة، وتعرض ما يشير إليه اسم الطريقة، يمكنك حذف @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
Make هذا الفحص، ولكن قد يكون هدف docs
أسرع إذا كنت ستغيّر Javadoc فقط ولا تحتاج إلى تشغيل هدف update-api
لأي سبب آخر.
استخدِم {@code foo} للتمييز بين قيم Java
استخدِم {@code...}
لتضمين قيم Java، مثل true
وfalse
وnull
، وذلك لتمييزها عن نص المستندات.
عند كتابة مستندات في مصادر 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 ينهي مستندات الملخّص بعد الحرف "ل". على سبيل المثال، اطّلِع على
TEXT_ALIGNMENT_CENTER
فيView.java
. يُرجى العِلم أنّ Metalava يصحّح هذا الخطأ تلقائيًا من خلال إدراج مسافة غير فاصلة بعد النقطة، ولكن يُفضّل عدم الوقوع في هذا الخطأ من البداية.
تنسيق المستندات ليتم عرضها بتنسيق HTML
يتم عرض Javadoc بتنسيق HTML، لذا يجب تنسيق هذه المستندات وفقًا لذلك:
يجب أن تستخدم فواصل الأسطر علامة
<p>
صريحة. لا تُضِف علامة إغلاق</p>
.لا تستخدِم ASCII لعرض القوائم أو الجداول.
يجب أن تستخدم القوائم
<ul>
أو<ol>
للقوائم غير المرتبة والمرتبة على التوالي. يجب أن يبدأ كل عنصر بعلامة<li>
، ولكن ليس من الضروري أن يتضمّن علامة إغلاق</li>
. يجب إضافة علامة إغلاق</ul>
أو</ol>
بعد آخر عنصر.يجب أن تستخدم الجداول
<table>
و<tr>
للصفوف و<th>
للعناوين و<td>
للخلايا. تتطلّب جميع علامات الجدول علامات إغلاق مطابقة. يمكنك استخدامclass="deprecated"
على أي علامة للإشارة إلى الإيقاف النهائي.لإنشاء خط رمز مضمّن، استخدِم
{@code foo}
.لإنشاء فقرات رمزية، استخدِم
<pre>
.يحلّل المتصفّح كل النص داخل كتلة
<pre>
، لذا يجب توخّي الحذر بشأن الأقواس<>
. يمكنك إلغاء تأثيرها باستخدام كيانَي HTML <
و>
.بدلاً من ذلك، يمكنك ترك الأقواس المجردة
<>
في مقتطف الرمز إذا كنت تضمّن الأقسام المخالفة في{@code foo}
. مثلاً:<pre>{@code <manifest>}</pre>
اتّبِع دليل أسلوب مرجع واجهة برمجة التطبيقات
لضمان اتّساق الأسلوب في ملخّصات الصفوف وأوصاف الطرق وأوصاف المَعلمات والعناصر الأخرى، اتّبِع الاقتراحات الواردة في إرشادات لغة Java الرسمية على كيفية كتابة تعليقات مستندات لأداة Javadoc.
القواعد الخاصة بإطار عمل Android
تتعلّق هذه القواعد بواجهات برمجة التطبيقات والأنماط وبُنى البيانات الخاصة بواجهات برمجة التطبيقات والسلوكيات المضمّنة في إطار عمل Android (على سبيل المثال، Bundle
أو Parcelable
).
يجب أن تستخدم أدوات إنشاء الأهداف النمط create*Intent()
يجب أن تستخدم الفئات الخاصة بالأهداف طرقًا تحمل الاسم createFooIntent()
.
استخدام Bundle بدلاً من إنشاء بنى بيانات جديدة للأغراض العامة
تجنَّب إنشاء بنى بيانات جديدة للأغراض العامة لتمثيل عمليات ربط عشوائية بين المفتاح والقيمة المكتوبة. بدلاً من ذلك، يمكنك استخدام Bundle
.
يحدث ذلك عادةً عند كتابة واجهات برمجة تطبيقات للنظام الأساسي تعمل كقنوات اتصال بين التطبيقات والخدمات غير التابعة للنظام الأساسي، حيث لا يقرأ النظام الأساسي البيانات المرسَلة عبر القناة، وقد يتم تحديد عقد واجهة برمجة التطبيقات جزئيًا خارج النظام الأساسي (على سبيل المثال، في مكتبة Jetpack).
في الحالات التي تقرأ فيها المنصة البيانات فعلاً، تجنَّب استخدام Bundle
واستخدِم فئة بيانات ذات نوع محدد.
يجب أن تتضمّن عمليات تنفيذ Parcelable الحقل CREATOR العلني
يتم عرض عملية إنشاء Parcelable من خلال CREATOR
، وليس من خلال طرق الإنشاء الأولية. إذا كانت الفئة تنفّذ Parcelable
، يجب أن يكون الحقل CREATOR
أيضًا واجهة برمجة تطبيقات عامة، ويجب أن يكون منشئ الفئة الذي يتلقّى وسيطة Parcel
خاصًا.
استخدام CharSequence لسلاسل واجهة المستخدم
عند عرض سلسلة في واجهة مستخدم، استخدِم CharSequence
للسماح بحالات Spannable
.
إذا كان مجرد مفتاح أو تصنيف أو قيمة أخرى غير مرئية للمستخدمين،
String
لا بأس بذلك.
تجنُّب استخدام التعدادات
يجب استخدام IntDef
بدلاً من التعدادات في جميع واجهات برمجة التطبيقات الخاصة بالمنصة، ويجب التفكير مليًا في استخدامها في واجهات برمجة التطبيقات غير المجمَّعة والمكتبات. استخدِم التعدادات فقط عندما تكون متأكّدًا من أنّه لن تتم إضافة قيم جديدة.
مزاياIntDef
:
- تتيح إضافة قيم بمرور الوقت
- يمكن أن تفشل عبارات
when
في Kotlin أثناء وقت التشغيل إذا أصبحت غير شاملة بسبب إضافة قيمة تعداد في النظام الأساسي.
- يمكن أن تفشل عبارات
- لا يتم استخدام أي فئات أو عناصر في وقت التشغيل، فقط الأنواع الأساسية
- على الرغم من أنّ R8 أو التصغير يمكن أن يتجنّب هذه التكلفة لواجهات برمجة التطبيقات غير المجمَّعة، لا يمكن أن يؤثر هذا التحسين في فئات واجهة برمجة التطبيقات الخاصة بالمنصة.
مزايا التعداد
- ميزة اللغة الاصطلاحية في Java وKotlin
- تفعيل الاستخدام الشامل لعبارة
when
switch- ملاحظة: يجب ألّا تتغيّر القيم بمرور الوقت، راجِع القائمة السابقة
- تسمية واضحة النطاق وقابلة للاكتشاف
- تتيح هذه السمة التحقّق من صحة البيانات في وقت الترجمة البرمجية.
- على سبيل المثال، عبارة
when
في Kotlin تعرض قيمة
- على سبيل المثال، عبارة
- هي فئة تعمل ويمكنها تنفيذ الواجهات، وتتضمّن أدوات مساعدة ثابتة، وتعرض طرقًا للأعضاء أو الإضافات، وتعرض الحقول.
اتّباع التسلسل الهرمي لطبقات حِزم Android
يتضمّن التسلسل الهرمي للحزمة android.*
ترتيبًا ضمنيًا، حيث لا يمكن أن تعتمد الحِزم ذات المستوى الأدنى على الحِزم ذات المستوى الأعلى.
تجنَّب الإشارة إلى Google والشركات الأخرى ومنتجاتها
منصة Android هي مشروع مفتوح المصدر وتهدف إلى أن تكون محايدة للمورّدين. يجب أن تكون واجهة برمجة التطبيقات عامة ويمكن لمدمجي الأنظمة أو التطبيقات التي لديها الأذونات المطلوبة استخدامها بشكل متساوٍ.
يجب أن تكون عمليات تنفيذ Parcelable نهائية
يتم دائمًا تحميل فئات Parcelable التي يحدّدها النظام الأساسي من framework.jar
، لذا لا يمكن لأي تطبيق محاولة إلغاء تنفيذ Parcelable
.
إذا كان تطبيق الإرسال يوسّع Parcelable
، لن يتضمّن تطبيق الاستلام التنفيذ المخصّص للمرسِل الذي يمكن استخدامه لفك الحزمة. ملاحظة حول التوافق مع الإصدارات السابقة: إذا لم تكن فئتك نهائية في السابق، ولكن لم يكن لديها دالة إنشاء متاحة للجميع، سيظل بإمكانك وضع العلامة final
عليها.
يجب أن تعيد الطرق التي تستدعي عملية النظام طرح RemoteException كـ RuntimeException
يتم عادةً عرض الخطأ RemoteException
من خلال AIDL الداخلي، ويشير إلى أنّ عملية النظام قد توقّفت أو أنّ التطبيق يحاول إرسال الكثير من البيانات. في كلتا الحالتين، يجب أن تعيد واجهة برمجة التطبيقات العامة طرح الاستثناء RuntimeException
لمنع التطبيقات من الاحتفاظ بقرارات الأمان أو السياسة.
إذا كنت تعرف أنّ الجانب الآخر من عملية استدعاء Binder
هو عملية النظام، يكون الرمز النموذجي التالي هو أفضل ممارسة:
try {
...
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
إصدار استثناءات محدّدة للتغييرات في واجهة برمجة التطبيقات
قد تتغير سلوكيات واجهات برمجة التطبيقات العامة بين مستويات واجهات برمجة التطبيقات المختلفة، ما يؤدي إلى تعطُّل التطبيق (على سبيل المثال، لفرض سياسات أمان جديدة).
عندما تحتاج واجهة برمجة التطبيقات إلى طرح استثناء لطلب كان صالحًا في السابق، يجب طرح استثناء جديد ومحدّد بدلاً من استثناء عام. على سبيل المثال، ExportedFlagRequired
بدلاً من SecurityException
(ويمكن أن يمتد ExportedFlagRequired
إلى SecurityException
).
سيساعد ذلك مطوّري التطبيقات والأدوات في رصد التغييرات في سلوك واجهة برمجة التطبيقات.
تنفيذ الدالة الإنشائية للنسخ بدلاً من الاستنساخ
ننصح بشدة بعدم استخدام طريقة clone()
في Java بسبب عدم توفّر عقود واجهة برمجة التطبيقات التي توفّرها الفئة 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
لأنّه صارم جدًا في تحليل معرّفات الموارد الموحّدة، ولا تستخدم 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 {}
عند إنشاء حزمة تطوير البرامج (SDK) للمنصة وملفات AAR الخاصة بالمكتبة، تستخرج إحدى الأدوات التعليقات التوضيحية وتجمّعها بشكل منفصل عن المصادر المجمَّعة. يقرأ "استوديو Android" هذا التنسيق المجمَّع ويفرض تعريفات الأنواع.
عدم إضافة مفاتيح جديدة لموفّر الإعدادات
لا تعرض مفاتيح جديدة من
Settings.Global
أو
Settings.System
أو
Settings.Secure
.
بدلاً من ذلك، أضِف واجهة برمجة تطبيقات Java مناسبة للحصول على البيانات وتعديلها في فئة ذات صلة، وهي عادةً فئة "إدارة". أضِف آلية استماع أو بث لإعلام العملاء بالتغييرات حسب الحاجة.
تتضمّن إعدادات SettingsProvider
عددًا من المشاكل مقارنةً بوظائف getter/setter:
- لا يتوفّر منع أخطاء الكتابة.
- لا توجد طريقة موحّدة لتقديم قيمة تلقائية.
- لا توجد طريقة مناسبة لتخصيص الأذونات.
- على سبيل المثال، لا يمكن حماية إعداداتك بإذن مخصّص.
- لا توجد طريقة مناسبة لإضافة منطق مخصّص بشكل صحيح.
- على سبيل المثال، لا يمكن تغيير قيمة الإعداد A استنادًا إلى قيمة الإعداد B.
مثال:
كانت السمة Settings.Secure.LOCATION_MODE
متاحة منذ فترة طويلة، ولكن أوقف فريق الموقع الجغرافي إتاحتها واستبدلها بواجهة برمجة تطبيقات Java
LocationManager.isLocationEnabled()
وMODE_CHANGED_ACTION
التي تتيح للفريق مرونة أكبر، وأصبحت دلالات واجهات برمجة التطبيقات أكثر وضوحًا الآن.
عدم توسيع Activity وAsyncTask
AsyncTask
هي تفاصيل التنفيذ. بدلاً من ذلك، يمكنك عرض أداة معالجة أو واجهة برمجة تطبيقات ListenableFuture
في androidx.
لا يمكن إنشاء فئات فرعية Activity
. يؤدي تمديد مدة النشاط لميزتك إلى جعلها غير متوافقة مع الميزات الأخرى التي تتطلب من المستخدمين تنفيذ الإجراء نفسه. بدلاً من ذلك، اعتمد على التركيب باستخدام أدوات مثل
LifecycleObserver.
استخدام Context.getUser()
يجب أن تستخدم الفئات المرتبطة بـ Context
، مثل أي شيء يتم عرضه من Context.getSystemService()
، المستخدم المرتبط بـ Context
بدلاً من عرض الأعضاء الذين يستهدفون مستخدمين محدّدين.
class FooManager {
Context mContext;
void fooBar() {
mIFooBar.fooBarForUser(mContext.getUser());
}
}
class FooManager {
Context mContext;
Foobar getFoobar() {
// Bad: doesn't appy mContext.getUserId().
mIFooBar.fooBarForUser(Process.myUserHandle());
}
Foobar getFoobar() {
// Also bad: doesn't appy mContext.getUserId().
mIFooBar.fooBar();
}
Foobar getFoobarForUser(UserHandle user) {
mIFooBar.fooBarForUser(user);
}
}
الاستثناء: قد تقبل إحدى الطرق وسيطة مستخدم إذا كانت تقبل قيمًا لا تمثّل مستخدمًا واحدًا، مثل UserHandle.ALL
.
استخدام UserHandle بدلاً من الأعداد الصحيحة العادية
يُفضَّل استخدام UserHandle
لتوفير أمان الأنواع وتجنُّب الخلط بين معرّفات المستخدمين ومعرّفات UID.
Foobar getFoobarForUser(UserHandle user);
Foobar getFoobarForUser(int userId);
في الحالات التي لا يمكن تجنّبها، يجب إضافة التعليق التوضيحي @UserIdInt
إلى int
الذي يمثّل معرّف مستخدم.
Foobar getFoobarForUser(@UserIdInt int user);
تفضيل أدوات الاستماع أو عمليات رد الاتصال لبث الأهداف
تُعدّ أغراض البث قوية جدًا، ولكنها تؤدي إلى سلوكيات طارئة يمكن أن تؤثر سلبًا في سلامة النظام، لذا يجب إضافة أغراض البث الجديدة بحذر.
في ما يلي بعض المخاوف المحدّدة التي تجعلنا لا نشجّع على تقديم أهداف بث جديدة:
عند إرسال عمليات بث بدون العلامة
FLAG_RECEIVER_REGISTERED_ONLY
، يتم فرض بدء أي تطبيقات غير قيد التشغيل حاليًا. على الرغم من أنّ هذا قد يكون أحيانًا نتيجة مقصودة، إلا أنّه يمكن أن يؤدي إلى إغلاق عشرات التطبيقات بشكل متزامن، ما يؤثر سلبًا في سلامة النظام. ننصحك باستخدام استراتيجيات بديلة، مثلJobScheduler
، لتنسيق أفضل عند استيفاء الشروط المسبقة المختلفة.عند إرسال عمليات بث، لا تتوفّر إمكانية كبيرة لفلترة المحتوى أو تعديله قبل تسليمه إلى التطبيقات. ويصعّب ذلك أو يستحيل الاستجابة لأي مخاوف بشأن الخصوصية في المستقبل أو إجراء تغييرات في السلوك استنادًا إلى حزمة SDK المستهدَفة للتطبيق المستلِم.
بما أنّ قوائم انتظار البث هي مورد مشترك، يمكن أن تصبح مثقلة وقد لا تؤدي إلى تسليم الحدث في الوقت المناسب. لقد رصدنا عدة قوائم انتظار للبث المباشر في بيئات حقيقية، وقد بلغ وقت الاستجابة فيها 10 دقائق أو أكثر.
لهذه الأسباب، ننصح الميزات الجديدة باستخدام أدوات معالجة أو عمليات ردّ الاتصال أو تسهيلات أخرى، مثل JobScheduler
، بدلاً من استخدام أغراض البث.
في الحالات التي تظل فيها أغراض البث هي التصميم المثالي، إليك بعض أفضل الممارسات التي يجب مراعاتها:
- استخدِم
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 من المنصة - ننصحك بشدة بإضافة إذن على مستوى النظام لمنع التطبيقات الضارة من إرسال
Intent
إلى خدمات المطوّرين.
إمكانية التشغيل التفاعلي بين Kotlin وJava
يمكنك الاطّلاع على دليل التوافق بين Kotlin وJava الرسمي لنظام Android للحصول على قائمة كاملة بالإرشادات. تم نسخ إرشادات محدّدة إلى هذا الدليل لتحسين قابلية العثور عليه.
إمكانية الوصول إلى واجهة برمجة التطبيقات
بعض واجهات برمجة تطبيقات Kotlin، مثل suspend fun
s، غير مخصّصة للاستخدام من قِبل مطوّري Java، ولكن لا تحاول التحكّم في إمكانية الوصول الخاصة بلغة معيّنة باستخدام @JvmSynthetic
لأنّ ذلك يؤدي إلى آثار جانبية على طريقة عرض واجهة برمجة التطبيقات في أدوات تصحيح الأخطاء، ما يجعل عملية تصحيح الأخطاء أكثر صعوبة.
راجِع دليل التوافق بين Kotlin وJava أو دليل Async للحصول على إرشادات محدّدة.
الكائنات المصاحبة
تستخدم لغة Kotlin الرمز companion object
لعرض الأعضاء الثابتين. في بعض الحالات، ستظهر هذه السمة من Java في فئة داخلية باسم 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 للحصول على شرح تفصيلي لأنواع التغييرات المتوافقة في واجهات برمجة التطبيقات المستندة إلى Java. يجب أن تتبع التغييرات التي تؤدي إلى عدم توافق الرمز الثنائي في واجهات برمجة التطبيقات المخفية (مثل واجهات برمجة التطبيقات الخاصة بالنظام) دورة الإيقاف نهائيًا/الاستبدال.
التغييرات التي تؤدي إلى إيقاف التوافق مع الإصدارات السابقة
ننصح بعدم إجراء تغييرات تؤدي إلى حدوث أخطاء في التعليمات البرمجية المصدر حتى إذا لم تؤدِّ إلى حدوث أخطاء في التعليمات البرمجية الثنائية. أحد الأمثلة على التغييرات المتوافقة مع الرمز الثنائي ولكنها تؤدي إلى حدوث مشاكل في الرمز المصدر هو إضافة نوع عام إلى فئة حالية، ما يؤدي إلى توافق الرمز الثنائي ولكن يمكن أن يؤدي إلى حدوث أخطاء في التجميع بسبب الوراثة أو المراجع الغامضة.
لن تؤدي التغييرات التي تتسبب في حدوث أخطاء في رمز المصدر إلى ظهور أخطاء عند تشغيل make update-api
، لذا
عليك الحرص على فهم تأثير التغييرات في تواقيع واجهة برمجة التطبيقات الحالية.
في بعض الحالات، تصبح التغييرات غير المتوافقة مع الإصدارات السابقة ضرورية لتحسين تجربة المطوّرين أو صحة الرمز البرمجي. على سبيل المثال، تؤدي إضافة تعليقات توضيحية بشأن إمكانية قبول القيم الخالية إلى مصادر Java إلى تحسين إمكانية التشغيل التفاعلي مع رمز Kotlin وتقليل احتمالية حدوث أخطاء، ولكنها تتطلب غالبًا إجراء تغييرات، وأحيانًا تغييرات كبيرة، على الرمز المصدري.
تغييرات على واجهات برمجة التطبيقات الخاصة
يمكنك تغيير واجهات برمجة التطبيقات التي تمّت إضافة التعليق التوضيحي @TestApi
إليها في أي وقت.
يجب الاحتفاظ بواجهات برمجة التطبيقات التي تمّت إضافة التعليق التوضيحي @SystemApi
إليها لمدة ثلاث سنوات. يجب إزالة واجهة برمجة تطبيقات النظام أو إعادة هيكلتها وفقًا للجدول الزمني التالي:
- API y - Added
- API y+1 - الإيقاف
- ضَع علامة
@Deprecated
على الرمز. - أضِف بدائل، وأنشئ رابطًا إلى البديل في Javadoc للرمز البرمجي المتوقّف نهائيًا باستخدام التعليق التوضيحي
@deprecated
الخاص بالمستندات. - أثناء دورة التطوير، أبلِغ المستخدمين الداخليين عن الأخطاء في واجهة برمجة التطبيقات وأخبِرهم بأنّه سيتم إيقافها نهائيًا. يساعد ذلك في التأكّد من أنّ واجهات برمجة التطبيقات البديلة مناسبة.
- ضَع علامة
- مستوى واجهة برمجة التطبيقات y+2 - الإزالة غير القسرية
- ضَع علامة
@removed
على الرمز. - يمكنك اختياريًا إيقاف التطبيقات التي تستهدف مستوى حزمة تطوير البرامج (SDK) الحالي للإصدار أو عدم إجراء أي عملية.
- ضَع علامة
- API y+3 - إزالة نهائية
- أزِل الرمز بالكامل من شجرة المصدر.
الإيقاف النهائي
نعتبر الإيقاف النهائي تغييرًا في واجهة برمجة التطبيقات، ويمكن أن يحدث في إصدار رئيسي (مثل إصدار يتضمّن حرفًا). استخدِم التعليق التوضيحي للمصدر @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
إشعارًا بشأن الإيقاف النهائي. - تعرض بيئات التطوير المتكاملة، مثل "استوديو Android"، تحذيرًا في الموقع الذي يتم فيه استخدام واجهة برمجة التطبيقات.
- قد تخفض بيئات التطوير المتكاملة ترتيب واجهة برمجة التطبيقات أو تخفيها من ميزة الإكمال التلقائي.
ونتيجةً لذلك، قد يؤدي إيقاف إحدى واجهات برمجة التطبيقات نهائيًا إلى تثبيط المطوّرين الذين يهتمون أكثر من غيرهم بسلامة الرمز البرمجي (أي الذين يستخدمون -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;
التغييرات على واجهات برمجة التطبيقات المتوقفة نهائيًا
يجب الحفاظ على سلوك واجهات برمجة التطبيقات المتوقّفة نهائيًا. وهذا يعني أنّه يجب أن تظل عمليات تنفيذ الاختبار كما هي، ويجب أن تستمر الاختبارات في النجاح بعد إيقاف واجهة برمجة التطبيقات نهائيًا. إذا لم تتضمّن واجهة برمجة التطبيقات اختبارات، عليك إضافة اختبارات.
لا توسّع مساحات واجهات برمجة التطبيقات المتوقّفة نهائيًا في الإصدارات المستقبلية. يمكنك إضافة تعليقات توضيحية بشأن صحة lint (مثل @Nullable
) إلى واجهة برمجة تطبيقات حالية تم إيقافها نهائيًا، ولكن يجب عدم إضافة واجهات برمجة تطبيقات جديدة.
لا تُضِف واجهات برمجة تطبيقات جديدة على أنّها متوقّفة نهائيًا. إذا تمت إضافة أي واجهات برمجة تطبيقات ثم تم إيقافها نهائيًا خلال دورة إصدار تجريبي (وبالتالي ستدخل مبدئيًا إلى مساحة واجهة برمجة التطبيقات المتاحة للجميع على أنّها متوقّفة نهائيًا)، عليك إزالتها قبل الانتهاء من واجهة برمجة التطبيقات.
تمّت الإزالة مبدئيًا
الإزالة غير الكاملة هي تغيير يؤدي إلى حدوث مشاكل في المصدر، ويجب تجنُّبها في واجهات برمجة التطبيقات العامة
إلا إذا وافق مجلس واجهات برمجة التطبيقات على ذلك صراحةً.
بالنسبة إلى واجهات برمجة التطبيقات الخاصة بالنظام، يجب إيقاف واجهة برمجة التطبيقات خلال مدة الإصدار الرئيسي قبل إزالتها بشكل غير نهائي. إزالة جميع الإشارات إلى واجهات برمجة التطبيقات في المستندات واستخدام تعليق توضيحي @removed <summary>
في المستندات عند إزالة واجهات برمجة التطبيقات بشكل مؤقت يجب أن يتضمّن الملخّص سبب الإزالة ويمكن أن يتضمّن استراتيجية نقل البيانات، كما أوضحنا في مقالة الإيقاف نهائيًا.
يمكن الحفاظ على سلوك واجهات برمجة التطبيقات التي تمت إزالتها بشكل غير نهائي كما هو، ولكن الأهم من ذلك هو يجب الحفاظ على هذا السلوك لكي لا يتعطّل المتصلون الحاليون عند استدعاء واجهة برمجة التطبيقات. وفي بعض الحالات، قد يعني ذلك الحفاظ على السلوك.
يجب الحفاظ على تغطية الاختبار ، ولكن قد يلزم تغيير محتوى الاختبارات لاستيعاب التغييرات السلوكية. يجب أن تستمر الاختبارات في التحقّق من أنّ المتصلين الحاليين لا يتعطّلون أثناء وقت التشغيل. يمكنك الحفاظ على سلوك واجهات برمجة التطبيقات التي تمت إزالتها بشكل غير نهائي كما هو، ولكن الأهم من ذلك، يجب الحفاظ على هذا السلوك بطريقة لا تؤدي إلى تعطُّل التطبيقات الحالية عند طلب واجهة برمجة التطبيقات. في بعض الحالات، قد يعني ذلك الحفاظ على السلوك.
يجب الحفاظ على تغطية الاختبار، ولكن قد تحتاج إلى تغيير محتوى الاختبارات لاستيعاب التغييرات السلوكية. يجب أن تستمر الاختبارات في التحقّق من أنّ المتصلين الحاليين لا يتعطّلون أثناء وقت التشغيل.
على المستوى الفني، نزيل واجهة برمجة التطبيقات من ملف JAR الخاص ببرنامج التجميع الأولي لحزمة تطوير البرامج (SDK) ومن مسار الفئة في وقت التجميع باستخدام التعليق التوضيحي @remove
Javadoc، ولكنها تظل متاحة في مسار الفئة في وقت التشغيل، على غرار واجهات برمجة التطبيقات @hide
:
/**
* Ringer volume. This is ...
*
* @removed Not functional since API 2.
*/
public static final String VOLUME_RING = ...
من منظور مطوّر التطبيقات، لن تظهر واجهة برمجة التطبيقات بعد ذلك في ميزة الإكمال التلقائي، ولن يتم تجميع الرمز المصدر الذي يشير إلى واجهة برمجة التطبيقات عندما يكون compileSdk
مساويًا أو أحدث من حزمة SDK التي تمت إزالة واجهة برمجة التطبيقات منها. ومع ذلك، سيستمر تجميع الرمز المصدر بنجاح مع حِزم SDK السابقة وستستمر عمل الملفات الثنائية التي تشير إلى واجهة برمجة التطبيقات.
يجب عدم إزالة فئات معيّنة من واجهة برمجة التطبيقات بشكلٍ مؤقت. يجب عدم إزالة فئات معيّنة من واجهة برمجة التطبيقات بشكل غير نهائي.
الطُرق المجردة
يجب عدم إزالة الطرق المجردة بشكل غير نهائي من الفئات التي قد يوسّعها المطوّرون. ويؤدي ذلك إلى استحالة توسيع المطوّرين للفئة بنجاح على جميع مستويات حزمة 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
}
}
}
يتيح تصميم "إطار عمل توافق التطبيقات" هذا للمطوّرين إيقاف تغييرات معيّنة في السلوك مؤقتًا أثناء إصدارات المعاينة والإصدارات التجريبية كجزء من عملية تصحيح أخطاء تطبيقاتهم، بدلاً من إجبارهم على التكيّف مع عشرات التغييرات في السلوك في الوقت نفسه.
التوافق مع الإصدارات الأحدث
التوافق مع الإصدارات الأحدث هو إحدى خصائص التصميم التي تتيح للنظام قبول مدخلات مخصّصة لإصدار أحدث منه. في ما يتعلق بتصميم واجهات برمجة التطبيقات، يجب الانتباه بشكل خاص إلى التصميم الأولي والتغييرات المستقبلية، لأنّ المطوّرين يتوقّعون كتابة الرمز البرمجي مرة واحدة واختباره مرة واحدة وتشغيله في كل مكان بدون أي مشاكل.
تتسبّب العناصر التالية في حدوث مشاكل التوافق مع الإصدارات الأحدث الأكثر شيوعًا في Android:
- إضافة ثوابت جديدة إلى مجموعة (مثل
@IntDef
أوenum
) كان يُفترض سابقًا أنّها مكتملة (على سبيل المثال، عندما يكونswitch
يحتوي علىdefault
يعرض استثناءً). - إضافة دعم لميزة لا يتم تسجيلها مباشرةً في مساحة عرض واجهة برمجة التطبيقات (على سبيل المثال، إتاحة إسناد موارد من النوع
ColorStateList
في XML، في حين كان يتم إتاحة موارد من النوع<color>
فقط في السابق) - تخفيف القيود المفروضة على عمليات التحقّق أثناء التشغيل، مثل إزالة عملية التحقّق من
requireNotNull()
التي كانت متوفّرة في الإصدارات الأقدم
في كل هذه الحالات، لا يكتشف المطوّرون وجود خطأ إلا في وقت التشغيل. والأسوأ من ذلك، قد يكتشفون ذلك نتيجة لتقارير الأعطال الواردة من الأجهزة القديمة في السوق.
بالإضافة إلى ذلك، تُعدّ جميع حالات التغيير هذه صالحة من الناحية الفنية. ولا تؤدي إلى حدوث مشاكل في التوافق الثنائي أو توافق المصدر، ولن يرصدها مدقق API.
ونتيجةً لذلك، يجب أن يولي مصمّمو واجهات برمجة التطبيقات اهتمامًا دقيقًا عند تعديل الفئات الحالية. اطرح السؤال التالي: "هل سيؤدي هذا التغيير إلى تعذُّر تشغيل الرمز الذي تمت كتابته واختباره فقط على أحدث إصدار من النظام الأساسي على الإصدارات الأقدم؟"
مخططات XML
إذا كان مخطط XML يعمل كواجهة ثابتة بين المكوّنات، يجب تحديد هذا المخطط بشكل صريح ويجب أن يتطوّر بطريقة متوافقة مع الإصدارات السابقة، على غرار واجهات برمجة التطبيقات الأخرى في Android. على سبيل المثال، يجب الحفاظ على بنية عناصر XML وسماتها بشكل مشابه لطريقة الحفاظ على الطرق والمتغيرات في مساحات واجهات برمجة التطبيقات الأخرى لنظام Android.
إيقاف 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 بشكل فردي، بدلاً من تحديث صورة النظام بأكملها.
يجب "فصل" وحدات Mainline عن النظام الأساسي، ما يعني أنّه يجب إجراء جميع التفاعلات بين كل وحدة وبقية العالم باستخدام واجهات برمجة التطبيقات الرسمية (العامة أو الخاصة بالنظام).
هناك أنماط تصميم معيّنة يجب أن تتّبعها الوحدات الرئيسية. يوضّح هذا القسم هذه الحالات.
نمط <Module>FrameworkInitializer
إذا كانت إحدى الوحدات الرئيسية بحاجة إلى عرض فئات @SystemService
(على سبيل المثال،
JobScheduler
)، استخدِم النمط التالي:
عرض فئة
<YourModule>FrameworkInitializer
من الوحدة يجب أن تكون هذه الفئة ضمن$BOOTCLASSPATH
. مثال: StatsFrameworkInitializerضَع علامة
@SystemApi(client = MODULE_LIBRARIES)
عليه.أضِف
public static void registerServiceWrappers()
طريقة إليها.استخدِم
SystemServiceRegistry.registerContextAwareService()
لتسجيل فئة مدير خدمة عندما تحتاج إلى مرجع إلىContext
.استخدِم
SystemServiceRegistry.registerStaticService()
لتسجيل فئة مدير خدمة عندما لا تحتاج إلى مرجع إلىContext
.استدعِ الإجراء
registerServiceWrappers()
من أداة التهيئة الثابتةSystemServiceRegistry
.
نمط <Module>ServiceManager
في العادة، لتسجيل عناصر ربط خدمة النظام أو الحصول على مراجع لها، يتم استخدام ServiceManager
، ولكن لا يمكن للوحدات الرئيسية استخدامها لأنّها مخفية. هذه الفئة مخفية لأنّه من المفترض ألا تسجّل الوحدات الرئيسية أو تشير إلى عناصر binder الخاصة بخدمة النظام التي تعرضها المنصة الثابتة أو الوحدات الأخرى.
يمكن أن تستخدم وحدات Mainline النمط التالي بدلاً من ذلك لتتمكّن من التسجيل والحصول على مراجع لخدمات Binder التي يتم تنفيذها داخل الوحدة.
أنشئ فئة
<YourModule>ServiceManager
باتّباع تصميم TelephonyServiceManagerعرض الفئة على أنّها
@SystemApi
إذا كنت بحاجة إلى الوصول إلى هذا الحقل من فئات$BOOTCLASSPATH
أو فئات خادم النظام فقط، يمكنك استخدام@SystemApi(client = MODULE_LIBRARIES)
، وإلا سيعمل@SystemApi(client = PRIVILEGED_APPS)
.سيتألف هذا الصف من:
- دالة إنشاء مخفية، وبالتالي لا يمكن لرمز النظام الأساسي الثابت إنشاء مثيل لها.
- طُرق getter العامة التي تعرض مثيلاً من
ServiceRegisterer
لاسم معيّن. إذا كان لديك عنصر ربط واحد، ستحتاج إلى طريقة getter واحدة. إذا كان لديك سمتان، يجب أن يكون لديك طريقتان للحصول على القيم. - في
ActivityThread.initializeMainlineModules()
، أنشئ مثيلاً لهذه الفئة، ومرِّره إلى طريقة ثابتة يعرضها وحدة الترميز. عادةً، يمكنك إضافة واجهة برمجة تطبيقات ثابتة@SystemApi(client = MODULE_LIBRARIES)
في فئةFrameworkInitializer
التي تستخدمها.
سيمنع هذا النمط الوحدات الرئيسية الأخرى من الوصول إلى واجهات برمجة التطبيقات هذه، لأنّه ليس هناك طريقة يمكن من خلالها للوحدات الأخرى الحصول على مثيل من <YourModule>ServiceManager
، على الرغم من أنّ واجهتَي برمجة التطبيقات get()
وregister()
مرئيتان لها.
في ما يلي كيفية حصول الاتصال الهاتفي على مرجع لخدمة الاتصال الهاتفي: رابط البحث عن الرمز.
إذا كان تطبيقك ينفّذ عنصرًا رابطًا للخدمة في الرمز البرمجي الأصلي، يمكنك استخدام
واجهات برمجة التطبيقات الأصلية AServiceManager
.
تتطابق واجهات برمجة التطبيقات هذه مع واجهات برمجة التطبيقات ServiceManager
Java، ولكن يتم عرض واجهات برمجة التطبيقات الأصلية مباشرةً للوحدات الرئيسية. لا تستخدِمها لتسجيل أو الإشارة إلى عناصر binder غير مملوكة للوحدة. إذا كنت تعرض كائنًا من نوع Binder من الرمز البرمجي الأصلي، لن تحتاج السمة <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
.