تهدف هذه الصفحة إلى تقديم دليل للمطوّرين لفهم المبادئ العامة التي يفرضها مجلس واجهات برمجة التطبيقات في مراجعات واجهات برمجة التطبيقات.
بالإضافة إلى اتّباع هذه الإرشادات عند كتابة واجهات برمجة التطبيقات، على المطوّرين تشغيل أداة 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) كالتفاصيل التنفيذية ويشمل ذلك 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
}
}
}
يختلف الإصدار الفردي عن singleton في أنّه يمكن للمطوّرين إنشاء إصدار زائف من 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 حول علامات قناع البت.
يجب أن تستخدم الثوابت النهائية الثابتة أسلوب التسمية الذي يتضمّن جميع الأحرف الكبيرة والفواصل السفلية
يجب كتابة جميع الكلمات في الثابت بأحرف كبيرة، ويجب الفصل بين الكلمات المتعددة باستخدام _. على سبيل المثال:
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);
راجِع أيضًا: وضع المَعلمات الاختيارية في نهاية عمليات التحميل الزائد
الجهات المنشئة
يُنصح باستخدام نمط 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 ربط الطرق من خلال عرض كائن 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 إلى بداية اسم طريقة الحصول على القيمة، بل سيتم استخدام اسم السمة نفسه كطريقة الحصول على القيمة.
لذلك، يُفضّل تسمية 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
Bitmask accessors
راجِع استخدام @IntDef لعلامات bitmask للاطّلاع على إرشادات واجهة برمجة التطبيقات بشأن تحديد علامات bitmask.
Setters
يجب توفير طريقتَي تحديد قيمة: إحداهما تأخذ سلسلة بت كاملة وتستبدل جميع العلامات الحالية، والأخرى تأخذ قناع بت مخصّصًا للسماح بمزيد من المرونة.
/**
* 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();
}
يجب أن تقبل الطرق التي تقبل عناصر الملفات أيضًا عمليات البث
لا تكون مواقع تخزين البيانات على 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 الأساسي، سيؤدي وضع علامات توضيحية على مَعلمات الطريقة إلى إنشاء مستندات تلقائيًا بالتنسيق "قد تكون هذه القيمة فارغة"، ما لم يتم استخدام "قيمة فارغة" بشكل صريح في مكان آخر في مستند المَعلمة.
الطُرق الحالية "غير القابلة للتصغير": يمكن إضافة التعليق التوضيحي @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()، تعرض عملية التحويل "إلى" عنصرًا جديدًا ومستقلاً. إذا تم تعديل الكائن الأصلي لاحقًا، لن يعكس الكائن الجديد هذه التغييرات.
وبالمثل، إذا تم تعديل العنصر new لاحقًا، لن يعكس العنصر old هذه التغييرات.
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()) لعمليات التحقّق من الأذونات بالتطبيق وليس بعملية النظام التي تستدعي التطبيق. وإذا أراد مستخدمو واجهة برمجة التطبيقات الحصول على معلومات معرّف المستخدم أو معرّف العملية الخاصين بالمتصل، يجب أن يكون ذلك جزءًا صريحًا من مساحة واجهة برمجة التطبيقات، وليس ضمنيًا استنادًا إلى المكان الذي تم فيه تنفيذ Executor الذي قدّمه المستخدم.
يجب أن تتيح واجهة برمجة التطبيقات تحديد Executor . في الحالات التي تتطلّب أداءً عاليًا، قد تحتاج التطبيقات إلى تنفيذ الرمز البرمجي إما على الفور أو بشكل متزامن مع تلقّي ملاحظات من واجهة برمجة التطبيقات. ويسمح قبول Executor بذلك.
إنّ إنشاء HandlerThread إضافي أو مشابه لـ trampoline بشكل دفاعي يؤدي إلى إبطال حالة الاستخدام المرغوبة هذه.
إذا كان التطبيق سيشغّل رمزًا برمجيًا مكلفًا في مكان ما ضمن عمليته الخاصة، فليكن. وستكون الحلول البديلة التي سيجدها مطوّرو التطبيقات للتغلّب على القيود التي تفرضها أصعب بكثير من حيث إمكانية توفير الدعم على المدى الطويل.
استثناء بشأن معاودة الاتصال الفردية: عندما تتطلّب طبيعة الأحداث التي يتم الإبلاغ عنها دعم مثيل واحد فقط من معاودة الاتصال، استخدِم النمط التالي:
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 من خلال callbackOutcomeReceiver.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:() -> UnitSupplier<R>:() -> RConsumer<T>:(T) -> UnitFunction<T,R>:(T) -> RPredicate<T>:(T) -> Boolean- تتوفّر العديد من الميزات الأخرى في المستندات المرجعية
موضع مَعلمات SAM
يجب وضع مَعلمات SAM في النهاية لإتاحة الاستخدام الاصطلاحي من Kotlin، حتى إذا تم تحميل الطريقة بشكل زائد باستخدام مَعلمات إضافية.
public void schedule(Runnable runnable)
public void schedule(int delay, Runnable runnable)
مستندات
هذه هي القواعد المتعلّقة بالمستندات المتاحة للجميع (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 الرسمية على كيفية كتابة تعليقات Doc لأداة Javadoc.
قواعد خاصة بإطار عمل Android
تتعلّق هذه القواعد بواجهات برمجة التطبيقات والأنماط وبُنى البيانات الخاصة بواجهات برمجة التطبيقات والسلوكيات المضمّنة في إطار عمل Android (على سبيل المثال، Bundle أو Parcelable).
يجب أن تستخدم أدوات إنشاء الأهداف النمط create*Intent()
يجب أن يستخدم صنّاع المحتوى للأهداف طرقًا تحمل الاسم createFooIntent().
استخدام حزمة بدلاً من إنشاء بنى بيانات جديدة للأغراض العامة
تجنَّب إنشاء بنى بيانات جديدة للأغراض العامة لتمثيل عمليات ربط عشوائية بين المفتاح والقيمة المكتوبة. بدلاً من ذلك، يمكنك استخدام Bundle.
يحدث ذلك عادةً عند كتابة واجهات برمجة تطبيقات للنظام الأساسي تعمل كقنوات اتصال بين التطبيقات والخدمات غير التابعة للنظام الأساسي، حيث لا يقرأ النظام الأساسي البيانات المرسَلة عبر القناة، وقد يتم تحديد عقد واجهة برمجة التطبيقات جزئيًا خارج النظام الأساسي (على سبيل المثال، في مكتبة Jetpack).
في الحالات التي تقرأ فيها المنصة البيانات فعلاً، تجنَّب استخدام Bundle واستخدِم فئة بيانات ذات نوع محدد.
يجب أن تتضمّن عمليات تنفيذ Parcelable الحقل CREATOR العلني
يتم عرض عملية إنشاء Parcelable من خلال CREATOR، وليس من خلال طرق الإنشاء الأولية. إذا كانت الفئة تنفّذ Parcelable، يجب أن يكون الحقل CREATOR أيضًا واجهة برمجة تطبيقات عامة، ويجب أن يكون منشئ الفئة الذي يتلقّى وسيطة Parcel خاصًا.
استخدام CharSequence لسلاسل واجهة المستخدم
عند عرض سلسلة في واجهة مستخدم، استخدِم CharSequence للسماح بحالات Spannable.
إذا كان مجرد مفتاح أو تصنيف أو قيمة أخرى غير مرئية للمستخدمين،
String لا بأس بذلك.
تجنُّب استخدام التعدادات
يجب استخدام IntDef بدلاً من التعدادات في جميع واجهات برمجة التطبيقات الخاصة بالنظام الأساسي، ويجب التفكير مليًا في استخدامها في واجهات برمجة التطبيقات غير المجمّعة والمكتبات. استخدِم التعدادات فقط عندما تكون متأكّدًا من أنّه لن تتم إضافة قيم جديدة.
مزاياIntDef:
- تتيح إضافة قيم بمرور الوقت
- يمكن أن
whenتفشل عبارات Kotlin في وقت التشغيل إذا أصبحت غير شاملة بسبب إضافة قيمة تعداد في النظام الأساسي.
- يمكن أن
- لا يتم استخدام أي فئات أو عناصر في وقت التشغيل، فقط الأنواع الأساسية
- على الرغم من أنّ R8 أو التصغير يمكن أن يتجنّب هذه التكلفة لواجهات برمجة التطبيقات غير المجمَّعة، لا يمكن أن يؤثّر هذا التحسين في فئات واجهة برمجة التطبيقات الخاصة بالمنصة.
مزايا التعداد
- ميزة اللغة الاصطلاحية في Java وKotlin
- تفعيل الاستخدام الشامل لعبارة
whenswitch- ملاحظة: يجب ألّا تتغيّر القيم بمرور الوقت، راجِع القائمة السابقة
- تسمية واضحة النطاق وقابلة للاكتشاف
- تتيح هذه السمة التحقّق من صحة البيانات في وقت الترجمة البرمجية.
- على سبيل المثال، عبارة
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 لأنّه صارم جدًا في تحليل معرّفات الموارد الموحّدة (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 عددًا من المشاكل مقارنةً بوظائف الجلب/الضبط:
- لا يمكن التحقّق من صحة الأنواع.
- لا توجد طريقة موحّدة لتقديم قيمة تلقائية.
- لا توجد طريقة مناسبة لتخصيص الأذونات.
- على سبيل المثال، لا يمكن حماية إعداداتك باستخدام إذن مخصّص.
- لا توجد طريقة مناسبة لإضافة منطق مخصّص بشكل صحيح.
- على سبيل المثال، لا يمكن تغيير قيمة الإعداد 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)إلى هذا الثابت. - مستند حول الفئة التي يجب أن يضيفها المطوّر إلى
AndroidManifest.xmlكي يتمكّن من تلقّي الأهداف من المنصة.<intent-filter> - ننصحك بشدة بإضافة إذن على مستوى النظام لمنع التطبيقات الضارة من إرسال
Intentإلى خدمات المطوّرين.
التوافق بين Kotlin وJava
يمكنك الاطّلاع على دليل التوافق الرسمي بين Kotlin وJava على Android للحصول على قائمة كاملة بالإرشادات. تم نسخ إرشادات محدّدة إلى هذا الدليل لتحسين إمكانية العثور عليه.
إذن الوصول إلى واجهة برمجة التطبيقات
بعض واجهات برمجة التطبيقات في Kotlin، مثل suspend funs، غير مخصّصة للاستخدام من قِبل مطوّري 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
- الإصدار 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> في مستندات Google عند إزالة واجهات برمجة التطبيقات بشكل غير نهائي يجب أن يتضمّن الملخّص سبب الإزالة ويمكن أن يتضمّن استراتيجية نقل البيانات، كما أوضحنا في مقالة الإيقاف نهائيًا.
يمكن الحفاظ على سلوك واجهات برمجة التطبيقات التي تمت إزالتها بشكل غير نهائي كما هو، ولكن الأهم من ذلك هو وجوب الحفاظ عليه بشكل لا يؤدي إلى تعطُّل المتصلين الحاليين عند استدعاء واجهة برمجة التطبيقات. في بعض الحالات، قد يعني ذلك الحفاظ على السلوك.
يجب الحفاظ على تغطية الاختبارات ، ولكن قد يلزم تغيير محتوى الاختبارات لاستيعاب التغييرات السلوكية. يجب أن تستمر الاختبارات في التحقّق من عدم تعطُّل المتصلين الحاليين أثناء وقت التشغيل. يمكنك الحفاظ على سلوك واجهات برمجة التطبيقات التي تمت إزالتها بشكل غير نهائي كما هو، ولكن الأهم من ذلك، يجب الحفاظ على هذا السلوك بحيث لا يتعطّل المتصلون الحاليون عند طلب واجهة برمجة التطبيقات. في بعض الحالات، قد يعني ذلك الحفاظ على السلوك.
يجب الحفاظ على تغطية الاختبار، ولكن قد تحتاج إلى تغيير محتوى الاختبارات لاستيعاب التغييرات السلوكية. يجب أن تستمر الاختبارات في التحقّق من عدم تعطُّل المتصلين الحاليين أثناء وقت التشغيل.
من الناحية الفنية، نزيل واجهة برمجة التطبيقات من ملف 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 Lint.
ونتيجةً لذلك، يجب أن يولي مصمّمو واجهات برمجة التطبيقات اهتمامًا كبيرًا عند تعديل الفئات الحالية. اطرح السؤال التالي: "هل سيؤدي هذا التغيير إلى تعذُّر تشغيل الرمز الذي تمت كتابته واختباره فقط على أحدث إصدار من النظام الأساسي على الإصدارات الأقدم؟"
مخططات 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 بشكل فردي، بدلاً من تحديث صورة النظام بأكملها.
يجب "فصل" الوحدات الرئيسية عن النظام الأساسي، ما يعني أنّه يجب إجراء جميع التفاعلات بين كل وحدة وبقية العالم باستخدام واجهات برمجة التطبيقات الرسمية (العامة أو الخاصة بالنظام).
هناك أنماط تصميم معيّنة يجب أن تتّبعها الوحدات الرئيسية. يوضّح هذا القسم هذه الحالات.
نمط <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 الخاصة بخدمة النظام التي تعرضها المنصة الثابتة أو الوحدات الأخرى.
يمكن أن تستخدم الوحدات الرئيسية النمط التالي بدلاً من ذلك لتتمكّن من التسجيل والحصول على مراجع لخدمات 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.
تتطابق واجهات برمجة التطبيقات هذه مع واجهات برمجة تطبيقات Java ServiceManager، ولكن يتم عرض واجهات برمجة التطبيقات الأصلية مباشرةً لوحدات Mainline. لا تستخدِمها لتسجيل أو الإشارة إلى عناصر binder غير مملوكة للوحدة. إذا كنت تعرض كائنًا من نوع Binder من الرمز البرمجي الأصلي، لن يحتاج <YourModule>ServiceManager.ServiceRegisterer إلى طريقة register().
تعريفات الأذونات في الوحدات الرئيسية
يمكن أن تحدّد الوحدات الرئيسية التي تحتوي على حِزم APK أذونات (مخصّصة) في حزمة APK
AndroidManifest.xml بالطريقة نفسها التي يتم بها تحديد الأذونات في حزمة APK عادية.
إذا كان الإذن المحدّد يُستخدَم داخليًا فقط في إحدى الوحدات، يجب أن يكون اسم الإذن مسبوقًا باسم حزمة APK، على سبيل المثال:
<permission
android:name="com.android.permissioncontroller.permission.MANAGE_ROLES_FROM_CONTROLLER"
android:protectionLevel="signature" />
إذا كان سيتم توفير الإذن المحدّد كجزء من واجهة برمجة تطبيقات منصة قابلة للتحديث للتطبيقات الأخرى، يجب أن يكون اسم الإذن مسبوقًا بـ "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.